diff options
Diffstat (limited to 'Source/WebCore/loader/cache')
40 files changed, 3081 insertions, 2681 deletions
diff --git a/Source/WebCore/loader/cache/CachePolicy.h b/Source/WebCore/loader/cache/CachePolicy.h index 0b9010b63..b8a1013c3 100644 --- a/Source/WebCore/loader/cache/CachePolicy.h +++ b/Source/WebCore/loader/cache/CachePolicy.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003, 2006 Apple Computer, Inc. All rights reserved. + * Copyright (C) 2003, 2006 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -10,10 +10,10 @@ * 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 + * 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 COMPUTER, INC. OR + * 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 @@ -23,19 +23,15 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef CachePolicy_h -#define CachePolicy_h +#pragma once namespace WebCore { - enum CachePolicy { - CachePolicyCache, - CachePolicyVerify, - CachePolicyRevalidate, - CachePolicyReload, - CachePolicyHistoryBuffer - }; +enum CachePolicy { + CachePolicyVerify, + CachePolicyRevalidate, + CachePolicyReload, + CachePolicyHistoryBuffer +}; -} - -#endif +} // namespace WebCore diff --git a/Source/WebCore/loader/cache/CachedCSSStyleSheet.cpp b/Source/WebCore/loader/cache/CachedCSSStyleSheet.cpp index 69bce8f40..af40cc204 100644 --- a/Source/WebCore/loader/cache/CachedCSSStyleSheet.cpp +++ b/Source/WebCore/loader/cache/CachedCSSStyleSheet.cpp @@ -3,7 +3,7 @@ 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. + Copyright (C) 2004-2017 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 @@ -19,9 +19,6 @@ 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" @@ -29,24 +26,22 @@ #include "CSSStyleSheet.h" #include "CachedResourceClientWalker.h" +#include "CachedResourceRequest.h" #include "CachedStyleSheetClient.h" +#include "HTTPHeaderNames.h" #include "HTTPParsers.h" #include "MemoryCache.h" -#include "ResourceBuffer.h" +#include "SharedBuffer.h" #include "StyleSheetContents.h" #include "TextResourceDecoder.h" #include <wtf/CurrentTime.h> -#include <wtf/Vector.h> namespace WebCore { -CachedCSSStyleSheet::CachedCSSStyleSheet(const ResourceRequest& resourceRequest, const String& charset) - : CachedResource(resourceRequest, CSSStyleSheet) - , m_decoder(TextResourceDecoder::create("text/css", charset)) +CachedCSSStyleSheet::CachedCSSStyleSheet(CachedResourceRequest&& request, SessionID sessionID) + : CachedResource(WTFMove(request), CSSStyleSheet, sessionID) + , m_decoder(TextResourceDecoder::create("text/css", request.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() @@ -55,16 +50,16 @@ CachedCSSStyleSheet::~CachedCSSStyleSheet() m_parsedStyleSheetCache->removedFromMemoryCache(); } -void CachedCSSStyleSheet::didAddClient(CachedResourceClient* c) +void CachedCSSStyleSheet::didAddClient(CachedResourceClient& client) { - ASSERT(c->resourceClientType() == CachedStyleSheetClient::expectedType()); + ASSERT(client.resourceClientType() == CachedStyleSheetClient::expectedType()); // CachedResource::didAddClient() must be before setCSSStyleSheet(), // because setCSSStyleSheet() may cause scripts to be executed, which could destroy 'c' if it is an instance of HTMLLinkElement. // see the comment of HTMLLinkElement::setCSSStyleSheet. - CachedResource::didAddClient(c); + CachedResource::didAddClient(client); if (!isLoading()) - static_cast<CachedStyleSheetClient*>(c)->setCSSStyleSheet(m_resourceRequest.url(), m_response.url(), m_decoder->encoding().name(), this); + static_cast<CachedStyleSheetClient&>(client).setCSSStyleSheet(m_resourceRequest.url(), m_response.url(), m_decoder->encoding().name(), this); } void CachedCSSStyleSheet::setEncoding(const String& chs) @@ -76,32 +71,39 @@ 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)) +const String CachedCSSStyleSheet::sheetText(MIMETypeCheck mimeTypeCheck, bool* hasValidMIMEType) const +{ + if (!m_data || m_data->isEmpty() || !canUseSheet(mimeTypeCheck, 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.append(m_decoder->flush()); - return sheetText; + return m_decoder->decodeAndFlush(m_data->data(), m_data->size()); } -void CachedCSSStyleSheet::finishLoading(ResourceBuffer* data) +void CachedCSSStyleSheet::setBodyDataFrom(const CachedResource& resource) +{ + ASSERT(resource.type() == type()); + const CachedCSSStyleSheet& sheet = static_cast<const CachedCSSStyleSheet&>(resource); + + CachedResource::setBodyDataFrom(resource); + + m_decoder = sheet.m_decoder; + m_decodedSheetText = sheet.m_decodedSheetText; + if (sheet.m_parsedStyleSheetCache) + saveParsedStyleSheet(*sheet.m_parsedStyleSheetCache); +} + +void CachedCSSStyleSheet::finishLoading(SharedBuffer* data) { m_data = data; - setEncodedSize(m_data.get() ? m_data->size() : 0); + setEncodedSize(data ? 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.append(m_decoder->flush()); - } + if (data) + m_decodedSheetText = m_decoder->decodeAndFlush(data->data(), data->size()); setLoading(false); checkNotify(); // Clear the decoded text as it is unlikely to be needed immediately again and is cheap to regenerate. @@ -118,12 +120,12 @@ void CachedCSSStyleSheet::checkNotify() c->setCSSStyleSheet(m_resourceRequest.url(), m_response.url(), m_decoder->encoding().name(), this); } -bool CachedCSSStyleSheet::canUseSheet(bool enforceMIMEType, bool* hasValidMIMEType) const +bool CachedCSSStyleSheet::canUseSheet(MIMETypeCheck mimeTypeCheck, bool* hasValidMIMEType) const { if (errorOccurred()) return false; - - if (!enforceMIMEType && !hasValidMIMEType) + + if (mimeTypeCheck == MIMETypeCheck::Lax) return true; // This check exactly matches Firefox. Note that we grab the Content-Type @@ -133,12 +135,10 @@ bool CachedCSSStyleSheet::canUseSheet(bool enforceMIMEType, bool* hasValidMIMETy // // 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"); + String mimeType = extractMIMETypeFromMediaType(response().httpHeaderField(HTTPHeaderName::ContentType)); + bool typeOK = mimeType.isEmpty() || equalLettersIgnoringASCIICase(mimeType, "text/css") || equalLettersIgnoringASCIICase(mimeType, "application/x-unknown-content-type"); if (hasValidMIMEType) *hasValidMIMEType = typeOK; - if (!enforceMIMEType) - return true; return typeOK; } @@ -148,22 +148,19 @@ void CachedCSSStyleSheet::destroyDecodedData() return; m_parsedStyleSheetCache->removedFromMemoryCache(); - m_parsedStyleSheetCache.clear(); + m_parsedStyleSheetCache = nullptr; setDecodedSize(0); - - if (!MemoryCache::shouldMakeResourcePurgeableOnEviction() && isSafeToMakePurgeable()) - makePurgeable(true); } -PassRefPtr<StyleSheetContents> CachedCSSStyleSheet::restoreParsedStyleSheet(const CSSParserContext& context) +RefPtr<StyleSheetContents> CachedCSSStyleSheet::restoreParsedStyleSheet(const CSSParserContext& context, CachePolicy cachePolicy) { if (!m_parsedStyleSheetCache) - return 0; - if (m_parsedStyleSheetCache->hasFailedOrCanceledSubresources()) { + return nullptr; + if (!m_parsedStyleSheetCache->subresourcesAllowReuse(cachePolicy)) { m_parsedStyleSheetCache->removedFromMemoryCache(); - m_parsedStyleSheetCache.clear(); - return 0; + m_parsedStyleSheetCache = nullptr; + return nullptr; } ASSERT(m_parsedStyleSheetCache->isCacheable()); @@ -171,20 +168,20 @@ PassRefPtr<StyleSheetContents> CachedCSSStyleSheet::restoreParsedStyleSheet(cons // Contexts must be identical so we know we would get the same exact result if we parsed again. if (m_parsedStyleSheetCache->parserContext() != context) - return 0; + return nullptr; didAccessDecodedData(monotonicallyIncreasingTime()); return m_parsedStyleSheetCache; } -void CachedCSSStyleSheet::saveParsedStyleSheet(PassRef<StyleSheetContents> sheet) +void CachedCSSStyleSheet::saveParsedStyleSheet(Ref<StyleSheetContents>&& sheet) { - ASSERT(sheet.get().isCacheable()); + ASSERT(sheet->isCacheable()); if (m_parsedStyleSheetCache) m_parsedStyleSheetCache->removedFromMemoryCache(); - m_parsedStyleSheetCache = std::move(sheet); + m_parsedStyleSheetCache = WTFMove(sheet); m_parsedStyleSheetCache->addedToMemoryCache(); setDecodedSize(m_parsedStyleSheetCache->estimatedSizeInBytes()); diff --git a/Source/WebCore/loader/cache/CachedCSSStyleSheet.h b/Source/WebCore/loader/cache/CachedCSSStyleSheet.h index 19ad61939..c33aa2e40 100644 --- a/Source/WebCore/loader/cache/CachedCSSStyleSheet.h +++ b/Source/WebCore/loader/cache/CachedCSSStyleSheet.h @@ -2,7 +2,7 @@ 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. + Copyright (C) 2004-2017 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 @@ -18,55 +18,53 @@ 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 +#pragma once #include "CachedResource.h" -#include <wtf/Vector.h> namespace WebCore { - class CachedResourceClient; - class StyleSheetContents; - class TextResourceDecoder; - struct CSSParserContext; +class StyleSheetContents; +class TextResourceDecoder; + +struct CSSParserContext; + +class CachedCSSStyleSheet final : public CachedResource { +public: + CachedCSSStyleSheet(CachedResourceRequest&&, SessionID); + virtual ~CachedCSSStyleSheet(); - class CachedCSSStyleSheet final : public CachedResource { - public: - CachedCSSStyleSheet(const ResourceRequest&, const String& charset); - virtual ~CachedCSSStyleSheet(); + enum class MIMETypeCheck { Strict, Lax }; + const String sheetText(MIMETypeCheck = MIMETypeCheck::Strict, bool* hasValidMIMEType = nullptr) const; - const String sheetText(bool enforceMIMEType = true, bool* hasValidMIMEType = 0) const; + RefPtr<StyleSheetContents> restoreParsedStyleSheet(const CSSParserContext&, CachePolicy); + void saveParsedStyleSheet(Ref<StyleSheetContents>&&); - PassRefPtr<StyleSheetContents> restoreParsedStyleSheet(const CSSParserContext&); - void saveParsedStyleSheet(PassRef<StyleSheetContents>); +private: + bool canUseSheet(MIMETypeCheck, bool* hasValidMIMEType) const; + bool mayTryReplaceEncodedData() const final { return true; } - private: - bool canUseSheet(bool enforceMIMEType, bool* hasValidMIMEType) const; - virtual PurgePriority purgePriority() const override { return PurgeLast; } - virtual bool mayTryReplaceEncodedData() const override { return true; } + void didAddClient(CachedResourceClient&) final; - virtual void didAddClient(CachedResourceClient*) override; + void setEncoding(const String&) final; + String encoding() const final; + const TextResourceDecoder* textResourceDecoder() const final { return m_decoder.get(); } + void finishLoading(SharedBuffer*) final; + void destroyDecodedData() final; - virtual void setEncoding(const String&) override; - virtual String encoding() const override; - virtual void finishLoading(ResourceBuffer*) override; - virtual void destroyDecodedData() override; + void setBodyDataFrom(const CachedResource&) final; - protected: - virtual void checkNotify() override; +protected: + void checkNotify() final; - RefPtr<TextResourceDecoder> m_decoder; - String m_decodedSheetText; + RefPtr<TextResourceDecoder> m_decoder; + String m_decodedSheetText; - RefPtr<StyleSheetContents> m_parsedStyleSheetCache; - }; + RefPtr<StyleSheetContents> m_parsedStyleSheetCache; +}; -} +} // namespace WebCore -#endif +SPECIALIZE_TYPE_TRAITS_CACHED_RESOURCE(CachedCSSStyleSheet, CachedResource::CSSStyleSheet) diff --git a/Source/WebCore/loader/cache/CachedFont.cpp b/Source/WebCore/loader/cache/CachedFont.cpp index fbf658cc0..91929778b 100644 --- a/Source/WebCore/loader/cache/CachedFont.cpp +++ b/Source/WebCore/loader/cache/CachedFont.cpp @@ -11,10 +11,10 @@ * 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 + * 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 COMPUTER, INC. OR + * 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 @@ -31,27 +31,22 @@ #include "CachedResourceClientWalker.h" #include "CachedResourceLoader.h" #include "FontCustomPlatformData.h" +#include "FontDescription.h" #include "FontPlatformData.h" -#include "MemoryCache.h" -#include "ResourceBuffer.h" #include "SharedBuffer.h" #include "TextResourceDecoder.h" +#include "TypedElementDescendantIterator.h" #include "WOFFFileFormat.h" #include <wtf/Vector.h> -#if ENABLE(SVG_FONTS) -#include "NodeList.h" -#include "SVGDocument.h" -#include "SVGElement.h" -#include "SVGFontElement.h" -#include "SVGGElement.h" -#include "SVGNames.h" +#if USE(DIRECT2D) +#include <dwrite.h> #endif namespace WebCore { -CachedFont::CachedFont(const ResourceRequest& resourceRequest) - : CachedResource(resourceRequest, FontResource) +CachedFont::CachedFont(CachedResourceRequest&& request, SessionID sessionID, Type type) + : CachedResource(WTFMove(request), type, sessionID) , m_loadInitiated(false) , m_hasCreatedFontDataWrappingResource(false) { @@ -61,21 +56,20 @@ CachedFont::~CachedFont() { } -void CachedFont::load(CachedResourceLoader*, const ResourceLoaderOptions& options) +void CachedFont::load(CachedResourceLoader&) { // Don't load the file yet. Wait for an access before triggering the load. setLoading(true); - m_options = options; } -void CachedFont::didAddClient(CachedResourceClient* c) +void CachedFont::didAddClient(CachedResourceClient& client) { - ASSERT(c->resourceClientType() == CachedFontClient::expectedType()); + ASSERT(client.resourceClientType() == CachedFontClient::expectedType()); if (!isLoading()) - static_cast<CachedFontClient*>(c)->fontLoaded(this); + static_cast<CachedFontClient&>(client).fontLoaded(*this); } -void CachedFont::finishLoading(ResourceBuffer* data) +void CachedFont::finishLoading(SharedBuffer* data) { m_data = data; setEncodedSize(m_data.get() ? m_data->size() : 0); @@ -83,103 +77,76 @@ void CachedFont::finishLoading(ResourceBuffer* data) checkNotify(); } -void CachedFont::beginLoadIfNeeded(CachedResourceLoader* dl) +void CachedFont::beginLoadIfNeeded(CachedResourceLoader& loader) { if (!m_loadInitiated) { m_loadInitiated = true; - CachedResource::load(dl, m_options); + CachedResource::load(loader); } } -bool CachedFont::ensureCustomFontData() +bool CachedFont::ensureCustomFontData(const AtomicString&) { - if (!m_fontData && !errorOccurred() && !isLoading() && m_data) { - SharedBuffer* buffer = m_data.get()->sharedBuffer(); - ASSERT(buffer); - - RefPtr<SharedBuffer> sfntBuffer; - - bool fontIsWOFF = isWOFF(buffer); - if (fontIsWOFF) { - Vector<char> sfnt; - if (convertWOFFToSfnt(buffer, sfnt)) { - sfntBuffer = SharedBuffer::adoptVector(sfnt); - buffer = sfntBuffer.get(); - } else - buffer = nullptr; - } - - m_fontData = buffer ? createFontCustomPlatformData(*buffer) : nullptr; - if (m_fontData) - m_hasCreatedFontDataWrappingResource = !fontIsWOFF; - else - setStatus(DecodeError); - } - return m_fontData.get(); + return ensureCustomFontData(m_data.get()); } -FontPlatformData CachedFont::platformDataFromCustomData(float size, bool bold, bool italic, FontOrientation orientation, FontWidthVariant widthVariant, FontRenderingMode renderingMode) +bool CachedFont::ensureCustomFontData(SharedBuffer* data) { -#if ENABLE(SVG_FONTS) - if (m_externalSVGDocument) - return FontPlatformData(size, bold, italic); -#endif - ASSERT(m_fontData); - return m_fontData->fontPlatformData(static_cast<int>(size), bold, italic, orientation, widthVariant, renderingMode); -} - -#if ENABLE(SVG_FONTS) -bool CachedFont::ensureSVGFontData() -{ - if (!m_externalSVGDocument && !errorOccurred() && !isLoading() && m_data) { - m_externalSVGDocument = SVGDocument::create(0, URL()); - - RefPtr<TextResourceDecoder> decoder = TextResourceDecoder::create("application/xml"); - String svgSource = decoder->decode(m_data->data(), m_data->size()); - svgSource.append(decoder->flush()); - - m_externalSVGDocument->setContent(svgSource); - - if (decoder->sawError()) - m_externalSVGDocument = 0; + if (!m_fontCustomPlatformData && !errorOccurred() && !isLoading() && data) { + bool wrapping; + m_fontCustomPlatformData = createCustomFontData(*data, wrapping); + m_hasCreatedFontDataWrappingResource = m_fontCustomPlatformData && wrapping; + if (!m_fontCustomPlatformData) + setStatus(DecodeError); } - return m_externalSVGDocument; + return m_fontCustomPlatformData.get(); } -SVGFontElement* CachedFont::getSVGFontById(const String& fontName) const +std::unique_ptr<FontCustomPlatformData> CachedFont::createCustomFontData(SharedBuffer& bytes, bool& wrapping) { - 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(isSVGFontElement(list->item(i))); + wrapping = true; + +#if !PLATFORM(COCOA) + if (isWOFF(bytes)) { + wrapping = false; + Vector<char> convertedFont; + if (!convertWOFFToSfnt(bytes, convertedFont)) + return nullptr; + + auto buffer = SharedBuffer::adoptVector(convertedFont); + return createFontCustomPlatformData(buffer); } #endif - if (fontName.isEmpty()) - return toSVGFontElement(list->item(0)); + return createFontCustomPlatformData(bytes); +} - for (unsigned i = 0; i < listLength; ++i) { - SVGFontElement* element = toSVGFontElement(list->item(i)); - if (element->getIdAttribute() == fontName) - return element; - } +RefPtr<Font> CachedFont::createFont(const FontDescription& fontDescription, const AtomicString&, bool syntheticBold, bool syntheticItalic, const FontFeatureSettings& fontFaceFeatures, const FontVariantSettings& fontFaceVariantSettings) +{ + return Font::create(platformDataFromCustomData(fontDescription, syntheticBold, syntheticItalic, fontFaceFeatures, fontFaceVariantSettings), true); +} - return 0; +FontPlatformData CachedFont::platformDataFromCustomData(const FontDescription& fontDescription, bool bold, bool italic, const FontFeatureSettings& fontFaceFeatures, const FontVariantSettings& fontFaceVariantSettings) +{ + ASSERT(m_fontCustomPlatformData); + return platformDataFromCustomData(*m_fontCustomPlatformData, fontDescription, bold, italic, fontFaceFeatures, fontFaceVariantSettings); } + +FontPlatformData CachedFont::platformDataFromCustomData(FontCustomPlatformData& fontCustomPlatformData, const FontDescription& fontDescription, bool bold, bool italic, const FontFeatureSettings& fontFaceFeatures, const FontVariantSettings& fontFaceVariantSettings) +{ +#if PLATFORM(COCOA) + return fontCustomPlatformData.fontPlatformData(fontDescription, bold, italic, fontFaceFeatures, fontFaceVariantSettings); +#else + UNUSED_PARAM(fontFaceFeatures); + UNUSED_PARAM(fontFaceVariantSettings); + return fontCustomPlatformData.fontPlatformData(fontDescription, bold, italic); #endif +} void CachedFont::allClientsRemoved() { - m_fontData = nullptr; + m_fontCustomPlatformData = nullptr; } void CachedFont::checkNotify() @@ -187,9 +154,9 @@ void CachedFont::checkNotify() if (isLoading()) return; - CachedResourceClientWalker<CachedFontClient> w(m_clients); - while (CachedFontClient* c = w.next()) - c->fontLoaded(this); + CachedResourceClientWalker<CachedFontClient> walker(m_clients); + while (CachedFontClient* client = walker.next()) + client->fontLoaded(*this); } bool CachedFont::mayTryReplaceEncodedData() const diff --git a/Source/WebCore/loader/cache/CachedFont.h b/Source/WebCore/loader/cache/CachedFont.h index f19582b96..25d292f0f 100644 --- a/Source/WebCore/loader/cache/CachedFont.h +++ b/Source/WebCore/loader/cache/CachedFont.h @@ -10,10 +10,10 @@ * 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 + * 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 COMPUTER, INC. OR + * 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 @@ -23,61 +23,63 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef CachedFont_h -#define CachedFont_h +#pragma once #include "CachedResource.h" #include "CachedResourceClient.h" -#include "FontOrientation.h" -#include "FontRenderingMode.h" -#include "FontWidthVariant.h" +#include "Font.h" +#include "TextFlags.h" namespace WebCore { class CachedResourceLoader; +class FontDescription; class FontPlatformData; class SVGDocument; class SVGFontElement; struct FontCustomPlatformData; -class CachedFont final : public CachedResource { +template <typename T> class FontTaggedSettings; +typedef FontTaggedSettings<int> FontFeatureSettings; + +class CachedFont : public CachedResource { public: - CachedFont(const ResourceRequest&); + CachedFont(CachedResourceRequest&&, SessionID, Type = FontResource); virtual ~CachedFont(); - void beginLoadIfNeeded(CachedResourceLoader* dl); - virtual bool stillNeedsLoad() const override { return !m_loadInitiated; } + void beginLoadIfNeeded(CachedResourceLoader&); + bool stillNeedsLoad() const override { return !m_loadInitiated; } + + virtual bool ensureCustomFontData(const AtomicString& remoteURI); + static std::unique_ptr<FontCustomPlatformData> createCustomFontData(SharedBuffer&, bool& wrapping); + static FontPlatformData platformDataFromCustomData(FontCustomPlatformData&, const FontDescription&, bool bold, bool italic, const FontFeatureSettings&, const FontVariantSettings&); - bool ensureCustomFontData(); - FontPlatformData platformDataFromCustomData(float size, bool bold, bool italic, FontOrientation = Horizontal, FontWidthVariant = RegularWidth, FontRenderingMode = NormalRenderingMode); + virtual RefPtr<Font> createFont(const FontDescription&, const AtomicString& remoteURI, bool syntheticBold, bool syntheticItalic, const FontFeatureSettings&, const FontVariantSettings&); -#if ENABLE(SVG_FONTS) - bool ensureSVGFontData(); - SVGFontElement* getSVGFontById(const String&) const; -#endif +protected: + FontPlatformData platformDataFromCustomData(const FontDescription&, bool bold, bool italic, const FontFeatureSettings&, const FontVariantSettings&); + + bool ensureCustomFontData(SharedBuffer* data); private: - virtual void checkNotify() override; - virtual bool mayTryReplaceEncodedData() const override; + void checkNotify() override; + bool mayTryReplaceEncodedData() const override; - virtual void load(CachedResourceLoader*, const ResourceLoaderOptions&) override; + void load(CachedResourceLoader&) override; + NO_RETURN_DUE_TO_ASSERT void setBodyDataFrom(const CachedResource&) final { ASSERT_NOT_REACHED(); } - virtual void didAddClient(CachedResourceClient*) override; - virtual void finishLoading(ResourceBuffer*) override; + void didAddClient(CachedResourceClient&) override; + void finishLoading(SharedBuffer*) override; - virtual void allClientsRemoved() override; + void allClientsRemoved() override; - std::unique_ptr<FontCustomPlatformData> m_fontData; + std::unique_ptr<FontCustomPlatformData> m_fontCustomPlatformData; bool m_loadInitiated; bool m_hasCreatedFontDataWrappingResource; -#if ENABLE(SVG_FONTS) - RefPtr<SVGDocument> m_externalSVGDocument; -#endif - friend class MemoryCache; }; } // namespace WebCore -#endif // CachedFont_h +SPECIALIZE_TYPE_TRAITS_CACHED_RESOURCE(CachedFont, CachedResource::FontResource) diff --git a/Source/WebCore/loader/cache/CachedFontClient.h b/Source/WebCore/loader/cache/CachedFontClient.h index e87040ea2..a1388fb88 100644 --- a/Source/WebCore/loader/cache/CachedFontClient.h +++ b/Source/WebCore/loader/cache/CachedFontClient.h @@ -23,8 +23,7 @@ * THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef CachedFontClient_h -#define CachedFontClient_h +#pragma once #include "CachedResourceClient.h" @@ -36,10 +35,8 @@ class CachedFontClient : public CachedResourceClient { public: virtual ~CachedFontClient() { } static CachedResourceClientType expectedType() { return FontType; } - virtual CachedResourceClientType resourceClientType() const override { return expectedType(); } - virtual void fontLoaded(CachedFont*) { } + CachedResourceClientType resourceClientType() const override { return expectedType(); } + virtual void fontLoaded(CachedFont&) { } }; } // namespace WebCore - -#endif // CachedFontClient_h diff --git a/Source/WebCore/loader/cache/CachedImage.cpp b/Source/WebCore/loader/cache/CachedImage.cpp index 5525fb1f3..65f0bf79c 100644 --- a/Source/WebCore/loader/cache/CachedImage.cpp +++ b/Source/WebCore/loader/cache/CachedImage.cpp @@ -35,15 +35,15 @@ #include "FrameLoaderTypes.h" #include "FrameView.h" #include "MemoryCache.h" -#include "Page.h" #include "RenderElement.h" -#include "ResourceBuffer.h" +#include "SVGImage.h" #include "SecurityOrigin.h" #include "Settings.h" +#include "SharedBuffer.h" #include "SubresourceLoader.h" #include <wtf/CurrentTime.h> +#include <wtf/NeverDestroyed.h> #include <wtf/StdLibExtras.h> -#include <wtf/Vector.h> #if PLATFORM(IOS) #include "SystemMemory.h" @@ -53,40 +53,28 @@ #include "PDFDocumentImage.h" #endif -#if ENABLE(SVG) -#include "SVGImage.h" -#endif - -#if ENABLE(DISK_IMAGE_CACHE) -#include "DiskImageCacheIOS.h" -#endif - namespace WebCore { -CachedImage::CachedImage(const ResourceRequest& resourceRequest) - : CachedResource(resourceRequest, ImageResource) - , m_image(0) - , m_shouldPaintBrokenImage(true) +CachedImage::CachedImage(CachedResourceRequest&& request, SessionID sessionID) + : CachedResource(WTFMove(request), ImageResource, sessionID) { setStatus(Unknown); } -CachedImage::CachedImage(Image* image) - : CachedResource(ResourceRequest(), ImageResource) +CachedImage::CachedImage(Image* image, SessionID sessionID) + : CachedResource(URL(), ImageResource, sessionID) , m_image(image) - , m_shouldPaintBrokenImage(true) { - setStatus(Cached); - setLoading(false); } -CachedImage::CachedImage(const URL& url, Image* image) - : CachedResource(ResourceRequest(url), ImageResource) +CachedImage::CachedImage(const URL& url, Image* image, SessionID sessionID) + : CachedResource(url, ImageResource, sessionID) , m_image(image) - , m_shouldPaintBrokenImage(true) + , m_isManuallyCached(true) { - setStatus(Cached); - setLoading(false); + // Use the incoming URL in the response field. This ensures that code using the response directly, + // such as origin checks for security, actually see something. + m_response.setURL(url); } CachedImage::~CachedImage() @@ -94,56 +82,69 @@ CachedImage::~CachedImage() clearImage(); } -void CachedImage::load(CachedResourceLoader* cachedResourceLoader, const ResourceLoaderOptions& options) +void CachedImage::load(CachedResourceLoader& loader) { - if (!cachedResourceLoader || cachedResourceLoader->autoLoadImages()) - CachedResource::load(cachedResourceLoader, options); + if (loader.shouldPerformImageLoad(url())) + CachedResource::load(loader); else setLoading(false); } -void CachedImage::didAddClient(CachedResourceClient* c) +void CachedImage::setBodyDataFrom(const CachedResource& resource) +{ + ASSERT(resource.type() == type()); + const CachedImage& image = static_cast<const CachedImage&>(resource); + + CachedResource::setBodyDataFrom(resource); + + m_image = image.m_image; + m_imageObserver = image.m_imageObserver; + if (m_imageObserver) + m_imageObserver->add(*this); + + if (m_image && is<SVGImage>(*m_image)) + m_svgImageCache = std::make_unique<SVGImageCache>(&downcast<SVGImage>(*m_image)); +} + +void CachedImage::didAddClient(CachedResourceClient& client) { if (m_data && !m_image && !errorOccurred()) { createImage(); - m_image->setData(m_data->sharedBuffer(), true); + m_image->setData(m_data.copyRef(), true); } - - ASSERT(c->resourceClientType() == CachedImageClient::expectedType()); + + ASSERT(client.resourceClientType() == CachedImageClient::expectedType()); if (m_image && !m_image->isNull()) - static_cast<CachedImageClient*>(c)->imageChanged(this); + static_cast<CachedImageClient&>(client).imageChanged(this); - CachedResource::didAddClient(c); + CachedResource::didAddClient(client); } -void CachedImage::didRemoveClient(CachedResourceClient* c) +void CachedImage::didRemoveClient(CachedResourceClient& client) { - ASSERT(c); - ASSERT(c->resourceClientType() == CachedImageClient::expectedType()); + ASSERT(client.resourceClientType() == CachedImageClient::expectedType()); + + m_pendingContainerSizeRequests.remove(&static_cast<CachedImageClient&>(client)); - m_pendingContainerSizeRequests.remove(static_cast<CachedImageClient*>(c)); -#if ENABLE(SVG) if (m_svgImageCache) - m_svgImageCache->removeClientFromCache(static_cast<CachedImageClient*>(c)); -#endif + m_svgImageCache->removeClientFromCache(&static_cast<CachedImageClient&>(client)); - CachedResource::didRemoveClient(c); + CachedResource::didRemoveClient(client); } void CachedImage::switchClientsToRevalidatedResource() { - ASSERT(resourceToRevalidate()); - ASSERT(resourceToRevalidate()->isImage()); + ASSERT(is<CachedImage>(resourceToRevalidate())); // Pending container size requests need to be transferred to the revalidated resource. if (!m_pendingContainerSizeRequests.isEmpty()) { // A copy of pending size requests is needed as they are deleted during CachedResource::switchClientsToRevalidateResouce(). ContainerSizeRequests switchContainerSizeRequests; - for (ContainerSizeRequests::iterator it = m_pendingContainerSizeRequests.begin(); it != m_pendingContainerSizeRequests.end(); ++it) - switchContainerSizeRequests.set(it->key, it->value); + for (auto& request : m_pendingContainerSizeRequests) + switchContainerSizeRequests.set(request.key, request.value); CachedResource::switchClientsToRevalidatedResource(); - CachedImage* revalidatedCachedImage = static_cast<CachedImage*>(resourceToRevalidate()); - for (ContainerSizeRequests::iterator it = switchContainerSizeRequests.begin(); it != switchContainerSizeRequests.end(); ++it) - revalidatedCachedImage->setContainerSizeForRenderer(it->key, it->value.first, it->value.second); + CachedImage& revalidatedCachedImage = downcast<CachedImage>(*resourceToRevalidate()); + for (auto& request : switchContainerSizeRequests) + revalidatedCachedImage.setContainerSizeForRenderer(request.key, request.value.first, request.value.second); return; } @@ -159,12 +160,17 @@ void CachedImage::allClientsRemoved() std::pair<Image*, float> CachedImage::brokenImage(float deviceScaleFactor) const { + if (deviceScaleFactor >= 3) { + static NeverDestroyed<Image*> brokenImageVeryHiRes(Image::loadPlatformResource("missingImage@3x").leakRef()); + return std::make_pair(brokenImageVeryHiRes, 3); + } + if (deviceScaleFactor >= 2) { - DEFINE_STATIC_LOCAL(Image*, brokenImageHiRes, (Image::loadPlatformResource("missingImage@2x").leakRef())); + static NeverDestroyed<Image*> brokenImageHiRes(Image::loadPlatformResource("missingImage@2x").leakRef()); return std::make_pair(brokenImageHiRes, 2); } - DEFINE_STATIC_LOCAL(Image*, brokenImageLoRes, (Image::loadPlatformResource("missingImage").leakRef())); + static NeverDestroyed<Image*> brokenImageLoRes(Image::loadPlatformResource("missingImage").leakRef()); return std::make_pair(brokenImageLoRes, 1); } @@ -175,8 +181,6 @@ bool CachedImage::willPaintBrokenImage() const Image* CachedImage::image() { - ASSERT(!isPurgeable()); - if (errorOccurred() && m_shouldPaintBrokenImage) { // Returning the 1x broken image is non-ideal, but we cannot reliably access the appropriate // deviceScaleFactor from here. It is critical that callers use CachedImage::brokenImage() @@ -192,8 +196,6 @@ Image* CachedImage::image() Image* CachedImage::imageForRenderer(const RenderObject* renderer) { - ASSERT(!isPurgeable()); - if (errorOccurred() && m_shouldPaintBrokenImage) { // Returning the 1x broken image is non-ideal, but we cannot reliably access the appropriate // deviceScaleFactor from here. It is critical that callers use CachedImage::brokenImage() @@ -204,19 +206,15 @@ Image* CachedImage::imageForRenderer(const RenderObject* renderer) if (!m_image) return Image::nullImage(); -#if ENABLE(SVG) if (m_image->isSVGImage()) { Image* image = m_svgImageCache->imageForRenderer(renderer); if (image != Image::nullImage()) return image; } -#else - UNUSED_PARAM(renderer); -#endif return m_image.get(); } -void CachedImage::setContainerSizeForRenderer(const CachedImageClient* renderer, const IntSize& containerSize, float containerZoom) +void CachedImage::setContainerSizeForRenderer(const CachedImageClient* renderer, const LayoutSize& containerSize, float containerZoom) { if (containerSize.isEmpty()) return; @@ -226,17 +224,13 @@ void CachedImage::setContainerSizeForRenderer(const CachedImageClient* renderer, m_pendingContainerSizeRequests.set(renderer, SizeAndZoom(containerSize, containerZoom)); return; } -#if ENABLE(SVG) + if (!m_image->isSVGImage()) { m_image->setContainerSize(containerSize); return; } m_svgImageCache->setContainerSizeForRenderer(renderer, containerSize, containerZoom); -#else - UNUSED_PARAM(containerZoom); - m_image->setContainerSize(containerSize); -#endif } bool CachedImage::usesImageContainerSize() const @@ -263,42 +257,19 @@ bool CachedImage::imageHasRelativeHeight() const return false; } -LayoutSize CachedImage::imageSizeForRenderer(const RenderObject* renderer, float multiplier, SizeType sizeType) +LayoutSize CachedImage::imageSizeForRenderer(const RenderElement* renderer, float multiplier, SizeType sizeType) { - ASSERT(!isPurgeable()); - if (!m_image) - return IntSize(); + return LayoutSize(); - LayoutSize imageSize(m_image->size()); + LayoutSize imageSize; -#if ENABLE(CSS_IMAGE_ORIENTATION) - if (renderer && m_image->isBitmapImage()) { - ImageOrientationDescription orientationDescription(renderer->shouldRespectImageOrientation(), renderer->style().imageOrientation()); - if (orientationDescription.respectImageOrientation() == RespectImageOrientation) - imageSize = toBitmapImage(m_image.get())->sizeRespectingOrientation(orientationDescription); - } -#else - if (m_image->isBitmapImage() && (renderer && renderer->shouldRespectImageOrientation() == RespectImageOrientation)) -#if !PLATFORM(IOS) - imageSize = toBitmapImage(m_image.get())->sizeRespectingOrientation(); -#else - { - // On iOS, the image may have been subsampled to accommodate our size restrictions. However - // we should tell the renderer what the original size was. - imageSize = toBitmapImage(m_image.get())->originalSizeRespectingOrientation(); - } else if (m_image->isBitmapImage()) - imageSize = toBitmapImage(m_image.get())->originalSize(); -#endif // !PLATFORM(IOS) -#endif // ENABLE(CSS_IMAGE_ORIENTATION) - -#if ENABLE(SVG) - else if (m_image->isSVGImage() && sizeType == UsedSize) { - imageSize = m_svgImageCache->imageSizeForRenderer(renderer); - } -#else - UNUSED_PARAM(sizeType); -#endif + if (is<BitmapImage>(*m_image) && renderer && renderer->shouldRespectImageOrientation() == RespectImageOrientation) + imageSize = LayoutSize(downcast<BitmapImage>(*m_image).sizeRespectingOrientation()); + else if (is<SVGImage>(*m_image) && sizeType == UsedSize) + imageSize = LayoutSize(m_svgImageCache->imageSizeForRenderer(renderer)); + else + imageSize = LayoutSize(m_image->size()); if (multiplier == 1.0f) return imageSize; @@ -331,7 +302,7 @@ void CachedImage::checkShouldPaintBrokenImage() if (!m_loader || m_loader->reachedTerminalState()) return; - m_shouldPaintBrokenImage = m_loader->frameLoader()->client().shouldPaintBrokenImage(m_resourceRequest.url()); + m_shouldPaintBrokenImage = m_loader->frameLoader()->client().shouldPaintBrokenImage(url()); } void CachedImage::clear() @@ -347,77 +318,98 @@ inline void CachedImage::createImage() // Create the image if it doesn't yet exist. if (m_image) return; + + m_imageObserver = CachedImageObserver::create(*this); + + if (m_response.mimeType() == "image/svg+xml") { + auto svgImage = SVGImage::create(*m_imageObserver, url()); + m_svgImageCache = std::make_unique<SVGImageCache>(svgImage.ptr()); + m_image = WTFMove(svgImage); #if USE(CG) && !USE(WEBKIT_IMAGE_DECODERS) - else if (m_response.mimeType() == "application/pdf") - m_image = PDFDocumentImage::create(this); + } else if (m_response.mimeType() == "application/pdf") { + m_image = PDFDocumentImage::create(m_imageObserver.get()); #endif -#if ENABLE(SVG) - else if (m_response.mimeType() == "image/svg+xml") { - RefPtr<SVGImage> svgImage = SVGImage::create(this); - m_svgImageCache = std::make_unique<SVGImageCache>(svgImage.get()); - m_image = svgImage.release(); - } -#endif - else - m_image = BitmapImage::create(this); + } else + m_image = BitmapImage::create(m_imageObserver.get()); if (m_image) { // Send queued container size requests. if (m_image->usesContainerSize()) { - for (ContainerSizeRequests::iterator it = m_pendingContainerSizeRequests.begin(); it != m_pendingContainerSizeRequests.end(); ++it) - setContainerSizeForRenderer(it->key, it->value.first, it->value.second); + for (auto& request : m_pendingContainerSizeRequests) + setContainerSizeForRenderer(request.key, request.value.first, request.value.second); } m_pendingContainerSizeRequests.clear(); } } -inline void CachedImage::clearImage() +CachedImage::CachedImageObserver::CachedImageObserver(CachedImage& image) { - // If our Image has an observer, it's always us so we need to clear the back pointer - // before dropping our reference. - if (m_image) - m_image->setImageObserver(0); - m_image.clear(); + m_cachedImages.reserveInitialCapacity(1); + m_cachedImages.append(&image); + if (auto* loader = image.loader()) { + m_allowSubsampling = loader->frameLoader()->frame().settings().imageSubsamplingEnabled(); + m_allowLargeImageAsyncDecoding = loader->frameLoader()->frame().settings().largeImageAsyncDecodingEnabled(); + m_allowAnimatedImageAsyncDecoding = loader->frameLoader()->frame().settings().animatedImageAsyncDecodingEnabled(); + m_showDebugBackground = loader->frameLoader()->frame().settings().showDebugBorders(); + } } -bool CachedImage::canBeDrawn() const +void CachedImage::CachedImageObserver::decodedSizeChanged(const Image* image, long long delta) { - if (!m_image || m_image->isNull()) - return false; + for (auto cachedImage : m_cachedImages) + cachedImage->decodedSizeChanged(image, delta); +} - if (!m_loader || m_loader->reachedTerminalState()) - return true; +void CachedImage::CachedImageObserver::didDraw(const Image* image) +{ + for (auto cachedImage : m_cachedImages) + cachedImage->didDraw(image); +} - size_t estimatedDecodedImageSize = m_image->width() * m_image->height() * 4; // no overflow check - return estimatedDecodedImageSize <= m_loader->frameLoader()->frame().settings().maximumDecodedImageSize(); +void CachedImage::CachedImageObserver::animationAdvanced(const Image* image) +{ + for (auto cachedImage : m_cachedImages) + cachedImage->animationAdvanced(image); } -void CachedImage::addIncrementalDataBuffer(ResourceBuffer* data) +void CachedImage::CachedImageObserver::changedInRect(const Image* image, const IntRect* rect) { - m_data = data; - if (!data) - return; + for (auto cachedImage : m_cachedImages) + cachedImage->changedInRect(image, rect); +} + +inline void CachedImage::clearImage() +{ + if (m_imageObserver) { + m_imageObserver->remove(*this); + m_imageObserver = nullptr; + } + m_image = nullptr; +} + +void CachedImage::addIncrementalDataBuffer(SharedBuffer& data) +{ + m_data = &data; createImage(); // 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). - bool sizeAvailable = m_image->setData(m_data->sharedBuffer(), false); + bool sizeAvailable = m_image->setData(&data, false); if (!sizeAvailable) return; - if (!canBeDrawn()) { - // There's no image to draw or its decoded size is bigger than the maximum allowed. + if (m_image->isNull()) { + // Image decoding failed. Either we need more image data or the image data is malformed. error(errorOccurred() ? status() : DecodeError); if (inCache()) - memoryCache()->remove(this); + MemoryCache::singleton().remove(*this); return; } - // Go ahead and tell our observers to try to draw. - // Each chunk from the network causes observers to repaint, which will - // force that chunk to decode. + // Tell our observers to try to draw. + // Each chunk from the network causes observers to repaint, which will force that chunk to decode. // 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(); @@ -425,32 +417,34 @@ void CachedImage::addIncrementalDataBuffer(ResourceBuffer* data) setEncodedSize(m_image->data() ? m_image->data()->size() : 0); } -void CachedImage::addDataBuffer(ResourceBuffer* data) +void CachedImage::addDataBuffer(SharedBuffer& data) { - ASSERT(m_options.dataBufferingPolicy == BufferData); + ASSERT(dataBufferingPolicy() == BufferData); addIncrementalDataBuffer(data); + CachedResource::addDataBuffer(data); } void CachedImage::addData(const char* data, unsigned length) { - ASSERT(m_options.dataBufferingPolicy == DoNotBufferData); - addIncrementalDataBuffer(ResourceBuffer::create(data, length).get()); + ASSERT(dataBufferingPolicy() == DoNotBufferData); + addIncrementalDataBuffer(SharedBuffer::create(data, length)); + CachedResource::addData(data, length); } -void CachedImage::finishLoading(ResourceBuffer* data) +void CachedImage::finishLoading(SharedBuffer* data) { m_data = data; if (!m_image && data) createImage(); if (m_image) - m_image->setData(m_data->sharedBuffer(), true); + m_image->setData(data, true); - if (!canBeDrawn()) { - // There's no image to draw or its decoded size is bigger than the maximum allowed. + if (!m_image || m_image->isNull()) { + // Image decoding failed; the image data is malformed. error(errorOccurred() ? status() : DecodeError); if (inCache()) - memoryCache()->remove(this); + MemoryCache::singleton().remove(*this); return; } @@ -460,6 +454,16 @@ void CachedImage::finishLoading(ResourceBuffer* data) CachedResource::finishLoading(data); } +void CachedImage::didReplaceSharedBufferContents() +{ + if (m_image) { + // Let the Image know that the SharedBuffer has been rejigged, so it can let go of any references to the heap-allocated resource buffer. + // FIXME(rdar://problem/24275617): It would be better if we could somehow tell the Image's decoder to swap in the new contents without destroying anything. + m_image->destroyDecodedData(true); + } + CachedResource::didReplaceSharedBufferContents(); +} + void CachedImage::error(CachedResource::Status status) { checkShouldPaintBrokenImage(); @@ -478,23 +482,20 @@ void CachedImage::responseReceived(const ResourceResponse& response) 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; + if (canDeleteImage && !isLoading() && !hasClients()) { + m_image = nullptr; setDecodedSize(0); - if (!MemoryCache::shouldMakeResourcePurgeableOnEviction()) - makePurgeable(true); } else if (m_image && !errorOccurred()) m_image->destroyDecodedData(); } -void CachedImage::decodedSizeChanged(const Image* image, int delta) +void CachedImage::decodedSizeChanged(const Image* image, long long delta) { if (!image || image != m_image) return; - - setDecodedSize(decodedSize() + delta); + + ASSERT(delta >= 0 || decodedSize() + delta >= 0); + setDecodedSize(static_cast<unsigned>(decodedSize() + delta)); } void CachedImage::didDraw(const Image* image) @@ -509,132 +510,47 @@ void CachedImage::didDraw(const Image* image) CachedResource::didAccessDecodedData(timeStamp); } -bool CachedImage::shouldPauseAnimation(const Image* image) -{ - if (!image || image != m_image) - return false; - - CachedResourceClientWalker<CachedImageClient> w(m_clients); - while (CachedImageClient* c = w.next()) { - if (c->willRenderImage(this)) - return false; - } - - return true; -} - void CachedImage::animationAdvanced(const Image* image) { if (!image || image != m_image) return; - notifyObservers(); + CachedResourceClientWalker<CachedImageClient> clientWalker(m_clients); + while (CachedImageClient* client = clientWalker.next()) + client->newImageAnimationFrameAvailable(*this); } -void CachedImage::changedInRect(const Image* image, const IntRect& rect) +void CachedImage::changedInRect(const Image* image, const IntRect* rect) { if (!image || image != m_image) return; - notifyObservers(&rect); -} - -void CachedImage::resumeAnimatingImagesForLoader(CachedResourceLoader* loader) -{ - const CachedResourceLoader::DocumentResourceMap& resources = loader->allCachedResources(); - - for (CachedResourceLoader::DocumentResourceMap::const_iterator it = resources.begin(), end = resources.end(); it != end; ++it) { - const CachedResourceHandle<CachedResource>& resource = it->value; - if (!resource || !resource->isImage()) - continue; - CachedImage* cachedImage = static_cast<CachedImage*>(resource.get()); - if (!cachedImage->hasImage()) - continue; - Image* image = cachedImage->image(); - if (!image->isBitmapImage()) - continue; - BitmapImage* bitmapImage = toBitmapImage(image); - if (!bitmapImage->canAnimate()) - continue; - cachedImage->animationAdvanced(bitmapImage); - } + notifyObservers(rect); } bool CachedImage::currentFrameKnownToBeOpaque(const RenderElement* renderer) { Image* image = imageForRenderer(renderer); - if (image->isBitmapImage()) - image->nativeImageForCurrentFrame(); // force decode return image->currentFrameKnownToBeOpaque(); } -#if ENABLE(DISK_IMAGE_CACHE) -bool CachedImage::canUseDiskImageCache() const -{ - if (isLoading() || errorOccurred()) - return false; - - if (!m_data) - return false; - - if (isPurgeable()) - return false; - - if (m_data->size() < diskImageCache().minimumImageSize()) - return false; - - // "Cache-Control: no-store" resources may be marked as such because they may - // contain sensitive information. We should not write these resources to disk. - if (m_response.cacheControlContainsNoStore()) - return false; - - // Testing shows that PDF images did not work when memory mapped. - // However, SVG images and Bitmap images were fine. See: - // <rdar://problem/8591834> Disk Image Cache should support PDF Images - if (m_response.mimeType() == "application/pdf") - return false; - - return true; -} - -void CachedImage::useDiskImageCache() +bool CachedImage::isOriginClean(SecurityOrigin* origin) { - ASSERT(canUseDiskImageCache()); - ASSERT(!isUsingDiskImageCache()); - m_data->sharedBuffer()->allowToBeMemoryMapped(); -} -#endif - -bool CachedImage::isOriginClean(SecurityOrigin* securityOrigin) -{ - if (!image()->hasSingleSecurityOrigin()) - return false; - if (passesAccessControlCheck(securityOrigin)) - return true; - return !securityOrigin->taintsCanvas(response().url()); + ASSERT_UNUSED(origin, origin); + ASSERT(this->origin()); + ASSERT(origin->toString() == this->origin()->toString()); + return !loadFailedOrCanceled() && isCORSSameOrigin(); } -#if USE(CF) -// FIXME: We should look to incorporate the functionality of CachedImageManual -// into CachedImage or find a better place for this class. -// FIXME: Remove the USE(CF) once we make MemoryCache::addImageToCache() platform-independent. -CachedImageManual::CachedImageManual(const URL& url, Image* image) - : CachedImage(url, image) - , m_fakeClient(std::make_unique<CachedImageClient>()) +CachedResource::RevalidationDecision CachedImage::makeRevalidationDecision(CachePolicy cachePolicy) const { - // Use the incoming URL in the response field. This ensures that code - // using the response directly, such as origin checks for security, - // actually see something. - m_response.setURL(url); -} - -bool CachedImageManual::mustRevalidateDueToCacheHeaders(CachePolicy) const -{ - // Do not revalidate manually cached images. This mechanism is used as a - // way to efficiently share an image from the client to content and - // the URL for that image may not represent a resource that can be - // retrieved by standard means. If the manual caching SPI is used, it is - // incumbent on the client to only use valid resources. - return false; + if (UNLIKELY(isManuallyCached())) { + // Do not revalidate manually cached images. This mechanism is used as a + // way to efficiently share an image from the client to content and + // the URL for that image may not represent a resource that can be + // retrieved by standard means. If the manual caching SPI is used, it is + // incumbent on the client to only use valid resources. + return RevalidationDecision::No; + } + return CachedResource::makeRevalidationDecision(cachePolicy); } -#endif } // namespace WebCore diff --git a/Source/WebCore/loader/cache/CachedImage.h b/Source/WebCore/loader/cache/CachedImage.h index 12b77cc46..985aa9749 100644 --- a/Source/WebCore/loader/cache/CachedImage.h +++ b/Source/WebCore/loader/cache/CachedImage.h @@ -20,17 +20,16 @@ Boston, MA 02110-1301, USA. */ -#ifndef CachedImage_h -#define CachedImage_h +#pragma once #include "CachedResource.h" +#include "Image.h" #include "ImageObserver.h" #include "IntRect.h" #include "IntSizeHash.h" #include "LayoutSize.h" #include "SVGImageCache.h" #include <wtf/HashMap.h> -#include <wtf/Vector.h> namespace WebCore { @@ -44,124 +43,132 @@ class SecurityOrigin; struct Length; -class CachedImage : public CachedResource, public ImageObserver { +class CachedImage final : public CachedResource { friend class MemoryCache; public: - CachedImage(const ResourceRequest&); - CachedImage(Image*); - CachedImage(const URL&, Image*); + CachedImage(CachedResourceRequest&&, SessionID); + CachedImage(Image*, SessionID); + // Constructor to use for manually cached images. + CachedImage(const URL&, Image*, SessionID); virtual ~CachedImage(); - Image* image(); // Returns the nullImage() if the image is not available yet. - Image* imageForRenderer(const RenderObject*); // Returns the nullImage() if the image is not available yet. + WEBCORE_EXPORT Image* image(); // Returns the nullImage() if the image is not available yet. + WEBCORE_EXPORT Image* imageForRenderer(const RenderObject*); // Returns the nullImage() if the image is not available yet. bool hasImage() const { return m_image.get(); } - bool currentFrameKnownToBeOpaque(const RenderElement*); // Side effect: ensures decoded image is in cache, therefore should only be called when about to draw the image. + bool currentFrameKnownToBeOpaque(const RenderElement*); std::pair<Image*, float> brokenImage(float deviceScaleFactor) const; // Returns an image and the image's resolution scale factor. - bool willPaintBrokenImage() const; + bool willPaintBrokenImage() const; - bool canRender(const RenderObject* renderer, float multiplier) { return !errorOccurred() && !imageSizeForRenderer(renderer, multiplier).isEmpty(); } + bool canRender(const RenderElement* renderer, float multiplier) { return !errorOccurred() && !imageSizeForRenderer(renderer, multiplier).isEmpty(); } - void setContainerSizeForRenderer(const CachedImageClient*, const IntSize&, float); + void setContainerSizeForRenderer(const CachedImageClient*, const LayoutSize&, float); bool usesImageContainerSize() const; bool imageHasRelativeWidth() const; bool imageHasRelativeHeight() const; - virtual void addDataBuffer(ResourceBuffer*) override; - virtual void finishLoading(ResourceBuffer*) override; + void addDataBuffer(SharedBuffer&) override; + void finishLoading(SharedBuffer*) override; enum SizeType { UsedSize, IntrinsicSize }; // This method takes a zoom multiplier that can be used to increase the natural size of the image by the zoom. - LayoutSize imageSizeForRenderer(const RenderObject*, float multiplier, SizeType = UsedSize); // returns the size of the complete image. + LayoutSize imageSizeForRenderer(const RenderElement*, float multiplier, SizeType = UsedSize); // returns the size of the complete image. void computeIntrinsicDimensions(Length& intrinsicWidth, Length& intrinsicHeight, FloatSize& intrinsicRatio); -#if USE(CF) - // FIXME: Remove the USE(CF) once we make MemoryCache::addImageToCache() platform-independent. - virtual bool isManual() const { return false; } -#endif - - static void resumeAnimatingImagesForLoader(CachedResourceLoader*); - -#if ENABLE(DISK_IMAGE_CACHE) - virtual bool canUseDiskImageCache() const override; - virtual void useDiskImageCache() override; -#endif + bool isManuallyCached() const { return m_isManuallyCached; } + RevalidationDecision makeRevalidationDecision(CachePolicy) const override; + void load(CachedResourceLoader&) override; bool isOriginClean(SecurityOrigin*); private: - virtual void load(CachedResourceLoader*, const ResourceLoaderOptions&) override; - void clear(); + CachedImage(CachedImage&, const ResourceRequest&, SessionID); + + void setBodyDataFrom(const CachedResource&) final; + void createImage(); void clearImage(); - bool canBeDrawn() const; // If not null, changeRect is the changed part of the image. - void notifyObservers(const IntRect* changeRect = 0); - virtual PurgePriority purgePriority() const override { return PurgeFirst; } + void notifyObservers(const IntRect* changeRect = nullptr); void checkShouldPaintBrokenImage(); - virtual void switchClientsToRevalidatedResource() override; - virtual bool mayTryReplaceEncodedData() const override { return true; } + void switchClientsToRevalidatedResource() final; + bool mayTryReplaceEncodedData() const final { return true; } - virtual void didAddClient(CachedResourceClient*) override; - virtual void didRemoveClient(CachedResourceClient*) override; + void didAddClient(CachedResourceClient&) final; + void didRemoveClient(CachedResourceClient&) final; - virtual void allClientsRemoved() override; - virtual void destroyDecodedData() override; + void allClientsRemoved() override; + void destroyDecodedData() override; - virtual void addData(const char* data, unsigned length) override; - virtual void error(CachedResource::Status) override; - virtual void responseReceived(const ResourceResponse&) override; + void addData(const char* data, unsigned length) override; + void error(CachedResource::Status) override; + void responseReceived(const ResourceResponse&) override; // For compatibility, images keep loading even if there are HTTP errors. - virtual bool shouldIgnoreHTTPStatusCodeErrors() const override { return true; } - - virtual bool isImage() const override { return true; } - virtual bool stillNeedsLoad() const override { return !errorOccurred() && status() == Unknown && !isLoading(); } + bool shouldIgnoreHTTPStatusCodeErrors() const override { return true; } + + bool stillNeedsLoad() const override { return !errorOccurred() && status() == Unknown && !isLoading(); } + + class CachedImageObserver final : public RefCounted<CachedImageObserver>, public ImageObserver { + public: + static Ref<CachedImageObserver> create(CachedImage& image) { return adoptRef(*new CachedImageObserver(image)); } + void add(CachedImage& image) { m_cachedImages.append(&image); } + void remove(CachedImage& image) { m_cachedImages.removeFirst(&image); } + + private: + explicit CachedImageObserver(CachedImage&); + + // ImageObserver API + URL sourceUrl() const override { return m_cachedImages[0]->url(); } + bool allowSubsampling() const final { return m_allowSubsampling; } + bool allowLargeImageAsyncDecoding() const override { return m_allowLargeImageAsyncDecoding; } + bool allowAnimatedImageAsyncDecoding() const override { return m_allowAnimatedImageAsyncDecoding; } + bool showDebugBackground() const final { return m_showDebugBackground; } + void decodedSizeChanged(const Image*, long long delta) final; + void didDraw(const Image*) final; + + void animationAdvanced(const Image*) final; + void changedInRect(const Image*, const IntRect*) final; + + Vector<CachedImage*> m_cachedImages; + // The default value of m_allowSubsampling should be the same as defaultImageSubsamplingEnabled in Settings.cpp +#if PLATFORM(IOS) + bool m_allowSubsampling { true }; +#else + bool m_allowSubsampling { false }; +#endif + bool m_allowLargeImageAsyncDecoding { true }; + bool m_allowAnimatedImageAsyncDecoding { true }; + bool m_showDebugBackground { false }; + }; - // ImageObserver - virtual void decodedSizeChanged(const Image*, int delta) override; - virtual void didDraw(const Image*) override; + void decodedSizeChanged(const Image*, long long delta); + void didDraw(const Image*); + void animationAdvanced(const Image*); + void changedInRect(const Image*, const IntRect*); - virtual bool shouldPauseAnimation(const Image*) override; - virtual void animationAdvanced(const Image*) override; - virtual void changedInRect(const Image*, const IntRect&) override; + void addIncrementalDataBuffer(SharedBuffer&); - void addIncrementalDataBuffer(ResourceBuffer*); + void didReplaceSharedBufferContents() override; - typedef std::pair<IntSize, float> SizeAndZoom; + typedef std::pair<LayoutSize, float> SizeAndZoom; typedef HashMap<const CachedImageClient*, SizeAndZoom> ContainerSizeRequests; ContainerSizeRequests m_pendingContainerSizeRequests; + RefPtr<CachedImageObserver> m_imageObserver; RefPtr<Image> m_image; -#if ENABLE(SVG) std::unique_ptr<SVGImageCache> m_svgImageCache; -#endif - bool m_shouldPaintBrokenImage; -}; - -#if USE(CF) -// FIXME: We should look to incorporate the functionality of CachedImageManual -// into CachedImage or find a better place for this class. -// FIXME: Remove the USE(CF) once we make MemoryCache::addImageToCache() platform-independent. -class CachedImageManual : public CachedImage { -public: - CachedImageManual(const URL&, Image*); - void addFakeClient() { addClient(m_fakeClient.get()); } - void removeFakeClient() { removeClient(m_fakeClient.get()); } - virtual bool isManual() const override { return true; } - virtual bool mustRevalidateDueToCacheHeaders(CachePolicy) const; -private: - std::unique_ptr<CachedResourceClient> m_fakeClient; + bool m_isManuallyCached { false }; + bool m_shouldPaintBrokenImage { true }; }; -#endif -} +} // namespace WebCore -#endif +SPECIALIZE_TYPE_TRAITS_CACHED_RESOURCE(CachedImage, CachedResource::ImageResource) diff --git a/Source/WebCore/loader/cache/CachedImageClient.h b/Source/WebCore/loader/cache/CachedImageClient.h index 1c3fac206..e09621ed8 100644 --- a/Source/WebCore/loader/cache/CachedImageClient.h +++ b/Source/WebCore/loader/cache/CachedImageClient.h @@ -20,8 +20,7 @@ Boston, MA 02110-1301, USA. */ -#ifndef CachedImageClient_h -#define CachedImageClient_h +#pragma once #include "CachedResourceClient.h" @@ -34,19 +33,14 @@ class CachedImageClient : public CachedResourceClient { public: virtual ~CachedImageClient() { } static CachedResourceClientType expectedType() { return ImageType; } - virtual CachedResourceClientType resourceClientType() const override { return expectedType(); } + CachedResourceClientType resourceClientType() const override { return expectedType(); } - // 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 whenever a frame of an image changes because we got more data from the network. + // If not null, the IntRect is the changed rect of the image. + virtual void imageChanged(CachedImage*, const IntRect* = nullptr) { } - // 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; } + // Called when GIF animation progresses. + virtual void newImageAnimationFrameAvailable(CachedImage& image) { imageChanged(&image); } }; -} - -#endif +} // namespace WebCore diff --git a/Source/WebCore/loader/cache/CachedRawResource.cpp b/Source/WebCore/loader/cache/CachedRawResource.cpp index a6eff5efb..05b9de128 100644 --- a/Source/WebCore/loader/cache/CachedRawResource.cpp +++ b/Source/WebCore/loader/cache/CachedRawResource.cpp @@ -29,23 +29,26 @@ #include "CachedRawResourceClient.h" #include "CachedResourceClientWalker.h" #include "CachedResourceLoader.h" -#include "ResourceBuffer.h" +#include "HTTPHeaderNames.h" +#include "SharedBuffer.h" #include "SubresourceLoader.h" -#include <wtf/PassRefPtr.h> +#include <wtf/text/StringView.h> namespace WebCore { -CachedRawResource::CachedRawResource(ResourceRequest& resourceRequest, Type type) - : CachedResource(resourceRequest, type) +CachedRawResource::CachedRawResource(CachedResourceRequest&& request, Type type, SessionID sessionID) + : CachedResource(WTFMove(request), type, sessionID) , m_identifier(0) + , m_allowEncodedDataReplacement(true) { + ASSERT(isMainOrMediaOrRawResource()); } -const char* CachedRawResource::calculateIncrementalDataChunk(ResourceBuffer* data, unsigned& incrementalDataLength) +const char* CachedRawResource::calculateIncrementalDataChunk(SharedBuffer* data, unsigned& incrementalDataLength) { incrementalDataLength = 0; if (!data) - return 0; + return nullptr; unsigned previousDataLength = encodedSize(); ASSERT(data->size() >= previousDataLength); @@ -53,34 +56,37 @@ const char* CachedRawResource::calculateIncrementalDataChunk(ResourceBuffer* dat return data->data() + previousDataLength; } -void CachedRawResource::addDataBuffer(ResourceBuffer* data) +void CachedRawResource::addDataBuffer(SharedBuffer& data) { - CachedResourceHandle<CachedRawResource> protect(this); - ASSERT(m_options.dataBufferingPolicy == BufferData); - m_data = data; + CachedResourceHandle<CachedRawResource> protectedThis(this); + ASSERT(dataBufferingPolicy() == BufferData); + m_data = &data; unsigned incrementalDataLength; - const char* incrementalData = calculateIncrementalDataChunk(data, incrementalDataLength); - if (data) - setEncodedSize(data->size()); + const char* incrementalData = calculateIncrementalDataChunk(&data, incrementalDataLength); + setEncodedSize(data.size()); notifyClientsDataWasReceived(incrementalData, incrementalDataLength); - if (m_options.dataBufferingPolicy == DoNotBufferData) { + if (dataBufferingPolicy() == DoNotBufferData) { if (m_loader) m_loader->setDataBufferingPolicy(DoNotBufferData); clear(); + return; } + + CachedResource::addDataBuffer(data); } void CachedRawResource::addData(const char* data, unsigned length) { - ASSERT(m_options.dataBufferingPolicy == DoNotBufferData); + ASSERT(dataBufferingPolicy() == DoNotBufferData); notifyClientsDataWasReceived(data, length); + CachedResource::addData(data, length); } -void CachedRawResource::finishLoading(ResourceBuffer* data) +void CachedRawResource::finishLoading(SharedBuffer* data) { - CachedResourceHandle<CachedRawResource> protect(this); - DataBufferingPolicy dataBufferingPolicy = m_options.dataBufferingPolicy; + CachedResourceHandle<CachedRawResource> protectedThis(this); + DataBufferingPolicy dataBufferingPolicy = this->dataBufferingPolicy(); if (dataBufferingPolicy == BufferData) { m_data = data; @@ -91,8 +97,12 @@ void CachedRawResource::finishLoading(ResourceBuffer* data) notifyClientsDataWasReceived(incrementalData, incrementalDataLength); } +#if USE(QUICK_LOOK) + m_allowEncodedDataReplacement = !m_loader->isQuickLookResource(); +#endif + CachedResource::finishLoading(data); - if (dataBufferingPolicy == BufferData && m_options.dataBufferingPolicy == DoNotBufferData) { + if (dataBufferingPolicy == BufferData && this->dataBufferingPolicy() == DoNotBufferData) { if (m_loader) m_loader->setDataBufferingPolicy(DoNotBufferData); clear(); @@ -104,37 +114,45 @@ void CachedRawResource::notifyClientsDataWasReceived(const char* data, unsigned if (!length) return; - CachedResourceHandle<CachedRawResource> protect(this); + CachedResourceHandle<CachedRawResource> protectedThis(this); CachedResourceClientWalker<CachedRawResourceClient> w(m_clients); while (CachedRawResourceClient* c = w.next()) - c->dataReceived(this, data, length); + c->dataReceived(*this, data, length); } -void CachedRawResource::didAddClient(CachedResourceClient* c) +void CachedRawResource::didAddClient(CachedResourceClient& c) { if (!hasClient(c)) return; // The calls to the client can result in events running, potentially causing // this resource to be evicted from the cache and all clients to be removed, // so a protector is necessary. - CachedResourceHandle<CachedRawResource> protect(this); - CachedRawResourceClient* client = static_cast<CachedRawResourceClient*>(c); + CachedResourceHandle<CachedRawResource> protectedThis(this); + CachedRawResourceClient& client = static_cast<CachedRawResourceClient&>(c); size_t redirectCount = m_redirectChain.size(); for (size_t i = 0; i < redirectCount; i++) { RedirectPair redirect = m_redirectChain[i]; ResourceRequest request(redirect.m_request); - client->redirectReceived(this, request, redirect.m_redirectResponse); + client.redirectReceived(*this, request, redirect.m_redirectResponse); if (!hasClient(c)) return; } ASSERT(redirectCount == m_redirectChain.size()); - if (!m_response.isNull()) - client->responseReceived(this, m_response); + if (!m_response.isNull()) { + ResourceResponse response(m_response); + if (validationCompleting()) + response.setSource(ResourceResponse::Source::MemoryCacheAfterValidation); + else { + ASSERT(!validationInProgress()); + response.setSource(ResourceResponse::Source::MemoryCache); + } + client.responseReceived(*this, response); + } if (!hasClient(c)) return; if (m_data) - client->dataReceived(this, m_data->data(), m_data->size()); + client.dataReceived(*this, m_data->data(), m_data->size()); if (!hasClient(c)) return; CachedResource::didAddClient(client); @@ -146,34 +164,51 @@ void CachedRawResource::allClientsRemoved() m_loader->cancelIfNotFinishing(); } -void CachedRawResource::willSendRequest(ResourceRequest& request, const ResourceResponse& response) +void CachedRawResource::redirectReceived(ResourceRequest& request, const ResourceResponse& response) { - CachedResourceHandle<CachedRawResource> protect(this); + CachedResourceHandle<CachedRawResource> protectedThis(this); if (!response.isNull()) { CachedResourceClientWalker<CachedRawResourceClient> w(m_clients); while (CachedRawResourceClient* c = w.next()) - c->redirectReceived(this, request, response); + c->redirectReceived(*this, request, response); m_redirectChain.append(RedirectPair(request, response)); } - CachedResource::willSendRequest(request, response); + CachedResource::redirectReceived(request, response); } void CachedRawResource::responseReceived(const ResourceResponse& response) { - CachedResourceHandle<CachedRawResource> protect(this); + CachedResourceHandle<CachedRawResource> protectedThis(this); if (!m_identifier) m_identifier = m_loader->identifier(); CachedResource::responseReceived(response); CachedResourceClientWalker<CachedRawResourceClient> w(m_clients); while (CachedRawResourceClient* c = w.next()) - c->responseReceived(this, m_response); + c->responseReceived(*this, m_response); +} + +bool CachedRawResource::shouldCacheResponse(const ResourceResponse& response) +{ + CachedResourceClientWalker<CachedRawResourceClient> w(m_clients); + while (CachedRawResourceClient* c = w.next()) { + if (!c->shouldCacheResponse(*this, response)) + return false; + } + return true; } void CachedRawResource::didSendData(unsigned long long bytesSent, unsigned long long totalBytesToBeSent) { CachedResourceClientWalker<CachedRawResourceClient> w(m_clients); while (CachedRawResourceClient* c = w.next()) - c->dataSent(this, bytesSent, totalBytesToBeSent); + c->dataSent(*this, bytesSent, totalBytesToBeSent); +} + +void CachedRawResource::finishedTimingForWorkerLoad(ResourceTiming&& resourceTiming) +{ + CachedResourceClientWalker<CachedRawResourceClient> w(m_clients); + while (CachedRawResourceClient* c = w.next()) + c->finishedTimingForWorkerLoad(*this, resourceTiming); } void CachedRawResource::switchClientsToRevalidatedResource() @@ -181,7 +216,7 @@ void CachedRawResource::switchClientsToRevalidatedResource() ASSERT(m_loader); // If we're in the middle of a successful revalidation, responseReceived() hasn't been called, so we haven't set m_identifier. ASSERT(!m_identifier); - static_cast<CachedRawResource*>(resourceToRevalidate())->m_identifier = m_loader->identifier(); + downcast<CachedRawResource>(*resourceToRevalidate()).m_identifier = m_loader->identifier(); CachedResource::switchClientsToRevalidatedResource(); } @@ -196,25 +231,26 @@ void CachedRawResource::setDataBufferingPolicy(DataBufferingPolicy dataBuffering m_options.dataBufferingPolicy = dataBufferingPolicy; } -static bool shouldIgnoreHeaderForCacheReuse(AtomicString headerName) +static bool shouldIgnoreHeaderForCacheReuse(HTTPHeaderName name) { + switch (name) { // FIXME: This list of headers that don't affect cache policy almost certainly isn't complete. - DEFINE_STATIC_LOCAL(HashSet<AtomicString>, m_headers, ()); - if (m_headers.isEmpty()) { - m_headers.add("Accept"); - m_headers.add("Cache-Control"); - m_headers.add("Origin"); - m_headers.add("Pragma"); - m_headers.add("Purpose"); - m_headers.add("Referer"); - m_headers.add("User-Agent"); + case HTTPHeaderName::Accept: + case HTTPHeaderName::CacheControl: + case HTTPHeaderName::Pragma: + case HTTPHeaderName::Purpose: + case HTTPHeaderName::Referer: + case HTTPHeaderName::UserAgent: + return true; + + default: + return false; } - return m_headers.contains(headerName); } bool CachedRawResource::canReuse(const ResourceRequest& newRequest) const { - if (m_options.dataBufferingPolicy == DoNotBufferData) + if (dataBufferingPolicy() == DoNotBufferData) return false; if (m_resourceRequest.httpMethod() != newRequest.httpMethod()) @@ -226,6 +262,9 @@ bool CachedRawResource::canReuse(const ResourceRequest& newRequest) const if (m_resourceRequest.allowCookies() != newRequest.allowCookies()) return false; + if (newRequest.isConditional()) + return false; + // Ensure most headers match the existing headers before continuing. // Note that the list of ignored headers includes some headers explicitly related to caching. // A more detailed check of caching policy will be performed later, this is simply a list of @@ -233,22 +272,23 @@ bool CachedRawResource::canReuse(const ResourceRequest& newRequest) const const HTTPHeaderMap& newHeaders = newRequest.httpHeaderFields(); const HTTPHeaderMap& oldHeaders = m_resourceRequest.httpHeaderFields(); - HTTPHeaderMap::const_iterator end = newHeaders.end(); - for (HTTPHeaderMap::const_iterator i = newHeaders.begin(); i != end; ++i) { - AtomicString headerName = i->key; - if (!shouldIgnoreHeaderForCacheReuse(headerName) && i->value != oldHeaders.get(headerName)) - return false; - } - - end = oldHeaders.end(); - for (HTTPHeaderMap::const_iterator i = oldHeaders.begin(); i != end; ++i) { - AtomicString headerName = i->key; - if (!shouldIgnoreHeaderForCacheReuse(headerName) && i->value != newHeaders.get(headerName)) + for (const auto& header : newHeaders) { + if (header.keyAsHTTPHeaderName) { + if (!shouldIgnoreHeaderForCacheReuse(header.keyAsHTTPHeaderName.value()) + && header.value != oldHeaders.commonHeaders().get(header.keyAsHTTPHeaderName.value())) + return false; + } else if (header.value != oldHeaders.uncommonHeaders().get(header.key)) return false; } - for (size_t i = 0; i < m_redirectChain.size(); i++) { - if (m_redirectChain[i].m_redirectResponse.cacheControlContainsNoStore()) + // For this second loop, we don't actually need to compare values, checking that the + // key is contained in newHeaders is sufficient due to the previous loop. + for (const auto& header : oldHeaders) { + if (header.keyAsHTTPHeaderName) { + if (!shouldIgnoreHeaderForCacheReuse(header.keyAsHTTPHeaderName.value()) + && !newHeaders.commonHeaders().contains(header.keyAsHTTPHeaderName.value())) + return false; + } else if (!newHeaders.uncommonHeaders().contains(header.key)) return false; } @@ -257,7 +297,7 @@ bool CachedRawResource::canReuse(const ResourceRequest& newRequest) const void CachedRawResource::clear() { - m_data.clear(); + m_data = nullptr; setEncodedSize(0); if (m_loader) m_loader->clearResourceData(); diff --git a/Source/WebCore/loader/cache/CachedRawResource.h b/Source/WebCore/loader/cache/CachedRawResource.h index 48e04ffec..7d7468c0e 100644 --- a/Source/WebCore/loader/cache/CachedRawResource.h +++ b/Source/WebCore/loader/cache/CachedRawResource.h @@ -20,19 +20,19 @@ Boston, MA 02110-1301, USA. */ -#ifndef CachedRawResource_h -#define CachedRawResource_h +#pragma once #include "CachedResource.h" namespace WebCore { class CachedResourceClient; +class ResourceTiming; class SubresourceLoader; class CachedRawResource final : public CachedResource { public: - CachedRawResource(ResourceRequest&, Type); + CachedRawResource(CachedResourceRequest&&, Type, SessionID); // FIXME: AssociatedURLLoader shouldn't be a DocumentThreadableLoader and therefore shouldn't // use CachedRawResource. However, it is, and it needs to be able to defer loading. @@ -40,38 +40,44 @@ public: virtual void setDefersLoading(bool); virtual void setDataBufferingPolicy(DataBufferingPolicy); - - // FIXME: This is exposed for the InpsectorInstrumentation for preflights in DocumentThreadableLoader. It's also really lame. + + // FIXME: This is exposed for the InspectorInstrumentation for preflights in DocumentThreadableLoader. It's also really lame. unsigned long identifier() const { return m_identifier; } void clear(); -private: - virtual void didAddClient(CachedResourceClient*) override; - virtual void addDataBuffer(ResourceBuffer*) override; - virtual void addData(const char* data, unsigned length) override; - virtual void finishLoading(ResourceBuffer*) override; + bool canReuse(const ResourceRequest&) const; + + bool wasRedirected() const { return !m_redirectChain.isEmpty(); }; - virtual bool shouldIgnoreHTTPStatusCodeErrors() const override { return true; } - virtual void allClientsRemoved() override; + void finishedTimingForWorkerLoad(ResourceTiming&&); + +private: + void didAddClient(CachedResourceClient&) final; + void addDataBuffer(SharedBuffer&) final; + void addData(const char* data, unsigned length) final; + void finishLoading(SharedBuffer*) final; - virtual void willSendRequest(ResourceRequest&, const ResourceResponse&) override; - virtual void responseReceived(const ResourceResponse&) override; - virtual void didSendData(unsigned long long bytesSent, unsigned long long totalBytesToBeSent) override; + bool shouldIgnoreHTTPStatusCodeErrors() const override { return true; } + void allClientsRemoved() override; - virtual void switchClientsToRevalidatedResource() override; - virtual bool mayTryReplaceEncodedData() const override { return true; } + void redirectReceived(ResourceRequest&, const ResourceResponse&) override; + void responseReceived(const ResourceResponse&) override; + bool shouldCacheResponse(const ResourceResponse&) override; + void didSendData(unsigned long long bytesSent, unsigned long long totalBytesToBeSent) override; - virtual bool canReuse(const ResourceRequest&) const override; + void switchClientsToRevalidatedResource() override; + bool mayTryReplaceEncodedData() const override { return m_allowEncodedDataReplacement; } - const char* calculateIncrementalDataChunk(ResourceBuffer*, unsigned& incrementalDataLength); + const char* calculateIncrementalDataChunk(SharedBuffer*, unsigned& incrementalDataLength); void notifyClientsDataWasReceived(const char* data, unsigned length); #if USE(SOUP) - virtual char* getOrCreateReadBuffer(size_t requestedSize, size_t& actualSize); + char* getOrCreateReadBuffer(size_t requestedSize, size_t& actualSize) override; #endif unsigned long m_identifier; + bool m_allowEncodedDataReplacement; struct RedirectPair { public: @@ -88,6 +94,8 @@ private: Vector<RedirectPair> m_redirectChain; }; -} +} // namespace WebCore -#endif // CachedRawResource_h +SPECIALIZE_TYPE_TRAITS_BEGIN(WebCore::CachedRawResource) + static bool isType(const WebCore::CachedResource& resource) { return resource.isMainOrMediaOrRawResource(); } +SPECIALIZE_TYPE_TRAITS_END() diff --git a/Source/WebCore/loader/cache/CachedRawResourceClient.h b/Source/WebCore/loader/cache/CachedRawResourceClient.h index 3d7fd5e5c..2e9fac591 100644 --- a/Source/WebCore/loader/cache/CachedRawResourceClient.h +++ b/Source/WebCore/loader/cache/CachedRawResourceClient.h @@ -20,8 +20,7 @@ Boston, MA 02110-1301, USA. */ -#ifndef CachedRawResourceClient_h -#define CachedRawResourceClient_h +#pragma once #include "CachedResourceClient.h" @@ -30,22 +29,23 @@ namespace WebCore { class CachedResource; class ResourceRequest; class ResourceResponse; +class ResourceTiming; class CachedRawResourceClient : public CachedResourceClient { public: virtual ~CachedRawResourceClient() { } static CachedResourceClientType expectedType() { return RawResourceType; } - virtual CachedResourceClientType resourceClientType() const override { return expectedType(); } - - virtual void dataSent(CachedResource*, unsigned long long /* bytesSent */, unsigned long long /* totalBytesToBeSent */) { } - virtual void responseReceived(CachedResource*, const ResourceResponse&) { } - virtual void dataReceived(CachedResource*, const char* /* data */, int /* length */) { } - virtual void redirectReceived(CachedResource*, ResourceRequest&, const ResourceResponse&) { } + CachedResourceClientType resourceClientType() const override { return expectedType(); } + + virtual void dataSent(CachedResource&, unsigned long long /* bytesSent */, unsigned long long /* totalBytesToBeSent */) { } + virtual void responseReceived(CachedResource&, const ResourceResponse&) { } + virtual bool shouldCacheResponse(CachedResource&, const ResourceResponse&) { return true; } + virtual void dataReceived(CachedResource&, const char* /* data */, int /* length */) { } + virtual void redirectReceived(CachedResource&, ResourceRequest&, const ResourceResponse&) { } + virtual void finishedTimingForWorkerLoad(CachedResource&, const ResourceTiming&) { } #if USE(SOUP) - virtual char* getOrCreateReadBuffer(CachedResource*, size_t /* requestedSize */, size_t& /* actualSize */) { return 0; } + virtual char* getOrCreateReadBuffer(CachedResource&, size_t /* requestedSize */, size_t& /* actualSize */) { return nullptr; } #endif }; } - -#endif // CachedRawResourceClient_h diff --git a/Source/WebCore/loader/cache/CachedResource.cpp b/Source/WebCore/loader/cache/CachedResource.cpp index 5f0bc76fa..8b09d3285 100644 --- a/Source/WebCore/loader/cache/CachedResource.cpp +++ b/Source/WebCore/loader/cache/CachedResource.cpp @@ -3,7 +3,7 @@ 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, 2009, 2010, 2011 Apple Inc. All rights reserved. + Copyright (C) 2004-2011, 2014 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 @@ -29,23 +29,23 @@ #include "CachedResourceHandle.h" #include "CachedResourceLoader.h" #include "CrossOriginAccessControl.h" +#include "DiagnosticLoggingClient.h" +#include "DiagnosticLoggingKeys.h" #include "Document.h" #include "DocumentLoader.h" #include "FrameLoader.h" #include "FrameLoaderClient.h" +#include "HTTPHeaderNames.h" #include "InspectorInstrumentation.h" #include "URL.h" #include "LoaderStrategy.h" #include "Logging.h" +#include "MainFrame.h" #include "MemoryCache.h" #include "PlatformStrategies.h" -#include "PurgeableBuffer.h" -#include "ResourceBuffer.h" #include "ResourceHandle.h" -#include "ResourceLoadScheduler.h" #include "SchemeRegistry.h" #include "SecurityOrigin.h" -#include "SecurityPolicy.h" #include "SubresourceLoader.h" #include <wtf/CurrentTime.h> #include <wtf/MathExtras.h> @@ -60,139 +60,99 @@ using namespace WTF; -namespace WebCore { +#define RELEASE_LOG_IF_ALLOWED(fmt, ...) RELEASE_LOG_IF(cachedResourceLoader.isAlwaysOnLoggingAllowed(), Network, "%p - CachedResource::" fmt, this, ##__VA_ARGS__) -// These response headers are not copied from a revalidated response to the -// cached response headers. For compatibility, this list is based on Chromium's -// net/http/http_response_headers.cc. -const char* const headersToIgnoreAfterRevalidation[] = { - "allow", - "connection", - "etag", - "expires", - "keep-alive", - "last-modified" - "proxy-authenticate", - "proxy-connection", - "trailer", - "transfer-encoding", - "upgrade", - "www-authenticate", - "x-frame-options", - "x-xss-protection", -}; - -// Some header prefixes mean "Don't copy this header from a 304 response.". -// Rather than listing all the relevant headers, we can consolidate them into -// this list, also grabbed from Chromium's net/http/http_response_headers.cc. -const char* const headerPrefixesToIgnoreAfterRevalidation[] = { - "content-", - "x-content-", - "x-webkit-" -}; - -static inline bool shouldUpdateHeaderAfterRevalidation(const AtomicString& header) -{ - for (size_t i = 0; i < WTF_ARRAY_LENGTH(headersToIgnoreAfterRevalidation); i++) { - if (equalIgnoringCase(header, headersToIgnoreAfterRevalidation[i])) - return false; - } - for (size_t i = 0; i < WTF_ARRAY_LENGTH(headerPrefixesToIgnoreAfterRevalidation); i++) { - if (header.startsWith(headerPrefixesToIgnoreAfterRevalidation[i], false)) - return false; - } - return true; -} +namespace WebCore { -static ResourceLoadPriority defaultPriorityForResourceType(CachedResource::Type type) +ResourceLoadPriority CachedResource::defaultPriorityForResourceType(Type type) { switch (type) { case CachedResource::MainResource: - return ResourceLoadPriorityVeryHigh; + return ResourceLoadPriority::VeryHigh; case CachedResource::CSSStyleSheet: - return ResourceLoadPriorityHigh; case CachedResource::Script: + return ResourceLoadPriority::High; +#if ENABLE(SVG_FONTS) + case CachedResource::SVGFontResource: +#endif + case CachedResource::MediaResource: case CachedResource::FontResource: case CachedResource::RawResource: - return ResourceLoadPriorityMedium; + return ResourceLoadPriority::Medium; case CachedResource::ImageResource: - return ResourceLoadPriorityLow; + return ResourceLoadPriority::Low; #if ENABLE(XSLT) case CachedResource::XSLStyleSheet: - return ResourceLoadPriorityHigh; + return ResourceLoadPriority::High; #endif -#if ENABLE(SVG) case CachedResource::SVGDocumentResource: - return ResourceLoadPriorityLow; -#endif + return ResourceLoadPriority::Low; #if ENABLE(LINK_PREFETCH) case CachedResource::LinkPrefetch: - return ResourceLoadPriorityVeryLow; + return ResourceLoadPriority::VeryLow; case CachedResource::LinkSubresource: - return ResourceLoadPriorityVeryLow; + return ResourceLoadPriority::VeryLow; #endif #if ENABLE(VIDEO_TRACK) case CachedResource::TextTrackResource: - return ResourceLoadPriorityLow; + return ResourceLoadPriority::Low; #endif } ASSERT_NOT_REACHED(); - return ResourceLoadPriorityLow; + return ResourceLoadPriority::Low; } -static double deadDecodedDataDeletionIntervalForResourceType(CachedResource::Type type) +static std::chrono::milliseconds deadDecodedDataDeletionIntervalForResourceType(CachedResource::Type type) { if (type == CachedResource::Script) - return 0; - return memoryCache()->deadDecodedDataDeletionInterval(); + return std::chrono::milliseconds { 0 }; + + return MemoryCache::singleton().deadDecodedDataDeletionInterval(); } DEFINE_DEBUG_ONLY_GLOBAL(RefCountedLeakCounter, cachedResourceLeakCounter, ("CachedResource")); -CachedResource::CachedResource(const ResourceRequest& request, Type type) - : m_resourceRequest(request) +CachedResource::CachedResource(CachedResourceRequest&& request, Type type, SessionID sessionID) + : m_resourceRequest(request.releaseResourceRequest()) + , m_options(request.options()) + , m_decodedDataDeletionTimer(*this, &CachedResource::destroyDecodedData, deadDecodedDataDeletionIntervalForResourceType(type)) + , m_sessionID(sessionID) , m_loadPriority(defaultPriorityForResourceType(type)) - , m_responseTimestamp(currentTime()) - , m_decodedDataDeletionTimer(this, &CachedResource::decodedDataDeletionTimerFired, deadDecodedDataDeletionIntervalForResourceType(type)) - , m_lastDecodedAccessTime(0) - , m_loadFinishTime(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_inCache(false) - , m_loading(false) - , m_switchingClientsToRevalidatedResource(false) + , m_responseTimestamp(std::chrono::system_clock::now()) + , m_fragmentIdentifierForRequest(request.releaseFragmentIdentifier()) + , m_origin(request.releaseOrigin()) + , m_initiatorName(request.initiatorName()) + , m_isLinkPreload(request.isLinkPreload()) , m_type(type) - , m_status(Pending) +{ + ASSERT(sessionID.isValid()); + + setLoadPriority(request.priority()); #ifndef NDEBUG - , m_deleted(false) - , m_lruIndex(0) + cachedResourceLeakCounter.increment(); #endif - , m_nextInAllResourcesList(0) - , m_prevInAllResourcesList(0) - , m_nextInLiveResourcesList(0) - , m_prevInLiveResourcesList(0) - , m_owningCachedResourceLoader(0) - , m_resourceToRevalidate(0) - , m_proxyResource(0) -{ - ASSERT(m_type == unsigned(type)); // m_type is a bitfield, so this tests careless updates of the enum. + + // FIXME: We should have a better way of checking for Navigation loads, maybe FetchMode::Options::Navigate. + ASSERT(m_origin || m_type == CachedResource::MainResource); + + if (isRequestCrossOrigin(m_origin.get(), m_resourceRequest.url(), m_options)) + setCrossOrigin(); +} + +// FIXME: For this constructor, we should probably mandate that the URL has no fragment identifier. +CachedResource::CachedResource(const URL& url, Type type, SessionID sessionID) + : m_resourceRequest(url) + , m_decodedDataDeletionTimer(*this, &CachedResource::destroyDecodedData, deadDecodedDataDeletionIntervalForResourceType(type)) + , m_sessionID(sessionID) + , m_responseTimestamp(std::chrono::system_clock::now()) + , m_fragmentIdentifierForRequest(CachedResourceRequest::splitFragmentIdentifierFromRequestURL(m_resourceRequest)) + , m_type(type) + , m_status(Cached) +{ + ASSERT(sessionID.isValid()); #ifndef NDEBUG cachedResourceLeakCounter.increment(); #endif - - if (!m_resourceRequest.url().hasFragmentIdentifier()) - return; - URL urlForCache = MemoryCache::removeFragmentIdentifierIfNeeded(m_resourceRequest.url()); - if (urlForCache.hasFragmentIdentifier()) - return; - m_fragmentIdentifierForRequest = m_resourceRequest.url().fragmentIdentifier(); - m_resourceRequest.setURL(urlForCache); } CachedResource::~CachedResource() @@ -201,7 +161,7 @@ CachedResource::~CachedResource() ASSERT(canDelete()); ASSERT(!inCache()); ASSERT(!m_deleted); - ASSERT(url().isNull() || memoryCache()->resourceForRequest(resourceRequest()) != this); + ASSERT(url().isNull() || !allowsCaching() || MemoryCache::singleton().resourceForRequest(resourceRequest(), sessionID()) != this); #ifndef NDEBUG m_deleted = true; @@ -209,99 +169,81 @@ CachedResource::~CachedResource() #endif if (m_owningCachedResourceLoader) - m_owningCachedResourceLoader->removeCachedResource(this); + m_owningCachedResourceLoader->removeCachedResource(*this); } void CachedResource::failBeforeStarting() { // FIXME: What if resources in other frames were waiting for this revalidation? LOG(ResourceLoading, "Cannot start loading '%s'", url().string().latin1().data()); - if (m_resourceToRevalidate) - memoryCache()->revalidationFailed(this); + if (allowsCaching() && m_resourceToRevalidate) + MemoryCache::singleton().revalidationFailed(*this); error(CachedResource::LoadError); } -void CachedResource::addAdditionalRequestHeaders(CachedResourceLoader* cachedResourceLoader) +void CachedResource::load(CachedResourceLoader& cachedResourceLoader) { - // Note: We skip the Content-Security-Policy check here because we check - // the Content-Security-Policy at the CachedResourceLoader layer so we can - // handle different resource types differently. - - FrameLoader& frameLoader = cachedResourceLoader->frame()->loader(); - String outgoingReferrer; - String outgoingOrigin; - if (m_resourceRequest.httpReferrer().isNull()) { - outgoingReferrer = frameLoader.outgoingReferrer(); - outgoingOrigin = frameLoader.outgoingOrigin(); - } else { - outgoingReferrer = m_resourceRequest.httpReferrer(); - outgoingOrigin = SecurityOrigin::createFromString(outgoingReferrer)->toString(); - } - - outgoingReferrer = SecurityPolicy::generateReferrerHeader(cachedResourceLoader->document()->referrerPolicy(), m_resourceRequest.url(), outgoingReferrer); - if (outgoingReferrer.isEmpty()) - m_resourceRequest.clearHTTPReferrer(); - else if (!m_resourceRequest.httpReferrer()) - m_resourceRequest.setHTTPReferrer(outgoingReferrer); - FrameLoader::addHTTPOriginIfNeeded(m_resourceRequest, outgoingOrigin); - - frameLoader.addExtraFieldsToSubresourceRequest(m_resourceRequest); -} - -void CachedResource::load(CachedResourceLoader* cachedResourceLoader, const ResourceLoaderOptions& options) -{ - if (!cachedResourceLoader->frame()) { + if (!cachedResourceLoader.frame()) { + RELEASE_LOG_IF_ALLOWED("load: No associated frame"); failBeforeStarting(); return; } + Frame& frame = *cachedResourceLoader.frame(); + + // Prevent new loads if we are in the PageCache or being added to the PageCache. + // We query the top document because new frames may be created in pagehide event handlers + // and their pageCacheState will not reflect the fact that they are about to enter page + // cache. + if (auto* topDocument = frame.mainFrame().document()) { + if (topDocument->pageCacheState() != Document::NotInPageCache) { + RELEASE_LOG_IF_ALLOWED("load: Already in page cache or being added to it (frame = %p)", &frame); + failBeforeStarting(); + return; + } + } - FrameLoader& frameLoader = cachedResourceLoader->frame()->loader(); - if (options.securityCheck == DoSecurityCheck && (frameLoader.state() == FrameStateProvisional || !frameLoader.activeDocumentLoader() || frameLoader.activeDocumentLoader()->isStopping())) { + FrameLoader& frameLoader = frame.loader(); + if (m_options.securityCheck == DoSecurityCheck && (frameLoader.state() == FrameStateProvisional || !frameLoader.activeDocumentLoader() || frameLoader.activeDocumentLoader()->isStopping())) { + if (frameLoader.state() == FrameStateProvisional) + RELEASE_LOG_IF_ALLOWED("load: Failed security check -- state is provisional (frame = %p)", &frame); + else if (!frameLoader.activeDocumentLoader()) + RELEASE_LOG_IF_ALLOWED("load: Failed security check -- not active document (frame = %p)", &frame); + else if (frameLoader.activeDocumentLoader()->isStopping()) + RELEASE_LOG_IF_ALLOWED("load: Failed security check -- active loader is stopping (frame = %p)", &frame); failBeforeStarting(); return; } - m_options = options; m_loading = true; -#if USE(QUICK_LOOK) - if (!m_resourceRequest.isNull() && m_resourceRequest.url().protocolIs(QLPreviewProtocol())) { - // When QuickLook is invoked to convert a document, it returns a unique URL in the - // NSURLReponse for the main document. To make safeQLURLForDocumentURLAndResourceURL() - // work, we need to use the QL URL not the original URL. - const URL& documentURL = cachedResourceLoader->frame() ? cachedResourceLoader->frame()->loader().documentLoader()->response().url() : cachedResourceLoader->document()->url(); - m_resourceRequest.setURL(safeQLURLForDocumentURLAndResourceURL(documentURL, url())); - } -#endif - - if (!accept().isEmpty()) - m_resourceRequest.setHTTPAccept(accept()); - if (isCacheValidator()) { CachedResource* resourceToRevalidate = m_resourceToRevalidate; ASSERT(resourceToRevalidate->canUseCacheValidator()); ASSERT(resourceToRevalidate->isLoaded()); - const String& lastModified = resourceToRevalidate->response().httpHeaderField("Last-Modified"); - const String& eTag = resourceToRevalidate->response().httpHeaderField("ETag"); + const String& lastModified = resourceToRevalidate->response().httpHeaderField(HTTPHeaderName::LastModified); + const String& eTag = resourceToRevalidate->response().httpHeaderField(HTTPHeaderName::ETag); if (!lastModified.isEmpty() || !eTag.isEmpty()) { - ASSERT(cachedResourceLoader->cachePolicy(type()) != CachePolicyReload); - if (cachedResourceLoader->cachePolicy(type()) == CachePolicyRevalidate) - m_resourceRequest.setHTTPHeaderField("Cache-Control", "max-age=0"); + ASSERT(cachedResourceLoader.cachePolicy(type()) != CachePolicyReload); + if (cachedResourceLoader.cachePolicy(type()) == CachePolicyRevalidate) + m_resourceRequest.setHTTPHeaderField(HTTPHeaderName::CacheControl, "max-age=0"); if (!lastModified.isEmpty()) - m_resourceRequest.setHTTPHeaderField("If-Modified-Since", lastModified); + m_resourceRequest.setHTTPHeaderField(HTTPHeaderName::IfModifiedSince, lastModified); if (!eTag.isEmpty()) - m_resourceRequest.setHTTPHeaderField("If-None-Match", eTag); + m_resourceRequest.setHTTPHeaderField(HTTPHeaderName::IfNoneMatch, eTag); } } #if ENABLE(LINK_PREFETCH) if (type() == CachedResource::LinkPrefetch || type() == CachedResource::LinkSubresource) - m_resourceRequest.setHTTPHeaderField("Purpose", "prefetch"); + m_resourceRequest.setHTTPHeaderField(HTTPHeaderName::Purpose, "prefetch"); #endif m_resourceRequest.setPriority(loadPriority()); - if (type() != MainResource) - addAdditionalRequestHeaders(cachedResourceLoader); + // Navigation algorithm is setting up the request before sending it to CachedResourceLoader?CachedResource. + // So no need for extra fields for MainResource. + if (type() != CachedResource::MainResource) + frameLoader.addExtraFieldsToSubresourceRequest(m_resourceRequest); + // FIXME: It's unfortunate that the cache layer and below get to know anything about fragment identifiers. // We should look into removing the expectation of that knowledge from the platform network stacks. @@ -313,8 +255,9 @@ void CachedResource::load(CachedResourceLoader* cachedResourceLoader, const Reso m_fragmentIdentifierForRequest = String(); } - m_loader = platformStrategies()->loaderStrategy()->resourceLoadScheduler()->scheduleSubresourceLoad(cachedResourceLoader->frame(), this, request, request.priority(), options); + m_loader = platformStrategies()->loaderStrategy()->loadResource(frame, *this, request, m_options); if (!m_loader) { + RELEASE_LOG_IF_ALLOWED("load: Unable to create SubresourceLoader (frame = %p)", &frame); failBeforeStarting(); return; } @@ -322,27 +265,55 @@ void CachedResource::load(CachedResourceLoader* cachedResourceLoader, const Reso m_status = Pending; } +void CachedResource::loadFrom(const CachedResource& resource) +{ + ASSERT(url() == resource.url()); + ASSERT(type() == resource.type()); + ASSERT(resource.status() == Status::Cached); + + if (isCrossOrigin() && m_options.mode == FetchOptions::Mode::Cors) { + ASSERT(m_origin); + String errorMessage; + if (!WebCore::passesAccessControlCheck(resource.response(), m_options.allowCredentials, *m_origin, errorMessage)) { + setResourceError(ResourceError(String(), 0, url(), errorMessage, ResourceError::Type::AccessControl)); + return; + } + } + + setBodyDataFrom(resource); + setStatus(Status::Cached); + setLoading(false); +} + +void CachedResource::setBodyDataFrom(const CachedResource& resource) +{ + m_data = resource.m_data; + m_response = resource.m_response; + setDecodedSize(resource.decodedSize()); + setEncodedSize(resource.encodedSize()); +} + void CachedResource::checkNotify() { - if (isLoading()) + if (isLoading() || stillNeedsLoad()) return; - CachedResourceClientWalker<CachedResourceClient> w(m_clients); - while (CachedResourceClient* c = w.next()) - c->notifyFinished(this); + CachedResourceClientWalker<CachedResourceClient> walker(m_clients); + while (CachedResourceClient* client = walker.next()) + client->notifyFinished(*this); } -void CachedResource::addDataBuffer(ResourceBuffer*) +void CachedResource::addDataBuffer(SharedBuffer&) { - ASSERT(m_options.dataBufferingPolicy == BufferData); + ASSERT(dataBufferingPolicy() == BufferData); } void CachedResource::addData(const char*, unsigned) { - ASSERT(m_options.dataBufferingPolicy == DoNotBufferData); + ASSERT(dataBufferingPolicy() == DoNotBufferData); } -void CachedResource::finishLoading(ResourceBuffer*) +void CachedResource::finishLoading(SharedBuffer*) { setLoading(false); checkNotify(); @@ -352,7 +323,7 @@ void CachedResource::error(CachedResource::Status status) { setStatus(status); ASSERT(errorOccurred()); - m_data.clear(); + m_data = nullptr; setLoading(false); checkNotify(); @@ -360,7 +331,7 @@ void CachedResource::error(CachedResource::Status status) void CachedResource::cancelLoad() { - if (!isLoading()) + if (!isLoading() && !stillNeedsLoad()) return; setStatus(LoadError); @@ -374,10 +345,30 @@ void CachedResource::finish() m_status = Cached; } -bool CachedResource::passesAccessControlCheck(SecurityOrigin* securityOrigin) +void CachedResource::setCrossOrigin() { - String errorDescription; - return WebCore::passesAccessControlCheck(m_response, resourceRequest().allowCookies() ? AllowStoredCredentials : DoNotAllowStoredCredentials, securityOrigin, errorDescription); + ASSERT(m_options.mode != FetchOptions::Mode::SameOrigin); + m_responseTainting = (m_options.mode == FetchOptions::Mode::Cors) ? ResourceResponse::Tainting::Cors : ResourceResponse::Tainting::Opaque; +} + +bool CachedResource::isCrossOrigin() const +{ + return m_responseTainting != ResourceResponse::Tainting::Basic; +} + +bool CachedResource::isCORSSameOrigin() const +{ + // Following resource types do not use CORS + ASSERT(type() != CachedResource::Type::FontResource); +#if ENABLE(SVG_FONTS) + ASSERT(type() != CachedResource::Type::SVGFontResource); +#endif +#if ENABLE(XSLT) + ASSERT(type() != CachedResource::XSLStyleSheet); +#endif + + // https://html.spec.whatwg.org/multipage/infrastructure.html#cors-same-origin + return !loadFailedOrCanceled() && m_responseTainting != ResourceResponse::Tainting::Opaque; } bool CachedResource::isExpired() const @@ -385,53 +376,62 @@ bool CachedResource::isExpired() const if (m_response.isNull()) return false; - return currentAge() > freshnessLifetime(); + return computeCurrentAge(m_response, m_responseTimestamp) > freshnessLifetime(m_response); } -double CachedResource::currentAge() const +static inline bool shouldCacheSchemeIndefinitely(StringView scheme) { - // RFC2616 13.2.3 - // No compensation for latency as that is not terribly important in practice - double dateValue = m_response.date(); - double apparentAge = std::isfinite(dateValue) ? std::max(0., m_responseTimestamp - dateValue) : 0; - double ageValue = m_response.age(); - double correctedReceivedAge = std::isfinite(ageValue) ? std::max(apparentAge, ageValue) : apparentAge; - double residentTime = currentTime() - m_responseTimestamp; - return correctedReceivedAge + residentTime; +#if PLATFORM(COCOA) + if (equalLettersIgnoringASCIICase(scheme, "applewebdata")) + return true; +#endif +#if USE(SOUP) + if (equalLettersIgnoringASCIICase(scheme, "resource")) + return true; +#endif + return equalLettersIgnoringASCIICase(scheme, "data"); } -double CachedResource::freshnessLifetime() const +std::chrono::microseconds CachedResource::freshnessLifetime(const ResourceResponse& response) const { - if (!m_response.url().protocolIsInHTTPFamily()) { - // Don't cache non-HTTP main resources since we can't check for freshness. - // FIXME: We should not cache subresources either, but when we tried this - // it caused performance and flakiness issues in our test infrastructure. - if (m_type == MainResource && !SchemeRegistry::shouldCacheResponsesFromURLSchemeIndefinitely(m_response.url().protocol())) - return 0; + if (!response.url().protocolIsInHTTPFamily()) { + StringView protocol = response.url().protocol(); + if (!shouldCacheSchemeIndefinitely(protocol)) { + // Don't cache non-HTTP main resources since we can't check for freshness. + // FIXME: We should not cache subresources either, but when we tried this + // it caused performance and flakiness issues in our test infrastructure. + if (m_type == MainResource || SchemeRegistry::shouldAlwaysRevalidateURLScheme(protocol.toStringWithoutCopying())) + return 0us; + } - return std::numeric_limits<double>::max(); + return std::chrono::microseconds::max(); } - // RFC2616 13.2.4 - double maxAgeValue = m_response.cacheControlMaxAge(); - if (std::isfinite(maxAgeValue)) - return maxAgeValue; - double expiresValue = m_response.expires(); - double dateValue = m_response.date(); - double creationTime = std::isfinite(dateValue) ? dateValue : m_responseTimestamp; - if (std::isfinite(expiresValue)) - return expiresValue - creationTime; - double lastModifiedValue = m_response.lastModified(); - if (std::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; + return computeFreshnessLifetimeForHTTPFamily(response, m_responseTimestamp); +} + +void CachedResource::redirectReceived(ResourceRequest&, const ResourceResponse& response) +{ + m_requestedFromNetworkingLayer = true; + if (response.isNull()) + return; + + updateRedirectChainStatus(m_redirectChainCacheStatus, response); +} + +void CachedResource::setResponse(const ResourceResponse& response) +{ + ASSERT(m_response.type() == ResourceResponse::Type::Default); + m_response = response; + m_response.setRedirected(m_redirectChainCacheStatus.status != RedirectChainCacheStatus::NoRedirection); + + m_varyingHeaderValues = collectVaryingRequestHeaders(m_resourceRequest, m_response, m_sessionID); } void CachedResource::responseReceived(const ResourceResponse& response) { setResponse(response); - m_responseTimestamp = currentTime(); + m_responseTimestamp = std::chrono::system_clock::now(); String encoding = response.textEncodingName(); if (!encoding.isNull()) setEncoding(encoding); @@ -440,32 +440,30 @@ void CachedResource::responseReceived(const ResourceResponse& response) void CachedResource::clearLoader() { ASSERT(m_loader); - m_loader = 0; + m_identifierForLoadWithoutResourceLoader = m_loader->identifier(); + m_loader = nullptr; + deleteIfPossible(); } -void CachedResource::addClient(CachedResourceClient* client) +void CachedResource::addClient(CachedResourceClient& client) { if (addClientToSet(client)) didAddClient(client); } -void CachedResource::didAddClient(CachedResourceClient* c) +void CachedResource::didAddClient(CachedResourceClient& client) { if (m_decodedDataDeletionTimer.isActive()) m_decodedDataDeletionTimer.stop(); - if (m_clientsAwaitingCallback.contains(c)) { - m_clients.add(c); - m_clientsAwaitingCallback.remove(c); - } + if (m_clientsAwaitingCallback.remove(&client)) + m_clients.add(&client); if (!isLoading() && !stillNeedsLoad()) - c->notifyFinished(this); + client.notifyFinished(*this); } -bool CachedResource::addClientToSet(CachedResourceClient* client) +bool CachedResource::addClientToSet(CachedResourceClient& client) { - ASSERT(!isPurgeable()); - if (m_preloadResult == PreloadNotReferenced) { if (isLoaded()) m_preloadResult = PreloadReferencedWhileComplete; @@ -474,78 +472,90 @@ bool CachedResource::addClientToSet(CachedResourceClient* client) else m_preloadResult = PreloadReferenced; } - if (!hasClients() && inCache()) - memoryCache()->addToLiveResourcesSize(this); + if (allowsCaching() && !hasClients() && inCache()) + MemoryCache::singleton().addToLiveResourcesSize(*this); if ((m_type == RawResource || m_type == MainResource) && !m_response.isNull() && !m_proxyResource) { // Certain resources (especially XHRs and main resources) do crazy things if an asynchronous load returns // synchronously (e.g., scripts may not have set all the state they need to handle the load). // Therefore, rather than immediately sending callbacks on a cache hit like other CachedResources, // we schedule the callbacks and ensure we never finish synchronously. - ASSERT(!m_clientsAwaitingCallback.contains(client)); - m_clientsAwaitingCallback.add(client, CachedResourceCallback::schedule(this, client)); + ASSERT(!m_clientsAwaitingCallback.contains(&client)); + m_clientsAwaitingCallback.add(&client, std::make_unique<Callback>(*this, client)); return false; } - m_clients.add(client); + m_clients.add(&client); return true; } -void CachedResource::removeClient(CachedResourceClient* client) +void CachedResource::removeClient(CachedResourceClient& client) { - OwnPtr<CachedResourceCallback> callback = m_clientsAwaitingCallback.take(client); + auto callback = m_clientsAwaitingCallback.take(&client); if (callback) { - ASSERT(!m_clients.contains(client)); + ASSERT(!m_clients.contains(&client)); callback->cancel(); - callback.clear(); + callback = nullptr; } else { - ASSERT(m_clients.contains(client)); - m_clients.remove(client); + ASSERT(m_clients.contains(&client)); + m_clients.remove(&client); didRemoveClient(client); } - bool deleted = deleteIfPossible(); - if (!deleted && !hasClients()) { - if (inCache()) { - memoryCache()->removeFromLiveResourcesSize(this); - memoryCache()->removeFromLiveDecodedResourcesList(this); - } - if (!m_switchingClientsToRevalidatedResource) - allClientsRemoved(); - destroyDecodedDataIfNeeded(); - 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 (url().protocolIs("https")) - memoryCache()->remove(this); - } else - memoryCache()->prune(); + if (deleteIfPossible()) { + // `this` object is dead here. + return; } - // This object may be dead here. + + if (hasClients()) + return; + + auto& memoryCache = MemoryCache::singleton(); + if (allowsCaching() && inCache()) { + memoryCache.removeFromLiveResourcesSize(*this); + memoryCache.removeFromLiveDecodedResourcesList(*this); + } + if (!m_switchingClientsToRevalidatedResource) + allClientsRemoved(); + destroyDecodedDataIfNeeded(); + + if (!allowsCaching()) + return; + + if (response().cacheControlContainsNoStore() && url().protocolIs("https")) { + // 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. + memoryCache.remove(*this); + } + memoryCache.pruneSoon(); } void CachedResource::destroyDecodedDataIfNeeded() { if (!m_decodedSize) return; - if (!memoryCache()->deadDecodedDataDeletionInterval()) + if (!MemoryCache::singleton().deadDecodedDataDeletionInterval().count()) return; m_decodedDataDeletionTimer.restart(); } -void CachedResource::decodedDataDeletionTimerFired(DeferrableOneShotTimer<CachedResource>&) +void CachedResource::decodedDataDeletionTimerFired() { destroyDecodedData(); } bool CachedResource::deleteIfPossible() { - if (canDelete() && !inCache()) { - InspectorInstrumentation::willDestroyCachedResource(this); - delete this; - return true; + if (canDelete()) { + if (!inCache()) { + InspectorInstrumentation::willDestroyCachedResource(*this); + delete this; + return true; + } + if (m_data) + m_data->hintMemoryNotNeededSoon(); } return false; } @@ -555,19 +565,19 @@ void CachedResource::setDecodedSize(unsigned size) if (size == m_decodedSize) return; - int delta = size - m_decodedSize; + long long delta = static_cast<long long>(size) - m_decodedSize; + + // The object must be moved to a different queue, since its size has been changed. + // Remove before updating m_decodedSize, so we find the resource in the correct LRU list. + if (allowsCaching() && inCache()) + MemoryCache::singleton().removeFromLRUList(*this); - // 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()) - memoryCache()->removeFromLRUList(this); - m_decodedSize = size; - if (inCache()) { + if (allowsCaching() && inCache()) { + auto& memoryCache = MemoryCache::singleton(); // Now insert into the new LRU list. - memoryCache()->insertInLRUList(this); + memoryCache.insertInLRUList(*this); // Insert into or remove from the live decoded list if necessary. // When inserting into the LiveDecodedResourcesList it is possible @@ -576,13 +586,14 @@ void CachedResource::setDecodedSize(unsigned size) // 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()) - memoryCache()->insertInLiveDecodedResourcesList(this); - else if (!m_decodedSize && m_inLiveDecodedResourcesList) - memoryCache()->removeFromLiveDecodedResourcesList(this); + bool inLiveDecodedResourcesList = memoryCache.inLiveDecodedResourcesList(*this); + if (m_decodedSize && !inLiveDecodedResourcesList && hasClients()) + memoryCache.insertInLiveDecodedResourcesList(*this); + else if (!m_decodedSize && inLiveDecodedResourcesList) + memoryCache.removeFromLiveDecodedResourcesList(*this); // Update the cache's size totals. - memoryCache()->adjustSize(hasClients(), delta); + memoryCache.adjustSize(hasClients(), delta); } } @@ -591,25 +602,19 @@ 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; + long long delta = static_cast<long long>(size) - m_encodedSize; + + // The object must be moved to a different queue, since its size has been changed. + // Remove before updating m_encodedSize, so we find the resource in the correct LRU list. + if (allowsCaching() && inCache()) + MemoryCache::singleton().removeFromLRUList(*this); - // 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()) - memoryCache()->removeFromLRUList(this); - m_encodedSize = size; - - if (inCache()) { - // Now insert into the new LRU list. - memoryCache()->insertInLRUList(this); - - // Update the cache's size totals. - memoryCache()->adjustSize(hasClients(), delta); + + if (allowsCaching() && inCache()) { + auto& memoryCache = MemoryCache::singleton(); + memoryCache.insertInLRUList(*this); + memoryCache.adjustSize(hasClients(), delta); } } @@ -617,12 +622,13 @@ void CachedResource::didAccessDecodedData(double timeStamp) { m_lastDecodedAccessTime = timeStamp; - if (inCache()) { - if (m_inLiveDecodedResourcesList) { - memoryCache()->removeFromLiveDecodedResourcesList(this); - memoryCache()->insertInLiveDecodedResourcesList(this); + if (allowsCaching() && inCache()) { + auto& memoryCache = MemoryCache::singleton(); + if (memoryCache.inLiveDecodedResourcesList(*this)) { + memoryCache.removeFromLiveDecodedResourcesList(*this); + memoryCache.insertInLiveDecodedResourcesList(*this); } - memoryCache()->prune(); + memoryCache.pruneSoon(); } } @@ -633,31 +639,27 @@ void CachedResource::setResourceToRevalidate(CachedResource* resource) ASSERT(resource != this); ASSERT(m_handlesToRevalidate.isEmpty()); ASSERT(resource->type() == type()); + ASSERT(!resource->m_proxyResource); 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); + ASSERT(m_resourceToRevalidate->m_proxyResource == this); + if (m_switchingClientsToRevalidatedResource) return; - // 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_resourceToRevalidate->m_proxyResource = nullptr; + m_resourceToRevalidate->deleteIfPossible(); + m_handlesToRevalidate.clear(); - m_resourceToRevalidate = 0; + m_resourceToRevalidate = nullptr; deleteIfPossible(); } @@ -670,9 +672,7 @@ void CachedResource::switchClientsToRevalidatedResource() LOG(ResourceLoading, "CachedResource %p switchClientsToRevalidatedResource %p", this, m_resourceToRevalidate); m_switchingClientsToRevalidatedResource = true; - HashSet<CachedResourceHandleBase*>::iterator end = m_handlesToRevalidate.end(); - for (HashSet<CachedResourceHandleBase*>::iterator it = m_handlesToRevalidate.begin(); it != end; ++it) { - CachedResourceHandleBase* handle = *it; + for (auto& handle : m_handlesToRevalidate) { handle->m_resource = m_resourceToRevalidate; m_resourceToRevalidate->registerHandle(handle); --m_handleCount; @@ -681,52 +681,37 @@ void CachedResource::switchClientsToRevalidatedResource() 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->key; - unsigned count = it->value; + for (auto& entry : m_clients) { + CachedResourceClient* client = entry.key; + unsigned count = entry.value; while (count) { clientsToMove.append(client); --count; } } - unsigned moveCount = clientsToMove.size(); - for (unsigned n = 0; n < moveCount; ++n) - removeClient(clientsToMove[n]); + for (auto& client : clientsToMove) + removeClient(*client); ASSERT(m_clients.isEmpty()); - for (unsigned n = 0; n < moveCount; ++n) - m_resourceToRevalidate->addClientToSet(clientsToMove[n]); - for (unsigned n = 0; n < moveCount; ++n) { + for (auto& client : clientsToMove) + m_resourceToRevalidate->addClientToSet(*client); + for (auto& client : clientsToMove) { // Calling didAddClient may do anything, including trying to cancel revalidation. // Assert that it didn't succeed. ASSERT(m_resourceToRevalidate); // 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]); + if (m_resourceToRevalidate->m_clients.contains(client)) + m_resourceToRevalidate->didAddClient(*client); } m_switchingClientsToRevalidatedResource = false; } void CachedResource::updateResponseAfterRevalidation(const ResourceResponse& validatingResponse) { - m_responseTimestamp = currentTime(); - - // 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) { - // Entity headers should not be sent by servers when generating a 304 - // response; misconfigured servers send them anyway. We shouldn't allow - // such headers to update the original request. We'll base this on the - // list defined by RFC2616 7.1, with a few additions for extension headers - // we care about. - if (!shouldUpdateHeaderAfterRevalidation(it->key)) - continue; - m_response.setHTTPHeaderField(it->key, it->value); - } + m_responseTimestamp = std::chrono::system_clock::now(); + + updateResponseHeadersAfterRevalidation(m_response, validatingResponse); } void CachedResource::registerHandle(CachedResourceHandleBase* h) @@ -758,95 +743,50 @@ bool CachedResource::canUseCacheValidator() const return m_response.hasCacheValidatorFields(); } -bool CachedResource::mustRevalidateDueToCacheHeaders(CachePolicy cachePolicy) const +CachedResource::RevalidationDecision CachedResource::makeRevalidationDecision(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 -{ -#if ENABLE(DISK_IMAGE_CACHE) - // It does not make sense to have a resource in the disk image cache - // (memory mapped on disk) and purgeable (in memory). So do not allow - // disk image cached resources to be purgeable. - if (isUsingDiskImageCache()) - return false; -#endif - - return !hasClients() && !m_proxyResource && !m_resourceToRevalidate; -} - -bool CachedResource::makePurgeable(bool purgeable) -{ - if (purgeable) { - ASSERT(isSafeToMakePurgeable()); - - if (m_purgeableData) { - ASSERT(!m_data); - return true; + switch (cachePolicy) { + case CachePolicyHistoryBuffer: + return RevalidationDecision::No; + + case CachePolicyReload: + return RevalidationDecision::YesDueToCachePolicy; + + case CachePolicyRevalidate: + if (m_response.cacheControlContainsImmutable() && m_response.url().protocolIs("https")) { + if (isExpired()) + return RevalidationDecision::YesDueToExpired; + return RevalidationDecision::No; } - 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; + return RevalidationDecision::YesDueToCachePolicy; - m_data->createPurgeableBuffer(); - if (!m_data->hasPurgeableBuffer()) - return false; + case CachePolicyVerify: + if (m_response.cacheControlContainsNoCache()) + return RevalidationDecision::YesDueToNoCache; + // FIXME: Cache-Control:no-store should prevent storing, not reuse. + if (m_response.cacheControlContainsNoStore()) + return RevalidationDecision::YesDueToNoStore; - m_purgeableData = m_data->releasePurgeableBuffer(); - m_purgeableData->setPurgePriority(purgePriority()); - m_purgeableData->makePurgeable(true); - m_data.clear(); - return true; - } - - if (!m_purgeableData) - return true; - ASSERT(!m_data); - ASSERT(!hasClients()); + if (isExpired()) + return RevalidationDecision::YesDueToExpired; - if (!m_purgeableData->makePurgeable(false)) - return false; - - m_data = ResourceBuffer::adoptSharedBuffer(SharedBuffer::adoptPurgeableBuffer(m_purgeableData.release())); - return true; + return RevalidationDecision::No; + }; + ASSERT_NOT_REACHED(); + return RevalidationDecision::No; } -bool CachedResource::isPurgeable() const +bool CachedResource::redirectChainAllowsReuse(ReuseExpiredRedirectionOrNot reuseExpiredRedirection) const { - return m_purgeableData && m_purgeableData->isPurgeable(); + return WebCore::redirectChainAllowsReuse(m_redirectChainCacheStatus, reuseExpiredRedirection); } -bool CachedResource::wasPurged() const +bool CachedResource::varyHeaderValuesMatch(const ResourceRequest& request) { - return m_purgeableData && m_purgeableData->wasPurged(); + if (m_varyingHeaderValues.isEmpty()) + return true; + + return verifyVaryingRequestHeaders(m_varyingHeaderValues, request, m_sessionID); } unsigned CachedResource::overheadSize() const @@ -855,59 +795,65 @@ unsigned CachedResource::overheadSize() const return sizeof(CachedResource) + m_response.memoryUsage() + kAverageClientsHashMapSize + m_resourceRequest.url().string().length() * 2; } -void CachedResource::setLoadPriority(ResourceLoadPriority loadPriority) +bool CachedResource::areAllClientsXMLHttpRequests() const { - if (loadPriority == ResourceLoadPriorityUnresolved) - loadPriority = defaultPriorityForResourceType(type()); - if (loadPriority == m_loadPriority) - return; - m_loadPriority = loadPriority; - if (m_loader) - m_loader->didChangePriority(loadPriority); + if (type() != RawResource) + return false; + + for (auto& client : m_clients) { + if (!client.key->isXMLHttpRequest()) + return false; + } + return true; } -CachedResource::CachedResourceCallback::CachedResourceCallback(CachedResource* resource, CachedResourceClient* client) - : m_resource(resource) - , m_client(client) - , m_callbackTimer(this, &CachedResourceCallback::timerFired) +void CachedResource::setLoadPriority(const std::optional<ResourceLoadPriority>& loadPriority) { - m_callbackTimer.startOneShot(0); + if (loadPriority) + m_loadPriority = loadPriority.value(); + else + m_loadPriority = defaultPriorityForResourceType(type()); } -void CachedResource::CachedResourceCallback::cancel() +inline CachedResource::Callback::Callback(CachedResource& resource, CachedResourceClient& client) + : m_resource(resource) + , m_client(client) + , m_timer(*this, &Callback::timerFired) { - if (m_callbackTimer.isActive()) - m_callbackTimer.stop(); + m_timer.startOneShot(0); } -void CachedResource::CachedResourceCallback::timerFired(Timer<CachedResourceCallback>&) +inline void CachedResource::Callback::cancel() { - m_resource->didAddClient(m_client); + if (m_timer.isActive()) + m_timer.stop(); } -#if ENABLE(DISK_IMAGE_CACHE) -bool CachedResource::isUsingDiskImageCache() const +void CachedResource::Callback::timerFired() { - return m_data && m_data->isUsingDiskImageCache(); + m_resource.didAddClient(m_client); } -#endif -#if PLATFORM(MAC) -void CachedResource::tryReplaceEncodedData(PassRefPtr<SharedBuffer> newBuffer) +#if USE(FOUNDATION) || USE(SOUP) + +void CachedResource::tryReplaceEncodedData(SharedBuffer& newBuffer) { if (!m_data) return; if (!mayTryReplaceEncodedData()) return; - - // Because the disk cache is asynchronous and racey with regards to the data we might be asked to replace, - // we need to verify that the new buffer has the same contents as our old buffer. - if (m_data->size() != newBuffer->size() || memcmp(m_data->data(), newBuffer->data(), m_data->size())) + + // We have to do the memcmp because we can't tell if the replacement file backed data is for the + // same resource or if we made a second request with the same URL which gave us a different + // resource. We have seen this happen for cached POST resources. + if (m_data->size() != newBuffer.size() || memcmp(m_data->data(), newBuffer.data(), m_data->size())) return; - m_data->tryReplaceSharedBufferContents(newBuffer.get()); + if (m_data->tryReplaceContentsWithPlatformBuffer(newBuffer)) + didReplaceSharedBufferContents(); } + #endif } diff --git a/Source/WebCore/loader/cache/CachedResource.h b/Source/WebCore/loader/cache/CachedResource.h index 8de69cdbf..6f4e0e1b2 100644 --- a/Source/WebCore/loader/cache/CachedResource.h +++ b/Source/WebCore/loader/cache/CachedResource.h @@ -20,37 +20,37 @@ Boston, MA 02110-1301, USA. */ -#ifndef CachedResource_h -#define CachedResource_h +#pragma once #include "CachePolicy.h" +#include "CacheValidation.h" #include "FrameLoaderTypes.h" -#include "PurgePriority.h" #include "ResourceError.h" #include "ResourceLoadPriority.h" #include "ResourceLoaderOptions.h" #include "ResourceRequest.h" #include "ResourceResponse.h" +#include "SessionID.h" #include "Timer.h" #include <time.h> #include <wtf/HashCountedSet.h> #include <wtf/HashSet.h> -#include <wtf/OwnPtr.h> +#include <wtf/TypeCasts.h> #include <wtf/Vector.h> #include <wtf/text/WTFString.h> namespace WebCore { -class MemoryCache; class CachedResourceClient; class CachedResourceHandleBase; class CachedResourceLoader; -class InspectorResource; -class PurgeableBuffer; -class ResourceBuffer; +class CachedResourceRequest; +class LoadTiming; +class MemoryCache; class SecurityOrigin; class SharedBuffer; class SubresourceLoader; +class TextResourceDecoder; // 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. @@ -58,8 +58,7 @@ class SubresourceLoader; class CachedResource { WTF_MAKE_NONCOPYABLE(CachedResource); WTF_MAKE_FAST_ALLOCATED; friend class MemoryCache; - friend class InspectorResource; - + public: enum Type { MainResource, @@ -67,10 +66,12 @@ public: CSSStyleSheet, Script, FontResource, - RawResource -#if ENABLE(SVG) - , SVGDocumentResource +#if ENABLE(SVG_FONTS) + SVGFontResource, #endif + MediaResource, + RawResource, + SVGDocumentResource #if ENABLE(XSLT) , XSLStyleSheet #endif @@ -91,16 +92,17 @@ public: DecodeError }; - CachedResource(const ResourceRequest&, Type); + CachedResource(CachedResourceRequest&&, Type, SessionID); virtual ~CachedResource(); - virtual void load(CachedResourceLoader*, const ResourceLoaderOptions&); + virtual void load(CachedResourceLoader&); virtual void setEncoding(const String&) { } virtual String encoding() const { return String(); } - virtual void addDataBuffer(ResourceBuffer*); + virtual const TextResourceDecoder* textResourceDecoder() const { return nullptr; } + virtual void addDataBuffer(SharedBuffer&); virtual void addData(const char* data, unsigned length); - virtual void finishLoading(ResourceBuffer*); + virtual void finishLoading(SharedBuffer*); virtual void error(CachedResource::Status); void setResourceError(const ResourceError& error) { m_error = error; } @@ -108,20 +110,20 @@ public: virtual bool shouldIgnoreHTTPStatusCodeErrors() const { return false; } + const ResourceRequest& resourceRequest() const { return m_resourceRequest; } ResourceRequest& resourceRequest() { return m_resourceRequest; } const URL& url() const { return m_resourceRequest.url();} -#if ENABLE(CACHE_PARTITIONING) const String& cachePartition() const { return m_resourceRequest.cachePartition(); } -#endif - Type type() const { return static_cast<Type>(m_type); } - + SessionID sessionID() const { return m_sessionID; } + Type type() const { return m_type; } + ResourceLoadPriority loadPriority() const { return m_loadPriority; } - void setLoadPriority(ResourceLoadPriority); + void setLoadPriority(const std::optional<ResourceLoadPriority>&); - void addClient(CachedResourceClient*); - void removeClient(CachedResourceClient*); + WEBCORE_EXPORT void addClient(CachedResourceClient&); + WEBCORE_EXPORT void removeClient(CachedResourceClient&); bool hasClients() const { return !m_clients.isEmpty() || !m_clientsAwaitingCallback.isEmpty(); } - bool hasClient(CachedResourceClient* client) { return m_clients.contains(client) || m_clientsAwaitingCallback.contains(client); } + bool hasClient(CachedResourceClient& client) { return m_clients.contains(&client) || m_clientsAwaitingCallback.contains(&client); } bool deleteIfPossible(); enum PreloadResult { @@ -132,8 +134,8 @@ public: }; PreloadResult preloadResult() const { return static_cast<PreloadResult>(m_preloadResult); } - virtual void didAddClient(CachedResourceClient*); - virtual void didRemoveClient(CachedResourceClient*) { } + virtual void didAddClient(CachedResourceClient&); + virtual void didRemoveClient(CachedResourceClient&) { } virtual void allClientsRemoved() { } void destroyDecodedDataIfNeeded(); @@ -146,7 +148,7 @@ public: 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; } @@ -155,10 +157,15 @@ public: SubresourceLoader* loader() { return m_loader.get(); } - virtual bool isImage() const { return false; } + bool areAllClientsXMLHttpRequests() const; + + bool isImage() const { return type() == ImageResource; } + // FIXME: CachedRawResource could be a main resource, an audio/video resource, or a raw XHR/icon resource. + bool isMainOrMediaOrRawResource() const { return type() == MainResource || type() == MediaResource || type() == RawResource; } bool ignoreForRequestCount() const { - return type() == MainResource + return m_resourceRequest.ignoreForRequestCount() + || type() == MainResource #if ENABLE(LINK_PREFETCH) || type() == LinkPrefetch || type() == LinkSubresource @@ -169,205 +176,220 @@ public: unsigned accessCount() const { return m_accessCount; } void increaseAccessCount() { m_accessCount++; } - // Computes the status of an object after loading. + // Computes the status of an object after loading. // Updates the expire date on the cache entry file void finish(); - bool passesAccessControlCheck(SecurityOrigin*); - // 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; } - - bool inLiveDecodedResourcesList() { return m_inLiveDecodedResourcesList; } - + void clearLoader(); - ResourceBuffer* resourceBuffer() const { ASSERT(!m_purgeableData); return m_data.get(); } + SharedBuffer* resourceBuffer() const { return m_data.get(); } - virtual void willSendRequest(ResourceRequest&, const ResourceResponse&) { m_requestedFromNetworkingLayer = true; } + virtual void redirectReceived(ResourceRequest&, const ResourceResponse&); virtual void responseReceived(const ResourceResponse&); - void setResponse(const ResourceResponse& response) { m_response = response; } + virtual bool shouldCacheResponse(const ResourceResponse&) { return true; } + void setResponse(const ResourceResponse&); const ResourceResponse& response() const { return m_response; } + void setCrossOrigin(); + bool isCrossOrigin() const; + bool isCORSSameOrigin() const; + ResourceResponse::Tainting responseTainting() const { return m_responseTainting; } + + void loadFrom(const CachedResource&); + + SecurityOrigin* origin() const { return m_origin.get(); } + AtomicString initiatorName() const { return m_initiatorName; } + bool canDelete() const { return !hasClients() && !m_loader && !m_preloadCount && !m_handleCount && !m_resourceToRevalidate && !m_proxyResource; } bool hasOneHandle() const { return m_handleCount == 1; } 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; } - void cancelLoad(); bool wasCanceled() const { return m_error.isCancellation(); } bool errorOccurred() const { return m_status == LoadError || m_status == DecodeError; } - bool loadFailedOrCanceled() { return !m_error.isNull(); } + bool loadFailedOrCanceled() const { return !m_error.isNull(); } bool shouldSendResourceLoadCallbacks() const { return m_options.sendLoadCallbacks == SendCallbacks; } DataBufferingPolicy dataBufferingPolicy() const { return m_options.dataBufferingPolicy; } - + + bool allowsCaching() const { return m_options.cachingPolicy == CachingPolicy::AllowCaching; } + const FetchOptions& options() const { return m_options; } + 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 isLinkPreload() { return m_isLinkPreload; } + void setLinkPreload() { m_isLinkPreload = true; } + + void registerHandle(CachedResourceHandleBase*); + WEBCORE_EXPORT void unregisterHandle(CachedResourceHandleBase*); + bool canUseCacheValidator() const; - virtual bool mustRevalidateDueToCacheHeaders(CachePolicy) const; + enum class RevalidationDecision { No, YesDueToCachePolicy, YesDueToNoStore, YesDueToNoCache, YesDueToExpired }; + virtual RevalidationDecision makeRevalidationDecision(CachePolicy) const; + bool redirectChainAllowsReuse(ReuseExpiredRedirectionOrNot) const; + bool hasRedirections() const { return m_redirectChainCacheStatus.status != RedirectChainCacheStatus::Status::NoRedirection; } + + bool varyHeaderValuesMatch(const ResourceRequest&); 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*); virtual void switchClientsToRevalidatedResource(); void clearResourceToRevalidate(); void updateResponseAfterRevalidation(const ResourceResponse& validatingResponse); - + bool validationInProgress() const { return m_proxyResource; } + bool validationCompleting() const { return m_proxyResource && m_proxyResource->m_switchingClientsToRevalidatedResource; } + virtual void didSendData(unsigned long long /* bytesSent */, unsigned long long /* totalBytesToBeSent */) { } + virtual void didRetrieveDerivedDataFromCache(const String& /* type */, SharedBuffer&) { } + void setLoadFinishTime(double finishTime) { m_loadFinishTime = finishTime; } double loadFinishTime() const { return m_loadFinishTime; } -#if ENABLE(DISK_IMAGE_CACHE) - bool isUsingDiskImageCache() const; - virtual bool canUseDiskImageCache() const { return false; } - virtual void useDiskImageCache() { ASSERT(canUseDiskImageCache()); } -#endif - - virtual bool canReuse(const ResourceRequest&) const { return true; } - -#if PLATFORM(MAC) - void tryReplaceEncodedData(PassRefPtr<SharedBuffer>); +#if USE(FOUNDATION) || USE(SOUP) + WEBCORE_EXPORT void tryReplaceEncodedData(SharedBuffer&); #endif #if USE(SOUP) - virtual char* getOrCreateReadBuffer(size_t /* requestedSize */, size_t& /* actualSize */) { return 0; } + virtual char* getOrCreateReadBuffer(size_t /* requestedSize */, size_t& /* actualSize */) { return nullptr; } #endif + unsigned long identifierForLoadWithoutResourceLoader() const { return m_identifierForLoadWithoutResourceLoader; } + static ResourceLoadPriority defaultPriorityForResourceType(Type); + protected: - virtual void checkNotify(); + // CachedResource constructor that may be used when the CachedResource can already be filled with response data. + CachedResource(const URL&, Type, SessionID); void setEncodedSize(unsigned); void setDecodedSize(unsigned); void didAccessDecodedData(double timeStamp); - bool isSafeToMakePurgeable() const; - - HashCountedSet<CachedResourceClient*> m_clients; + virtual void didReplaceSharedBufferContents() { } - class CachedResourceCallback { - public: - static PassOwnPtr<CachedResourceCallback> schedule(CachedResource* resource, CachedResourceClient* client) { return adoptPtr(new CachedResourceCallback(resource, client)); } - void cancel(); - private: - CachedResourceCallback(CachedResource*, CachedResourceClient*); - void timerFired(Timer<CachedResourceCallback>&); - - CachedResource* m_resource; - CachedResourceClient* m_client; - Timer<CachedResourceCallback> m_callbackTimer; - }; - HashMap<CachedResourceClient*, OwnPtr<CachedResourceCallback>> m_clientsAwaitingCallback; + virtual void setBodyDataFrom(const CachedResource&); + // FIXME: Make the rest of these data members private and use functions in derived classes instead. + HashCountedSet<CachedResourceClient*> m_clients; ResourceRequest m_resourceRequest; - String m_accept; RefPtr<SubresourceLoader> m_loader; ResourceLoaderOptions m_options; - ResourceLoadPriority m_loadPriority; - ResourceResponse m_response; - double m_responseTimestamp; - - RefPtr<ResourceBuffer> m_data; - OwnPtr<PurgeableBuffer> m_purgeableData; - DeferrableOneShotTimer<CachedResource> m_decodedDataDeletionTimer; + ResourceResponse::Tainting m_responseTainting { ResourceResponse::Tainting::Basic }; + RefPtr<SharedBuffer> m_data; + DeferrableOneShotTimer m_decodedDataDeletionTimer; private: - bool addClientToSet(CachedResourceClient*); + class Callback; + + bool addClientToSet(CachedResourceClient&); - void decodedDataDeletionTimerFired(DeferrableOneShotTimer<CachedResource>&); + void decodedDataDeletionTimerFired(); - virtual PurgePriority purgePriority() const { return PurgeDefault; } + virtual void checkNotify(); virtual bool mayTryReplaceEncodedData() const { return false; } - double currentAge() const; - double freshnessLifetime() const; + std::chrono::microseconds freshnessLifetime(const ResourceResponse&) const; - void addAdditionalRequestHeaders(CachedResourceLoader*); + void addAdditionalRequestHeaders(CachedResourceLoader&); void failBeforeStarting(); + HashMap<CachedResourceClient*, std::unique_ptr<Callback>> m_clientsAwaitingCallback; + SessionID m_sessionID; + ResourceLoadPriority m_loadPriority; + std::chrono::system_clock::time_point m_responseTimestamp; + String m_fragmentIdentifierForRequest; ResourceError m_error; + RefPtr<SecurityOrigin> m_origin; + AtomicString m_initiatorName; - double m_lastDecodedAccessTime; // Used as a "thrash guard" in the cache - double m_loadFinishTime; + double m_lastDecodedAccessTime { 0 }; // Used as a "thrash guard" in the cache + double m_loadFinishTime { 0 }; - unsigned m_encodedSize; - unsigned m_decodedSize; - unsigned m_accessCount; - unsigned m_handleCount; - unsigned m_preloadCount; + unsigned m_encodedSize { 0 }; + unsigned m_decodedSize { 0 }; + unsigned m_accessCount { 0 }; + unsigned m_handleCount { 0 }; + unsigned m_preloadCount { 0 }; - unsigned m_preloadResult : 2; // PreloadResult + PreloadResult m_preloadResult { PreloadNotReferenced }; - bool m_inLiveDecodedResourcesList : 1; - bool m_requestedFromNetworkingLayer : 1; + bool m_requestedFromNetworkingLayer { false }; - bool m_inCache : 1; - bool m_loading : 1; + bool m_inCache { false }; + bool m_loading { false }; + bool m_isLinkPreload { false }; - bool m_switchingClientsToRevalidatedResource : 1; + bool m_switchingClientsToRevalidatedResource { false }; - unsigned m_type : 4; // Type - unsigned m_status : 3; // Status + Type m_type; // Type + unsigned m_status { Pending }; // Status #ifndef NDEBUG - bool m_deleted; - unsigned m_lruIndex; + bool m_deleted { false }; + unsigned m_lruIndex { 0 }; #endif - CachedResource* m_nextInAllResourcesList; - CachedResource* m_prevInAllResourcesList; - - CachedResource* m_nextInLiveResourcesList; - CachedResource* m_prevInLiveResourcesList; + CachedResourceLoader* m_owningCachedResourceLoader { nullptr }; // only non-null for resources that are not in the cache - 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; + CachedResource* m_resourceToRevalidate { nullptr }; // 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; + CachedResource* m_proxyResource { nullptr }; // These handles will need to be updated to point to the m_resourceToRevalidate in case we get 304 response. HashSet<CachedResourceHandleBase*> m_handlesToRevalidate; -}; -} + RedirectChainCacheStatus m_redirectChainCacheStatus; + + Vector<std::pair<String, String>> m_varyingHeaderValues; + + unsigned long m_identifierForLoadWithoutResourceLoader { 0 }; +}; +class CachedResource::Callback { +#if !COMPILER(MSVC) + WTF_MAKE_FAST_ALLOCATED; #endif +public: + Callback(CachedResource&, CachedResourceClient&); + + void cancel(); + +private: + void timerFired(); + + CachedResource& m_resource; + CachedResourceClient& m_client; + Timer m_timer; +}; + +} // namespace WebCore + +#define SPECIALIZE_TYPE_TRAITS_CACHED_RESOURCE(ToClassName, CachedResourceType) \ +SPECIALIZE_TYPE_TRAITS_BEGIN(WebCore::ToClassName) \ + static bool isType(const WebCore::CachedResource& resource) { return resource.type() == WebCore::CachedResourceType; } \ +SPECIALIZE_TYPE_TRAITS_END() diff --git a/Source/WebCore/loader/cache/CachedResourceClient.h b/Source/WebCore/loader/cache/CachedResourceClient.h index 402ff91a1..17c361d71 100644 --- a/Source/WebCore/loader/cache/CachedResourceClient.h +++ b/Source/WebCore/loader/cache/CachedResourceClient.h @@ -21,39 +21,33 @@ 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/FastMalloc.h> +#pragma once namespace WebCore { + class CachedResource; class CachedResourceClient { - WTF_MAKE_FAST_ALLOCATED; public: enum CachedResourceClientType { BaseResourceType, ImageType, FontType, StyleSheetType, -#if ENABLE(SVG) SVGDocumentType, -#endif RawResourceType }; virtual ~CachedResourceClient() { } - virtual void notifyFinished(CachedResource*) { } - virtual void deprecatedDidReceiveCachedResource(CachedResource*) { } - + virtual void notifyFinished(CachedResource&) { } + virtual void deprecatedDidReceiveCachedResource(CachedResource&) { } + virtual bool isXMLHttpRequest() const { return false; } + static CachedResourceClientType expectedType() { return BaseResourceType; } virtual CachedResourceClientType resourceClientType() const { return expectedType(); } protected: CachedResourceClient() { } }; -} -#endif +} diff --git a/Source/WebCore/loader/cache/CachedResourceClientWalker.h b/Source/WebCore/loader/cache/CachedResourceClientWalker.h index de352f037..39ee0234f 100644 --- a/Source/WebCore/loader/cache/CachedResourceClientWalker.h +++ b/Source/WebCore/loader/cache/CachedResourceClientWalker.h @@ -22,8 +22,7 @@ pages from the web. It has a memory cache for these objects. */ -#ifndef CachedResourceClientWalker_h -#define CachedResourceClientWalker_h +#pragma once #include "CachedResourceClient.h" #include <wtf/HashCountedSet.h> @@ -56,7 +55,7 @@ public: } } - return 0; + return nullptr; } private: const HashCountedSet<CachedResourceClient*>& m_clientSet; @@ -64,6 +63,4 @@ private: size_t m_index; }; -} - -#endif +} // namespace WebCore diff --git a/Source/WebCore/loader/cache/CachedResourceHandle.cpp b/Source/WebCore/loader/cache/CachedResourceHandle.cpp index 07c84ed70..097276e93 100644 --- a/Source/WebCore/loader/cache/CachedResourceHandle.cpp +++ b/Source/WebCore/loader/cache/CachedResourceHandle.cpp @@ -31,7 +31,7 @@ namespace WebCore { CachedResourceHandleBase::CachedResourceHandleBase() - : m_resource(0) + : m_resource(nullptr) { } diff --git a/Source/WebCore/loader/cache/CachedResourceHandle.h b/Source/WebCore/loader/cache/CachedResourceHandle.h index 5b6b42540..3dbbda6c1 100644 --- a/Source/WebCore/loader/cache/CachedResourceHandle.h +++ b/Source/WebCore/loader/cache/CachedResourceHandle.h @@ -23,9 +23,9 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef CachedResourceHandle_h -#define CachedResourceHandle_h +#pragma once +#include "PlatformExportMacros.h" #include <wtf/Forward.h> namespace WebCore { @@ -49,7 +49,7 @@ protected: CachedResourceHandleBase(CachedResource*); CachedResourceHandleBase(const CachedResourceHandleBase&); - void setResource(CachedResource*); + WEBCORE_EXPORT void setResource(CachedResource*); private: CachedResourceHandleBase& operator=(const CachedResourceHandleBase&) { return *this; } @@ -68,6 +68,7 @@ public: R* get() const { return reinterpret_cast<R*>(CachedResourceHandleBase::get()); } R* operator->() const { return get(); } + R& operator*() const { ASSERT(get()); return *get(); } CachedResourceHandle& operator=(R* res) { setResource(res); return *this; } CachedResourceHandle& operator=(const CachedResourceHandle& o) { setResource(o.get()); return *this; } @@ -95,5 +96,3 @@ template <class R, class RR> bool operator!=(const RR* res, const CachedResource } } // namespace WebCore - -#endif // CachedResourceHandle diff --git a/Source/WebCore/loader/cache/CachedResourceLoader.cpp b/Source/WebCore/loader/cache/CachedResourceLoader.cpp index bd38fe823..baf827eb7 100644 --- a/Source/WebCore/loader/cache/CachedResourceLoader.cpp +++ b/Source/WebCore/loader/cache/CachedResourceLoader.cpp @@ -2,7 +2,7 @@ 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, 2009, 2010, 2011 Apple Inc. All rights reserved. + Copyright (C) 2004-2016 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 @@ -28,18 +28,22 @@ #include "CachedResourceLoader.h" #include "CachedCSSStyleSheet.h" -#include "CachedSVGDocument.h" #include "CachedFont.h" #include "CachedImage.h" #include "CachedRawResource.h" #include "CachedResourceRequest.h" +#include "CachedSVGDocument.h" +#include "CachedSVGFont.h" #include "CachedScript.h" #include "CachedXSLStyleSheet.h" #include "Chrome.h" #include "ChromeClient.h" -#include "Console.h" +#include "ContentExtensionError.h" +#include "ContentExtensionRule.h" #include "ContentSecurityPolicy.h" #include "DOMWindow.h" +#include "DiagnosticLoggingClient.h" +#include "DiagnosticLoggingKeys.h" #include "Document.h" #include "DocumentLoader.h" #include "Frame.h" @@ -48,16 +52,26 @@ #include "HTMLElement.h" #include "HTMLFrameOwnerElement.h" #include "LoaderStrategy.h" +#include "LocalizedStrings.h" #include "Logging.h" +#include "MainFrame.h" #include "MemoryCache.h" #include "Page.h" #include "PingLoader.h" #include "PlatformStrategies.h" #include "RenderElement.h" -#include "ResourceLoadScheduler.h" +#include "ResourceLoadInfo.h" +#include "ResourceTiming.h" +#include "RuntimeEnabledFeatures.h" #include "ScriptController.h" #include "SecurityOrigin.h" +#include "SecurityPolicy.h" +#include "SessionID.h" #include "Settings.h" +#include "StyleSheetContents.h" +#include "SubresourceLoader.h" +#include "UserContentController.h" +#include "UserStyleSheet.h" #include <wtf/text/CString.h> #include <wtf/text/WTFString.h> @@ -65,56 +79,57 @@ #include "CachedTextTrack.h" #endif -#if ENABLE(RESOURCE_TIMING) -#include "Performance.h" -#endif - #define PRELOAD_DEBUG 0 +#define RELEASE_LOG_IF_ALLOWED(fmt, ...) RELEASE_LOG_IF(isAlwaysOnLoggingAllowed(), Network, "%p - CachedResourceLoader::" fmt, this, ##__VA_ARGS__) + namespace WebCore { -static CachedResource* createResource(CachedResource::Type type, ResourceRequest& request, const String& charset) +static CachedResource* createResource(CachedResource::Type type, CachedResourceRequest&& request, SessionID sessionID) { switch (type) { case CachedResource::ImageResource: - return new CachedImage(request); + return new CachedImage(WTFMove(request), sessionID); case CachedResource::CSSStyleSheet: - return new CachedCSSStyleSheet(request, charset); + return new CachedCSSStyleSheet(WTFMove(request), sessionID); case CachedResource::Script: - return new CachedScript(request, charset); -#if ENABLE(SVG) + return new CachedScript(WTFMove(request), sessionID); case CachedResource::SVGDocumentResource: - return new CachedSVGDocument(request); + return new CachedSVGDocument(WTFMove(request), sessionID); +#if ENABLE(SVG_FONTS) + case CachedResource::SVGFontResource: + return new CachedSVGFont(WTFMove(request), sessionID); #endif case CachedResource::FontResource: - return new CachedFont(request); + return new CachedFont(WTFMove(request), sessionID); + case CachedResource::MediaResource: case CachedResource::RawResource: case CachedResource::MainResource: - return new CachedRawResource(request, type); + return new CachedRawResource(WTFMove(request), type, sessionID); #if ENABLE(XSLT) case CachedResource::XSLStyleSheet: - return new CachedXSLStyleSheet(request); + return new CachedXSLStyleSheet(WTFMove(request), sessionID); #endif #if ENABLE(LINK_PREFETCH) case CachedResource::LinkPrefetch: - return new CachedResource(request, CachedResource::LinkPrefetch); + return new CachedResource(WTFMove(request), CachedResource::LinkPrefetch, sessionID); case CachedResource::LinkSubresource: - return new CachedResource(request, CachedResource::LinkSubresource); + return new CachedResource(WTFMove(request), CachedResource::LinkSubresource, sessionID); #endif #if ENABLE(VIDEO_TRACK) case CachedResource::TextTrackResource: - return new CachedTextTrack(request); + return new CachedTextTrack(WTFMove(request), sessionID); #endif } ASSERT_NOT_REACHED(); - return 0; + return nullptr; } CachedResourceLoader::CachedResourceLoader(DocumentLoader* documentLoader) - : m_document(0) + : m_document(nullptr) , m_documentLoader(documentLoader) , m_requestCount(0) - , m_garbageCollectDocumentResourcesTimer(this, &CachedResourceLoader::garbageCollectDocumentResourcesTimerFired) + , m_garbageCollectDocumentResourcesTimer(*this, &CachedResourceLoader::garbageCollectDocumentResources) , m_autoLoadImages(true) , m_imagesEnabled(true) , m_allowStaleResources(false) @@ -123,157 +138,233 @@ CachedResourceLoader::CachedResourceLoader(DocumentLoader* documentLoader) CachedResourceLoader::~CachedResourceLoader() { - m_documentLoader = 0; - m_document = 0; + m_documentLoader = nullptr; + m_document = nullptr; - clearPreloads(); - DocumentResourceMap::iterator end = m_documentResources.end(); - for (DocumentResourceMap::iterator it = m_documentResources.begin(); it != end; ++it) - it->value->setOwningCachedResourceLoader(0); + clearPreloads(ClearPreloadsMode::ClearAllPreloads); + for (auto& resource : m_documentResources.values()) + resource->setOwningCachedResourceLoader(nullptr); // Make sure no requests still point to this CachedResourceLoader ASSERT(m_requestCount == 0); } -CachedResource* CachedResourceLoader::cachedResource(const String& resourceURL) const +CachedResource* CachedResourceLoader::cachedResource(const String& resourceURL) const { - URL url = m_document->completeURL(resourceURL); - return cachedResource(url); + ASSERT(!resourceURL.isNull()); + return cachedResource(MemoryCache::removeFragmentIdentifierIfNeeded(m_document->completeURL(resourceURL))); } -CachedResource* CachedResourceLoader::cachedResource(const URL& resourceURL) const +CachedResource* CachedResourceLoader::cachedResource(const URL& url) const { - URL url = MemoryCache::removeFragmentIdentifierIfNeeded(resourceURL); - return m_documentResources.get(url).get(); + ASSERT(!MemoryCache::shouldRemoveFragmentIdentifier(url)); + return m_documentResources.get(url).get(); } Frame* CachedResourceLoader::frame() const { - return m_documentLoader ? m_documentLoader->frame() : 0; + return m_documentLoader ? m_documentLoader->frame() : nullptr; +} + +SessionID CachedResourceLoader::sessionID() const +{ + SessionID sessionID = SessionID::defaultSessionID(); + + if (Frame* f = frame()) + sessionID = f->page()->sessionID(); + + return sessionID; } -CachedResourceHandle<CachedImage> CachedResourceLoader::requestImage(CachedResourceRequest& request) +CachedResourceHandle<CachedImage> CachedResourceLoader::requestImage(CachedResourceRequest&& request) { - if (Frame* f = frame()) { - if (f->loader().pageDismissalEventBeingDispatched() != FrameLoader::NoDismissal) { + if (Frame* frame = this->frame()) { + if (frame->loader().pageDismissalEventBeingDispatched() != FrameLoader::PageDismissalType::None) { + if (Document* document = frame->document()) + request.upgradeInsecureRequestIfNeeded(*document); URL requestURL = request.resourceRequest().url(); - if (requestURL.isValid() && canRequest(CachedResource::ImageResource, requestURL, request.options(), request.forPreload())) - PingLoader::loadImage(f, requestURL); - return 0; + if (requestURL.isValid() && canRequest(CachedResource::ImageResource, requestURL, request, ForPreload::No)) + PingLoader::loadImage(*frame, requestURL); + return nullptr; } } - request.setDefer(clientDefersImage(request.resourceRequest().url()) ? CachedResourceRequest::DeferredByClient : CachedResourceRequest::NoDefer); - return static_cast<CachedImage*>(requestResource(CachedResource::ImageResource, request).get()); + + auto defer = clientDefersImage(request.resourceRequest().url()) ? DeferOption::DeferredByClient : DeferOption::NoDefer; + return downcast<CachedImage>(requestResource(CachedResource::ImageResource, WTFMove(request), ForPreload::No, defer).get()); } -CachedResourceHandle<CachedFont> CachedResourceLoader::requestFont(CachedResourceRequest& request) +CachedResourceHandle<CachedFont> CachedResourceLoader::requestFont(CachedResourceRequest&& request, bool isSVG) { - return static_cast<CachedFont*>(requestResource(CachedResource::FontResource, request).get()); +#if ENABLE(SVG_FONTS) + if (isSVG) + return downcast<CachedSVGFont>(requestResource(CachedResource::SVGFontResource, WTFMove(request)).get()); +#else + UNUSED_PARAM(isSVG); +#endif + return downcast<CachedFont>(requestResource(CachedResource::FontResource, WTFMove(request)).get()); } #if ENABLE(VIDEO_TRACK) -CachedResourceHandle<CachedTextTrack> CachedResourceLoader::requestTextTrack(CachedResourceRequest& request) +CachedResourceHandle<CachedTextTrack> CachedResourceLoader::requestTextTrack(CachedResourceRequest&& request) { - return static_cast<CachedTextTrack*>(requestResource(CachedResource::TextTrackResource, request).get()); + return downcast<CachedTextTrack>(requestResource(CachedResource::TextTrackResource, WTFMove(request)).get()); } #endif -CachedResourceHandle<CachedCSSStyleSheet> CachedResourceLoader::requestCSSStyleSheet(CachedResourceRequest& request) +CachedResourceHandle<CachedCSSStyleSheet> CachedResourceLoader::requestCSSStyleSheet(CachedResourceRequest&& request) { - return static_cast<CachedCSSStyleSheet*>(requestResource(CachedResource::CSSStyleSheet, request).get()); + return downcast<CachedCSSStyleSheet>(requestResource(CachedResource::CSSStyleSheet, WTFMove(request)).get()); } -CachedResourceHandle<CachedCSSStyleSheet> CachedResourceLoader::requestUserCSSStyleSheet(CachedResourceRequest& request) +CachedResourceHandle<CachedCSSStyleSheet> CachedResourceLoader::requestUserCSSStyleSheet(CachedResourceRequest&& request) { - URL url = MemoryCache::removeFragmentIdentifierIfNeeded(request.resourceRequest().url()); - -#if ENABLE(CACHE_PARTITIONING) - request.mutableResourceRequest().setCachePartition(document()->topOrigin()->cachePartition()); -#endif - - if (CachedResource* existing = memoryCache()->resourceForRequest(request.resourceRequest())) { - if (existing->type() == CachedResource::CSSStyleSheet) - return static_cast<CachedCSSStyleSheet*>(existing); - memoryCache()->remove(existing); + ASSERT(document()); + request.setDomainForCachePartition(*document()); + + auto& memoryCache = MemoryCache::singleton(); + if (request.allowsCaching()) { + if (CachedResource* existing = memoryCache.resourceForRequest(request.resourceRequest(), sessionID())) { + if (is<CachedCSSStyleSheet>(*existing)) + return downcast<CachedCSSStyleSheet>(existing); + memoryCache.remove(*existing); + } } - if (url.string() != request.resourceRequest().url()) - request.mutableResourceRequest().setURL(url); - CachedResourceHandle<CachedCSSStyleSheet> userSheet = new CachedCSSStyleSheet(request.resourceRequest(), request.charset()); + request.removeFragmentIdentifierIfNeeded(); + + CachedResourceHandle<CachedCSSStyleSheet> userSheet = new CachedCSSStyleSheet(WTFMove(request), sessionID()); - memoryCache()->add(userSheet.get()); + if (userSheet->allowsCaching()) + memoryCache.add(*userSheet); // FIXME: loadResource calls setOwningCachedResourceLoader() if the resource couldn't be added to cache. Does this function need to call it, too? - userSheet->load(this, ResourceLoaderOptions(DoNotSendCallbacks, SniffContent, BufferData, AllowStoredCredentials, AskClientForAllCredentials, SkipSecurityCheck, UseDefaultOriginRestrictionsForType)); - + userSheet->load(*this); return userSheet; } -CachedResourceHandle<CachedScript> CachedResourceLoader::requestScript(CachedResourceRequest& request) +CachedResourceHandle<CachedScript> CachedResourceLoader::requestScript(CachedResourceRequest&& request) { - return static_cast<CachedScript*>(requestResource(CachedResource::Script, request).get()); + return downcast<CachedScript>(requestResource(CachedResource::Script, WTFMove(request)).get()); } #if ENABLE(XSLT) -CachedResourceHandle<CachedXSLStyleSheet> CachedResourceLoader::requestXSLStyleSheet(CachedResourceRequest& request) +CachedResourceHandle<CachedXSLStyleSheet> CachedResourceLoader::requestXSLStyleSheet(CachedResourceRequest&& request) { - return static_cast<CachedXSLStyleSheet*>(requestResource(CachedResource::XSLStyleSheet, request).get()); + return downcast<CachedXSLStyleSheet>(requestResource(CachedResource::XSLStyleSheet, WTFMove(request)).get()); } #endif -#if ENABLE(SVG) -CachedResourceHandle<CachedSVGDocument> CachedResourceLoader::requestSVGDocument(CachedResourceRequest& request) +CachedResourceHandle<CachedSVGDocument> CachedResourceLoader::requestSVGDocument(CachedResourceRequest&& request) { - return static_cast<CachedSVGDocument*>(requestResource(CachedResource::SVGDocumentResource, request).get()); + return downcast<CachedSVGDocument>(requestResource(CachedResource::SVGDocumentResource, WTFMove(request)).get()); } -#endif #if ENABLE(LINK_PREFETCH) -CachedResourceHandle<CachedResource> CachedResourceLoader::requestLinkResource(CachedResource::Type type, CachedResourceRequest& request) +CachedResourceHandle<CachedResource> CachedResourceLoader::requestLinkResource(CachedResource::Type type, CachedResourceRequest&& request) { ASSERT(frame()); ASSERT(type == CachedResource::LinkPrefetch || type == CachedResource::LinkSubresource); - return requestResource(type, request); + return requestResource(type, WTFMove(request)); } #endif -CachedResourceHandle<CachedRawResource> CachedResourceLoader::requestRawResource(CachedResourceRequest& request) +CachedResourceHandle<CachedRawResource> CachedResourceLoader::requestMedia(CachedResourceRequest&& request) { - return static_cast<CachedRawResource*>(requestResource(CachedResource::RawResource, request).get()); + return downcast<CachedRawResource>(requestResource(CachedResource::MediaResource, WTFMove(request)).get()); } -CachedResourceHandle<CachedRawResource> CachedResourceLoader::requestMainResource(CachedResourceRequest& request) +CachedResourceHandle<CachedRawResource> CachedResourceLoader::requestRawResource(CachedResourceRequest&& request) { - return static_cast<CachedRawResource*>(requestResource(CachedResource::MainResource, request).get()); + return downcast<CachedRawResource>(requestResource(CachedResource::RawResource, WTFMove(request)).get()); +} + +CachedResourceHandle<CachedRawResource> CachedResourceLoader::requestMainResource(CachedResourceRequest&& request) +{ + return downcast<CachedRawResource>(requestResource(CachedResource::MainResource, WTFMove(request)).get()); +} + +static MixedContentChecker::ContentType contentTypeFromResourceType(CachedResource::Type type) +{ + switch (type) { + // https://w3c.github.io/webappsec-mixed-content/#category-optionally-blockable + // Editor's Draft, 11 February 2016 + // 3.1. Optionally-blockable Content + case CachedResource::ImageResource: + case CachedResource::MediaResource: + return MixedContentChecker::ContentType::ActiveCanWarn; + + case CachedResource::CSSStyleSheet: + case CachedResource::Script: + case CachedResource::FontResource: + return MixedContentChecker::ContentType::Active; + +#if ENABLE(SVG_FONTS) + case CachedResource::SVGFontResource: + return MixedContentChecker::ContentType::Active; +#endif + + case CachedResource::RawResource: + case CachedResource::SVGDocumentResource: + return MixedContentChecker::ContentType::Active; +#if ENABLE(XSLT) + case CachedResource::XSLStyleSheet: + return MixedContentChecker::ContentType::Active; +#endif + +#if ENABLE(LINK_PREFETCH) + case CachedResource::LinkPrefetch: + case CachedResource::LinkSubresource: + return MixedContentChecker::ContentType::Active; +#endif + +#if ENABLE(VIDEO_TRACK) + case CachedResource::TextTrackResource: + return MixedContentChecker::ContentType::Active; +#endif + default: + ASSERT_NOT_REACHED(); + return MixedContentChecker::ContentType::Active; + } } bool CachedResourceLoader::checkInsecureContent(CachedResource::Type type, const URL& url) const { + if (!canRequestInContentDispositionAttachmentSandbox(type, url)) + return false; + switch (type) { case CachedResource::Script: #if ENABLE(XSLT) case CachedResource::XSLStyleSheet: #endif -#if ENABLE(SVG) case CachedResource::SVGDocumentResource: -#endif case CachedResource::CSSStyleSheet: // These resource can inject script into the current document (Script, // XSL) or exfiltrate the content of the current document (CSS). - if (Frame* f = frame()) - if (!f->loader().mixedContentChecker().canRunInsecureContent(m_document->securityOrigin(), url)) + if (Frame* frame = this->frame()) { + if (!frame->loader().mixedContentChecker().canRunInsecureContent(m_document->securityOrigin(), url)) return false; + Frame& top = frame->tree().top(); + if (&top != frame && !top.loader().mixedContentChecker().canRunInsecureContent(top.document()->securityOrigin(), url)) + return false; + } break; #if ENABLE(VIDEO_TRACK) case CachedResource::TextTrackResource: #endif + case CachedResource::MediaResource: case CachedResource::RawResource: case CachedResource::ImageResource: +#if ENABLE(SVG_FONTS) + case CachedResource::SVGFontResource: +#endif case CachedResource::FontResource: { // These resources can corrupt only the frame's pixels. - if (Frame* f = frame()) { - Frame& topFrame = f->tree().top(); - if (!topFrame.loader().mixedContentChecker().canDisplayInsecureContent(topFrame.document()->securityOrigin(), url)) + if (Frame* frame = this->frame()) { + if (!frame->loader().mixedContentChecker().canDisplayInsecureContent(m_document->securityOrigin(), contentTypeFromResourceType(type), url, MixedContentChecker::AlwaysDisplayInNonStrictMode::Yes)) + return false; + Frame& topFrame = frame->tree().top(); + if (!topFrame.loader().mixedContentChecker().canDisplayInsecureContent(topFrame.document()->securityOrigin(), contentTypeFromResourceType(type), url)) return false; } break; @@ -289,306 +380,602 @@ bool CachedResourceLoader::checkInsecureContent(CachedResource::Type type, const return true; } -bool CachedResourceLoader::canRequest(CachedResource::Type type, const URL& url, const ResourceLoaderOptions& options, bool forPreload) +bool CachedResourceLoader::allowedByContentSecurityPolicy(CachedResource::Type type, const URL& url, const ResourceLoaderOptions& options, ContentSecurityPolicy::RedirectResponseReceived redirectResponseReceived) const { - if (document() && !document()->securityOrigin()->canDisplay(url)) { - if (!forPreload) - FrameLoader::reportLocalLoadFailed(frame(), url.stringCenterEllipsizedToLength()); - LOG(ResourceLoading, "CachedResourceLoader::requestResource URL was not allowed by SecurityOrigin::canDisplay"); - return 0; - } - - // FIXME: Convert this to check the isolated world's Content Security Policy once webkit.org/b/104520 is solved. - bool shouldBypassMainWorldContentSecurityPolicy = (frame() && frame()->script().shouldBypassMainWorldContentSecurityPolicy()); + if (options.contentSecurityPolicyImposition == ContentSecurityPolicyImposition::SkipPolicyCheck) + return true; - // 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::MainResource: - case CachedResource::ImageResource: - case CachedResource::CSSStyleSheet: - case CachedResource::Script: - case CachedResource::FontResource: - case CachedResource::RawResource: -#if ENABLE(LINK_PREFETCH) - case CachedResource::LinkPrefetch: - case CachedResource::LinkSubresource: -#endif -#if ENABLE(VIDEO_TRACK) - case CachedResource::TextTrackResource: -#endif - if (options.requestOriginPolicy == RestrictToSameOrigin && !m_document->securityOrigin()->canRequest(url)) { - printAccessDeniedMessage(url); - return false; - } - break; -#if ENABLE(SVG) - case CachedResource::SVGDocumentResource: -#endif -#if ENABLE(XSLT) - case CachedResource::XSLStyleSheet: - if (!m_document->securityOrigin()->canRequest(url)) { - printAccessDeniedMessage(url); - return false; - } -#endif - break; - } + ASSERT(m_document); + ASSERT(m_document->contentSecurityPolicy()); switch (type) { #if ENABLE(XSLT) case CachedResource::XSLStyleSheet: - if (!shouldBypassMainWorldContentSecurityPolicy && !m_document->contentSecurityPolicy()->allowScriptFromSource(url)) - return false; - break; #endif case CachedResource::Script: - if (!shouldBypassMainWorldContentSecurityPolicy && !m_document->contentSecurityPolicy()->allowScriptFromSource(url)) + if (!m_document->contentSecurityPolicy()->allowScriptFromSource(url, redirectResponseReceived)) return false; - - if (frame()) { - if (!frame()->loader().client().allowScriptFromSource(frame()->settings().isScriptEnabled(), url)) - return false; - } break; case CachedResource::CSSStyleSheet: - if (!shouldBypassMainWorldContentSecurityPolicy && !m_document->contentSecurityPolicy()->allowStyleFromSource(url)) + if (!m_document->contentSecurityPolicy()->allowStyleFromSource(url, redirectResponseReceived)) return false; break; -#if ENABLE(SVG) case CachedResource::SVGDocumentResource: -#endif case CachedResource::ImageResource: - if (!shouldBypassMainWorldContentSecurityPolicy && !m_document->contentSecurityPolicy()->allowImageFromSource(url)) - return false; - break; - case CachedResource::FontResource: { - if (!shouldBypassMainWorldContentSecurityPolicy && !m_document->contentSecurityPolicy()->allowFontFromSource(url)) + if (!m_document->contentSecurityPolicy()->allowImageFromSource(url, redirectResponseReceived)) return false; break; - } - case CachedResource::MainResource: - case CachedResource::RawResource: -#if ENABLE(LINK_PREFETCH) - case CachedResource::LinkPrefetch: - case CachedResource::LinkSubresource: +#if ENABLE(SVG_FONTS) + case CachedResource::SVGFontResource: #endif + case CachedResource::FontResource: + if (!m_document->contentSecurityPolicy()->allowFontFromSource(url, redirectResponseReceived)) + return false; break; + case CachedResource::MediaResource: #if ENABLE(VIDEO_TRACK) case CachedResource::TextTrackResource: - if (!shouldBypassMainWorldContentSecurityPolicy && !m_document->contentSecurityPolicy()->allowMediaFromSource(url)) +#endif + if (!m_document->contentSecurityPolicy()->allowMediaFromSource(url, redirectResponseReceived)) return false; break; -#endif + case CachedResource::RawResource: + return true; + default: + ASSERT_NOT_REACHED(); } + return true; +} + +static inline bool isSameOriginDataURL(const URL& url, const ResourceLoaderOptions& options) +{ + // FIXME: Remove same-origin data URL flag since it was removed from fetch spec (https://github.com/whatwg/fetch/issues/381). + return url.protocolIsData() && options.sameOriginDataURLFlag == SameOriginDataURLFlag::Set; +} + +bool CachedResourceLoader::canRequest(CachedResource::Type type, const URL& url, const CachedResourceRequest& request, ForPreload forPreload) +{ + auto& options = request.options(); + + if (document() && !document()->securityOrigin().canDisplay(url)) { + if (forPreload == ForPreload::No) + FrameLoader::reportLocalLoadFailed(frame(), url.stringCenterEllipsizedToLength()); + LOG(ResourceLoading, "CachedResourceLoader::requestResource URL was not allowed by SecurityOrigin::canDisplay"); + return false; + } + + if (options.mode == FetchOptions::Mode::SameOrigin && !m_document->securityOrigin().canRequest(url) && !isSameOriginDataURL(url, options)) { + printAccessDeniedMessage(url); + return false; + } + + if (!allowedByContentSecurityPolicy(type, url, options, ContentSecurityPolicy::RedirectResponseReceived::No)) + return false; + // SVG Images have unique security rules that prevent all subresource requests except for data urls. if (type != CachedResource::MainResource && frame() && frame()->page()) { if (frame()->page()->chrome().client().isSVGImageChromeClient() && !url.protocolIsData()) return false; } - // Last of all, check for insecure content. We do this last so that when - // folks block insecure content with a CSP policy, they don't get a warning. + // Last of all, check for insecure content. We do this last so that when folks block insecure content with a CSP policy, they don't get a warning. // They'll still get a warning in the console about CSP blocking the load. - // FIXME: Should we consider forPreload here? + // FIXME: Should we consider whether the request is for preload here? + if (!checkInsecureContent(type, url)) + return false; + + return true; +} + +// FIXME: Should we find a way to know whether the redirection is for a preload request like we do for CachedResourceLoader::canRequest? +bool CachedResourceLoader::canRequestAfterRedirection(CachedResource::Type type, const URL& url, const ResourceLoaderOptions& options) const +{ + if (document() && !document()->securityOrigin().canDisplay(url)) { + FrameLoader::reportLocalLoadFailed(frame(), url.stringCenterEllipsizedToLength()); + LOG(ResourceLoading, "CachedResourceLoader::requestResource URL was not allowed by SecurityOrigin::canDisplay"); + return false; + } + + // FIXME: According to https://fetch.spec.whatwg.org/#http-redirect-fetch, we should check that the URL is HTTP(s) except if in navigation mode. + // But we currently allow at least data URLs to be loaded. + + if (options.mode == FetchOptions::Mode::SameOrigin && !m_document->securityOrigin().canRequest(url)) { + printAccessDeniedMessage(url); + return false; + } + + if (!allowedByContentSecurityPolicy(type, url, options, ContentSecurityPolicy::RedirectResponseReceived::Yes)) + return false; + + // Last of all, check for insecure content. We do this last so that when folks block insecure content with a CSP policy, they don't get a warning. + // They'll still get a warning in the console about CSP blocking the load. if (!checkInsecureContent(type, url)) return false; return true; } -bool CachedResourceLoader::shouldContinueAfterNotifyingLoadedFromMemoryCache(CachedResource* resource) +bool CachedResourceLoader::updateRequestAfterRedirection(CachedResource::Type type, ResourceRequest& request, const ResourceLoaderOptions& options) +{ + ASSERT(m_documentLoader); + if (auto* document = m_documentLoader->cachedResourceLoader().document()) + upgradeInsecureResourceRequestIfNeeded(request, *document); + + // FIXME: We might want to align the checks done here with the ones done in CachedResourceLoader::requestResource, content extensions blocking in particular. + + return canRequestAfterRedirection(type, request.url(), options); +} + +bool CachedResourceLoader::canRequestInContentDispositionAttachmentSandbox(CachedResource::Type type, const URL& url) const +{ + Document* document; + + // FIXME: Do we want to expand this to all resource types that the mixed content checker would consider active content? + switch (type) { + case CachedResource::MainResource: + if (auto ownerElement = frame() ? frame()->ownerElement() : nullptr) { + document = &ownerElement->document(); + break; + } + return true; + case CachedResource::CSSStyleSheet: + document = m_document; + break; + default: + return true; + } + + if (!document->shouldEnforceContentDispositionAttachmentSandbox() || document->securityOrigin().canRequest(url)) + return true; + + String message = "Unsafe attempt to load URL " + url.stringCenterEllipsizedToLength() + " from document with Content-Disposition: attachment at URL " + document->url().stringCenterEllipsizedToLength() + "."; + document->addConsoleMessage(MessageSource::Security, MessageLevel::Error, message); + return false; +} + +bool CachedResourceLoader::shouldContinueAfterNotifyingLoadedFromMemoryCache(const CachedResourceRequest& request, CachedResource* resource) { if (!resource || !frame() || resource->status() != CachedResource::Cached) return true; - ResourceRequest newRequest; + ResourceRequest newRequest = ResourceRequest(resource->url()); + newRequest.setInitiatorIdentifier(request.resourceRequest().initiatorIdentifier()); + if (request.resourceRequest().hiddenFromInspector()) + newRequest.setHiddenFromInspector(true); frame()->loader().loadedResourceFromMemoryCache(resource, newRequest); - + // FIXME <http://webkit.org/b/113251>: If the delegate modifies the request's // URL, it is no longer appropriate to use this CachedResource. return !newRequest.isNull(); } -CachedResourceHandle<CachedResource> CachedResourceLoader::requestResource(CachedResource::Type type, CachedResourceRequest& request) +bool CachedResourceLoader::shouldUpdateCachedResourceWithCurrentRequest(const CachedResource& resource, const CachedResourceRequest& request) +{ + // WebKit is not supporting CORS for fonts (https://bugs.webkit.org/show_bug.cgi?id=86817), no need to update the resource before reusing it. + if (resource.type() == CachedResource::Type::FontResource) + return false; + +#if ENABLE(SVG_FONTS) + if (resource.type() == CachedResource::Type::SVGFontResource) + return false; +#endif + +#if ENABLE(XSLT) + // Load is same-origin, we do not check for CORS. + if (resource.type() == CachedResource::XSLStyleSheet) + return false; +#endif + + // FIXME: We should enable resource reuse for these resource types + switch (resource.type()) { + case CachedResource::SVGDocumentResource: + return false; + case CachedResource::MainResource: + return false; +#if ENABLE(LINK_PREFETCH) + case CachedResource::LinkPrefetch: + return false; + case CachedResource::LinkSubresource: + return false; +#endif + default: + break; + } + + if (resource.options().mode != request.options().mode || !originsMatch(request.origin(), resource.origin())) + return true; + + if (resource.options().redirect != request.options().redirect && resource.hasRedirections()) + return true; + + return false; +} + +static inline bool isResourceSuitableForDirectReuse(const CachedResource& resource, const CachedResourceRequest& request) +{ + // FIXME: For being loaded requests, the response tainting may not be correctly computed if the fetch mode is not the same. + // Even if the fetch mode is the same, we are not sure that the resource can be reused (Vary: Origin header for instance). + // We should find a way to improve this. + if (resource.status() != CachedResource::Cached) + return false; + + // If the cached resource has not followed redirections, it is incomplete and we should not use it. + // Let's make sure the memory cache has no such resource. + ASSERT(resource.response().type() != ResourceResponse::Type::Opaqueredirect); + + // We could support redirect modes other than Follow in case of a redirected resource. + // This case is rare and is not worth optimizing currently. + if (request.options().redirect != FetchOptions::Redirect::Follow && resource.hasRedirections()) + return false; + + // FIXME: Implement reuse of cached raw resources. + if (resource.type() == CachedResource::Type::RawResource || resource.type() == CachedResource::Type::MediaResource) + return false; + + return true; +} + +CachedResourceHandle<CachedResource> CachedResourceLoader::updateCachedResourceWithCurrentRequest(const CachedResource& resource, CachedResourceRequest&& request) +{ + if (!isResourceSuitableForDirectReuse(resource, request)) { + request.setCachingPolicy(CachingPolicy::DisallowCaching); + return loadResource(resource.type(), WTFMove(request)); + } + + auto resourceHandle = createResource(resource.type(), WTFMove(request), sessionID()); + resourceHandle->loadFrom(resource); + return resourceHandle; +} + +static inline void logMemoryCacheResourceRequest(Frame* frame, const String& key, const String& description) { + if (!frame || !frame->page()) + return; + frame->page()->diagnosticLoggingClient().logDiagnosticMessage(key, description, ShouldSample::Yes); +} + +void CachedResourceLoader::prepareFetch(CachedResource::Type type, CachedResourceRequest& request) +{ + // Implementing step 1 to 7 of https://fetch.spec.whatwg.org/#fetching + + if (!request.origin() && document()) + request.setOrigin(document()->securityOrigin()); + + request.setAcceptHeaderIfNone(type); + + // Accept-Language value is handled in underlying port-specific code. + // FIXME: Decide whether to support client hints +} + +void CachedResourceLoader::updateHTTPRequestHeaders(CachedResource::Type type, CachedResourceRequest& request) +{ + // Implementing steps 7 to 12 of https://fetch.spec.whatwg.org/#http-network-or-cache-fetch + + // FIXME: We should reconcile handling of MainResource with other resources. + if (type != CachedResource::Type::MainResource) { + // In some cases we may try to load resources in frameless documents. Such loads always fail. + // FIXME: We shouldn't need to do the check on frame. + if (auto* frame = this->frame()) + request.updateReferrerOriginAndUserAgentHeaders(frame->loader(), document() ? document()->referrerPolicy() : ReferrerPolicy::Default); + } + + request.updateAccordingCacheMode(); +} + +CachedResourceHandle<CachedResource> CachedResourceLoader::requestResource(CachedResource::Type type, CachedResourceRequest&& request, ForPreload forPreload, DeferOption defer) +{ + if (Document* document = this->document()) + request.upgradeInsecureRequestIfNeeded(*document); + URL url = request.resourceRequest().url(); - - LOG(ResourceLoading, "CachedResourceLoader::requestResource '%s', charset '%s', priority=%d, forPreload=%u", url.stringCenterEllipsizedToLength().latin1().data(), request.charset().latin1().data(), request.priority(), request.forPreload()); - - // If only the fragment identifiers differ, it is the same resource. - url = MemoryCache::removeFragmentIdentifierIfNeeded(url); - if (!url.isValid()) - return 0; + LOG(ResourceLoading, "CachedResourceLoader::requestResource '%s', charset '%s', priority=%d, forPreload=%u", url.stringCenterEllipsizedToLength().latin1().data(), request.charset().latin1().data(), request.priority() ? static_cast<int>(request.priority().value()) : -1, forPreload == ForPreload::Yes); - if (!canRequest(type, url, request.options(), request.forPreload())) - return 0; + if (!url.isValid()) { + RELEASE_LOG_IF_ALLOWED("requestResource: URL is invalid (frame = %p)", frame()); + return nullptr; + } - if (Frame* f = frame()) - f->loader().client().dispatchWillRequestResource(&request); + prepareFetch(type, request); + + // We are passing url as well as request, as request url may contain a fragment identifier. + if (!canRequest(type, url, request, forPreload)) { + RELEASE_LOG_IF_ALLOWED("requestResource: Not allowed to request resource (frame = %p)", frame()); + return nullptr; + } - if (memoryCache()->disabled()) { +#if ENABLE(CONTENT_EXTENSIONS) + if (frame() && frame()->mainFrame().page() && m_documentLoader) { + const auto& resourceRequest = request.resourceRequest(); + auto blockedStatus = frame()->mainFrame().page()->userContentProvider().processContentExtensionRulesForLoad(resourceRequest.url(), toResourceType(type), *m_documentLoader); + request.applyBlockedStatus(blockedStatus); + if (blockedStatus.blockedLoad) { + RELEASE_LOG_IF_ALLOWED("requestResource: Resource blocked by content blocker (frame = %p)", frame()); + if (type == CachedResource::Type::MainResource) { + auto resource = createResource(type, WTFMove(request), sessionID()); + ASSERT(resource); + resource->error(CachedResource::Status::LoadError); + resource->setResourceError(ResourceError(ContentExtensions::WebKitContentBlockerDomain, 0, resourceRequest.url(), WEB_UI_STRING("The URL was blocked by a content blocker", "WebKitErrorBlockedByContentBlocker description"))); + return resource; + } + return nullptr; + } + if (blockedStatus.madeHTTPS + && type == CachedResource::Type::MainResource + && m_documentLoader->isLoadingMainResource()) { + // This is to make sure the correct 'new' URL shows in the location bar. + m_documentLoader->frameLoader()->client().dispatchDidChangeProvisionalURL(); + } + url = request.resourceRequest().url(); // The content extension could have changed it from http to https. + url = MemoryCache::removeFragmentIdentifierIfNeeded(url); // Might need to remove fragment identifier again. + } +#endif + +#if ENABLE(WEB_TIMING) + LoadTiming loadTiming; + loadTiming.markStartTimeAndFetchStart(); + InitiatorContext initiatorContext = request.options().initiatorContext; +#endif + + if (request.resourceRequest().url().protocolIsInHTTPFamily()) + updateHTTPRequestHeaders(type, request); + + auto& memoryCache = MemoryCache::singleton(); + if (request.allowsCaching() && memoryCache.disabled()) { DocumentResourceMap::iterator it = m_documentResources.find(url.string()); if (it != m_documentResources.end()) { - it->value->setOwningCachedResourceLoader(0); + it->value->setOwningCachedResourceLoader(nullptr); m_documentResources.remove(it); } } // See if we can use an existing resource from the cache. CachedResourceHandle<CachedResource> resource; -#if ENABLE(CACHE_PARTITIONING) if (document()) - request.mutableResourceRequest().setCachePartition(document()->topOrigin()->cachePartition()); -#endif + request.setDomainForCachePartition(*document()); + + if (request.allowsCaching()) + resource = memoryCache.resourceForRequest(request.resourceRequest(), sessionID()); + + if (resource && request.isLinkPreload() && !resource->isLinkPreload()) + resource->setLinkPreload(); - resource = memoryCache()->resourceForRequest(request.resourceRequest()); + logMemoryCacheResourceRequest(frame(), DiagnosticLoggingKeys::memoryCacheUsageKey(), resource ? DiagnosticLoggingKeys::inMemoryCacheKey() : DiagnosticLoggingKeys::notInMemoryCacheKey()); - const RevalidationPolicy policy = determineRevalidationPolicy(type, request.mutableResourceRequest(), request.forPreload(), resource.get(), request.defer()); + RevalidationPolicy policy = determineRevalidationPolicy(type, request, resource.get(), forPreload, defer); switch (policy) { case Reload: - memoryCache()->remove(resource.get()); + memoryCache.remove(*resource); FALLTHROUGH; case Load: - resource = loadResource(type, request, request.charset()); + if (resource) + logMemoryCacheResourceRequest(frame(), DiagnosticLoggingKeys::memoryCacheEntryDecisionKey(), DiagnosticLoggingKeys::unusedKey()); + resource = loadResource(type, WTFMove(request)); break; case Revalidate: - resource = revalidateResource(request, resource.get()); + if (resource) + logMemoryCacheResourceRequest(frame(), DiagnosticLoggingKeys::memoryCacheEntryDecisionKey(), DiagnosticLoggingKeys::revalidatingKey()); + resource = revalidateResource(WTFMove(request), *resource); break; case Use: - if (!shouldContinueAfterNotifyingLoadedFromMemoryCache(resource.get())) - return 0; - memoryCache()->resourceAccessed(resource.get()); + ASSERT(resource); + if (shouldUpdateCachedResourceWithCurrentRequest(*resource, request)) { + resource = updateCachedResourceWithCurrentRequest(*resource, WTFMove(request)); + if (resource->status() != CachedResource::Status::Cached) + policy = Load; + } else { + if (!shouldContinueAfterNotifyingLoadedFromMemoryCache(request, resource.get())) + return nullptr; + logMemoryCacheResourceRequest(frame(), DiagnosticLoggingKeys::memoryCacheEntryDecisionKey(), DiagnosticLoggingKeys::usedKey()); + memoryCache.resourceAccessed(*resource); +#if ENABLE(WEB_TIMING) + loadTiming.setResponseEnd(MonotonicTime::now()); + + if (RuntimeEnabledFeatures::sharedFeatures().resourceTimingEnabled()) { + ResourceTiming resourceTiming = ResourceTiming::fromCache(url, request.initiatorName(), loadTiming); + if (initiatorContext == InitiatorContext::Worker) { + ASSERT(is<CachedRawResource>(resource.get())); + downcast<CachedRawResource>(resource.get())->finishedTimingForWorkerLoad(WTFMove(resourceTiming)); + } else if (document()) { + ASSERT(initiatorContext == InitiatorContext::Document); + m_resourceTimingInfo.storeResourceTimingInitiatorInformation(resource, request.initiatorName(), frame()); + m_resourceTimingInfo.addResourceTiming(*resource.get(), *document(), WTFMove(resourceTiming)); + } + } +#endif + if (forPreload == ForPreload::No) + resource->setLoadPriority(request.priority()); + } break; } if (!resource) - return 0; + return nullptr; - if (!request.forPreload() || policy != Use) - resource->setLoadPriority(request.priority()); + if (forPreload == ForPreload::No && resource->loader() && resource->resourceRequest().ignoreForRequestCount()) { + resource->resourceRequest().setIgnoreForRequestCount(false); + incrementRequestCount(*resource); + } - if ((policy != Use || resource->stillNeedsLoad()) && CachedResourceRequest::NoDefer == request.defer()) { - resource->load(this, request.options()); + if ((policy != Use || resource->stillNeedsLoad()) && defer == DeferOption::NoDefer) { + resource->load(*this); // We don't support immediate loads, but we do support immediate failure. if (resource->errorOccurred()) { - if (resource->inCache()) - memoryCache()->remove(resource.get()); - return 0; + if (resource->allowsCaching() && resource->inCache()) + memoryCache.remove(*resource); + return nullptr; } } - if (!request.resourceRequest().url().protocolIsData()) - m_validatedURLs.add(request.resourceRequest().url()); + if (document() && !document()->loadEventFinished() && !resource->resourceRequest().url().protocolIsData()) + m_validatedURLs.add(resource->resourceRequest().url()); ASSERT(resource->url() == url.string()); m_documentResources.set(resource->url(), resource); return resource; } -CachedResourceHandle<CachedResource> CachedResourceLoader::revalidateResource(const CachedResourceRequest& request, CachedResource* resource) +void CachedResourceLoader::documentDidFinishLoadEvent() { - ASSERT(resource); - ASSERT(resource->inCache()); - ASSERT(!memoryCache()->disabled()); - ASSERT(resource->canUseCacheValidator()); - ASSERT(!resource->resourceToRevalidate()); - - // Copy the URL out of the resource to be revalidated in case it gets deleted by the remove() call below. - String url = resource->url(); - CachedResourceHandle<CachedResource> newResource = createResource(resource->type(), resource->resourceRequest(), resource->encoding()); - - LOG(ResourceLoading, "Resource %p created to revalidate %p", newResource.get(), resource); - newResource->setResourceToRevalidate(resource); - - memoryCache()->remove(resource); - memoryCache()->add(newResource.get()); -#if ENABLE(RESOURCE_TIMING) - storeResourceTimingInitiatorInformation(resource, request); -#else - UNUSED_PARAM(request); + m_validatedURLs.clear(); +} + +CachedResourceHandle<CachedResource> CachedResourceLoader::revalidateResource(CachedResourceRequest&& request, CachedResource& resource) +{ + ASSERT(resource.inCache()); + auto& memoryCache = MemoryCache::singleton(); + ASSERT(!memoryCache.disabled()); + ASSERT(resource.canUseCacheValidator()); + ASSERT(!resource.resourceToRevalidate()); + ASSERT(resource.sessionID() == sessionID()); + ASSERT(resource.allowsCaching()); + + CachedResourceHandle<CachedResource> newResource = createResource(resource.type(), WTFMove(request), resource.sessionID()); + + LOG(ResourceLoading, "Resource %p created to revalidate %p", newResource.get(), &resource); + newResource->setResourceToRevalidate(&resource); + + memoryCache.remove(resource); + memoryCache.add(*newResource); +#if ENABLE(WEB_TIMING) + if (RuntimeEnabledFeatures::sharedFeatures().resourceTimingEnabled()) + m_resourceTimingInfo.storeResourceTimingInitiatorInformation(newResource, newResource->initiatorName(), frame()); #endif return newResource; } -CachedResourceHandle<CachedResource> CachedResourceLoader::loadResource(CachedResource::Type type, CachedResourceRequest& request, const String& charset) +CachedResourceHandle<CachedResource> CachedResourceLoader::loadResource(CachedResource::Type type, CachedResourceRequest&& request) { - ASSERT(!memoryCache()->resourceForRequest(request.resourceRequest())); + auto& memoryCache = MemoryCache::singleton(); + ASSERT(!request.allowsCaching() || !memoryCache.resourceForRequest(request.resourceRequest(), sessionID()) + || request.resourceRequest().cachePolicy() == DoNotUseAnyCache || request.resourceRequest().cachePolicy() == ReloadIgnoringCacheData || request.resourceRequest().cachePolicy() == RefreshAnyCacheData); LOG(ResourceLoading, "Loading CachedResource for '%s'.", request.resourceRequest().url().stringCenterEllipsizedToLength().latin1().data()); - CachedResourceHandle<CachedResource> resource = createResource(type, request.mutableResourceRequest(), charset); + CachedResourceHandle<CachedResource> resource = createResource(type, WTFMove(request), sessionID()); - if (!memoryCache()->add(resource.get())) + if (resource->allowsCaching() && !memoryCache.add(*resource)) resource->setOwningCachedResourceLoader(this); -#if ENABLE(RESOURCE_TIMING) - storeResourceTimingInitiatorInformation(resource, request); +#if ENABLE(WEB_TIMING) + if (RuntimeEnabledFeatures::sharedFeatures().resourceTimingEnabled()) + m_resourceTimingInfo.storeResourceTimingInitiatorInformation(resource, resource->initiatorName(), frame()); #endif return resource; } -#if ENABLE(RESOURCE_TIMING) -void CachedResourceLoader::storeResourceTimingInitiatorInformation(const CachedResourceHandle<CachedResource>& resource, const CachedResourceRequest& request) +static void logRevalidation(const String& reason, DiagnosticLoggingClient& logClient) { - if (resource->type() == CachedResource::MainResource) { - // <iframe>s should report the initial navigation requested by the parent document, but not subsequent navigations. - if (frame()->ownerElement() && m_documentLoader->frameLoader()->stateMachine()->committingFirstRealLoad()) { - InitiatorInfo info = { frame()->ownerElement()->localName(), monotonicallyIncreasingTime() }; - m_initiatorMap.add(resource.get(), info); - } - } else { - InitiatorInfo info = { request.initiatorName(), monotonicallyIncreasingTime() }; - m_initiatorMap.add(resource.get(), info); + logClient.logDiagnosticMessage(DiagnosticLoggingKeys::cachedResourceRevalidationReasonKey(), reason, ShouldSample::Yes); +} + +static void logResourceRevalidationDecision(CachedResource::RevalidationDecision reason, const Frame* frame) +{ + if (!frame || !frame->page()) + return; + auto& logClient = frame->page()->diagnosticLoggingClient(); + switch (reason) { + case CachedResource::RevalidationDecision::No: + break; + case CachedResource::RevalidationDecision::YesDueToExpired: + logRevalidation(DiagnosticLoggingKeys::isExpiredKey(), logClient); + break; + case CachedResource::RevalidationDecision::YesDueToNoStore: + logRevalidation(DiagnosticLoggingKeys::noStoreKey(), logClient); + break; + case CachedResource::RevalidationDecision::YesDueToNoCache: + logRevalidation(DiagnosticLoggingKeys::noCacheKey(), logClient); + break; + case CachedResource::RevalidationDecision::YesDueToCachePolicy: + logRevalidation(DiagnosticLoggingKeys::reloadKey(), logClient); + break; } } -#endif // ENABLE(RESOURCE_TIMING) -CachedResourceLoader::RevalidationPolicy CachedResourceLoader::determineRevalidationPolicy(CachedResource::Type type, ResourceRequest& request, bool forPreload, CachedResource* existingResource, CachedResourceRequest::DeferOption defer) const +CachedResourceLoader::RevalidationPolicy CachedResourceLoader::determineRevalidationPolicy(CachedResource::Type type, CachedResourceRequest& cachedResourceRequest, CachedResource* existingResource, ForPreload forPreload, DeferOption defer) const { + auto& request = cachedResourceRequest.resourceRequest(); + if (!existingResource) return Load; + if (request.cachePolicy() == DoNotUseAnyCache || request.cachePolicy() == ReloadIgnoringCacheData) + return Load; + + if (request.cachePolicy() == RefreshAnyCacheData) + return Reload; + // We already have a preload going for this URL. - if (forPreload && existingResource->isPreloaded()) + if (forPreload == ForPreload::Yes && 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."); + logMemoryCacheResourceRequest(frame(), DiagnosticLoggingKeys::inMemoryCacheKey(), DiagnosticLoggingKeys::unusedReasonTypeMismatchKey()); return Reload; } - if (!existingResource->canReuse(request)) + if (!existingResource->varyHeaderValuesMatch(request)) return Reload; + auto* textDecoder = existingResource->textResourceDecoder(); + if (textDecoder && !textDecoder->hasEqualEncodingForCharset(cachedResourceRequest.charset())) + return Reload; + + // FIXME: We should use the same cache policy for all resource types. The raw resource policy is overly strict + // while the normal subresource policy is too loose. + if (existingResource->isMainOrMediaOrRawResource() && frame()) { + bool strictPolicyDisabled = frame()->loader().isStrictRawResourceValidationPolicyDisabledForTesting(); + bool canReuseRawResource = strictPolicyDisabled || downcast<CachedRawResource>(*existingResource).canReuse(request); + if (!canReuseRawResource) + return Reload; + } + // Conditional requests should have failed canReuse check. ASSERT(!request.isConditional()); - // Do not load from cache if images are not enabled. The load for this image will be blocked - // in CachedImage::load. - if (CachedResourceRequest::DeferredByClient == defer) + // Do not load from cache if images are not enabled. The load for this image will be blocked in CachedImage::load. + if (defer == DeferOption::DeferredByClient) return Reload; - - // Don't reload resources while pasting. - if (m_allowStaleResources) + + // Don't reload resources while pasting or if cache mode allows stale resources. + if (m_allowStaleResources || cachedResourceRequest.options().cache == FetchOptions::Cache::ForceCache || cachedResourceRequest.options().cache == FetchOptions::Cache::OnlyIfCached) return Use; - - // Alwaus use preloads. + + ASSERT(cachedResourceRequest.options().cache == FetchOptions::Cache::Default || cachedResourceRequest.options().cache == FetchOptions::Cache::NoCache); + + // Always use preloads. if (existingResource->isPreloaded()) return Use; - - // CachePolicyHistoryBuffer uses the cache no matter what. - if (cachePolicy(type) == CachePolicyHistoryBuffer) + + // We can find resources that are being validated from cache only when validation is just successfully completing. + if (existingResource->validationCompleting()) return Use; + ASSERT(!existingResource->validationInProgress()); + + // Validate the redirect chain. + bool cachePolicyIsHistoryBuffer = cachePolicy(type) == CachePolicyHistoryBuffer; + if (!existingResource->redirectChainAllowsReuse(cachePolicyIsHistoryBuffer ? ReuseExpiredRedirection : DoNotReuseExpiredRedirection)) { + LOG(ResourceLoading, "CachedResourceLoader::determineRevalidationPolicy reloading due to not cached or expired redirections."); + logMemoryCacheResourceRequest(frame(), DiagnosticLoggingKeys::inMemoryCacheKey(), DiagnosticLoggingKeys::unusedReasonRedirectChainKey()); + return Reload; + } + + // CachePolicyHistoryBuffer uses the cache except if this is a main resource with "cache-control: no-store". + if (cachePolicyIsHistoryBuffer) { + // FIXME: Ignoring "cache-control: no-cache" for sub-resources on history navigation but not the main + // resource is inconsistent. We should probably harmonize this. + if (!existingResource->response().cacheControlContainsNoStore() || type != CachedResource::MainResource) + return Use; + } // Don't reuse resources with Cache-control: no-store. if (existingResource->response().cacheControlContainsNoStore()) { LOG(ResourceLoading, "CachedResourceLoader::determineRevalidationPolicy reloading due to Cache-control: no-store."); + logMemoryCacheResourceRequest(frame(), DiagnosticLoggingKeys::inMemoryCacheKey(), DiagnosticLoggingKeys::unusedReasonNoStoreKey()); return Reload; } @@ -600,6 +987,7 @@ CachedResourceLoader::RevalidationPolicy CachedResourceLoader::determineRevalida // client's requests are made without CORS and some with. if (existingResource->resourceRequest().allowCookies() != request.allowCookies()) { LOG(ResourceLoading, "CachedResourceLoader::determineRevalidationPolicy reloading due to difference in credentials settings."); + logMemoryCacheResourceRequest(frame(), DiagnosticLoggingKeys::inMemoryCacheKey(), DiagnosticLoggingKeys::unusedReasonCredentialSettingsKey()); return Reload; } @@ -610,27 +998,40 @@ CachedResourceLoader::RevalidationPolicy CachedResourceLoader::determineRevalida // CachePolicyReload always reloads if (cachePolicy(type) == CachePolicyReload) { LOG(ResourceLoading, "CachedResourceLoader::determineRevalidationPolicy reloading due to CachePolicyReload."); + logMemoryCacheResourceRequest(frame(), DiagnosticLoggingKeys::inMemoryCacheKey(), DiagnosticLoggingKeys::unusedReasonReloadKey()); return Reload; } // 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"); + logMemoryCacheResourceRequest(frame(), DiagnosticLoggingKeys::inMemoryCacheKey(), DiagnosticLoggingKeys::unusedReasonErrorKey()); return Reload; } - - // For resources that are not yet loaded we ignore the cache policy. - if (existingResource->isLoading()) + + if (existingResource->isLoading()) { + // Do not use cached main resources that are still loading because sharing + // loading CachedResources in this case causes issues with regards to cancellation. + // If one of the DocumentLoader clients decides to cancel the load, then the load + // would be cancelled for all other DocumentLoaders as well. + if (type == CachedResource::Type::MainResource) + return Reload; + // For cached subresources that are still loading we ignore the cache policy. return Use; + } + + auto revalidationDecision = existingResource->makeRevalidationDecision(cachePolicy(type)); + logResourceRevalidationDecision(revalidationDecision, frame()); // Check if the cache headers requires us to revalidate (cache expiration for example). - if (existingResource->mustRevalidateDueToCacheHeaders(cachePolicy(type))) { + if (revalidationDecision != CachedResource::RevalidationDecision::No) { // 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."); + LOG(ResourceLoading, "CachedResourceLoader::determineRevalidationPolicy reloading due to missing cache validators."); + logMemoryCacheResourceRequest(frame(), DiagnosticLoggingKeys::inMemoryCacheKey(), DiagnosticLoggingKeys::unusedReasonMustRevalidateNoValidatorKey()); return Reload; } @@ -651,7 +1052,7 @@ void CachedResourceLoader::printAccessDeniedMessage(const URL& url) const else message = "Unsafe attempt to load URL " + url.stringCenterEllipsizedToLength() + " from frame with URL " + m_document->url().stringCenterEllipsizedToLength() + ". Domains, protocols and ports must match.\n"; - frame()->document()->addConsoleMessage(SecurityMessageSource, ErrorMessageLevel, message); + frame()->document()->addConsoleMessage(MessageSource::Security, MessageLevel::Error, message); } void CachedResourceLoader::setAutoLoadImages(bool enable) @@ -680,72 +1081,72 @@ void CachedResourceLoader::setImagesEnabled(bool enable) reloadImagesIfNotDeferred(); } -bool CachedResourceLoader::clientDefersImage(const URL& url) const +bool CachedResourceLoader::clientDefersImage(const URL&) const +{ + return !m_imagesEnabled; +} + +bool CachedResourceLoader::shouldPerformImageLoad(const URL& url) const { - return frame() && !frame()->loader().client().allowImage(m_imagesEnabled, url); + return m_autoLoadImages || url.protocolIsData(); } bool CachedResourceLoader::shouldDeferImageLoad(const URL& url) const { - return clientDefersImage(url) || !m_autoLoadImages; + return clientDefersImage(url) || !shouldPerformImageLoad(url); } void CachedResourceLoader::reloadImagesIfNotDeferred() { - DocumentResourceMap::iterator end = m_documentResources.end(); - for (DocumentResourceMap::iterator it = m_documentResources.begin(); it != end; ++it) { - CachedResource* resource = it->value.get(); - if (resource->type() == CachedResource::ImageResource && resource->stillNeedsLoad() && !clientDefersImage(resource->url())) - const_cast<CachedResource*>(resource)->load(this, defaultCachedResourceOptions()); + for (auto& resource : m_documentResources.values()) { + if (is<CachedImage>(*resource) && resource->stillNeedsLoad() && !clientDefersImage(resource->url())) + downcast<CachedImage>(*resource).load(*this); } } CachePolicy CachedResourceLoader::cachePolicy(CachedResource::Type type) const { - if (!frame()) + Frame* frame = this->frame(); + if (!frame) return CachePolicyVerify; if (type != CachedResource::MainResource) - return frame()->loader().subresourceCachePolicy(); - - if (frame()->loader().loadType() == FrameLoadTypeReloadFromOrigin || frame()->loader().loadType() == FrameLoadTypeReload) + return frame->loader().subresourceCachePolicy(); + + if (Page* page = frame->page()) { + if (page->isResourceCachingDisabled()) + return CachePolicyReload; + } + + switch (frame->loader().loadType()) { + case FrameLoadType::ReloadFromOrigin: + case FrameLoadType::Reload: return CachePolicyReload; - return CachePolicyVerify; + case FrameLoadType::Back: + case FrameLoadType::Forward: + case FrameLoadType::IndexedBackForward: + // Do not revalidate cached main resource on back/forward navigation. + return CachePolicyHistoryBuffer; + default: + return CachePolicyVerify; + } } -void CachedResourceLoader::removeCachedResource(CachedResource* resource) const +void CachedResourceLoader::removeCachedResource(CachedResource& resource) { #ifndef NDEBUG - DocumentResourceMap::iterator it = m_documentResources.find(resource->url()); + DocumentResourceMap::iterator it = m_documentResources.find(resource.url()); if (it != m_documentResources.end()) - ASSERT(it->value.get() == resource); + ASSERT(it->value.get() == &resource); #endif - m_documentResources.remove(resource->url()); + m_documentResources.remove(resource.url()); } -void CachedResourceLoader::loadDone(CachedResource* resource, bool shouldPerformPostLoadActions) +void CachedResourceLoader::loadDone(bool shouldPerformPostLoadActions) { RefPtr<DocumentLoader> protectDocumentLoader(m_documentLoader); RefPtr<Document> protectDocument(m_document); -#if ENABLE(RESOURCE_TIMING) - if (resource && resource->response().isHTTP() && ((!resource->errorOccurred() && !resource->wasCanceled()) || resource->response().httpStatusCode() == 304)) { - HashMap<CachedResource*, InitiatorInfo>::iterator initiatorIt = m_initiatorMap.find(resource); - if (initiatorIt != m_initiatorMap.end()) { - ASSERT(document()); - Document* initiatorDocument = document(); - if (resource->type() == CachedResource::MainResource) - initiatorDocument = document()->parentDocument(); - ASSERT(initiatorDocument); - const InitiatorInfo& info = initiatorIt->value; - initiatorDocument->domWindow()->performance()->addResourceTiming(info.name, initiatorDocument, resource->resourceRequest(), resource->response(), info.startTime, resource->loadFinishTime()); - m_initiatorMap.remove(initiatorIt); - } - } -#else - UNUSED_PARAM(resource); -#endif // ENABLE(RESOURCE_TIMING) - if (frame()) frame()->loader().loadDone(); @@ -762,111 +1163,65 @@ void CachedResourceLoader::loadDone(CachedResource* resource, bool shouldPerform // bookkeeping on CachedResources, so instead pseudo-GC them -- when the // reference count reaches 1, m_documentResources is the only reference, so // remove it from the map. -void CachedResourceLoader::garbageCollectDocumentResourcesTimerFired(Timer<CachedResourceLoader>& timer) -{ - ASSERT_UNUSED(timer, &timer == &m_garbageCollectDocumentResourcesTimer); - garbageCollectDocumentResources(); -} - void CachedResourceLoader::garbageCollectDocumentResources() { typedef Vector<String, 10> StringVector; StringVector resourcesToDelete; - for (DocumentResourceMap::iterator it = m_documentResources.begin(); it != m_documentResources.end(); ++it) { - if (it->value->hasOneHandle()) { - resourcesToDelete.append(it->key); - it->value->setOwningCachedResourceLoader(0); + for (auto& resource : m_documentResources) { + if (resource.value->hasOneHandle()) { + resourcesToDelete.append(resource.key); + resource.value->setOwningCachedResourceLoader(nullptr); } } - for (StringVector::const_iterator it = resourcesToDelete.begin(); it != resourcesToDelete.end(); ++it) - m_documentResources.remove(*it); + for (auto& resource : resourcesToDelete) + m_documentResources.remove(resource); } void CachedResourceLoader::performPostLoadActions() { - checkForPendingPreloads(); - - platformStrategies()->loaderStrategy()->resourceLoadScheduler()->servePendingRequests(); + platformStrategies()->loaderStrategy()->servePendingRequests(); } -void CachedResourceLoader::incrementRequestCount(const CachedResource* res) +void CachedResourceLoader::incrementRequestCount(const CachedResource& resource) { - if (res->ignoreForRequestCount()) + if (resource.ignoreForRequestCount()) return; ++m_requestCount; } -void CachedResourceLoader::decrementRequestCount(const CachedResource* res) +void CachedResourceLoader::decrementRequestCount(const CachedResource& resource) { - if (res->ignoreForRequestCount()) + if (resource.ignoreForRequestCount()) return; --m_requestCount; ASSERT(m_requestCount > -1); } -void CachedResourceLoader::preload(CachedResource::Type type, CachedResourceRequest& request, const String& charset) +CachedResourceHandle<CachedResource> CachedResourceLoader::preload(CachedResource::Type type, CachedResourceRequest&& request) { - // We always preload resources on iOS. See <https://bugs.webkit.org/show_bug.cgi?id=91276>. - // FIXME: We should consider adding a setting to toggle aggressive preloading behavior as opposed - // to making this behavior specific to iOS. -#if !PLATFORM(IOS) - bool hasRendering = m_document->body() && m_document->renderView(); - bool canBlockParser = type == CachedResource::Script || type == CachedResource::CSSStyleSheet; - if (!hasRendering && !canBlockParser) { - // Don't preload subresources that can't block the parser before we have something to draw. - // This helps prevent preloads from delaying first display when bandwidth is limited. - PendingPreload pendingPreload = { type, request, charset }; - m_pendingPreloads.append(pendingPreload); - return; - } -#endif - requestPreload(type, request, charset); -} - -void CachedResourceLoader::checkForPendingPreloads() -{ - if (m_pendingPreloads.isEmpty() || !m_document->body() || !m_document->body()->renderer()) - return; -#if PLATFORM(IOS) - // We always preload resources on iOS. See <https://bugs.webkit.org/show_bug.cgi?id=91276>. - // So, we should never have any pending preloads. - // FIXME: We should look to avoid compiling this code entirely when building for iOS. - ASSERT_NOT_REACHED(); -#endif - while (!m_pendingPreloads.isEmpty()) { - PendingPreload preload = m_pendingPreloads.takeFirst(); - // 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(preload.m_request.resourceRequest().url())) - requestPreload(preload.m_type, preload.m_request, preload.m_charset); - } - m_pendingPreloads.clear(); -} + if (request.charset().isEmpty() && (type == CachedResource::Script || type == CachedResource::CSSStyleSheet)) + request.setCharset(m_document->charset()); -void CachedResourceLoader::requestPreload(CachedResource::Type type, CachedResourceRequest& request, const String& charset) -{ - String encoding; - if (type == CachedResource::Script || type == CachedResource::CSSStyleSheet) - encoding = charset.isEmpty() ? m_document->charset() : charset; - - request.setCharset(encoding); - request.setForPreload(true); - - CachedResourceHandle<CachedResource> resource = requestResource(type, request); + CachedResourceHandle<CachedResource> resource = requestResource(type, WTFMove(request), ForPreload::Yes); if (!resource || (m_preloads && m_preloads->contains(resource.get()))) - return; + return nullptr; + // Fonts need special treatment since just creating the resource doesn't trigger a load. + if (type == CachedResource::FontResource) + downcast<CachedFont>(resource.get())->beginLoadIfNeeded(*this); resource->increasePreloadCount(); if (!m_preloads) - m_preloads = adoptPtr(new ListHashSet<CachedResource*>); + m_preloads = std::make_unique<ListHashSet<CachedResource*>>(); m_preloads->add(resource.get()); #if PRELOAD_DEBUG printf("PRELOADING %s\n", resource->url().latin1().data()); #endif + return resource; } bool CachedResourceLoader::isPreloaded(const String& urlString) const @@ -874,24 +1229,15 @@ bool CachedResourceLoader::isPreloaded(const String& urlString) const const URL& url = m_document->completeURL(urlString); if (m_preloads) { - ListHashSet<CachedResource*>::iterator end = m_preloads->end(); - for (ListHashSet<CachedResource*>::iterator it = m_preloads->begin(); it != end; ++it) { - CachedResource* resource = *it; + for (auto& resource : *m_preloads) { if (resource->url() == url) return true; } } - - Deque<PendingPreload>::const_iterator dequeEnd = m_pendingPreloads.end(); - for (Deque<PendingPreload>::const_iterator it = m_pendingPreloads.begin(); it != dequeEnd; ++it) { - PendingPreload pendingPreload = *it; - if (pendingPreload.m_request.resourceRequest().url() == url) - return true; - } return false; } -void CachedResourceLoader::clearPreloads() +void CachedResourceLoader::clearPreloads(ClearPreloadsMode mode) { #if PRELOAD_DEBUG printPreloadStats(); @@ -899,20 +1245,21 @@ void CachedResourceLoader::clearPreloads() 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(); - bool deleted = res->deleteIfPossible(); - if (!deleted && res->preloadResult() == CachedResource::PreloadNotReferenced) - memoryCache()->remove(res); + std::unique_ptr<ListHashSet<CachedResource*>> remainingLinkPreloads; + for (auto* resource : *m_preloads) { + ASSERT(resource); + if (mode == ClearPreloadsMode::ClearSpeculativePreloads && resource->isLinkPreload()) { + if (!remainingLinkPreloads) + remainingLinkPreloads = std::make_unique<ListHashSet<CachedResource*>>(); + remainingLinkPreloads->add(resource); + continue; + } + resource->decreasePreloadCount(); + bool deleted = resource->deleteIfPossible(); + if (!deleted && resource->preloadResult() == CachedResource::PreloadNotReferenced) + MemoryCache::singleton().remove(*resource); } - m_preloads.clear(); -} - -void CachedResourceLoader::clearPendingPreloads() -{ - m_pendingPreloads.clear(); + m_preloads = WTFMove(remainingLinkPreloads); } #if PRELOAD_DEBUG @@ -924,37 +1271,35 @@ void CachedResourceLoader::printPreloadStats() 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) { + for (auto& resource : m_preloads) { + if (resource->preloadResult() == CachedResource::PreloadNotReferenced) + printf("!! UNREFERENCED PRELOAD %s\n", resource->url().latin1().data()); + else if (resource->preloadResult() == CachedResource::PreloadReferencedWhileComplete) + printf("HIT COMPLETE PRELOAD %s\n", resource->url().latin1().data()); + else if (resource->preloadResult() == CachedResource::PreloadReferencedWhileLoading) + printf("HIT LOADING PRELOAD %s\n", resource->url().latin1().data()); + + if (resource->type() == CachedResource::Script) { scripts++; - if (res->preloadResult() < CachedResource::PreloadReferencedWhileLoading) + if (resource->preloadResult() < CachedResource::PreloadReferencedWhileLoading) scriptMisses++; - } else if (res->type() == CachedResource::CSSStyleSheet) { + } else if (resource->type() == CachedResource::CSSStyleSheet) { stylesheets++; - if (res->preloadResult() < CachedResource::PreloadReferencedWhileLoading) + if (resource->preloadResult() < CachedResource::PreloadReferencedWhileLoading) stylesheetMisses++; } else { images++; - if (res->preloadResult() < CachedResource::PreloadReferencedWhileLoading) + if (resource->preloadResult() < CachedResource::PreloadReferencedWhileLoading) imageMisses++; } - - if (res->errorOccurred()) - memoryCache()->remove(res); - - res->decreasePreloadCount(); + + if (resource->errorOccurred() && resource->preloadResult() == CachedResource::PreloadNotReferenced) + MemoryCache::singleton().remove(resource); + + resource->decreasePreloadCount(); } - m_preloads.clear(); - + m_preloads = nullptr; + if (scripts) printf("SCRIPTS: %d (%d hits, hit rate %d%%)\n", scripts, scripts - scriptMisses, (scripts - scriptMisses) * 100 / scripts); if (stylesheets) @@ -966,8 +1311,13 @@ void CachedResourceLoader::printPreloadStats() const ResourceLoaderOptions& CachedResourceLoader::defaultCachedResourceOptions() { - static ResourceLoaderOptions options(SendCallbacks, SniffContent, BufferData, AllowStoredCredentials, AskClientForAllCredentials, DoSecurityCheck, UseDefaultOriginRestrictionsForType); + static NeverDestroyed<ResourceLoaderOptions> options(SendCallbacks, SniffContent, BufferData, AllowStoredCredentials, ClientCredentialPolicy::MayAskClientForCredentials, FetchOptions::Credentials::Include, DoSecurityCheck, FetchOptions::Mode::NoCors, DoNotIncludeCertificateInfo, ContentSecurityPolicyImposition::DoPolicyCheck, DefersLoadingPolicy::AllowDefersLoading, CachingPolicy::AllowCaching); return options; } +bool CachedResourceLoader::isAlwaysOnLoggingAllowed() const +{ + return m_documentLoader ? m_documentLoader->isAlwaysOnLoggingAllowed() : true; +} + } diff --git a/Source/WebCore/loader/cache/CachedResourceLoader.h b/Source/WebCore/loader/cache/CachedResourceLoader.h index bcc793e0c..23eb1dd6f 100644 --- a/Source/WebCore/loader/cache/CachedResourceLoader.h +++ b/Source/WebCore/loader/cache/CachedResourceLoader.h @@ -23,14 +23,15 @@ pages from the web. It has a memory cache for these objects. */ -#ifndef CachedResourceLoader_h -#define CachedResourceLoader_h +#pragma once #include "CachePolicy.h" #include "CachedResource.h" #include "CachedResourceHandle.h" #include "CachedResourceRequest.h" +#include "ContentSecurityPolicy.h" #include "ResourceLoadPriority.h" +#include "ResourceTimingInformation.h" #include "Timer.h" #include <wtf/Deque.h> #include <wtf/HashMap.h> @@ -68,28 +69,26 @@ friend class ImageLoader; friend class ResourceCacheValidationSuppressor; public: - static PassRef<CachedResourceLoader> create(DocumentLoader* documentLoader) { return adoptRef(*new CachedResourceLoader(documentLoader)); } + static Ref<CachedResourceLoader> create(DocumentLoader* documentLoader) { return adoptRef(*new CachedResourceLoader(documentLoader)); } ~CachedResourceLoader(); - CachedResourceHandle<CachedImage> requestImage(CachedResourceRequest&); - CachedResourceHandle<CachedCSSStyleSheet> requestCSSStyleSheet(CachedResourceRequest&); - CachedResourceHandle<CachedCSSStyleSheet> requestUserCSSStyleSheet(CachedResourceRequest&); - CachedResourceHandle<CachedScript> requestScript(CachedResourceRequest&); - CachedResourceHandle<CachedFont> requestFont(CachedResourceRequest&); - CachedResourceHandle<CachedRawResource> requestRawResource(CachedResourceRequest&); - CachedResourceHandle<CachedRawResource> requestMainResource(CachedResourceRequest&); - -#if ENABLE(SVG) - CachedResourceHandle<CachedSVGDocument> requestSVGDocument(CachedResourceRequest&); -#endif + CachedResourceHandle<CachedImage> requestImage(CachedResourceRequest&&); + CachedResourceHandle<CachedCSSStyleSheet> requestCSSStyleSheet(CachedResourceRequest&&); + CachedResourceHandle<CachedCSSStyleSheet> requestUserCSSStyleSheet(CachedResourceRequest&&); + CachedResourceHandle<CachedScript> requestScript(CachedResourceRequest&&); + CachedResourceHandle<CachedFont> requestFont(CachedResourceRequest&&, bool isSVG); + CachedResourceHandle<CachedRawResource> requestMedia(CachedResourceRequest&&); + CachedResourceHandle<CachedRawResource> requestRawResource(CachedResourceRequest&&); + CachedResourceHandle<CachedRawResource> requestMainResource(CachedResourceRequest&&); + CachedResourceHandle<CachedSVGDocument> requestSVGDocument(CachedResourceRequest&&); #if ENABLE(XSLT) - CachedResourceHandle<CachedXSLStyleSheet> requestXSLStyleSheet(CachedResourceRequest&); + CachedResourceHandle<CachedXSLStyleSheet> requestXSLStyleSheet(CachedResourceRequest&&); #endif #if ENABLE(LINK_PREFETCH) - CachedResourceHandle<CachedResource> requestLinkResource(CachedResource::Type, CachedResourceRequest&); + CachedResourceHandle<CachedResource> requestLinkResource(CachedResource::Type, CachedResourceRequest&&); #endif #if ENABLE(VIDEO_TRACK) - CachedResourceHandle<CachedTextTrack> requestTextTrack(CachedResourceRequest&); + CachedResourceHandle<CachedTextTrack> requestTextTrack(CachedResourceRequest&&); #endif // Logs an access denied message to the console for the specified URL. @@ -97,67 +96,89 @@ public: CachedResource* cachedResource(const String& url) const; CachedResource* cachedResource(const URL& url) const; - + typedef HashMap<String, CachedResourceHandle<CachedResource>> DocumentResourceMap; const DocumentResourceMap& allCachedResources() const { return m_documentResources; } bool autoLoadImages() const { return m_autoLoadImages; } void setAutoLoadImages(bool); + bool imagesEnabled() const { return m_imagesEnabled; } void setImagesEnabled(bool); bool shouldDeferImageLoad(const URL&) const; + bool shouldPerformImageLoad(const URL&) const; CachePolicy cachePolicy(CachedResource::Type) const; Frame* frame() const; // Can be null Document* document() const { return m_document; } // Can be null void setDocument(Document* document) { m_document = document; } - void clearDocumentLoader() { m_documentLoader = 0; } + void clearDocumentLoader() { m_documentLoader = nullptr; } + SessionID sessionID() const; - void removeCachedResource(CachedResource*) const; + void removeCachedResource(CachedResource&); - void loadDone(CachedResource*, bool shouldPerformPostLoadActions = true); + void loadDone(bool shouldPerformPostLoadActions = true); - void garbageCollectDocumentResources(); - - void incrementRequestCount(const CachedResource*); - void decrementRequestCount(const CachedResource*); + WEBCORE_EXPORT void garbageCollectDocumentResources(); + + void incrementRequestCount(const CachedResource&); + void decrementRequestCount(const CachedResource&); int requestCount() const { return m_requestCount; } - bool isPreloaded(const String& urlString) const; - void clearPreloads(); - void clearPendingPreloads(); - void preload(CachedResource::Type, CachedResourceRequest&, const String& charset); - void checkForPendingPreloads(); + WEBCORE_EXPORT bool isPreloaded(const String& urlString) const; + enum class ClearPreloadsMode { ClearSpeculativePreloads, ClearAllPreloads }; + void clearPreloads(ClearPreloadsMode); + CachedResourceHandle<CachedResource> preload(CachedResource::Type, CachedResourceRequest&&); void printPreloadStats(); - bool canRequest(CachedResource::Type, const URL&, const ResourceLoaderOptions&, bool forPreload = false); + + bool updateRequestAfterRedirection(CachedResource::Type, ResourceRequest&, const ResourceLoaderOptions&); static const ResourceLoaderOptions& defaultCachedResourceOptions(); + void documentDidFinishLoadEvent(); + +#if ENABLE(WEB_TIMING) + ResourceTimingInformation& resourceTimingInformation() { return m_resourceTimingInfo; } +#endif + + bool isAlwaysOnLoggingAllowed() const; + private: explicit CachedResourceLoader(DocumentLoader*); - CachedResourceHandle<CachedResource> requestResource(CachedResource::Type, CachedResourceRequest&); - CachedResourceHandle<CachedResource> revalidateResource(const CachedResourceRequest&, CachedResource*); - CachedResourceHandle<CachedResource> loadResource(CachedResource::Type, CachedResourceRequest&, const String& charset); -#if ENABLE(RESOURCE_TIMING) - void storeResourceTimingInitiatorInformation(const CachedResourceHandle<CachedResource>&, const CachedResourceRequest&); -#endif - void requestPreload(CachedResource::Type, CachedResourceRequest&, const String& charset); + enum class ForPreload { Yes, No }; + enum class DeferOption { NoDefer, DeferredByClient }; + + CachedResourceHandle<CachedResource> requestResource(CachedResource::Type, CachedResourceRequest&&, ForPreload = ForPreload::No, DeferOption = DeferOption::NoDefer); + CachedResourceHandle<CachedResource> revalidateResource(CachedResourceRequest&&, CachedResource&); + CachedResourceHandle<CachedResource> loadResource(CachedResource::Type, CachedResourceRequest&&); + + void prepareFetch(CachedResource::Type, CachedResourceRequest&); + void updateHTTPRequestHeaders(CachedResource::Type, CachedResourceRequest&); + void updateReferrerOriginAndUserAgentHeaders(CachedResourceRequest&); + + bool canRequest(CachedResource::Type, const URL&, const CachedResourceRequest&, ForPreload); enum RevalidationPolicy { Use, Revalidate, Reload, Load }; - RevalidationPolicy determineRevalidationPolicy(CachedResource::Type, ResourceRequest&, bool forPreload, CachedResource* existingResource, CachedResourceRequest::DeferOption) const; - - bool shouldContinueAfterNotifyingLoadedFromMemoryCache(CachedResource*); + RevalidationPolicy determineRevalidationPolicy(CachedResource::Type, CachedResourceRequest&, CachedResource* existingResource, ForPreload, DeferOption) const; + + bool shouldUpdateCachedResourceWithCurrentRequest(const CachedResource&, const CachedResourceRequest&); + CachedResourceHandle<CachedResource> updateCachedResourceWithCurrentRequest(const CachedResource&, CachedResourceRequest&&); + + bool shouldContinueAfterNotifyingLoadedFromMemoryCache(const CachedResourceRequest&, CachedResource*); bool checkInsecureContent(CachedResource::Type, const URL&) const; + bool allowedByContentSecurityPolicy(CachedResource::Type, const URL&, const ResourceLoaderOptions&, ContentSecurityPolicy::RedirectResponseReceived) const; - void garbageCollectDocumentResourcesTimerFired(Timer<CachedResourceLoader>&); void performPostLoadActions(); bool clientDefersImage(const URL&) const; void reloadImagesIfNotDeferred(); - + + bool canRequestAfterRedirection(CachedResource::Type, const URL&, const ResourceLoaderOptions&) const; + bool canRequestInContentDispositionAttachmentSandbox(CachedResource::Type, const URL&) const; + HashSet<String> m_validatedURLs; mutable DocumentResourceMap m_documentResources; Document* m_document; @@ -165,22 +186,12 @@ private: int m_requestCount; - OwnPtr<ListHashSet<CachedResource*>> m_preloads; - struct PendingPreload { - CachedResource::Type m_type; - CachedResourceRequest m_request; - String m_charset; - }; - Deque<PendingPreload> m_pendingPreloads; - - Timer<CachedResourceLoader> m_garbageCollectDocumentResourcesTimer; - -#if ENABLE(RESOURCE_TIMING) - struct InitiatorInfo { - AtomicString name; - double startTime; - }; - HashMap<CachedResource*, InitiatorInfo> m_initiatorMap; + std::unique_ptr<ListHashSet<CachedResource*>> m_preloads; + + Timer m_garbageCollectDocumentResourcesTimer; + +#if ENABLE(WEB_TIMING) + ResourceTimingInformation m_resourceTimingInfo; #endif // 29 bits left @@ -193,25 +204,19 @@ class ResourceCacheValidationSuppressor { WTF_MAKE_NONCOPYABLE(ResourceCacheValidationSuppressor); WTF_MAKE_FAST_ALLOCATED; public: - ResourceCacheValidationSuppressor(CachedResourceLoader* loader) + ResourceCacheValidationSuppressor(CachedResourceLoader& loader) : m_loader(loader) - , m_previousState(false) + , m_previousState(m_loader.m_allowStaleResources) { - if (m_loader) { - m_previousState = m_loader->m_allowStaleResources; - m_loader->m_allowStaleResources = true; - } + m_loader.m_allowStaleResources = true; } ~ResourceCacheValidationSuppressor() { - if (m_loader) - m_loader->m_allowStaleResources = m_previousState; + m_loader.m_allowStaleResources = m_previousState; } private: - CachedResourceLoader* m_loader; + CachedResourceLoader& m_loader; bool m_previousState; }; } // namespace WebCore - -#endif diff --git a/Source/WebCore/loader/cache/CachedResourceRequest.cpp b/Source/WebCore/loader/cache/CachedResourceRequest.cpp index 55a493859..a5ac5637a 100644 --- a/Source/WebCore/loader/cache/CachedResourceRequest.cpp +++ b/Source/WebCore/loader/cache/CachedResourceRequest.cpp @@ -13,7 +13,7 @@ * THIS SOFTWARE IS PROVIDED BY GOOGLE 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 + * 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 @@ -27,52 +27,49 @@ #include "CachedResourceRequest.h" #include "CachedResourceLoader.h" +#include "ContentExtensionActions.h" +#include "CrossOriginAccessControl.h" #include "Document.h" #include "Element.h" +#include "FrameLoader.h" +#include "HTTPHeaderValues.h" +#include "MemoryCache.h" +#include "SecurityPolicy.h" +#include <wtf/NeverDestroyed.h> namespace WebCore { -CachedResourceRequest::CachedResourceRequest(const ResourceRequest& resourceRequest, const String& charset, ResourceLoadPriority priority) - : m_resourceRequest(resourceRequest) - , m_charset(charset) - , m_options(CachedResourceLoader::defaultCachedResourceOptions()) - , m_priority(priority) - , m_forPreload(false) - , m_defer(NoDefer) -{ -} - -CachedResourceRequest::CachedResourceRequest(const ResourceRequest& resourceRequest, const ResourceLoaderOptions& options) - : m_resourceRequest(resourceRequest) +CachedResourceRequest::CachedResourceRequest(ResourceRequest&& resourceRequest, const ResourceLoaderOptions& options, std::optional<ResourceLoadPriority> priority, String&& charset) + : m_resourceRequest(WTFMove(resourceRequest)) + , m_charset(WTFMove(charset)) , m_options(options) - , m_priority(ResourceLoadPriorityUnresolved) - , m_forPreload(false) - , m_defer(NoDefer) -{ -} - -CachedResourceRequest::CachedResourceRequest(const ResourceRequest& resourceRequest, ResourceLoadPriority priority) - : m_resourceRequest(resourceRequest) - , m_options(CachedResourceLoader::defaultCachedResourceOptions()) , m_priority(priority) - , m_forPreload(false) - , m_defer(NoDefer) + , m_fragmentIdentifier(splitFragmentIdentifierFromRequestURL(m_resourceRequest)) { } -CachedResourceRequest::~CachedResourceRequest() +String CachedResourceRequest::splitFragmentIdentifierFromRequestURL(ResourceRequest& request) { + if (!MemoryCache::shouldRemoveFragmentIdentifier(request.url())) + return { }; + URL url = request.url(); + String fragmentIdentifier = url.fragmentIdentifier(); + url.removeFragmentIdentifier(); + request.setURL(url); + return fragmentIdentifier; } -void CachedResourceRequest::setInitiator(PassRefPtr<Element> element) +void CachedResourceRequest::setInitiator(Element& element) { - ASSERT(!m_initiatorElement && m_initiatorName.isEmpty()); - m_initiatorElement = element; + ASSERT(!m_initiatorElement); + ASSERT(m_initiatorName.isEmpty()); + m_initiatorElement = &element; } void CachedResourceRequest::setInitiator(const AtomicString& name) { - ASSERT(!m_initiatorElement && m_initiatorName.isEmpty()); + ASSERT(!m_initiatorElement); + ASSERT(m_initiatorName.isEmpty()); m_initiatorName = name; } @@ -83,8 +80,198 @@ const AtomicString& CachedResourceRequest::initiatorName() const if (!m_initiatorName.isEmpty()) return m_initiatorName; - DEFINE_STATIC_LOCAL(AtomicString, defaultName, ("resource", AtomicString::ConstructFromLiteral)); + static NeverDestroyed<AtomicString> defaultName("other", AtomicString::ConstructFromLiteral); return defaultName; } +void CachedResourceRequest::setAsPotentiallyCrossOrigin(const String& mode, Document& document) +{ + ASSERT(m_options.mode == FetchOptions::Mode::NoCors); + + m_origin = &document.securityOrigin(); + + if (mode.isNull()) + return; + + m_options.mode = FetchOptions::Mode::Cors; + + FetchOptions::Credentials credentials = equalLettersIgnoringASCIICase(mode, "omit") + ? FetchOptions::Credentials::Omit : equalLettersIgnoringASCIICase(mode, "use-credentials") + ? FetchOptions::Credentials::Include : FetchOptions::Credentials::SameOrigin; + m_options.credentials = credentials; + m_options.allowCredentials = credentials == FetchOptions::Credentials::Include ? AllowStoredCredentials : DoNotAllowStoredCredentials; + WebCore::updateRequestForAccessControl(m_resourceRequest, document.securityOrigin(), m_options.allowCredentials); +} + +void CachedResourceRequest::updateForAccessControl(Document& document) +{ + ASSERT(m_options.mode == FetchOptions::Mode::Cors); + + m_origin = &document.securityOrigin(); + WebCore::updateRequestForAccessControl(m_resourceRequest, *m_origin, m_options.allowCredentials); +} + +void upgradeInsecureResourceRequestIfNeeded(ResourceRequest& request, Document& document) +{ + URL url = request.url(); + + ASSERT(document.contentSecurityPolicy()); + document.contentSecurityPolicy()->upgradeInsecureRequestIfNeeded(url, ContentSecurityPolicy::InsecureRequestType::Load); + + if (url == request.url()) + return; + + request.setURL(url); +} + +void CachedResourceRequest::upgradeInsecureRequestIfNeeded(Document& document) +{ + upgradeInsecureResourceRequestIfNeeded(m_resourceRequest, document); +} + +void CachedResourceRequest::setDomainForCachePartition(Document& document) +{ + m_resourceRequest.setDomainForCachePartition(document.topOrigin().domainForCachePartition()); +} + +static inline String acceptHeaderValueFromType(CachedResource::Type type) +{ + switch (type) { + case CachedResource::Type::MainResource: + return ASCIILiteral("text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"); + case CachedResource::Type::ImageResource: + return ASCIILiteral("image/png,image/svg+xml,image/*;q=0.8,*/*;q=0.5"); + case CachedResource::Type::CSSStyleSheet: + return ASCIILiteral("text/css,*/*;q=0.1"); + case CachedResource::Type::SVGDocumentResource: + return ASCIILiteral("image/svg+xml"); +#if ENABLE(XSLT) + case CachedResource::Type::XSLStyleSheet: + // FIXME: This should accept more general xml formats */*+xml, image/svg+xml for example. + return ASCIILiteral("text/xml,application/xml,application/xhtml+xml,text/xsl,application/rss+xml,application/atom+xml"); +#endif + default: + return ASCIILiteral("*/*"); + } +} + +void CachedResourceRequest::setAcceptHeaderIfNone(CachedResource::Type type) +{ + if (!m_resourceRequest.hasHTTPHeader(HTTPHeaderName::Accept)) + m_resourceRequest.setHTTPHeaderField(HTTPHeaderName::Accept, acceptHeaderValueFromType(type)); +} + +void CachedResourceRequest::updateAccordingCacheMode() +{ + if (m_options.cache == FetchOptions::Cache::Default + && (m_resourceRequest.hasHTTPHeaderField(HTTPHeaderName::IfModifiedSince) + || m_resourceRequest.hasHTTPHeaderField(HTTPHeaderName::IfNoneMatch) + || m_resourceRequest.hasHTTPHeaderField(HTTPHeaderName::IfUnmodifiedSince) + || m_resourceRequest.hasHTTPHeaderField(HTTPHeaderName::IfMatch) + || m_resourceRequest.hasHTTPHeaderField(HTTPHeaderName::IfRange))) + m_options.cache = FetchOptions::Cache::NoStore; + + switch (m_options.cache) { + case FetchOptions::Cache::NoCache: + m_resourceRequest.setCachePolicy(RefreshAnyCacheData); + m_resourceRequest.addHTTPHeaderFieldIfNotPresent(HTTPHeaderName::CacheControl, HTTPHeaderValues::maxAge0()); + break; + case FetchOptions::Cache::NoStore: + m_options.cachingPolicy = CachingPolicy::DisallowCaching; + m_resourceRequest.setCachePolicy(DoNotUseAnyCache); + m_resourceRequest.addHTTPHeaderFieldIfNotPresent(HTTPHeaderName::Pragma, HTTPHeaderValues::noCache()); + m_resourceRequest.addHTTPHeaderFieldIfNotPresent(HTTPHeaderName::CacheControl, HTTPHeaderValues::noCache()); + break; + case FetchOptions::Cache::Reload: + m_resourceRequest.setCachePolicy(ReloadIgnoringCacheData); + m_resourceRequest.addHTTPHeaderFieldIfNotPresent(HTTPHeaderName::Pragma, HTTPHeaderValues::noCache()); + m_resourceRequest.addHTTPHeaderFieldIfNotPresent(HTTPHeaderName::CacheControl, HTTPHeaderValues::noCache()); + break; + case FetchOptions::Cache::Default: + break; + case FetchOptions::Cache::ForceCache: + m_resourceRequest.setCachePolicy(ReturnCacheDataElseLoad); + break; + case FetchOptions::Cache::OnlyIfCached: + m_resourceRequest.setCachePolicy(ReturnCacheDataDontLoad); + break; + } +} + +void CachedResourceRequest::removeFragmentIdentifierIfNeeded() +{ + URL url = MemoryCache::removeFragmentIdentifierIfNeeded(m_resourceRequest.url()); + if (url.string() != m_resourceRequest.url()) + m_resourceRequest.setURL(url); +} + +#if ENABLE(CONTENT_EXTENSIONS) + +void CachedResourceRequest::applyBlockedStatus(const ContentExtensions::BlockedStatus& blockedStatus) +{ + ContentExtensions::applyBlockedStatusToRequest(blockedStatus, m_resourceRequest); +} + +#endif + +void CachedResourceRequest::updateReferrerOriginAndUserAgentHeaders(FrameLoader& frameLoader, ReferrerPolicy defaultPolicy) +{ + // Implementing step 7 to 9 of https://fetch.spec.whatwg.org/#http-network-or-cache-fetch + + String outgoingOrigin; + String outgoingReferrer = m_resourceRequest.httpReferrer(); + if (!outgoingReferrer.isNull()) + outgoingOrigin = SecurityOrigin::createFromString(outgoingReferrer)->toString(); + else { + outgoingReferrer = frameLoader.outgoingReferrer(); + outgoingOrigin = frameLoader.outgoingOrigin(); + } + + // FIXME: Refactor SecurityPolicy::generateReferrerHeader to align with new terminology used in https://w3c.github.io/webappsec-referrer-policy. + switch (m_options.referrerPolicy) { + case FetchOptions::ReferrerPolicy::EmptyString: { + outgoingReferrer = SecurityPolicy::generateReferrerHeader(defaultPolicy, m_resourceRequest.url(), outgoingReferrer); + break; } + case FetchOptions::ReferrerPolicy::NoReferrerWhenDowngrade: + outgoingReferrer = SecurityPolicy::generateReferrerHeader(ReferrerPolicy::Default, m_resourceRequest.url(), outgoingReferrer); + break; + case FetchOptions::ReferrerPolicy::NoReferrer: + outgoingReferrer = String(); + break; + case FetchOptions::ReferrerPolicy::Origin: + outgoingReferrer = SecurityPolicy::generateReferrerHeader(ReferrerPolicy::Origin, m_resourceRequest.url(), outgoingReferrer); + break; + case FetchOptions::ReferrerPolicy::OriginWhenCrossOrigin: + if (isRequestCrossOrigin(m_origin.get(), m_resourceRequest.url(), m_options)) + outgoingReferrer = SecurityPolicy::generateReferrerHeader(ReferrerPolicy::Origin, m_resourceRequest.url(), outgoingReferrer); + break; + case FetchOptions::ReferrerPolicy::UnsafeUrl: + break; + }; + + if (outgoingReferrer.isEmpty()) + m_resourceRequest.clearHTTPReferrer(); + else + m_resourceRequest.setHTTPReferrer(outgoingReferrer); + FrameLoader::addHTTPOriginIfNeeded(m_resourceRequest, outgoingOrigin); + + frameLoader.applyUserAgent(m_resourceRequest); +} + +bool isRequestCrossOrigin(SecurityOrigin* origin, const URL& requestURL, const ResourceLoaderOptions& options) +{ + if (!origin) + return false; + + // Using same origin mode guarantees the loader will not do a cross-origin load, so we let it take care of it and just return false. + if (options.mode == FetchOptions::Mode::SameOrigin) + return false; + + // FIXME: We should remove options.sameOriginDataURLFlag once https://github.com/whatwg/fetch/issues/393 is fixed. + if (requestURL.protocolIsData() && options.sameOriginDataURLFlag == SameOriginDataURLFlag::Set) + return false; + + return !origin->canRequest(requestURL); +} + } // namespace WebCore diff --git a/Source/WebCore/loader/cache/CachedResourceRequest.h b/Source/WebCore/loader/cache/CachedResourceRequest.h index 0122c9958..1030b3660 100644 --- a/Source/WebCore/loader/cache/CachedResourceRequest.h +++ b/Source/WebCore/loader/cache/CachedResourceRequest.h @@ -13,7 +13,7 @@ * THIS SOFTWARE IS PROVIDED BY GOOGLE 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 + * 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 @@ -23,55 +23,83 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef CachedResourceRequest_h -#define CachedResourceRequest_h +#pragma once +#include "CachedResource.h" +#include "DocumentLoader.h" #include "Element.h" #include "ResourceLoadPriority.h" #include "ResourceLoaderOptions.h" #include "ResourceRequest.h" +#include "SecurityOrigin.h" #include <wtf/RefPtr.h> #include <wtf/text/AtomicString.h> namespace WebCore { + +namespace ContentExtensions { +struct BlockedStatus; +} + class Document; +class FrameLoader; +enum class ReferrerPolicy; + +bool isRequestCrossOrigin(SecurityOrigin*, const URL& requestURL, const ResourceLoaderOptions&); class CachedResourceRequest { public: - enum DeferOption { NoDefer, DeferredByClient }; - - explicit CachedResourceRequest(const ResourceRequest&, const String& charset = String(), ResourceLoadPriority = ResourceLoadPriorityUnresolved); - CachedResourceRequest(const ResourceRequest&, const ResourceLoaderOptions&); - CachedResourceRequest(const ResourceRequest&, ResourceLoadPriority); - ~CachedResourceRequest(); + CachedResourceRequest(ResourceRequest&&, const ResourceLoaderOptions&, std::optional<ResourceLoadPriority> = std::nullopt, String&& charset = String()); - ResourceRequest& mutableResourceRequest() { return m_resourceRequest; } + ResourceRequest&& releaseResourceRequest() { return WTFMove(m_resourceRequest); } const ResourceRequest& resourceRequest() const { return m_resourceRequest; } const String& charset() const { return m_charset; } void setCharset(const String& charset) { m_charset = charset; } const ResourceLoaderOptions& options() const { return m_options; } void setOptions(const ResourceLoaderOptions& options) { m_options = options; } - void setPriority(ResourceLoadPriority priority) { m_priority = priority; } - ResourceLoadPriority priority() const { return m_priority; } - bool forPreload() const { return m_forPreload; } - void setForPreload(bool forPreload) { m_forPreload = forPreload; } - DeferOption defer() const { return m_defer; } - void setDefer(DeferOption defer) { m_defer = defer; } - void setInitiator(PassRefPtr<Element>); + const std::optional<ResourceLoadPriority>& priority() const { return m_priority; } + void setInitiator(Element&); void setInitiator(const AtomicString& name); const AtomicString& initiatorName() const; + bool allowsCaching() const { return m_options.cachingPolicy == CachingPolicy::AllowCaching; } + void setCachingPolicy(CachingPolicy policy) { m_options.cachingPolicy = policy; } + + void setAsPotentiallyCrossOrigin(const String&, Document&); + void updateForAccessControl(Document&); + + void updateReferrerOriginAndUserAgentHeaders(FrameLoader&, ReferrerPolicy); + void upgradeInsecureRequestIfNeeded(Document&); + void setAcceptHeaderIfNone(CachedResource::Type); + void updateAccordingCacheMode(); + void removeFragmentIdentifierIfNeeded(); +#if ENABLE(CONTENT_EXTENSIONS) + void applyBlockedStatus(const ContentExtensions::BlockedStatus&); +#endif + void setDomainForCachePartition(Document&); + bool isLinkPreload() const { return m_isLinkPreload; } + void setIsLinkPreload() { m_isLinkPreload = true; } + + void setOrigin(Ref<SecurityOrigin>&& origin) { m_origin = WTFMove(origin); } + RefPtr<SecurityOrigin> releaseOrigin() { return WTFMove(m_origin); } + SecurityOrigin* origin() const { return m_origin.get(); } + + String&& releaseFragmentIdentifier() { return WTFMove(m_fragmentIdentifier); } + void clearFragmentIdentifier() { m_fragmentIdentifier = { }; } + + static String splitFragmentIdentifierFromRequestURL(ResourceRequest&); private: ResourceRequest m_resourceRequest; String m_charset; ResourceLoaderOptions m_options; - ResourceLoadPriority m_priority; - bool m_forPreload; - DeferOption m_defer; + std::optional<ResourceLoadPriority> m_priority; RefPtr<Element> m_initiatorElement; AtomicString m_initiatorName; + RefPtr<SecurityOrigin> m_origin; + String m_fragmentIdentifier; + bool m_isLinkPreload { false }; }; -} // namespace WebCore +void upgradeInsecureResourceRequestIfNeeded(ResourceRequest&, Document&); -#endif +} // namespace WebCore diff --git a/Source/WebCore/loader/cache/CachedResourceRequestInitiators.cpp b/Source/WebCore/loader/cache/CachedResourceRequestInitiators.cpp index 91a17b66b..b339a8e58 100644 --- a/Source/WebCore/loader/cache/CachedResourceRequestInitiators.cpp +++ b/Source/WebCore/loader/cache/CachedResourceRequestInitiators.cpp @@ -13,7 +13,7 @@ * THIS SOFTWARE IS PROVIDED BY GOOGLE 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 + * 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 @@ -30,6 +30,7 @@ namespace WebCore { CachedResourceRequestInitiators::CachedResourceRequestInitiators() : css("css", AtomicString::ConstructFromLiteral) + , fetch("fetch", AtomicString::ConstructFromLiteral) , icon("icon", AtomicString::ConstructFromLiteral) , xmlhttprequest("xmlhttprequest", AtomicString::ConstructFromLiteral) { diff --git a/Source/WebCore/loader/cache/CachedResourceRequestInitiators.h b/Source/WebCore/loader/cache/CachedResourceRequestInitiators.h index 24ac2387d..50538cafd 100644 --- a/Source/WebCore/loader/cache/CachedResourceRequestInitiators.h +++ b/Source/WebCore/loader/cache/CachedResourceRequestInitiators.h @@ -13,7 +13,7 @@ * THIS SOFTWARE IS PROVIDED BY GOOGLE 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 + * 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 @@ -23,8 +23,7 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef CachedResourceRequestInitiators_h -#define CachedResourceRequestInitiators_h +#pragma once #include "ThreadGlobalData.h" #include <wtf/text/AtomicString.h> @@ -32,12 +31,14 @@ namespace WebCore { struct CachedResourceRequestInitiators { + CachedResourceRequestInitiators(); + const AtomicString css; + const AtomicString fetch; const AtomicString icon; const AtomicString xmlhttprequest; WTF_MAKE_NONCOPYABLE(CachedResourceRequestInitiators); WTF_MAKE_FAST_ALLOCATED; private: - CachedResourceRequestInitiators(); friend class ThreadGlobalData; }; @@ -47,5 +48,3 @@ inline const CachedResourceRequestInitiators& cachedResourceRequestInitiators() } } // namespace WebCore - -#endif diff --git a/Source/WebCore/loader/cache/CachedSVGDocument.cpp b/Source/WebCore/loader/cache/CachedSVGDocument.cpp index 15946263f..08b40169d 100644 --- a/Source/WebCore/loader/cache/CachedSVGDocument.cpp +++ b/Source/WebCore/loader/cache/CachedSVGDocument.cpp @@ -21,22 +21,16 @@ */ #include "config.h" - -#if ENABLE(SVG) #include "CachedSVGDocument.h" -#include "CachedResourceClient.h" -#include "CachedResourceHandle.h" -#include "ResourceBuffer.h" -#include <wtf/text/StringBuilder.h> +#include "SharedBuffer.h" namespace WebCore { -CachedSVGDocument::CachedSVGDocument(const ResourceRequest& request) - : CachedResource(request, SVGDocumentResource) +CachedSVGDocument::CachedSVGDocument(CachedResourceRequest&& request, SessionID sessionID) + : CachedResource(WTFMove(request), SVGDocumentResource, sessionID) , m_decoder(TextResourceDecoder::create("application/xml")) { - setAccept("image/svg+xml"); } CachedSVGDocument::~CachedSVGDocument() @@ -53,20 +47,14 @@ String CachedSVGDocument::encoding() const return m_decoder->encoding().name(); } -void CachedSVGDocument::finishLoading(ResourceBuffer* data) +void CachedSVGDocument::finishLoading(SharedBuffer* data) { if (data) { - StringBuilder decodedText; - decodedText.append(m_decoder->decode(data->data(), data->size())); - decodedText.append(m_decoder->flush()); // We don't need to create a new frame because the new document belongs to the parent UseElement. - m_document = SVGDocument::create(0, response().url()); - m_document->setContent(decodedText.toString()); + m_document = SVGDocument::create(nullptr, response().url()); + m_document->setContent(m_decoder->decodeAndFlush(data->data(), data->size())); } CachedResource::finishLoading(data); } } - -#endif - diff --git a/Source/WebCore/loader/cache/CachedSVGDocument.h b/Source/WebCore/loader/cache/CachedSVGDocument.h index d6a6f5d4e..7981f1de0 100644 --- a/Source/WebCore/loader/cache/CachedSVGDocument.h +++ b/Source/WebCore/loader/cache/CachedSVGDocument.h @@ -20,10 +20,8 @@ Boston, MA 02110-1301, USA. */ -#ifndef CachedSVGDocument_h -#define CachedSVGDocument_h +#pragma once -#if ENABLE(SVG) #include "CachedResource.h" #include "CachedResourceHandle.h" #include "SVGDocument.h" @@ -33,24 +31,22 @@ namespace WebCore { class CachedSVGDocument final : public CachedResource { public: - explicit CachedSVGDocument(const ResourceRequest&); + explicit CachedSVGDocument(CachedResourceRequest&&, SessionID); virtual ~CachedSVGDocument(); SVGDocument* document() const { return m_document.get(); } -protected: +private: + bool mayTryReplaceEncodedData() const override { return true; } + void setEncoding(const String&) override; + String encoding() const override; + const TextResourceDecoder* textResourceDecoder() const override { return m_decoder.get(); } + void finishLoading(SharedBuffer*) override; + RefPtr<SVGDocument> m_document; RefPtr<TextResourceDecoder> m_decoder; - -private: - virtual bool mayTryReplaceEncodedData() const override { return true; } - virtual void setEncoding(const String&) override; - virtual String encoding() const override; - virtual void finishLoading(ResourceBuffer*) override; }; } // namespace WebCore -#endif // USE(SVG) - -#endif // CachedSVGDocument_h +SPECIALIZE_TYPE_TRAITS_CACHED_RESOURCE(CachedSVGDocument, CachedResource::SVGDocumentResource) diff --git a/Source/WebCore/loader/cache/CachedSVGDocumentClient.h b/Source/WebCore/loader/cache/CachedSVGDocumentClient.h index a01e0aee9..499dcd10e 100644 --- a/Source/WebCore/loader/cache/CachedSVGDocumentClient.h +++ b/Source/WebCore/loader/cache/CachedSVGDocumentClient.h @@ -20,26 +20,17 @@ Boston, MA 02110-1301, USA. */ -#ifndef CachedSVGDocumentClient_h -#define CachedSVGDocumentClient_h - -#if ENABLE(SVG) +#pragma once #include "CachedResourceClient.h" namespace WebCore { -class CachedSVGDocument; - class CachedSVGDocumentClient : public CachedResourceClient { public: virtual ~CachedSVGDocumentClient() { } static CachedResourceClientType expectedType() { return SVGDocumentType; } - virtual CachedResourceClientType resourceClientType() const override { return expectedType(); } + CachedResourceClientType resourceClientType() const override { return expectedType(); } }; } // namespace WebCore - -#endif // ENABLE(SVG) - -#endif // CachedSVGDocumentClient_h diff --git a/Source/WebCore/loader/cache/CachedSVGDocumentReference.cpp b/Source/WebCore/loader/cache/CachedSVGDocumentReference.cpp index 116f20e88..a9bf7c66e 100644 --- a/Source/WebCore/loader/cache/CachedSVGDocumentReference.cpp +++ b/Source/WebCore/loader/cache/CachedSVGDocumentReference.cpp @@ -26,7 +26,6 @@ #include "config.h" #include "CachedSVGDocumentReference.h" -#if ENABLE(SVG) && ENABLE(CSS_FILTERS) #include "CachedResourceHandle.h" #include "CachedResourceLoader.h" #include "CachedResourceRequest.h" @@ -37,7 +36,7 @@ namespace WebCore { CachedSVGDocumentReference::CachedSVGDocumentReference(const String& url) : m_url(url) - , m_document(0) + , m_document(nullptr) , m_loadRequested(false) { } @@ -45,24 +44,23 @@ CachedSVGDocumentReference::CachedSVGDocumentReference(const String& url) CachedSVGDocumentReference::~CachedSVGDocumentReference() { if (m_document) - m_document->removeClient(this); + m_document->removeClient(*this); } -void CachedSVGDocumentReference::load(CachedResourceLoader* loader) +void CachedSVGDocumentReference::load(CachedResourceLoader& loader, const ResourceLoaderOptions& options) { - ASSERT(loader); if (m_loadRequested) return; - CachedResourceRequest request(ResourceRequest(loader->document()->completeURL(m_url))); + auto fetchOptions = options; + fetchOptions.mode = FetchOptions::Mode::SameOrigin; + CachedResourceRequest request(ResourceRequest(loader.document()->completeURL(m_url)), fetchOptions); request.setInitiator(cachedResourceRequestInitiators().css); - m_document = loader->requestSVGDocument(request); + m_document = loader.requestSVGDocument(WTFMove(request)); if (m_document) - m_document->addClient(this); + m_document->addClient(*this); m_loadRequested = true; } } - -#endif diff --git a/Source/WebCore/loader/cache/CachedSVGDocumentReference.h b/Source/WebCore/loader/cache/CachedSVGDocumentReference.h index a297abcf8..70cb1afa4 100644 --- a/Source/WebCore/loader/cache/CachedSVGDocumentReference.h +++ b/Source/WebCore/loader/cache/CachedSVGDocumentReference.h @@ -23,10 +23,8 @@ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef CachedSVGDocumentReference_h -#define CachedSVGDocumentReference_h +#pragma once -#if ENABLE(SVG) && ENABLE(CSS_FILTERS) #include "CachedResourceHandle.h" #include "CachedSVGDocumentClient.h" #include <wtf/text/WTFString.h> @@ -35,6 +33,7 @@ namespace WebCore { class CachedSVGDocument; class CachedResourceLoader; +struct ResourceLoaderOptions; class CachedSVGDocumentReference : public CachedSVGDocumentClient { public: @@ -42,7 +41,7 @@ public: virtual ~CachedSVGDocumentReference(); - void load(CachedResourceLoader*); + void load(CachedResourceLoader&, const ResourceLoaderOptions&); bool loadRequested() const { return m_loadRequested; } CachedSVGDocument* document() { return m_document.get(); } @@ -53,8 +52,4 @@ private: bool m_loadRequested; }; -}; - -#endif // ENABLE(SVG) && ENABLE(CSS_FILTERS) - -#endif // CachedSVGDocumentReference_h +} // namespace WebCore diff --git a/Source/WebCore/loader/cache/CachedSVGFont.cpp b/Source/WebCore/loader/cache/CachedSVGFont.cpp new file mode 100644 index 000000000..a06641256 --- /dev/null +++ b/Source/WebCore/loader/cache/CachedSVGFont.cpp @@ -0,0 +1,142 @@ +/* + * 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 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 "CachedSVGFont.h" + +#if ENABLE(SVG_FONTS) + +#include "FontDescription.h" +#include "FontPlatformData.h" +#include "NoEventDispatchAssertion.h" +#include "SVGDocument.h" +#include "SVGFontElement.h" +#include "SVGFontFaceElement.h" +#include "SharedBuffer.h" +#include "TextResourceDecoder.h" +#include "TypedElementDescendantIterator.h" +#include "SVGToOTFFontConversion.h" + +#if USE(DIRECT2D) +#include <dwrite.h> +#endif + +namespace WebCore { + +CachedSVGFont::CachedSVGFont(CachedResourceRequest&& request, SessionID sessionID) + : CachedFont(WTFMove(request), sessionID, SVGFontResource) + , m_externalSVGFontElement(nullptr) +{ +} + +RefPtr<Font> CachedSVGFont::createFont(const FontDescription& fontDescription, const AtomicString& remoteURI, bool syntheticBold, bool syntheticItalic, const FontFeatureSettings& fontFaceFeatures, const FontVariantSettings& fontFaceVariantSettings) +{ + if (firstFontFace(remoteURI)) + return CachedFont::createFont(fontDescription, remoteURI, syntheticBold, syntheticItalic, fontFaceFeatures, fontFaceVariantSettings); + return nullptr; +} + +FontPlatformData CachedSVGFont::platformDataFromCustomData(const FontDescription& fontDescription, bool bold, bool italic, const FontFeatureSettings& fontFaceFeatures, const FontVariantSettings& fontFaceVariantSettings) +{ + if (m_externalSVGDocument) + return FontPlatformData(fontDescription.computedPixelSize(), bold, italic); + return CachedFont::platformDataFromCustomData(fontDescription, bold, italic, fontFaceFeatures, fontFaceVariantSettings); +} + +bool CachedSVGFont::ensureCustomFontData(const AtomicString& remoteURI) +{ + if (!m_externalSVGDocument && !errorOccurred() && !isLoading() && m_data) { + bool sawError = false; + { + // We may get here during render tree updates when events are forbidden. + // Frameless document can't run scripts or call back to the client so this is safe. + m_externalSVGDocument = SVGDocument::create(nullptr, URL()); + auto decoder = TextResourceDecoder::create("application/xml"); + + NoEventDispatchAssertion::EventAllowedScope allowedScope(*m_externalSVGDocument); + + m_externalSVGDocument->setContent(decoder->decodeAndFlush(m_data->data(), m_data->size())); + sawError = decoder->sawError(); + } + + if (sawError) + m_externalSVGDocument = nullptr; + if (m_externalSVGDocument) + maybeInitializeExternalSVGFontElement(remoteURI); + if (!m_externalSVGFontElement) + return false; + if (auto convertedFont = convertSVGToOTFFont(*m_externalSVGFontElement)) + m_convertedFont = SharedBuffer::adoptVector(convertedFont.value()); + else { + m_externalSVGDocument = nullptr; + m_externalSVGFontElement = nullptr; + return false; + } + } + + return m_externalSVGDocument && CachedFont::ensureCustomFontData(m_convertedFont.get()); +} + +SVGFontElement* CachedSVGFont::getSVGFontById(const String& fontName) const +{ + ASSERT(m_externalSVGDocument); + auto elements = descendantsOfType<SVGFontElement>(*m_externalSVGDocument); + + if (fontName.isEmpty()) + return elements.first(); + + for (auto& element : elements) { + if (element.getIdAttribute() == fontName) + return &element; + } + return nullptr; +} + +SVGFontElement* CachedSVGFont::maybeInitializeExternalSVGFontElement(const AtomicString& remoteURI) +{ + if (m_externalSVGFontElement) + return m_externalSVGFontElement; + String fragmentIdentifier; + size_t start = remoteURI.find('#'); + if (start != notFound) + fragmentIdentifier = remoteURI.string().substring(start + 1); + m_externalSVGFontElement = getSVGFontById(fragmentIdentifier); + return m_externalSVGFontElement; +} + +SVGFontFaceElement* CachedSVGFont::firstFontFace(const AtomicString& remoteURI) +{ + if (!maybeInitializeExternalSVGFontElement(remoteURI)) + return nullptr; + + if (auto* firstFontFace = childrenOfType<SVGFontFaceElement>(*m_externalSVGFontElement).first()) + return firstFontFace; + return nullptr; +} + +} + +#endif diff --git a/Source/WebCore/loader/cache/CachedSVGFont.h b/Source/WebCore/loader/cache/CachedSVGFont.h new file mode 100644 index 000000000..93ed7a0c4 --- /dev/null +++ b/Source/WebCore/loader/cache/CachedSVGFont.h @@ -0,0 +1,61 @@ +/* + * 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 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. + */ + +#pragma once + +#if ENABLE(SVG_FONTS) + +#include "CachedFont.h" + +namespace WebCore { + +class SVGFontFaceElement; + +class CachedSVGFont final : public CachedFont { +public: + CachedSVGFont(CachedResourceRequest&&, SessionID); + + bool ensureCustomFontData(const AtomicString& remoteURI) override; + + RefPtr<Font> createFont(const FontDescription&, const AtomicString& remoteURI, bool syntheticBold, bool syntheticItalic, const FontFeatureSettings&, const FontVariantSettings&) override; + +private: + FontPlatformData platformDataFromCustomData(const FontDescription&, bool bold, bool italic, const FontFeatureSettings&, const FontVariantSettings&); + + SVGFontElement* getSVGFontById(const String&) const; + + SVGFontElement* maybeInitializeExternalSVGFontElement(const AtomicString& remoteURI); + SVGFontFaceElement* firstFontFace(const AtomicString& remoteURI); + + RefPtr<SharedBuffer> m_convertedFont; + RefPtr<SVGDocument> m_externalSVGDocument; + SVGFontElement* m_externalSVGFontElement; +}; + +} // namespace WebCore + +SPECIALIZE_TYPE_TRAITS_CACHED_RESOURCE(CachedSVGFont, CachedResource::SVGFontResource) + +#endif // ENABLE(SVG_FONTS) diff --git a/Source/WebCore/loader/cache/CachedScript.cpp b/Source/WebCore/loader/cache/CachedScript.cpp index 48facd7bc..84de38ebe 100644 --- a/Source/WebCore/loader/cache/CachedScript.cpp +++ b/Source/WebCore/loader/cache/CachedScript.cpp @@ -29,24 +29,20 @@ #include "CachedResourceClient.h" #include "CachedResourceClientWalker.h" +#include "CachedResourceRequest.h" +#include "HTTPHeaderNames.h" #include "HTTPParsers.h" #include "MIMETypeRegistry.h" -#include "MemoryCache.h" -#include "ResourceBuffer.h" #include "RuntimeApplicationChecks.h" +#include "SharedBuffer.h" #include "TextResourceDecoder.h" -#include <wtf/Vector.h> namespace WebCore { -CachedScript::CachedScript(const ResourceRequest& resourceRequest, const String& charset) - : CachedResource(resourceRequest, Script) - , m_decoder(TextResourceDecoder::create(ASCIILiteral("application/javascript"), charset)) +CachedScript::CachedScript(CachedResourceRequest&& request, SessionID sessionID) + : CachedResource(WTFMove(request), Script, sessionID) + , m_decoder(TextResourceDecoder::create(ASCIILiteral("application/javascript"), request.charset())) { - // 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() @@ -65,27 +61,55 @@ String CachedScript::encoding() const String CachedScript::mimeType() const { - return extractMIMETypeFromMediaType(m_response.httpHeaderField("Content-Type")).lower(); + return extractMIMETypeFromMediaType(m_response.httpHeaderField(HTTPHeaderName::ContentType)).convertToASCIILowercase(); } -const String& CachedScript::script() +StringView CachedScript::script() { - ASSERT(!isPurgeable()); + if (!m_data) + return { }; - if (!m_script && m_data) { - m_script = m_decoder->decode(m_data->data(), encodedSize()); - m_script.append(m_decoder->flush()); + if (m_decodingState == NeverDecoded + && TextEncoding(encoding()).isByteBasedEncoding() + && m_data->size() + && charactersAreAllASCII(reinterpret_cast<const LChar*>(m_data->data()), m_data->size())) { + + m_decodingState = DataAndDecodedStringHaveSameBytes; + + // If the encoded and decoded data are the same, there is no decoded data cost! + setDecodedSize(0); + m_decodedDataDeletionTimer.stop(); + + m_scriptHash = StringHasher::computeHashAndMaskTop8Bits(reinterpret_cast<const LChar*>(m_data->data()), m_data->size()); + } + + if (m_decodingState == DataAndDecodedStringHaveSameBytes) + return { reinterpret_cast<const LChar*>(m_data->data()), m_data->size() }; + + if (!m_script) { + m_script = m_decoder->decodeAndFlush(m_data->data(), encodedSize()); + ASSERT(!m_scriptHash || m_scriptHash == m_script.impl()->hash()); + if (m_decodingState == NeverDecoded) + m_scriptHash = m_script.impl()->hash(); + m_decodingState = DataAndDecodedStringHaveDifferentBytes; setDecodedSize(m_script.sizeInBytes()); } + m_decodedDataDeletionTimer.restart(); - return m_script; } -void CachedScript::finishLoading(ResourceBuffer* data) +unsigned CachedScript::scriptHash() +{ + if (m_decodingState == NeverDecoded) + script(); + return m_scriptHash; +} + +void CachedScript::finishLoading(SharedBuffer* data) { m_data = data; - setEncodedSize(m_data.get() ? m_data->size() : 0); + setEncodedSize(data ? data->size() : 0); CachedResource::finishLoading(data); } @@ -93,27 +117,40 @@ void CachedScript::destroyDecodedData() { m_script = String(); setDecodedSize(0); - if (!MemoryCache::shouldMakeResourcePurgeableOnEviction() && isSafeToMakePurgeable()) - makePurgeable(true); +} + +void CachedScript::setBodyDataFrom(const CachedResource& resource) +{ + ASSERT(resource.type() == type()); + auto& script = static_cast<const CachedScript&>(resource); + + CachedResource::setBodyDataFrom(resource); + + m_script = script.m_script; + m_scriptHash = script.m_scriptHash; + m_decodingState = script.m_decodingState; + m_decoder = script.m_decoder; } #if ENABLE(NOSNIFF) bool CachedScript::mimeTypeAllowedByNosniff() const { - return !parseContentTypeOptionsHeader(m_response.httpHeaderField("X-Content-Type-Options")) == ContentTypeOptionsNosniff || MIMETypeRegistry::isSupportedJavaScriptMIMEType(mimeType()); + return parseContentTypeOptionsHeader(m_response.httpHeaderField(HTTPHeaderName::XContentTypeOptions)) != ContentTypeOptionsNosniff || MIMETypeRegistry::isSupportedJavaScriptMIMEType(mimeType()); } #endif bool CachedScript::shouldIgnoreHTTPStatusCodeErrors() const { +#if PLATFORM(MAC) // This is a workaround for <rdar://problem/13916291> // REGRESSION (r119759): Adobe Flash Player "smaller" installer relies on the incorrect firing // of a load event and needs an app-specific hack for compatibility. // The installer in question tries to load .js file that doesn't exist, causing the server to // return a 404 response. Normally, this would trigger an error event to be dispatched, but the // installer expects a load event instead so we work around it here. - if (applicationIsSolidStateNetworksDownloader()) + if (MacApplication::isSolidStateNetworksDownloader()) return true; +#endif return CachedResource::shouldIgnoreHTTPStatusCodeErrors(); } diff --git a/Source/WebCore/loader/cache/CachedScript.h b/Source/WebCore/loader/cache/CachedScript.h index 80b073c2a..f8898d378 100644 --- a/Source/WebCore/loader/cache/CachedScript.h +++ b/Source/WebCore/loader/cache/CachedScript.h @@ -23,44 +23,51 @@ pages from the web. It has a memory cache for these objects. */ -#ifndef CachedScript_h -#define CachedScript_h +#pragma once #include "CachedResource.h" namespace WebCore { - class CachedResourceLoader; - class TextResourceDecoder; +class TextResourceDecoder; - class CachedScript final : public CachedResource { - public: - CachedScript(const ResourceRequest&, const String& charset); - virtual ~CachedScript(); +class CachedScript final : public CachedResource { +public: + CachedScript(CachedResourceRequest&&, SessionID); + virtual ~CachedScript(); - const String& script(); + StringView script(); + unsigned scriptHash(); - String mimeType() const; + String mimeType() const; #if ENABLE(NOSNIFF) - bool mimeTypeAllowedByNosniff() const; + bool mimeTypeAllowedByNosniff() const; #endif - private: - virtual PurgePriority purgePriority() const override { return PurgeLast; } - virtual bool mayTryReplaceEncodedData() const override { return true; } +private: + bool mayTryReplaceEncodedData() const final { return true; } - virtual bool shouldIgnoreHTTPStatusCodeErrors() const override; + bool shouldIgnoreHTTPStatusCodeErrors() const final; - virtual void setEncoding(const String&) override; - virtual String encoding() const override; - virtual void finishLoading(ResourceBuffer*) override; + void setEncoding(const String&) final; + String encoding() const final; + const TextResourceDecoder* textResourceDecoder() const final { return m_decoder.get(); } + void finishLoading(SharedBuffer*) final; - virtual void destroyDecodedData() override; + void destroyDecodedData() final; - String m_script; - RefPtr<TextResourceDecoder> m_decoder; - }; -} + void setBodyDataFrom(const CachedResource&) final; -#endif + String m_script; + unsigned m_scriptHash { 0 }; + + enum DecodingState { NeverDecoded, DataAndDecodedStringHaveSameBytes, DataAndDecodedStringHaveDifferentBytes }; + DecodingState m_decodingState { NeverDecoded }; + + RefPtr<TextResourceDecoder> m_decoder; +}; + +} // namespace WebCore + +SPECIALIZE_TYPE_TRAITS_CACHED_RESOURCE(CachedScript, CachedResource::Script) diff --git a/Source/WebCore/loader/cache/CachedStyleSheetClient.h b/Source/WebCore/loader/cache/CachedStyleSheetClient.h index eed2d3a3e..c2f187389 100644 --- a/Source/WebCore/loader/cache/CachedStyleSheetClient.h +++ b/Source/WebCore/loader/cache/CachedStyleSheetClient.h @@ -23,8 +23,7 @@ pages from the web. It has a memory cache for these objects. */ -#ifndef CachedStyleSheetClient_h -#define CachedStyleSheetClient_h +#pragma once #include "CachedResourceClient.h" #include <wtf/Forward.h> @@ -38,11 +37,9 @@ class CachedStyleSheetClient : public CachedResourceClient { public: virtual ~CachedStyleSheetClient() { } static CachedResourceClientType expectedType() { return StyleSheetType; } - virtual CachedResourceClientType resourceClientType() const override { return expectedType(); } + CachedResourceClientType resourceClientType() const override { return expectedType(); } virtual void setCSSStyleSheet(const String& /* href */, const URL& /* baseURL */, const String& /* charset */, const CachedCSSStyleSheet*) { } virtual void setXSLStyleSheet(const String& /* href */, const URL& /* baseURL */, const String& /* sheet */) { } }; } // namespace WebCore - -#endif // CachedStyleSheetClient_h diff --git a/Source/WebCore/loader/cache/CachedTextTrack.cpp b/Source/WebCore/loader/cache/CachedTextTrack.cpp index 94974a591..1d82bd71b 100644 --- a/Source/WebCore/loader/cache/CachedTextTrack.cpp +++ b/Source/WebCore/loader/cache/CachedTextTrack.cpp @@ -32,36 +32,36 @@ #include "CachedResourceClient.h" #include "CachedResourceClientWalker.h" #include "CachedResourceLoader.h" -#include "ResourceBuffer.h" #include "SharedBuffer.h" #include "TextResourceDecoder.h" -#include <wtf/Vector.h> namespace WebCore { -CachedTextTrack::CachedTextTrack(const ResourceRequest& resourceRequest) - : CachedResource(resourceRequest, TextTrackResource) +CachedTextTrack::CachedTextTrack(CachedResourceRequest&& request, SessionID sessionID) + : CachedResource(WTFMove(request), TextTrackResource, sessionID) { } -CachedTextTrack::~CachedTextTrack() +void CachedTextTrack::updateData(SharedBuffer* data) { -} - -void CachedTextTrack::addDataBuffer(ResourceBuffer* data) -{ - ASSERT(m_options.dataBufferingPolicy == BufferData); + ASSERT(dataBufferingPolicy() == BufferData); m_data = data; - setEncodedSize(m_data.get() ? m_data->size() : 0); + setEncodedSize(data ? data->size() : 0); CachedResourceClientWalker<CachedResourceClient> walker(m_clients); - while (CachedResourceClient *client = walker.next()) - client->deprecatedDidReceiveCachedResource(this); + while (CachedResourceClient* client = walker.next()) + client->deprecatedDidReceiveCachedResource(*this); +} + +void CachedTextTrack::addDataBuffer(SharedBuffer& data) +{ + updateData(&data); + CachedResource::addDataBuffer(data); } -void CachedTextTrack::finishLoading(ResourceBuffer* data) +void CachedTextTrack::finishLoading(SharedBuffer* data) { - addDataBuffer(data); + updateData(data); CachedResource::finishLoading(data); } diff --git a/Source/WebCore/loader/cache/CachedTextTrack.h b/Source/WebCore/loader/cache/CachedTextTrack.h index 0df56e2d3..adf544a1b 100644 --- a/Source/WebCore/loader/cache/CachedTextTrack.h +++ b/Source/WebCore/loader/cache/CachedTextTrack.h @@ -23,28 +23,29 @@ * THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef CachedTextTrack_h -#define CachedTextTrack_h +#pragma once #if ENABLE(VIDEO_TRACK) #include "CachedResource.h" -#include "FontOrientation.h" +#include "TextFlags.h" namespace WebCore { class CachedTextTrack final : public CachedResource { public: - CachedTextTrack(const ResourceRequest&); - virtual ~CachedTextTrack(); + CachedTextTrack(CachedResourceRequest&&, SessionID); private: - virtual bool mayTryReplaceEncodedData() const override { return true; } - virtual void addDataBuffer(ResourceBuffer*) override; - virtual void finishLoading(ResourceBuffer*) override; + bool mayTryReplaceEncodedData() const override { return true; } + void addDataBuffer(SharedBuffer&) override; + void finishLoading(SharedBuffer*) override; + + void updateData(SharedBuffer*); }; -} +} // namespace WebCore + +SPECIALIZE_TYPE_TRAITS_CACHED_RESOURCE(CachedTextTrack, CachedResource::TextTrackResource) -#endif -#endif +#endif // ENABLE(VIDEO_TRACK) diff --git a/Source/WebCore/loader/cache/CachedXSLStyleSheet.cpp b/Source/WebCore/loader/cache/CachedXSLStyleSheet.cpp index eb6c8e1fe..c9f0dc87a 100644 --- a/Source/WebCore/loader/cache/CachedXSLStyleSheet.cpp +++ b/Source/WebCore/loader/cache/CachedXSLStyleSheet.cpp @@ -29,28 +29,28 @@ #include "CachedResourceClientWalker.h" #include "CachedStyleSheetClient.h" -#include "ResourceBuffer.h" +#include "SharedBuffer.h" #include "TextResourceDecoder.h" -#include <wtf/Vector.h> namespace WebCore { #if ENABLE(XSLT) -CachedXSLStyleSheet::CachedXSLStyleSheet(const ResourceRequest& resourceRequest) - : CachedResource(resourceRequest, XSLStyleSheet) +CachedXSLStyleSheet::CachedXSLStyleSheet(CachedResourceRequest&& request, SessionID sessionID) + : CachedResource(WTFMove(request), XSLStyleSheet, sessionID) , 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) -{ - ASSERT(c->resourceClientType() == CachedStyleSheetClient::expectedType()); +CachedXSLStyleSheet::~CachedXSLStyleSheet() +{ +} + +void CachedXSLStyleSheet::didAddClient(CachedResourceClient& client) +{ + ASSERT(client.resourceClientType() == CachedStyleSheetClient::expectedType()); if (!isLoading()) - static_cast<CachedStyleSheetClient*>(c)->setXSLStyleSheet(m_resourceRequest.url(), m_response.url(), m_sheet); + static_cast<CachedStyleSheetClient&>(client).setXSLStyleSheet(m_resourceRequest.url(), m_response.url(), m_sheet); } void CachedXSLStyleSheet::setEncoding(const String& chs) @@ -63,14 +63,12 @@ String CachedXSLStyleSheet::encoding() const return m_decoder->encoding().name(); } -void CachedXSLStyleSheet::finishLoading(ResourceBuffer* data) +void CachedXSLStyleSheet::finishLoading(SharedBuffer* data) { m_data = data; - setEncodedSize(m_data.get() ? m_data->size() : 0); - if (m_data.get()) { - m_sheet = m_decoder->decode(m_data->data(), encodedSize()); - m_sheet.append(m_decoder->flush()); - } + setEncodedSize(data ? data->size() : 0); + if (data) + m_sheet = m_decoder->decodeAndFlush(data->data(), encodedSize()); setLoading(false); checkNotify(); } diff --git a/Source/WebCore/loader/cache/CachedXSLStyleSheet.h b/Source/WebCore/loader/cache/CachedXSLStyleSheet.h index 307bda691..3ef0f600e 100644 --- a/Source/WebCore/loader/cache/CachedXSLStyleSheet.h +++ b/Source/WebCore/loader/cache/CachedXSLStyleSheet.h @@ -23,42 +23,38 @@ pages from the web. It has a memory cache for these objects. */ -#ifndef CachedXSLStyleSheet_h -#define CachedXSLStyleSheet_h +#pragma once + +#if ENABLE(XSLT) #include "CachedResource.h" -#include <wtf/Vector.h> namespace WebCore { - class CachedResourceLoader; - class TextResourceDecoder; - -#if ENABLE(XSLT) - class CachedXSLStyleSheet final : public CachedResource { - public: - CachedXSLStyleSheet(const ResourceRequest&); - - const String& sheet() const { return m_sheet; } - - protected: - virtual void checkNotify() override; +class TextResourceDecoder; - String m_sheet; - RefPtr<TextResourceDecoder> m_decoder; +class CachedXSLStyleSheet final : public CachedResource { +public: + CachedXSLStyleSheet(CachedResourceRequest&&, SessionID); + virtual ~CachedXSLStyleSheet(); - private: - virtual bool mayTryReplaceEncodedData() const override { return true; } + const String& sheet() const { return m_sheet; } - virtual void didAddClient(CachedResourceClient*) override; +private: + void checkNotify() final; + bool mayTryReplaceEncodedData() const final { return true; } + void didAddClient(CachedResourceClient&) final; + void setEncoding(const String&) final; + String encoding() const final; + const TextResourceDecoder* textResourceDecoder() const final { return m_decoder.get(); } + void finishLoading(SharedBuffer*) final; - virtual void setEncoding(const String&) override; - virtual String encoding() const override; - virtual void finishLoading(ResourceBuffer*) override; - }; + String m_sheet; + RefPtr<TextResourceDecoder> m_decoder; +}; -#endif +} // namespace WebCore -} +SPECIALIZE_TYPE_TRAITS_CACHED_RESOURCE(CachedXSLStyleSheet, CachedResource::XSLStyleSheet) -#endif +#endif // ENABLE(XSLT) diff --git a/Source/WebCore/loader/cache/MemoryCache.cpp b/Source/WebCore/loader/cache/MemoryCache.cpp index 652422cf6..75ee84bb7 100644 --- a/Source/WebCore/loader/cache/MemoryCache.cpp +++ b/Source/WebCore/loader/cache/MemoryCache.cpp @@ -25,9 +25,9 @@ #include "BitmapImage.h" #include "CachedImage.h" +#include "CachedImageClient.h" #include "CachedResource.h" #include "CachedResourceHandle.h" -#include "CrossThreadTask.h" #include "Document.h" #include "FrameLoader.h" #include "FrameLoaderTypes.h" @@ -35,166 +35,156 @@ #include "Image.h" #include "Logging.h" #include "PublicSuffix.h" -#include "SecurityOrigin.h" -#include "SecurityOriginHash.h" +#include "SharedBuffer.h" #include "WorkerGlobalScope.h" #include "WorkerLoaderProxy.h" #include "WorkerThread.h" #include <stdio.h> #include <wtf/CurrentTime.h> #include <wtf/MathExtras.h> -#include <wtf/TemporaryChange.h> +#include <wtf/NeverDestroyed.h> +#include <wtf/SetForScope.h> #include <wtf/text/CString.h> -#if ENABLE(DISK_IMAGE_CACHE) -#include "DiskImageCacheIOS.h" -#include "ResourceBuffer.h" -#endif - 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; +static const auto defaultDecodedDataDeletionInterval = std::chrono::seconds { 0 }; -MemoryCache* memoryCache() +MemoryCache& MemoryCache::singleton() { - static MemoryCache* staticCache = new MemoryCache; ASSERT(WTF::isMainThread()); - - return staticCache; + static NeverDestroyed<MemoryCache> memoryCache; + return memoryCache; } MemoryCache::MemoryCache() : m_disabled(false) - , m_pruneEnabled(true) , m_inPruneResources(false) , m_capacity(cDefaultCacheCapacity) , m_minDeadCapacity(0) , m_maxDeadCapacity(cDefaultCacheCapacity) - , m_deadDecodedDataDeletionInterval(cDefaultDecodedDataDeletionInterval) + , m_deadDecodedDataDeletionInterval(defaultDecodedDataDeletionInterval) , m_liveSize(0) , m_deadSize(0) + , m_pruneTimer(*this, &MemoryCache::prune) { + static_assert(sizeof(long long) > sizeof(unsigned), "Numerical overflow can happen when adjusting the size of the cached memory."); } -URL MemoryCache::removeFragmentIdentifierIfNeeded(const URL& originalURL) +auto MemoryCache::sessionResourceMap(SessionID sessionID) const -> CachedResourceMap* +{ + ASSERT(sessionID.isValid()); + return m_sessionResources.get(sessionID); +} + +auto MemoryCache::ensureSessionResourceMap(SessionID sessionID) -> CachedResourceMap& +{ + ASSERT(sessionID.isValid()); + auto& map = m_sessionResources.add(sessionID, nullptr).iterator->value; + if (!map) + map = std::make_unique<CachedResourceMap>(); + return *map; +} + +bool MemoryCache::shouldRemoveFragmentIdentifier(const URL& originalURL) { if (!originalURL.hasFragmentIdentifier()) - return originalURL; + return false; // Strip away fragment identifier from HTTP URLs. - // Data URLs must be unmodified. For file and custom URLs clients may expect resources + // Data URLs must be unmodified. For file and custom URLs clients may expect resources // to be unique even when they differ by the fragment identifier only. - if (!originalURL.protocolIsInHTTPFamily()) + return originalURL.protocolIsInHTTPFamily(); +} + +URL MemoryCache::removeFragmentIdentifierIfNeeded(const URL& originalURL) +{ + if (!shouldRemoveFragmentIdentifier(originalURL)) return originalURL; URL url = originalURL; url.removeFragmentIdentifier(); return url; } -bool MemoryCache::add(CachedResource* resource) +bool MemoryCache::add(CachedResource& resource) { if (disabled()) return false; ASSERT(WTF::isMainThread()); -#if ENABLE(CACHE_PARTITIONING) - CachedResourceItem* originMap = m_resources.get(resource->url()); - if (!originMap) { - originMap = new CachedResourceItem; - m_resources.set(resource->url(), adoptPtr(originMap)); - } - originMap->set(resource->cachePartition(), resource); -#else - m_resources.set(resource->url(), resource); -#endif - resource->setInCache(true); + auto key = std::make_pair(resource.url(), resource.cachePartition()); + + ensureSessionResourceMap(resource.sessionID()).set(key, &resource); + resource.setInCache(true); resourceAccessed(resource); - LOG(ResourceLoading, "MemoryCache::add Added '%s', resource %p\n", resource->url().string().latin1().data(), resource); + LOG(ResourceLoading, "MemoryCache::add Added '%s', resource %p\n", resource.url().string().latin1().data(), &resource); return true; } -void MemoryCache::revalidationSucceeded(CachedResource* revalidatingResource, const ResourceResponse& response) +void MemoryCache::revalidationSucceeded(CachedResource& revalidatingResource, const ResourceResponse& response) { - CachedResource* resource = revalidatingResource->resourceToRevalidate(); - ASSERT(resource); - ASSERT(!resource->inCache()); - ASSERT(resource->isLoaded()); - ASSERT(revalidatingResource->inCache()); + ASSERT(revalidatingResource.resourceToRevalidate()); + CachedResource& resource = *revalidatingResource.resourceToRevalidate(); + ASSERT(!resource.inCache()); + ASSERT(resource.isLoaded()); - // Calling evict() can potentially delete revalidatingResource, which we use + // Calling remove() can potentially delete revalidatingResource, which we use // below. This mustn't be the case since revalidation means it is loaded // and so canDelete() is false. - ASSERT(!revalidatingResource->canDelete()); + ASSERT(!revalidatingResource.canDelete()); - evict(revalidatingResource); + remove(revalidatingResource); -#if ENABLE(CACHE_PARTITIONING) - ASSERT(!m_resources.get(resource->url()) || !m_resources.get(resource->url())->get(resource->cachePartition())); - CachedResourceItem* originMap = m_resources.get(resource->url()); - if (!originMap) { - originMap = new CachedResourceItem; - m_resources.set(resource->url(), adoptPtr(originMap)); - } - originMap->set(resource->cachePartition(), resource); -#else - ASSERT(!m_resources.get(resource->url())); - m_resources.set(resource->url(), resource); -#endif - resource->setInCache(true); - resource->updateResponseAfterRevalidation(response); + auto& resources = ensureSessionResourceMap(resource.sessionID()); + auto key = std::make_pair(resource.url(), resource.cachePartition()); + + ASSERT(!resources.get(key)); + resources.set(key, &resource); + resource.setInCache(true); + resource.updateResponseAfterRevalidation(response); insertInLRUList(resource); - int delta = resource->size(); - if (resource->decodedSize() && resource->hasClients()) + long long delta = resource.size(); + if (resource.decodedSize() && resource.hasClients()) insertInLiveDecodedResourcesList(resource); if (delta) - adjustSize(resource->hasClients(), delta); - - revalidatingResource->switchClientsToRevalidatedResource(); - ASSERT(!revalidatingResource->m_deleted); + adjustSize(resource.hasClients(), delta); + + revalidatingResource.switchClientsToRevalidatedResource(); + ASSERT(!revalidatingResource.m_deleted); // this deletes the revalidating resource - revalidatingResource->clearResourceToRevalidate(); + revalidatingResource.clearResourceToRevalidate(); } -void MemoryCache::revalidationFailed(CachedResource* revalidatingResource) +void MemoryCache::revalidationFailed(CachedResource& revalidatingResource) { ASSERT(WTF::isMainThread()); - LOG(ResourceLoading, "Revalidation failed for %p", revalidatingResource); - ASSERT(revalidatingResource->resourceToRevalidate()); - revalidatingResource->clearResourceToRevalidate(); + LOG(ResourceLoading, "Revalidation failed for %p", &revalidatingResource); + ASSERT(revalidatingResource.resourceToRevalidate()); + revalidatingResource.clearResourceToRevalidate(); } -CachedResource* MemoryCache::resourceForURL(const URL& resourceURL) +CachedResource* MemoryCache::resourceForRequest(const ResourceRequest& request, SessionID sessionID) { - return resourceForRequest(ResourceRequest(resourceURL)); + // FIXME: Change all clients to make sure HTTP(s) URLs have no fragment identifiers before calling here. + // CachedResourceLoader is now doing this. Add an assertion once all other clients are doing it too. + auto* resources = sessionResourceMap(sessionID); + if (!resources) + return nullptr; + return resourceForRequestImpl(request, *resources); } -CachedResource* MemoryCache::resourceForRequest(const ResourceRequest& request) +CachedResource* MemoryCache::resourceForRequestImpl(const ResourceRequest& request, CachedResourceMap& resources) { ASSERT(WTF::isMainThread()); URL url = removeFragmentIdentifierIfNeeded(request.url()); -#if ENABLE(CACHE_PARTITIONING) - CachedResourceItem* item = m_resources.get(url); - CachedResource* resource = 0; - if (item) - resource = item->get(request.cachePartition()); -#else - CachedResource* resource = m_resources.get(url); -#endif - 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; + + auto key = std::make_pair(url, request.cachePartition()); + return resources.get(key); } unsigned MemoryCache::deadCapacity() const @@ -212,51 +202,46 @@ unsigned MemoryCache::liveCapacity() const return m_capacity - deadCapacity(); } -#if USE(CG) -// FIXME: Remove the USE(CG) once we either make NativeImagePtr a smart pointer on all platforms or -// remove the usage of CFRetain() in MemoryCache::addImageToCache() so as to make the code platform-independent. -bool MemoryCache::addImageToCache(NativeImagePtr image, const URL& url, const String& cachePartition) +static CachedImageClient& dummyCachedImageClient() +{ + static NeverDestroyed<CachedImageClient> client; + return client; +} + +bool MemoryCache::addImageToCache(NativeImagePtr&& image, const URL& url, const String& domainForCachePartition) { ASSERT(image); - removeImageFromCache(url, cachePartition); // Remove cache entry if it already exists. + SessionID sessionID = SessionID::defaultSessionID(); + removeImageFromCache(url, domainForCachePartition); // Remove cache entry if it already exists. - RefPtr<BitmapImage> bitmapImage = BitmapImage::create(image, nullptr); + RefPtr<BitmapImage> bitmapImage = BitmapImage::create(WTFMove(image), nullptr); if (!bitmapImage) return false; - CachedImageManual* cachedImage = new CachedImageManual(url, bitmapImage.get()); - if (!cachedImage) - return false; + auto cachedImage = std::make_unique<CachedImage>(url, bitmapImage.get(), sessionID); - // Actual release of the CGImageRef is done in BitmapImage. - CFRetain(image); - cachedImage->addFakeClient(); + cachedImage->addClient(dummyCachedImageClient()); cachedImage->setDecodedSize(bitmapImage->decodedSize()); -#if ENABLE(CACHE_PARTITIONING) - cachedImage->resourceRequest().setCachePartition(cachePartition); -#endif - add(cachedImage); - return true; + cachedImage->resourceRequest().setDomainForCachePartition(domainForCachePartition); + + return add(*cachedImage.release()); } -void MemoryCache::removeImageFromCache(const URL& url, const String& cachePartition) +void MemoryCache::removeImageFromCache(const URL& url, const String& domainForCachePartition) { -#if ENABLE(CACHE_PARTITIONING) - CachedResource* resource; - if (CachedResourceItem* item = m_resources.get(url)) - resource = item->get(ResourceRequest::partitionName(cachePartition)); - else - resource = nullptr; -#else - UNUSED_PARAM(cachePartition); - CachedResource* resource = m_resources.get(url); -#endif + auto* resources = sessionResourceMap(SessionID::defaultSessionID()); + if (!resources) + return; + + auto key = std::make_pair(url, ResourceRequest::partitionName(domainForCachePartition)); + + CachedResource* resource = resources->get(key); if (!resource) return; // A resource exists and is not a manually cached image, so just remove it. - if (!resource->isImage() || !static_cast<CachedImage*>(resource)->isManual()) { - evict(resource); + if (!is<CachedImage>(*resource) || !downcast<CachedImage>(*resource).isManuallyCached()) { + remove(*resource); return; } @@ -265,15 +250,11 @@ void MemoryCache::removeImageFromCache(const URL& url, const String& cachePartit // dead resources are pruned. That might be immediately since // removing the last client triggers a MemoryCache::prune, so the // resource may be deleted after this call. - static_cast<CachedImageManual*>(resource)->removeFakeClient(); + downcast<CachedImage>(*resource).removeClient(dummyCachedImageClient()); } -#endif void MemoryCache::pruneLiveResources(bool shouldDestroyDecodedDataForAllLiveResources) { - if (!m_pruneEnabled) - return; - unsigned capacity = shouldDestroyDecodedDataForAllLiveResources ? 0 : liveCapacity(); if (capacity && m_liveSize <= capacity) return; @@ -283,41 +264,67 @@ void MemoryCache::pruneLiveResources(bool shouldDestroyDecodedDataForAllLiveReso pruneLiveResourcesToSize(targetSize, shouldDestroyDecodedDataForAllLiveResources); } -void MemoryCache::pruneLiveResourcesToPercentage(float prunePercentage) +void MemoryCache::forEachResource(const std::function<void(CachedResource&)>& function) { - if (!m_pruneEnabled) - return; + for (auto& unprotectedLRUList : m_allResources) { + Vector<CachedResourceHandle<CachedResource>> lruList; + copyToVector(*unprotectedLRUList, lruList); + for (auto& resource : lruList) + function(*resource); + } +} - if (prunePercentage < 0.0f || prunePercentage > 0.95f) +void MemoryCache::forEachSessionResource(SessionID sessionID, const std::function<void (CachedResource&)>& function) +{ + auto it = m_sessionResources.find(sessionID); + if (it == m_sessionResources.end()) return; - unsigned currentSize = m_liveSize + m_deadSize; - unsigned targetSize = static_cast<unsigned>(currentSize * prunePercentage); + Vector<CachedResourceHandle<CachedResource>> resourcesForSession; + copyValuesToVector(*it->value, resourcesForSession); + + for (auto& resource : resourcesForSession) + function(*resource); +} - pruneLiveResourcesToSize(targetSize); +void MemoryCache::destroyDecodedDataForAllImages() +{ + MemoryCache::singleton().forEachResource([](CachedResource& resource) { + if (resource.isImage()) + resource.destroyDecodedData(); + }); } void MemoryCache::pruneLiveResourcesToSize(unsigned targetSize, bool shouldDestroyDecodedDataForAllLiveResources) { if (m_inPruneResources) return; - TemporaryChange<bool> reentrancyProtector(m_inPruneResources, true); + SetForScope<bool> reentrancyProtector(m_inPruneResources, true); double currentTime = FrameView::currentPaintTimeStamp(); if (!currentTime) // In case prune is called directly, outside of a Frame paint. currentTime = monotonicallyIncreasingTime(); // Destroy any decoded data in live objects that we can. - // Start from the tail, since this is the least recently accessed of the objects. + // Start from the head, 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; + auto it = m_liveDecodedResources.begin(); + while (it != m_liveDecodedResources.end()) { + auto* current = *it; + + // Increment the iterator now because the call to destroyDecodedData() below + // may cause a call to ListHashSet::remove() and invalidate the current + // iterator. Note that this is safe because unlike iteration of most + // WTF Hash data structures, iteration is guaranteed safe against mutation + // of the ListHashSet, except for removal of the item currently pointed to + // by a given iterator. + ++it; + ASSERT(current->hasClients()); if (current->isLoaded() && current->decodedSize()) { // Check to see if the remaining resources are too new to prune. @@ -325,23 +332,18 @@ void MemoryCache::pruneLiveResourcesToSize(unsigned targetSize, bool shouldDestr if (!shouldDestroyDecodedDataForAllLiveResources && 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. + // 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; @@ -350,135 +352,61 @@ void MemoryCache::pruneDeadResources() pruneDeadResourcesToSize(targetSize); } -void MemoryCache::pruneDeadResourcesToPercentage(float prunePercentage) -{ - if (!m_pruneEnabled) - return; - - if (prunePercentage < 0.0f || prunePercentage > 0.95f) - return; - - unsigned currentSize = m_liveSize + m_deadSize; - unsigned targetSize = static_cast<unsigned>(currentSize * prunePercentage); - - pruneDeadResourcesToSize(targetSize); -} - void MemoryCache::pruneDeadResourcesToSize(unsigned targetSize) { if (m_inPruneResources) return; - TemporaryChange<bool> reentrancyProtector(m_inPruneResources, true); - - int size = m_allResources.size(); + SetForScope<bool> reentrancyProtector(m_inPruneResources, true); - // 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; - 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; - + for (int i = m_allResources.size() - 1; i >= 0; i--) { + // Make a copy of the LRUList first (and ref the resources) as calling + // destroyDecodedData() can alter the LRUList. + Vector<CachedResourceHandle<CachedResource>> lruList; + copyToVector(*m_allResources[i], lruList); + // First flush all the decoded data in this queue. - while (current) { - // Protect 'previous' so it can't get deleted during destroyDecodedData(). - CachedResourceHandle<CachedResource> previous = current->m_prevInAllResourcesList; - ASSERT(!previous || previous->inCache()); - if (!current->hasClients() && !current->isPreloaded() && current->isLoaded()) { + // Remove from the head, since this is the least frequently accessed of the objects. + for (auto& resource : lruList) { + if (!resource->inCache()) + continue; + + if (!resource->hasClients() && !resource->isPreloaded() && resource->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(); + resource->destroyDecodedData(); if (targetSize && m_deadSize <= targetSize) return; } - // Decoded data may reference other resources. Stop iterating if 'previous' somehow got - // kicked out of cache during destroyDecodedData(). - if (previous && !previous->inCache()) - break; - current = previous.get(); } - // Now evict objects from this queue. - current = m_allResources[i].m_tail; - while (current) { - CachedResourceHandle<CachedResource> previous = current->m_prevInAllResourcesList; - ASSERT(!previous || previous->inCache()); - if (!current->hasClients() && !current->isPreloaded() && !current->isCacheValidator()) { - if (!makeResourcePurgeable(current)) - evict(current); + // Now evict objects from this list. + // Remove from the head, since this is the least frequently accessed of the objects. + for (auto& resource : lruList) { + if (!resource->inCache()) + continue; + if (!resource->hasClients() && !resource->isPreloaded() && !resource->isCacheValidator()) { + remove(*resource); if (targetSize && m_deadSize <= targetSize) return; } - if (previous && !previous->inCache()) - break; - current = previous.get(); } // Shrink the vector back down so we don't waste time inspecting // empty LRU lists on future prunes. - if (m_allResources[i].m_head) + if (!m_allResources[i]->isEmpty()) canShrinkLRULists = false; else if (canShrinkLRULists) - m_allResources.resize(i); + m_allResources.shrink(i); } } -#if ENABLE(DISK_IMAGE_CACHE) -void MemoryCache::flushCachedImagesToDisk() -{ - if (!diskImageCache().isEnabled()) - return; - -#ifndef NDEBUG - double start = WTF::currentTimeMS(); - unsigned resourceCount = 0; - unsigned cachedSize = 0; -#endif - - for (size_t i = m_allResources.size(); i; ) { - --i; - CachedResource* current = m_allResources[i].m_tail; - while (current) { - CachedResource* previous = current->m_prevInAllResourcesList; - - if (!current->isUsingDiskImageCache() && current->canUseDiskImageCache()) { - current->useDiskImageCache(); - current->destroyDecodedData(); -#ifndef NDEBUG - LOG(DiskImageCache, "Cache::diskCacheResources(): attempting to save (%d) bytes", current->resourceBuffer()->sharedBuffer()->size()); - ++resourceCount; - cachedSize += current->resourceBuffer()->sharedBuffer()->size(); -#endif - } - - current = previous; - } - } - -#ifndef NDEBUG - double end = WTF::currentTimeMS(); - LOG(DiskImageCache, "DiskImageCache: took (%f) ms to cache (%d) bytes for (%d) resources", end - start, cachedSize, resourceCount); -#endif -} -#endif // ENABLE(DISK_IMAGE_CACHE) - void MemoryCache::setCapacities(unsigned minDeadBytes, unsigned maxDeadBytes, unsigned totalBytes) { ASSERT(minDeadBytes <= maxDeadBytes); @@ -489,400 +417,269 @@ void MemoryCache::setCapacities(unsigned minDeadBytes, unsigned maxDeadBytes, un 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) +void MemoryCache::remove(CachedResource& resource) { ASSERT(WTF::isMainThread()); - LOG(ResourceLoading, "Evicting resource %p for '%s' from cache", resource, resource->url().string().latin1().data()); + LOG(ResourceLoading, "Evicting resource %p for '%s' from cache", &resource, resource.url().string().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. -#if ENABLE(CACHE_PARTITIONING) - CachedResourceItem* item = m_resources.get(resource->url()); - if (item) { - item->remove(resource->cachePartition()); - if (!item->size()) - m_resources.remove(resource->url()); - } -#else - m_resources.remove(resource->url()); -#endif - 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 -#if ENABLE(CACHE_PARTITIONING) - ASSERT(!m_resources.get(resource->url()) || m_resources.get(resource->url())->get(resource->cachePartition()) != resource); -#else - ASSERT(m_resources.get(resource->url()) != resource); -#endif + if (auto* resources = sessionResourceMap(resource.sessionID())) { + auto key = std::make_pair(resource.url(), resource.cachePartition()); + + if (resource.inCache()) { + // Remove resource from the resource map. + resources->remove(key); + resource.setInCache(false); + + // If the resource map is now empty, remove it from m_sessionResources. + if (resources->isEmpty()) + m_sessionResources.remove(resource.sessionID()); + + // Remove from the appropriate LRU list. + removeFromLRUList(resource); + removeFromLiveDecodedResourcesList(resource); + adjustSize(resource.hasClients(), -static_cast<long long>(resource.size())); + } else + ASSERT(resources->get(key) != &resource); + } - resource->deleteIfPossible(); + resource.deleteIfPossible(); } -MemoryCache::LRUList* MemoryCache::lruListFor(CachedResource* resource) +auto MemoryCache::lruListFor(CachedResource& resource) -> LRUList& { - unsigned accessCount = std::max(resource->accessCount(), 1U); - unsigned queueIndex = WTF::fastLog2(resource->size() / accessCount); + unsigned accessCount = std::max(resource.accessCount(), 1U); + unsigned queueIndex = WTF::fastLog2(resource.size() / accessCount); #ifndef NDEBUG - resource->m_lruIndex = queueIndex; + resource.m_lruIndex = queueIndex; #endif - if (m_allResources.size() <= queueIndex) - m_allResources.grow(queueIndex + 1); - return &m_allResources[queueIndex]; + + m_allResources.reserveCapacity(queueIndex + 1); + while (m_allResources.size() <= queueIndex) + m_allResources.uncheckedAppend(std::make_unique<LRUList>()); + return *m_allResources[queueIndex]; } -void MemoryCache::removeFromLRUList(CachedResource* resource) +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) + if (!resource.accessCount()) return; #if !ASSERT_DISABLED - unsigned oldListIndex = resource->m_lruIndex; + unsigned oldListIndex = resource.m_lruIndex; #endif - LRUList* list = lruListFor(resource); + 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; + ASSERT(resource.m_lruIndex == oldListIndex); - if (prev) - prev->m_nextInAllResourcesList = next; - else if (list->m_head == resource) - list->m_head = next; + bool removed = list.remove(&resource); + ASSERT_UNUSED(removed, removed); } -void MemoryCache::insertInLRUList(CachedResource* resource) +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; + ASSERT(resource.inCache()); + ASSERT(resource.accessCount() > 0); - if (!resource->m_nextInAllResourcesList) - list->m_tail = resource; - -#if !ASSERT_DISABLED - // 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 - + auto addResult = lruListFor(resource).add(&resource); + ASSERT_UNUSED(addResult, addResult.isNewEntry); } -void MemoryCache::resourceAccessed(CachedResource* resource) +void MemoryCache::resourceAccessed(CachedResource& resource) { - ASSERT(resource->inCache()); + 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()); + if (!resource.accessCount()) + adjustSize(resource.hasClients(), resource.size()); // Add to our access count. - resource->increaseAccessCount(); + resource.increaseAccessCount(); // Now insert into the new queue. insertInLRUList(resource); } -void MemoryCache::removeResourcesWithOrigin(SecurityOrigin* origin) +void MemoryCache::removeResourcesWithOrigin(SecurityOrigin& origin) { - Vector<CachedResource*> resourcesWithOrigin; - - CachedResourceMap::iterator e = m_resources.end(); -#if ENABLE(CACHE_PARTITIONING) - String originPartition = ResourceRequest::partitionName(origin->host()); -#endif + String originPartition = ResourceRequest::partitionName(origin.host()); - for (CachedResourceMap::iterator it = m_resources.begin(); it != e; ++it) { -#if ENABLE(CACHE_PARTITIONING) - for (CachedResourceItem::iterator itemIterator = it->value->begin(); itemIterator != it->value->end(); ++itemIterator) { - CachedResource* resource = itemIterator->value; - String partition = itemIterator->key; - if (partition == originPartition) { - resourcesWithOrigin.append(resource); + Vector<CachedResource*> resourcesWithOrigin; + for (auto& resources : m_sessionResources.values()) { + for (auto& keyValue : *resources) { + auto& resource = *keyValue.value; + auto& partitionName = keyValue.key.second; + if (partitionName == originPartition) { + resourcesWithOrigin.append(&resource); continue; } -#else - CachedResource* resource = it->value; -#endif - RefPtr<SecurityOrigin> resourceOrigin = SecurityOrigin::createFromString(resource->url()); - if (!resourceOrigin) - continue; - if (resourceOrigin->equal(origin)) - resourcesWithOrigin.append(resource); -#if ENABLE(CACHE_PARTITIONING) + RefPtr<SecurityOrigin> resourceOrigin = SecurityOrigin::create(resource.url()); + if (resourceOrigin->equal(&origin)) + resourcesWithOrigin.append(&resource); } -#endif } - for (size_t i = 0; i < resourcesWithOrigin.size(); ++i) - remove(resourcesWithOrigin[i]); + for (auto* resource : resourcesWithOrigin) + remove(*resource); +} + +void MemoryCache::removeResourcesWithOrigins(SessionID sessionID, const HashSet<RefPtr<SecurityOrigin>>& origins) +{ + auto* resourceMap = sessionResourceMap(sessionID); + if (!resourceMap) + return; + + HashSet<String> originPartitions; + + for (auto& origin : origins) + originPartitions.add(ResourceRequest::partitionName(origin->host())); + + Vector<CachedResource*> resourcesToRemove; + for (auto& keyValuePair : *resourceMap) { + auto& resource = *keyValuePair.value; + auto& partitionName = keyValuePair.key.second; + if (originPartitions.contains(partitionName)) { + resourcesToRemove.append(&resource); + continue; + } + if (origins.contains(SecurityOrigin::create(resource.url()).ptr())) + resourcesToRemove.append(&resource); + } + + for (auto& resource : resourcesToRemove) + remove(*resource); } void MemoryCache::getOriginsWithCache(SecurityOriginSet& origins) { -#if ENABLE(CACHE_PARTITIONING) - DEFINE_STATIC_LOCAL(String, httpString, ("http")); -#endif - CachedResourceMap::iterator e = m_resources.end(); - for (CachedResourceMap::iterator it = m_resources.begin(); it != e; ++it) { -#if ENABLE(CACHE_PARTITIONING) - if (it->value->begin()->key == emptyString()) - origins.add(SecurityOrigin::createFromString(it->value->begin()->value->url())); - else - origins.add(SecurityOrigin::create(httpString, it->value->begin()->key, 0)); -#else - origins.add(SecurityOrigin::createFromString(it->value->url())); -#endif + for (auto& resources : m_sessionResources.values()) { + for (auto& keyValue : *resources) { + auto& resource = *keyValue.value; + auto& partitionName = keyValue.key.second; + if (!partitionName.isEmpty()) + origins.add(SecurityOrigin::create(ASCIILiteral("http"), partitionName, 0)); + else + origins.add(SecurityOrigin::create(resource.url())); + } } } -void MemoryCache::removeFromLiveDecodedResourcesList(CachedResource* resource) +HashSet<RefPtr<SecurityOrigin>> MemoryCache::originsWithCache(SessionID sessionID) const { - // 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; + HashSet<RefPtr<SecurityOrigin>> origins; -#if !ASSERT_DISABLED - // 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; + auto it = m_sessionResources.find(sessionID); + if (it != m_sessionResources.end()) { + for (auto& keyValue : *it->value) { + auto& resource = *keyValue.value; + auto& partitionName = keyValue.key.second; + if (!partitionName.isEmpty()) + origins.add(SecurityOrigin::create("http", partitionName, 0)); + else + origins.add(SecurityOrigin::create(resource.url())); } } - 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; + return origins; +} - if (prev) - prev->m_nextInLiveResourcesList = next; - else if (m_liveDecodedResources.m_head == resource) - m_liveDecodedResources.m_head = next; +void MemoryCache::removeFromLiveDecodedResourcesList(CachedResource& resource) +{ + m_liveDecodedResources.remove(&resource); } -void MemoryCache::insertInLiveDecodedResourcesList(CachedResource* resource) +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; - -#if !ASSERT_DISABLED - // 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 - + ASSERT(!m_liveDecodedResources.contains(&resource)); + m_liveDecodedResources.add(&resource); } -void MemoryCache::addToLiveResourcesSize(CachedResource* resource) +void MemoryCache::addToLiveResourcesSize(CachedResource& resource) { - m_liveSize += resource->size(); - m_deadSize -= resource->size(); + m_liveSize += resource.size(); + m_deadSize -= resource.size(); } -void MemoryCache::removeFromLiveResourcesSize(CachedResource* resource) +void MemoryCache::removeFromLiveResourcesSize(CachedResource& resource) { - m_liveSize -= resource->size(); - m_deadSize += resource->size(); + m_liveSize -= resource.size(); + m_deadSize += resource.size(); } -void MemoryCache::adjustSize(bool live, int delta) +void MemoryCache::adjustSize(bool live, long long delta) { if (live) { - ASSERT(delta >= 0 || ((int)m_liveSize + delta >= 0)); + ASSERT(delta >= 0 || (static_cast<long long>(m_liveSize) + delta >= 0)); m_liveSize += delta; } else { - ASSERT(delta >= 0 || ((int)m_deadSize + delta >= 0)); + ASSERT(delta >= 0 || (static_cast<long long>(m_deadSize) + delta >= 0)); m_deadSize += delta; } } -void MemoryCache::removeUrlFromCache(ScriptExecutionContext* context, const String& urlString) -{ - removeRequestFromCache(context, ResourceRequest(urlString)); -} - -void MemoryCache::removeRequestFromCache(ScriptExecutionContext* context, const ResourceRequest& request) +void MemoryCache::removeRequestFromSessionCaches(ScriptExecutionContext& context, const ResourceRequest& request) { - if (context->isWorkerGlobalScope()) { - WorkerGlobalScope* workerGlobalScope = static_cast<WorkerGlobalScope*>(context); - workerGlobalScope->thread()->workerLoaderProxy().postTaskToLoader(createCallbackTask(&crossThreadRemoveRequestFromCache, request)); + if (is<WorkerGlobalScope>(context)) { + downcast<WorkerGlobalScope>(context).thread().workerLoaderProxy().postTaskToLoader([request = request.isolatedCopy()] (ScriptExecutionContext& context) { + MemoryCache::removeRequestFromSessionCaches(context, request); + }); return; } - removeRequestFromCacheImpl(context, request); -} - -void MemoryCache::removeRequestFromCacheImpl(ScriptExecutionContext*, const ResourceRequest& request) -{ - if (CachedResource* resource = memoryCache()->resourceForRequest(request)) - memoryCache()->remove(resource); -} - -void MemoryCache::crossThreadRemoveRequestFromCache(ScriptExecutionContext* context, PassOwnPtr<WebCore::CrossThreadResourceRequestData> requestData) -{ - OwnPtr<ResourceRequest> request(ResourceRequest::adopt(requestData)); - MemoryCache::removeRequestFromCacheImpl(context, *request); + auto& memoryCache = MemoryCache::singleton(); + for (auto& resources : memoryCache.m_sessionResources) { + if (CachedResource* resource = memoryCache.resourceForRequestImpl(request, *resources.value)) + memoryCache.remove(*resource); + } } -void MemoryCache::TypeStatistic::addResource(CachedResource* o) +void MemoryCache::TypeStatistic::addResource(CachedResource& resource) { - 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; -#if ENABLE(DISK_IMAGE_CACHE) - // Only the data inside the resource was mapped, not the entire resource. - mappedSize += o->isUsingDiskImageCache() ? o->resourceBuffer()->sharedBuffer()->size() : 0; -#endif + size += resource.size(); + liveSize += resource.hasClients() ? resource.size() : 0; + decodedSize += resource.decodedSize(); } MemoryCache::Statistics MemoryCache::getStatistics() { Statistics stats; - CachedResourceMap::iterator e = m_resources.end(); - for (CachedResourceMap::iterator i = m_resources.begin(); i != e; ++i) { -#if ENABLE(CACHE_PARTITIONING) - for (CachedResourceItem::iterator itemIterator = i->value->begin(); itemIterator != i->value->end(); ++itemIterator) { - CachedResource* resource = itemIterator->value; -#else - CachedResource* resource = i->value; -#endif + + for (auto& resources : m_sessionResources.values()) { + for (auto* resource : resources->values()) { switch (resource->type()) { case CachedResource::ImageResource: - stats.images.addResource(resource); + stats.images.addResource(*resource); break; case CachedResource::CSSStyleSheet: - stats.cssStyleSheets.addResource(resource); + stats.cssStyleSheets.addResource(*resource); break; case CachedResource::Script: - stats.scripts.addResource(resource); + stats.scripts.addResource(*resource); break; #if ENABLE(XSLT) case CachedResource::XSLStyleSheet: - stats.xslStyleSheets.addResource(resource); + stats.xslStyleSheets.addResource(*resource); break; #endif +#if ENABLE(SVG_FONTS) + case CachedResource::SVGFontResource: +#endif case CachedResource::FontResource: - stats.fonts.addResource(resource); + stats.fonts.addResource(*resource); break; default: break; } -#if ENABLE(CACHE_PARTITIONING) } -#endif } return stats; } @@ -893,16 +690,10 @@ void MemoryCache::setDisabled(bool disabled) if (!m_disabled) return; - for (;;) { - CachedResourceMap::iterator outerIterator = m_resources.begin(); - if (outerIterator == m_resources.end()) - break; -#if ENABLE(CACHE_PARTITIONING) - CachedResourceItem::iterator innerIterator = outerIterator->value->begin(); - evict(innerIterator->value); -#else - evict(outerIterator->value); -#endif + while (!m_sessionResources.isEmpty()) { + auto& resources = *m_sessionResources.begin()->value; + ASSERT(!resources.isEmpty()); + remove(*resources.begin()->value); } } @@ -915,66 +706,65 @@ void MemoryCache::evictResources() setDisabled(false); } +void MemoryCache::evictResources(SessionID sessionID) +{ + if (disabled()) + return; + + forEachSessionResource(sessionID, [this] (CachedResource& resource) { remove(resource); }); + + ASSERT(!m_sessionResources.contains(sessionID)); +} + +bool MemoryCache::needsPruning() const +{ + return m_liveSize + m_deadSize > m_capacity || m_deadSize > m_maxDeadCapacity; +} + void MemoryCache::prune() { - if (m_liveSize + m_deadSize <= m_capacity && m_deadSize <= m_maxDeadCapacity) // Fast path. + if (!needsPruning()) return; pruneDeadResources(); // Prune dead first, in case it was "borrowing" capacity from live. pruneLiveResources(); } -void MemoryCache::pruneToPercentage(float targetPercentLive) +void MemoryCache::pruneSoon() { - pruneDeadResourcesToPercentage(targetPercentLive); // Prune dead first, in case it was "borrowing" capacity from live. - pruneLiveResourcesToPercentage(targetPercentLive); + if (m_pruneTimer.isActive()) + return; + if (!needsPruning()) + return; + m_pruneTimer.startOneShot(0); } - #ifndef NDEBUG void MemoryCache::dumpStats() { Statistics s = getStatistics(); -#if ENABLE(DISK_IMAGE_CACHE) - printf("%-13s %-13s %-13s %-13s %-13s %-13s %-13s %-13s %-13s\n", "", "Count", "Size", "LiveSize", "DecodedSize", "PurgeableSize", "PurgedSize", "Mapped", "\"Real\""); - printf("%-13s %-13s %-13s %-13s %-13s %-13s %-13s %-13s %-13s\n", "-------------", "-------------", "-------------", "-------------", "-------------", "-------------", "-------------", "-------------", "-------------"); - printf("%-13s %13d %13d %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, s.images.mappedSize, s.images.size - s.images.mappedSize); -#else - 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); -#endif - 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); + printf("%-13s %-13s %-13s %-13s %-13s\n", "", "Count", "Size", "LiveSize", "DecodedSize"); + printf("%-13s %-13s %-13s %-13s %-13s\n", "-------------", "-------------", "-------------", "-------------", "-------------"); + printf("%-13s %13d %13d %13d %13d\n", "Images", s.images.count, s.images.size, s.images.liveSize, s.images.decodedSize); + printf("%-13s %13d %13d %13d %13d\n", "CSS", s.cssStyleSheets.count, s.cssStyleSheets.size, s.cssStyleSheets.liveSize, s.cssStyleSheets.decodedSize); #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); + printf("%-13s %13d %13d %13d %13d\n", "XSL", s.xslStyleSheets.count, s.xslStyleSheets.size, s.xslStyleSheets.liveSize, s.xslStyleSheets.decodedSize); #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", "-------------", "-------------", "-------------", "-------------", "-------------", "-------------", "-------------"); + printf("%-13s %13d %13d %13d %13d\n", "JavaScript", s.scripts.count, s.scripts.size, s.scripts.liveSize, s.scripts.decodedSize); + printf("%-13s %13d %13d %13d %13d\n", "Fonts", s.fonts.count, s.fonts.size, s.fonts.liveSize, s.fonts.decodedSize); + printf("%-13s %-13s %-13s %-13s %-13s\n\n", "-------------", "-------------", "-------------", "-------------", "-------------"); } void MemoryCache::dumpLRULists(bool includeLive) const { -#if ENABLE(DISK_IMAGE_CACHE) - printf("LRU-SP lists in eviction order (Kilobytes decoded, Kilobytes encoded, Access count, Referenced, isPurgeable, wasPurged, isMemoryMapped):\n"); -#else - printf("LRU-SP lists in eviction order (Kilobytes decoded, Kilobytes encoded, Access count, Referenced, isPurgeable, wasPurged):\n"); -#endif + printf("LRU-SP lists in eviction order (Kilobytes decoded, Kilobytes encoded, Access count, Referenced):\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()) -#if ENABLE(DISK_IMAGE_CACHE) - printf("(%.1fK, %.1fK, %uA, %dR, %d, %d, %d); ", current->decodedSize() / 1024.0f, (current->encodedSize() + current->overheadSize()) / 1024.0f, current->accessCount(), current->hasClients(), current->isPurgeable(), current->wasPurged(), current->isUsingDiskImageCache()); -#else - 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()); -#endif - - current = prev; + for (auto* resource : *m_allResources[i]) { + if (includeLive || !resource->hasClients()) + printf("(%.1fK, %.1fK, %uA, %dR); ", resource->decodedSize() / 1024.0f, (resource->encodedSize() + resource->overheadSize()) / 1024.0f, resource->accessCount(), resource->hasClients()); } } } diff --git a/Source/WebCore/loader/cache/MemoryCache.h b/Source/WebCore/loader/cache/MemoryCache.h index c023a9725..c5d97e1d3 100644 --- a/Source/WebCore/loader/cache/MemoryCache.h +++ b/Source/WebCore/loader/cache/MemoryCache.h @@ -22,13 +22,16 @@ pages from the web. It has a memory cache for these objects. */ -#ifndef Cache_h -#define Cache_h +#pragma once -#include "NativeImagePtr.h" +#include "NativeImage.h" #include "SecurityOriginHash.h" +#include "SessionID.h" +#include "Timer.h" +#include <wtf/Forward.h> #include <wtf/HashMap.h> #include <wtf/HashSet.h> +#include <wtf/ListHashSet.h> #include <wtf/Noncopyable.h> #include <wtf/Vector.h> #include <wtf/text/StringHash.h> @@ -36,16 +39,12 @@ namespace WebCore { -class CachedCSSStyleSheet; class CachedResource; -class CachedResourceLoader; class URL; class ResourceRequest; class ResourceResponse; class ScriptExecutionContext; class SecurityOrigin; -struct CrossThreadResourceRequestData; -struct SecurityOriginHash; // This cache holds subresources used by Web pages: images, scripts, stylesheets, etc. @@ -60,54 +59,26 @@ struct SecurityOriginHash; // -------|-----+++++++++++++++| // -------|-----+++++++++++++++|+++++ -// 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 { WTF_MAKE_NONCOPYABLE(MemoryCache); WTF_MAKE_FAST_ALLOCATED; + friend NeverDestroyed<MemoryCache>; + friend class Internals; public: - friend MemoryCache* memoryCache(); - -#if ENABLE(CACHE_PARTITIONING) - typedef HashMap<String, CachedResource*> CachedResourceItem; - typedef HashMap<String, OwnPtr<CachedResourceItem>> CachedResourceMap; -#else - typedef HashMap<String, CachedResource*> CachedResourceMap; -#endif - - 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; -#if ENABLE(DISK_IMAGE_CACHE) - int mappedSize; - TypeStatistic() : count(0), size(0), liveSize(0), decodedSize(0), purgeableSize(0), purgedSize(0), mappedSize(0) { } -#else - TypeStatistic() : count(0), size(0), liveSize(0), decodedSize(0), purgeableSize(0), purgedSize(0) { } -#endif - void addResource(CachedResource*); + + TypeStatistic() + : count(0) + , size(0) + , liveSize(0) + , decodedSize(0) + { + } + + void addResource(CachedResource&); }; struct Statistics { @@ -118,98 +89,92 @@ public: TypeStatistic fonts; }; - CachedResource* resourceForURL(const URL&); - CachedResource* resourceForRequest(const ResourceRequest&); - - bool add(CachedResource* resource); - void remove(CachedResource* resource) { evict(resource); } + WEBCORE_EXPORT static MemoryCache& singleton(); - static URL removeFragmentIdentifierIfNeeded(const URL& originalURL); - - void revalidationSucceeded(CachedResource* revalidatingResource, const ResourceResponse&); - void revalidationFailed(CachedResource* revalidatingResource); - - // Sets the cache's memory capacities, in bytes. These will hold only approximately, + WEBCORE_EXPORT CachedResource* resourceForRequest(const ResourceRequest&, SessionID); + + bool add(CachedResource&); + void remove(CachedResource&); + + static bool shouldRemoveFragmentIdentifier(const URL&); + static URL removeFragmentIdentifierIfNeeded(const URL&); + + void revalidationSucceeded(CachedResource& revalidatingResource, const ResourceResponse&); + void revalidationFailed(CachedResource& revalidatingResource); + + void forEachResource(const std::function<void(CachedResource&)>&); + void forEachSessionResource(SessionID, const std::function<void(CachedResource&)>&); + void destroyDecodedDataForAllImages(); + + // 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); + WEBCORE_EXPORT 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); + WEBCORE_EXPORT void setDisabled(bool); bool disabled() const { return m_disabled; } - void evictResources(); - - void setPruneEnabled(bool enabled) { m_pruneEnabled = enabled; } + WEBCORE_EXPORT void evictResources(); + WEBCORE_EXPORT void evictResources(SessionID); + void prune(); - void pruneToPercentage(float targetPercentLive); + void pruneSoon(); + unsigned size() const { return m_liveSize + m_deadSize; } - void setDeadDecodedDataDeletionInterval(double interval) { m_deadDecodedDataDeletionInterval = interval; } - double deadDecodedDataDeletionInterval() const { return m_deadDecodedDataDeletionInterval; } + void setDeadDecodedDataDeletionInterval(std::chrono::milliseconds interval) { m_deadDecodedDataDeletionInterval = interval; } + std::chrono::milliseconds deadDecodedDataDeletionInterval() const { return m_deadDecodedDataDeletionInterval; } // Calls to put the cached resource into and out of LRU lists. - void insertInLRUList(CachedResource*); - void removeFromLRUList(CachedResource*); + void insertInLRUList(CachedResource&); + void removeFromLRUList(CachedResource&); // Called to adjust the cache totals when a resource changes size. - void adjustSize(bool live, int delta); + void adjustSize(bool live, long long 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*); + void insertInLiveDecodedResourcesList(CachedResource&); + void removeFromLiveDecodedResourcesList(CachedResource&); - static bool shouldMakeResourcePurgeableOnEviction(); + void addToLiveResourcesSize(CachedResource&); + void removeFromLiveResourcesSize(CachedResource&); -#if ENABLE(DISK_IMAGE_CACHE) - void flushCachedImagesToDisk(); // Flush encoded data from resources still referenced by web pages. -#endif - - static void removeUrlFromCache(ScriptExecutionContext*, const String& urlString); - static void removeRequestFromCache(ScriptExecutionContext*, const ResourceRequest&); + static void removeRequestFromSessionCaches(ScriptExecutionContext&, const ResourceRequest&); // Function to collect cache statistics for the caches window in the Safari Debug menu. - Statistics getStatistics(); + WEBCORE_EXPORT Statistics getStatistics(); - void resourceAccessed(CachedResource*); + void resourceAccessed(CachedResource&); + bool inLiveDecodedResourcesList(CachedResource& resource) const { return m_liveDecodedResources.contains(&resource); } typedef HashSet<RefPtr<SecurityOrigin>> SecurityOriginSet; - void removeResourcesWithOrigin(SecurityOrigin*); - void getOriginsWithCache(SecurityOriginSet& origins); - - unsigned minDeadCapacity() const { return m_minDeadCapacity; } - unsigned maxDeadCapacity() const { return m_maxDeadCapacity; } - unsigned capacity() const { return m_capacity; } - unsigned liveSize() const { return m_liveSize; } - unsigned deadSize() const { return m_deadSize; } - -#if USE(CG) - // FIXME: Remove the USE(CG) once we either make NativeImagePtr a smart pointer on all platforms or - // remove the usage of CFRetain() in MemoryCache::addImageToCache() so as to make the code platform-independent. - bool addImageToCache(NativeImagePtr, const URL&, const String& cachePartition); - void removeImageFromCache(const URL&, const String& cachePartition); -#endif + WEBCORE_EXPORT void removeResourcesWithOrigin(SecurityOrigin&); + WEBCORE_EXPORT void removeResourcesWithOrigins(SessionID, const HashSet<RefPtr<SecurityOrigin>>&); + WEBCORE_EXPORT void getOriginsWithCache(SecurityOriginSet& origins); + WEBCORE_EXPORT HashSet<RefPtr<SecurityOrigin>> originsWithCache(SessionID) const; + + WEBCORE_EXPORT bool addImageToCache(NativeImagePtr&&, const URL&, const String& domainForCachePartition); + WEBCORE_EXPORT void removeImageFromCache(const URL&, const String& domainForCachePartition); // pruneDead*() - Flush decoded and encoded data from resources not referenced by Web pages. // pruneLive*() - Flush decoded data from resources still referenced by Web pages. - void pruneDeadResources(); // Automatically decide how much to prune. - void pruneLiveResources(bool shouldDestroyDecodedDataForAllLiveResources = false); + WEBCORE_EXPORT void pruneDeadResources(); // Automatically decide how much to prune. + WEBCORE_EXPORT void pruneLiveResources(bool shouldDestroyDecodedDataForAllLiveResources = false); + + WEBCORE_EXPORT void pruneDeadResourcesToSize(unsigned targetSize); + WEBCORE_EXPORT void pruneLiveResourcesToSize(unsigned targetSize, bool shouldDestroyDecodedDataForAllLiveResources = false); private: - void pruneDeadResourcesToPercentage(float prunePercentage); // Prune to % current size - void pruneLiveResourcesToPercentage(float prunePercentage); - void pruneDeadResourcesToSize(unsigned targetSize); - void pruneLiveResourcesToSize(unsigned targetSize, bool shouldDestroyDecodedDataForAllLiveResources = false); + typedef HashMap<std::pair<URL, String /* partitionName */>, CachedResource*> CachedResourceMap; + typedef ListHashSet<CachedResource*> LRUList; MemoryCache(); ~MemoryCache(); // Not implemented to make sure nobody accidentally calls delete -- WebCore does not delete singletons. - LRUList* lruListFor(CachedResource*); + LRUList& lruListFor(CachedResource&); #ifndef NDEBUG void dumpStats(); void dumpLRULists(bool includeLive) const; @@ -217,21 +182,20 @@ private: unsigned liveCapacity() const; unsigned deadCapacity() const; + bool needsPruning() const; - bool makeResourcePurgeable(CachedResource*); - void evict(CachedResource*); + CachedResource* resourceForRequestImpl(const ResourceRequest&, CachedResourceMap&); - static void removeRequestFromCacheImpl(ScriptExecutionContext*, const ResourceRequest&); - static void crossThreadRemoveRequestFromCache(ScriptExecutionContext*, PassOwnPtr<CrossThreadResourceRequestData>); + CachedResourceMap& ensureSessionResourceMap(SessionID); + CachedResourceMap* sessionResourceMap(SessionID) const; bool m_disabled; // Whether or not the cache is enabled. - bool m_pruneEnabled; bool m_inPruneResources; unsigned m_capacity; unsigned m_minDeadCapacity; unsigned m_maxDeadCapacity; - double m_deadDecodedDataDeletionInterval; + std::chrono::milliseconds 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. @@ -239,28 +203,17 @@ private: // 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; + Vector<std::unique_ptr<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). - CachedResourceMap m_resources; -}; - -inline bool MemoryCache::shouldMakeResourcePurgeableOnEviction() -{ -#if PLATFORM(IOS) - return true; -#else - return false; -#endif -} + typedef HashMap<SessionID, std::unique_ptr<CachedResourceMap>> SessionCachedResourceMap; + SessionCachedResourceMap m_sessionResources; -// Function to obtain the global cache. -MemoryCache* memoryCache(); - -} + Timer m_pruneTimer; +}; -#endif +} // namespace WebCore |