Home | History | Annotate | Download | only in loader
      1 /*
      2     Copyright (C) 1998 Lars Knoll (knoll (at) mpi-hd.mpg.de)
      3     Copyright (C) 2001 Dirk Mueller (mueller (at) kde.org)
      4     Copyright (C) 2002 Waldo Bastian (bastian (at) kde.org)
      5     Copyright (C) 2004, 2005, 2006, 2008 Apple Inc. All rights reserved.
      6     Copyright (C) 2009 Torch Mobile Inc. http://www.torchmobile.com/
      7 
      8     This library is free software; you can redistribute it and/or
      9     modify it under the terms of the GNU Library General Public
     10     License as published by the Free Software Foundation; either
     11     version 2 of the License, or (at your option) any later version.
     12 
     13     This library is distributed in the hope that it will be useful,
     14     but WITHOUT ANY WARRANTY; without even the implied warranty of
     15     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     16     Library General Public License for more details.
     17 
     18     You should have received a copy of the GNU Library General Public License
     19     along with this library; see the file COPYING.LIB.  If not, write to
     20     the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
     21     Boston, MA 02110-1301, USA.
     22 
     23     This class provides all functionality needed for loading images, style sheets and html
     24     pages from the web. It has a memory cache for these objects.
     25 */
     26 
     27 #include "config.h"
     28 #include "DocLoader.h"
     29 
     30 #include "Cache.h"
     31 #include "CachedCSSStyleSheet.h"
     32 #include "CachedFont.h"
     33 #include "CachedImage.h"
     34 #include "CachedScript.h"
     35 #include "CachedXSLStyleSheet.h"
     36 #include "Console.h"
     37 #include "CString.h"
     38 #include "Document.h"
     39 #include "DOMWindow.h"
     40 #include "HTMLElement.h"
     41 #include "Frame.h"
     42 #include "FrameLoader.h"
     43 #include "FrameLoaderClient.h"
     44 #include "loader.h"
     45 #include "SecurityOrigin.h"
     46 #include "Settings.h"
     47 
     48 #define PRELOAD_DEBUG 0
     49 
     50 namespace WebCore {
     51 
     52 DocLoader::DocLoader(Document* doc)
     53     : m_cache(cache())
     54     , m_doc(doc)
     55     , m_requestCount(0)
     56 #ifdef ANDROID_BLOCK_NETWORK_IMAGE
     57     , m_blockNetworkImage(false)
     58 #endif
     59     , m_autoLoadImages(true)
     60     , m_loadInProgress(false)
     61     , m_allowStaleResources(false)
     62 {
     63     m_cache->addDocLoader(this);
     64 }
     65 
     66 DocLoader::~DocLoader()
     67 {
     68     if (m_requestCount)
     69         m_cache->loader()->cancelRequests(this);
     70 
     71     clearPreloads();
     72     DocumentResourceMap::iterator end = m_documentResources.end();
     73     for (DocumentResourceMap::iterator it = m_documentResources.begin(); it != end; ++it)
     74         it->second->setDocLoader(0);
     75     m_cache->removeDocLoader(this);
     76 
     77     // Make sure no requests still point to this DocLoader
     78     ASSERT(m_requestCount == 0);
     79 }
     80 
     81 Frame* DocLoader::frame() const
     82 {
     83     return m_doc->frame();
     84 }
     85 
     86 void DocLoader::checkForReload(const KURL& fullURL)
     87 {
     88     if (m_allowStaleResources)
     89         return; // Don't reload resources while pasting
     90 
     91     if (fullURL.isEmpty())
     92         return;
     93 
     94     if (m_reloadedURLs.contains(fullURL.string()))
     95         return;
     96 
     97     CachedResource* existing = cache()->resourceForURL(fullURL.string());
     98     if (!existing || existing->isPreloaded())
     99         return;
    100 
    101     switch (cachePolicy()) {
    102     case CachePolicyVerify:
    103         if (!existing->mustRevalidate(CachePolicyVerify))
    104             return;
    105         cache()->revalidateResource(existing, this);
    106         break;
    107     case CachePolicyCache:
    108         if (!existing->mustRevalidate(CachePolicyCache))
    109             return;
    110         cache()->revalidateResource(existing, this);
    111         break;
    112     case CachePolicyReload:
    113         cache()->remove(existing);
    114         break;
    115     case CachePolicyRevalidate:
    116         cache()->revalidateResource(existing, this);
    117         break;
    118     case CachePolicyAllowStale:
    119         return;
    120     }
    121 
    122     m_reloadedURLs.add(fullURL.string());
    123 }
    124 
    125 CachedImage* DocLoader::requestImage(const String& url)
    126 {
    127     if (Frame* f = frame()) {
    128         Settings* settings = f->settings();
    129         if (!f->loader()->client()->allowImages(!settings || settings->areImagesEnabled()))
    130             return 0;
    131     }
    132     CachedImage* resource = static_cast<CachedImage*>(requestResource(CachedResource::ImageResource, url, String()));
    133     if (autoLoadImages() && resource && resource->stillNeedsLoad()) {
    134 #ifdef ANDROID_BLOCK_NETWORK_IMAGE
    135         if (shouldBlockNetworkImage(url)) {
    136             return resource;
    137         }
    138 #endif
    139         resource->setLoading(true);
    140         cache()->loader()->load(this, resource, true);
    141     }
    142     return resource;
    143 }
    144 
    145 CachedFont* DocLoader::requestFont(const String& url)
    146 {
    147     return static_cast<CachedFont*>(requestResource(CachedResource::FontResource, url, String()));
    148 }
    149 
    150 CachedCSSStyleSheet* DocLoader::requestCSSStyleSheet(const String& url, const String& charset)
    151 {
    152     return static_cast<CachedCSSStyleSheet*>(requestResource(CachedResource::CSSStyleSheet, url, charset));
    153 }
    154 
    155 CachedCSSStyleSheet* DocLoader::requestUserCSSStyleSheet(const String& url, const String& charset)
    156 {
    157     return cache()->requestUserCSSStyleSheet(this, url, charset);
    158 }
    159 
    160 CachedScript* DocLoader::requestScript(const String& url, const String& charset)
    161 {
    162     return static_cast<CachedScript*>(requestResource(CachedResource::Script, url, charset));
    163 }
    164 
    165 #if ENABLE(XSLT)
    166 CachedXSLStyleSheet* DocLoader::requestXSLStyleSheet(const String& url)
    167 {
    168     return static_cast<CachedXSLStyleSheet*>(requestResource(CachedResource::XSLStyleSheet, url, String()));
    169 }
    170 #endif
    171 
    172 #if ENABLE(XBL)
    173 CachedXBLDocument* DocLoader::requestXBLDocument(const String& url)
    174 {
    175     return static_cast<CachedXSLStyleSheet*>(requestResource(CachedResource::XBL, url, String()));
    176 }
    177 #endif
    178 
    179 bool DocLoader::canRequest(CachedResource::Type type, const KURL& url)
    180 {
    181     // Some types of resources can be loaded only from the same origin.  Other
    182     // types of resources, like Images, Scripts, and CSS, can be loaded from
    183     // any URL.
    184     switch (type) {
    185     case CachedResource::ImageResource:
    186     case CachedResource::CSSStyleSheet:
    187     case CachedResource::Script:
    188     case CachedResource::FontResource:
    189         // These types of resources can be loaded from any origin.
    190         // FIXME: Are we sure about CachedResource::FontResource?
    191         break;
    192 #if ENABLE(XSLT)
    193     case CachedResource::XSLStyleSheet:
    194 #endif
    195 #if ENABLE(XBL)
    196     case CachedResource::XBL:
    197 #endif
    198 #if ENABLE(XSLT) || ENABLE(XBL)
    199         if (!m_doc->securityOrigin()->canRequest(url)) {
    200             printAccessDeniedMessage(url);
    201             return false;
    202         }
    203         break;
    204 #endif
    205     default:
    206         ASSERT_NOT_REACHED();
    207         break;
    208     }
    209 
    210     // Given that the load is allowed by the same-origin policy, we should
    211     // check whether the load passes the mixed-content policy.
    212     //
    213     // Note: Currently, we always allow mixed content, but we generate a
    214     //       callback to the FrameLoaderClient in case the embedder wants to
    215     //       update any security indicators.
    216     //
    217     switch (type) {
    218     case CachedResource::Script:
    219 #if ENABLE(XSLT)
    220     case CachedResource::XSLStyleSheet:
    221 #endif
    222 #if ENABLE(XBL)
    223     case CachedResource::XBL:
    224 #endif
    225         // These resource can inject script into the current document.
    226         if (Frame* f = frame())
    227             f->loader()->checkIfRunInsecureContent(m_doc->securityOrigin(), url);
    228         break;
    229     case CachedResource::ImageResource:
    230     case CachedResource::CSSStyleSheet:
    231     case CachedResource::FontResource: {
    232         // These resources can corrupt only the frame's pixels.
    233         if (Frame* f = frame()) {
    234             Frame* top = f->tree()->top();
    235             top->loader()->checkIfDisplayInsecureContent(top->document()->securityOrigin(), url);
    236         }
    237         break;
    238     }
    239     default:
    240         ASSERT_NOT_REACHED();
    241         break;
    242     }
    243     // FIXME: Consider letting the embedder block mixed content loads.
    244     return true;
    245 }
    246 
    247 CachedResource* DocLoader::requestResource(CachedResource::Type type, const String& url, const String& charset, bool isPreload)
    248 {
    249     KURL fullURL = m_doc->completeURL(url);
    250 
    251     if (!fullURL.isValid() || !canRequest(type, fullURL))
    252         return 0;
    253 
    254     if (cache()->disabled()) {
    255         DocumentResourceMap::iterator it = m_documentResources.find(fullURL.string());
    256 
    257         if (it != m_documentResources.end()) {
    258             it->second->setDocLoader(0);
    259             m_documentResources.remove(it);
    260         }
    261     }
    262 
    263     checkForReload(fullURL);
    264 
    265     CachedResource* resource = cache()->requestResource(this, type, fullURL, charset, isPreload);
    266     if (resource) {
    267         // Check final URL of resource to catch redirects.
    268         // See <https://bugs.webkit.org/show_bug.cgi?id=21963>.
    269         if (fullURL != resource->url() && !canRequest(type, KURL(ParsedURLString, resource->url())))
    270             return 0;
    271 
    272         m_documentResources.set(resource->url(), resource);
    273         checkCacheObjectStatus(resource);
    274     }
    275     return resource;
    276 }
    277 
    278 void DocLoader::printAccessDeniedMessage(const KURL& url) const
    279 {
    280     if (url.isNull())
    281         return;
    282 
    283     if (!frame())
    284         return;
    285 
    286     Settings* settings = frame()->settings();
    287     if (!settings || settings->privateBrowsingEnabled())
    288         return;
    289 
    290     String message = m_doc->url().isNull() ?
    291         String::format("Unsafe attempt to load URL %s.",
    292                        url.string().utf8().data()) :
    293         String::format("Unsafe attempt to load URL %s from frame with URL %s. "
    294                        "Domains, protocols and ports must match.\n",
    295                        url.string().utf8().data(),
    296                        m_doc->url().string().utf8().data());
    297 
    298     // FIXME: provide a real line number and source URL.
    299     frame()->domWindow()->console()->addMessage(OtherMessageSource, LogMessageType, ErrorMessageLevel, message, 1, String());
    300 }
    301 
    302 void DocLoader::setAutoLoadImages(bool enable)
    303 {
    304     if (enable == m_autoLoadImages)
    305         return;
    306 
    307     m_autoLoadImages = enable;
    308 
    309     if (!m_autoLoadImages)
    310         return;
    311 
    312     DocumentResourceMap::iterator end = m_documentResources.end();
    313     for (DocumentResourceMap::iterator it = m_documentResources.begin(); it != end; ++it) {
    314         CachedResource* resource = it->second.get();
    315         if (resource->type() == CachedResource::ImageResource) {
    316             CachedImage* image = const_cast<CachedImage*>(static_cast<const CachedImage*>(resource));
    317 #ifdef ANDROID_BLOCK_NETWORK_IMAGE
    318             if (shouldBlockNetworkImage(image->url()))
    319                 continue;
    320 #endif
    321 
    322             if (image->stillNeedsLoad())
    323                 cache()->loader()->load(this, image, true);
    324         }
    325     }
    326 }
    327 
    328 #ifdef ANDROID_BLOCK_NETWORK_IMAGE
    329 bool DocLoader::shouldBlockNetworkImage(const String& url) const
    330 {
    331     if (!m_blockNetworkImage)
    332         return false;
    333 
    334     KURL kurl = m_doc->completeURL(url);
    335     if (kurl.protocolIs("http") || kurl.protocolIs("https"))
    336         return true;
    337 
    338     return false;
    339 }
    340 
    341 void DocLoader::setBlockNetworkImage(bool block)
    342 {
    343     if (block == m_blockNetworkImage)
    344         return;
    345 
    346     m_blockNetworkImage = block;
    347 
    348     if (!m_autoLoadImages || m_blockNetworkImage)
    349         return;
    350 
    351     DocumentResourceMap::iterator end = m_documentResources.end();
    352     for (DocumentResourceMap::iterator it = m_documentResources.begin(); it != end; ++it) {
    353         CachedResource* resource = it->second.get();
    354         if (resource->type() == CachedResource::ImageResource) {
    355             CachedImage* image = const_cast<CachedImage*>(static_cast<const CachedImage*>(resource));
    356             if (image->stillNeedsLoad())
    357                 cache()->loader()->load(this, image, true);
    358         }
    359     }
    360 }
    361 #endif
    362 
    363 CachePolicy DocLoader::cachePolicy() const
    364 {
    365     return frame() ? frame()->loader()->subresourceCachePolicy() : CachePolicyVerify;
    366 }
    367 
    368 void DocLoader::removeCachedResource(CachedResource* resource) const
    369 {
    370 #ifndef NDEBUG
    371     DocumentResourceMap::iterator it = m_documentResources.find(resource->url());
    372     if (it != m_documentResources.end())
    373         ASSERT(it->second.get() == resource);
    374 #endif
    375     m_documentResources.remove(resource->url());
    376 }
    377 
    378 void DocLoader::setLoadInProgress(bool load)
    379 {
    380     m_loadInProgress = load;
    381     if (!load && frame())
    382         frame()->loader()->loadDone();
    383 }
    384 
    385 void DocLoader::checkCacheObjectStatus(CachedResource* resource)
    386 {
    387     // Return from the function for objects that we didn't load from the cache or if we don't have a frame.
    388     if (!resource || !frame())
    389         return;
    390 
    391     switch (resource->status()) {
    392         case CachedResource::Cached:
    393             break;
    394         case CachedResource::NotCached:
    395         case CachedResource::Unknown:
    396         case CachedResource::New:
    397         case CachedResource::Pending:
    398             return;
    399     }
    400 
    401     // FIXME: If the WebKit client changes or cancels the request, WebCore does not respect this and continues the load.
    402     frame()->loader()->loadedResourceFromMemoryCache(resource);
    403 }
    404 
    405 void DocLoader::incrementRequestCount()
    406 {
    407     ++m_requestCount;
    408 }
    409 
    410 void DocLoader::decrementRequestCount()
    411 {
    412     --m_requestCount;
    413     ASSERT(m_requestCount > -1);
    414 }
    415 
    416 int DocLoader::requestCount()
    417 {
    418     if (loadInProgress())
    419          return m_requestCount + 1;
    420     return m_requestCount;
    421 }
    422 
    423 void DocLoader::preload(CachedResource::Type type, const String& url, const String& charset, bool referencedFromBody)
    424 {
    425     bool hasRendering = m_doc->body() && m_doc->body()->renderer();
    426     if (!hasRendering && (referencedFromBody || type == CachedResource::ImageResource)) {
    427         // Don't preload images or body resources before we have something to draw. This prevents
    428         // preloads from body delaying first display when bandwidth is limited.
    429         PendingPreload pendingPreload = { type, url, charset };
    430         m_pendingPreloads.append(pendingPreload);
    431         return;
    432     }
    433     requestPreload(type, url, charset);
    434 }
    435 
    436 void DocLoader::checkForPendingPreloads()
    437 {
    438     unsigned count = m_pendingPreloads.size();
    439     if (!count || !m_doc->body() || !m_doc->body()->renderer())
    440         return;
    441     for (unsigned i = 0; i < count; ++i) {
    442         PendingPreload& preload = m_pendingPreloads[i];
    443         // Don't request preload if the resource already loaded normally (this will result in double load if the page is being reloaded with cached results ignored).
    444         if (!cachedResource(m_doc->completeURL(preload.m_url)))
    445             requestPreload(preload.m_type, preload.m_url, preload.m_charset);
    446     }
    447     m_pendingPreloads.clear();
    448 }
    449 
    450 void DocLoader::requestPreload(CachedResource::Type type, const String& url, const String& charset)
    451 {
    452     String encoding;
    453     if (type == CachedResource::Script || type == CachedResource::CSSStyleSheet)
    454         encoding = charset.isEmpty() ? m_doc->frame()->loader()->encoding() : charset;
    455 
    456     CachedResource* resource = requestResource(type, url, encoding, true);
    457     if (!resource || m_preloads.contains(resource))
    458         return;
    459     resource->increasePreloadCount();
    460     m_preloads.add(resource);
    461 #if PRELOAD_DEBUG
    462     printf("PRELOADING %s\n",  resource->url().latin1().data());
    463 #endif
    464 }
    465 
    466 void DocLoader::clearPreloads()
    467 {
    468 #if PRELOAD_DEBUG
    469     printPreloadStats();
    470 #endif
    471     ListHashSet<CachedResource*>::iterator end = m_preloads.end();
    472     for (ListHashSet<CachedResource*>::iterator it = m_preloads.begin(); it != end; ++it) {
    473         CachedResource* res = *it;
    474         res->decreasePreloadCount();
    475         if (res->canDelete() && !res->inCache())
    476             delete res;
    477         else if (res->preloadResult() == CachedResource::PreloadNotReferenced)
    478             cache()->remove(res);
    479     }
    480     m_preloads.clear();
    481 }
    482 
    483 void DocLoader::clearPendingPreloads()
    484 {
    485     m_pendingPreloads.clear();
    486 }
    487 
    488 #if PRELOAD_DEBUG
    489 void DocLoader::printPreloadStats()
    490 {
    491     unsigned scripts = 0;
    492     unsigned scriptMisses = 0;
    493     unsigned stylesheets = 0;
    494     unsigned stylesheetMisses = 0;
    495     unsigned images = 0;
    496     unsigned imageMisses = 0;
    497     ListHashSet<CachedResource*>::iterator end = m_preloads.end();
    498     for (ListHashSet<CachedResource*>::iterator it = m_preloads.begin(); it != end; ++it) {
    499         CachedResource* res = *it;
    500         if (res->preloadResult() == CachedResource::PreloadNotReferenced)
    501             printf("!! UNREFERENCED PRELOAD %s\n", res->url().latin1().data());
    502         else if (res->preloadResult() == CachedResource::PreloadReferencedWhileComplete)
    503             printf("HIT COMPLETE PRELOAD %s\n", res->url().latin1().data());
    504         else if (res->preloadResult() == CachedResource::PreloadReferencedWhileLoading)
    505             printf("HIT LOADING PRELOAD %s\n", res->url().latin1().data());
    506 
    507         if (res->type() == CachedResource::Script) {
    508             scripts++;
    509             if (res->preloadResult() < CachedResource::PreloadReferencedWhileLoading)
    510                 scriptMisses++;
    511         } else if (res->type() == CachedResource::CSSStyleSheet) {
    512             stylesheets++;
    513             if (res->preloadResult() < CachedResource::PreloadReferencedWhileLoading)
    514                 stylesheetMisses++;
    515         } else {
    516             images++;
    517             if (res->preloadResult() < CachedResource::PreloadReferencedWhileLoading)
    518                 imageMisses++;
    519         }
    520 
    521         if (res->errorOccurred())
    522             cache()->remove(res);
    523 
    524         res->decreasePreloadCount();
    525     }
    526     m_preloads.clear();
    527 
    528     if (scripts)
    529         printf("SCRIPTS: %d (%d hits, hit rate %d%%)\n", scripts, scripts - scriptMisses, (scripts - scriptMisses) * 100 / scripts);
    530     if (stylesheets)
    531         printf("STYLESHEETS: %d (%d hits, hit rate %d%%)\n", stylesheets, stylesheets - stylesheetMisses, (stylesheets - stylesheetMisses) * 100 / stylesheets);
    532     if (images)
    533         printf("IMAGES:  %d (%d hits, hit rate %d%%)\n", images, images - imageMisses, (images - imageMisses) * 100 / images);
    534 }
    535 #endif
    536 
    537 }
    538