diff -r 000000000000 -r 4f2f89ce4247 WebCore/loader/DocLoader.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WebCore/loader/DocLoader.cpp Fri Sep 17 09:02:29 2010 +0300 @@ -0,0 +1,518 @@ +/* + Copyright (C) 1998 Lars Knoll (knoll@mpi-hd.mpg.de) + Copyright (C) 2001 Dirk Mueller (mueller@kde.org) + Copyright (C) 2002 Waldo Bastian (bastian@kde.org) + Copyright (C) 2004, 2005, 2006, 2008 Apple Inc. All rights reserved. + Copyright (C) 2009 Torch Mobile Inc. http://www.torchmobile.com/ + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. + + This class provides all functionality needed for loading images, style sheets and html + pages from the web. It has a memory cache for these objects. +*/ + +#include "config.h" +#include "DocLoader.h" + +#include "Cache.h" +#include "CachedCSSStyleSheet.h" +#include "CachedFont.h" +#include "CachedImage.h" +#include "CachedScript.h" +#include "CachedXSLStyleSheet.h" +#include "Console.h" +#include "Document.h" +#include "DOMWindow.h" +#include "HTMLElement.h" +#include "Frame.h" +#include "FrameLoader.h" +#include "FrameLoaderClient.h" +#include "loader.h" +#include "SecurityOrigin.h" +#include "Settings.h" +#include + +#define PRELOAD_DEBUG 0 + +namespace WebCore { + +DocLoader::DocLoader(Document* doc) + : m_cache(cache()) + , m_doc(doc) + , m_requestCount(0) + , m_autoLoadImages(true) + , m_loadInProgress(false) + , m_allowStaleResources(false) +{ + m_cache->addDocLoader(this); +} + +DocLoader::~DocLoader() +{ + if (m_requestCount) + m_cache->loader()->cancelRequests(this); + + clearPreloads(); + DocumentResourceMap::iterator end = m_documentResources.end(); + for (DocumentResourceMap::iterator it = m_documentResources.begin(); it != end; ++it) + it->second->setDocLoader(0); + m_cache->removeDocLoader(this); + + // Make sure no requests still point to this DocLoader + ASSERT(m_requestCount == 0); +} + +Frame* DocLoader::frame() const +{ + return m_doc->frame(); +} + +void DocLoader::checkForReload(const KURL& fullURL) +{ + if (m_allowStaleResources) + return; // Don't reload resources while pasting + + if (fullURL.isEmpty()) + return; + + if (m_reloadedURLs.contains(fullURL.string())) + return; + + CachedResource* existing = cache()->resourceForURL(fullURL.string()); + if (!existing || existing->isPreloaded()) + return; + + switch (cachePolicy()) { + case CachePolicyVerify: + if (!existing->mustRevalidate(CachePolicyVerify)) + return; + cache()->revalidateResource(existing, this); + break; + case CachePolicyCache: + if (!existing->mustRevalidate(CachePolicyCache)) + return; + cache()->revalidateResource(existing, this); + break; + case CachePolicyReload: + cache()->remove(existing); + break; + case CachePolicyRevalidate: + cache()->revalidateResource(existing, this); + break; + case CachePolicyAllowStale: + return; + } + + m_reloadedURLs.add(fullURL.string()); +} + +CachedImage* DocLoader::requestImage(const String& url) +{ + if (Frame* f = frame()) { + Settings* settings = f->settings(); + if (!f->loader()->client()->allowImages(!settings || settings->areImagesEnabled())) + return 0; + } + CachedImage* resource = static_cast(requestResource(CachedResource::ImageResource, url, String())); + if (autoLoadImages() && resource && resource->stillNeedsLoad()) { + resource->setLoading(true); + cache()->loader()->load(this, resource, true); + } + return resource; +} + +CachedFont* DocLoader::requestFont(const String& url) +{ + return static_cast(requestResource(CachedResource::FontResource, url, String())); +} + +CachedCSSStyleSheet* DocLoader::requestCSSStyleSheet(const String& url, const String& charset) +{ + return static_cast(requestResource(CachedResource::CSSStyleSheet, url, charset)); +} + +CachedCSSStyleSheet* DocLoader::requestUserCSSStyleSheet(const String& url, const String& charset) +{ + return cache()->requestUserCSSStyleSheet(this, url, charset); +} + +CachedScript* DocLoader::requestScript(const String& url, const String& charset) +{ + return static_cast(requestResource(CachedResource::Script, url, charset)); +} + +#if ENABLE(XSLT) +CachedXSLStyleSheet* DocLoader::requestXSLStyleSheet(const String& url) +{ + return static_cast(requestResource(CachedResource::XSLStyleSheet, url, String())); +} +#endif + +#if ENABLE(XBL) +CachedXBLDocument* DocLoader::requestXBLDocument(const String& url) +{ + return static_cast(requestResource(CachedResource::XBL, url, String())); +} +#endif + +#if ENABLE(LINK_PREFETCH) +CachedResource* DocLoader::requestLinkPrefetch(const String& url) +{ + return requestResource(CachedResource::LinkPrefetch, url, String()); +} +#endif + +bool DocLoader::canRequest(CachedResource::Type type, const KURL& url) +{ + // Some types of resources can be loaded only from the same origin. Other + // types of resources, like Images, Scripts, and CSS, can be loaded from + // any URL. + switch (type) { + case CachedResource::ImageResource: + case CachedResource::CSSStyleSheet: + case CachedResource::Script: + case CachedResource::FontResource: +#if ENABLE(LINK_PREFETCH) + case CachedResource::LinkPrefetch: +#endif + // These types of resources can be loaded from any origin. + // FIXME: Are we sure about CachedResource::FontResource? + break; +#if ENABLE(XSLT) + case CachedResource::XSLStyleSheet: +#endif +#if ENABLE(XBL) + case CachedResource::XBL: +#endif +#if ENABLE(XSLT) || ENABLE(XBL) + if (!m_doc->securityOrigin()->canRequest(url)) { + printAccessDeniedMessage(url); + return false; + } + break; +#endif + default: + ASSERT_NOT_REACHED(); + break; + } + + // Given that the load is allowed by the same-origin policy, we should + // check whether the load passes the mixed-content policy. + // + // Note: Currently, we always allow mixed content, but we generate a + // callback to the FrameLoaderClient in case the embedder wants to + // update any security indicators. + // + switch (type) { + case CachedResource::Script: +#if ENABLE(XSLT) + case CachedResource::XSLStyleSheet: +#endif +#if ENABLE(XBL) + case CachedResource::XBL: +#endif + // These resource can inject script into the current document. + if (Frame* f = frame()) + f->loader()->checkIfRunInsecureContent(m_doc->securityOrigin(), url); + break; + case CachedResource::ImageResource: + case CachedResource::CSSStyleSheet: + case CachedResource::FontResource: { + // These resources can corrupt only the frame's pixels. + if (Frame* f = frame()) { + Frame* top = f->tree()->top(); + top->loader()->checkIfDisplayInsecureContent(top->document()->securityOrigin(), url); + } + break; + } +#if ENABLE(LINK_PREFETCH) + case CachedResource::LinkPrefetch: + // Prefetch cannot affect the current document. + break; +#endif + default: + ASSERT_NOT_REACHED(); + break; + } + // FIXME: Consider letting the embedder block mixed content loads. + return true; +} + +CachedResource* DocLoader::requestResource(CachedResource::Type type, const String& url, const String& charset, bool isPreload) +{ + KURL fullURL = m_doc->completeURL(url); + + if (!fullURL.isValid() || !canRequest(type, fullURL)) + return 0; + + if (cache()->disabled()) { + DocumentResourceMap::iterator it = m_documentResources.find(fullURL.string()); + + if (it != m_documentResources.end()) { + it->second->setDocLoader(0); + m_documentResources.remove(it); + } + } + + checkForReload(fullURL); + + CachedResource* resource = cache()->requestResource(this, type, fullURL, charset, isPreload); + if (resource) { + // Check final URL of resource to catch redirects. + // See . + if (fullURL != resource->url() && !canRequest(type, KURL(ParsedURLString, resource->url()))) + return 0; + + m_documentResources.set(resource->url(), resource); + checkCacheObjectStatus(resource); + } + return resource; +} + +void DocLoader::printAccessDeniedMessage(const KURL& url) const +{ + if (url.isNull()) + return; + + if (!frame()) + return; + + Settings* settings = frame()->settings(); + if (!settings || settings->privateBrowsingEnabled()) + return; + + String message = m_doc->url().isNull() ? + String::format("Unsafe attempt to load URL %s.", + url.string().utf8().data()) : + String::format("Unsafe attempt to load URL %s from frame with URL %s. " + "Domains, protocols and ports must match.\n", + url.string().utf8().data(), + m_doc->url().string().utf8().data()); + + // FIXME: provide a real line number and source URL. + frame()->domWindow()->console()->addMessage(OtherMessageSource, LogMessageType, ErrorMessageLevel, message, 1, String()); +} + +void DocLoader::setAutoLoadImages(bool enable) +{ + if (enable == m_autoLoadImages) + return; + + m_autoLoadImages = enable; + + if (!m_autoLoadImages) + return; + + DocumentResourceMap::iterator end = m_documentResources.end(); + for (DocumentResourceMap::iterator it = m_documentResources.begin(); it != end; ++it) { + CachedResource* resource = it->second.get(); + if (resource->type() == CachedResource::ImageResource) { + CachedImage* image = const_cast(static_cast(resource)); + + if (image->stillNeedsLoad()) + cache()->loader()->load(this, image, true); + } + } +} + +CachePolicy DocLoader::cachePolicy() const +{ + return frame() ? frame()->loader()->subresourceCachePolicy() : CachePolicyVerify; +} + +void DocLoader::removeCachedResource(CachedResource* resource) const +{ +#ifndef NDEBUG + DocumentResourceMap::iterator it = m_documentResources.find(resource->url()); + if (it != m_documentResources.end()) + ASSERT(it->second.get() == resource); +#endif + m_documentResources.remove(resource->url()); +} + +void DocLoader::setLoadInProgress(bool load) +{ + m_loadInProgress = load; + if (!load && frame()) + frame()->loader()->loadDone(); +} + +void DocLoader::checkCacheObjectStatus(CachedResource* resource) +{ + // Return from the function for objects that we didn't load from the cache or if we don't have a frame. + if (!resource || !frame()) + return; + + switch (resource->status()) { + case CachedResource::Cached: + break; + case CachedResource::NotCached: + case CachedResource::Unknown: + case CachedResource::New: + case CachedResource::Pending: + return; + } + + // FIXME: If the WebKit client changes or cancels the request, WebCore does not respect this and continues the load. + frame()->loader()->loadedResourceFromMemoryCache(resource); +} + +void DocLoader::incrementRequestCount(const CachedResource* res) +{ + if (res->isPrefetch()) + return; + + ++m_requestCount; +} + +void DocLoader::decrementRequestCount(const CachedResource* res) +{ + if (res->isPrefetch()) + return; + + --m_requestCount; + ASSERT(m_requestCount > -1); +} + +int DocLoader::requestCount() +{ + if (loadInProgress()) + return m_requestCount + 1; + return m_requestCount; +} + +void DocLoader::preload(CachedResource::Type type, const String& url, const String& charset, bool referencedFromBody) +{ + bool hasRendering = m_doc->body() && m_doc->body()->renderer(); + if (!hasRendering && (referencedFromBody || type == CachedResource::ImageResource)) { + // Don't preload images or body resources before we have something to draw. This prevents + // preloads from body delaying first display when bandwidth is limited. + PendingPreload pendingPreload = { type, url, charset }; + m_pendingPreloads.append(pendingPreload); + return; + } + requestPreload(type, url, charset); +} + +void DocLoader::checkForPendingPreloads() +{ + unsigned count = m_pendingPreloads.size(); + if (!count || !m_doc->body() || !m_doc->body()->renderer()) + return; + for (unsigned i = 0; i < count; ++i) { + PendingPreload& preload = m_pendingPreloads[i]; + // 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). + if (!cachedResource(m_doc->completeURL(preload.m_url))) + requestPreload(preload.m_type, preload.m_url, preload.m_charset); + } + m_pendingPreloads.clear(); +} + +void DocLoader::requestPreload(CachedResource::Type type, const String& url, const String& charset) +{ + String encoding; + if (type == CachedResource::Script || type == CachedResource::CSSStyleSheet) + encoding = charset.isEmpty() ? m_doc->frame()->loader()->writer()->encoding() : charset; + + CachedResource* resource = requestResource(type, url, encoding, true); + if (!resource || (m_preloads && m_preloads->contains(resource))) + return; + resource->increasePreloadCount(); + + if (!m_preloads) + m_preloads.set(new ListHashSet); + m_preloads->add(resource); + +#if PRELOAD_DEBUG + printf("PRELOADING %s\n", resource->url().latin1().data()); +#endif +} + +void DocLoader::clearPreloads() +{ +#if PRELOAD_DEBUG + printPreloadStats(); +#endif + if (!m_preloads) + return; + + ListHashSet::iterator end = m_preloads->end(); + for (ListHashSet::iterator it = m_preloads->begin(); it != end; ++it) { + CachedResource* res = *it; + res->decreasePreloadCount(); + if (res->canDelete() && !res->inCache()) + delete res; + else if (res->preloadResult() == CachedResource::PreloadNotReferenced) + cache()->remove(res); + } + m_preloads.clear(); +} + +void DocLoader::clearPendingPreloads() +{ + m_pendingPreloads.clear(); +} + +#if PRELOAD_DEBUG +void DocLoader::printPreloadStats() +{ + unsigned scripts = 0; + unsigned scriptMisses = 0; + unsigned stylesheets = 0; + unsigned stylesheetMisses = 0; + unsigned images = 0; + unsigned imageMisses = 0; + ListHashSet::iterator end = m_preloads.end(); + for (ListHashSet::iterator it = m_preloads.begin(); it != end; ++it) { + CachedResource* res = *it; + if (res->preloadResult() == CachedResource::PreloadNotReferenced) + printf("!! UNREFERENCED PRELOAD %s\n", res->url().latin1().data()); + else if (res->preloadResult() == CachedResource::PreloadReferencedWhileComplete) + printf("HIT COMPLETE PRELOAD %s\n", res->url().latin1().data()); + else if (res->preloadResult() == CachedResource::PreloadReferencedWhileLoading) + printf("HIT LOADING PRELOAD %s\n", res->url().latin1().data()); + + if (res->type() == CachedResource::Script) { + scripts++; + if (res->preloadResult() < CachedResource::PreloadReferencedWhileLoading) + scriptMisses++; + } else if (res->type() == CachedResource::CSSStyleSheet) { + stylesheets++; + if (res->preloadResult() < CachedResource::PreloadReferencedWhileLoading) + stylesheetMisses++; + } else { + images++; + if (res->preloadResult() < CachedResource::PreloadReferencedWhileLoading) + imageMisses++; + } + + if (res->errorOccurred()) + cache()->remove(res); + + res->decreasePreloadCount(); + } + m_preloads.clear(); + + if (scripts) + printf("SCRIPTS: %d (%d hits, hit rate %d%%)\n", scripts, scripts - scriptMisses, (scripts - scriptMisses) * 100 / scripts); + if (stylesheets) + printf("STYLESHEETS: %d (%d hits, hit rate %d%%)\n", stylesheets, stylesheets - stylesheetMisses, (stylesheets - stylesheetMisses) * 100 / stylesheets); + if (images) + printf("IMAGES: %d (%d hits, hit rate %d%%)\n", images, images - imageMisses, (images - imageMisses) * 100 / images); +} +#endif + +}