diff options
Diffstat (limited to 'Source/WebCore/loader/cache')
24 files changed, 4753 insertions, 0 deletions
diff --git a/Source/WebCore/loader/cache/CachePolicy.h b/Source/WebCore/loader/cache/CachePolicy.h new file mode 100644 index 0000000..0b9010b --- /dev/null +++ b/Source/WebCore/loader/cache/CachePolicy.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2003, 2006 Apple Computer, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef CachePolicy_h +#define CachePolicy_h + +namespace WebCore { + + enum CachePolicy { + CachePolicyCache, + CachePolicyVerify, + CachePolicyRevalidate, + CachePolicyReload, + CachePolicyHistoryBuffer + }; + +} + +#endif diff --git a/Source/WebCore/loader/cache/CachedCSSStyleSheet.cpp b/Source/WebCore/loader/cache/CachedCSSStyleSheet.cpp new file mode 100644 index 0000000..ae7a03c --- /dev/null +++ b/Source/WebCore/loader/cache/CachedCSSStyleSheet.cpp @@ -0,0 +1,151 @@ +/* + 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) 2006 Samuel Weinig (sam.weinig@gmail.com) + Copyright (C) 2004, 2005, 2006 Apple Computer, Inc. + + 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 "CachedCSSStyleSheet.h" + +#include "MemoryCache.h" +#include "CachedResourceClient.h" +#include "CachedResourceClientWalker.h" +#include "HTTPParsers.h" +#include "TextResourceDecoder.h" +#include "SharedBuffer.h" +#include <wtf/Vector.h> + +namespace WebCore { + +CachedCSSStyleSheet::CachedCSSStyleSheet(const String& url, const String& charset) + : CachedResource(url, CSSStyleSheet) + , m_decoder(TextResourceDecoder::create("text/css", charset)) +{ + // Prefer text/css but accept any type (dell.com serves a stylesheet + // as text/html; see <http://bugs.webkit.org/show_bug.cgi?id=11451>). + setAccept("text/css,*/*;q=0.1"); +} + +CachedCSSStyleSheet::~CachedCSSStyleSheet() +{ +} + +void CachedCSSStyleSheet::didAddClient(CachedResourceClient *c) +{ + if (!isLoading()) + c->setCSSStyleSheet(m_url, m_response.url(), m_decoder->encoding().name(), this); +} + +void CachedCSSStyleSheet::allClientsRemoved() +{ + if (!MemoryCache::shouldMakeResourcePurgeableOnEviction() && isSafeToMakePurgeable()) + makePurgeable(true); +} + +void CachedCSSStyleSheet::setEncoding(const String& chs) +{ + m_decoder->setEncoding(chs, TextResourceDecoder::EncodingFromHTTPHeader); +} + +String CachedCSSStyleSheet::encoding() const +{ + return m_decoder->encoding().name(); +} + +const String CachedCSSStyleSheet::sheetText(bool enforceMIMEType, bool* hasValidMIMEType) const +{ + ASSERT(!isPurgeable()); + + if (!m_data || m_data->isEmpty() || !canUseSheet(enforceMIMEType, hasValidMIMEType)) + return String(); + + if (!m_decodedSheetText.isNull()) + return m_decodedSheetText; + + // Don't cache the decoded text, regenerating is cheap and it can use quite a bit of memory + String sheetText = m_decoder->decode(m_data->data(), m_data->size()); + sheetText += m_decoder->flush(); + return sheetText; +} + +void CachedCSSStyleSheet::data(PassRefPtr<SharedBuffer> data, bool allDataReceived) +{ + if (!allDataReceived) + return; + + m_data = data; + setEncodedSize(m_data.get() ? m_data->size() : 0); + // Decode the data to find out the encoding and keep the sheet text around during checkNotify() + if (m_data) { + m_decodedSheetText = m_decoder->decode(m_data->data(), m_data->size()); + m_decodedSheetText += m_decoder->flush(); + } + setLoading(false); + checkNotify(); + // Clear the decoded text as it is unlikely to be needed immediately again and is cheap to regenerate. + m_decodedSheetText = String(); +} + +void CachedCSSStyleSheet::checkNotify() +{ + if (isLoading()) + return; + + CachedResourceClientWalker w(m_clients); + while (CachedResourceClient *c = w.next()) + c->setCSSStyleSheet(m_url, m_response.url(), m_decoder->encoding().name(), this); +} + +void CachedCSSStyleSheet::error(CachedResource::Status status) +{ + setStatus(status); + ASSERT(errorOccurred()); + setLoading(false); + checkNotify(); +} + +bool CachedCSSStyleSheet::canUseSheet(bool enforceMIMEType, bool* hasValidMIMEType) const +{ + if (errorOccurred()) + return false; + + if (!enforceMIMEType && !hasValidMIMEType) + return true; + + // This check exactly matches Firefox. Note that we grab the Content-Type + // header directly because we want to see what the value is BEFORE content + // sniffing. Firefox does this by setting a "type hint" on the channel. + // This implementation should be observationally equivalent. + // + // This code defaults to allowing the stylesheet for non-HTTP protocols so + // folks can use standards mode for local HTML documents. + String mimeType = extractMIMETypeFromMediaType(response().httpHeaderField("Content-Type")); + bool typeOK = mimeType.isEmpty() || equalIgnoringCase(mimeType, "text/css") || equalIgnoringCase(mimeType, "application/x-unknown-content-type"); + if (hasValidMIMEType) + *hasValidMIMEType = typeOK; + if (!enforceMIMEType) + return true; + return typeOK; +} + +} diff --git a/Source/WebCore/loader/cache/CachedCSSStyleSheet.h b/Source/WebCore/loader/cache/CachedCSSStyleSheet.h new file mode 100644 index 0000000..a982e03 --- /dev/null +++ b/Source/WebCore/loader/cache/CachedCSSStyleSheet.h @@ -0,0 +1,67 @@ +/* + Copyright (C) 1998 Lars Knoll (knoll@mpi-hd.mpg.de) + Copyright (C) 2001 Dirk Mueller <mueller@kde.org> + Copyright (C) 2006 Samuel Weinig (sam.weinig@gmail.com) + Copyright (C) 2004, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved. + + 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. +*/ + +#ifndef CachedCSSStyleSheet_h +#define CachedCSSStyleSheet_h + +#include "CachedResource.h" +#include "TextEncoding.h" +#include <wtf/Vector.h> + +namespace WebCore { + + class CachedResourceLoader; + class TextResourceDecoder; + + class CachedCSSStyleSheet : public CachedResource { + public: + CachedCSSStyleSheet(const String& URL, const String& charset); + virtual ~CachedCSSStyleSheet(); + + const String sheetText(bool enforceMIMEType = true, bool* hasValidMIMEType = 0) const; + + virtual void didAddClient(CachedResourceClient*); + + virtual void allClientsRemoved(); + + virtual void setEncoding(const String&); + virtual String encoding() const; + virtual void data(PassRefPtr<SharedBuffer> data, bool allDataReceived); + virtual void error(CachedResource::Status); + + void checkNotify(); + + private: + bool canUseSheet(bool enforceMIMEType, bool* hasValidMIMEType) const; + virtual PurgePriority purgePriority() const { return PurgeLast; } + + protected: + RefPtr<TextResourceDecoder> m_decoder; + String m_decodedSheetText; + }; + +} + +#endif diff --git a/Source/WebCore/loader/cache/CachedFont.cpp b/Source/WebCore/loader/cache/CachedFont.cpp new file mode 100644 index 0000000..d6967bf --- /dev/null +++ b/Source/WebCore/loader/cache/CachedFont.cpp @@ -0,0 +1,217 @@ +/* + * Copyright (C) 2006, 2007, 2008 Apple Inc. All rights reserved. + * Copyright (C) 2009 Torch Mobile, Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "CachedFont.h" + +#if PLATFORM(CG) || PLATFORM(QT) || PLATFORM(GTK) || (PLATFORM(CHROMIUM) && (OS(WINDOWS) || OS(LINUX) || OS(FREEBSD))) || PLATFORM(HAIKU) || OS(WINCE) || PLATFORM(ANDROID) || PLATFORM(BREWMP) +#define STORE_FONT_CUSTOM_PLATFORM_DATA +#endif + +#include "CachedResourceClient.h" +#include "CachedResourceClientWalker.h" +#include "CachedResourceLoader.h" +#include "FontPlatformData.h" +#include "MemoryCache.h" +#include "SharedBuffer.h" +#include "TextResourceDecoder.h" +#include <wtf/Vector.h> + +#ifdef STORE_FONT_CUSTOM_PLATFORM_DATA +#include "FontCustomPlatformData.h" +#endif + +#if ENABLE(SVG_FONTS) +#include "NodeList.h" +#include "SVGElement.h" +#include "SVGFontElement.h" +#include "SVGGElement.h" +#include "SVGNames.h" +#endif + +namespace WebCore { + +CachedFont::CachedFont(const String &url) + : CachedResource(url, FontResource) + , m_fontData(0) + , m_loadInitiated(false) +#if ENABLE(SVG_FONTS) + , m_isSVGFont(false) +#endif +{ +} + +CachedFont::~CachedFont() +{ +#ifdef STORE_FONT_CUSTOM_PLATFORM_DATA + delete m_fontData; +#endif +} + +void CachedFont::load(CachedResourceLoader*) +{ + // Don't load the file yet. Wait for an access before triggering the load. + setLoading(true); +} + +void CachedFont::didAddClient(CachedResourceClient* c) +{ + if (!isLoading()) + c->fontLoaded(this); +} + +void CachedFont::data(PassRefPtr<SharedBuffer> data, bool allDataReceived) +{ + if (!allDataReceived) + return; + + m_data = data; + setEncodedSize(m_data.get() ? m_data->size() : 0); + setLoading(false); + checkNotify(); +} + +void CachedFont::beginLoadIfNeeded(CachedResourceLoader* dl) +{ + if (!m_loadInitiated) { + m_loadInitiated = true; + dl->load(this, false); + } +} + +bool CachedFont::ensureCustomFontData() +{ +#ifdef STORE_FONT_CUSTOM_PLATFORM_DATA +#if ENABLE(SVG_FONTS) + ASSERT(!m_isSVGFont); +#endif + if (!m_fontData && !errorOccurred() && !isLoading() && m_data) { + m_fontData = createFontCustomPlatformData(m_data.get()); + if (!m_fontData) + setStatus(DecodeError); + } +#endif + return m_fontData; +} + +FontPlatformData CachedFont::platformDataFromCustomData(float size, bool bold, bool italic, FontOrientation orientation, FontRenderingMode renderingMode) +{ +#if ENABLE(SVG_FONTS) + if (m_externalSVGDocument) + return FontPlatformData(size, bold, italic); +#endif +#ifdef STORE_FONT_CUSTOM_PLATFORM_DATA + ASSERT(m_fontData); + return m_fontData->fontPlatformData(static_cast<int>(size), bold, italic, orientation, renderingMode); +#else + return FontPlatformData(); +#endif +} + +#if ENABLE(SVG_FONTS) +bool CachedFont::ensureSVGFontData() +{ + ASSERT(m_isSVGFont); + if (!m_externalSVGDocument && !errorOccurred() && !isLoading() && m_data) { + m_externalSVGDocument = SVGDocument::create(0, KURL()); + m_externalSVGDocument->open(); + + RefPtr<TextResourceDecoder> decoder = TextResourceDecoder::create("application/xml"); + m_externalSVGDocument->write(decoder->decode(m_data->data(), m_data->size())); + m_externalSVGDocument->write(decoder->flush()); + if (decoder->sawError()) { + m_externalSVGDocument.clear(); + return 0; + } + + m_externalSVGDocument->finishParsing(); + m_externalSVGDocument->close(); + } + + return m_externalSVGDocument; +} + +SVGFontElement* CachedFont::getSVGFontById(const String& fontName) const +{ + ASSERT(m_isSVGFont); + RefPtr<NodeList> list = m_externalSVGDocument->getElementsByTagNameNS(SVGNames::fontTag.namespaceURI(), SVGNames::fontTag.localName()); + if (!list) + return 0; + + unsigned listLength = list->length(); + if (!listLength) + return 0; + +#ifndef NDEBUG + for (unsigned i = 0; i < listLength; ++i) { + ASSERT(list->item(i)); + ASSERT(list->item(i)->hasTagName(SVGNames::fontTag)); + } +#endif + + if (fontName.isEmpty()) + return static_cast<SVGFontElement*>(list->item(0)); + + for (unsigned i = 0; i < listLength; ++i) { + SVGFontElement* element = static_cast<SVGFontElement*>(list->item(i)); + if (element->getIdAttribute() == fontName) + return element; + } + + return 0; +} +#endif + +void CachedFont::allClientsRemoved() +{ +#ifdef STORE_FONT_CUSTOM_PLATFORM_DATA + if (m_fontData) { + delete m_fontData; + m_fontData = 0; + } +#endif +} + +void CachedFont::checkNotify() +{ + if (isLoading()) + return; + + CachedResourceClientWalker w(m_clients); + while (CachedResourceClient *c = w.next()) + c->fontLoaded(this); +} + + +void CachedFont::error(CachedResource::Status status) +{ + setStatus(status); + ASSERT(errorOccurred()); + setLoading(false); + checkNotify(); +} + +} diff --git a/Source/WebCore/loader/cache/CachedFont.h b/Source/WebCore/loader/cache/CachedFont.h new file mode 100644 index 0000000..5814087 --- /dev/null +++ b/Source/WebCore/loader/cache/CachedFont.h @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2007, 2008 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef CachedFont_h +#define CachedFont_h + +#include "CachedResource.h" +#include "FontOrientation.h" +#include "FontRenderingMode.h" +#include <wtf/Vector.h> + +#if ENABLE(SVG_FONTS) +#include "SVGElement.h" +#include "SVGDocument.h" +#endif + +namespace WebCore { + +class CachedResourceLoader; +class MemoryCache; +class FontPlatformData; +class SVGFontElement; + +struct FontCustomPlatformData; + +class CachedFont : public CachedResource { +public: + CachedFont(const String& url); + virtual ~CachedFont(); + + virtual void load(CachedResourceLoader* cachedResourceLoader); + + virtual void didAddClient(CachedResourceClient*); + virtual void data(PassRefPtr<SharedBuffer> data, bool allDataReceived); + virtual void error(CachedResource::Status); + + virtual void allClientsRemoved(); + + void checkNotify(); + + void beginLoadIfNeeded(CachedResourceLoader* dl); + + bool ensureCustomFontData(); + FontPlatformData platformDataFromCustomData(float size, bool bold, bool italic, FontOrientation = Horizontal, FontRenderingMode = NormalRenderingMode); + +#if ENABLE(SVG_FONTS) + bool isSVGFont() const { return m_isSVGFont; } + void setSVGFont(bool isSVG) { m_isSVGFont = isSVG; } + bool ensureSVGFontData(); + SVGFontElement* getSVGFontById(const String&) const; +#endif + +private: + FontCustomPlatformData* m_fontData; + bool m_loadInitiated; + +#if ENABLE(SVG_FONTS) + bool m_isSVGFont; + RefPtr<SVGDocument> m_externalSVGDocument; +#endif + + friend class MemoryCache; +}; + +} + +#endif diff --git a/Source/WebCore/loader/cache/CachedImage.cpp b/Source/WebCore/loader/cache/CachedImage.cpp new file mode 100644 index 0000000..c550eec --- /dev/null +++ b/Source/WebCore/loader/cache/CachedImage.cpp @@ -0,0 +1,386 @@ +/* + 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) 2006 Samuel Weinig (sam.weinig@gmail.com) + Copyright (C) 2004, 2005, 2006, 2007 Apple Inc. All rights reserved. + + 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. +*/ + +#include "config.h" +#include "CachedImage.h" + +#include "BitmapImage.h" +#include "MemoryCache.h" +#include "CachedResourceClient.h" +#include "CachedResourceClientWalker.h" +#include "CachedResourceLoader.h" +#include "CachedResourceRequest.h" +#include "Frame.h" +#include "FrameLoaderTypes.h" +#include "FrameView.h" +#include "Settings.h" +#include "SharedBuffer.h" +#include <wtf/CurrentTime.h> +#include <wtf/StdLibExtras.h> +#include <wtf/Vector.h> + +#if PLATFORM(CG) +#include "PDFDocumentImage.h" +#endif + +#if ENABLE(SVG_AS_IMAGE) +#include "SVGImage.h" +#endif + +using std::max; + +namespace WebCore { + +CachedImage::CachedImage(const String& url) + : CachedResource(url, ImageResource) + , m_image(0) + , m_decodedDataDeletionTimer(this, &CachedImage::decodedDataDeletionTimerFired) +{ + setStatus(Unknown); +} + +CachedImage::CachedImage(Image* image) + : CachedResource(String(), ImageResource) + , m_image(image) + , m_decodedDataDeletionTimer(this, &CachedImage::decodedDataDeletionTimerFired) +{ + setStatus(Cached); + setLoading(false); +} + +CachedImage::~CachedImage() +{ +} + +void CachedImage::decodedDataDeletionTimerFired(Timer<CachedImage>*) +{ + ASSERT(!hasClients()); + destroyDecodedData(); +} + +void CachedImage::load(CachedResourceLoader* cachedResourceLoader) +{ +#ifdef ANDROID_BLOCK_NETWORK_IMAGE + if (!cachedResourceLoader || (cachedResourceLoader->autoLoadImages() && !cachedResourceLoader->shouldBlockNetworkImage(m_url))) +#else + if (!cachedResourceLoader || cachedResourceLoader->autoLoadImages()) +#endif + CachedResource::load(cachedResourceLoader, true, DoSecurityCheck, true); + else + setLoading(false); +} + +void CachedImage::didAddClient(CachedResourceClient* c) +{ + if (m_decodedDataDeletionTimer.isActive()) + m_decodedDataDeletionTimer.stop(); + + if (m_data && !m_image && !errorOccurred()) { + createImage(); + m_image->setData(m_data, true); + } + + if (m_image && !m_image->isNull()) + c->imageChanged(this); + + if (!isLoading()) + c->notifyFinished(this); +} + +void CachedImage::allClientsRemoved() +{ + if (m_image && !errorOccurred()) + m_image->resetAnimation(); + if (double interval = cache()->deadDecodedDataDeletionInterval()) + m_decodedDataDeletionTimer.startOneShot(interval); +} + +static Image* brokenImage() +{ + DEFINE_STATIC_LOCAL(RefPtr<Image>, brokenImage, (Image::loadPlatformResource("missingImage"))); + return brokenImage.get(); +} + +Image* CachedImage::image() const +{ + ASSERT(!isPurgeable()); + + if (errorOccurred()) + return brokenImage(); + + if (m_image) + return m_image.get(); + + return Image::nullImage(); +} + +void CachedImage::setImageContainerSize(const IntSize& containerSize) +{ + if (m_image) + m_image->setContainerSize(containerSize); +} + +bool CachedImage::usesImageContainerSize() const +{ + if (m_image) + return m_image->usesContainerSize(); + + return false; +} + +bool CachedImage::imageHasRelativeWidth() const +{ + if (m_image) + return m_image->hasRelativeWidth(); + + return false; +} + +bool CachedImage::imageHasRelativeHeight() const +{ + if (m_image) + return m_image->hasRelativeHeight(); + + return false; +} + +IntSize CachedImage::imageSize(float multiplier) const +{ + ASSERT(!isPurgeable()); + + if (!m_image) + return IntSize(); + if (multiplier == 1.0f) + return m_image->size(); + + // Don't let images that have a width/height >= 1 shrink below 1 when zoomed. + bool hasWidth = m_image->size().width() > 0; + bool hasHeight = m_image->size().height() > 0; + int width = m_image->size().width() * (m_image->hasRelativeWidth() ? 1.0f : multiplier); + int height = m_image->size().height() * (m_image->hasRelativeHeight() ? 1.0f : multiplier); + if (hasWidth) + width = max(1, width); + if (hasHeight) + height = max(1, height); + return IntSize(width, height); +} + +IntRect CachedImage::imageRect(float multiplier) const +{ + ASSERT(!isPurgeable()); + + if (!m_image) + return IntRect(); + if (multiplier == 1.0f || (!m_image->hasRelativeWidth() && !m_image->hasRelativeHeight())) + return m_image->rect(); + + float widthMultiplier = (m_image->hasRelativeWidth() ? 1.0f : multiplier); + float heightMultiplier = (m_image->hasRelativeHeight() ? 1.0f : multiplier); + + // Don't let images that have a width/height >= 1 shrink below 1 when zoomed. + bool hasWidth = m_image->rect().width() > 0; + bool hasHeight = m_image->rect().height() > 0; + + int width = static_cast<int>(m_image->rect().width() * widthMultiplier); + int height = static_cast<int>(m_image->rect().height() * heightMultiplier); + if (hasWidth) + width = max(1, width); + if (hasHeight) + height = max(1, height); + + int x = static_cast<int>(m_image->rect().x() * widthMultiplier); + int y = static_cast<int>(m_image->rect().y() * heightMultiplier); + + return IntRect(x, y, width, height); +} + +void CachedImage::notifyObservers(const IntRect* changeRect) +{ + CachedResourceClientWalker w(m_clients); + while (CachedResourceClient* c = w.next()) + c->imageChanged(this, changeRect); +} + +void CachedImage::clear() +{ + destroyDecodedData(); + m_image = 0; + setEncodedSize(0); +} + +inline void CachedImage::createImage() +{ + // Create the image if it doesn't yet exist. + if (m_image) + return; +#if PLATFORM(CG) && !USE(WEBKIT_IMAGE_DECODERS) + if (m_response.mimeType() == "application/pdf") { + m_image = PDFDocumentImage::create(); + return; + } +#endif +#if ENABLE(SVG_AS_IMAGE) + if (m_response.mimeType() == "image/svg+xml") { + m_image = SVGImage::create(this); + return; + } +#endif + m_image = BitmapImage::create(this); +#if PLATFORM(ANDROID) + m_image->setURL(url()); +#endif +} + +size_t CachedImage::maximumDecodedImageSize() +{ + Frame* frame = m_request ? m_request->cachedResourceLoader()->frame() : 0; + if (!frame) + return 0; + Settings* settings = frame->settings(); + return settings ? settings->maximumDecodedImageSize() : 0; +} + +void CachedImage::data(PassRefPtr<SharedBuffer> data, bool allDataReceived) +{ + m_data = data; + + createImage(); + + bool sizeAvailable = false; + + // Have the image update its data from its internal buffer. + // It will not do anything now, but will delay decoding until + // queried for info (like size or specific image frames). + sizeAvailable = m_image->setData(m_data, allDataReceived); + + // Go ahead and tell our observers to try to draw if we have either + // received all the data or the size is known. Each chunk from the + // network causes observers to repaint, which will force that chunk + // to decode. + if (sizeAvailable || allDataReceived) { + size_t maxDecodedImageSize = maximumDecodedImageSize(); + IntSize s = imageSize(1.0f); + size_t estimatedDecodedImageSize = s.width() * s.height() * 4; // no overflow check + if (m_image->isNull() || (maxDecodedImageSize > 0 && estimatedDecodedImageSize > maxDecodedImageSize)) { + error(errorOccurred() ? status() : DecodeError); + if (inCache()) + cache()->remove(this); + return; + } + + // It would be nice to only redraw the decoded band of the image, but with the current design + // (decoding delayed until painting) that seems hard. + notifyObservers(); + + if (m_image) + setEncodedSize(m_image->data() ? m_image->data()->size() : 0); + } + + if (allDataReceived) { + setLoading(false); + checkNotify(); + } +} + +void CachedImage::error(CachedResource::Status status) +{ + clear(); + setStatus(status); + ASSERT(errorOccurred()); + m_data.clear(); + notifyObservers(); + setLoading(false); + checkNotify(); +} + +void CachedImage::checkNotify() +{ + if (isLoading()) + return; + + CachedResourceClientWalker w(m_clients); + while (CachedResourceClient* c = w.next()) + c->notifyFinished(this); +} + +void CachedImage::destroyDecodedData() +{ + bool canDeleteImage = !m_image || (m_image->hasOneRef() && m_image->isBitmapImage()); + if (isSafeToMakePurgeable() && canDeleteImage && !isLoading()) { + // Image refs the data buffer so we should not make it purgeable while the image is alive. + // Invoking addClient() will reconstruct the image object. + m_image = 0; + setDecodedSize(0); + if (!MemoryCache::shouldMakeResourcePurgeableOnEviction()) + makePurgeable(true); + } else if (m_image && !errorOccurred()) + m_image->destroyDecodedData(); +} + +void CachedImage::decodedSizeChanged(const Image* image, int delta) +{ + if (image != m_image) + return; + + setDecodedSize(decodedSize() + delta); +} + +void CachedImage::didDraw(const Image* image) +{ + if (image != m_image) + return; + + double timeStamp = FrameView::currentPaintTimeStamp(); + if (!timeStamp) // If didDraw is called outside of a Frame paint. + timeStamp = currentTime(); + + CachedResource::didAccessDecodedData(timeStamp); +} + +bool CachedImage::shouldPauseAnimation(const Image* image) +{ + if (image != m_image) + return false; + + CachedResourceClientWalker w(m_clients); + while (CachedResourceClient* c = w.next()) { + if (c->willRenderImage(this)) + return false; + } + + return true; +} + +void CachedImage::animationAdvanced(const Image* image) +{ + if (image == m_image) + notifyObservers(); +} + +void CachedImage::changedInRect(const Image* image, const IntRect& rect) +{ + if (image == m_image) + notifyObservers(&rect); +} + +} //namespace WebCore diff --git a/Source/WebCore/loader/cache/CachedImage.h b/Source/WebCore/loader/cache/CachedImage.h new file mode 100644 index 0000000..345d1e7 --- /dev/null +++ b/Source/WebCore/loader/cache/CachedImage.h @@ -0,0 +1,105 @@ +/* + Copyright (C) 1998 Lars Knoll (knoll@mpi-hd.mpg.de) + Copyright (C) 2001 Dirk Mueller <mueller@kde.org> + Copyright (C) 2006 Samuel Weinig (sam.weinig@gmail.com) + Copyright (C) 2004, 2005, 2006, 2007 Apple Inc. All rights reserved. + + 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. +*/ + +#ifndef CachedImage_h +#define CachedImage_h + +#include "CachedResource.h" +#include "ImageObserver.h" +#include "IntRect.h" +#include "Timer.h" +#include <wtf/Vector.h> + +namespace WebCore { + +class CachedResourceLoader; +class MemoryCache; + +class CachedImage : public CachedResource, public ImageObserver { + friend class MemoryCache; + +public: + CachedImage(const String& url); + CachedImage(Image*); + virtual ~CachedImage(); + + virtual void load(CachedResourceLoader* cachedResourceLoader); + + Image* image() const; // Returns the nullImage() if the image is not available yet. + bool hasImage() const { return m_image.get(); } + + bool canRender(float multiplier) const { return !errorOccurred() && !imageSize(multiplier).isEmpty(); } + + // These are only used for SVGImage right now + void setImageContainerSize(const IntSize&); + bool usesImageContainerSize() const; + bool imageHasRelativeWidth() const; + bool imageHasRelativeHeight() const; + + // Both of these methods take a zoom multiplier that can be used to increase the natural size of the image by the + // zoom. + IntSize imageSize(float multiplier) const; // returns the size of the complete image. + IntRect imageRect(float multiplier) const; // The size of the currently decoded portion of the image. + + virtual void didAddClient(CachedResourceClient*); + + virtual void allClientsRemoved(); + virtual void destroyDecodedData(); + + virtual void data(PassRefPtr<SharedBuffer> data, bool allDataReceived); + virtual void error(CachedResource::Status); + + // For compatibility, images keep loading even if there are HTTP errors. + virtual bool shouldIgnoreHTTPStatusCodeErrors() const { return true; } + + void checkNotify(); + + virtual bool isImage() const { return true; } + + void clear(); + + bool stillNeedsLoad() const { return !errorOccurred() && status() == Unknown && !isLoading(); } + void load(); + + // ImageObserver + virtual void decodedSizeChanged(const Image* image, int delta); + virtual void didDraw(const Image*); + + virtual bool shouldPauseAnimation(const Image*); + virtual void animationAdvanced(const Image*); + virtual void changedInRect(const Image*, const IntRect&); + +private: + void createImage(); + size_t maximumDecodedImageSize(); + // If not null, changeRect is the changed part of the image. + void notifyObservers(const IntRect* changeRect = 0); + void decodedDataDeletionTimerFired(Timer<CachedImage>*); + virtual PurgePriority purgePriority() const { return PurgeFirst; } + + RefPtr<Image> m_image; + Timer<CachedImage> m_decodedDataDeletionTimer; +}; + +} + +#endif diff --git a/Source/WebCore/loader/cache/CachedResource.cpp b/Source/WebCore/loader/cache/CachedResource.cpp new file mode 100644 index 0000000..a9d9b0a --- /dev/null +++ b/Source/WebCore/loader/cache/CachedResource.cpp @@ -0,0 +1,596 @@ +/* + 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) 2006 Samuel Weinig (sam.weinig@gmail.com) + Copyright (C) 2004, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved. + + 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. +*/ + +#include "config.h" +#include "CachedResource.h" + +#include "MemoryCache.h" +#include "CachedMetadata.h" +#include "CachedResourceClient.h" +#include "CachedResourceClientWalker.h" +#include "CachedResourceHandle.h" +#include "CachedResourceLoader.h" +#include "CachedResourceRequest.h" +#include "Frame.h" +#include "FrameLoaderClient.h" +#include "KURL.h" +#include "Logging.h" +#include "PurgeableBuffer.h" +#include "ResourceHandle.h" +#include "SharedBuffer.h" +#include <wtf/CurrentTime.h> +#include <wtf/MathExtras.h> +#include <wtf/RefCountedLeakCounter.h> +#include <wtf/StdLibExtras.h> +#include <wtf/Vector.h> + +using namespace WTF; + +namespace WebCore { + +static ResourceLoadPriority defaultPriorityForResourceType(CachedResource::Type type) +{ + switch (type) { + case CachedResource::CSSStyleSheet: +#if ENABLE(XSLT) + case CachedResource::XSLStyleSheet: +#endif + return ResourceLoadPriorityHigh; + case CachedResource::Script: + case CachedResource::FontResource: + return ResourceLoadPriorityMedium; + case CachedResource::ImageResource: + return ResourceLoadPriorityLow; +#if ENABLE(LINK_PREFETCH) + case CachedResource::LinkPrefetch: + return ResourceLoadPriorityVeryLow; +#endif + } + ASSERT_NOT_REACHED(); + return ResourceLoadPriorityLow; +} + +#ifndef NDEBUG +static RefCountedLeakCounter cachedResourceLeakCounter("CachedResource"); +#endif + +CachedResource::CachedResource(const String& url, Type type) + : m_url(url) + , m_request(0) + , m_loadPriority(defaultPriorityForResourceType(type)) + , m_responseTimestamp(currentTime()) + , m_lastDecodedAccessTime(0) + , m_encodedSize(0) + , m_decodedSize(0) + , m_accessCount(0) + , m_handleCount(0) + , m_preloadCount(0) + , m_preloadResult(PreloadNotReferenced) + , m_inLiveDecodedResourcesList(false) + , m_requestedFromNetworkingLayer(false) + , m_sendResourceLoadCallbacks(true) + , m_inCache(false) + , m_loading(false) + , m_type(type) + , m_status(Pending) +#ifndef NDEBUG + , m_deleted(false) + , m_lruIndex(0) +#endif + , m_nextInAllResourcesList(0) + , m_prevInAllResourcesList(0) + , m_nextInLiveResourcesList(0) + , m_prevInLiveResourcesList(0) + , m_owningCachedResourceLoader(0) + , m_resourceToRevalidate(0) + , m_proxyResource(0) +{ +#ifndef NDEBUG + cachedResourceLeakCounter.increment(); +#endif +} + +CachedResource::~CachedResource() +{ + ASSERT(!m_resourceToRevalidate); // Should be true because canDelete() checks this. + ASSERT(canDelete()); + ASSERT(!inCache()); + ASSERT(!m_deleted); + ASSERT(url().isNull() || cache()->resourceForURL(KURL(ParsedURLString, url())) != this); +#ifndef NDEBUG + m_deleted = true; + cachedResourceLeakCounter.decrement(); +#endif + + if (m_owningCachedResourceLoader) + m_owningCachedResourceLoader->removeCachedResource(this); +} + +void CachedResource::load(CachedResourceLoader* cachedResourceLoader, bool incremental, SecurityCheckPolicy securityCheck, bool sendResourceLoadCallbacks) +{ + m_sendResourceLoadCallbacks = sendResourceLoadCallbacks; + cachedResourceLoader->load(this, incremental, securityCheck, sendResourceLoadCallbacks); + m_loading = true; +} + +void CachedResource::data(PassRefPtr<SharedBuffer>, bool allDataReceived) +{ + if (!allDataReceived) + return; + + setLoading(false); + CachedResourceClientWalker w(m_clients); + while (CachedResourceClient* c = w.next()) + c->notifyFinished(this); +} + +void CachedResource::finish() +{ + m_status = Cached; +} + +bool CachedResource::isExpired() const +{ + if (m_response.isNull()) + return false; + + return currentAge() > freshnessLifetime(); +} + +double CachedResource::currentAge() const +{ + // RFC2616 13.2.3 + // No compensation for latency as that is not terribly important in practice + double dateValue = m_response.date(); + double apparentAge = isfinite(dateValue) ? max(0., m_responseTimestamp - dateValue) : 0; + double ageValue = m_response.age(); + double correctedReceivedAge = isfinite(ageValue) ? max(apparentAge, ageValue) : apparentAge; + double residentTime = currentTime() - m_responseTimestamp; + return correctedReceivedAge + residentTime; +} + +double CachedResource::freshnessLifetime() const +{ + // Cache non-http resources liberally + if (!m_response.url().protocolInHTTPFamily()) + return std::numeric_limits<double>::max(); + + // RFC2616 13.2.4 + double maxAgeValue = m_response.cacheControlMaxAge(); + if (isfinite(maxAgeValue)) + return maxAgeValue; + double expiresValue = m_response.expires(); + double dateValue = m_response.date(); + double creationTime = isfinite(dateValue) ? dateValue : m_responseTimestamp; + if (isfinite(expiresValue)) + return expiresValue - creationTime; + double lastModifiedValue = m_response.lastModified(); + if (isfinite(lastModifiedValue)) + return (creationTime - lastModifiedValue) * 0.1; + // If no cache headers are present, the specification leaves the decision to the UA. Other browsers seem to opt for 0. + return 0; +} + +void CachedResource::setResponse(const ResourceResponse& response) +{ + m_response = response; + m_responseTimestamp = currentTime(); +} + +void CachedResource::setSerializedCachedMetadata(const char* data, size_t size) +{ + // We only expect to receive cached metadata from the platform once. + // If this triggers, it indicates an efficiency problem which is most + // likely unexpected in code designed to improve performance. + ASSERT(!m_cachedMetadata); + + m_cachedMetadata = CachedMetadata::deserialize(data, size); +} + +void CachedResource::setCachedMetadata(unsigned dataTypeID, const char* data, size_t size) +{ + // Currently, only one type of cached metadata per resource is supported. + // If the need arises for multiple types of metadata per resource this could + // be enhanced to store types of metadata in a map. + ASSERT(!m_cachedMetadata); + + m_cachedMetadata = CachedMetadata::create(dataTypeID, data, size); + ResourceHandle::cacheMetadata(m_response, m_cachedMetadata->serialize()); +} + +CachedMetadata* CachedResource::cachedMetadata(unsigned dataTypeID) const +{ + if (!m_cachedMetadata || m_cachedMetadata->dataTypeID() != dataTypeID) + return 0; + return m_cachedMetadata.get(); +} + +void CachedResource::setRequest(CachedResourceRequest* request) +{ + if (request && !m_request) + m_status = Pending; + m_request = request; + if (canDelete() && !inCache()) + delete this; +} + +void CachedResource::addClient(CachedResourceClient* client) +{ + addClientToSet(client); + didAddClient(client); +} + +void CachedResource::didAddClient(CachedResourceClient* c) +{ + if (!isLoading()) + c->notifyFinished(this); +} + +void CachedResource::addClientToSet(CachedResourceClient* client) +{ + ASSERT(!isPurgeable()); + + if (m_preloadResult == PreloadNotReferenced) { + if (isLoaded()) + m_preloadResult = PreloadReferencedWhileComplete; + else if (m_requestedFromNetworkingLayer) + m_preloadResult = PreloadReferencedWhileLoading; + else + m_preloadResult = PreloadReferenced; + } + if (!hasClients() && inCache()) + cache()->addToLiveResourcesSize(this); + m_clients.add(client); +} + +void CachedResource::removeClient(CachedResourceClient* client) +{ + ASSERT(m_clients.contains(client)); + m_clients.remove(client); + + if (canDelete() && !inCache()) + delete this; + else if (!hasClients() && inCache()) { + cache()->removeFromLiveResourcesSize(this); + cache()->removeFromLiveDecodedResourcesList(this); + allClientsRemoved(); + if (response().cacheControlContainsNoStore()) { + // RFC2616 14.9.2: + // "no-store: ... MUST make a best-effort attempt to remove the information from volatile storage as promptly as possible" + // "... History buffers MAY store such responses as part of their normal operation." + // We allow non-secure content to be reused in history, but we do not allow secure content to be reused. + if (protocolIs(url(), "https")) + cache()->remove(this); + } else + cache()->prune(); + } + // This object may be dead here. +} + +void CachedResource::deleteIfPossible() +{ + if (canDelete() && !inCache()) + delete this; +} + +void CachedResource::setDecodedSize(unsigned size) +{ + if (size == m_decodedSize) + return; + + int delta = size - m_decodedSize; + + // The object must now be moved to a different queue, since its size has been changed. + // We have to remove explicitly before updating m_decodedSize, so that we find the correct previous + // queue. + if (inCache()) + cache()->removeFromLRUList(this); + + m_decodedSize = size; + + if (inCache()) { + // Now insert into the new LRU list. + cache()->insertInLRUList(this); + + // Insert into or remove from the live decoded list if necessary. + // When inserting into the LiveDecodedResourcesList it is possible + // that the m_lastDecodedAccessTime is still zero or smaller than + // the m_lastDecodedAccessTime of the current list head. This is a + // violation of the invariant that the list is to be kept sorted + // by access time. The weakening of the invariant does not pose + // a problem. For more details please see: https://bugs.webkit.org/show_bug.cgi?id=30209 + if (m_decodedSize && !m_inLiveDecodedResourcesList && hasClients()) + cache()->insertInLiveDecodedResourcesList(this); + else if (!m_decodedSize && m_inLiveDecodedResourcesList) + cache()->removeFromLiveDecodedResourcesList(this); + + // Update the cache's size totals. + cache()->adjustSize(hasClients(), delta); + } +} + +void CachedResource::setEncodedSize(unsigned size) +{ + if (size == m_encodedSize) + return; + + // The size cannot ever shrink (unless it is being nulled out because of an error). If it ever does, assert. + ASSERT(size == 0 || size >= m_encodedSize); + + int delta = size - m_encodedSize; + + // The object must now be moved to a different queue, since its size has been changed. + // We have to remove explicitly before updating m_encodedSize, so that we find the correct previous + // queue. + if (inCache()) + cache()->removeFromLRUList(this); + + m_encodedSize = size; + + if (inCache()) { + // Now insert into the new LRU list. + cache()->insertInLRUList(this); + + // Update the cache's size totals. + cache()->adjustSize(hasClients(), delta); + } +} + +void CachedResource::didAccessDecodedData(double timeStamp) +{ + m_lastDecodedAccessTime = timeStamp; + + if (inCache()) { + if (m_inLiveDecodedResourcesList) { + cache()->removeFromLiveDecodedResourcesList(this); + cache()->insertInLiveDecodedResourcesList(this); + } + cache()->prune(); + } +} + +void CachedResource::setResourceToRevalidate(CachedResource* resource) +{ + ASSERT(resource); + ASSERT(!m_resourceToRevalidate); + ASSERT(resource != this); + ASSERT(m_handlesToRevalidate.isEmpty()); + ASSERT(resource->type() == type()); + + LOG(ResourceLoading, "CachedResource %p setResourceToRevalidate %p", this, resource); + + // The following assert should be investigated whenever it occurs. Although it should never fire, it currently does in rare circumstances. + // https://bugs.webkit.org/show_bug.cgi?id=28604. + // So the code needs to be robust to this assert failing thus the "if (m_resourceToRevalidate->m_proxyResource == this)" in CachedResource::clearResourceToRevalidate. + ASSERT(!resource->m_proxyResource); + + resource->m_proxyResource = this; + m_resourceToRevalidate = resource; +} + +void CachedResource::clearResourceToRevalidate() +{ + ASSERT(m_resourceToRevalidate); + // A resource may start revalidation before this method has been called, so check that this resource is still the proxy resource before clearing it out. + if (m_resourceToRevalidate->m_proxyResource == this) { + m_resourceToRevalidate->m_proxyResource = 0; + m_resourceToRevalidate->deleteIfPossible(); + } + m_handlesToRevalidate.clear(); + m_resourceToRevalidate = 0; + deleteIfPossible(); +} + +void CachedResource::switchClientsToRevalidatedResource() +{ + ASSERT(m_resourceToRevalidate); + ASSERT(m_resourceToRevalidate->inCache()); + ASSERT(!inCache()); + + LOG(ResourceLoading, "CachedResource %p switchClientsToRevalidatedResource %p", this, m_resourceToRevalidate); + + HashSet<CachedResourceHandleBase*>::iterator end = m_handlesToRevalidate.end(); + for (HashSet<CachedResourceHandleBase*>::iterator it = m_handlesToRevalidate.begin(); it != end; ++it) { + CachedResourceHandleBase* handle = *it; + handle->m_resource = m_resourceToRevalidate; + m_resourceToRevalidate->registerHandle(handle); + --m_handleCount; + } + ASSERT(!m_handleCount); + m_handlesToRevalidate.clear(); + + Vector<CachedResourceClient*> clientsToMove; + HashCountedSet<CachedResourceClient*>::iterator end2 = m_clients.end(); + for (HashCountedSet<CachedResourceClient*>::iterator it = m_clients.begin(); it != end2; ++it) { + CachedResourceClient* client = it->first; + unsigned count = it->second; + while (count) { + clientsToMove.append(client); + --count; + } + } + // Equivalent of calling removeClient() for all clients + m_clients.clear(); + + unsigned moveCount = clientsToMove.size(); + for (unsigned n = 0; n < moveCount; ++n) + m_resourceToRevalidate->addClientToSet(clientsToMove[n]); + for (unsigned n = 0; n < moveCount; ++n) { + // Calling didAddClient for a client may end up removing another client. In that case it won't be in the set anymore. + if (m_resourceToRevalidate->m_clients.contains(clientsToMove[n])) + m_resourceToRevalidate->didAddClient(clientsToMove[n]); + } +} + +void CachedResource::updateResponseAfterRevalidation(const ResourceResponse& validatingResponse) +{ + m_responseTimestamp = currentTime(); + + DEFINE_STATIC_LOCAL(const AtomicString, contentHeaderPrefix, ("content-")); + // RFC2616 10.3.5 + // Update cached headers from the 304 response + const HTTPHeaderMap& newHeaders = validatingResponse.httpHeaderFields(); + HTTPHeaderMap::const_iterator end = newHeaders.end(); + for (HTTPHeaderMap::const_iterator it = newHeaders.begin(); it != end; ++it) { + // Don't allow 304 response to update content headers, these can't change but some servers send wrong values. + if (it->first.startsWith(contentHeaderPrefix, false)) + continue; + m_response.setHTTPHeaderField(it->first, it->second); + } +} + +void CachedResource::registerHandle(CachedResourceHandleBase* h) +{ + ++m_handleCount; + if (m_resourceToRevalidate) + m_handlesToRevalidate.add(h); +} + +void CachedResource::unregisterHandle(CachedResourceHandleBase* h) +{ + ASSERT(m_handleCount > 0); + --m_handleCount; + + if (m_resourceToRevalidate) + m_handlesToRevalidate.remove(h); + + if (!m_handleCount) + deleteIfPossible(); +} + +bool CachedResource::canUseCacheValidator() const +{ + if (m_loading || errorOccurred()) + return false; + + if (m_response.cacheControlContainsNoStore()) + return false; + + DEFINE_STATIC_LOCAL(const AtomicString, lastModifiedHeader, ("last-modified")); + DEFINE_STATIC_LOCAL(const AtomicString, eTagHeader, ("etag")); + return !m_response.httpHeaderField(lastModifiedHeader).isEmpty() || !m_response.httpHeaderField(eTagHeader).isEmpty(); +} + +bool CachedResource::mustRevalidateDueToCacheHeaders(CachePolicy cachePolicy) const +{ + ASSERT(cachePolicy == CachePolicyRevalidate || cachePolicy == CachePolicyCache || cachePolicy == CachePolicyVerify); + + if (cachePolicy == CachePolicyRevalidate) + return true; + + if (m_response.cacheControlContainsNoCache() || m_response.cacheControlContainsNoStore()) { + LOG(ResourceLoading, "CachedResource %p mustRevalidate because of m_response.cacheControlContainsNoCache() || m_response.cacheControlContainsNoStore()\n", this); + return true; + } + + if (cachePolicy == CachePolicyCache) { + if (m_response.cacheControlContainsMustRevalidate() && isExpired()) { + LOG(ResourceLoading, "CachedResource %p mustRevalidate because of cachePolicy == CachePolicyCache and m_response.cacheControlContainsMustRevalidate() && isExpired()\n", this); + return true; + } + return false; + } + + // CachePolicyVerify + if (isExpired()) { + LOG(ResourceLoading, "CachedResource %p mustRevalidate because of isExpired()\n", this); + return true; + } + + return false; +} + +bool CachedResource::isSafeToMakePurgeable() const +{ + return !hasClients() && !m_proxyResource && !m_resourceToRevalidate; +} + +bool CachedResource::makePurgeable(bool purgeable) +{ + if (purgeable) { + ASSERT(isSafeToMakePurgeable()); + + if (m_purgeableData) { + ASSERT(!m_data); + return true; + } + if (!m_data) + return false; + + // Should not make buffer purgeable if it has refs other than this since we don't want two copies. + if (!m_data->hasOneRef()) + return false; + + if (m_data->hasPurgeableBuffer()) { + m_purgeableData = m_data->releasePurgeableBuffer(); + } else { + m_purgeableData = PurgeableBuffer::create(m_data->data(), m_data->size()); + if (!m_purgeableData) + return false; + m_purgeableData->setPurgePriority(purgePriority()); + } + + m_purgeableData->makePurgeable(true); + m_data.clear(); + return true; + } + + if (!m_purgeableData) + return true; + ASSERT(!m_data); + ASSERT(!hasClients()); + + if (!m_purgeableData->makePurgeable(false)) + return false; + + m_data = SharedBuffer::adoptPurgeableBuffer(m_purgeableData.release()); + return true; +} + +bool CachedResource::isPurgeable() const +{ + return m_purgeableData && m_purgeableData->isPurgeable(); +} + +bool CachedResource::wasPurged() const +{ + return m_purgeableData && m_purgeableData->wasPurged(); +} + +unsigned CachedResource::overheadSize() const +{ + return sizeof(CachedResource) + m_response.memoryUsage() + 576; + /* + 576 = 192 + // average size of m_url + 384; // average size of m_clients hash map + */ +} + +void CachedResource::setLoadPriority(ResourceLoadPriority loadPriority) +{ + if (loadPriority == ResourceLoadPriorityUnresolved) + return; + m_loadPriority = loadPriority; +} + +} diff --git a/Source/WebCore/loader/cache/CachedResource.h b/Source/WebCore/loader/cache/CachedResource.h new file mode 100644 index 0000000..3600a02 --- /dev/null +++ b/Source/WebCore/loader/cache/CachedResource.h @@ -0,0 +1,296 @@ +/* + Copyright (C) 1998 Lars Knoll (knoll@mpi-hd.mpg.de) + Copyright (C) 2001 Dirk Mueller <mueller@kde.org> + Copyright (C) 2006 Samuel Weinig (sam.weinig@gmail.com) + Copyright (C) 2004, 2005, 2006, 2007 Apple Inc. All rights reserved. + + 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. +*/ + +#ifndef CachedResource_h +#define CachedResource_h + +#include "CachePolicy.h" +#include "FrameLoaderTypes.h" +#include "PlatformString.h" +#include "PurgePriority.h" +#include "ResourceLoadPriority.h" +#include "ResourceResponse.h" +#include <wtf/HashCountedSet.h> +#include <wtf/HashSet.h> +#include <wtf/OwnPtr.h> +#include <wtf/Vector.h> +#include <time.h> + +namespace WebCore { + +class MemoryCache; +class CachedMetadata; +class CachedResourceClient; +class CachedResourceHandleBase; +class CachedResourceLoader; +class CachedResourceRequest; +class Frame; +class InspectorResource; +class PurgeableBuffer; + +// A resource that is held in the cache. Classes who want to use this object should derive +// from CachedResourceClient, to get the function calls in case the requested data has arrived. +// This class also does the actual communication with the loader to obtain the resource from the network. +class CachedResource : public Noncopyable { + friend class MemoryCache; + friend class InspectorResource; + +public: + enum Type { + ImageResource, + CSSStyleSheet, + Script, + FontResource +#if ENABLE(XSLT) + , XSLStyleSheet +#endif +#if ENABLE(LINK_PREFETCH) + , LinkPrefetch +#endif + }; + + enum Status { + Unknown, // let cache decide what to do with it + Pending, // only partially loaded + Cached, // regular case + LoadError, + DecodeError + }; + + CachedResource(const String& url, Type); + virtual ~CachedResource(); + + virtual void load(CachedResourceLoader* cachedResourceLoader) { load(cachedResourceLoader, false, DoSecurityCheck, true); } + void load(CachedResourceLoader*, bool incremental, SecurityCheckPolicy, bool sendResourceLoadCallbacks); + + virtual void setEncoding(const String&) { } + virtual String encoding() const { return String(); } + virtual void data(PassRefPtr<SharedBuffer> data, bool allDataReceived); + virtual void error(CachedResource::Status) { } + + virtual bool shouldIgnoreHTTPStatusCodeErrors() const { return false; } + + const String &url() const { return m_url; } + Type type() const { return static_cast<Type>(m_type); } + + ResourceLoadPriority loadPriority() const { return m_loadPriority; } + void setLoadPriority(ResourceLoadPriority); + + void addClient(CachedResourceClient*); + void removeClient(CachedResourceClient*); + bool hasClients() const { return !m_clients.isEmpty(); } + void deleteIfPossible(); + + enum PreloadResult { + PreloadNotReferenced, + PreloadReferenced, + PreloadReferencedWhileLoading, + PreloadReferencedWhileComplete + }; + PreloadResult preloadResult() const { return static_cast<PreloadResult>(m_preloadResult); } + void setRequestedFromNetworkingLayer() { m_requestedFromNetworkingLayer = true; } + + virtual void didAddClient(CachedResourceClient*); + virtual void allClientsRemoved() { } + + unsigned count() const { return m_clients.size(); } + + Status status() const { return static_cast<Status>(m_status); } + void setStatus(Status status) { m_status = status; } + + unsigned size() const { return encodedSize() + decodedSize() + overheadSize(); } + unsigned encodedSize() const { return m_encodedSize; } + unsigned decodedSize() const { return m_decodedSize; } + unsigned overheadSize() const; + + bool isLoaded() const { return !m_loading; } // FIXME. Method name is inaccurate. Loading might not have started yet. + + bool isLoading() const { return m_loading; } + void setLoading(bool b) { m_loading = b; } + + virtual bool isImage() const { return false; } + bool isPrefetch() const + { +#if ENABLE(LINK_PREFETCH) + return type() == LinkPrefetch; +#else + return false; +#endif + } + + unsigned accessCount() const { return m_accessCount; } + void increaseAccessCount() { m_accessCount++; } + + // Computes the status of an object after loading. + // Updates the expire date on the cache entry file + void finish(); + + // Called by the cache if the object has been removed from the cache + // while still being referenced. This means the object should delete itself + // if the number of clients observing it ever drops to 0. + // The resource can be brought back to cache after successful revalidation. + void setInCache(bool inCache) { m_inCache = inCache; } + bool inCache() const { return m_inCache; } + + void setInLiveDecodedResourcesList(bool b) { m_inLiveDecodedResourcesList = b; } + bool inLiveDecodedResourcesList() { return m_inLiveDecodedResourcesList; } + + void setRequest(CachedResourceRequest*); + + SharedBuffer* data() const { ASSERT(!m_purgeableData); return m_data.get(); } + + void setResponse(const ResourceResponse&); + const ResourceResponse& response() const { return m_response; } + + // Sets the serialized metadata retrieved from the platform's cache. + void setSerializedCachedMetadata(const char*, size_t); + + // Caches the given metadata in association with this resource and suggests + // that the platform persist it. The dataTypeID is a pseudo-randomly chosen + // identifier that is used to distinguish data generated by the caller. + void setCachedMetadata(unsigned dataTypeID, const char*, size_t); + + // Returns cached metadata of the given type associated with this resource. + CachedMetadata* cachedMetadata(unsigned dataTypeID) const; + + bool canDelete() const { return !hasClients() && !m_request && !m_preloadCount && !m_handleCount && !m_resourceToRevalidate && !m_proxyResource; } + + bool isExpired() const; + + // List of acceptable MIME types separated by ",". + // A MIME type may contain a wildcard, e.g. "text/*". + String accept() const { return m_accept; } + void setAccept(const String& accept) { m_accept = accept; } + + bool errorOccurred() const { return (status() == LoadError || status() == DecodeError); } + + bool sendResourceLoadCallbacks() const { return m_sendResourceLoadCallbacks; } + + virtual void destroyDecodedData() { } + + void setOwningCachedResourceLoader(CachedResourceLoader* cachedResourceLoader) { m_owningCachedResourceLoader = cachedResourceLoader; } + + bool isPreloaded() const { return m_preloadCount; } + void increasePreloadCount() { ++m_preloadCount; } + void decreasePreloadCount() { ASSERT(m_preloadCount); --m_preloadCount; } + + void registerHandle(CachedResourceHandleBase* h); + void unregisterHandle(CachedResourceHandleBase* h); + + bool canUseCacheValidator() const; + bool mustRevalidateDueToCacheHeaders(CachePolicy) const; + bool isCacheValidator() const { return m_resourceToRevalidate; } + CachedResource* resourceToRevalidate() const { return m_resourceToRevalidate; } + + bool isPurgeable() const; + bool wasPurged() const; + + // This is used by the archive machinery to get at a purged resource without + // triggering a load. We should make it protected again if we can find a + // better way to handle the archive case. + bool makePurgeable(bool purgeable); + + // HTTP revalidation support methods for CachedResourceLoader. + void setResourceToRevalidate(CachedResource*); + void switchClientsToRevalidatedResource(); + void clearResourceToRevalidate(); + void updateResponseAfterRevalidation(const ResourceResponse& validatingResponse); + +protected: + void setEncodedSize(unsigned); + void setDecodedSize(unsigned); + void didAccessDecodedData(double timeStamp); + + bool isSafeToMakePurgeable() const; + + HashCountedSet<CachedResourceClient*> m_clients; + + String m_url; + String m_accept; + CachedResourceRequest* m_request; + ResourceLoadPriority m_loadPriority; + + ResourceResponse m_response; + double m_responseTimestamp; + + RefPtr<SharedBuffer> m_data; + OwnPtr<PurgeableBuffer> m_purgeableData; + +private: + void addClientToSet(CachedResourceClient*); + + virtual PurgePriority purgePriority() const { return PurgeDefault; } + + double currentAge() const; + double freshnessLifetime() const; + + RefPtr<CachedMetadata> m_cachedMetadata; + + double m_lastDecodedAccessTime; // Used as a "thrash guard" in the cache + + unsigned m_encodedSize; + unsigned m_decodedSize; + unsigned m_accessCount; + unsigned m_handleCount; + unsigned m_preloadCount; + + unsigned m_preloadResult : 2; // PreloadResult + + bool m_inLiveDecodedResourcesList : 1; + bool m_requestedFromNetworkingLayer : 1; + bool m_sendResourceLoadCallbacks : 1; + + bool m_inCache : 1; + bool m_loading : 1; + + unsigned m_type : 3; // Type + unsigned m_status : 3; // Status + +#ifndef NDEBUG + bool m_deleted; + unsigned m_lruIndex; +#endif + + CachedResource* m_nextInAllResourcesList; + CachedResource* m_prevInAllResourcesList; + + CachedResource* m_nextInLiveResourcesList; + CachedResource* m_prevInLiveResourcesList; + + CachedResourceLoader* m_owningCachedResourceLoader; // only non-0 for resources that are not in the cache + + // If this field is non-null we are using the resource as a proxy for checking whether an existing resource is still up to date + // using HTTP If-Modified-Since/If-None-Match headers. If the response is 304 all clients of this resource are moved + // to to be clients of m_resourceToRevalidate and the resource is deleted. If not, the field is zeroed and this + // resources becomes normal resource load. + CachedResource* m_resourceToRevalidate; + + // If this field is non-null, the resource has a proxy for checking whether it is still up to date (see m_resourceToRevalidate). + CachedResource* m_proxyResource; + + // These handles will need to be updated to point to the m_resourceToRevalidate in case we get 304 response. + HashSet<CachedResourceHandleBase*> m_handlesToRevalidate; +}; + +} + +#endif diff --git a/Source/WebCore/loader/cache/CachedResourceClient.h b/Source/WebCore/loader/cache/CachedResourceClient.h new file mode 100644 index 0000000..275d331 --- /dev/null +++ b/Source/WebCore/loader/cache/CachedResourceClient.h @@ -0,0 +1,71 @@ +/* + Copyright (C) 1998 Lars Knoll (knoll@mpi-hd.mpg.de) + Copyright (C) 2001 Dirk Mueller <mueller@kde.org> + Copyright (C) 2004, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved. + + 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. +*/ + +#ifndef CachedResourceClient_h +#define CachedResourceClient_h + +#include <wtf/FastAllocBase.h> +#include <wtf/Forward.h> + +namespace WebCore { + + class CachedCSSStyleSheet; + class CachedFont; + class CachedResource; + class CachedImage; + class Image; + class IntRect; + class KURL; + + /** + * @internal + * + * a client who wants to load stylesheets, images or scripts from the web has to + * inherit from this class and overload one of the 3 functions + * + */ + class CachedResourceClient : public FastAllocBase + { + public: + virtual ~CachedResourceClient() { } + + // Called whenever a frame of an image changes, either because we got more data from the network or + // because we are animating. If not null, the IntRect is the changed rect of the image. + virtual void imageChanged(CachedImage*, const IntRect* = 0) { }; + + // Called to find out if this client wants to actually display the image. Used to tell when we + // can halt animation. Content nodes that hold image refs for example would not render the image, + // but RenderImages would (assuming they have visibility: visible and their render tree isn't hidden + // e.g., in the b/f cache or in a background tab). + virtual bool willRenderImage(CachedImage*) { return false; } + + virtual void setCSSStyleSheet(const String& /* href */, const KURL& /* baseURL */, const String& /* charset */, const CachedCSSStyleSheet*) { } + virtual void setXSLStyleSheet(const String& /* href */, const KURL& /* baseURL */, const String& /* sheet */) { } + virtual void fontLoaded(CachedFont*) {}; + virtual void notifyFinished(CachedResource*) { } + }; + +} + +#endif diff --git a/Source/WebCore/loader/cache/CachedResourceClientWalker.cpp b/Source/WebCore/loader/cache/CachedResourceClientWalker.cpp new file mode 100644 index 0000000..142a2a1 --- /dev/null +++ b/Source/WebCore/loader/cache/CachedResourceClientWalker.cpp @@ -0,0 +1,53 @@ +/* + 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, 2007 Apple Inc. All rights reserved. + + 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 "CachedResourceClientWalker.h" + +namespace WebCore { + +CachedResourceClientWalker::CachedResourceClientWalker(const HashCountedSet<CachedResourceClient*>& set) + : m_clientSet(set), m_clientVector(set.size()), m_index(0) +{ + typedef HashCountedSet<CachedResourceClient*>::const_iterator Iterator; + Iterator end = set.end(); + size_t clientIndex = 0; + for (Iterator current = set.begin(); current != end; ++current) + m_clientVector[clientIndex++] = current->first; +} + +CachedResourceClient* CachedResourceClientWalker::next() +{ + size_t size = m_clientVector.size(); + while (m_index < size) { + CachedResourceClient* next = m_clientVector[m_index++]; + if (m_clientSet.contains(next)) + return next; + } + + return 0; +} + +} diff --git a/Source/WebCore/loader/cache/CachedResourceClientWalker.h b/Source/WebCore/loader/cache/CachedResourceClientWalker.h new file mode 100644 index 0000000..d079584 --- /dev/null +++ b/Source/WebCore/loader/cache/CachedResourceClientWalker.h @@ -0,0 +1,49 @@ +/* + Copyright (C) 1998 Lars Knoll (knoll@mpi-hd.mpg.de) + Copyright (C) 2001 Dirk Mueller <mueller@kde.org> + Copyright (C) 2004, 2005, 2006, 2007 Apple Inc. All rights reserved. + + 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. +*/ + +#ifndef CachedResourceClientWalker_h +#define CachedResourceClientWalker_h + +#include <wtf/HashCountedSet.h> +#include <wtf/Vector.h> + +namespace WebCore { + + class CachedResourceClient; + + // Call this "walker" instead of iterator so people won't expect Qt or STL-style iterator interface. + // Just keep calling next() on this. It's safe from deletions of items. + class CachedResourceClientWalker { + public: + CachedResourceClientWalker(const HashCountedSet<CachedResourceClient*>&); + CachedResourceClient* next(); + private: + const HashCountedSet<CachedResourceClient*>& m_clientSet; + Vector<CachedResourceClient*> m_clientVector; + size_t m_index; + }; + +} + +#endif diff --git a/Source/WebCore/loader/cache/CachedResourceHandle.cpp b/Source/WebCore/loader/cache/CachedResourceHandle.cpp new file mode 100644 index 0000000..871292c --- /dev/null +++ b/Source/WebCore/loader/cache/CachedResourceHandle.cpp @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2008 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "CachedResourceHandle.h" + +namespace WebCore { + +void CachedResourceHandleBase::setResource(CachedResource* resource) +{ + if (resource == m_resource) + return; + if (m_resource) + m_resource->unregisterHandle(this); + m_resource = resource; + if (m_resource) + m_resource->registerHandle(this); +} + +} diff --git a/Source/WebCore/loader/cache/CachedResourceHandle.h b/Source/WebCore/loader/cache/CachedResourceHandle.h new file mode 100644 index 0000000..7d485bf --- /dev/null +++ b/Source/WebCore/loader/cache/CachedResourceHandle.h @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2008 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef CachedResourceHandle_h +#define CachedResourceHandle_h + +#include "CachedResource.h" + +namespace WebCore { + + class CachedResourceHandleBase { + public: + ~CachedResourceHandleBase() { if (m_resource) m_resource->unregisterHandle(this); } + CachedResource* get() const { return m_resource; } + + bool operator!() const { return !m_resource; } + + // This conversion operator allows implicit conversion to bool but not to other integer types. + // Parenthesis is needed for winscw compiler to resolve class qualifier in this case. + typedef CachedResource* (CachedResourceHandleBase::*UnspecifiedBoolType); + operator UnspecifiedBoolType() const { return m_resource ? &CachedResourceHandleBase::m_resource : 0; } + + protected: + CachedResourceHandleBase() : m_resource(0) {} + CachedResourceHandleBase(CachedResource* res) { m_resource = res; if (m_resource) m_resource->registerHandle(this); } + CachedResourceHandleBase(const CachedResourceHandleBase& o) : m_resource(o.m_resource) { if (m_resource) m_resource->registerHandle(this); } + + void setResource(CachedResource*); + + private: + CachedResourceHandleBase& operator=(const CachedResourceHandleBase&) { return *this; } + + friend class CachedResource; + + CachedResource* m_resource; + }; + + template <class R> class CachedResourceHandle : public CachedResourceHandleBase { + public: + CachedResourceHandle() { } + CachedResourceHandle(R* res); + CachedResourceHandle(const CachedResourceHandle<R>& o) : CachedResourceHandleBase(o) { } + + R* get() const { return reinterpret_cast<R*>(CachedResourceHandleBase::get()); } + R* operator->() const { return get(); } + + CachedResourceHandle& operator=(R* res) { setResource(res); return *this; } + CachedResourceHandle& operator=(const CachedResourceHandle& o) { setResource(o.get()); return *this; } + bool operator==(const CachedResourceHandleBase& o) const { return get() == o.get(); } + bool operator!=(const CachedResourceHandleBase& o) const { return get() != o.get(); } + }; + + // Don't inline for winscw compiler to prevent the compiler aggressively resolving + // the base class of R* when CachedResourceHandler<T>(R*) is inlined. The bug is + // reported at: https://xdabug001.ext.nokia.com/bugzilla/show_bug.cgi?id=9812. + template <class R> +#if !COMPILER(WINSCW) + inline +#endif + CachedResourceHandle<R>::CachedResourceHandle(R* res) : CachedResourceHandleBase(res) + { + } + + template <class R, class RR> bool operator==(const CachedResourceHandle<R>& h, const RR* res) + { + return h.get() == res; + } + template <class R, class RR> bool operator==(const RR* res, const CachedResourceHandle<R>& h) + { + return h.get() == res; + } + template <class R, class RR> bool operator!=(const CachedResourceHandle<R>& h, const RR* res) + { + return h.get() != res; + } + template <class R, class RR> bool operator!=(const RR* res, const CachedResourceHandle<R>& h) + { + return h.get() != res; + } +} + +#endif diff --git a/Source/WebCore/loader/cache/CachedResourceLoader.cpp b/Source/WebCore/loader/cache/CachedResourceLoader.cpp new file mode 100644 index 0000000..3fcace6 --- /dev/null +++ b/Source/WebCore/loader/cache/CachedResourceLoader.cpp @@ -0,0 +1,733 @@ +/* + 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 "CachedResourceLoader.h" + +#include "CachedCSSStyleSheet.h" +#include "CachedFont.h" +#include "CachedImage.h" +#include "CachedResourceRequest.h" +#include "CachedScript.h" +#include "CachedXSLStyleSheet.h" +#include "Console.h" +#include "DOMWindow.h" +#include "Document.h" +#include "Frame.h" +#include "FrameLoader.h" +#include "FrameLoaderClient.h" +#include "HTMLElement.h" +#include "Logging.h" +#include "MemoryCache.h" +#include "PingLoader.h" +#include "SecurityOrigin.h" +#include "Settings.h" +#include <wtf/text/StringConcatenate.h> + +#define PRELOAD_DEBUG 0 + +namespace WebCore { + +static CachedResource* createResource(CachedResource::Type type, const KURL& url, const String& charset) +{ + switch (type) { + case CachedResource::ImageResource: + return new CachedImage(url.string()); + case CachedResource::CSSStyleSheet: + return new CachedCSSStyleSheet(url.string(), charset); + case CachedResource::Script: + return new CachedScript(url.string(), charset); + case CachedResource::FontResource: + return new CachedFont(url.string()); +#if ENABLE(XSLT) + case CachedResource::XSLStyleSheet: + return new CachedXSLStyleSheet(url.string()); +#endif +#if ENABLE(LINK_PREFETCH) + case CachedResource::LinkPrefetch: + return new CachedResource(url.string(), CachedResource::LinkPrefetch); +#endif + } + ASSERT_NOT_REACHED(); + return 0; +} + +CachedResourceLoader::CachedResourceLoader(Document* document) + : m_cache(cache()) + , m_document(document) + , m_requestCount(0) +#ifdef ANDROID_BLOCK_NETWORK_IMAGE + , m_blockNetworkImage(false) +#endif + , m_autoLoadImages(true) + , m_loadFinishing(false) + , m_allowStaleResources(false) +{ + m_cache->addCachedResourceLoader(this); +} + +CachedResourceLoader::~CachedResourceLoader() +{ + cancelRequests(); + clearPreloads(); + DocumentResourceMap::iterator end = m_documentResources.end(); + for (DocumentResourceMap::iterator it = m_documentResources.begin(); it != end; ++it) + it->second->setOwningCachedResourceLoader(0); + m_cache->removeCachedResourceLoader(this); + + // Make sure no requests still point to this CachedResourceLoader + ASSERT(m_requestCount == 0); +} + +CachedResource* CachedResourceLoader::cachedResource(const String& resourceURL) const +{ + KURL url = m_document->completeURL(resourceURL); + return cachedResource(url); +} + +CachedResource* CachedResourceLoader::cachedResource(const KURL& resourceURL) const +{ + KURL url = MemoryCache::removeFragmentIdentifierIfNeeded(resourceURL); + return m_documentResources.get(url).get(); +} + +Frame* CachedResourceLoader::frame() const +{ + return m_document->frame(); +} + +CachedImage* CachedResourceLoader::requestImage(const String& url) +{ + if (Frame* f = frame()) { + Settings* settings = f->settings(); + if (!f->loader()->client()->allowImages(!settings || settings->areImagesEnabled())) + return 0; + + if (f->loader()->pageDismissalEventBeingDispatched()) { + KURL completeURL = m_document->completeURL(url); + if (completeURL.isValid() && canRequest(CachedResource::ImageResource, completeURL)) + PingLoader::loadImage(f, completeURL); + return 0; + } + } + CachedImage* resource = static_cast<CachedImage*>(requestResource(CachedResource::ImageResource, url, String())); + if (autoLoadImages() && resource && resource->stillNeedsLoad()) { +#ifdef ANDROID_BLOCK_NETWORK_IMAGE + if (shouldBlockNetworkImage(url)) { + return resource; + } +#endif + resource->setLoading(true); + load(resource, true); + } + return resource; +} + +CachedFont* CachedResourceLoader::requestFont(const String& url) +{ + return static_cast<CachedFont*>(requestResource(CachedResource::FontResource, url, String())); +} + +CachedCSSStyleSheet* CachedResourceLoader::requestCSSStyleSheet(const String& url, const String& charset, ResourceLoadPriority priority) +{ + return static_cast<CachedCSSStyleSheet*>(requestResource(CachedResource::CSSStyleSheet, url, charset, priority)); +} + +CachedCSSStyleSheet* CachedResourceLoader::requestUserCSSStyleSheet(const String& requestURL, const String& charset) +{ + KURL url = MemoryCache::removeFragmentIdentifierIfNeeded(KURL(KURL(), requestURL)); + + if (CachedResource* existing = cache()->resourceForURL(url)) { + if (existing->type() == CachedResource::CSSStyleSheet) + return static_cast<CachedCSSStyleSheet*>(existing); + cache()->remove(existing); + } + CachedCSSStyleSheet* userSheet = new CachedCSSStyleSheet(url, charset); + + bool inCache = cache()->add(userSheet); + if (!inCache) + userSheet->setInCache(true); + + userSheet->load(this, /*incremental*/ false, SkipSecurityCheck, /*sendResourceLoadCallbacks*/ false); + + if (!inCache) + userSheet->setInCache(false); + + return userSheet; +} + +CachedScript* CachedResourceLoader::requestScript(const String& url, const String& charset) +{ + return static_cast<CachedScript*>(requestResource(CachedResource::Script, url, charset)); +} + +#if ENABLE(XSLT) +CachedXSLStyleSheet* CachedResourceLoader::requestXSLStyleSheet(const String& url) +{ + return static_cast<CachedXSLStyleSheet*>(requestResource(CachedResource::XSLStyleSheet, url, String())); +} +#endif + +#if ENABLE(LINK_PREFETCH) +CachedResource* CachedResourceLoader::requestLinkPrefetch(const String& url) +{ + ASSERT(frame()); + return requestResource(CachedResource::LinkPrefetch, url, String()); +} +#endif + +bool CachedResourceLoader::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: + if (!m_document->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 + // These resource can inject script into the current document. + if (Frame* f = frame()) + f->loader()->checkIfRunInsecureContent(m_document->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* CachedResourceLoader::requestResource(CachedResource::Type type, const String& resourceURL, const String& charset, ResourceLoadPriority priority, bool forPreload) +{ + KURL url = m_document->completeURL(resourceURL); + + LOG(ResourceLoading, "CachedResourceLoader::requestResource '%s', charset '%s', priority=%d, forPreload=%u", url.string().latin1().data(), charset.latin1().data(), priority, forPreload); + + // If only the fragment identifiers differ, it is the same resource. + url = MemoryCache::removeFragmentIdentifierIfNeeded(url); + + if (!url.isValid()) + return 0; + + if (!canRequest(type, url)) + return 0; + + // FIXME: Figure out what is the correct way to merge this security check with the one above. + if (!document()->securityOrigin()->canDisplay(url)) { + if (!forPreload) + FrameLoader::reportLocalLoadFailed(document()->frame(), url.string()); + LOG(ResourceLoading, "CachedResourceLoader::requestResource URL was not allowed by SecurityOrigin::canDisplay"); + return 0; + } + + if (cache()->disabled()) { + DocumentResourceMap::iterator it = m_documentResources.find(url.string()); + if (it != m_documentResources.end()) { + it->second->setOwningCachedResourceLoader(0); + m_documentResources.remove(it); + } + } + + // See if we can use an existing resource from the cache. + CachedResource* resource = cache()->resourceForURL(url); + + switch (determineRevalidationPolicy(type, forPreload, resource)) { + case Load: + resource = loadResource(type, url, charset, priority); + break; + case Reload: + cache()->remove(resource); + resource = loadResource(type, url, charset, priority); + break; + case Revalidate: + resource = revalidateResource(resource, priority); + break; + case Use: + cache()->resourceAccessed(resource); + notifyLoadedFromMemoryCache(resource); + break; + } + + if (!resource) + return 0; + + ASSERT(resource->url() == url.string()); + m_documentResources.set(resource->url(), resource); + + return resource; +} + +CachedResource* CachedResourceLoader::revalidateResource(CachedResource* resource, ResourceLoadPriority priority) +{ + ASSERT(resource); + ASSERT(resource->inCache()); + ASSERT(!cache()->disabled()); + ASSERT(resource->canUseCacheValidator()); + ASSERT(!resource->resourceToRevalidate()); + + const String& url = resource->url(); + CachedResource* newResource = createResource(resource->type(), KURL(ParsedURLString, url), resource->encoding()); + + LOG(ResourceLoading, "Resource %p created to revalidate %p", newResource, resource); + newResource->setResourceToRevalidate(resource); + + cache()->remove(resource); + cache()->add(newResource); + + newResource->setLoadPriority(priority); + newResource->load(this); + + m_validatedURLs.add(url); + return newResource; +} + +CachedResource* CachedResourceLoader::loadResource(CachedResource::Type type, const KURL& url, const String& charset, ResourceLoadPriority priority) +{ + ASSERT(!cache()->resourceForURL(url)); + + LOG(ResourceLoading, "Loading CachedResource for '%s'.", url.string().latin1().data()); + + CachedResource* resource = createResource(type, url, charset); + + bool inCache = cache()->add(resource); + + // Pretend the resource is in the cache, to prevent it from being deleted during the load() call. + // FIXME: CachedResource should just use normal refcounting instead. + if (!inCache) + resource->setInCache(true); + + resource->setLoadPriority(priority); + resource->load(this); + + if (!inCache) { + resource->setOwningCachedResourceLoader(this); + resource->setInCache(false); + } + + // We don't support immediate loads, but we do support immediate failure. + if (resource->errorOccurred()) { + if (inCache) + cache()->remove(resource); + else + delete resource; + return 0; + } + + m_validatedURLs.add(url.string()); + return resource; +} + +CachedResourceLoader::RevalidationPolicy CachedResourceLoader::determineRevalidationPolicy(CachedResource::Type type, bool forPreload, CachedResource* existingResource) const +{ + if (!existingResource) + return Load; + + // We already have a preload going for this URL. + if (forPreload && existingResource->isPreloaded()) + return Use; + + // If the same URL has been loaded as a different type, we need to reload. + if (existingResource->type() != type) { + LOG(ResourceLoading, "CachedResourceLoader::determineRevalidationPolicy reloading due to type mismatch."); + return Reload; + } + + // Don't reload resources while pasting. + if (m_allowStaleResources) + return Use; + + // Alwaus use preloads. + if (existingResource->isPreloaded()) + return Use; + + // Avoid loading the same resource multiple times for a single document, even if the cache policies would tell us to. + if (m_validatedURLs.contains(existingResource->url())) + return Use; + + // CachePolicyReload always reloads + if (cachePolicy() == CachePolicyReload) { + LOG(ResourceLoading, "CachedResourceLoader::determineRevalidationPolicy reloading due to CachePolicyReload."); + return Reload; + } + + // CachePolicyHistoryBuffer uses the cache no matter what. + if (cachePolicy() == CachePolicyHistoryBuffer) + return Use; + + // We'll try to reload the resource if it failed last time. + if (existingResource->errorOccurred()) { + LOG(ResourceLoading, "CachedResourceLoader::determineRevalidationPolicye reloading due to resource being in the error state"); + return Reload; + } + + // For resources that are not yet loaded we ignore the cache policy. + if (existingResource->isLoading()) + return Use; + + // Check if the cache headers requires us to revalidate (cache expiration for example). + if (existingResource->mustRevalidateDueToCacheHeaders(cachePolicy())) { + // See if the resource has usable ETag or Last-modified headers. + if (existingResource->canUseCacheValidator()) + return Revalidate; + + // No, must reload. + LOG(ResourceLoading, "CachedResourceLoader::determineRevalidationPolicy reloading due to missing cache validators."); + return Reload; + } + + return Use; +} + +void CachedResourceLoader::printAccessDeniedMessage(const KURL& url) const +{ + if (url.isNull()) + return; + + if (!frame()) + return; + + Settings* settings = frame()->settings(); + if (!settings || settings->privateBrowsingEnabled()) + return; + + String message = m_document->url().isNull() ? + makeString("Unsafe attempt to load URL ", url.string(), '.') : + makeString("Unsafe attempt to load URL ", url.string(), " from frame with URL ", m_document->url().string(), ". Domains, protocols and ports must match.\n"); + + // FIXME: provide a real line number and source URL. + frame()->domWindow()->console()->addMessage(OtherMessageSource, LogMessageType, ErrorMessageLevel, message, 1, String()); +} + +void CachedResourceLoader::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<CachedImage*>(static_cast<const CachedImage*>(resource)); +#ifdef ANDROID_BLOCK_NETWORK_IMAGE + if (shouldBlockNetworkImage(image->url())) + continue; +#endif + + if (image->stillNeedsLoad()) + load(image, true); + } + } +} + +#ifdef ANDROID_BLOCK_NETWORK_IMAGE +bool CachedResourceLoader::shouldBlockNetworkImage(const String& url) const +{ + if (!m_blockNetworkImage) + return false; + + KURL kurl = m_document->completeURL(url); + if (kurl.protocolIs("http") || kurl.protocolIs("https")) + return true; + + return false; +} + +void CachedResourceLoader::setBlockNetworkImage(bool block) +{ + if (block == m_blockNetworkImage) + return; + + m_blockNetworkImage = block; + + if (!m_autoLoadImages || m_blockNetworkImage) + 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<CachedImage*>(static_cast<const CachedImage*>(resource)); + if (image->stillNeedsLoad()) + load(image, true); + } + } +} +#endif + +CachePolicy CachedResourceLoader::cachePolicy() const +{ + return frame() ? frame()->loader()->subresourceCachePolicy() : CachePolicyVerify; +} + +void CachedResourceLoader::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 CachedResourceLoader::load(CachedResource* resource, bool incremental, SecurityCheckPolicy securityCheck, bool sendResourceLoadCallbacks) +{ + incrementRequestCount(resource); + + RefPtr<CachedResourceRequest> request = CachedResourceRequest::load(this, resource, incremental, securityCheck, sendResourceLoadCallbacks); + if (request) + m_requests.add(request); +} + +void CachedResourceLoader::loadDone(CachedResourceRequest* request) +{ + m_loadFinishing = false; + RefPtr<CachedResourceRequest> protect(request); + if (request) + m_requests.remove(request); + if (frame()) + frame()->loader()->loadDone(); + checkForPendingPreloads(); +} + +void CachedResourceLoader::cancelRequests() +{ + clearPendingPreloads(); + Vector<CachedResourceRequest*, 256> requestsToCancel; + RequestSet::iterator end = m_requests.end(); + for (RequestSet::iterator i = m_requests.begin(); i != end; ++i) + requestsToCancel.append((*i).get()); + + for (unsigned i = 0; i < requestsToCancel.size(); ++i) + requestsToCancel[i]->didFail(true); +} + +void CachedResourceLoader::notifyLoadedFromMemoryCache(CachedResource* resource) +{ + if (!resource || !frame() || resource->status() != CachedResource::Cached) + 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 CachedResourceLoader::incrementRequestCount(const CachedResource* res) +{ + if (res->isPrefetch()) + return; + + ++m_requestCount; +} + +void CachedResourceLoader::decrementRequestCount(const CachedResource* res) +{ + if (res->isPrefetch()) + return; + + --m_requestCount; + ASSERT(m_requestCount > -1); +} + +int CachedResourceLoader::requestCount() +{ + if (m_loadFinishing) + return m_requestCount + 1; + return m_requestCount; +} + +void CachedResourceLoader::preload(CachedResource::Type type, const String& url, const String& charset, bool referencedFromBody) +{ + bool hasRendering = m_document->body() && m_document->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 CachedResourceLoader::checkForPendingPreloads() +{ + unsigned count = m_pendingPreloads.size(); + if (!count || !m_document->body() || !m_document->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_document->completeURL(preload.m_url))) + requestPreload(preload.m_type, preload.m_url, preload.m_charset); + } + m_pendingPreloads.clear(); +} + +void CachedResourceLoader::requestPreload(CachedResource::Type type, const String& url, const String& charset) +{ + String encoding; + if (type == CachedResource::Script || type == CachedResource::CSSStyleSheet) + encoding = charset.isEmpty() ? m_document->frame()->loader()->writer()->encoding() : charset; + + CachedResource* resource = requestResource(type, url, encoding, ResourceLoadPriorityUnresolved, true); + if (!resource || (m_preloads && m_preloads->contains(resource))) + return; + resource->increasePreloadCount(); + + if (!m_preloads) + m_preloads = adoptPtr(new ListHashSet<CachedResource*>); + m_preloads->add(resource); + +#if PRELOAD_DEBUG + printf("PRELOADING %s\n", resource->url().latin1().data()); +#endif +} + +void CachedResourceLoader::clearPreloads() +{ +#if PRELOAD_DEBUG + printPreloadStats(); +#endif + if (!m_preloads) + return; + + ListHashSet<CachedResource*>::iterator end = m_preloads->end(); + for (ListHashSet<CachedResource*>::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 CachedResourceLoader::clearPendingPreloads() +{ + m_pendingPreloads.clear(); +} + +#if PRELOAD_DEBUG +void CachedResourceLoader::printPreloadStats() +{ + unsigned scripts = 0; + unsigned scriptMisses = 0; + unsigned stylesheets = 0; + unsigned stylesheetMisses = 0; + unsigned images = 0; + unsigned imageMisses = 0; + ListHashSet<CachedResource*>::iterator end = m_preloads.end(); + for (ListHashSet<CachedResource*>::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 + +} diff --git a/Source/WebCore/loader/cache/CachedResourceLoader.h b/Source/WebCore/loader/cache/CachedResourceLoader.h new file mode 100644 index 0000000..1d53976 --- /dev/null +++ b/Source/WebCore/loader/cache/CachedResourceLoader.h @@ -0,0 +1,156 @@ +/* + Copyright (C) 1998 Lars Knoll (knoll@mpi-hd.mpg.de) + Copyright (C) 2001 Dirk Mueller <mueller@kde.org> + Copyright (C) 2004, 2005, 2006 Apple Computer, Inc. + 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. +*/ + +#ifndef CachedResourceLoader_h +#define CachedResourceLoader_h + +#include "CachedResource.h" +#include "CachedResourceHandle.h" +#include "CachePolicy.h" +#include "ResourceLoadPriority.h" +#include <wtf/HashMap.h> +#include <wtf/HashSet.h> +#include <wtf/ListHashSet.h> +#include <wtf/text/StringHash.h> + +namespace WebCore { + +class CachedCSSStyleSheet; +class CachedFont; +class CachedImage; +class CachedResourceRequest; +class CachedScript; +class CachedXSLStyleSheet; +class Document; +class Frame; +class ImageLoader; +class KURL; + +// The CachedResourceLoader manages the loading of scripts/images/stylesheets for a single document. +class CachedResourceLoader : public Noncopyable { +friend class MemoryCache; +friend class ImageLoader; + +public: + CachedResourceLoader(Document*); + ~CachedResourceLoader(); + + CachedImage* requestImage(const String& url); + CachedCSSStyleSheet* requestCSSStyleSheet(const String& url, const String& charset, ResourceLoadPriority priority = ResourceLoadPriorityUnresolved); + CachedCSSStyleSheet* requestUserCSSStyleSheet(const String& url, const String& charset); + CachedScript* requestScript(const String& url, const String& charset); + CachedFont* requestFont(const String& url); + +#if ENABLE(XSLT) + CachedXSLStyleSheet* requestXSLStyleSheet(const String& url); +#endif +#if ENABLE(LINK_PREFETCH) + CachedResource* requestLinkPrefetch(const String &url); +#endif + + // Logs an access denied message to the console for the specified URL. + void printAccessDeniedMessage(const KURL& url) const; + + CachedResource* cachedResource(const String& url) const; + CachedResource* cachedResource(const KURL& url) const; + + typedef HashMap<String, CachedResourceHandle<CachedResource> > DocumentResourceMap; + const DocumentResourceMap& allCachedResources() const { return m_documentResources; } + + bool autoLoadImages() const { return m_autoLoadImages; } + void setAutoLoadImages(bool); + +#ifdef ANDROID_BLOCK_NETWORK_IMAGE + bool blockNetworkImage() const { return m_blockNetworkImage; } + void setBlockNetworkImage(bool); + bool shouldBlockNetworkImage(const String& url) const; +#endif + + CachePolicy cachePolicy() const; + + Frame* frame() const; // Can be NULL + Document* document() const { return m_document; } + + void removeCachedResource(CachedResource*) const; + + void load(CachedResource*, bool incremental = false, SecurityCheckPolicy = DoSecurityCheck, bool sendResourceLoadCallbacks = true); + void loadFinishing() { m_loadFinishing = true; } + void loadDone(CachedResourceRequest*); + void cancelRequests(); + + void setAllowStaleResources(bool allowStaleResources) { m_allowStaleResources = allowStaleResources; } + + void incrementRequestCount(const CachedResource*); + void decrementRequestCount(const CachedResource*); + int requestCount(); + + void clearPreloads(); + void clearPendingPreloads(); + void preload(CachedResource::Type, const String& url, const String& charset, bool referencedFromBody); + void checkForPendingPreloads(); + void printPreloadStats(); + +private: + CachedResource* requestResource(CachedResource::Type, const String& url, const String& charset, ResourceLoadPriority priority = ResourceLoadPriorityUnresolved, bool isPreload = false); + CachedResource* revalidateResource(CachedResource*, ResourceLoadPriority priority); + CachedResource* loadResource(CachedResource::Type, const KURL&, const String& charset, ResourceLoadPriority priority); + void requestPreload(CachedResource::Type, const String& url, const String& charset); + + enum RevalidationPolicy { Use, Revalidate, Reload, Load }; + RevalidationPolicy determineRevalidationPolicy(CachedResource::Type, bool forPreload, CachedResource* existingResource) const; + + void notifyLoadedFromMemoryCache(CachedResource*); + bool canRequest(CachedResource::Type, const KURL&); + + MemoryCache* m_cache; + HashSet<String> m_validatedURLs; + mutable DocumentResourceMap m_documentResources; + Document* m_document; + + typedef HashSet<RefPtr<CachedResourceRequest> > RequestSet; + RequestSet m_requests; + + int m_requestCount; + + OwnPtr<ListHashSet<CachedResource*> > m_preloads; + struct PendingPreload { + CachedResource::Type m_type; + String m_url; + String m_charset; + }; + Vector<PendingPreload> m_pendingPreloads; + + //29 bits left +#ifdef ANDROID_BLOCK_NETWORK_IMAGE + bool m_blockNetworkImage : 1; +#endif + bool m_autoLoadImages : 1; + bool m_loadFinishing : 1; + bool m_allowStaleResources : 1; +}; + +} + +#endif diff --git a/Source/WebCore/loader/cache/CachedResourceRequest.cpp b/Source/WebCore/loader/cache/CachedResourceRequest.cpp new file mode 100644 index 0000000..827bb8e --- /dev/null +++ b/Source/WebCore/loader/cache/CachedResourceRequest.cpp @@ -0,0 +1,280 @@ +/* + 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) 2006 Samuel Weinig (sam.weinig@gmail.com) + Copyright (C) 2004, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved. + + 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. +*/ + +#include "config.h" +#include "CachedResourceRequest.h" + +#include "MemoryCache.h" +#include "CachedImage.h" +#include "CachedResource.h" +#include "CachedResourceLoader.h" +#include "Frame.h" +#include "FrameLoader.h" +#include "Logging.h" +#include "ResourceHandle.h" +#include "ResourceLoadScheduler.h" +#include "ResourceRequest.h" +#include "ResourceResponse.h" +#include "SharedBuffer.h" +#include <wtf/Assertions.h> +#include <wtf/Vector.h> +#include <wtf/text/CString.h> + +namespace WebCore { + +static ResourceRequest::TargetType cachedResourceTypeToTargetType(CachedResource::Type type) +{ + switch (type) { + case CachedResource::CSSStyleSheet: +#if ENABLE(XSLT) + case CachedResource::XSLStyleSheet: +#endif + return ResourceRequest::TargetIsStyleSheet; + case CachedResource::Script: + return ResourceRequest::TargetIsScript; + case CachedResource::FontResource: + return ResourceRequest::TargetIsFontResource; + case CachedResource::ImageResource: + return ResourceRequest::TargetIsImage; +#if ENABLE(LINK_PREFETCH) + case CachedResource::LinkPrefetch: + return ResourceRequest::TargetIsPrefetch; +#endif + } + ASSERT_NOT_REACHED(); + return ResourceRequest::TargetIsSubresource; +} + +CachedResourceRequest::CachedResourceRequest(CachedResourceLoader* cachedResourceLoader, CachedResource* resource, bool incremental) + : m_cachedResourceLoader(cachedResourceLoader) + , m_resource(resource) + , m_incremental(incremental) + , m_multipart(false) + , m_finishing(false) +{ + m_resource->setRequest(this); +} + +CachedResourceRequest::~CachedResourceRequest() +{ + m_resource->setRequest(0); +} + +PassRefPtr<CachedResourceRequest> CachedResourceRequest::load(CachedResourceLoader* cachedResourceLoader, CachedResource* resource, bool incremental, SecurityCheckPolicy securityCheck, bool sendResourceLoadCallbacks) +{ + RefPtr<CachedResourceRequest> request = adoptRef(new CachedResourceRequest(cachedResourceLoader, resource, incremental)); + + ResourceRequest resourceRequest(resource->url()); + resourceRequest.setTargetType(cachedResourceTypeToTargetType(resource->type())); + + if (!resource->accept().isEmpty()) + resourceRequest.setHTTPAccept(resource->accept()); + + if (resource->isCacheValidator()) { + CachedResource* resourceToRevalidate = resource->resourceToRevalidate(); + ASSERT(resourceToRevalidate->canUseCacheValidator()); + ASSERT(resourceToRevalidate->isLoaded()); + const String& lastModified = resourceToRevalidate->response().httpHeaderField("Last-Modified"); + const String& eTag = resourceToRevalidate->response().httpHeaderField("ETag"); + if (!lastModified.isEmpty() || !eTag.isEmpty()) { + ASSERT(cachedResourceLoader->cachePolicy() != CachePolicyReload); + if (cachedResourceLoader->cachePolicy() == CachePolicyRevalidate) + resourceRequest.setHTTPHeaderField("Cache-Control", "max-age=0"); + if (!lastModified.isEmpty()) + resourceRequest.setHTTPHeaderField("If-Modified-Since", lastModified); + if (!eTag.isEmpty()) + resourceRequest.setHTTPHeaderField("If-None-Match", eTag); + } + } + +#if ENABLE(LINK_PREFETCH) + if (resource->type() == CachedResource::LinkPrefetch) + resourceRequest.setHTTPHeaderField("Purpose", "prefetch"); +#endif + + ResourceLoadPriority priority = resource->loadPriority(); + + RefPtr<SubresourceLoader> loader = resourceLoadScheduler()->scheduleSubresourceLoad(cachedResourceLoader->document()->frame(), + request.get(), resourceRequest, priority, securityCheck, sendResourceLoadCallbacks); + if (!loader || loader->reachedTerminalState()) { + // FIXME: What if resources in other frames were waiting for this revalidation? + LOG(ResourceLoading, "Cannot start loading '%s'", resource->url().latin1().data()); + cachedResourceLoader->decrementRequestCount(resource); + cachedResourceLoader->loadFinishing(); + if (resource->resourceToRevalidate()) + cache()->revalidationFailed(resource); + resource->error(CachedResource::LoadError); + cachedResourceLoader->loadDone(0); + return 0; + } + request->m_loader = loader; + return request.release(); +} + +void CachedResourceRequest::willSendRequest(SubresourceLoader*, ResourceRequest&, const ResourceResponse&) +{ + m_resource->setRequestedFromNetworkingLayer(); +} + +void CachedResourceRequest::didFinishLoading(SubresourceLoader* loader) +{ + if (m_finishing) + return; + + ASSERT(loader == m_loader.get()); + ASSERT(!m_resource->resourceToRevalidate()); + LOG(ResourceLoading, "Received '%s'.", m_resource->url().latin1().data()); + + // Prevent the document from being destroyed before we are done with + // the cachedResourceLoader that it will delete when the document gets deleted. + RefPtr<Document> protector(m_cachedResourceLoader->document()); + if (!m_multipart) + m_cachedResourceLoader->decrementRequestCount(m_resource); + m_finishing = true; + + // If we got a 4xx response, we're pretending to have received a network + // error, so we can't send the successful data() and finish() callbacks. + if (!m_resource->errorOccurred()) { + m_cachedResourceLoader->loadFinishing(); + m_resource->data(loader->resourceData(), true); + if (!m_resource->errorOccurred()) + m_resource->finish(); + } + m_cachedResourceLoader->loadDone(this); +} + +void CachedResourceRequest::didFail(SubresourceLoader*, const ResourceError&) +{ + if (!m_loader) + return; + didFail(); +} + +void CachedResourceRequest::didFail(bool cancelled) +{ + if (m_finishing) + return; + + LOG(ResourceLoading, "Failed to load '%s' (cancelled=%d).\n", m_resource->url().latin1().data(), cancelled); + + // Prevent the document from being destroyed before we are done with + // the cachedResourceLoader that it will delete when the document gets deleted. + RefPtr<Document> protector(m_cachedResourceLoader->document()); + if (!m_multipart) + m_cachedResourceLoader->decrementRequestCount(m_resource); + m_finishing = true; + m_loader->clearClient(); + + if (m_resource->resourceToRevalidate()) + cache()->revalidationFailed(m_resource); + + if (!cancelled) { + m_cachedResourceLoader->loadFinishing(); + m_resource->error(CachedResource::LoadError); + } + + if (cancelled || !m_resource->isPreloaded()) + cache()->remove(m_resource); + + m_cachedResourceLoader->loadDone(this); +} + +void CachedResourceRequest::didReceiveResponse(SubresourceLoader* loader, const ResourceResponse& response) +{ + ASSERT(loader == m_loader.get()); + if (m_resource->isCacheValidator()) { + if (response.httpStatusCode() == 304) { + // 304 Not modified / Use local copy + loader->clearClient(); + RefPtr<Document> protector(m_cachedResourceLoader->document()); + m_cachedResourceLoader->decrementRequestCount(m_resource); + m_finishing = true; + + // Existing resource is ok, just use it updating the expiration time. + cache()->revalidationSucceeded(m_resource, response); + + if (m_cachedResourceLoader->frame()) + m_cachedResourceLoader->frame()->loader()->checkCompleted(); + + m_cachedResourceLoader->loadDone(this); + return; + } + // Did not get 304 response, continue as a regular resource load. + cache()->revalidationFailed(m_resource); + } + + m_resource->setResponse(response); + + String encoding = response.textEncodingName(); + if (!encoding.isNull()) + m_resource->setEncoding(encoding); + + if (m_multipart) { + ASSERT(m_resource->isImage()); + static_cast<CachedImage*>(m_resource)->clear(); + if (m_cachedResourceLoader->frame()) + m_cachedResourceLoader->frame()->loader()->checkCompleted(); + } else if (response.isMultipart()) { + m_multipart = true; + + // We don't count multiParts in a CachedResourceLoader's request count + m_cachedResourceLoader->decrementRequestCount(m_resource); + + // If we get a multipart response, we must have a handle + ASSERT(loader->handle()); + if (!m_resource->isImage()) + loader->handle()->cancel(); + } +} + +void CachedResourceRequest::didReceiveData(SubresourceLoader* loader, const char* data, int size) +{ + ASSERT(loader == m_loader.get()); + ASSERT(!m_resource->isCacheValidator()); + + if (m_resource->errorOccurred()) + return; + + if (m_resource->response().httpStatusCode() >= 400) { + if (!m_resource->shouldIgnoreHTTPStatusCodeErrors()) + m_resource->error(CachedResource::LoadError); + return; + } + + // Set the data. + if (m_multipart) { + // The loader delivers the data in a multipart section all at once, send eof. + // The resource data will change as the next part is loaded, so we need to make a copy. + RefPtr<SharedBuffer> copiedData = SharedBuffer::create(data, size); + m_resource->data(copiedData.release(), true); + } else if (m_incremental) + m_resource->data(loader->resourceData(), false); +} + +void CachedResourceRequest::didReceiveCachedMetadata(SubresourceLoader*, const char* data, int size) +{ + ASSERT(!m_resource->isCacheValidator()); + m_resource->setSerializedCachedMetadata(data, size); +} + +} //namespace WebCore diff --git a/Source/WebCore/loader/cache/CachedResourceRequest.h b/Source/WebCore/loader/cache/CachedResourceRequest.h new file mode 100644 index 0000000..389b9ce --- /dev/null +++ b/Source/WebCore/loader/cache/CachedResourceRequest.h @@ -0,0 +1,65 @@ +/* + Copyright (C) 1998 Lars Knoll (knoll@mpi-hd.mpg.de) + Copyright (C) 2001 Dirk Mueller <mueller@kde.org> + Copyright (C) 2004, 2006, 2007, 2008 Apple Inc. All rights reserved. + + 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. +*/ + +#ifndef CachedResourceRequest_h +#define CachedResourceRequest_h + +#include "FrameLoaderTypes.h" +#include "SubresourceLoader.h" +#include "SubresourceLoaderClient.h" +#include <wtf/HashMap.h> +#include <wtf/Noncopyable.h> +#include <wtf/RefPtr.h> + +namespace WebCore { + + class CachedResource; + class CachedResourceLoader; + class Request; + + class CachedResourceRequest : public RefCounted<CachedResourceRequest>, private SubresourceLoaderClient { + public: + static PassRefPtr<CachedResourceRequest> load(CachedResourceLoader*, CachedResource*, bool incremental, SecurityCheckPolicy, bool sendResourceLoadCallbacks); + ~CachedResourceRequest(); + void didFail(bool cancelled = false); + + CachedResourceLoader* cachedResourceLoader() const { return m_cachedResourceLoader; } + + private: + CachedResourceRequest(CachedResourceLoader*, CachedResource*, bool incremental); + virtual void willSendRequest(SubresourceLoader*, ResourceRequest&, const ResourceResponse&); + virtual void didReceiveResponse(SubresourceLoader*, const ResourceResponse&); + virtual void didReceiveData(SubresourceLoader*, const char*, int); + virtual void didReceiveCachedMetadata(SubresourceLoader*, const char*, int); + virtual void didFinishLoading(SubresourceLoader*); + virtual void didFail(SubresourceLoader*, const ResourceError&); + + RefPtr<SubresourceLoader> m_loader; + CachedResourceLoader* m_cachedResourceLoader; + CachedResource* m_resource; + bool m_incremental; + bool m_multipart; + bool m_finishing; + }; + +} + +#endif diff --git a/Source/WebCore/loader/cache/CachedScript.cpp b/Source/WebCore/loader/cache/CachedScript.cpp new file mode 100644 index 0000000..54b4503 --- /dev/null +++ b/Source/WebCore/loader/cache/CachedScript.cpp @@ -0,0 +1,124 @@ +/* + 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) 2006 Samuel Weinig (sam.weinig@gmail.com) + Copyright (C) 2004, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved. + + 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 "CachedScript.h" + +#include "MemoryCache.h" +#include "CachedResourceClient.h" +#include "CachedResourceClientWalker.h" +#include "SharedBuffer.h" +#include "TextResourceDecoder.h" +#include <wtf/Vector.h> + +namespace WebCore { + +CachedScript::CachedScript(const String& url, const String& charset) + : CachedResource(url, Script) + , m_decoder(TextResourceDecoder::create("application/javascript", charset)) + , m_decodedDataDeletionTimer(this, &CachedScript::decodedDataDeletionTimerFired) +{ + // It's javascript we want. + // But some websites think their scripts are <some wrong mimetype here> + // and refuse to serve them if we only accept application/x-javascript. + setAccept("*/*"); +} + +CachedScript::~CachedScript() +{ +} + +void CachedScript::allClientsRemoved() +{ + m_decodedDataDeletionTimer.startOneShot(0); +} + +void CachedScript::setEncoding(const String& chs) +{ + m_decoder->setEncoding(chs, TextResourceDecoder::EncodingFromHTTPHeader); +} + +String CachedScript::encoding() const +{ + return m_decoder->encoding().name(); +} + +const String& CachedScript::script() +{ + ASSERT(!isPurgeable()); + + if (!m_script && m_data) { + m_script = m_decoder->decode(m_data->data(), encodedSize()); + m_script += m_decoder->flush(); + setDecodedSize(m_script.length() * sizeof(UChar)); + } + m_decodedDataDeletionTimer.startOneShot(0); + return m_script; +} + +void CachedScript::data(PassRefPtr<SharedBuffer> data, bool allDataReceived) +{ + if (!allDataReceived) + return; + + m_data = data; + setEncodedSize(m_data.get() ? m_data->size() : 0); + setLoading(false); + checkNotify(); +} + +void CachedScript::checkNotify() +{ + if (isLoading()) + return; + + CachedResourceClientWalker w(m_clients); + while (CachedResourceClient* c = w.next()) + c->notifyFinished(this); +} + +void CachedScript::error(CachedResource::Status status) +{ + setStatus(status); + ASSERT(errorOccurred()); + setLoading(false); + checkNotify(); +} + +void CachedScript::destroyDecodedData() +{ + m_script = String(); + setDecodedSize(0); + if (!MemoryCache::shouldMakeResourcePurgeableOnEviction() && isSafeToMakePurgeable()) + makePurgeable(true); +} + +void CachedScript::decodedDataDeletionTimerFired(Timer<CachedScript>*) +{ + destroyDecodedData(); +} + +} // namespace WebCore diff --git a/Source/WebCore/loader/cache/CachedScript.h b/Source/WebCore/loader/cache/CachedScript.h new file mode 100644 index 0000000..30fcb1e --- /dev/null +++ b/Source/WebCore/loader/cache/CachedScript.h @@ -0,0 +1,65 @@ +/* + Copyright (C) 1998 Lars Knoll (knoll@mpi-hd.mpg.de) + Copyright (C) 2001 Dirk Mueller <mueller@kde.org> + Copyright (C) 2006 Samuel Weinig (sam.weinig@gmail.com) + Copyright (C) 2004, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved. + + 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. +*/ + +#ifndef CachedScript_h +#define CachedScript_h + +#include "CachedResource.h" +#include "Timer.h" + +namespace WebCore { + + class CachedResourceLoader; + class TextResourceDecoder; + + class CachedScript : public CachedResource { + public: + CachedScript(const String& url, const String& charset); + virtual ~CachedScript(); + + const String& script(); + + virtual void allClientsRemoved(); + + virtual void setEncoding(const String&); + virtual String encoding() const; + virtual void data(PassRefPtr<SharedBuffer> data, bool allDataReceived); + virtual void error(CachedResource::Status); + + void checkNotify(); + + virtual void destroyDecodedData(); + + private: + void decodedDataDeletionTimerFired(Timer<CachedScript>*); + virtual PurgePriority purgePriority() const { return PurgeLast; } + + String m_script; + RefPtr<TextResourceDecoder> m_decoder; + Timer<CachedScript> m_decodedDataDeletionTimer; + }; +} + +#endif diff --git a/Source/WebCore/loader/cache/CachedXSLStyleSheet.cpp b/Source/WebCore/loader/cache/CachedXSLStyleSheet.cpp new file mode 100644 index 0000000..ca7bf13 --- /dev/null +++ b/Source/WebCore/loader/cache/CachedXSLStyleSheet.cpp @@ -0,0 +1,100 @@ +/* + 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) 2006 Samuel Weinig (sam.weinig@gmail.com) + Copyright (C) 2004, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved. + + 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 "CachedXSLStyleSheet.h" + +#include "CachedResourceClient.h" +#include "CachedResourceClientWalker.h" +#include "SharedBuffer.h" +#include "TextResourceDecoder.h" +#include <wtf/Vector.h> + +namespace WebCore { + +#if ENABLE(XSLT) + +CachedXSLStyleSheet::CachedXSLStyleSheet(const String &url) + : CachedResource(url, XSLStyleSheet) + , m_decoder(TextResourceDecoder::create("text/xsl")) +{ + // It's XML we want. + // FIXME: This should accept more general xml formats */*+xml, image/svg+xml for example. + setAccept("text/xml, application/xml, application/xhtml+xml, text/xsl, application/rss+xml, application/atom+xml"); +} + +void CachedXSLStyleSheet::didAddClient(CachedResourceClient* c) +{ + if (!isLoading()) + c->setXSLStyleSheet(m_url, m_response.url(), m_sheet); +} + +void CachedXSLStyleSheet::setEncoding(const String& chs) +{ + m_decoder->setEncoding(chs, TextResourceDecoder::EncodingFromHTTPHeader); +} + +String CachedXSLStyleSheet::encoding() const +{ + return m_decoder->encoding().name(); +} + +void CachedXSLStyleSheet::data(PassRefPtr<SharedBuffer> data, bool allDataReceived) +{ + if (!allDataReceived) + return; + + m_data = data; + setEncodedSize(m_data.get() ? m_data->size() : 0); + if (m_data.get()) { + m_sheet = String(m_decoder->decode(m_data->data(), encodedSize())); + m_sheet += m_decoder->flush(); + } + setLoading(false); + checkNotify(); +} + +void CachedXSLStyleSheet::checkNotify() +{ + if (isLoading()) + return; + + CachedResourceClientWalker w(m_clients); + while (CachedResourceClient *c = w.next()) + c->setXSLStyleSheet(m_url, m_response.url(), m_sheet); +} + +void CachedXSLStyleSheet::error(CachedResource::Status status) +{ + setStatus(status); + ASSERT(errorOccurred()); + setLoading(false); + checkNotify(); +} + +#endif + +} diff --git a/Source/WebCore/loader/cache/CachedXSLStyleSheet.h b/Source/WebCore/loader/cache/CachedXSLStyleSheet.h new file mode 100644 index 0000000..8b29792 --- /dev/null +++ b/Source/WebCore/loader/cache/CachedXSLStyleSheet.h @@ -0,0 +1,62 @@ +/* + Copyright (C) 1998 Lars Knoll (knoll@mpi-hd.mpg.de) + Copyright (C) 2001 Dirk Mueller <mueller@kde.org> + Copyright (C) 2006 Samuel Weinig (sam.weinig@gmail.com) + Copyright (C) 2004, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved. + + 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. +*/ + +#ifndef CachedXSLStyleSheet_h +#define CachedXSLStyleSheet_h + +#include "CachedResource.h" +#include <wtf/Vector.h> + +namespace WebCore { + + class CachedResourceLoader; + class TextResourceDecoder; + +#if ENABLE(XSLT) + class CachedXSLStyleSheet : public CachedResource { + public: + CachedXSLStyleSheet(const String& url); + + const String& sheet() const { return m_sheet; } + + virtual void didAddClient(CachedResourceClient*); + + virtual void setEncoding(const String&); + virtual String encoding() const; + virtual void data(PassRefPtr<SharedBuffer> data, bool allDataReceived); + virtual void error(CachedResource::Status); + + void checkNotify(); + + protected: + String m_sheet; + RefPtr<TextResourceDecoder> m_decoder; + }; + +#endif + +} + +#endif diff --git a/Source/WebCore/loader/cache/MemoryCache.cpp b/Source/WebCore/loader/cache/MemoryCache.cpp new file mode 100644 index 0000000..930033a --- /dev/null +++ b/Source/WebCore/loader/cache/MemoryCache.cpp @@ -0,0 +1,670 @@ +/* + 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, 2007, 2008 Apple Inc. All rights reserved. + + 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. +*/ + +#include "config.h" +#include "MemoryCache.h" + +#include "CachedCSSStyleSheet.h" +#include "CachedFont.h" +#include "CachedImage.h" +#include "CachedScript.h" +#include "CachedXSLStyleSheet.h" +#include "CachedResourceLoader.h" +#include "Document.h" +#include "FrameLoader.h" +#include "FrameLoaderTypes.h" +#include "FrameView.h" +#include "Image.h" +#include "Logging.h" +#include "ResourceHandle.h" +#include "SecurityOrigin.h" +#include <stdio.h> +#include <wtf/CurrentTime.h> +#include <wtf/text/CString.h> + +using namespace std; + +namespace WebCore { + +static const int cDefaultCacheCapacity = 8192 * 1024; +static const double cMinDelayBeforeLiveDecodedPrune = 1; // Seconds. +static const float cTargetPrunePercentage = .95f; // Percentage of capacity toward which we prune, to avoid immediately pruning again. +static const double cDefaultDecodedDataDeletionInterval = 0; + +MemoryCache* cache() +{ + static MemoryCache* staticCache = new MemoryCache; + return staticCache; +} + +MemoryCache::MemoryCache() + : m_disabled(false) + , m_pruneEnabled(true) + , m_inPruneDeadResources(false) + , m_capacity(cDefaultCacheCapacity) + , m_minDeadCapacity(0) + , m_maxDeadCapacity(cDefaultCacheCapacity) + , m_deadDecodedDataDeletionInterval(cDefaultDecodedDataDeletionInterval) + , m_liveSize(0) + , m_deadSize(0) +{ +} + +KURL MemoryCache::removeFragmentIdentifierIfNeeded(const KURL& originalURL) +{ + if (!originalURL.hasFragmentIdentifier()) + return originalURL; + // Strip away fragment identifier from HTTP and file urls. + // Data urls must be unmodified and it is also safer to keep them for custom protocols. + if (!(originalURL.protocolInHTTPFamily() || originalURL.isLocalFile())) + return originalURL; + KURL url = originalURL; + url.removeFragmentIdentifier(); + return url; +} + +bool MemoryCache::add(CachedResource* resource) +{ + if (disabled()) + return false; + + m_resources.set(resource->url(), resource); + resource->setInCache(true); + + resourceAccessed(resource); + + LOG(ResourceLoading, "MemoryCache::add Added '%s', resource %p\n", resource->url().latin1().data(), resource); + return true; +} + +void MemoryCache::revalidationSucceeded(CachedResource* revalidatingResource, const ResourceResponse& response) +{ + CachedResource* resource = revalidatingResource->resourceToRevalidate(); + ASSERT(resource); + ASSERT(!resource->inCache()); + ASSERT(resource->isLoaded()); + ASSERT(revalidatingResource->inCache()); + + evict(revalidatingResource); + + ASSERT(!m_resources.get(resource->url())); + m_resources.set(resource->url(), resource); + resource->setInCache(true); + resource->updateResponseAfterRevalidation(response); + insertInLRUList(resource); + int delta = resource->size(); + if (resource->decodedSize() && resource->hasClients()) + insertInLiveDecodedResourcesList(resource); + if (delta) + adjustSize(resource->hasClients(), delta); + + revalidatingResource->switchClientsToRevalidatedResource(); + // this deletes the revalidating resource + revalidatingResource->clearResourceToRevalidate(); +} + +void MemoryCache::revalidationFailed(CachedResource* revalidatingResource) +{ + LOG(ResourceLoading, "Revalidation failed for %p", revalidatingResource); + ASSERT(revalidatingResource->resourceToRevalidate()); + revalidatingResource->clearResourceToRevalidate(); +} + +CachedResource* MemoryCache::resourceForURL(const KURL& resourceURL) +{ + KURL url = removeFragmentIdentifierIfNeeded(resourceURL); + CachedResource* resource = m_resources.get(url); + bool wasPurgeable = MemoryCache::shouldMakeResourcePurgeableOnEviction() && resource && resource->isPurgeable(); + if (resource && !resource->makePurgeable(false)) { + ASSERT(!resource->hasClients()); + evict(resource); + return 0; + } + // Add the size back since we had subtracted it when we marked the memory as purgeable. + if (wasPurgeable) + adjustSize(resource->hasClients(), resource->size()); + return resource; +} + +unsigned MemoryCache::deadCapacity() const +{ + // Dead resource capacity is whatever space is not occupied by live resources, bounded by an independent minimum and maximum. + unsigned capacity = m_capacity - min(m_liveSize, m_capacity); // Start with available capacity. + capacity = max(capacity, m_minDeadCapacity); // Make sure it's above the minimum. + capacity = min(capacity, m_maxDeadCapacity); // Make sure it's below the maximum. + return capacity; +} + +unsigned MemoryCache::liveCapacity() const +{ + // Live resource capacity is whatever is left over after calculating dead resource capacity. + return m_capacity - deadCapacity(); +} + +void MemoryCache::pruneLiveResources() +{ + if (!m_pruneEnabled) + return; + + unsigned capacity = liveCapacity(); + if (capacity && m_liveSize <= capacity) + return; + + unsigned targetSize = static_cast<unsigned>(capacity * cTargetPrunePercentage); // Cut by a percentage to avoid immediately pruning again. + double currentTime = FrameView::currentPaintTimeStamp(); + if (!currentTime) // In case prune is called directly, outside of a Frame paint. + currentTime = WTF::currentTime(); + + // Destroy any decoded data in live objects that we can. + // Start from the tail, since this is the least recently accessed of the objects. + + // The list might not be sorted by the m_lastDecodedAccessTime. The impact + // of this weaker invariant is minor as the below if statement to check the + // elapsedTime will evaluate to false as the currentTime will be a lot + // greater than the current->m_lastDecodedAccessTime. + // For more details see: https://bugs.webkit.org/show_bug.cgi?id=30209 + CachedResource* current = m_liveDecodedResources.m_tail; + while (current) { + CachedResource* prev = current->m_prevInLiveResourcesList; + ASSERT(current->hasClients()); + if (current->isLoaded() && current->decodedSize()) { + // Check to see if the remaining resources are too new to prune. + double elapsedTime = currentTime - current->m_lastDecodedAccessTime; + if (elapsedTime < cMinDelayBeforeLiveDecodedPrune) + return; + + // Destroy our decoded data. This will remove us from + // m_liveDecodedResources, and possibly move us to a different LRU + // list in m_allResources. + current->destroyDecodedData(); + + if (targetSize && m_liveSize <= targetSize) + return; + } + current = prev; + } +} + +void MemoryCache::pruneDeadResources() +{ + if (!m_pruneEnabled) + return; + + unsigned capacity = deadCapacity(); + if (capacity && m_deadSize <= capacity) + return; + + unsigned targetSize = static_cast<unsigned>(capacity * cTargetPrunePercentage); // Cut by a percentage to avoid immediately pruning again. + int size = m_allResources.size(); + + if (!m_inPruneDeadResources) { + // See if we have any purged resources we can evict. + for (int i = 0; i < size; i++) { + CachedResource* current = m_allResources[i].m_tail; + while (current) { + CachedResource* prev = current->m_prevInAllResourcesList; + if (current->wasPurged()) { + ASSERT(!current->hasClients()); + ASSERT(!current->isPreloaded()); + evict(current); + } + current = prev; + } + } + if (targetSize && m_deadSize <= targetSize) + return; + } + + bool canShrinkLRULists = true; + m_inPruneDeadResources = true; + for (int i = size - 1; i >= 0; i--) { + // Remove from the tail, since this is the least frequently accessed of the objects. + CachedResource* current = m_allResources[i].m_tail; + + // First flush all the decoded data in this queue. + while (current) { + CachedResource* prev = current->m_prevInAllResourcesList; + if (!current->hasClients() && !current->isPreloaded() && current->isLoaded()) { + // Destroy our decoded data. This will remove us from + // m_liveDecodedResources, and possibly move us to a different + // LRU list in m_allResources. + current->destroyDecodedData(); + + if (targetSize && m_deadSize <= targetSize) { + m_inPruneDeadResources = false; + return; + } + } + current = prev; + } + + // Now evict objects from this queue. + current = m_allResources[i].m_tail; + while (current) { + CachedResource* prev = current->m_prevInAllResourcesList; + if (!current->hasClients() && !current->isPreloaded() && !current->isCacheValidator()) { + if (!makeResourcePurgeable(current)) + evict(current); + + // If evict() caused pruneDeadResources() to be re-entered, bail out. This can happen when removing an + // SVG CachedImage that has subresources. + if (!m_inPruneDeadResources) + return; + + if (targetSize && m_deadSize <= targetSize) { + m_inPruneDeadResources = false; + return; + } + } + current = prev; + } + + // Shrink the vector back down so we don't waste time inspecting + // empty LRU lists on future prunes. + if (m_allResources[i].m_head) + canShrinkLRULists = false; + else if (canShrinkLRULists) + m_allResources.resize(i); + } + m_inPruneDeadResources = false; +} + +void MemoryCache::setCapacities(unsigned minDeadBytes, unsigned maxDeadBytes, unsigned totalBytes) +{ + ASSERT(minDeadBytes <= maxDeadBytes); + ASSERT(maxDeadBytes <= totalBytes); + m_minDeadCapacity = minDeadBytes; + m_maxDeadCapacity = maxDeadBytes; + m_capacity = totalBytes; + prune(); +} + +bool MemoryCache::makeResourcePurgeable(CachedResource* resource) +{ + if (!MemoryCache::shouldMakeResourcePurgeableOnEviction()) + return false; + + if (!resource->inCache()) + return false; + + if (resource->isPurgeable()) + return true; + + if (!resource->isSafeToMakePurgeable()) + return false; + + if (!resource->makePurgeable(true)) + return false; + + adjustSize(resource->hasClients(), -static_cast<int>(resource->size())); + + return true; +} + +void MemoryCache::evict(CachedResource* resource) +{ + LOG(ResourceLoading, "Evicting resource %p for '%s' from cache", resource, resource->url().latin1().data()); + // The resource may have already been removed by someone other than our caller, + // who needed a fresh copy for a reload. See <http://bugs.webkit.org/show_bug.cgi?id=12479#c6>. + if (resource->inCache()) { + // Remove from the resource map. + m_resources.remove(resource->url()); + resource->setInCache(false); + + // Remove from the appropriate LRU list. + removeFromLRUList(resource); + removeFromLiveDecodedResourcesList(resource); + + // If the resource was purged, it means we had already decremented the size when we made the + // resource purgeable in makeResourcePurgeable(). So adjust the size if we are evicting a + // resource that was not marked as purgeable. + if (!MemoryCache::shouldMakeResourcePurgeableOnEviction() || !resource->isPurgeable()) + adjustSize(resource->hasClients(), -static_cast<int>(resource->size())); + } else + ASSERT(m_resources.get(resource->url()) != resource); + + if (resource->canDelete()) + delete resource; +} + +void MemoryCache::addCachedResourceLoader(CachedResourceLoader* cachedResourceLoader) +{ + m_cachedResourceLoaders.add(cachedResourceLoader); +} + +void MemoryCache::removeCachedResourceLoader(CachedResourceLoader* cachedResourceLoader) +{ + m_cachedResourceLoaders.remove(cachedResourceLoader); +} + +static inline unsigned fastLog2(unsigned i) +{ + unsigned log2 = 0; + if (i & (i - 1)) + log2 += 1; + if (i >> 16) + log2 += 16, i >>= 16; + if (i >> 8) + log2 += 8, i >>= 8; + if (i >> 4) + log2 += 4, i >>= 4; + if (i >> 2) + log2 += 2, i >>= 2; + if (i >> 1) + log2 += 1; + return log2; +} + +MemoryCache::LRUList* MemoryCache::lruListFor(CachedResource* resource) +{ + unsigned accessCount = max(resource->accessCount(), 1U); + unsigned queueIndex = fastLog2(resource->size() / accessCount); +#ifndef NDEBUG + resource->m_lruIndex = queueIndex; +#endif + if (m_allResources.size() <= queueIndex) + m_allResources.grow(queueIndex + 1); + return &m_allResources[queueIndex]; +} + +void MemoryCache::removeFromLRUList(CachedResource* resource) +{ + // If we've never been accessed, then we're brand new and not in any list. + if (resource->accessCount() == 0) + return; + +#if !ASSERT_DISABLED + unsigned oldListIndex = resource->m_lruIndex; +#endif + + LRUList* list = lruListFor(resource); + +#if !ASSERT_DISABLED + // Verify that the list we got is the list we want. + ASSERT(resource->m_lruIndex == oldListIndex); + + // Verify that we are in fact in this list. + bool found = false; + for (CachedResource* current = list->m_head; current; current = current->m_nextInAllResourcesList) { + if (current == resource) { + found = true; + break; + } + } + ASSERT(found); +#endif + + CachedResource* next = resource->m_nextInAllResourcesList; + CachedResource* prev = resource->m_prevInAllResourcesList; + + if (next == 0 && prev == 0 && list->m_head != resource) + return; + + resource->m_nextInAllResourcesList = 0; + resource->m_prevInAllResourcesList = 0; + + if (next) + next->m_prevInAllResourcesList = prev; + else if (list->m_tail == resource) + list->m_tail = prev; + + if (prev) + prev->m_nextInAllResourcesList = next; + else if (list->m_head == resource) + list->m_head = next; +} + +void MemoryCache::insertInLRUList(CachedResource* resource) +{ + // Make sure we aren't in some list already. + ASSERT(!resource->m_nextInAllResourcesList && !resource->m_prevInAllResourcesList); + ASSERT(resource->inCache()); + ASSERT(resource->accessCount() > 0); + + LRUList* list = lruListFor(resource); + + resource->m_nextInAllResourcesList = list->m_head; + if (list->m_head) + list->m_head->m_prevInAllResourcesList = resource; + list->m_head = resource; + + if (!resource->m_nextInAllResourcesList) + list->m_tail = resource; + +#ifndef NDEBUG + // Verify that we are in now in the list like we should be. + list = lruListFor(resource); + bool found = false; + for (CachedResource* current = list->m_head; current; current = current->m_nextInAllResourcesList) { + if (current == resource) { + found = true; + break; + } + } + ASSERT(found); +#endif + +} + +void MemoryCache::resourceAccessed(CachedResource* resource) +{ + ASSERT(resource->inCache()); + + // Need to make sure to remove before we increase the access count, since + // the queue will possibly change. + removeFromLRUList(resource); + + // If this is the first time the resource has been accessed, adjust the size of the cache to account for its initial size. + if (!resource->accessCount()) + adjustSize(resource->hasClients(), resource->size()); + + // Add to our access count. + resource->increaseAccessCount(); + + // Now insert into the new queue. + insertInLRUList(resource); +} + +void MemoryCache::removeFromLiveDecodedResourcesList(CachedResource* resource) +{ + // If we've never been accessed, then we're brand new and not in any list. + if (!resource->m_inLiveDecodedResourcesList) + return; + resource->m_inLiveDecodedResourcesList = false; + +#ifndef NDEBUG + // Verify that we are in fact in this list. + bool found = false; + for (CachedResource* current = m_liveDecodedResources.m_head; current; current = current->m_nextInLiveResourcesList) { + if (current == resource) { + found = true; + break; + } + } + ASSERT(found); +#endif + + CachedResource* next = resource->m_nextInLiveResourcesList; + CachedResource* prev = resource->m_prevInLiveResourcesList; + + if (next == 0 && prev == 0 && m_liveDecodedResources.m_head != resource) + return; + + resource->m_nextInLiveResourcesList = 0; + resource->m_prevInLiveResourcesList = 0; + + if (next) + next->m_prevInLiveResourcesList = prev; + else if (m_liveDecodedResources.m_tail == resource) + m_liveDecodedResources.m_tail = prev; + + if (prev) + prev->m_nextInLiveResourcesList = next; + else if (m_liveDecodedResources.m_head == resource) + m_liveDecodedResources.m_head = next; +} + +void MemoryCache::insertInLiveDecodedResourcesList(CachedResource* resource) +{ + // Make sure we aren't in the list already. + ASSERT(!resource->m_nextInLiveResourcesList && !resource->m_prevInLiveResourcesList && !resource->m_inLiveDecodedResourcesList); + resource->m_inLiveDecodedResourcesList = true; + + resource->m_nextInLiveResourcesList = m_liveDecodedResources.m_head; + if (m_liveDecodedResources.m_head) + m_liveDecodedResources.m_head->m_prevInLiveResourcesList = resource; + m_liveDecodedResources.m_head = resource; + + if (!resource->m_nextInLiveResourcesList) + m_liveDecodedResources.m_tail = resource; + +#ifndef NDEBUG + // Verify that we are in now in the list like we should be. + bool found = false; + for (CachedResource* current = m_liveDecodedResources.m_head; current; current = current->m_nextInLiveResourcesList) { + if (current == resource) { + found = true; + break; + } + } + ASSERT(found); +#endif + +} + +void MemoryCache::addToLiveResourcesSize(CachedResource* resource) +{ + m_liveSize += resource->size(); + m_deadSize -= resource->size(); +} + +void MemoryCache::removeFromLiveResourcesSize(CachedResource* resource) +{ + m_liveSize -= resource->size(); + m_deadSize += resource->size(); +} + +void MemoryCache::adjustSize(bool live, int delta) +{ + if (live) { + ASSERT(delta >= 0 || ((int)m_liveSize + delta >= 0)); + m_liveSize += delta; + } else { + ASSERT(delta >= 0 || ((int)m_deadSize + delta >= 0)); + m_deadSize += delta; + } +} + +void MemoryCache::TypeStatistic::addResource(CachedResource* o) +{ + bool purged = o->wasPurged(); + bool purgeable = o->isPurgeable() && !purged; + int pageSize = (o->encodedSize() + o->overheadSize() + 4095) & ~4095; + count++; + size += purged ? 0 : o->size(); + liveSize += o->hasClients() ? o->size() : 0; + decodedSize += o->decodedSize(); + purgeableSize += purgeable ? pageSize : 0; + purgedSize += purged ? pageSize : 0; +} + +MemoryCache::Statistics MemoryCache::getStatistics() +{ + Statistics stats; + CachedResourceMap::iterator e = m_resources.end(); + for (CachedResourceMap::iterator i = m_resources.begin(); i != e; ++i) { + CachedResource* resource = i->second; + switch (resource->type()) { + case CachedResource::ImageResource: + stats.images.addResource(resource); + break; + case CachedResource::CSSStyleSheet: + stats.cssStyleSheets.addResource(resource); + break; + case CachedResource::Script: + stats.scripts.addResource(resource); + break; +#if ENABLE(XSLT) + case CachedResource::XSLStyleSheet: + stats.xslStyleSheets.addResource(resource); + break; +#endif + case CachedResource::FontResource: + stats.fonts.addResource(resource); + break; + default: + break; + } + } + return stats; +} + +void MemoryCache::setDisabled(bool disabled) +{ + m_disabled = disabled; + if (!m_disabled) + return; + + for (;;) { + CachedResourceMap::iterator i = m_resources.begin(); + if (i == m_resources.end()) + break; + evict(i->second); + } +} + +#ifndef NDEBUG +void MemoryCache::dumpStats() +{ + Statistics s = getStatistics(); + printf("%-13s %-13s %-13s %-13s %-13s %-13s %-13s\n", "", "Count", "Size", "LiveSize", "DecodedSize", "PurgeableSize", "PurgedSize"); + printf("%-13s %-13s %-13s %-13s %-13s %-13s %-13s\n", "-------------", "-------------", "-------------", "-------------", "-------------", "-------------", "-------------"); + printf("%-13s %13d %13d %13d %13d %13d %13d\n", "Images", s.images.count, s.images.size, s.images.liveSize, s.images.decodedSize, s.images.purgeableSize, s.images.purgedSize); + printf("%-13s %13d %13d %13d %13d %13d %13d\n", "CSS", s.cssStyleSheets.count, s.cssStyleSheets.size, s.cssStyleSheets.liveSize, s.cssStyleSheets.decodedSize, s.cssStyleSheets.purgeableSize, s.cssStyleSheets.purgedSize); +#if ENABLE(XSLT) + printf("%-13s %13d %13d %13d %13d %13d %13d\n", "XSL", s.xslStyleSheets.count, s.xslStyleSheets.size, s.xslStyleSheets.liveSize, s.xslStyleSheets.decodedSize, s.xslStyleSheets.purgeableSize, s.xslStyleSheets.purgedSize); +#endif + printf("%-13s %13d %13d %13d %13d %13d %13d\n", "JavaScript", s.scripts.count, s.scripts.size, s.scripts.liveSize, s.scripts.decodedSize, s.scripts.purgeableSize, s.scripts.purgedSize); + printf("%-13s %13d %13d %13d %13d %13d %13d\n", "Fonts", s.fonts.count, s.fonts.size, s.fonts.liveSize, s.fonts.decodedSize, s.fonts.purgeableSize, s.fonts.purgedSize); + printf("%-13s %-13s %-13s %-13s %-13s %-13s %-13s\n\n", "-------------", "-------------", "-------------", "-------------", "-------------", "-------------", "-------------"); +} + +void MemoryCache::dumpLRULists(bool includeLive) const +{ + printf("LRU-SP lists in eviction order (Kilobytes decoded, Kilobytes encoded, Access count, Referenced, isPurgeable, wasPurged):\n"); + + int size = m_allResources.size(); + for (int i = size - 1; i >= 0; i--) { + printf("\n\nList %d: ", i); + CachedResource* current = m_allResources[i].m_tail; + while (current) { + CachedResource* prev = current->m_prevInAllResourcesList; + if (includeLive || !current->hasClients()) + printf("(%.1fK, %.1fK, %uA, %dR, %d, %d); ", current->decodedSize() / 1024.0f, (current->encodedSize() + current->overheadSize()) / 1024.0f, current->accessCount(), current->hasClients(), current->isPurgeable(), current->wasPurged()); + + current = prev; + } + } +} +#endif + +} // namespace WebCore diff --git a/Source/WebCore/loader/cache/MemoryCache.h b/Source/WebCore/loader/cache/MemoryCache.h new file mode 100644 index 0000000..dc47733 --- /dev/null +++ b/Source/WebCore/loader/cache/MemoryCache.h @@ -0,0 +1,231 @@ +/* + Copyright (C) 1998 Lars Knoll (knoll@mpi-hd.mpg.de) + Copyright (C) 2001 Dirk Mueller <mueller@kde.org> + Copyright (C) 2004, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved. + + 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. +*/ + +#ifndef Cache_h +#define Cache_h + +#include "CachePolicy.h" +#include "CachedResource.h" +#include "PlatformString.h" +#include <wtf/HashMap.h> +#include <wtf/HashSet.h> +#include <wtf/Noncopyable.h> +#include <wtf/Vector.h> +#include <wtf/text/StringHash.h> + +namespace WebCore { + +class CachedCSSStyleSheet; +class CachedResource; +class CachedResourceLoader; +class KURL; + +// This cache holds subresources used by Web pages: images, scripts, stylesheets, etc. + +// The cache keeps a flexible but bounded window of dead resources that grows/shrinks +// depending on the live resource load. Here's an example of cache growth over time, +// with a min dead resource capacity of 25% and a max dead resource capacity of 50%: + +// |-----| Dead: - +// |----------| Live: + +// --|----------| Cache boundary: | (objects outside this mark have been evicted) +// --|----------++++++++++| +// -------|-----+++++++++++++++| +// -------|-----+++++++++++++++|+++++ + +// The behavior of the cache changes in the following way if shouldMakeResourcePurgeableOnEviction +// returns true. +// +// 1. Dead resources in the cache are kept in non-purgeable memory. +// 2. When we prune dead resources, instead of freeing them, we mark their memory as purgeable and +// keep the resources until the kernel reclaims the purgeable memory. +// +// By leaving the in-cache dead resources in dirty resident memory, we decrease the likelihood of +// the kernel claiming that memory and forcing us to refetch the resource (for example when a user +// presses back). +// +// And by having an unbounded number of resource objects using purgeable memory, we can use as much +// memory as is available on the machine. The trade-off here is that the CachedResource object (and +// its member variables) are allocated in non-purgeable TC-malloc'd memory so we would see slightly +// more memory use due to this. + +class MemoryCache : public Noncopyable { +public: + friend MemoryCache* cache(); + + typedef HashMap<String, CachedResource*> CachedResourceMap; + + struct LRUList { + CachedResource* m_head; + CachedResource* m_tail; + LRUList() : m_head(0), m_tail(0) { } + }; + + struct TypeStatistic { + int count; + int size; + int liveSize; + int decodedSize; + int purgeableSize; + int purgedSize; + TypeStatistic() : count(0), size(0), liveSize(0), decodedSize(0), purgeableSize(0), purgedSize(0) { } + void addResource(CachedResource*); + }; + + struct Statistics { + TypeStatistic images; + TypeStatistic cssStyleSheets; + TypeStatistic scripts; +#if ENABLE(XSLT) + TypeStatistic xslStyleSheets; +#endif + TypeStatistic fonts; + }; + + CachedResource* resourceForURL(const KURL&); + + bool add(CachedResource* resource); + void remove(CachedResource* resource) { evict(resource); } + + static KURL removeFragmentIdentifierIfNeeded(const KURL& originalURL); + + void revalidationSucceeded(CachedResource* revalidatingResource, const ResourceResponse&); + void revalidationFailed(CachedResource* revalidatingResource); + + // Sets the cache's memory capacities, in bytes. These will hold only approximately, + // since the decoded cost of resources like scripts and stylesheets is not known. + // - minDeadBytes: The maximum number of bytes that dead resources should consume when the cache is under pressure. + // - maxDeadBytes: The maximum number of bytes that dead resources should consume when the cache is not under pressure. + // - totalBytes: The maximum number of bytes that the cache should consume overall. + void setCapacities(unsigned minDeadBytes, unsigned maxDeadBytes, unsigned totalBytes); + + // Turn the cache on and off. Disabling the cache will remove all resources from the cache. They may + // still live on if they are referenced by some Web page though. + void setDisabled(bool); + bool disabled() const { return m_disabled; } + + void setPruneEnabled(bool enabled) { m_pruneEnabled = enabled; } + void prune() + { + if (m_liveSize + m_deadSize <= m_capacity && m_maxDeadCapacity && m_deadSize <= m_maxDeadCapacity) // Fast path. + return; + + pruneDeadResources(); // Prune dead first, in case it was "borrowing" capacity from live. + pruneLiveResources(); + } + + void setDeadDecodedDataDeletionInterval(double interval) { m_deadDecodedDataDeletionInterval = interval; } + double deadDecodedDataDeletionInterval() const { return m_deadDecodedDataDeletionInterval; } + + void addCachedResourceLoader(CachedResourceLoader*); + void removeCachedResourceLoader(CachedResourceLoader*); + + // Calls to put the cached resource into and out of LRU lists. + void insertInLRUList(CachedResource*); + void removeFromLRUList(CachedResource*); + + // Called to adjust the cache totals when a resource changes size. + void adjustSize(bool live, int delta); + + // Track decoded resources that are in the cache and referenced by a Web page. + void insertInLiveDecodedResourcesList(CachedResource*); + void removeFromLiveDecodedResourcesList(CachedResource*); + + void addToLiveResourcesSize(CachedResource*); + void removeFromLiveResourcesSize(CachedResource*); + + static bool shouldMakeResourcePurgeableOnEviction(); + + // Function to collect cache statistics for the caches window in the Safari Debug menu. + Statistics getStatistics(); + + void resourceAccessed(CachedResource*); + +#ifdef ANDROID_INSTRUMENT + unsigned getLiveSize() { return m_liveSize; } + unsigned getDeadSize() { return m_deadSize; } +#endif + +private: + MemoryCache(); + ~MemoryCache(); // Not implemented to make sure nobody accidentally calls delete -- WebCore does not delete singletons. + + LRUList* lruListFor(CachedResource*); +#ifndef NDEBUG + void dumpStats(); + void dumpLRULists(bool includeLive) const; +#endif + + unsigned liveCapacity() const; + unsigned deadCapacity() const; + + void pruneDeadResources(); // Flush decoded and encoded data from resources not referenced by Web pages. + void pruneLiveResources(); // Flush decoded data from resources still referenced by Web pages. + + bool makeResourcePurgeable(CachedResource*); + void evict(CachedResource*); + + // Member variables. + HashSet<CachedResourceLoader*> m_cachedResourceLoaders; + + bool m_disabled; // Whether or not the cache is enabled. + bool m_pruneEnabled; + bool m_inPruneDeadResources; + + unsigned m_capacity; + unsigned m_minDeadCapacity; + unsigned m_maxDeadCapacity; + double m_deadDecodedDataDeletionInterval; + + unsigned m_liveSize; // The number of bytes currently consumed by "live" resources in the cache. + unsigned m_deadSize; // The number of bytes currently consumed by "dead" resources in the cache. + + // Size-adjusted and popularity-aware LRU list collection for cache objects. This collection can hold + // more resources than the cached resource map, since it can also hold "stale" multiple versions of objects that are + // waiting to die when the clients referencing them go away. + Vector<LRUList, 32> m_allResources; + + // List just for live resources with decoded data. Access to this list is based off of painting the resource. + LRUList m_liveDecodedResources; + + // A URL-based map of all resources that are in the cache (including the freshest version of objects that are currently being + // referenced by a Web page). + HashMap<String, CachedResource*> m_resources; +}; + +inline bool MemoryCache::shouldMakeResourcePurgeableOnEviction() +{ +#if PLATFORM(IOS) + return true; +#else + return false; +#endif +} + +// Function to obtain the global cache. +MemoryCache* cache(); + +} + +#endif |