diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2018-05-15 10:20:33 +0200 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2018-05-15 10:28:57 +0000 |
commit | d17ea114e5ef69ad5d5d7413280a13e6428098aa (patch) | |
tree | 2c01a75df69f30d27b1432467cfe7c1467a498da /chromium/third_party/blink/renderer/core/loader/resource | |
parent | 8c5c43c7b138c9b4b0bf56d946e61d3bbc111bec (diff) | |
download | qtwebengine-chromium-d17ea114e5ef69ad5d5d7413280a13e6428098aa.tar.gz |
BASELINE: Update Chromium to 67.0.3396.47
Change-Id: Idcb1341782e417561a2473eeecc82642dafda5b7
Reviewed-by: Michal Klocek <michal.klocek@qt.io>
Diffstat (limited to 'chromium/third_party/blink/renderer/core/loader/resource')
30 files changed, 6801 insertions, 0 deletions
diff --git a/chromium/third_party/blink/renderer/core/loader/resource/css_style_sheet_resource.cc b/chromium/third_party/blink/renderer/core/loader/resource/css_style_sheet_resource.cc new file mode 100644 index 00000000000..2c147e68e22 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/resource/css_style_sheet_resource.cc @@ -0,0 +1,256 @@ +/* + Copyright (C) 1998 Lars Knoll (knoll@mpi-hd.mpg.de) + Copyright (C) 2001 Dirk Mueller (mueller@kde.org) + Copyright (C) 2002 Waldo Bastian (bastian@kde.org) + Copyright (C) 2006 Samuel Weinig (sam.weinig@gmail.com) + Copyright (C) 2004, 2005, 2006 Apple Computer, Inc. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. + + This class provides all functionality needed for loading images, style + sheets and html pages from the web. It has a memory cache for these objects. +*/ + +#include "third_party/blink/renderer/core/loader/resource/css_style_sheet_resource.h" + +#include "services/network/public/mojom/request_context_frame_type.mojom-blink.h" +#include "third_party/blink/renderer/core/css/style_sheet_contents.h" +#include "third_party/blink/renderer/core/frame/web_feature.h" +#include "third_party/blink/renderer/platform/loader/fetch/fetch_parameters.h" +#include "third_party/blink/renderer/platform/loader/fetch/memory_cache.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource_loader_options.h" +#include "third_party/blink/renderer/platform/loader/fetch/text_resource_decoder_options.h" +#include "third_party/blink/renderer/platform/network/http_names.h" +#include "third_party/blink/renderer/platform/network/mime/mime_type_registry.h" +#include "third_party/blink/renderer/platform/runtime_enabled_features.h" +#include "third_party/blink/renderer/platform/weborigin/security_policy.h" +#include "third_party/blink/renderer/platform/wtf/text/text_encoding.h" + +namespace blink { + +CSSStyleSheetResource* CSSStyleSheetResource::Fetch(FetchParameters& params, + ResourceFetcher* fetcher, + ResourceClient* client) { + DCHECK_EQ(params.GetResourceRequest().GetFrameType(), + network::mojom::RequestContextFrameType::kNone); + params.SetRequestContext(WebURLRequest::kRequestContextStyle); + CSSStyleSheetResource* resource = ToCSSStyleSheetResource( + fetcher->RequestResource(params, CSSStyleSheetResourceFactory(), client)); + return resource; +} + +CSSStyleSheetResource* CSSStyleSheetResource::CreateForTest( + const KURL& url, + const WTF::TextEncoding& encoding) { + ResourceRequest request(url); + request.SetFetchCredentialsMode(network::mojom::FetchCredentialsMode::kOmit); + ResourceLoaderOptions options; + TextResourceDecoderOptions decoder_options( + TextResourceDecoderOptions::kCSSContent, encoding); + return new CSSStyleSheetResource(request, options, decoder_options); +} + +CSSStyleSheetResource::CSSStyleSheetResource( + const ResourceRequest& resource_request, + const ResourceLoaderOptions& options, + const TextResourceDecoderOptions& decoder_options) + : TextResource(resource_request, kCSSStyleSheet, options, decoder_options) { +} + +CSSStyleSheetResource::~CSSStyleSheetResource() = default; + +void CSSStyleSheetResource::SetParsedStyleSheetCache( + StyleSheetContents* new_sheet) { + if (parsed_style_sheet_cache_) + parsed_style_sheet_cache_->ClearReferencedFromResource(); + parsed_style_sheet_cache_ = new_sheet; + if (parsed_style_sheet_cache_) + parsed_style_sheet_cache_->SetReferencedFromResource(this); + + // Updates the decoded size to take parsed stylesheet cache into account. + UpdateDecodedSize(); +} + +void CSSStyleSheetResource::Trace(blink::Visitor* visitor) { + visitor->Trace(parsed_style_sheet_cache_); + TextResource::Trace(visitor); +} + +ReferrerPolicy CSSStyleSheetResource::GetReferrerPolicy() const { + ReferrerPolicy referrer_policy = kReferrerPolicyDefault; + String referrer_policy_header = + GetResponse().HttpHeaderField(HTTPNames::Referrer_Policy); + if (!referrer_policy_header.IsNull()) { + SecurityPolicy::ReferrerPolicyFromHeaderValue( + referrer_policy_header, kDoNotSupportReferrerPolicyLegacyKeywords, + &referrer_policy); + } + return referrer_policy; +} + +const String CSSStyleSheetResource::SheetText( + const CSSParserContext* parser_context, + MIMETypeCheck mime_type_check) const { + if (!CanUseSheet(parser_context, mime_type_check)) + return String(); + + // Use cached decoded sheet text when available + if (!decoded_sheet_text_.IsNull()) { + // We should have the decoded sheet text cached when the resource is fully + // loaded. + DCHECK_EQ(GetStatus(), ResourceStatus::kCached); + + return decoded_sheet_text_; + } + + if (!Data() || Data()->IsEmpty()) + return String(); + + return DecodedText(); +} + +void CSSStyleSheetResource::NotifyFinished() { + // Decode the data to find out the encoding and cache the decoded sheet text. + if (Data()) + SetDecodedSheetText(DecodedText()); + + Resource::NotifyFinished(); + + // Clear raw bytes as now we have the full decoded sheet text. + // We wait for all LinkStyle::setCSSStyleSheet to run (at least once) + // as SubresourceIntegrity checks require raw bytes. + // Note that LinkStyle::setCSSStyleSheet can be called from didAddClient too, + // but is safe as we should have a cached ResourceIntegrityDisposition. + ClearData(); +} + +void CSSStyleSheetResource::DestroyDecodedDataIfPossible() { + if (!parsed_style_sheet_cache_) + return; + + SetParsedStyleSheetCache(nullptr); +} + +void CSSStyleSheetResource::DestroyDecodedDataForFailedRevalidation() { + SetDecodedSheetText(String()); + DestroyDecodedDataIfPossible(); +} + +bool CSSStyleSheetResource::CanUseSheet(const CSSParserContext* parser_context, + MIMETypeCheck mime_type_check) const { + if (ErrorOccurred()) + return false; + + // For `file:` URLs, we may need to be a little more strict than the below. + // Though we'll likely change this in the future, for the moment we're going + // to enforce a file-extension requirement on stylesheets loaded from `file:` + // URLs and see how far it gets us. + KURL sheet_url = GetResponse().Url(); + if (sheet_url.IsLocalFile()) { + if (parser_context) { + parser_context->Count(WebFeature::kLocalCSSFile); + } + // Grab |sheet_url|'s filename's extension (if present), and check whether + // or not it maps to a `text/css` MIME type: + String extension; + int last_dot = sheet_url.LastPathComponent().ReverseFind('.'); + if (last_dot != -1) + extension = sheet_url.LastPathComponent().Substring(last_dot + 1); + if (!EqualIgnoringASCIICase( + MIMETypeRegistry::GetMIMETypeForExtension(extension), "text/css")) { + if (parser_context) { + parser_context->CountDeprecation( + WebFeature::kLocalCSSFileExtensionRejected); + } + if (RuntimeEnabledFeatures::RequireCSSExtensionForFileEnabled()) { + return false; + } + } + } + + // This check exactly matches Firefox. Note that we grab the Content-Type + // header directly because we want to see what the value is BEFORE content + // sniffing. Firefox does this by setting a "type hint" on the channel. This + // implementation should be observationally equivalent. + // + // This code defaults to allowing the stylesheet for non-HTTP protocols so + // folks can use standards mode for local HTML documents. + if (mime_type_check == MIMETypeCheck::kLax) + return true; + AtomicString content_type = HttpContentType(); + return content_type.IsEmpty() || + DeprecatedEqualIgnoringCase(content_type, "text/css") || + DeprecatedEqualIgnoringCase(content_type, + "application/x-unknown-content-type"); +} + +StyleSheetContents* CSSStyleSheetResource::CreateParsedStyleSheetFromCache( + const CSSParserContext* context) { + if (!parsed_style_sheet_cache_) + return nullptr; + if (parsed_style_sheet_cache_->HasFailedOrCanceledSubresources()) { + SetParsedStyleSheetCache(nullptr); + return nullptr; + } + + DCHECK(parsed_style_sheet_cache_->IsCacheableForResource()); + DCHECK(parsed_style_sheet_cache_->IsReferencedFromResource()); + + // Contexts must be identical so we know we would get the same exact result if + // we parsed again. + if (*parsed_style_sheet_cache_->ParserContext() != *context) + return nullptr; + + DCHECK(!parsed_style_sheet_cache_->IsLoading()); + + // If the stylesheet has a media query, we need to clone the cached sheet + // due to potential differences in the rule set. + if (RuntimeEnabledFeatures::CacheStyleSheetWithMediaQueriesEnabled() && + parsed_style_sheet_cache_->HasMediaQueries()) { + return parsed_style_sheet_cache_->Copy(); + } + + return parsed_style_sheet_cache_; +} + +void CSSStyleSheetResource::SaveParsedStyleSheet(StyleSheetContents* sheet) { + DCHECK(sheet); + DCHECK(sheet->IsCacheableForResource()); + + if (!GetMemoryCache()->Contains(this)) { + // This stylesheet resource did conflict with another resource and was not + // added to the cache. + SetParsedStyleSheetCache(nullptr); + return; + } + SetParsedStyleSheetCache(sheet); +} + +void CSSStyleSheetResource::SetDecodedSheetText( + const String& decoded_sheet_text) { + decoded_sheet_text_ = decoded_sheet_text; + UpdateDecodedSize(); +} + +void CSSStyleSheetResource::UpdateDecodedSize() { + size_t decoded_size = decoded_sheet_text_.CharactersSizeInBytes(); + if (parsed_style_sheet_cache_) + decoded_size += parsed_style_sheet_cache_->EstimatedSizeInBytes(); + SetDecodedSize(decoded_size); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/loader/resource/css_style_sheet_resource.h b/chromium/third_party/blink/renderer/core/loader/resource/css_style_sheet_resource.h new file mode 100644 index 00000000000..7d97a8d6ec5 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/resource/css_style_sheet_resource.h @@ -0,0 +1,101 @@ +/* + Copyright (C) 1998 Lars Knoll (knoll@mpi-hd.mpg.de) + Copyright (C) 2001 Dirk Mueller <mueller@kde.org> + Copyright (C) 2006 Samuel Weinig (sam.weinig@gmail.com) + Copyright (C) 2004, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. + + This class provides all functionality needed for loading images, style + sheets and html pages from the web. It has a memory cache for these objects. +*/ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_RESOURCE_CSS_STYLE_SHEET_RESOURCE_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_RESOURCE_CSS_STYLE_SHEET_RESOURCE_H_ + +#include "third_party/blink/renderer/core/core_export.h" +#include "third_party/blink/renderer/core/loader/resource/text_resource.h" +#include "third_party/blink/renderer/platform/heap/handle.h" +#include "third_party/blink/renderer/platform/loader/fetch/text_resource_decoder_options.h" +#include "third_party/blink/renderer/platform/wtf/text/text_encoding.h" + +namespace blink { + +class CSSParserContext; +class FetchParameters; +class KURL; +class ResourceFetcher; +class StyleSheetContents; + +class CORE_EXPORT CSSStyleSheetResource final : public TextResource { + public: + enum class MIMETypeCheck { kStrict, kLax }; + + static CSSStyleSheetResource* Fetch(FetchParameters&, + ResourceFetcher*, + ResourceClient*); + static CSSStyleSheetResource* CreateForTest(const KURL&, + const WTF::TextEncoding&); + + ~CSSStyleSheetResource() override; + void Trace(blink::Visitor*) override; + + const String SheetText(const CSSParserContext*, + MIMETypeCheck = MIMETypeCheck::kStrict) const; + StyleSheetContents* CreateParsedStyleSheetFromCache(const CSSParserContext*); + void SaveParsedStyleSheet(StyleSheetContents*); + ReferrerPolicy GetReferrerPolicy() const; + + private: + class CSSStyleSheetResourceFactory : public ResourceFactory { + public: + CSSStyleSheetResourceFactory() + : ResourceFactory(Resource::kCSSStyleSheet, + TextResourceDecoderOptions::kCSSContent) {} + + Resource* Create( + const ResourceRequest& request, + const ResourceLoaderOptions& options, + const TextResourceDecoderOptions& decoder_options) const override { + return new CSSStyleSheetResource(request, options, decoder_options); + } + }; + CSSStyleSheetResource(const ResourceRequest&, + const ResourceLoaderOptions&, + const TextResourceDecoderOptions&); + + bool CanUseSheet(const CSSParserContext*, MIMETypeCheck) const; + void NotifyFinished() override; + + void SetParsedStyleSheetCache(StyleSheetContents*); + void SetDecodedSheetText(const String&); + + void DestroyDecodedDataIfPossible() override; + void DestroyDecodedDataForFailedRevalidation() override; + void UpdateDecodedSize(); + + // Decoded sheet text cache is available iff loading this CSS resource is + // successfully complete. + String decoded_sheet_text_; + + Member<StyleSheetContents> parsed_style_sheet_cache_; +}; + +DEFINE_RESOURCE_TYPE_CASTS(CSSStyleSheet); + +} // namespace blink + +#endif diff --git a/chromium/third_party/blink/renderer/core/loader/resource/css_style_sheet_resource_test.cc b/chromium/third_party/blink/renderer/core/loader/resource/css_style_sheet_resource_test.cc new file mode 100644 index 00000000000..aad3c092971 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/resource/css_style_sheet_resource_test.cc @@ -0,0 +1,178 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "third_party/blink/renderer/core/loader/resource/css_style_sheet_resource.h" + +#include <memory> +#include "base/memory/scoped_refptr.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/blink/public/platform/platform.h" +#include "third_party/blink/public/platform/web_url_response.h" +#include "third_party/blink/renderer/core/css/css_crossfade_value.h" +#include "third_party/blink/renderer/core/css/css_image_value.h" +#include "third_party/blink/renderer/core/css/css_primitive_value.h" +#include "third_party/blink/renderer/core/css/css_property_value.h" +#include "third_party/blink/renderer/core/css/css_property_value_set.h" +#include "third_party/blink/renderer/core/css/css_selector_list.h" +#include "third_party/blink/renderer/core/css/css_style_sheet.h" +#include "third_party/blink/renderer/core/css/parser/css_parser_context.h" +#include "third_party/blink/renderer/core/css/parser/css_parser_selector.h" +#include "third_party/blink/renderer/core/css/style_rule.h" +#include "third_party/blink/renderer/core/css/style_sheet_contents.h" +#include "third_party/blink/renderer/core/dom/document.h" +#include "third_party/blink/renderer/core/loader/resource/image_resource.h" +#include "third_party/blink/renderer/core/testing/page_test_base.h" +#include "third_party/blink/renderer/platform/heap/handle.h" +#include "third_party/blink/renderer/platform/heap/heap.h" +#include "third_party/blink/renderer/platform/loader/fetch/fetch_context.h" +#include "third_party/blink/renderer/platform/loader/fetch/fetch_initiator_type_names.h" +#include "third_party/blink/renderer/platform/loader/fetch/memory_cache.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource_request.h" +#include "third_party/blink/renderer/platform/testing/unit_test_helpers.h" +#include "third_party/blink/renderer/platform/testing/url_test_helpers.h" +#include "third_party/blink/renderer/platform/weborigin/kurl.h" +#include "third_party/blink/renderer/platform/wtf/text/text_encoding.h" +#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h" + +namespace blink { + +class Document; + +namespace { + +class CSSStyleSheetResourceTest : public PageTestBase { + protected: + CSSStyleSheetResourceTest() { + original_memory_cache_ = + ReplaceMemoryCacheForTesting(MemoryCache::Create()); + } + + ~CSSStyleSheetResourceTest() override { + ReplaceMemoryCacheForTesting(original_memory_cache_.Release()); + } + + void SetUp() override { + PageTestBase::SetUp(IntSize()); + GetDocument().SetURL(KURL("https://localhost/")); + } + + CSSStyleSheetResource* CreateAndSaveTestStyleSheetResource() { + const char kUrl[] = "https://localhost/style.css"; + const KURL css_url(kUrl); + + CSSStyleSheetResource* css_resource = + CSSStyleSheetResource::CreateForTest(css_url, UTF8Encoding()); + css_resource->ResponseReceived(ResourceResponse(css_url, "style/css"), + nullptr); + css_resource->FinishForTest(); + GetMemoryCache()->Add(css_resource); + return css_resource; + } + + Persistent<MemoryCache> original_memory_cache_; +}; + +TEST_F(CSSStyleSheetResourceTest, DuplicateResourceNotCached) { + const char kUrl[] = "https://localhost/style.css"; + const KURL image_url(kUrl); + const KURL css_url(kUrl); + + // Emulate using <img> to do async stylesheet preloads. + + Resource* image_resource = ImageResource::CreateForTest(image_url); + ASSERT_TRUE(image_resource); + GetMemoryCache()->Add(image_resource); + ASSERT_TRUE(GetMemoryCache()->Contains(image_resource)); + + CSSStyleSheetResource* css_resource = + CSSStyleSheetResource::CreateForTest(css_url, UTF8Encoding()); + css_resource->ResponseReceived(ResourceResponse(css_url, "style/css"), + nullptr); + css_resource->FinishForTest(); + + CSSParserContext* parser_context = CSSParserContext::Create( + kHTMLStandardMode, SecureContextMode::kInsecureContext); + StyleSheetContents* contents = StyleSheetContents::Create(parser_context); + CSSStyleSheet* sheet = CSSStyleSheet::Create(contents, GetDocument()); + EXPECT_TRUE(sheet); + + contents->CheckLoaded(); + css_resource->SaveParsedStyleSheet(contents); + + // Verify that the cache will have a mapping for |imageResource| at |url|. + // The underlying |contents| for the stylesheet resource must have a + // matching reference status. + EXPECT_TRUE(GetMemoryCache()->Contains(image_resource)); + EXPECT_FALSE(GetMemoryCache()->Contains(css_resource)); + EXPECT_FALSE(contents->IsReferencedFromResource()); + EXPECT_FALSE(css_resource->CreateParsedStyleSheetFromCache(parser_context)); +} + +TEST_F(CSSStyleSheetResourceTest, CreateFromCacheRestoresOriginalSheet) { + CSSStyleSheetResource* css_resource = CreateAndSaveTestStyleSheetResource(); + + CSSParserContext* parser_context = CSSParserContext::Create( + kHTMLStandardMode, SecureContextMode::kInsecureContext); + StyleSheetContents* contents = StyleSheetContents::Create(parser_context); + CSSStyleSheet* sheet = CSSStyleSheet::Create(contents, GetDocument()); + ASSERT_TRUE(sheet); + + contents->ParseString("div { color: red; }"); + contents->NotifyLoadedSheet(css_resource); + contents->CheckLoaded(); + EXPECT_TRUE(contents->IsCacheableForResource()); + + css_resource->SaveParsedStyleSheet(contents); + EXPECT_TRUE(GetMemoryCache()->Contains(css_resource)); + EXPECT_TRUE(contents->IsReferencedFromResource()); + + StyleSheetContents* parsed_stylesheet = + css_resource->CreateParsedStyleSheetFromCache(parser_context); + ASSERT_EQ(contents, parsed_stylesheet); +} + +TEST_F(CSSStyleSheetResourceTest, + CreateFromCacheWithMediaQueriesCopiesOriginalSheet) { + CSSStyleSheetResource* css_resource = CreateAndSaveTestStyleSheetResource(); + + CSSParserContext* parser_context = CSSParserContext::Create( + kHTMLStandardMode, SecureContextMode::kInsecureContext); + StyleSheetContents* contents = StyleSheetContents::Create(parser_context); + CSSStyleSheet* sheet = CSSStyleSheet::Create(contents, GetDocument()); + ASSERT_TRUE(sheet); + + contents->ParseString("@media { div { color: red; } }"); + contents->NotifyLoadedSheet(css_resource); + contents->CheckLoaded(); + EXPECT_TRUE(contents->IsCacheableForResource()); + + contents->EnsureRuleSet(MediaQueryEvaluator(), kRuleHasNoSpecialState); + EXPECT_TRUE(contents->HasRuleSet()); + + css_resource->SaveParsedStyleSheet(contents); + EXPECT_TRUE(GetMemoryCache()->Contains(css_resource)); + EXPECT_TRUE(contents->IsReferencedFromResource()); + + StyleSheetContents* parsed_stylesheet = + css_resource->CreateParsedStyleSheetFromCache(parser_context); + ASSERT_TRUE(parsed_stylesheet); + + sheet->ClearOwnerNode(); + sheet = CSSStyleSheet::Create(parsed_stylesheet, GetDocument()); + ASSERT_TRUE(sheet); + + EXPECT_TRUE(contents->HasSingleOwnerDocument()); + EXPECT_EQ(0U, contents->ClientSize()); + EXPECT_TRUE(contents->IsReferencedFromResource()); + EXPECT_TRUE(contents->HasRuleSet()); + + EXPECT_TRUE(parsed_stylesheet->HasSingleOwnerDocument()); + EXPECT_TRUE(parsed_stylesheet->HasOneClient()); + EXPECT_FALSE(parsed_stylesheet->IsReferencedFromResource()); + EXPECT_FALSE(parsed_stylesheet->HasRuleSet()); +} + +} // namespace +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/loader/resource/document_resource.cc b/chromium/third_party/blink/renderer/core/loader/resource/document_resource.cc new file mode 100644 index 00000000000..b10e13e7177 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/resource/document_resource.cc @@ -0,0 +1,92 @@ +/* + Copyright (C) 2010 Rob Buis <rwlbuis@gmail.com> + Copyright (C) 2011 Cosmin Truta <ctruta@gmail.com> + Copyright (C) 2012 University of Szeged + Copyright (C) 2012 Renata Hodovan <reni@webkit.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "third_party/blink/renderer/core/loader/resource/document_resource.h" + +#include "services/network/public/mojom/request_context_frame_type.mojom-blink.h" +#include "third_party/blink/renderer/core/dom/xml_document.h" +#include "third_party/blink/renderer/platform/loader/fetch/fetch_parameters.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h" +#include "third_party/blink/renderer/platform/loader/fetch/text_resource_decoder_options.h" +#include "third_party/blink/renderer/platform/shared_buffer.h" +#include "third_party/blink/renderer/platform/wtf/text/string_builder.h" + +namespace blink { + +DocumentResource* DocumentResource::FetchSVGDocument(FetchParameters& params, + ResourceFetcher* fetcher, + ResourceClient* client) { + DCHECK_EQ(params.GetResourceRequest().GetFrameType(), + network::mojom::RequestContextFrameType::kNone); + params.SetRequestContext(WebURLRequest::kRequestContextImage); + return ToDocumentResource( + fetcher->RequestResource(params, SVGDocumentResourceFactory(), client)); +} + +DocumentResource::DocumentResource( + const ResourceRequest& request, + Type type, + const ResourceLoaderOptions& options, + const TextResourceDecoderOptions& decoder_options) + : TextResource(request, type, options, decoder_options) { + // FIXME: We'll support more types to support HTMLImports. + DCHECK_EQ(type, kSVGDocument); +} + +DocumentResource::~DocumentResource() = default; + +void DocumentResource::Trace(blink::Visitor* visitor) { + visitor->Trace(document_); + Resource::Trace(visitor); +} + +void DocumentResource::NotifyFinished() { + if (Data() && MimeTypeAllowed()) { + // We don't need to create a new frame because the new document belongs to + // the parent UseElement. + document_ = CreateDocument(GetResponse().Url()); + document_->SetContent(DecodedText()); + } + Resource::NotifyFinished(); +} + +bool DocumentResource::MimeTypeAllowed() const { + DCHECK_EQ(GetType(), kSVGDocument); + AtomicString mime_type = GetResponse().MimeType(); + if (GetResponse().IsHTTP()) + mime_type = HttpContentType(); + return mime_type == "image/svg+xml" || mime_type == "text/xml" || + mime_type == "application/xml" || mime_type == "application/xhtml+xml"; +} + +Document* DocumentResource::CreateDocument(const KURL& url) { + switch (GetType()) { + case kSVGDocument: + return XMLDocument::CreateSVG(DocumentInit::Create().WithURL(url)); + default: + // FIXME: We'll add more types to support HTMLImports. + NOTREACHED(); + return nullptr; + } +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/loader/resource/document_resource.h b/chromium/third_party/blink/renderer/core/loader/resource/document_resource.h new file mode 100644 index 00000000000..019cb11cd03 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/resource/document_resource.h @@ -0,0 +1,85 @@ +/* + Copyright (C) 2010 Rob Buis <rwlbuis@gmail.com> + Copyright (C) 2011 Cosmin Truta <ctruta@gmail.com> + Copyright (C) 2012 University of Szeged + Copyright (C) 2012 Renata Hodovan <reni@webkit.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_RESOURCE_DOCUMENT_RESOURCE_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_RESOURCE_DOCUMENT_RESOURCE_H_ + +#include <memory> +#include "third_party/blink/renderer/core/loader/resource/text_resource.h" +#include "third_party/blink/renderer/platform/heap/handle.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource_client.h" +#include "third_party/blink/renderer/platform/loader/fetch/text_resource_decoder_options.h" + +namespace blink { + +class Document; +class FetchParameters; +class ResourceFetcher; + +class CORE_EXPORT DocumentResource final : public TextResource { + public: + static DocumentResource* FetchSVGDocument(FetchParameters&, + ResourceFetcher*, + ResourceClient*); + ~DocumentResource() override; + void Trace(blink::Visitor*) override; + + Document* GetDocument() const { return document_.Get(); } + + void NotifyFinished() override; + + private: + class SVGDocumentResourceFactory : public ResourceFactory { + public: + SVGDocumentResourceFactory() + : ResourceFactory(Resource::kSVGDocument, + TextResourceDecoderOptions::kXMLContent) {} + + Resource* Create( + const ResourceRequest& request, + const ResourceLoaderOptions& options, + const TextResourceDecoderOptions& decoder_options) const override { + return new DocumentResource(request, Resource::kSVGDocument, options, + decoder_options); + } + }; + DocumentResource(const ResourceRequest&, + Type, + const ResourceLoaderOptions&, + const TextResourceDecoderOptions&); + + bool MimeTypeAllowed() const; + Document* CreateDocument(const KURL&); + + Member<Document> document_; +}; + +DEFINE_TYPE_CASTS(DocumentResource, + Resource, + resource, + resource->GetType() == Resource::kSVGDocument, + resource.GetType() == Resource::kSVGDocument); + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_RESOURCE_DOCUMENT_RESOURCE_H_ diff --git a/chromium/third_party/blink/renderer/core/loader/resource/font_resource.cc b/chromium/third_party/blink/renderer/core/loader/resource/font_resource.cc new file mode 100644 index 00000000000..5691f6eadb8 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/resource/font_resource.cc @@ -0,0 +1,243 @@ +/* + * Copyright (C) 2006, 2007, 2008 Apple Inc. All rights reserved. + * Copyright (C) 2009 Torch Mobile, Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "third_party/blink/renderer/core/loader/resource/font_resource.h" + +#include "services/network/public/mojom/request_context_frame_type.mojom-blink.h" +#include "third_party/blink/renderer/platform/fonts/font_custom_platform_data.h" +#include "third_party/blink/renderer/platform/fonts/font_platform_data.h" +#include "third_party/blink/renderer/platform/histogram.h" +#include "third_party/blink/renderer/platform/loader/fetch/fetch_parameters.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource_client_walker.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource_loader.h" +#include "third_party/blink/renderer/platform/shared_buffer.h" +#include "third_party/blink/renderer/platform/wtf/time.h" + +namespace blink { + +// Durations of font-display periods. +// https://tabatkins.github.io/specs/css-font-display/#font-display-desc +// TODO(toyoshim): Revisit short limit value once cache-aware font display is +// launched. crbug.com/570205 +constexpr TimeDelta kFontLoadWaitShort = TimeDelta::FromMilliseconds(100); +constexpr TimeDelta kFontLoadWaitLong = TimeDelta::FromMilliseconds(3000); + +enum FontPackageFormat { + kPackageFormatUnknown, + kPackageFormatSFNT, + kPackageFormatWOFF, + kPackageFormatWOFF2, + kPackageFormatSVG, + kPackageFormatEnumMax +}; + +static FontPackageFormat PackageFormatOf(SharedBuffer* buffer) { + static constexpr size_t kMaxHeaderSize = 4; + char data[kMaxHeaderSize]; + if (!buffer->GetBytes(data, kMaxHeaderSize)) + return kPackageFormatUnknown; + + if (data[0] == 'w' && data[1] == 'O' && data[2] == 'F' && data[3] == 'F') + return kPackageFormatWOFF; + if (data[0] == 'w' && data[1] == 'O' && data[2] == 'F' && data[3] == '2') + return kPackageFormatWOFF2; + return kPackageFormatSFNT; +} + +static void RecordPackageFormatHistogram(FontPackageFormat format) { + DEFINE_THREAD_SAFE_STATIC_LOCAL( + EnumerationHistogram, package_format_histogram, + ("WebFont.PackageFormat", kPackageFormatEnumMax)); + package_format_histogram.Count(format); +} + +FontResource* FontResource::Fetch(FetchParameters& params, + ResourceFetcher* fetcher, + FontResourceClient* client) { + DCHECK_EQ(params.GetResourceRequest().GetFrameType(), + network::mojom::RequestContextFrameType::kNone); + params.SetRequestContext(WebURLRequest::kRequestContextFont); + return ToFontResource( + fetcher->RequestResource(params, FontResourceFactory(), client)); +} + +FontResource::FontResource(const ResourceRequest& resource_request, + const ResourceLoaderOptions& options) + : Resource(resource_request, kFont, options), + load_limit_state_(kLoadNotStarted), + cors_failed_(false) {} + +FontResource::~FontResource() = default; + +void FontResource::DidAddClient(ResourceClient* c) { + DCHECK(FontResourceClient::IsExpectedType(c)); + Resource::DidAddClient(c); + + // Block client callbacks if currently loading from cache. + if (IsLoading() && Loader()->IsCacheAwareLoadingActivated()) + return; + + ProhibitAddRemoveClientInScope prohibit_add_remove_client(this); + if (load_limit_state_ == kShortLimitExceeded || + load_limit_state_ == kLongLimitExceeded) + static_cast<FontResourceClient*>(c)->FontLoadShortLimitExceeded(this); + if (load_limit_state_ == kLongLimitExceeded) + static_cast<FontResourceClient*>(c)->FontLoadLongLimitExceeded(this); +} + +void FontResource::SetRevalidatingRequest(const ResourceRequest& request) { + // Reload will use the same object, and needs to reset |m_loadLimitState| + // before any didAddClient() is called again. + DCHECK(IsLoaded()); + DCHECK(!font_load_short_limit_.IsActive()); + DCHECK(!font_load_long_limit_.IsActive()); + load_limit_state_ = kLoadNotStarted; + Resource::SetRevalidatingRequest(request); +} + +void FontResource::StartLoadLimitTimers( + base::SingleThreadTaskRunner* task_runner) { + DCHECK(IsLoading()); + DCHECK_EQ(load_limit_state_, kLoadNotStarted); + load_limit_state_ = kUnderLimit; + + font_load_short_limit_ = PostDelayedCancellableTask( + *task_runner, FROM_HERE, + WTF::Bind(&FontResource::FontLoadShortLimitCallback, + WrapWeakPersistent(this)), + kFontLoadWaitShort); + font_load_long_limit_ = PostDelayedCancellableTask( + *task_runner, FROM_HERE, + WTF::Bind(&FontResource::FontLoadLongLimitCallback, + WrapWeakPersistent(this)), + kFontLoadWaitLong); +} + +scoped_refptr<FontCustomPlatformData> FontResource::GetCustomFontData() { + if (!font_data_ && !ErrorOccurred() && !IsLoading()) { + if (Data()) + font_data_ = FontCustomPlatformData::Create(Data(), ots_parsing_message_); + + if (font_data_) { + RecordPackageFormatHistogram(PackageFormatOf(Data())); + } else { + SetStatus(ResourceStatus::kDecodeError); + RecordPackageFormatHistogram(kPackageFormatUnknown); + } + } + return font_data_; +} + +void FontResource::WillReloadAfterDiskCacheMiss() { + DCHECK(IsLoading()); + DCHECK(Loader()->IsCacheAwareLoadingActivated()); + if (load_limit_state_ == kShortLimitExceeded || + load_limit_state_ == kLongLimitExceeded) { + NotifyClientsShortLimitExceeded(); + } + if (load_limit_state_ == kLongLimitExceeded) + NotifyClientsLongLimitExceeded(); + + DEFINE_STATIC_LOCAL( + EnumerationHistogram, load_limit_histogram, + ("WebFont.LoadLimitOnDiskCacheMiss", kLoadLimitStateEnumMax)); + load_limit_histogram.Count(load_limit_state_); +} + +void FontResource::FontLoadShortLimitCallback() { + DCHECK(IsLoading()); + DCHECK_EQ(load_limit_state_, kUnderLimit); + load_limit_state_ = kShortLimitExceeded; + + // Block client callbacks if currently loading from cache. + if (Loader()->IsCacheAwareLoadingActivated()) + return; + NotifyClientsShortLimitExceeded(); +} + +void FontResource::FontLoadLongLimitCallback() { + DCHECK(IsLoading()); + DCHECK_EQ(load_limit_state_, kShortLimitExceeded); + load_limit_state_ = kLongLimitExceeded; + + // Block client callbacks if currently loading from cache. + if (Loader()->IsCacheAwareLoadingActivated()) + return; + NotifyClientsLongLimitExceeded(); +} + +void FontResource::NotifyClientsShortLimitExceeded() { + ProhibitAddRemoveClientInScope prohibit_add_remove_client(this); + ResourceClientWalker<FontResourceClient> walker(Clients()); + while (FontResourceClient* client = walker.Next()) + client->FontLoadShortLimitExceeded(this); +} + +void FontResource::NotifyClientsLongLimitExceeded() { + ProhibitAddRemoveClientInScope prohibit_add_remove_client(this); + ResourceClientWalker<FontResourceClient> walker(Clients()); + while (FontResourceClient* client = walker.Next()) + client->FontLoadLongLimitExceeded(this); +} + +void FontResource::AllClientsAndObserversRemoved() { + font_data_ = nullptr; + Resource::AllClientsAndObserversRemoved(); +} + +void FontResource::NotifyFinished() { + font_load_short_limit_.Cancel(); + font_load_long_limit_.Cancel(); + + Resource::NotifyFinished(); +} + +bool FontResource::IsLowPriorityLoadingAllowedForRemoteFont() const { + DCHECK(!IsLoaded()); + if (Url().ProtocolIsData()) + return false; + ResourceClientWalker<FontResourceClient> walker(Clients()); + while (FontResourceClient* client = walker.Next()) { + if (!client->IsLowPriorityLoadingAllowedForRemoteFont()) { + return false; + } + } + return true; +} + +void FontResource::OnMemoryDump(WebMemoryDumpLevelOfDetail level, + WebProcessMemoryDump* memory_dump) const { + Resource::OnMemoryDump(level, memory_dump); + if (!font_data_) + return; + const String name = GetMemoryDumpName() + "/decoded_webfont"; + WebMemoryAllocatorDump* dump = memory_dump->CreateMemoryAllocatorDump(name); + dump->AddScalar("size", "bytes", font_data_->DataSize()); + memory_dump->AddSuballocation(dump->Guid(), "malloc"); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/loader/resource/font_resource.h b/chromium/third_party/blink/renderer/core/loader/resource/font_resource.h new file mode 100644 index 00000000000..d64559e8b04 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/resource/font_resource.h @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2007, 2008 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_RESOURCE_FONT_RESOURCE_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_RESOURCE_FONT_RESOURCE_H_ + +#include "base/gtest_prod_util.h" +#include "base/memory/scoped_refptr.h" +#include "base/single_thread_task_runner.h" +#include "third_party/blink/renderer/core/core_export.h" +#include "third_party/blink/renderer/platform/heap/handle.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource_client.h" +#include "third_party/blink/renderer/platform/web_task_runner.h" + +namespace blink { + +class FetchParameters; +class ResourceFetcher; +class FontCustomPlatformData; +class FontResourceClient; + +class CORE_EXPORT FontResource final : public Resource { + public: + static FontResource* Fetch(FetchParameters&, + ResourceFetcher*, + FontResourceClient*); + ~FontResource() override; + + void DidAddClient(ResourceClient*) override; + + void SetRevalidatingRequest(const ResourceRequest&) override; + + void AllClientsAndObserversRemoved() override; + void StartLoadLimitTimers(base::SingleThreadTaskRunner*); + + String OtsParsingMessage() const { return ots_parsing_message_; } + + scoped_refptr<FontCustomPlatformData> GetCustomFontData(); + + // Returns true if the loading priority of the remote font resource can be + // lowered. The loading priority of the font can be lowered only if the + // font is not needed for painting the text. + bool IsLowPriorityLoadingAllowedForRemoteFont() const; + + void WillReloadAfterDiskCacheMiss() override; + + void OnMemoryDump(WebMemoryDumpLevelOfDetail, + WebProcessMemoryDump*) const override; + + private: + class FontResourceFactory : public NonTextResourceFactory { + public: + FontResourceFactory() : NonTextResourceFactory(Resource::kFont) {} + + Resource* Create(const ResourceRequest& request, + const ResourceLoaderOptions& options) const override { + return new FontResource(request, options); + } + }; + FontResource(const ResourceRequest&, const ResourceLoaderOptions&); + + void NotifyFinished() override; + void FontLoadShortLimitCallback(); + void FontLoadLongLimitCallback(); + void NotifyClientsShortLimitExceeded(); + void NotifyClientsLongLimitExceeded(); + + // This is used in UMA histograms, should not change order. + enum LoadLimitState { + kLoadNotStarted, + kUnderLimit, + kShortLimitExceeded, + kLongLimitExceeded, + kLoadLimitStateEnumMax + }; + + scoped_refptr<FontCustomPlatformData> font_data_; + String ots_parsing_message_; + LoadLimitState load_limit_state_; + bool cors_failed_; + TaskHandle font_load_short_limit_; + TaskHandle font_load_long_limit_; + + friend class MemoryCache; + FRIEND_TEST_ALL_PREFIXES(FontResourceTest, CacheAwareFontLoading); +}; + +DEFINE_RESOURCE_TYPE_CASTS(Font); + +class FontResourceClient : public ResourceClient { + public: + ~FontResourceClient() override = default; + static bool IsExpectedType(ResourceClient* client) { + return client->GetResourceClientType() == kFontType; + } + ResourceClientType GetResourceClientType() const final { return kFontType; } + + // If cache-aware loading is activated, both callbacks will be blocked until + // disk cache miss. Calls to addClient() and removeClient() in both callbacks + // are prohibited to prevent race issues regarding current loading state. + virtual void FontLoadShortLimitExceeded(FontResource*) {} + virtual void FontLoadLongLimitExceeded(FontResource*) {} + + // Returns true if loading priority of remote font resources can be lowered. + virtual bool IsLowPriorityLoadingAllowedForRemoteFont() const { + // Only the RemoteFontFaceSources clients can prevent lowering of loading + // priority of the remote fonts. Set the default to true to prevent + // other clients from incorrectly returning false. + return true; + } +}; + +} // namespace blink + +#endif diff --git a/chromium/third_party/blink/renderer/core/loader/resource/font_resource_test.cc b/chromium/third_party/blink/renderer/core/loader/resource/font_resource_test.cc new file mode 100644 index 00000000000..66fe55ab2d8 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/resource/font_resource_test.cc @@ -0,0 +1,167 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "third_party/blink/renderer/core/loader/resource/font_resource.h" + +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/blink/public/platform/modules/fetch/fetch_api_request.mojom-shared.h" +#include "third_party/blink/public/platform/platform.h" +#include "third_party/blink/public/platform/web_url_loader_mock_factory.h" +#include "third_party/blink/renderer/core/css/css_font_face_src_value.h" +#include "third_party/blink/renderer/core/loader/resource/mock_font_resource_client.h" +#include "third_party/blink/renderer/core/testing/dummy_page_holder.h" +#include "third_party/blink/renderer/platform/exported/wrapped_resource_response.h" +#include "third_party/blink/renderer/platform/loader/fetch/fetch_parameters.h" +#include "third_party/blink/renderer/platform/loader/fetch/memory_cache.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource_error.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource_loader.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource_request.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource_response.h" +#include "third_party/blink/renderer/platform/loader/testing/mock_fetch_context.h" +#include "third_party/blink/renderer/platform/loader/testing/mock_resource_client.h" +#include "third_party/blink/renderer/platform/runtime_enabled_features.h" +#include "third_party/blink/renderer/platform/weborigin/kurl.h" + +namespace blink { + +class FontResourceTest : public testing::Test { + void TearDown() override { + Platform::Current() + ->GetURLLoaderMockFactory() + ->UnregisterAllURLsAndClearMemoryCache(); + } +}; + +// Tests if ResourceFetcher works fine with FontResource that requires defered +// loading supports. +TEST_F(FontResourceTest, + ResourceFetcherRevalidateDeferedResourceFromTwoInitiators) { + KURL url("http://127.0.0.1:8000/font.woff"); + ResourceResponse response(url); + response.SetHTTPStatusCode(200); + response.SetHTTPHeaderField(HTTPNames::ETag, "1234567890"); + Platform::Current()->GetURLLoaderMockFactory()->RegisterURL( + url, WrappedResourceResponse(response), ""); + + MockFetchContext* context = + MockFetchContext::Create(MockFetchContext::kShouldLoadNewResource); + ResourceFetcher* fetcher = ResourceFetcher::Create(context); + + // Fetch to cache a resource. + ResourceRequest request1(url); + FetchParameters fetch_params1(request1); + Resource* resource1 = FontResource::Fetch(fetch_params1, fetcher, nullptr); + ASSERT_FALSE(resource1->ErrorOccurred()); + fetcher->StartLoad(resource1); + Platform::Current()->GetURLLoaderMockFactory()->ServeAsynchronousRequests(); + EXPECT_TRUE(resource1->IsLoaded()); + EXPECT_FALSE(resource1->ErrorOccurred()); + + // Set the context as it is on reloads. + context->SetLoadComplete(true); + + // Revalidate the resource. + ResourceRequest request2(url); + request2.SetCacheMode(mojom::FetchCacheMode::kValidateCache); + FetchParameters fetch_params2(request2); + Resource* resource2 = FontResource::Fetch(fetch_params2, fetcher, nullptr); + ASSERT_FALSE(resource2->ErrorOccurred()); + EXPECT_EQ(resource1, resource2); + EXPECT_TRUE(resource2->IsCacheValidator()); + EXPECT_TRUE(resource2->StillNeedsLoad()); + + // Fetch the same resource again before actual load operation starts. + ResourceRequest request3(url); + request3.SetCacheMode(mojom::FetchCacheMode::kValidateCache); + FetchParameters fetch_params3(request3); + Resource* resource3 = FontResource::Fetch(fetch_params3, fetcher, nullptr); + ASSERT_FALSE(resource3->ErrorOccurred()); + EXPECT_EQ(resource2, resource3); + EXPECT_TRUE(resource3->IsCacheValidator()); + EXPECT_TRUE(resource3->StillNeedsLoad()); + + // StartLoad() can be called from any initiator. Here, call it from the + // latter. + fetcher->StartLoad(resource3); + Platform::Current()->GetURLLoaderMockFactory()->ServeAsynchronousRequests(); + EXPECT_TRUE(resource3->IsLoaded()); + EXPECT_FALSE(resource3->ErrorOccurred()); + EXPECT_TRUE(resource2->IsLoaded()); + EXPECT_FALSE(resource2->ErrorOccurred()); + + GetMemoryCache()->Remove(resource1); +} + +// Tests if cache-aware font loading works correctly. +TEST_F(FontResourceTest, CacheAwareFontLoading) { + KURL url("http://127.0.0.1:8000/font.woff"); + ResourceResponse response(url); + response.SetHTTPStatusCode(200); + Platform::Current()->GetURLLoaderMockFactory()->RegisterURL( + url, WrappedResourceResponse(response), ""); + + RuntimeEnabledFeatures::Backup features_backup; + RuntimeEnabledFeatures::SetWebFontsCacheAwareTimeoutAdaptationEnabled(true); + + std::unique_ptr<DummyPageHolder> dummy_page_holder = + DummyPageHolder::Create(IntSize(800, 600)); + Document& document = dummy_page_holder->GetDocument(); + ResourceFetcher* fetcher = document.Fetcher(); + CSSFontFaceSrcValue* src_value = CSSFontFaceSrcValue::Create( + url.GetString(), url.GetString(), + Referrer(document.Url(), document.GetReferrerPolicy()), + kDoNotCheckContentSecurityPolicy); + + // Route font requests in this test through CSSFontFaceSrcValue::Fetch + // instead of calling FontResource::Fetch directly. CSSFontFaceSrcValue + // requests a FontResource only once, and skips calling FontResource::Fetch + // on future CSSFontFaceSrcValue::Fetch calls. This tests wants to ensure + // correct behavior in the case where we reuse a FontResource without it being + // a "cache hit" in ResourceFetcher's view. + Persistent<MockFontResourceClient> client = new MockFontResourceClient; + FontResource& resource = src_value->Fetch(&document, client); + + fetcher->StartLoad(&resource); + EXPECT_TRUE(resource.Loader()->IsCacheAwareLoadingActivated()); + resource.load_limit_state_ = FontResource::kUnderLimit; + + // FontResource callbacks should be blocked during cache-aware loading. + resource.FontLoadShortLimitCallback(); + EXPECT_FALSE(client->FontLoadShortLimitExceededCalled()); + + // Fail first request as disk cache miss. + resource.Loader()->HandleError(ResourceError::CacheMissError(url)); + + // Once cache miss error returns, previously blocked callbacks should be + // called immediately. + EXPECT_FALSE(resource.Loader()->IsCacheAwareLoadingActivated()); + EXPECT_TRUE(client->FontLoadShortLimitExceededCalled()); + EXPECT_FALSE(client->FontLoadLongLimitExceededCalled()); + + // Add client now, FontLoadShortLimitExceeded() should be called. + Persistent<MockFontResourceClient> client2 = new MockFontResourceClient; + FontResource& resource2 = src_value->Fetch(&document, client2); + EXPECT_EQ(&resource, &resource2); + EXPECT_TRUE(client2->FontLoadShortLimitExceededCalled()); + EXPECT_FALSE(client2->FontLoadLongLimitExceededCalled()); + + // FontResource callbacks are not blocked now. + resource.FontLoadLongLimitCallback(); + EXPECT_TRUE(client->FontLoadLongLimitExceededCalled()); + + // Add client now, both callbacks should be called. + Persistent<MockFontResourceClient> client3 = new MockFontResourceClient; + FontResource& resource3 = src_value->Fetch(&document, client3); + EXPECT_EQ(&resource, &resource3); + EXPECT_TRUE(client3->FontLoadShortLimitExceededCalled()); + EXPECT_TRUE(client3->FontLoadLongLimitExceededCalled()); + + Platform::Current()->GetURLLoaderMockFactory()->ServeAsynchronousRequests(); + GetMemoryCache()->Remove(&resource); + + features_backup.Restore(); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/loader/resource/image_resource.cc b/chromium/third_party/blink/renderer/core/loader/resource/image_resource.cc new file mode 100644 index 00000000000..b869c662aaf --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/resource/image_resource.cc @@ -0,0 +1,730 @@ +/* + Copyright (C) 1998 Lars Knoll (knoll@mpi-hd.mpg.de) + Copyright (C) 2001 Dirk Mueller (mueller@kde.org) + Copyright (C) 2002 Waldo Bastian (bastian@kde.org) + Copyright (C) 2006 Samuel Weinig (sam.weinig@gmail.com) + Copyright (C) 2004, 2005, 2006, 2007 Apple Inc. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "third_party/blink/renderer/core/loader/resource/image_resource.h" + +#include <stdint.h> +#include <v8.h> +#include <memory> + +#include "third_party/blink/public/platform/platform.h" +#include "third_party/blink/renderer/core/loader/resource/image_resource_content.h" +#include "third_party/blink/renderer/core/loader/resource/image_resource_info.h" +#include "third_party/blink/renderer/platform/histogram.h" +#include "third_party/blink/renderer/platform/instance_counters.h" +#include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h" +#include "third_party/blink/renderer/platform/loader/fetch/fetch_initiator_type_names.h" +#include "third_party/blink/renderer/platform/loader/fetch/fetch_parameters.h" +#include "third_party/blink/renderer/platform/loader/fetch/memory_cache.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource_client.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource_loader.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource_loader_options.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource_loading_log.h" +#include "third_party/blink/renderer/platform/network/http_parsers.h" +#include "third_party/blink/renderer/platform/runtime_enabled_features.h" +#include "third_party/blink/renderer/platform/shared_buffer.h" +#include "third_party/blink/renderer/platform/weborigin/kurl.h" +#include "third_party/blink/renderer/platform/weborigin/security_violation_reporting_policy.h" +#include "third_party/blink/renderer/platform/wtf/std_lib_extras.h" +#include "third_party/blink/renderer/platform/wtf/time.h" +#include "v8/include/v8.h" + +namespace blink { + +namespace { + +// The amount of time to wait before informing the clients that the image has +// been updated (in seconds). This effectively throttles invalidations that +// result from new data arriving for this image. +constexpr double kFlushDelaySeconds = 1.; + +bool HasServerLoFiResponseHeaders(const ResourceResponse& response) { + return response.HttpHeaderField("chrome-proxy-content-transform") + .Contains("empty-image") || + // Check for the legacy Server Lo-Fi response headers, since it's + // possible that an old Lo-Fi image could be served from the cache. + response.HttpHeaderField("chrome-proxy").Contains("q=low"); +} + +} // namespace + +class ImageResource::ImageResourceInfoImpl final + : public GarbageCollectedFinalized<ImageResourceInfoImpl>, + public ImageResourceInfo { + USING_GARBAGE_COLLECTED_MIXIN(ImageResourceInfoImpl); + + public: + ImageResourceInfoImpl(ImageResource* resource) : resource_(resource) { + DCHECK(resource_); + } + void Trace(blink::Visitor* visitor) override { + visitor->Trace(resource_); + ImageResourceInfo::Trace(visitor); + } + + private: + const KURL& Url() const override { return resource_->Url(); } + bool IsSchedulingReload() const override { + return resource_->is_scheduling_reload_; + } + const ResourceResponse& GetResponse() const override { + return resource_->GetResponse(); + } + bool ShouldShowPlaceholder() const override { + return resource_->ShouldShowPlaceholder(); + } + bool IsCacheValidator() const override { + return resource_->IsCacheValidator(); + } + bool SchedulingReloadOrShouldReloadBrokenPlaceholder() const override { + return resource_->is_scheduling_reload_ || + resource_->ShouldReloadBrokenPlaceholder(); + } + bool IsAccessAllowed( + const SecurityOrigin* security_origin, + DoesCurrentFrameHaveSingleSecurityOrigin + does_current_frame_has_single_security_origin) const override { + return resource_->IsAccessAllowed( + security_origin, does_current_frame_has_single_security_origin); + } + bool HasCacheControlNoStoreHeader() const override { + return resource_->HasCacheControlNoStoreHeader(); + } + Optional<ResourceError> GetResourceError() const override { + if (resource_->LoadFailedOrCanceled()) + return resource_->GetResourceError(); + return WTF::nullopt; + } + + void SetDecodedSize(size_t size) override { resource_->SetDecodedSize(size); } + void WillAddClientOrObserver() override { + resource_->WillAddClientOrObserver(); + } + void DidRemoveClientOrObserver() override { + resource_->DidRemoveClientOrObserver(); + } + void EmulateLoadStartedForInspector( + ResourceFetcher* fetcher, + const KURL& url, + const AtomicString& initiator_name) override { + fetcher->EmulateLoadStartedForInspector(resource_.Get(), url, + WebURLRequest::kRequestContextImage, + initiator_name); + } + + const Member<ImageResource> resource_; +}; + +class ImageResource::ImageResourceFactory : public NonTextResourceFactory { + STACK_ALLOCATED(); + + public: + ImageResourceFactory(const FetchParameters& fetch_params) + : NonTextResourceFactory(Resource::kImage), + fetch_params_(&fetch_params) {} + + Resource* Create(const ResourceRequest& request, + const ResourceLoaderOptions& options) const override { + return new ImageResource(request, options, + ImageResourceContent::CreateNotStarted(), + fetch_params_->GetPlaceholderImageRequestType() == + FetchParameters::kAllowPlaceholder); + } + + private: + // Weak, unowned pointer. Must outlive |this|. + const FetchParameters* fetch_params_; +}; + +ImageResource* ImageResource::Fetch(FetchParameters& params, + ResourceFetcher* fetcher) { + if (params.GetResourceRequest().GetRequestContext() == + WebURLRequest::kRequestContextUnspecified) { + params.SetRequestContext(WebURLRequest::kRequestContextImage); + } + + ImageResource* resource = ToImageResource( + fetcher->RequestResource(params, ImageResourceFactory(params), nullptr)); + + // If the fetch originated from user agent CSS we should mark it as a user + // agent resource. + if (params.Options().initiator_info.name == FetchInitiatorTypeNames::uacss) + resource->FlagAsUserAgentResource(); + return resource; +} + +bool ImageResource::CanReuse( + const FetchParameters& params, + scoped_refptr<const SecurityOrigin> new_source_origin) const { + // If the image is a placeholder, but this fetch doesn't allow a + // placeholder, then do not reuse this resource. + if (params.GetPlaceholderImageRequestType() != + FetchParameters::kAllowPlaceholder && + placeholder_option_ != PlaceholderOption::kDoNotReloadPlaceholder) + return false; + + return Resource::CanReuse(params, std::move(new_source_origin)); +} + +bool ImageResource::CanUseCacheValidator() const { + // Disable revalidation while ImageResourceContent is still waiting for + // SVG load completion. + // TODO(hiroshige): Clean up revalidation-related dependencies. + if (!GetContent()->IsLoaded()) + return false; + + return Resource::CanUseCacheValidator(); +} + +ImageResource* ImageResource::Create(const ResourceRequest& request) { + ResourceLoaderOptions options; + return new ImageResource(request, options, + ImageResourceContent::CreateNotStarted(), false); +} + +ImageResource* ImageResource::CreateForTest(const KURL& url) { + ResourceRequest request(url); + return Create(request); +} + +ImageResource::ImageResource(const ResourceRequest& resource_request, + const ResourceLoaderOptions& options, + ImageResourceContent* content, + bool is_placeholder) + : Resource(resource_request, kImage, options), + content_(content), + is_scheduling_reload_(false), + placeholder_option_( + is_placeholder ? PlaceholderOption::kShowAndReloadPlaceholderAlways + : PlaceholderOption::kDoNotReloadPlaceholder) { + DCHECK(GetContent()); + RESOURCE_LOADING_DVLOG(1) << "new ImageResource(ResourceRequest) " << this; + GetContent()->SetImageResourceInfo(new ImageResourceInfoImpl(this)); +} + +ImageResource::~ImageResource() { + RESOURCE_LOADING_DVLOG(1) << "~ImageResource " << this; + + if (is_referenced_from_ua_stylesheet_) + InstanceCounters::DecrementCounter(InstanceCounters::kUACSSResourceCounter); +} + +void ImageResource::OnMemoryDump(WebMemoryDumpLevelOfDetail level_of_detail, + WebProcessMemoryDump* memory_dump) const { + Resource::OnMemoryDump(level_of_detail, memory_dump); + const String name = GetMemoryDumpName() + "/image_content"; + auto* dump = memory_dump->CreateMemoryAllocatorDump(name); + size_t encoded_size = + content_->HasImage() ? content_->GetImage()->Data()->size() : 0; + dump->AddScalar("size", "bytes", encoded_size); +} + +void ImageResource::Trace(blink::Visitor* visitor) { + visitor->Trace(multipart_parser_); + visitor->Trace(content_); + Resource::Trace(visitor); + MultipartImageResourceParser::Client::Trace(visitor); +} + +void ImageResource::NotifyFinished() { + // Don't notify clients of completion if this ImageResource is + // about to be reloaded. + if (is_scheduling_reload_ || ShouldReloadBrokenPlaceholder()) + return; + + Resource::NotifyFinished(); +} + +bool ImageResource::HasClientsOrObservers() const { + return Resource::HasClientsOrObservers() || GetContent()->HasObservers(); +} + +void ImageResource::DidAddClient(ResourceClient* client) { + DCHECK((multipart_parser_ && IsLoading()) || !Data() || + GetContent()->HasImage()); + + // Don't notify observers and clients of completion if this ImageResource is + // about to be reloaded. + if (is_scheduling_reload_ || ShouldReloadBrokenPlaceholder()) + return; + + Resource::DidAddClient(client); +} + +void ImageResource::DestroyDecodedDataForFailedRevalidation() { + // Clears the image, as we must create a new image for the failed + // revalidation response. + UpdateImage(nullptr, ImageResourceContent::kClearAndUpdateImage, false); + SetDecodedSize(0); +} + +void ImageResource::DestroyDecodedDataIfPossible() { + GetContent()->DestroyDecodedData(); + if (GetContent()->HasImage() && !IsUnusedPreload() && + GetContent()->IsRefetchableDataFromDiskCache()) { + UMA_HISTOGRAM_MEMORY_KB("Memory.Renderer.EstimatedDroppableEncodedSize", + EncodedSize() / 1024); + } +} + +void ImageResource::AllClientsAndObserversRemoved() { + // After ErrorOccurred() is set true in Resource::FinishAsError() before + // the subsequent UpdateImage() in ImageResource::FinishAsError(), + // HasImage() is true and ErrorOccurred() is true. + // |is_during_finish_as_error_| is introduced to allow such cases. + // crbug.com/701723 + // TODO(hiroshige): Make the CHECK condition cleaner. + CHECK(is_during_finish_as_error_ || !GetContent()->HasImage() || + !ErrorOccurred()); + // If possible, delay the resetting until back at the event loop. Doing so + // after a conservative GC prevents resetAnimation() from upsetting ongoing + // animation updates (crbug.com/613709) + if (!ThreadHeap::WillObjectBeLazilySwept(this)) { + Platform::Current()->CurrentThread()->GetTaskRunner()->PostTask( + FROM_HERE, WTF::Bind(&ImageResourceContent::DoResetAnimation, + WrapWeakPersistent(GetContent()))); + } else { + GetContent()->DoResetAnimation(); + } + if (multipart_parser_) + multipart_parser_->Cancel(); + Resource::AllClientsAndObserversRemoved(); +} + +scoped_refptr<const SharedBuffer> ImageResource::ResourceBuffer() const { + if (Data()) + return Data(); + return GetContent()->ResourceBuffer(); +} + +void ImageResource::AppendData(const char* data, size_t length) { + v8::Isolate::GetCurrent()->AdjustAmountOfExternalAllocatedMemory(length); + if (multipart_parser_) { + multipart_parser_->AppendData(data, length); + } else { + Resource::AppendData(data, length); + + // Update the image immediately if needed. + if (GetContent()->ShouldUpdateImageImmediately()) { + UpdateImage(Data(), ImageResourceContent::kUpdateImage, false); + return; + } + + // For other cases, only update at |kFlushDelaySeconds| intervals. This + // throttles how frequently we update |m_image| and how frequently we + // inform the clients which causes an invalidation of this image. In other + // words, we only invalidate this image every |kFlushDelaySeconds| seconds + // while loading. + if (Loader() && !is_pending_flushing_) { + scoped_refptr<base::SingleThreadTaskRunner> task_runner = + Loader()->GetLoadingTaskRunner(); + double now = WTF::CurrentTimeTicksInSeconds(); + if (!last_flush_time_) + last_flush_time_ = now; + + DCHECK_LE(last_flush_time_, now); + double flush_delay = last_flush_time_ - now + kFlushDelaySeconds; + if (flush_delay < 0.) + flush_delay = 0.; + task_runner->PostDelayedTask(FROM_HERE, + WTF::Bind(&ImageResource::FlushImageIfNeeded, + WrapWeakPersistent(this)), + TimeDelta::FromSecondsD(flush_delay)); + is_pending_flushing_ = true; + } + } +} + +void ImageResource::FlushImageIfNeeded() { + // We might have already loaded the image fully, in which case we don't need + // to call |updateImage()|. + if (IsLoading()) { + last_flush_time_ = WTF::CurrentTimeTicksInSeconds(); + UpdateImage(Data(), ImageResourceContent::kUpdateImage, false); + } + is_pending_flushing_ = false; +} + +void ImageResource::DecodeError(bool all_data_received) { + size_t size = EncodedSize(); + + ClearData(); + SetEncodedSize(0); + if (!ErrorOccurred()) + SetStatus(ResourceStatus::kDecodeError); + + if (multipart_parser_) + multipart_parser_->Cancel(); + + bool is_multipart = !!multipart_parser_; + // Finishes loading if needed, and notifies observers. + if (!all_data_received && Loader()) { + // Observers are notified via ImageResource::finish(). + // TODO(hiroshige): Do not call didFinishLoading() directly. + Loader()->DidFinishLoading(CurrentTimeTicksInSeconds(), size, size, size, + false); + } else { + auto result = GetContent()->UpdateImage( + nullptr, GetStatus(), + ImageResourceContent::kClearImageAndNotifyObservers, all_data_received, + is_multipart); + DCHECK_EQ(result, ImageResourceContent::UpdateImageResult::kNoDecodeError); + } + + GetMemoryCache()->Remove(this); +} + +void ImageResource::UpdateImageAndClearBuffer() { + UpdateImage(Data(), ImageResourceContent::kClearAndUpdateImage, true); + ClearData(); +} + +void ImageResource::NotifyStartLoad() { + CHECK_EQ(GetStatus(), ResourceStatus::kPending); + GetContent()->NotifyStartLoad(); +} + +void ImageResource::Finish(double load_finish_time, + base::SingleThreadTaskRunner* task_runner) { + if (multipart_parser_) { + if (!ErrorOccurred()) + multipart_parser_->Finish(); + if (Data()) + UpdateImageAndClearBuffer(); + } else { + UpdateImage(Data(), ImageResourceContent::kUpdateImage, true); + // As encoded image data can be created from m_image (see + // ImageResource::resourceBuffer(), we don't have to keep m_data. Let's + // clear this. As for the lifetimes of m_image and m_data, see this + // document: + // https://docs.google.com/document/d/1v0yTAZ6wkqX2U_M6BNIGUJpM1s0TIw1VsqpxoL7aciY/edit?usp=sharing + ClearData(); + } + Resource::Finish(load_finish_time, task_runner); +} + +void ImageResource::FinishAsError(const ResourceError& error, + base::SingleThreadTaskRunner* task_runner) { + if (multipart_parser_) + multipart_parser_->Cancel(); + // TODO(hiroshige): Move setEncodedSize() call to Resource::error() if it + // is really needed, or remove it otherwise. + SetEncodedSize(0); + is_during_finish_as_error_ = true; + Resource::FinishAsError(error, task_runner); + is_during_finish_as_error_ = false; + UpdateImage(nullptr, ImageResourceContent::kClearImageAndNotifyObservers, + true); +} + +// Determines if |response| likely contains the entire resource for the purposes +// of determining whether or not to show a placeholder, e.g. if the server +// responded with a full 200 response or if the full image is smaller than the +// requested range. +static bool IsEntireResource(const ResourceResponse& response) { + if (response.HttpStatusCode() != 206) + return true; + + int64_t first_byte_position = -1, last_byte_position = -1, + instance_length = -1; + return ParseContentRangeHeaderFor206( + response.HttpHeaderField("Content-Range"), &first_byte_position, + &last_byte_position, &instance_length) && + first_byte_position == 0 && last_byte_position + 1 == instance_length; +} + +void ImageResource::ResponseReceived( + const ResourceResponse& response, + std::unique_ptr<WebDataConsumerHandle> handle) { + DCHECK(!handle); + DCHECK(!multipart_parser_); + // If there's no boundary, just handle the request normally. + if (response.IsMultipart() && !response.MultipartBoundary().IsEmpty()) { + multipart_parser_ = new MultipartImageResourceParser( + response, response.MultipartBoundary(), this); + } + + // Notify the base class that a response has been received. Note that after + // this call, |GetResponse()| will represent the full effective + // ResourceResponse, while |response| might just be a revalidation response + // (e.g. a 304) with a partial set of updated headers that were folded into + // the cached response. + Resource::ResponseReceived(response, std::move(handle)); + + if (placeholder_option_ == + PlaceholderOption::kShowAndReloadPlaceholderAlways && + IsEntireResource(GetResponse())) { + if (GetResponse().HttpStatusCode() < 400 || + GetResponse().HttpStatusCode() >= 600) { + // Don't treat a complete and broken image as a placeholder if the + // response code is something other than a 4xx or 5xx error. + // This is done to prevent reissuing the request in cases like + // "204 No Content" responses to tracking requests triggered by <img> + // tags, and <img> tags used to preload non-image resources. + placeholder_option_ = PlaceholderOption::kDoNotReloadPlaceholder; + } else { + placeholder_option_ = PlaceholderOption::kReloadPlaceholderOnDecodeError; + } + } + + if (HasServerLoFiResponseHeaders(GetResponse())) { + // Ensure that the PreviewsState bit for Server Lo-Fi is set iff Chrome + // received the appropriate Server Lo-Fi response headers for this image. + // + // Normally, the |kServerLoFiOn| bit should already be set if Server Lo-Fi + // response headers are coming back, but it's possible for legacy Lo-Fi + // images to be served from the cache even if Chrome isn't in Lo-Fi mode. + // This also serves as a nice last line of defence to ensure that Server + // Lo-Fi images can be reloaded to show the original even if e.g. a server + // bug causes Lo-Fi images to be sent when they aren't expected. + SetPreviewsState(GetResourceRequest().GetPreviewsState() | + WebURLRequest::kServerLoFiOn); + } else if (GetResourceRequest().GetPreviewsState() & + WebURLRequest::kServerLoFiOn) { + // If Chrome expects a Lo-Fi response, but the server decided to send the + // full image, then clear the Server Lo-Fi Previews state bit. + WebURLRequest::PreviewsState new_previews_state = + GetResourceRequest().GetPreviewsState(); + + new_previews_state &= ~WebURLRequest::kServerLoFiOn; + if (new_previews_state == WebURLRequest::kPreviewsUnspecified) + new_previews_state = WebURLRequest::kPreviewsOff; + + SetPreviewsState(new_previews_state); + } +} + +bool ImageResource::ShouldShowPlaceholder() const { + if (RuntimeEnabledFeatures::ClientPlaceholdersForServerLoFiEnabled() && + (GetResourceRequest().GetPreviewsState() & + WebURLRequest::kServerLoFiOn)) { + // If the runtime feature is enabled, show Client Lo-Fi placeholder images + // in place of Server Lo-Fi responses. This is done so that all Lo-Fi images + // have a consistent appearance. + return true; + } + + switch (placeholder_option_) { + case PlaceholderOption::kShowAndReloadPlaceholderAlways: + case PlaceholderOption::kShowAndDoNotReloadPlaceholder: + return true; + case PlaceholderOption::kReloadPlaceholderOnDecodeError: + case PlaceholderOption::kDoNotReloadPlaceholder: + return false; + } + NOTREACHED(); + return false; +} + +bool ImageResource::ShouldReloadBrokenPlaceholder() const { + switch (placeholder_option_) { + case PlaceholderOption::kShowAndReloadPlaceholderAlways: + return ErrorOccurred(); + case PlaceholderOption::kReloadPlaceholderOnDecodeError: + return GetStatus() == ResourceStatus::kDecodeError; + case PlaceholderOption::kShowAndDoNotReloadPlaceholder: + case PlaceholderOption::kDoNotReloadPlaceholder: + return false; + } + NOTREACHED(); + return false; +} + +void ImageResource::ReloadIfLoFiOrPlaceholderImage( + ResourceFetcher* fetcher, + ReloadLoFiOrPlaceholderPolicy policy) { + if (policy == kReloadIfNeeded && !ShouldReloadBrokenPlaceholder()) + return; + + // If the image is loaded, then the |PreviewsState::kServerLoFiOn| bit should + // be set iff the image has Server Lo-Fi response headers. + DCHECK(!IsLoaded() || + HasServerLoFiResponseHeaders(GetResponse()) == + static_cast<bool>(GetResourceRequest().GetPreviewsState() & + WebURLRequest::kServerLoFiOn)); + + if (placeholder_option_ == PlaceholderOption::kDoNotReloadPlaceholder && + !(GetResourceRequest().GetPreviewsState() & WebURLRequest::kServerLoFiOn)) + return; + + // Prevent clients and observers from being notified of completion while the + // reload is being scheduled, so that e.g. canceling an existing load in + // progress doesn't cause clients and observers to be notified of completion + // prematurely. + DCHECK(!is_scheduling_reload_); + is_scheduling_reload_ = true; + + SetCachePolicyBypassingCache(); + + // The reloaded image should not use any previews transformations. + WebURLRequest::PreviewsState previews_state_for_reload = + WebURLRequest::kPreviewsNoTransform; + WebURLRequest::PreviewsState old_previews_state = + GetResourceRequest().GetPreviewsState(); + + if (policy == kReloadIfNeeded && (GetResourceRequest().GetPreviewsState() & + WebURLRequest::kClientLoFiOn)) { + // If the image attempted to use Client LoFi, but encountered a decoding + // error and is being automatically reloaded, then also set the appropriate + // PreviewsState bit for that. This allows the embedder to count the + // bandwidth used for this reload against the data savings of the initial + // response. + previews_state_for_reload |= WebURLRequest::kClientLoFiAutoReload; + } + SetPreviewsState(previews_state_for_reload); + + if (placeholder_option_ != PlaceholderOption::kDoNotReloadPlaceholder) + ClearRangeRequestHeader(); + + if (old_previews_state & WebURLRequest::kClientLoFiOn && + policy != kReloadAlways) { + placeholder_option_ = PlaceholderOption::kShowAndDoNotReloadPlaceholder; + } else { + placeholder_option_ = PlaceholderOption::kDoNotReloadPlaceholder; + } + + if (IsLoading()) { + Loader()->Cancel(); + // Canceling the loader causes error() to be called, which in turn calls + // clear() and notifyObservers(), so there's no need to call these again + // here. + } else { + ClearData(); + SetEncodedSize(0); + UpdateImage(nullptr, ImageResourceContent::kClearImageAndNotifyObservers, + false); + } + + SetStatus(ResourceStatus::kNotStarted); + + DCHECK(is_scheduling_reload_); + is_scheduling_reload_ = false; + + fetcher->StartLoad(this); +} + +void ImageResource::OnePartInMultipartReceived( + const ResourceResponse& response) { + DCHECK(multipart_parser_); + + if (!GetResponse().IsNull()) { + CHECK_EQ(GetResponse().WasFetchedViaServiceWorker(), + response.WasFetchedViaServiceWorker()); + CHECK_EQ(GetResponse().ResponseTypeViaServiceWorker(), + response.ResponseTypeViaServiceWorker()); + } + + SetResponse(response); + if (multipart_parsing_state_ == MultipartParsingState::kWaitingForFirstPart) { + // We have nothing to do because we don't have any data. + multipart_parsing_state_ = MultipartParsingState::kParsingFirstPart; + return; + } + UpdateImageAndClearBuffer(); + + if (multipart_parsing_state_ == MultipartParsingState::kParsingFirstPart) { + multipart_parsing_state_ = MultipartParsingState::kFinishedParsingFirstPart; + // Notify finished when the first part ends. + if (!ErrorOccurred()) + SetStatus(ResourceStatus::kCached); + // We notify clients and observers of finish in checkNotify() and + // updateImageAndClearBuffer(), respectively, and they will not be + // notified again in Resource::finish()/error(). + NotifyFinished(); + if (Loader()) + Loader()->DidFinishLoadingFirstPartInMultipart(); + } +} + +void ImageResource::MultipartDataReceived(const char* bytes, size_t size) { + DCHECK(multipart_parser_); + Resource::AppendData(bytes, size); +} + +bool ImageResource::IsAccessAllowed( + const SecurityOrigin* security_origin, + ImageResourceInfo::DoesCurrentFrameHaveSingleSecurityOrigin + does_current_frame_has_single_security_origin) const { + if (GetResponse().WasFetchedViaServiceWorker()) + return GetCORSStatus() != CORSStatus::kServiceWorkerOpaque; + + if (does_current_frame_has_single_security_origin != + ImageResourceInfo::kHasSingleSecurityOrigin) + return false; + + DCHECK(security_origin); + if (PassesAccessControlCheck(*security_origin)) + return true; + + return security_origin->CanReadContent(GetResponse().Url()); +} + +ImageResourceContent* ImageResource::GetContent() { + return content_; +} + +const ImageResourceContent* ImageResource::GetContent() const { + return content_; +} + +ResourcePriority ImageResource::PriorityFromObservers() { + return GetContent()->PriorityFromObservers(); +} + +void ImageResource::UpdateImage( + scoped_refptr<SharedBuffer> shared_buffer, + ImageResourceContent::UpdateImageOption update_image_option, + bool all_data_received) { + bool is_multipart = !!multipart_parser_; + auto result = GetContent()->UpdateImage(std::move(shared_buffer), GetStatus(), + update_image_option, + all_data_received, is_multipart); + if (result == ImageResourceContent::UpdateImageResult::kShouldDecodeError) { + // In case of decode error, we call imageNotifyFinished() iff we don't + // initiate reloading: + // [(a): when this is in the middle of loading, or (b): otherwise] + // 1. The updateImage() call above doesn't call notifyObservers(). + // 2. notifyObservers(ShouldNotifyFinish) is called + // (a) via updateImage() called in ImageResource::finish() + // called via didFinishLoading() called in decodeError(), or + // (b) via updateImage() called in decodeError(). + // imageNotifyFinished() is called here iff we will not initiate + // reloading in Step 3 due to notifyObservers()'s + // schedulingReloadOrShouldReloadBrokenPlaceholder() check. + // 3. reloadIfLoFiOrPlaceholderImage() is called via ResourceFetcher + // (a) via didFinishLoading() called in decodeError(), or + // (b) after returning ImageResource::updateImage(). + DecodeError(all_data_received); + } +} + +void ImageResource::FlagAsUserAgentResource() { + if (is_referenced_from_ua_stylesheet_) + return; + + InstanceCounters::IncrementCounter(InstanceCounters::kUACSSResourceCounter); + is_referenced_from_ua_stylesheet_ = true; +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/loader/resource/image_resource.h b/chromium/third_party/blink/renderer/core/loader/resource/image_resource.h new file mode 100644 index 00000000000..5f9de3fa72b --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/resource/image_resource.h @@ -0,0 +1,192 @@ +/* + Copyright (C) 1998 Lars Knoll (knoll@mpi-hd.mpg.de) + Copyright (C) 2001 Dirk Mueller <mueller@kde.org> + Copyright (C) 2006 Samuel Weinig (sam.weinig@gmail.com) + Copyright (C) 2004, 2005, 2006, 2007 Apple Inc. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_RESOURCE_IMAGE_RESOURCE_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_RESOURCE_IMAGE_RESOURCE_H_ + +#include <memory> +#include "third_party/blink/renderer/core/core_export.h" +#include "third_party/blink/renderer/core/loader/resource/image_resource_content.h" +#include "third_party/blink/renderer/core/loader/resource/image_resource_info.h" +#include "third_party/blink/renderer/core/loader/resource/multipart_image_resource_parser.h" +#include "third_party/blink/renderer/platform/heap/handle.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource.h" +#include "third_party/blink/renderer/platform/timer.h" + +namespace blink { + +class FetchParameters; +class ImageResourceContent; +class ResourceClient; +class ResourceFetcher; +class SecurityOrigin; + +// ImageResource implements blink::Resource interface and image-specific logic +// for loading images. +// Image-related things (blink::Image and ImageResourceObserver) are handled by +// ImageResourceContent. +// Most users should use ImageResourceContent instead of ImageResource. +// https://docs.google.com/document/d/1O-fB83mrE0B_V8gzXNqHgmRLCvstTB4MMi3RnVLr8bE/edit?usp=sharing +// +// As for the lifetimes of ImageResourceContent::m_image and m_data, see this +// document: +// https://docs.google.com/document/d/1v0yTAZ6wkqX2U_M6BNIGUJpM1s0TIw1VsqpxoL7aciY/edit?usp=sharing +class CORE_EXPORT ImageResource final + : public Resource, + public MultipartImageResourceParser::Client { + USING_GARBAGE_COLLECTED_MIXIN(ImageResource); + + public: + // Use ImageResourceContent::Fetch() unless ImageResource is required. + // TODO(hiroshige): Make Fetch() private. + static ImageResource* Fetch(FetchParameters&, ResourceFetcher*); + + // TODO(hiroshige): Make Create() test-only by refactoring ImageDocument. + static ImageResource* Create(const ResourceRequest&); + static ImageResource* CreateForTest(const KURL&); + + ~ImageResource() override; + + ImageResourceContent* GetContent(); + const ImageResourceContent* GetContent() const; + + void ReloadIfLoFiOrPlaceholderImage(ResourceFetcher*, + ReloadLoFiOrPlaceholderPolicy) override; + + void DidAddClient(ResourceClient*) override; + + ResourcePriority PriorityFromObservers() override; + + void AllClientsAndObserversRemoved() override; + + bool CanReuse( + const FetchParameters&, + scoped_refptr<const SecurityOrigin> new_source_origin) const override; + bool CanUseCacheValidator() const override; + + scoped_refptr<const SharedBuffer> ResourceBuffer() const override; + void NotifyStartLoad() override; + void ResponseReceived(const ResourceResponse&, + std::unique_ptr<WebDataConsumerHandle>) override; + void AppendData(const char*, size_t) override; + void Finish(double finish_time, base::SingleThreadTaskRunner*) override; + void FinishAsError(const ResourceError&, + base::SingleThreadTaskRunner*) override; + + // For compatibility, images keep loading even if there are HTTP errors. + bool ShouldIgnoreHTTPStatusCodeErrors() const override { return true; } + + // MultipartImageResourceParser::Client + void OnePartInMultipartReceived(const ResourceResponse&) final; + void MultipartDataReceived(const char*, size_t) final; + + bool ShouldShowPlaceholder() const; + + // If the ImageResource came from a user agent CSS stylesheet then we should + // flag it so that it can persist beyond navigation. + void FlagAsUserAgentResource(); + + void OnMemoryDump(WebMemoryDumpLevelOfDetail, + WebProcessMemoryDump*) const override; + + void Trace(blink::Visitor*) override; + + private: + enum class MultipartParsingState : uint8_t { + kWaitingForFirstPart, + kParsingFirstPart, + kFinishedParsingFirstPart, + }; + + class ImageResourceInfoImpl; + class ImageResourceFactory; + + ImageResource(const ResourceRequest&, + const ResourceLoaderOptions&, + ImageResourceContent*, + bool is_placeholder); + + // Only for ImageResourceInfoImpl. + void DecodeError(bool all_data_received); + bool IsAccessAllowed( + const SecurityOrigin*, + ImageResourceInfo::DoesCurrentFrameHaveSingleSecurityOrigin) const; + + bool HasClientsOrObservers() const override; + + void UpdateImageAndClearBuffer(); + void UpdateImage(scoped_refptr<SharedBuffer>, + ImageResourceContent::UpdateImageOption, + bool all_data_received); + + void NotifyFinished() override; + + void DestroyDecodedDataIfPossible() override; + void DestroyDecodedDataForFailedRevalidation() override; + + void FlushImageIfNeeded(); + + bool ShouldReloadBrokenPlaceholder() const; + + Member<ImageResourceContent> content_; + + Member<MultipartImageResourceParser> multipart_parser_; + MultipartParsingState multipart_parsing_state_ = + MultipartParsingState::kWaitingForFirstPart; + + // Indicates if the ImageResource is currently scheduling a reload, e.g. + // because reloadIfLoFi() was called. + bool is_scheduling_reload_; + + // Indicates if this ImageResource is either attempting to load a placeholder + // image, or is a (possibly broken) placeholder image. + enum class PlaceholderOption { + // Do not show or reload placeholder. + kDoNotReloadPlaceholder, + + // Show placeholder, and do not reload. The original image will still be + // loaded and shown if the image is explicitly reloaded, e.g. when + // ReloadIfLoFiOrPlaceholderImage is called with kReloadAlways. + kShowAndDoNotReloadPlaceholder, + + // Do not show placeholder, reload only when decode error occurs. + kReloadPlaceholderOnDecodeError, + + // Show placeholder and reload. + kShowAndReloadPlaceholderAlways, + }; + PlaceholderOption placeholder_option_; + + double last_flush_time_ = 0.; + + bool is_during_finish_as_error_ = false; + + bool is_referenced_from_ua_stylesheet_ = false; + + bool is_pending_flushing_ = false; +}; + +DEFINE_RESOURCE_TYPE_CASTS(Image); + +} // namespace blink + +#endif diff --git a/chromium/third_party/blink/renderer/core/loader/resource/image_resource_content.cc b/chromium/third_party/blink/renderer/core/loader/resource/image_resource_content.cc new file mode 100644 index 00000000000..d87d87ea33c --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/resource/image_resource_content.cc @@ -0,0 +1,639 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "third_party/blink/renderer/core/loader/resource/image_resource_content.h" + +#include <memory> + +#include "third_party/blink/renderer/core/loader/resource/image_resource.h" +#include "third_party/blink/renderer/core/loader/resource/image_resource_info.h" +#include "third_party/blink/renderer/core/loader/resource/image_resource_observer.h" +#include "third_party/blink/renderer/core/svg/graphics/svg_image.h" +#include "third_party/blink/renderer/platform/geometry/int_size.h" +#include "third_party/blink/renderer/platform/graphics/bitmap_image.h" +#include "third_party/blink/renderer/platform/graphics/placeholder_image.h" +#include "third_party/blink/renderer/platform/histogram.h" +#include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h" +#include "third_party/blink/renderer/platform/network/http_parsers.h" +#include "third_party/blink/renderer/platform/shared_buffer.h" +#include "third_party/blink/renderer/platform/wtf/std_lib_extras.h" +#include "third_party/blink/renderer/platform/wtf/vector.h" +#include "v8/include/v8.h" + +namespace blink { + +namespace { + +class NullImageResourceInfo final + : public GarbageCollectedFinalized<NullImageResourceInfo>, + public ImageResourceInfo { + USING_GARBAGE_COLLECTED_MIXIN(NullImageResourceInfo); + + public: + NullImageResourceInfo() = default; + + void Trace(blink::Visitor* visitor) override { + ImageResourceInfo::Trace(visitor); + } + + private: + const KURL& Url() const override { return url_; } + bool IsSchedulingReload() const override { return false; } + const ResourceResponse& GetResponse() const override { return response_; } + bool ShouldShowPlaceholder() const override { return false; } + bool IsCacheValidator() const override { return false; } + bool SchedulingReloadOrShouldReloadBrokenPlaceholder() const override { + return false; + } + bool IsAccessAllowed( + const SecurityOrigin*, + DoesCurrentFrameHaveSingleSecurityOrigin) const override { + return true; + } + bool HasCacheControlNoStoreHeader() const override { return false; } + Optional<ResourceError> GetResourceError() const override { + return WTF::nullopt; + } + + void SetDecodedSize(size_t) override {} + void WillAddClientOrObserver() override {} + void DidRemoveClientOrObserver() override {} + void EmulateLoadStartedForInspector( + ResourceFetcher*, + const KURL&, + const AtomicString& initiator_name) override {} + + const KURL url_; + const ResourceResponse response_; +}; + +int64_t EstimateOriginalImageSizeForPlaceholder( + const ResourceResponse& response) { + if (response.HttpHeaderField("chrome-proxy-content-transform") == + "empty-image") { + const String& str = response.HttpHeaderField("chrome-proxy"); + size_t index = str.Find("ofcl="); + if (index != kNotFound) { + bool ok = false; + int bytes = str.Substring(index + (sizeof("ofcl=") - 1)).ToInt(&ok); + if (ok && bytes >= 0) + return bytes; + } + } + + int64_t first = -1, last = -1, length = -1; + if (response.HttpStatusCode() == 206 && + ParseContentRangeHeaderFor206(response.HttpHeaderField("content-range"), + &first, &last, &length) && + length >= 0) { + return length; + } + + return response.EncodedBodyLength(); +} + +} // namespace + +ImageResourceContent::ImageResourceContent(scoped_refptr<blink::Image> image) + : is_refetchable_data_from_disk_cache_(true), + device_pixel_ratio_header_value_(1.0), + has_device_pixel_ratio_header_value_(false), + image_(std::move(image)) { + DEFINE_STATIC_LOCAL(NullImageResourceInfo, null_info, + (new NullImageResourceInfo())); + info_ = &null_info; +} + +ImageResourceContent* ImageResourceContent::CreateLoaded( + scoped_refptr<blink::Image> image) { + DCHECK(image); + ImageResourceContent* content = new ImageResourceContent(std::move(image)); + content->content_status_ = ResourceStatus::kCached; + return content; +} + +ImageResourceContent* ImageResourceContent::Fetch(FetchParameters& params, + ResourceFetcher* fetcher) { + // TODO(hiroshige): Remove direct references to ImageResource by making + // the dependencies around ImageResource and ImageResourceContent cleaner. + ImageResource* resource = ImageResource::Fetch(params, fetcher); + if (!resource) + return nullptr; + return resource->GetContent(); +} + +void ImageResourceContent::SetImageResourceInfo(ImageResourceInfo* info) { + info_ = info; +} + +void ImageResourceContent::Trace(blink::Visitor* visitor) { + visitor->Trace(info_); + ImageObserver::Trace(visitor); +} + +void ImageResourceContent::MarkObserverFinished( + ImageResourceObserver* observer) { + ProhibitAddRemoveObserverInScope prohibit_add_remove_observer_in_scope(this); + + auto it = observers_.find(observer); + if (it == observers_.end()) + return; + observers_.erase(it); + finished_observers_.insert(observer); +} + +void ImageResourceContent::AddObserver(ImageResourceObserver* observer) { + CHECK(!is_add_remove_observer_prohibited_); + + info_->WillAddClientOrObserver(); + + { + ProhibitAddRemoveObserverInScope prohibit_add_remove_observer_in_scope( + this); + observers_.insert(observer); + } + + if (info_->IsCacheValidator()) + return; + + if (image_ && !image_->IsNull()) { + observer->ImageChanged(this, CanDeferInvalidation::kNo); + } + + if (IsLoaded() && observers_.Contains(observer) && + !info_->SchedulingReloadOrShouldReloadBrokenPlaceholder()) { + MarkObserverFinished(observer); + observer->ImageNotifyFinished(this); + } +} + +void ImageResourceContent::RemoveObserver(ImageResourceObserver* observer) { + DCHECK(observer); + CHECK(!is_add_remove_observer_prohibited_); + ProhibitAddRemoveObserverInScope prohibit_add_remove_observer_in_scope(this); + + auto it = observers_.find(observer); + if (it != observers_.end()) { + observers_.erase(it); + } else { + it = finished_observers_.find(observer); + DCHECK(it != finished_observers_.end()); + finished_observers_.erase(it); + } + info_->DidRemoveClientOrObserver(); +} + +static void PriorityFromObserver(const ImageResourceObserver* observer, + ResourcePriority& priority) { + ResourcePriority next_priority = observer->ComputeResourcePriority(); + if (next_priority.visibility == ResourcePriority::kNotVisible) + return; + priority.visibility = ResourcePriority::kVisible; + priority.intra_priority_value += next_priority.intra_priority_value; +} + +ResourcePriority ImageResourceContent::PriorityFromObservers() const { + ProhibitAddRemoveObserverInScope prohibit_add_remove_observer_in_scope(this); + ResourcePriority priority; + + for (const auto& it : finished_observers_) + PriorityFromObserver(it.key, priority); + for (const auto& it : observers_) + PriorityFromObserver(it.key, priority); + + return priority; +} + +void ImageResourceContent::DestroyDecodedData() { + if (!image_) + return; + CHECK(!ErrorOccurred()); + image_->DestroyDecodedData(); +} + +void ImageResourceContent::DoResetAnimation() { + if (image_) + image_->ResetAnimation(); +} + +scoped_refptr<const SharedBuffer> ImageResourceContent::ResourceBuffer() const { + if (image_) + return image_->Data(); + return nullptr; +} + +bool ImageResourceContent::ShouldUpdateImageImmediately() const { + // If we don't have the size available yet, then update immediately since + // we need to know the image size as soon as possible. Likewise for + // animated images, update right away since we shouldn't throttle animated + // images. + return size_available_ == Image::kSizeUnavailable || + (image_ && image_->MaybeAnimated()); +} + +blink::Image* ImageResourceContent::GetImage() const { + if (!image_ || ErrorOccurred()) + return Image::NullImage(); + + return image_.get(); +} + +std::pair<blink::Image*, float> ImageResourceContent::BrokenCanvas( + float device_scale_factor) { + if (device_scale_factor >= 2) { + DEFINE_STATIC_REF(blink::Image, broken_canvas_hi_res, + (blink::Image::LoadPlatformResource("brokenCanvas@2x"))); + return std::make_pair(broken_canvas_hi_res, 2); + } + + DEFINE_STATIC_REF(blink::Image, broken_canvas_lo_res, + (blink::Image::LoadPlatformResource("brokenCanvas"))); + return std::make_pair(broken_canvas_lo_res, 1); +} + +IntSize ImageResourceContent::IntrinsicSize( + RespectImageOrientationEnum should_respect_image_orientation) { + if (!image_) + return IntSize(); + if (should_respect_image_orientation == kRespectImageOrientation && + image_->IsBitmapImage()) + return ToBitmapImage(image_.get())->SizeRespectingOrientation(); + return image_->Size(); +} + +void ImageResourceContent::NotifyObservers( + NotifyFinishOption notifying_finish_option, + CanDeferInvalidation defer, + const IntRect* change_rect) { + { + Vector<ImageResourceObserver*> finished_observers_as_vector; + { + ProhibitAddRemoveObserverInScope prohibit_add_remove_observer_in_scope( + this); + finished_observers_as_vector = finished_observers_.AsVector(); + } + + for (auto* observer : finished_observers_as_vector) { + if (finished_observers_.Contains(observer)) + observer->ImageChanged(this, defer, change_rect); + } + } + { + Vector<ImageResourceObserver*> observers_as_vector; + { + ProhibitAddRemoveObserverInScope prohibit_add_remove_observer_in_scope( + this); + observers_as_vector = observers_.AsVector(); + } + + for (auto* observer : observers_as_vector) { + if (observers_.Contains(observer)) { + observer->ImageChanged(this, defer, change_rect); + if (notifying_finish_option == kShouldNotifyFinish && + observers_.Contains(observer) && + !info_->SchedulingReloadOrShouldReloadBrokenPlaceholder()) { + MarkObserverFinished(observer); + observer->ImageNotifyFinished(this); + } + } + } + } +} + +scoped_refptr<Image> ImageResourceContent::CreateImage(bool is_multipart) { + device_pixel_ratio_header_value_ = + info_->GetResponse() + .HttpHeaderField(HTTPNames::Content_DPR) + .ToFloat(&has_device_pixel_ratio_header_value_); + if (!has_device_pixel_ratio_header_value_ || + device_pixel_ratio_header_value_ <= 0.0) { + device_pixel_ratio_header_value_ = 1.0; + has_device_pixel_ratio_header_value_ = false; + } + if (info_->GetResponse().MimeType() == "image/svg+xml") + return SVGImage::Create(this, is_multipart); + return BitmapImage::Create(this, is_multipart); +} + +void ImageResourceContent::ClearImage() { + if (!image_) + return; + int64_t length = image_->Data() ? image_->Data()->size() : 0; + v8::Isolate::GetCurrent()->AdjustAmountOfExternalAllocatedMemory(-length); + + // If our Image has an observer, it's always us so we need to clear the back + // pointer before dropping our reference. + image_->ClearImageObserver(); + image_ = nullptr; + size_available_ = Image::kSizeUnavailable; +} + +// |new_status| is the status of corresponding ImageResource. +void ImageResourceContent::UpdateToLoadedContentStatus( + ResourceStatus new_status) { + // When |ShouldNotifyFinish|, we set content_status_ + // to a loaded ResourceStatus. + + // Checks |new_status| (i.e. Resource's current status). + switch (new_status) { + case ResourceStatus::kCached: + case ResourceStatus::kPending: + // In case of successful load, Resource's status can be + // kCached (e.g. for second part of multipart image) or + // still Pending (e.g. for a non-multipart image). + // Therefore we use kCached as the new state here. + new_status = ResourceStatus::kCached; + break; + + case ResourceStatus::kLoadError: + case ResourceStatus::kDecodeError: + // In case of error, Resource's status is set to an error status + // before UpdateImage() and thus we use the error status as-is. + break; + + case ResourceStatus::kNotStarted: + CHECK(false); + break; + } + + // Checks ImageResourceContent's previous status. + switch (GetContentStatus()) { + case ResourceStatus::kPending: + // A non-multipart image or the first part of a multipart image. + break; + + case ResourceStatus::kCached: + case ResourceStatus::kLoadError: + case ResourceStatus::kDecodeError: + // Second (or later) part of a multipart image. + // TODO(hiroshige): Assert that this is actually a multipart image. + break; + + case ResourceStatus::kNotStarted: + // Should have updated to kPending via NotifyStartLoad(). + CHECK(false); + break; + } + + // Updates the status. + content_status_ = new_status; +} + +void ImageResourceContent::NotifyStartLoad() { + // Checks ImageResourceContent's previous status. + switch (GetContentStatus()) { + case ResourceStatus::kPending: + CHECK(false); + break; + + case ResourceStatus::kNotStarted: + // Normal load start. + break; + + case ResourceStatus::kCached: + case ResourceStatus::kLoadError: + case ResourceStatus::kDecodeError: + // Load start due to revalidation/reload. + break; + } + + content_status_ = ResourceStatus::kPending; +} + +void ImageResourceContent::AsyncLoadCompleted(const blink::Image* image) { + if (image_ != image) + return; + CHECK_EQ(size_available_, Image::kSizeAvailableAndLoadingAsynchronously); + size_available_ = Image::kSizeAvailable; + UpdateToLoadedContentStatus(ResourceStatus::kCached); + NotifyObservers(kShouldNotifyFinish, CanDeferInvalidation::kNo); +} + +ImageResourceContent::UpdateImageResult ImageResourceContent::UpdateImage( + scoped_refptr<SharedBuffer> data, + ResourceStatus status, + UpdateImageOption update_image_option, + bool all_data_received, + bool is_multipart) { + TRACE_EVENT0("blink", "ImageResourceContent::updateImage"); + +#if DCHECK_IS_ON() + DCHECK(!is_update_image_being_called_); + AutoReset<bool> scope(&is_update_image_being_called_, true); +#endif + + CHECK_NE(GetContentStatus(), ResourceStatus::kNotStarted); + + // Clears the existing image, if instructed by |updateImageOption|. + switch (update_image_option) { + case kClearAndUpdateImage: + case kClearImageAndNotifyObservers: + ClearImage(); + break; + case kUpdateImage: + break; + } + + // Updates the image, if instructed by |updateImageOption|. + switch (update_image_option) { + case kClearImageAndNotifyObservers: + DCHECK(!data); + break; + + case kUpdateImage: + case kClearAndUpdateImage: + // 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). + if (data) { + if (!image_) + image_ = CreateImage(is_multipart); + DCHECK(image_); + size_available_ = image_->SetData(std::move(data), all_data_received); + DCHECK(all_data_received || + size_available_ != + Image::kSizeAvailableAndLoadingAsynchronously); + } + + // Go ahead and tell our observers to try to draw if we have either + // received all the data or the size is known. Each chunk from the network + // causes observers to repaint, which will force that chunk to decode. + if (size_available_ == Image::kSizeUnavailable && !all_data_received) + return UpdateImageResult::kNoDecodeError; + + if (info_->ShouldShowPlaceholder() && all_data_received) { + if (image_ && !image_->IsNull()) { + IntSize dimensions = image_->Size(); + ClearImage(); + image_ = PlaceholderImage::Create( + this, dimensions, + EstimateOriginalImageSizeForPlaceholder(info_->GetResponse())); + } + } + + // As per spec, zero intrinsic size SVG is a valid image so do not + // consider such an image as DecodeError. + // https://www.w3.org/TR/SVG/struct.html#SVGElementWidthAttribute + if (!image_ || + (image_->IsNull() && (!image_->IsSVGImage() || + size_available_ == Image::kSizeUnavailable))) { + ClearImage(); + return UpdateImageResult::kShouldDecodeError; + } + break; + } + + DCHECK(all_data_received || + size_available_ != Image::kSizeAvailableAndLoadingAsynchronously); + + // Notifies the observers. + // 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. + // + // In the case of kSizeAvailableAndLoadingAsynchronously, we are waiting for + // SVG image completion, and thus we notify observers of kDoNotNotifyFinish + // here, and will notify observers of finish later in AsyncLoadCompleted(). + // + // Don't allow defering of invalidation if it resulted from a data update. + // This is necessary to ensure that all PaintImages in a recording committed + // to the compositor have the same data. + if (all_data_received && + size_available_ != Image::kSizeAvailableAndLoadingAsynchronously) { + UpdateToLoadedContentStatus(status); + NotifyObservers(kShouldNotifyFinish, CanDeferInvalidation::kNo); + } else { + NotifyObservers(kDoNotNotifyFinish, CanDeferInvalidation::kNo); + } + + return UpdateImageResult::kNoDecodeError; +} + +void ImageResourceContent::DecodedSizeChangedTo(const blink::Image* image, + size_t new_size) { + if (!image || image != image_) + return; + + info_->SetDecodedSize(new_size); +} + +bool ImageResourceContent::ShouldPauseAnimation(const blink::Image* image) { + if (!image || image != image_) + return false; + + ProhibitAddRemoveObserverInScope prohibit_add_remove_observer_in_scope(this); + + for (const auto& it : finished_observers_) { + if (it.key->WillRenderImage()) + return false; + } + + for (const auto& it : observers_) { + if (it.key->WillRenderImage()) + return false; + } + + return true; +} + +void ImageResourceContent::AnimationAdvanced(const blink::Image* image) { + if (!image || image != image_) + return; + NotifyObservers(kDoNotNotifyFinish, CanDeferInvalidation::kYes); +} + +void ImageResourceContent::UpdateImageAnimationPolicy() { + if (!image_) + return; + + ImageAnimationPolicy new_policy = kImageAnimationPolicyAllowed; + { + ProhibitAddRemoveObserverInScope prohibit_add_remove_observer_in_scope( + this); + for (const auto& it : finished_observers_) { + if (it.key->GetImageAnimationPolicy(new_policy)) + break; + } + for (const auto& it : observers_) { + if (it.key->GetImageAnimationPolicy(new_policy)) + break; + } + } + + image_->SetAnimationPolicy(new_policy); +} + +void ImageResourceContent::ChangedInRect(const blink::Image* image, + const IntRect& rect) { + if (!image || image != image_) + return; + NotifyObservers(kDoNotNotifyFinish, CanDeferInvalidation::kYes, &rect); +} + +bool ImageResourceContent::IsAccessAllowed( + const SecurityOrigin* security_origin) { + return info_->IsAccessAllowed( + security_origin, GetImage()->CurrentFrameHasSingleSecurityOrigin() + ? ImageResourceInfo::kHasSingleSecurityOrigin + : ImageResourceInfo::kHasMultipleSecurityOrigin); +} + +void ImageResourceContent::EmulateLoadStartedForInspector( + ResourceFetcher* fetcher, + const KURL& url, + const AtomicString& initiator_name) { + info_->EmulateLoadStartedForInspector(fetcher, url, initiator_name); +} + +bool ImageResourceContent::IsLoaded() const { + return GetContentStatus() > ResourceStatus::kPending; +} + +bool ImageResourceContent::IsLoading() const { + return GetContentStatus() == ResourceStatus::kPending; +} + +bool ImageResourceContent::ErrorOccurred() const { + return GetContentStatus() == ResourceStatus::kLoadError || + GetContentStatus() == ResourceStatus::kDecodeError; +} + +bool ImageResourceContent::LoadFailedOrCanceled() const { + return GetContentStatus() == ResourceStatus::kLoadError; +} + +ResourceStatus ImageResourceContent::GetContentStatus() const { + return content_status_; +} + +// TODO(hiroshige): Consider removing the following methods, or stoping +// redirecting to ImageResource. +const KURL& ImageResourceContent::Url() const { + return info_->Url(); +} + +bool ImageResourceContent::HasCacheControlNoStoreHeader() const { + return info_->HasCacheControlNoStoreHeader(); +} + +float ImageResourceContent::DevicePixelRatioHeaderValue() const { + return device_pixel_ratio_header_value_; +} + +bool ImageResourceContent::HasDevicePixelRatioHeaderValue() const { + return has_device_pixel_ratio_header_value_; +} + +const ResourceResponse& ImageResourceContent::GetResponse() const { + return info_->GetResponse(); +} + +Optional<ResourceError> ImageResourceContent::GetResourceError() const { + return info_->GetResourceError(); +} + +bool ImageResourceContent::IsCacheValidator() const { + return info_->IsCacheValidator(); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/loader/resource/image_resource_content.h b/chromium/third_party/blink/renderer/core/loader/resource/image_resource_content.h new file mode 100644 index 00000000000..66b8c3f0569 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/resource/image_resource_content.h @@ -0,0 +1,236 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_RESOURCE_IMAGE_RESOURCE_CONTENT_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_RESOURCE_IMAGE_RESOURCE_CONTENT_H_ + +#include <memory> +#include "third_party/blink/renderer/core/core_export.h" +#include "third_party/blink/renderer/core/loader/resource/image_resource_observer.h" +#include "third_party/blink/renderer/platform/geometry/int_rect.h" +#include "third_party/blink/renderer/platform/graphics/image.h" +#include "third_party/blink/renderer/platform/graphics/image_observer.h" +#include "third_party/blink/renderer/platform/graphics/image_orientation.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource_error.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource_load_priority.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource_status.h" +#include "third_party/blink/renderer/platform/weborigin/kurl.h" +#include "third_party/blink/renderer/platform/wtf/auto_reset.h" +#include "third_party/blink/renderer/platform/wtf/hash_counted_set.h" +#include "third_party/blink/renderer/platform/wtf/hash_map.h" + +namespace blink { + +class FetchParameters; +class ImageResourceInfo; +class ImageResourceObserver; +class ResourceError; +class ResourceFetcher; +class ResourceResponse; +class SecurityOrigin; + +// ImageResourceContent is a container that holds fetch result of +// an ImageResource in a decoded form. +// Classes that use the fetched images +// should hold onto this class and/or inherit ImageResourceObserver, +// instead of holding onto ImageResource or inheriting ResourceClient. +// https://docs.google.com/document/d/1O-fB83mrE0B_V8gzXNqHgmRLCvstTB4MMi3RnVLr8bE/edit?usp=sharing +// TODO(hiroshige): Make ImageResourceContent ResourceClient and remove the +// word 'observer' from ImageResource. +// TODO(hiroshige): Rename local variables of type ImageResourceContent to +// e.g. |imageContent|. Currently they have Resource-like names. +class CORE_EXPORT ImageResourceContent final + : public GarbageCollectedFinalized<ImageResourceContent>, + public ImageObserver { + USING_GARBAGE_COLLECTED_MIXIN(ImageResourceContent); + + public: + // Used for loading. + // Returned content will be associated immediately later with ImageResource. + static ImageResourceContent* CreateNotStarted() { + return new ImageResourceContent(nullptr); + } + + // Creates ImageResourceContent from an already loaded image. + static ImageResourceContent* CreateLoaded(scoped_refptr<blink::Image>); + + static ImageResourceContent* Fetch(FetchParameters&, ResourceFetcher*); + + // Returns the NullImage() if the image is not available yet. + blink::Image* GetImage() const; + bool HasImage() const { return image_.get(); } + + // Returns an image and the image's resolution scale factor. + static std::pair<blink::Image*, float> BrokenCanvas( + float device_scale_factor); + + // The device pixel ratio we got from the server for this image, or 1.0. + float DevicePixelRatioHeaderValue() const; + bool HasDevicePixelRatioHeaderValue() const; + + // Returns the intrinsic width and height of the image, or 0x0 if no image + // exists. If the image is a BitmapImage, then this corresponds to the + // physical pixel dimensions of the image. If the image is an SVGImage, this + // does not quite return the intrinsic width/height, but rather a concrete + // object size resolved using a default object size of 300x150. + // TODO(fs): Make SVGImages return proper intrinsic width/height. + IntSize IntrinsicSize( + RespectImageOrientationEnum should_respect_image_orientation); + + void UpdateImageAnimationPolicy(); + + void AddObserver(ImageResourceObserver*); + void RemoveObserver(ImageResourceObserver*); + + bool IsSizeAvailable() const { + return size_available_ != Image::kSizeUnavailable; + } + + void Trace(blink::Visitor*) override; + + // Content status and deriving predicates. + // https://docs.google.com/document/d/1O-fB83mrE0B_V8gzXNqHgmRLCvstTB4MMi3RnVLr8bE/edit#heading=h.6cyqmir0f30h + // Normal transitions: + // kNotStarted -> kPending -> kCached|kLoadError|kDecodeError. + // Additional transitions in multipart images: + // kCached -> kLoadError|kDecodeError. + // Transitions due to revalidation: + // kCached -> kPending. + // Transitions due to reload: + // kCached|kLoadError|kDecodeError -> kPending. + // + // ImageResourceContent::GetContentStatus() can be different from + // ImageResource::GetStatus(). Use ImageResourceContent::GetContentStatus(). + ResourceStatus GetContentStatus() const; + bool IsLoaded() const; + bool IsLoading() const; + bool ErrorOccurred() const; + bool LoadFailedOrCanceled() const; + + // Redirecting methods to Resource. + const KURL& Url() const; + bool IsAccessAllowed(const SecurityOrigin*); + const ResourceResponse& GetResponse() const; + Optional<ResourceError> GetResourceError() const; + // DEPRECATED: ImageResourceContents consumers shouldn't need to worry about + // whether the underlying Resource is being revalidated. + bool IsCacheValidator() const; + + // For FrameSerializer. + bool HasCacheControlNoStoreHeader() const; + + void EmulateLoadStartedForInspector(ResourceFetcher*, + const KURL&, + const AtomicString& initiator_name); + + void SetNotRefetchableDataFromDiskCache() { + is_refetchable_data_from_disk_cache_ = false; + } + + // The following public methods should be called from ImageResource only. + + // UpdateImage() is the single control point of image content modification + // from ImageResource that all image updates should call. + // We clear and/or update images in this single method + // (controlled by UpdateImageOption) rather than providing separate methods, + // in order to centralize state changes and + // not to expose the state in between to ImageResource. + enum UpdateImageOption { + // Updates the image (including placeholder and decode error handling + // and notifying observers) if needed. + kUpdateImage, + + // Clears the image and then updates the image if needed. + kClearAndUpdateImage, + + // Clears the image and always notifies observers (without updating). + kClearImageAndNotifyObservers, + }; + enum class UpdateImageResult { + kNoDecodeError, + + // Decode error occurred. Observers are not notified. + // Only occurs when UpdateImage or ClearAndUpdateImage is specified. + kShouldDecodeError, + }; + WARN_UNUSED_RESULT UpdateImageResult UpdateImage(scoped_refptr<SharedBuffer>, + ResourceStatus, + UpdateImageOption, + bool all_data_received, + bool is_multipart); + + void NotifyStartLoad(); + void DestroyDecodedData(); + void DoResetAnimation(); + + void SetImageResourceInfo(ImageResourceInfo*); + + ResourcePriority PriorityFromObservers() const; + scoped_refptr<const SharedBuffer> ResourceBuffer() const; + bool ShouldUpdateImageImmediately() const; + bool HasObservers() const { + return !observers_.IsEmpty() || !finished_observers_.IsEmpty(); + } + bool IsRefetchableDataFromDiskCache() const { + return is_refetchable_data_from_disk_cache_; + } + + private: + using CanDeferInvalidation = ImageResourceObserver::CanDeferInvalidation; + + explicit ImageResourceContent(scoped_refptr<blink::Image> = nullptr); + + // ImageObserver + void DecodedSizeChangedTo(const blink::Image*, size_t new_size) override; + bool ShouldPauseAnimation(const blink::Image*) override; + void AnimationAdvanced(const blink::Image*) override; + void ChangedInRect(const blink::Image*, const IntRect&) override; + void AsyncLoadCompleted(const blink::Image*) override; + + scoped_refptr<Image> CreateImage(bool is_multipart); + void ClearImage(); + + enum NotifyFinishOption { kShouldNotifyFinish, kDoNotNotifyFinish }; + + // If not null, changeRect is the changed part of the image. + void NotifyObservers(NotifyFinishOption, + CanDeferInvalidation, + const IntRect* change_rect = nullptr); + void MarkObserverFinished(ImageResourceObserver*); + void UpdateToLoadedContentStatus(ResourceStatus); + + class ProhibitAddRemoveObserverInScope : public AutoReset<bool> { + public: + ProhibitAddRemoveObserverInScope(const ImageResourceContent* content) + : AutoReset(&content->is_add_remove_observer_prohibited_, true) {} + }; + + ResourceStatus content_status_ = ResourceStatus::kNotStarted; + + // Indicates if this resource's encoded image data can be purged and refetched + // from disk cache to save memory usage. See crbug/664437. + bool is_refetchable_data_from_disk_cache_; + + mutable bool is_add_remove_observer_prohibited_ = false; + + Image::SizeAvailability size_available_ = Image::kSizeUnavailable; + + Member<ImageResourceInfo> info_; + + float device_pixel_ratio_header_value_; + bool has_device_pixel_ratio_header_value_; + + scoped_refptr<blink::Image> image_; + + HashCountedSet<ImageResourceObserver*> observers_; + HashCountedSet<ImageResourceObserver*> finished_observers_; + +#if DCHECK_IS_ON() + bool is_update_image_being_called_ = false; +#endif +}; + +} // namespace blink + +#endif diff --git a/chromium/third_party/blink/renderer/core/loader/resource/image_resource_info.h b/chromium/third_party/blink/renderer/core/loader/resource/image_resource_info.h new file mode 100644 index 00000000000..bedf300abe6 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/resource/image_resource_info.h @@ -0,0 +1,66 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_RESOURCE_IMAGE_RESOURCE_INFO_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_RESOURCE_IMAGE_RESOURCE_INFO_H_ + +#include "third_party/blink/renderer/core/core_export.h" +#include "third_party/blink/renderer/platform/heap/handle.h" +#include "third_party/blink/renderer/platform/heap/heap.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource_error.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource_status.h" +#include "third_party/blink/renderer/platform/weborigin/kurl.h" +#include "third_party/blink/renderer/platform/wtf/forward.h" +#include "third_party/blink/renderer/platform/wtf/optional.h" + +namespace blink { + +class ResourceError; +class ResourceFetcher; +class ResourceResponse; +class SecurityOrigin; + +// Delegate class of ImageResource that encapsulates the interface and data +// visible to ImageResourceContent. +// Do not add new members or new call sites unless really needed. +// TODO(hiroshige): reduce the members of this class to further decouple +// ImageResource and ImageResourceContent. +class CORE_EXPORT ImageResourceInfo : public GarbageCollectedMixin { + public: + ~ImageResourceInfo() = default; + virtual const KURL& Url() const = 0; + virtual bool IsSchedulingReload() const = 0; + virtual const ResourceResponse& GetResponse() const = 0; + virtual bool ShouldShowPlaceholder() const = 0; + virtual bool IsCacheValidator() const = 0; + virtual bool SchedulingReloadOrShouldReloadBrokenPlaceholder() const = 0; + enum DoesCurrentFrameHaveSingleSecurityOrigin { + kHasMultipleSecurityOrigin, + kHasSingleSecurityOrigin + }; + virtual bool IsAccessAllowed( + const SecurityOrigin*, + DoesCurrentFrameHaveSingleSecurityOrigin) const = 0; + virtual bool HasCacheControlNoStoreHeader() const = 0; + virtual Optional<ResourceError> GetResourceError() const = 0; + + // TODO(hiroshige): Remove this once MemoryCache becomes further weaker. + virtual void SetDecodedSize(size_t) = 0; + + // TODO(hiroshige): Remove these. + virtual void WillAddClientOrObserver() = 0; + virtual void DidRemoveClientOrObserver() = 0; + + // TODO(hiroshige): Remove this. crbug.com/666214 + virtual void EmulateLoadStartedForInspector( + ResourceFetcher*, + const KURL&, + const AtomicString& initiator_name) = 0; + + void Trace(blink::Visitor* visitor) override {} +}; + +} // namespace blink + +#endif diff --git a/chromium/third_party/blink/renderer/core/loader/resource/image_resource_observer.h b/chromium/third_party/blink/renderer/core/loader/resource/image_resource_observer.h new file mode 100644 index 00000000000..ea7ad59d5c7 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/resource/image_resource_observer.h @@ -0,0 +1,95 @@ +/* + Copyright (C) 1998 Lars Knoll (knoll@mpi-hd.mpg.de) + Copyright (C) 2001 Dirk Mueller <mueller@kde.org> + Copyright (C) 2006 Samuel Weinig (sam.weinig@gmail.com) + Copyright (C) 2004, 2005, 2006, 2007 Apple Inc. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_RESOURCE_IMAGE_RESOURCE_OBSERVER_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_RESOURCE_IMAGE_RESOURCE_OBSERVER_H_ + +#include "third_party/blink/renderer/core/core_export.h" +#include "third_party/blink/renderer/core/style/style_image.h" +#include "third_party/blink/renderer/platform/graphics/image_animation_policy.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource_priority.h" +#include "third_party/blink/renderer/platform/wtf/forward.h" + +namespace blink { + +class ImageResourceContent; +class IntRect; + +class CORE_EXPORT ImageResourceObserver { + public: + // Used to notify the observers whether the invalidation resulting from an + // image change notification can be deferred. In cases where the image is + // changing as a result of an animation, its performant to avoid continuous + // invalidations of offscreen content. + // Note that the observer can ignore kYes and perform an immediate + // invalidation, but kNo must be strictly enforced, i.e., if specified the + // invalidation can not be deferred. + enum class CanDeferInvalidation { kYes, kNo }; + + virtual ~ImageResourceObserver() = default; + + // 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(ImageResourceContent*, + CanDeferInvalidation, + const IntRect* = nullptr) {} + + // Sub-classes that have an associated image need to override this function + // to get notified of any image change. + virtual void ImageChanged(WrappedImagePtr, + CanDeferInvalidation, + const IntRect* = nullptr) {} + + // Called just after imageChanged() if all image data is received or errored. + // TODO(hiroshige): Merge imageNotifyFinished() into imageChanged(). + virtual void ImageNotifyFinished(ImageResourceContent*) {} + + // 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 LayoutImages would (assuming they + // have visibility: visible and their layout tree isn't hidden e.g., in the + // b/f cache or in a background tab). + // + // An implementation of this method is not allowed to add or remove + // ImageResource observers. + virtual bool WillRenderImage() { return false; } + + // Called to get imageAnimation policy from settings. An implementation of + // this method is not allowed to add or remove ImageResource observers. + virtual bool GetImageAnimationPolicy(ImageAnimationPolicy&) { return false; } + + // Return the observer's requested resource priority. An implementation of + // this method is not allowed to add or remove ImageResource observers. + virtual ResourcePriority ComputeResourcePriority() const { + return ResourcePriority(); + } + + // Name for debugging, e.g. shown in memory-infra. + virtual String DebugName() const = 0; + + static bool IsExpectedType(ImageResourceObserver*) { return true; } +}; + +} // namespace blink + +#endif diff --git a/chromium/third_party/blink/renderer/core/loader/resource/image_resource_test.cc b/chromium/third_party/blink/renderer/core/loader/resource/image_resource_test.cc new file mode 100644 index 00000000000..5cdb96946ac --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/resource/image_resource_test.cc @@ -0,0 +1,2016 @@ +/* + * Copyright (c) 2013, Google 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: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "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 THE COPYRIGHT + * OWNER 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 "third_party/blink/renderer/core/loader/resource/image_resource.h" + +#include <memory> +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/blink/public/platform/modules/fetch/fetch_api_request.mojom-shared.h" +#include "third_party/blink/public/platform/platform.h" +#include "third_party/blink/public/platform/web_url.h" +#include "third_party/blink/public/platform/web_url_loader_mock_factory.h" +#include "third_party/blink/public/platform/web_url_response.h" +#include "third_party/blink/renderer/core/loader/empty_clients.h" +#include "third_party/blink/renderer/core/loader/resource/mock_image_resource_observer.h" +#include "third_party/blink/renderer/core/testing/dummy_page_holder.h" +#include "third_party/blink/renderer/platform/exported/wrapped_resource_response.h" +#include "third_party/blink/renderer/platform/graphics/bitmap_image.h" +#include "third_party/blink/renderer/platform/graphics/image.h" +#include "third_party/blink/renderer/platform/instance_counters.h" +#include "third_party/blink/renderer/platform/loader/fetch/fetch_initiator_info.h" +#include "third_party/blink/renderer/platform/loader/fetch/fetch_initiator_type_names.h" +#include "third_party/blink/renderer/platform/loader/fetch/fetch_parameters.h" +#include "third_party/blink/renderer/platform/loader/fetch/memory_cache.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource_finish_observer.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource_loader.h" +#include "third_party/blink/renderer/platform/loader/fetch/unique_identifier.h" +#include "third_party/blink/renderer/platform/loader/testing/mock_fetch_context.h" +#include "third_party/blink/renderer/platform/loader/testing/mock_resource_client.h" +#include "third_party/blink/renderer/platform/network/http_names.h" +#include "third_party/blink/renderer/platform/scheduler/test/fake_task_runner.h" +#include "third_party/blink/renderer/platform/shared_buffer.h" +#include "third_party/blink/renderer/platform/testing/runtime_enabled_features_test_helpers.h" +#include "third_party/blink/renderer/platform/testing/scoped_mocked_url.h" +#include "third_party/blink/renderer/platform/testing/testing_platform_support_with_mock_scheduler.h" +#include "third_party/blink/renderer/platform/testing/unit_test_helpers.h" +#include "third_party/blink/renderer/platform/wtf/text/base64.h" + +namespace blink { + +using test::ScopedMockedURLLoad; + +namespace { + +// An image of size 1x1. +constexpr unsigned char kJpegImage[] = { + 0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46, 0x49, 0x46, 0x00, 0x01, + 0x01, 0x01, 0x00, 0x48, 0x00, 0x48, 0x00, 0x00, 0xff, 0xfe, 0x00, 0x13, + 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x20, 0x77, 0x69, 0x74, 0x68, + 0x20, 0x47, 0x49, 0x4d, 0x50, 0xff, 0xdb, 0x00, 0x43, 0x00, 0x05, 0x03, + 0x04, 0x04, 0x04, 0x03, 0x05, 0x04, 0x04, 0x04, 0x05, 0x05, 0x05, 0x06, + 0x07, 0x0c, 0x08, 0x07, 0x07, 0x07, 0x07, 0x0f, 0x0b, 0x0b, 0x09, 0x0c, + 0x11, 0x0f, 0x12, 0x12, 0x11, 0x0f, 0x11, 0x11, 0x13, 0x16, 0x1c, 0x17, + 0x13, 0x14, 0x1a, 0x15, 0x11, 0x11, 0x18, 0x21, 0x18, 0x1a, 0x1d, 0x1d, + 0x1f, 0x1f, 0x1f, 0x13, 0x17, 0x22, 0x24, 0x22, 0x1e, 0x24, 0x1c, 0x1e, + 0x1f, 0x1e, 0xff, 0xdb, 0x00, 0x43, 0x01, 0x05, 0x05, 0x05, 0x07, 0x06, + 0x07, 0x0e, 0x08, 0x08, 0x0e, 0x1e, 0x14, 0x11, 0x14, 0x1e, 0x1e, 0x1e, + 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, + 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, + 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, + 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0xff, + 0xc0, 0x00, 0x11, 0x08, 0x00, 0x01, 0x00, 0x01, 0x03, 0x01, 0x22, 0x00, + 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xff, 0xc4, 0x00, 0x15, 0x00, 0x01, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x08, 0xff, 0xc4, 0x00, 0x14, 0x10, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xff, 0xc4, 0x00, 0x14, 0x01, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xff, 0xc4, 0x00, 0x14, 0x11, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, + 0xda, 0x00, 0x0c, 0x03, 0x01, 0x00, 0x02, 0x11, 0x03, 0x11, 0x00, 0x3f, + 0x00, 0xb2, 0xc0, 0x07, 0xff, 0xd9}; + +constexpr int kJpegImageWidth = 1; +constexpr int kJpegImageHeight = 1; + +constexpr size_t kJpegImageSubrangeWithDimensionsLength = + sizeof(kJpegImage) - 1; +constexpr size_t kJpegImageSubrangeWithoutDimensionsLength = 3; + +// Ensure that the image decoder can determine the dimensions of kJpegImage from +// just the first kJpegImageSubrangeWithDimensionsLength bytes. If this test +// fails, then the test data here probably needs to be updated. +TEST(ImageResourceTest, DimensionsDecodableFromPartialTestImage) { + scoped_refptr<Image> image = BitmapImage::Create(); + EXPECT_EQ( + Image::kSizeAvailable, + image->SetData(SharedBuffer::Create( + kJpegImage, kJpegImageSubrangeWithDimensionsLength), + true)); + EXPECT_TRUE(image->IsBitmapImage()); + EXPECT_EQ(1, image->width()); + EXPECT_EQ(1, image->height()); +} + +// An image of size 50x50. +constexpr unsigned char kJpegImage2[] = { + 0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46, 0x49, 0x46, 0x00, 0x01, + 0x01, 0x01, 0x00, 0x48, 0x00, 0x48, 0x00, 0x00, 0xff, 0xdb, 0x00, 0x43, + 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xdb, 0x00, 0x43, 0x01, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xc0, 0x00, 0x11, 0x08, 0x00, 0x32, 0x00, 0x32, 0x03, + 0x01, 0x22, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xff, 0xc4, 0x00, + 0x14, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xc4, 0x00, 0x14, 0x10, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xc4, 0x00, 0x15, 0x01, 0x01, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x02, 0xff, 0xc4, 0x00, 0x14, 0x11, 0x01, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xff, 0xda, 0x00, 0x0c, 0x03, 0x01, 0x00, 0x02, 0x11, 0x03, + 0x11, 0x00, 0x3f, 0x00, 0x00, 0x94, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xd9}; + +constexpr char kSvgImage[] = + "<svg width=\"200\" height=\"200\" xmlns=\"http://www.w3.org/2000/svg\" " + "xmlns:xlink=\"http://www.w3.org/1999/xlink\">" + "<rect x=\"0\" y=\"0\" width=\"100px\" height=\"100px\" fill=\"red\"/>" + "</svg>"; + +constexpr char kSvgImage2[] = + "<svg width=\"300\" height=\"300\" xmlns=\"http://www.w3.org/2000/svg\" " + "xmlns:xlink=\"http://www.w3.org/1999/xlink\">" + "<rect x=\"0\" y=\"0\" width=\"200px\" height=\"200px\" fill=\"green\"/>" + "</svg>"; + +constexpr char kTestURL[] = "http://www.test.com/cancelTest.html"; + +String GetTestFilePath() { + return test::CoreTestDataPath("cancelTest.html"); +} + +constexpr char kSvgImageWithSubresource[] = + "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"198\" height=\"100\">" + "<style>" + " <![CDATA[@font-face{font-family:\"test\"; " + " src:url('data:font/ttf;base64,invalidFontData');}]]>" + "</style>" + "<text x=\"50\" y=\"50\" font-family=\"test\" font-size=\"16\">Fox</text>" + "</svg>"; + +void ReceiveResponse(ImageResource* image_resource, + const KURL& url, + const AtomicString& mime_type, + const char* data, + size_t data_size) { + ResourceResponse response(url, mime_type); + response.SetHTTPStatusCode(200); + image_resource->SetStatus(ResourceStatus::kPending); + image_resource->NotifyStartLoad(); + image_resource->ResponseReceived(response, nullptr); + image_resource->AppendData(data, data_size); + image_resource->FinishForTest(); +} + +void TestThatReloadIsStartedThenServeReload( + const KURL& test_url, + ImageResource* image_resource, + ImageResourceContent* content, + MockImageResourceObserver* observer, + mojom::FetchCacheMode cache_mode_for_reload, + bool placeholder_before_reload) { + const char* data = reinterpret_cast<const char*>(kJpegImage2); + constexpr size_t kDataLength = sizeof(kJpegImage2); + constexpr int kImageWidth = 50; + constexpr int kImageHeight = 50; + + // Checks that |imageResource| and |content| are ready for non-placeholder + // reloading. + EXPECT_EQ(ResourceStatus::kPending, image_resource->GetStatus()); + EXPECT_FALSE(image_resource->ResourceBuffer()); + EXPECT_EQ(placeholder_before_reload, image_resource->ShouldShowPlaceholder()); + EXPECT_EQ(g_null_atom, + image_resource->GetResourceRequest().HttpHeaderField("range")); + EXPECT_EQ(cache_mode_for_reload, + image_resource->GetResourceRequest().GetCacheMode()); + EXPECT_EQ(content, image_resource->GetContent()); + EXPECT_FALSE(content->HasImage()); + + // Checks |observer| before reloading. + const int original_image_changed_count = observer->ImageChangedCount(); + const bool already_notified_finish = observer->ImageNotifyFinishedCalled(); + const int image_width_on_image_notify_finished = + observer->ImageWidthOnImageNotifyFinished(); + ASSERT_NE(kImageWidth, image_width_on_image_notify_finished); + + // Does Reload. + image_resource->Loader()->DidReceiveResponse(WrappedResourceResponse( + ResourceResponse(test_url, "image/jpeg", kDataLength))); + image_resource->Loader()->DidReceiveData(data, kDataLength); + image_resource->Loader()->DidFinishLoading(0.0, kDataLength, kDataLength, + kDataLength, false); + + // Checks |imageResource|'s status after reloading. + EXPECT_EQ(ResourceStatus::kCached, image_resource->GetStatus()); + EXPECT_FALSE(image_resource->ErrorOccurred()); + EXPECT_EQ(kDataLength, image_resource->EncodedSize()); + + // Checks |observer| after reloading that it is notified of updates/finish. + EXPECT_LT(original_image_changed_count, observer->ImageChangedCount()); + EXPECT_EQ(kImageWidth, observer->ImageWidthOnLastImageChanged()); + EXPECT_TRUE(observer->ImageNotifyFinishedCalled()); + if (!already_notified_finish) { + // If imageNotifyFinished() has not been called before the reloaded + // response is served, then imageNotifyFinished() should be called with + // the new image (of width |imageWidth|). + EXPECT_EQ(kImageWidth, observer->ImageWidthOnImageNotifyFinished()); + } + + // Checks |content| receives the correct image. + EXPECT_TRUE(content->HasImage()); + EXPECT_FALSE(content->GetImage()->IsNull()); + EXPECT_EQ(kImageWidth, content->GetImage()->width()); + EXPECT_EQ(kImageHeight, content->GetImage()->height()); + EXPECT_FALSE(content->GetImage()->PaintImageForCurrentFrame().is_multipart()); +} + +AtomicString BuildContentRange(size_t range_length, size_t total_length) { + return AtomicString(String("bytes 0-" + String::Number(range_length - 1) + + "/" + String::Number(total_length))); +} + +void TestThatIsPlaceholderRequestAndServeResponse( + const KURL& url, + ImageResource* image_resource, + MockImageResourceObserver* observer) { + // Checks that |imageResource| is requesting for placeholder. + EXPECT_TRUE(image_resource->ShouldShowPlaceholder()); + EXPECT_EQ("bytes=0-2047", + image_resource->GetResourceRequest().HttpHeaderField("range")); + EXPECT_EQ(0, observer->ImageChangedCount()); + + // Serves partial response that is sufficient for creating a placeholder. + ResourceResponse response(url, "image/jpeg", + kJpegImageSubrangeWithDimensionsLength); + response.SetHTTPStatusCode(206); + response.SetHTTPHeaderField( + "content-range", BuildContentRange(kJpegImageSubrangeWithDimensionsLength, + sizeof(kJpegImage))); + image_resource->Loader()->DidReceiveResponse( + WrappedResourceResponse(response)); + image_resource->Loader()->DidReceiveData( + reinterpret_cast<const char*>(kJpegImage), + kJpegImageSubrangeWithDimensionsLength); + image_resource->Loader()->DidFinishLoading( + 0.0, kJpegImageSubrangeWithDimensionsLength, + kJpegImageSubrangeWithDimensionsLength, + kJpegImageSubrangeWithDimensionsLength, false); + + // Checks that |imageResource| is successfully loaded, showing a placeholder. + EXPECT_EQ(ResourceStatus::kCached, image_resource->GetStatus()); + EXPECT_EQ(kJpegImageSubrangeWithDimensionsLength, + image_resource->EncodedSize()); + + EXPECT_LT(0, observer->ImageChangedCount()); + EXPECT_EQ(kJpegImageWidth, observer->ImageWidthOnLastImageChanged()); + EXPECT_TRUE(observer->ImageNotifyFinishedCalled()); + EXPECT_EQ(kJpegImageWidth, observer->ImageWidthOnImageNotifyFinished()); + ASSERT_TRUE(image_resource->GetContent()->HasImage()); + EXPECT_EQ(kJpegImageWidth, image_resource->GetContent()->GetImage()->width()); + EXPECT_EQ(kJpegImageHeight, + image_resource->GetContent()->GetImage()->height()); + + // A placeholder image. + EXPECT_TRUE(image_resource->ShouldShowPlaceholder()); + EXPECT_FALSE(image_resource->GetContent()->GetImage()->IsBitmapImage()); + EXPECT_FALSE(image_resource->GetContent()->GetImage()->IsSVGImage()); +} + +void TestThatIsNotPlaceholderRequestAndServeResponse( + const KURL& url, + ImageResource* image_resource, + MockImageResourceObserver* observer) { + // Checks that |imageResource| is NOT requesting for placeholder. + EXPECT_FALSE(image_resource->ShouldShowPlaceholder()); + EXPECT_EQ(g_null_atom, + image_resource->GetResourceRequest().HttpHeaderField("range")); + EXPECT_EQ(0, observer->ImageChangedCount()); + + // Serves full response. + image_resource->Loader()->DidReceiveResponse(WrappedResourceResponse( + ResourceResponse(url, "image/jpeg", sizeof(kJpegImage)))); + image_resource->Loader()->DidReceiveData( + reinterpret_cast<const char*>(kJpegImage), sizeof(kJpegImage)); + image_resource->Loader()->DidFinishLoading( + 0.0, sizeof(kJpegImage), sizeof(kJpegImage), sizeof(kJpegImage), false); + + // Checks that |imageResource| is successfully loaded, + // showing a non-placeholder image. + EXPECT_EQ(ResourceStatus::kCached, image_resource->GetStatus()); + EXPECT_EQ(sizeof(kJpegImage), image_resource->EncodedSize()); + + EXPECT_LT(0, observer->ImageChangedCount()); + EXPECT_EQ(kJpegImageWidth, observer->ImageWidthOnLastImageChanged()); + EXPECT_TRUE(observer->ImageNotifyFinishedCalled()); + EXPECT_EQ(kJpegImageWidth, observer->ImageWidthOnImageNotifyFinished()); + ASSERT_TRUE(image_resource->GetContent()->HasImage()); + EXPECT_EQ(kJpegImageWidth, image_resource->GetContent()->GetImage()->width()); + EXPECT_EQ(kJpegImageHeight, + image_resource->GetContent()->GetImage()->height()); + + // A non-placeholder bitmap image. + EXPECT_FALSE(image_resource->ShouldShowPlaceholder()); + EXPECT_TRUE(image_resource->GetContent()->GetImage()->IsBitmapImage()); + EXPECT_FALSE(image_resource->GetContent()->GetImage()->IsSVGImage()); +} + +ResourceFetcher* CreateFetcher() { + return ResourceFetcher::Create( + MockFetchContext::Create(MockFetchContext::kShouldLoadNewResource)); +} + +TEST(ImageResourceTest, MultipartImage) { + ResourceFetcher* fetcher = CreateFetcher(); + KURL test_url(kTestURL); + ScopedMockedURLLoad scoped_mocked_url_load(test_url, GetTestFilePath()); + + // Emulate starting a real load, but don't expect any "real" + // WebURLLoaderClient callbacks. + ImageResource* image_resource = ImageResource::CreateForTest(test_url); + image_resource->SetIdentifier(CreateUniqueIdentifier()); + fetcher->StartLoad(image_resource); + + std::unique_ptr<MockImageResourceObserver> observer = + MockImageResourceObserver::Create(image_resource->GetContent()); + EXPECT_EQ(ResourceStatus::kPending, image_resource->GetStatus()); + + // Send the multipart response. No image or data buffer is created. Note that + // the response must be routed through ResourceLoader to ensure the load is + // flagged as multipart. + ResourceResponse multipart_response(NullURL(), "multipart/x-mixed-replace"); + multipart_response.SetMultipartBoundary("boundary", strlen("boundary")); + image_resource->Loader()->DidReceiveResponse( + WrappedResourceResponse(multipart_response), nullptr); + EXPECT_FALSE(image_resource->ResourceBuffer()); + EXPECT_FALSE(image_resource->GetContent()->HasImage()); + EXPECT_EQ(0, observer->ImageChangedCount()); + EXPECT_FALSE(observer->ImageNotifyFinishedCalled()); + EXPECT_EQ("multipart/x-mixed-replace", + image_resource->GetResponse().MimeType()); + + const char kFirstPart[] = + "--boundary\n" + "Content-Type: image/svg+xml\n\n"; + image_resource->AppendData(kFirstPart, strlen(kFirstPart)); + // Send the response for the first real part. No image or data buffer is + // created. + EXPECT_FALSE(image_resource->ResourceBuffer()); + EXPECT_FALSE(image_resource->GetContent()->HasImage()); + EXPECT_EQ(0, observer->ImageChangedCount()); + EXPECT_FALSE(observer->ImageNotifyFinishedCalled()); + EXPECT_EQ("image/svg+xml", image_resource->GetResponse().MimeType()); + + const char kSecondPart[] = + "<svg xmlns='http://www.w3.org/2000/svg' width='1' height='1'><rect " + "width='1' height='1' fill='green'/></svg>\n"; + // The first bytes arrive. The data buffer is created, but no image is + // created. + image_resource->AppendData(kSecondPart, strlen(kSecondPart)); + EXPECT_TRUE(image_resource->ResourceBuffer()); + EXPECT_FALSE(image_resource->GetContent()->HasImage()); + EXPECT_EQ(0, observer->ImageChangedCount()); + EXPECT_FALSE(observer->ImageNotifyFinishedCalled()); + + // Add an observer to check an assertion error doesn't happen + // (crbug.com/630983). + std::unique_ptr<MockImageResourceObserver> observer2 = + MockImageResourceObserver::Create(image_resource->GetContent()); + EXPECT_EQ(0, observer2->ImageChangedCount()); + EXPECT_FALSE(observer2->ImageNotifyFinishedCalled()); + + const char kThirdPart[] = "--boundary"; + image_resource->AppendData(kThirdPart, strlen(kThirdPart)); + ASSERT_TRUE(image_resource->ResourceBuffer()); + EXPECT_EQ(strlen(kSecondPart) - 1, image_resource->ResourceBuffer()->size()); + + // This part finishes. The image is created, callbacks are sent, and the data + // buffer is cleared. + image_resource->Loader()->DidFinishLoading(0.0, 0, 0, 0, false); + EXPECT_TRUE(image_resource->ResourceBuffer()); + EXPECT_FALSE(image_resource->ErrorOccurred()); + ASSERT_TRUE(image_resource->GetContent()->HasImage()); + EXPECT_FALSE(image_resource->GetContent()->GetImage()->IsNull()); + EXPECT_EQ(kJpegImageWidth, image_resource->GetContent()->GetImage()->width()); + EXPECT_EQ(kJpegImageHeight, + image_resource->GetContent()->GetImage()->height()); + EXPECT_TRUE(image_resource->GetContent()->GetImage()->IsSVGImage()); + EXPECT_TRUE(image_resource->GetContent() + ->GetImage() + ->PaintImageForCurrentFrame() + .is_multipart()); + + EXPECT_EQ(1, observer->ImageChangedCount()); + EXPECT_TRUE(observer->ImageNotifyFinishedCalled()); + EXPECT_EQ(1, observer2->ImageChangedCount()); + EXPECT_TRUE(observer2->ImageNotifyFinishedCalled()); +} + +TEST(ImageResourceTest, BitmapMultipartImage) { + ResourceFetcher* fetcher = CreateFetcher(); + KURL test_url(kTestURL); + ScopedMockedURLLoad scoped_mocked_url_load(test_url, GetTestFilePath()); + ImageResource* image_resource = + ImageResource::Create(ResourceRequest(test_url)); + image_resource->SetIdentifier(CreateUniqueIdentifier()); + fetcher->StartLoad(image_resource); + + ResourceResponse multipart_response(NullURL(), "multipart/x-mixed-replace"); + multipart_response.SetMultipartBoundary("boundary", strlen("boundary")); + image_resource->Loader()->DidReceiveResponse( + WrappedResourceResponse(multipart_response), nullptr); + EXPECT_FALSE(image_resource->GetContent()->HasImage()); + + const char kBoundary[] = "--boundary\n"; + const char kContentType[] = "Content-Type: image/jpeg\n\n"; + image_resource->AppendData(kBoundary, strlen(kBoundary)); + image_resource->AppendData(kContentType, strlen(kContentType)); + image_resource->AppendData(reinterpret_cast<const char*>(kJpegImage), + sizeof(kJpegImage)); + image_resource->AppendData(kBoundary, strlen(kBoundary)); + image_resource->Loader()->DidFinishLoading(0.0, 0, 0, 0, false); + EXPECT_TRUE(image_resource->GetContent()->HasImage()); + EXPECT_TRUE(image_resource->GetContent()->GetImage()->IsBitmapImage()); + EXPECT_TRUE(image_resource->GetContent() + ->GetImage() + ->PaintImageForCurrentFrame() + .is_multipart()); +} + +TEST(ImageResourceTest, CancelOnRemoveObserver) { + KURL test_url(kTestURL); + ScopedMockedURLLoad scoped_mocked_url_load(test_url, GetTestFilePath()); + + ResourceFetcher* fetcher = CreateFetcher(); + scheduler::FakeTaskRunner* task_runner = + static_cast<scheduler::FakeTaskRunner*>( + fetcher->Context().GetLoadingTaskRunner().get()); + task_runner->SetTime(1); + + // Emulate starting a real load. + ImageResource* image_resource = ImageResource::CreateForTest(test_url); + image_resource->SetIdentifier(CreateUniqueIdentifier()); + + fetcher->StartLoad(image_resource); + GetMemoryCache()->Add(image_resource); + + std::unique_ptr<MockImageResourceObserver> observer = + MockImageResourceObserver::Create(image_resource->GetContent()); + EXPECT_EQ(ResourceStatus::kPending, image_resource->GetStatus()); + + // The load should still be alive, but a timer should be started to cancel the + // load inside removeClient(). + observer->RemoveAsObserver(); + EXPECT_EQ(ResourceStatus::kPending, image_resource->GetStatus()); + EXPECT_TRUE(GetMemoryCache()->ResourceForURL(test_url)); + + // Trigger the cancel timer, ensure the load was cancelled and the resource + // was evicted from the cache. + task_runner->RunUntilIdle(); + EXPECT_EQ(ResourceStatus::kLoadError, image_resource->GetStatus()); + EXPECT_FALSE(GetMemoryCache()->ResourceForURL(test_url)); +} + +class MockFinishObserver : public GarbageCollectedFinalized<MockFinishObserver>, + public ResourceFinishObserver { + USING_GARBAGE_COLLECTED_MIXIN(MockFinishObserver); + + public: + static MockFinishObserver* Create() { + return + + new testing::StrictMock<MockFinishObserver>; + } + MOCK_METHOD0(NotifyFinished, void()); + String DebugName() const override { return "MockFinishObserver"; } + + virtual void Trace(blink::Visitor* visitor) { + blink::ResourceFinishObserver::Trace(visitor); + } + + protected: + MockFinishObserver() = default; +}; + +TEST(ImageResourceTest, CancelWithImageAndFinishObserver) { + KURL test_url(kTestURL); + ScopedMockedURLLoad scoped_mocked_url_load(test_url, GetTestFilePath()); + + ResourceFetcher* fetcher = CreateFetcher(); + + // Emulate starting a real load. + ImageResource* image_resource = ImageResource::CreateForTest(test_url); + image_resource->SetIdentifier(CreateUniqueIdentifier()); + + fetcher->StartLoad(image_resource); + GetMemoryCache()->Add(image_resource); + + Persistent<MockFinishObserver> finish_observer = MockFinishObserver::Create(); + image_resource->AddFinishObserver( + finish_observer, fetcher->Context().GetLoadingTaskRunner().get()); + + // Send the image response. + image_resource->ResponseReceived( + ResourceResponse(NullURL(), "image/jpeg", sizeof(kJpegImage)), nullptr); + image_resource->AppendData(reinterpret_cast<const char*>(kJpegImage), + sizeof(kJpegImage)); + ASSERT_TRUE(image_resource->GetContent()->HasImage()); + EXPECT_EQ(ResourceStatus::kPending, image_resource->GetStatus()); + + // This shouldn't crash. crbug.com/701723 + image_resource->Loader()->Cancel(); + + EXPECT_EQ(ResourceStatus::kLoadError, image_resource->GetStatus()); + EXPECT_FALSE(GetMemoryCache()->ResourceForURL(test_url)); + + // ResourceFinishObserver is notified asynchronously. + EXPECT_CALL(*finish_observer, NotifyFinished()); + blink::test::RunPendingTasks(); +} + +TEST(ImageResourceTest, DecodedDataRemainsWhileHasClients) { + ImageResource* image_resource = ImageResource::CreateForTest(NullURL()); + image_resource->SetStatus(ResourceStatus::kPending); + image_resource->NotifyStartLoad(); + + std::unique_ptr<MockImageResourceObserver> observer = + MockImageResourceObserver::Create(image_resource->GetContent()); + + // Send the image response. + image_resource->ResponseReceived( + ResourceResponse(NullURL(), "multipart/x-mixed-replace"), nullptr); + + image_resource->ResponseReceived( + ResourceResponse(NullURL(), "image/jpeg", sizeof(kJpegImage)), nullptr); + image_resource->AppendData(reinterpret_cast<const char*>(kJpegImage), + sizeof(kJpegImage)); + EXPECT_NE(0u, image_resource->EncodedSizeMemoryUsageForTesting()); + image_resource->FinishForTest(); + EXPECT_EQ(0u, image_resource->EncodedSizeMemoryUsageForTesting()); + EXPECT_FALSE(image_resource->ErrorOccurred()); + ASSERT_TRUE(image_resource->GetContent()->HasImage()); + EXPECT_FALSE(image_resource->GetContent()->GetImage()->IsNull()); + EXPECT_TRUE(observer->ImageNotifyFinishedCalled()); + + // The prune comes when the ImageResource still has observers. The image + // should not be deleted. + image_resource->Prune(); + EXPECT_TRUE(image_resource->IsAlive()); + ASSERT_TRUE(image_resource->GetContent()->HasImage()); + EXPECT_FALSE(image_resource->GetContent()->GetImage()->IsNull()); + + // The ImageResource no longer has observers. The decoded image data should be + // deleted by prune. + observer->RemoveAsObserver(); + image_resource->Prune(); + EXPECT_FALSE(image_resource->IsAlive()); + EXPECT_TRUE(image_resource->GetContent()->HasImage()); + // TODO(hajimehoshi): Should check imageResource doesn't have decoded image + // data. +} + +TEST(ImageResourceTest, UpdateBitmapImages) { + ImageResource* image_resource = ImageResource::CreateForTest(NullURL()); + image_resource->SetStatus(ResourceStatus::kPending); + image_resource->NotifyStartLoad(); + + std::unique_ptr<MockImageResourceObserver> observer = + MockImageResourceObserver::Create(image_resource->GetContent()); + + // Send the image response. + image_resource->ResponseReceived( + ResourceResponse(NullURL(), "image/jpeg", sizeof(kJpegImage)), nullptr); + image_resource->AppendData(reinterpret_cast<const char*>(kJpegImage), + sizeof(kJpegImage)); + image_resource->FinishForTest(); + EXPECT_FALSE(image_resource->ErrorOccurred()); + ASSERT_TRUE(image_resource->GetContent()->HasImage()); + EXPECT_FALSE(image_resource->GetContent()->GetImage()->IsNull()); + EXPECT_EQ(2, observer->ImageChangedCount()); + EXPECT_TRUE(observer->ImageNotifyFinishedCalled()); + EXPECT_TRUE(image_resource->GetContent()->GetImage()->IsBitmapImage()); +} + +class ImageResourceReloadTest + : public testing::TestWithParam<bool>, + private ScopedClientPlaceholdersForServerLoFiForTest { + public: + ImageResourceReloadTest() + : ScopedClientPlaceholdersForServerLoFiForTest(GetParam()) {} + ~ImageResourceReloadTest() override = default; + + bool IsClientPlaceholderForServerLoFiEnabled() const { return GetParam(); } + + void SetUp() override { + } +}; + +TEST_P(ImageResourceReloadTest, ReloadIfLoFiOrPlaceholderAfterFinished) { + KURL test_url(kTestURL); + ScopedMockedURLLoad scoped_mocked_url_load(test_url, GetTestFilePath()); + ImageResource* image_resource = ImageResource::CreateForTest(test_url); + image_resource->SetStatus(ResourceStatus::kPending); + image_resource->NotifyStartLoad(); + + std::unique_ptr<MockImageResourceObserver> observer = + MockImageResourceObserver::Create(image_resource->GetContent()); + ResourceFetcher* fetcher = CreateFetcher(); + + // Send the image response. + ResourceResponse resource_response(NullURL(), "image/jpeg", + sizeof(kJpegImage)); + resource_response.AddHTTPHeaderField("chrome-proxy-content-transform", + "empty-image"); + + image_resource->ResponseReceived(resource_response, nullptr); + image_resource->AppendData(reinterpret_cast<const char*>(kJpegImage), + sizeof(kJpegImage)); + image_resource->FinishForTest(); + EXPECT_FALSE(image_resource->ErrorOccurred()); + ASSERT_TRUE(image_resource->GetContent()->HasImage()); + EXPECT_FALSE(image_resource->GetContent()->GetImage()->IsNull()); + EXPECT_EQ(2, observer->ImageChangedCount()); + EXPECT_EQ(kJpegImageWidth, observer->ImageWidthOnLastImageChanged()); + // The observer should have been notified that the image load completed. + EXPECT_TRUE(observer->ImageNotifyFinishedCalled()); + EXPECT_EQ(kJpegImageWidth, observer->ImageWidthOnImageNotifyFinished()); + EXPECT_NE(IsClientPlaceholderForServerLoFiEnabled(), + image_resource->GetContent()->GetImage()->IsBitmapImage()); + EXPECT_EQ(IsClientPlaceholderForServerLoFiEnabled(), + image_resource->ShouldShowPlaceholder()); + EXPECT_EQ(kJpegImageWidth, image_resource->GetContent()->GetImage()->width()); + EXPECT_EQ(kJpegImageHeight, + image_resource->GetContent()->GetImage()->height()); + + // Call reloadIfLoFiOrPlaceholderImage() after the image has finished loading. + image_resource->ReloadIfLoFiOrPlaceholderImage(fetcher, + Resource::kReloadAlways); + + EXPECT_EQ(3, observer->ImageChangedCount()); + TestThatReloadIsStartedThenServeReload( + test_url, image_resource, image_resource->GetContent(), observer.get(), + mojom::FetchCacheMode::kBypassCache, false); +} + +TEST_P(ImageResourceReloadTest, + ReloadIfLoFiOrPlaceholderAfterFinishedWithOldHeaders) { + KURL test_url(kTestURL); + ScopedMockedURLLoad scoped_mocked_url_load(test_url, GetTestFilePath()); + ImageResource* image_resource = ImageResource::CreateForTest(test_url); + image_resource->SetStatus(ResourceStatus::kPending); + image_resource->NotifyStartLoad(); + + std::unique_ptr<MockImageResourceObserver> observer = + MockImageResourceObserver::Create(image_resource->GetContent()); + ResourceFetcher* fetcher = CreateFetcher(); + + // Send the image response. + ResourceResponse resource_response(NullURL(), "image/jpeg", + sizeof(kJpegImage)); + resource_response.AddHTTPHeaderField("chrome-proxy", "q=low"); + + image_resource->ResponseReceived(resource_response, nullptr); + image_resource->AppendData(reinterpret_cast<const char*>(kJpegImage), + sizeof(kJpegImage)); + image_resource->FinishForTest(); + EXPECT_FALSE(image_resource->ErrorOccurred()); + ASSERT_TRUE(image_resource->GetContent()->HasImage()); + EXPECT_FALSE(image_resource->GetContent()->GetImage()->IsNull()); + EXPECT_EQ(2, observer->ImageChangedCount()); + EXPECT_EQ(kJpegImageWidth, observer->ImageWidthOnLastImageChanged()); + // The observer should have been notified that the image load completed. + EXPECT_TRUE(observer->ImageNotifyFinishedCalled()); + EXPECT_EQ(kJpegImageWidth, observer->ImageWidthOnImageNotifyFinished()); + EXPECT_NE(IsClientPlaceholderForServerLoFiEnabled(), + image_resource->GetContent()->GetImage()->IsBitmapImage()); + EXPECT_EQ(IsClientPlaceholderForServerLoFiEnabled(), + image_resource->ShouldShowPlaceholder()); + EXPECT_EQ(kJpegImageWidth, image_resource->GetContent()->GetImage()->width()); + EXPECT_EQ(kJpegImageHeight, + image_resource->GetContent()->GetImage()->height()); + + // Call reloadIfLoFiOrPlaceholderImage() after the image has finished loading. + image_resource->ReloadIfLoFiOrPlaceholderImage(fetcher, + Resource::kReloadAlways); + + EXPECT_EQ(3, observer->ImageChangedCount()); + TestThatReloadIsStartedThenServeReload( + test_url, image_resource, image_resource->GetContent(), observer.get(), + mojom::FetchCacheMode::kBypassCache, false); +} + +TEST_P(ImageResourceReloadTest, + ReloadIfLoFiOrPlaceholderAfterFinishedWithoutLoFiHeaders) { + KURL test_url(kTestURL); + ScopedMockedURLLoad scoped_mocked_url_load(test_url, GetTestFilePath()); + ResourceRequest request(test_url); + request.SetPreviewsState(WebURLRequest::kServerLoFiOn); + request.SetFetchCredentialsMode(network::mojom::FetchCredentialsMode::kOmit); + ImageResource* image_resource = ImageResource::Create(request); + image_resource->SetStatus(ResourceStatus::kPending); + image_resource->NotifyStartLoad(); + + std::unique_ptr<MockImageResourceObserver> observer = + MockImageResourceObserver::Create(image_resource->GetContent()); + ResourceFetcher* fetcher = CreateFetcher(); + + // Send the image response, without any LoFi image response headers. + image_resource->ResponseReceived( + ResourceResponse(NullURL(), "image/jpeg", sizeof(kJpegImage)), nullptr); + image_resource->AppendData(reinterpret_cast<const char*>(kJpegImage), + sizeof(kJpegImage)); + image_resource->FinishForTest(); + EXPECT_FALSE(image_resource->ErrorOccurred()); + ASSERT_TRUE(image_resource->GetContent()->HasImage()); + EXPECT_FALSE(image_resource->GetContent()->GetImage()->IsNull()); + EXPECT_EQ(2, observer->ImageChangedCount()); + EXPECT_EQ(kJpegImageWidth, observer->ImageWidthOnLastImageChanged()); + // The observer should have been notified that the image load completed. + EXPECT_TRUE(observer->ImageNotifyFinishedCalled()); + EXPECT_EQ(kJpegImageWidth, observer->ImageWidthOnImageNotifyFinished()); + EXPECT_TRUE(image_resource->GetContent()->GetImage()->IsBitmapImage()); + EXPECT_EQ(kJpegImageWidth, image_resource->GetContent()->GetImage()->width()); + EXPECT_EQ(kJpegImageHeight, + image_resource->GetContent()->GetImage()->height()); + + // Call reloadIfLoFiOrPlaceholderImage() after the image has finished loading. + image_resource->ReloadIfLoFiOrPlaceholderImage(fetcher, + Resource::kReloadAlways); + + // The image should not have been reloaded, since it didn't have the LoFi + // image response headers. + EXPECT_EQ(2, observer->ImageChangedCount()); + EXPECT_TRUE(image_resource->IsLoaded()); +} + +TEST_P(ImageResourceReloadTest, ReloadIfLoFiOrPlaceholderViaResourceFetcher) { + ResourceFetcher* fetcher = CreateFetcher(); + + KURL test_url(kTestURL); + ScopedMockedURLLoad scoped_mocked_url_load(test_url, GetTestFilePath()); + + ResourceRequest request = ResourceRequest(test_url); + request.SetPreviewsState(WebURLRequest::kServerLoFiOn); + FetchParameters fetch_params(request); + ImageResource* image_resource = ImageResource::Fetch(fetch_params, fetcher); + ImageResourceContent* content = image_resource->GetContent(); + + std::unique_ptr<MockImageResourceObserver> observer = + MockImageResourceObserver::Create(content); + + // Send the image response. + ResourceResponse resource_response(NullURL(), "image/jpeg", + sizeof(kJpegImage)); + resource_response.AddHTTPHeaderField("chrome-proxy-content-transform", + "empty-image"); + + image_resource->Loader()->DidReceiveResponse( + WrappedResourceResponse(resource_response)); + image_resource->Loader()->DidReceiveData( + reinterpret_cast<const char*>(kJpegImage), sizeof(kJpegImage)); + image_resource->Loader()->DidFinishLoading( + 0.0, sizeof(kJpegImage), sizeof(kJpegImage), sizeof(kJpegImage), false); + + EXPECT_TRUE(observer->ImageNotifyFinishedCalled()); + EXPECT_EQ(image_resource, fetcher->CachedResource(test_url)); + + fetcher->ReloadLoFiImages(); + + EXPECT_EQ(3, observer->ImageChangedCount()); + + TestThatReloadIsStartedThenServeReload( + test_url, image_resource, content, observer.get(), + mojom::FetchCacheMode::kBypassCache, false); + + GetMemoryCache()->Remove(image_resource); +} + +TEST_P(ImageResourceReloadTest, ReloadIfLoFiOrPlaceholderBeforeResponse) { + KURL test_url(kTestURL); + ScopedMockedURLLoad scoped_mocked_url_load(test_url, GetTestFilePath()); + + ResourceRequest request(test_url); + request.SetPreviewsState(WebURLRequest::kServerLoFiOn); + FetchParameters fetch_params(request); + ResourceFetcher* fetcher = CreateFetcher(); + + ImageResource* image_resource = ImageResource::Fetch(fetch_params, fetcher); + std::unique_ptr<MockImageResourceObserver> observer = + MockImageResourceObserver::Create(image_resource->GetContent()); + + EXPECT_FALSE(image_resource->ErrorOccurred()); + EXPECT_EQ(IsClientPlaceholderForServerLoFiEnabled(), + image_resource->ShouldShowPlaceholder()); + + // Call reloadIfLoFiOrPlaceholderImage() while the image is still loading. + image_resource->ReloadIfLoFiOrPlaceholderImage(fetcher, + Resource::kReloadAlways); + + EXPECT_EQ(1, observer->ImageChangedCount()); + EXPECT_EQ(0, observer->ImageWidthOnLastImageChanged()); + // The observer should not have been notified of completion yet, since the + // image is still loading. + EXPECT_FALSE(observer->ImageNotifyFinishedCalled()); + + TestThatReloadIsStartedThenServeReload( + test_url, image_resource, image_resource->GetContent(), observer.get(), + mojom::FetchCacheMode::kBypassCache, false); +} + +TEST_P(ImageResourceReloadTest, ReloadIfLoFiOrPlaceholderDuringResponse) { + KURL test_url(kTestURL); + ScopedMockedURLLoad scoped_mocked_url_load(test_url, GetTestFilePath()); + + ResourceRequest request(test_url); + request.SetPreviewsState(WebURLRequest::kServerLoFiOn); + FetchParameters fetch_params(request); + ResourceFetcher* fetcher = CreateFetcher(); + + ImageResource* image_resource = ImageResource::Fetch(fetch_params, fetcher); + std::unique_ptr<MockImageResourceObserver> observer = + MockImageResourceObserver::Create(image_resource->GetContent()); + + // Send the image response. + ResourceResponse resource_response(test_url, "image/jpeg", + sizeof(kJpegImage)); + resource_response.AddHTTPHeaderField("chrome-proxy-content-transform", + "empty-image"); + + image_resource->Loader()->DidReceiveResponse( + WrappedResourceResponse(resource_response)); + image_resource->Loader()->DidReceiveData( + reinterpret_cast<const char*>(kJpegImage), sizeof(kJpegImage)); + + EXPECT_FALSE(image_resource->ErrorOccurred()); + ASSERT_TRUE(image_resource->GetContent()->HasImage()); + EXPECT_FALSE(image_resource->GetContent()->GetImage()->IsNull()); + EXPECT_EQ(1, observer->ImageChangedCount()); + EXPECT_EQ(kJpegImageWidth, observer->ImageWidthOnLastImageChanged()); + EXPECT_FALSE(observer->ImageNotifyFinishedCalled()); + EXPECT_EQ(IsClientPlaceholderForServerLoFiEnabled(), + image_resource->ShouldShowPlaceholder()); + EXPECT_EQ(kJpegImageWidth, image_resource->GetContent()->GetImage()->width()); + EXPECT_EQ(kJpegImageHeight, + image_resource->GetContent()->GetImage()->height()); + + // Call reloadIfLoFiOrPlaceholderImage() while the image is still loading. + image_resource->ReloadIfLoFiOrPlaceholderImage(fetcher, + Resource::kReloadAlways); + + EXPECT_EQ(2, observer->ImageChangedCount()); + EXPECT_EQ(0, observer->ImageWidthOnLastImageChanged()); + // The observer should not have been notified of completion yet, since the + // image is still loading. + EXPECT_FALSE(observer->ImageNotifyFinishedCalled()); + + TestThatReloadIsStartedThenServeReload( + test_url, image_resource, image_resource->GetContent(), observer.get(), + mojom::FetchCacheMode::kBypassCache, false); +} + +TEST_P(ImageResourceReloadTest, ReloadIfLoFiOrPlaceholderForPlaceholder) { + KURL test_url(kTestURL); + ScopedMockedURLLoad scoped_mocked_url_load(test_url, GetTestFilePath()); + + ResourceFetcher* fetcher = CreateFetcher(); + FetchParameters params{ResourceRequest(test_url)}; + params.SetAllowImagePlaceholder(); + ImageResource* image_resource = ImageResource::Fetch(params, fetcher); + EXPECT_EQ(FetchParameters::kAllowPlaceholder, + params.GetPlaceholderImageRequestType()); + std::unique_ptr<MockImageResourceObserver> observer = + MockImageResourceObserver::Create(image_resource->GetContent()); + + TestThatIsPlaceholderRequestAndServeResponse(test_url, image_resource, + observer.get()); + + image_resource->ReloadIfLoFiOrPlaceholderImage(fetcher, + Resource::kReloadAlways); + + TestThatReloadIsStartedThenServeReload( + test_url, image_resource, image_resource->GetContent(), observer.get(), + mojom::FetchCacheMode::kBypassCache, false); +} + +TEST_P(ImageResourceReloadTest, ReloadLoFiImagesWithDuplicateURLs) { + KURL test_url(kTestURL); + ScopedMockedURLLoad scoped_mocked_url_load(test_url, GetTestFilePath()); + ResourceFetcher* fetcher = CreateFetcher(); + + FetchParameters placeholder_params{ResourceRequest(test_url)}; + placeholder_params.SetAllowImagePlaceholder(); + ImageResource* placeholder_resource = + ImageResource::Fetch(placeholder_params, fetcher); + EXPECT_EQ(FetchParameters::kAllowPlaceholder, + placeholder_params.GetPlaceholderImageRequestType()); + EXPECT_TRUE(placeholder_resource->ShouldShowPlaceholder()); + + FetchParameters full_image_params{ResourceRequest(test_url)}; + ImageResource* full_image_resource = + ImageResource::Fetch(full_image_params, fetcher); + EXPECT_EQ(FetchParameters::kDisallowPlaceholder, + full_image_params.GetPlaceholderImageRequestType()); + EXPECT_FALSE(full_image_resource->ShouldShowPlaceholder()); + + // The |placeholder_resource| should not be reused for the + // |full_image_resource|. + EXPECT_NE(placeholder_resource, full_image_resource); + + fetcher->ReloadLoFiImages(); + + EXPECT_FALSE(placeholder_resource->ShouldShowPlaceholder()); + EXPECT_FALSE(full_image_resource->ShouldShowPlaceholder()); +} + +INSTANTIATE_TEST_CASE_P(/* no prefix */, + ImageResourceReloadTest, + testing::Bool()); + +TEST(ImageResourceTest, SVGImage) { + KURL url("http://127.0.0.1:8000/foo"); + ImageResource* image_resource = ImageResource::CreateForTest(url); + std::unique_ptr<MockImageResourceObserver> observer = + MockImageResourceObserver::Create(image_resource->GetContent()); + + ReceiveResponse(image_resource, url, "image/svg+xml", kSvgImage, + strlen(kSvgImage)); + + EXPECT_FALSE(image_resource->ErrorOccurred()); + ASSERT_TRUE(image_resource->GetContent()->HasImage()); + EXPECT_FALSE(image_resource->GetContent()->GetImage()->IsNull()); + EXPECT_EQ(1, observer->ImageChangedCount()); + EXPECT_TRUE(observer->ImageNotifyFinishedCalled()); + EXPECT_FALSE(image_resource->GetContent()->GetImage()->IsBitmapImage()); +} + +TEST(ImageResourceTest, SVGImageWithSubresource) { + KURL url("http://127.0.0.1:8000/foo"); + ImageResource* image_resource = ImageResource::CreateForTest(url); + std::unique_ptr<MockImageResourceObserver> observer = + MockImageResourceObserver::Create(image_resource->GetContent()); + + ReceiveResponse(image_resource, url, "image/svg+xml", + kSvgImageWithSubresource, strlen(kSvgImageWithSubresource)); + + EXPECT_FALSE(image_resource->ErrorOccurred()); + ASSERT_TRUE(image_resource->GetContent()->HasImage()); + EXPECT_FALSE(image_resource->GetContent()->GetImage()->IsNull()); + EXPECT_FALSE(image_resource->GetContent()->GetImage()->IsBitmapImage()); + + // At this point, image is (mostly) available but the loading is not yet + // finished because of SVG's subresources, and thus ImageChanged() or + // ImageNotifyFinished() are not called. + EXPECT_EQ(ResourceStatus::kPending, + image_resource->GetContent()->GetContentStatus()); + EXPECT_EQ(1, observer->ImageChangedCount()); + EXPECT_FALSE(observer->ImageNotifyFinishedCalled()); + EXPECT_EQ(198, image_resource->GetContent()->GetImage()->width()); + EXPECT_EQ(100, image_resource->GetContent()->GetImage()->height()); + + // A new client added here shouldn't notified of finish. + std::unique_ptr<MockImageResourceObserver> observer2 = + MockImageResourceObserver::Create(image_resource->GetContent()); + EXPECT_EQ(1, observer2->ImageChangedCount()); + EXPECT_FALSE(observer2->ImageNotifyFinishedCalled()); + + // After asynchronous tasks are executed, the loading of SVG document is + // completed and ImageNotifyFinished() is called. + test::RunPendingTasks(); + EXPECT_EQ(ResourceStatus::kCached, + image_resource->GetContent()->GetContentStatus()); + EXPECT_EQ(2, observer->ImageChangedCount()); + EXPECT_TRUE(observer->ImageNotifyFinishedCalled()); + EXPECT_EQ(2, observer2->ImageChangedCount()); + EXPECT_TRUE(observer2->ImageNotifyFinishedCalled()); + EXPECT_EQ(198, image_resource->GetContent()->GetImage()->width()); + EXPECT_EQ(100, image_resource->GetContent()->GetImage()->height()); + + GetMemoryCache()->EvictResources(); +} + +TEST(ImageResourceTest, SuccessfulRevalidationJpeg) { + KURL url("http://127.0.0.1:8000/foo"); + ImageResource* image_resource = ImageResource::CreateForTest(url); + std::unique_ptr<MockImageResourceObserver> observer = + MockImageResourceObserver::Create(image_resource->GetContent()); + + ReceiveResponse(image_resource, url, "image/jpeg", + reinterpret_cast<const char*>(kJpegImage), + sizeof(kJpegImage)); + + EXPECT_FALSE(image_resource->ErrorOccurred()); + ASSERT_TRUE(image_resource->GetContent()->HasImage()); + EXPECT_FALSE(image_resource->GetContent()->GetImage()->IsNull()); + EXPECT_EQ(2, observer->ImageChangedCount()); + EXPECT_TRUE(observer->ImageNotifyFinishedCalled()); + EXPECT_TRUE(image_resource->GetContent()->GetImage()->IsBitmapImage()); + EXPECT_EQ(kJpegImageWidth, image_resource->GetContent()->GetImage()->width()); + EXPECT_EQ(kJpegImageHeight, + image_resource->GetContent()->GetImage()->height()); + + image_resource->SetRevalidatingRequest(ResourceRequest(url)); + ResourceResponse response(url); + response.SetHTTPStatusCode(304); + + image_resource->ResponseReceived(response, nullptr); + + EXPECT_FALSE(image_resource->ErrorOccurred()); + ASSERT_TRUE(image_resource->GetContent()->HasImage()); + EXPECT_FALSE(image_resource->GetContent()->GetImage()->IsNull()); + EXPECT_EQ(2, observer->ImageChangedCount()); + EXPECT_TRUE(observer->ImageNotifyFinishedCalled()); + EXPECT_TRUE(image_resource->GetContent()->GetImage()->IsBitmapImage()); + EXPECT_EQ(kJpegImageWidth, image_resource->GetContent()->GetImage()->width()); + EXPECT_EQ(kJpegImageHeight, + image_resource->GetContent()->GetImage()->height()); +} + +TEST(ImageResourceTest, SuccessfulRevalidationSvg) { + KURL url("http://127.0.0.1:8000/foo"); + ImageResource* image_resource = ImageResource::CreateForTest(url); + std::unique_ptr<MockImageResourceObserver> observer = + MockImageResourceObserver::Create(image_resource->GetContent()); + + ReceiveResponse(image_resource, url, "image/svg+xml", kSvgImage, + strlen(kSvgImage)); + + EXPECT_FALSE(image_resource->ErrorOccurred()); + ASSERT_TRUE(image_resource->GetContent()->HasImage()); + EXPECT_FALSE(image_resource->GetContent()->GetImage()->IsNull()); + EXPECT_EQ(1, observer->ImageChangedCount()); + EXPECT_TRUE(observer->ImageNotifyFinishedCalled()); + EXPECT_FALSE(image_resource->GetContent()->GetImage()->IsBitmapImage()); + EXPECT_EQ(200, image_resource->GetContent()->GetImage()->width()); + EXPECT_EQ(200, image_resource->GetContent()->GetImage()->height()); + + image_resource->SetRevalidatingRequest(ResourceRequest(url)); + ResourceResponse response(url); + response.SetHTTPStatusCode(304); + image_resource->ResponseReceived(response, nullptr); + + EXPECT_FALSE(image_resource->ErrorOccurred()); + ASSERT_TRUE(image_resource->GetContent()->HasImage()); + EXPECT_FALSE(image_resource->GetContent()->GetImage()->IsNull()); + EXPECT_EQ(1, observer->ImageChangedCount()); + EXPECT_TRUE(observer->ImageNotifyFinishedCalled()); + EXPECT_FALSE(image_resource->GetContent()->GetImage()->IsBitmapImage()); + EXPECT_EQ(200, image_resource->GetContent()->GetImage()->width()); + EXPECT_EQ(200, image_resource->GetContent()->GetImage()->height()); +} + +TEST(ImageResourceTest, FailedRevalidationJpegToJpeg) { + KURL url("http://127.0.0.1:8000/foo"); + ImageResource* image_resource = ImageResource::CreateForTest(url); + std::unique_ptr<MockImageResourceObserver> observer = + MockImageResourceObserver::Create(image_resource->GetContent()); + + ReceiveResponse(image_resource, url, "image/jpeg", + reinterpret_cast<const char*>(kJpegImage), + sizeof(kJpegImage)); + + EXPECT_FALSE(image_resource->ErrorOccurred()); + ASSERT_TRUE(image_resource->GetContent()->HasImage()); + EXPECT_FALSE(image_resource->GetContent()->GetImage()->IsNull()); + EXPECT_EQ(2, observer->ImageChangedCount()); + EXPECT_TRUE(observer->ImageNotifyFinishedCalled()); + EXPECT_TRUE(image_resource->GetContent()->GetImage()->IsBitmapImage()); + EXPECT_EQ(kJpegImageWidth, image_resource->GetContent()->GetImage()->width()); + EXPECT_EQ(kJpegImageHeight, + image_resource->GetContent()->GetImage()->height()); + + image_resource->SetRevalidatingRequest(ResourceRequest(url)); + ReceiveResponse(image_resource, url, "image/jpeg", + reinterpret_cast<const char*>(kJpegImage2), + sizeof(kJpegImage2)); + + EXPECT_FALSE(image_resource->ErrorOccurred()); + ASSERT_TRUE(image_resource->GetContent()->HasImage()); + EXPECT_FALSE(image_resource->GetContent()->GetImage()->IsNull()); + EXPECT_EQ(4, observer->ImageChangedCount()); + EXPECT_TRUE(observer->ImageNotifyFinishedCalled()); + EXPECT_TRUE(image_resource->GetContent()->GetImage()->IsBitmapImage()); + EXPECT_EQ(50, image_resource->GetContent()->GetImage()->width()); + EXPECT_EQ(50, image_resource->GetContent()->GetImage()->height()); +} + +TEST(ImageResourceTest, FailedRevalidationJpegToSvg) { + KURL url("http://127.0.0.1:8000/foo"); + ImageResource* image_resource = ImageResource::CreateForTest(url); + std::unique_ptr<MockImageResourceObserver> observer = + MockImageResourceObserver::Create(image_resource->GetContent()); + + ReceiveResponse(image_resource, url, "image/jpeg", + reinterpret_cast<const char*>(kJpegImage), + sizeof(kJpegImage)); + + EXPECT_FALSE(image_resource->ErrorOccurred()); + ASSERT_TRUE(image_resource->GetContent()->HasImage()); + EXPECT_FALSE(image_resource->GetContent()->GetImage()->IsNull()); + EXPECT_EQ(2, observer->ImageChangedCount()); + EXPECT_TRUE(observer->ImageNotifyFinishedCalled()); + EXPECT_TRUE(image_resource->GetContent()->GetImage()->IsBitmapImage()); + EXPECT_EQ(kJpegImageWidth, image_resource->GetContent()->GetImage()->width()); + EXPECT_EQ(kJpegImageHeight, + image_resource->GetContent()->GetImage()->height()); + + image_resource->SetRevalidatingRequest(ResourceRequest(url)); + ReceiveResponse(image_resource, url, "image/svg+xml", kSvgImage, + strlen(kSvgImage)); + + EXPECT_FALSE(image_resource->ErrorOccurred()); + ASSERT_TRUE(image_resource->GetContent()->HasImage()); + EXPECT_FALSE(image_resource->GetContent()->GetImage()->IsNull()); + EXPECT_EQ(3, observer->ImageChangedCount()); + EXPECT_TRUE(observer->ImageNotifyFinishedCalled()); + EXPECT_FALSE(image_resource->GetContent()->GetImage()->IsBitmapImage()); + EXPECT_EQ(200, image_resource->GetContent()->GetImage()->width()); + EXPECT_EQ(200, image_resource->GetContent()->GetImage()->height()); +} + +TEST(ImageResourceTest, FailedRevalidationSvgToJpeg) { + KURL url("http://127.0.0.1:8000/foo"); + ImageResource* image_resource = ImageResource::CreateForTest(url); + std::unique_ptr<MockImageResourceObserver> observer = + MockImageResourceObserver::Create(image_resource->GetContent()); + + ReceiveResponse(image_resource, url, "image/svg+xml", kSvgImage, + strlen(kSvgImage)); + + EXPECT_FALSE(image_resource->ErrorOccurred()); + ASSERT_TRUE(image_resource->GetContent()->HasImage()); + EXPECT_FALSE(image_resource->GetContent()->GetImage()->IsNull()); + EXPECT_EQ(1, observer->ImageChangedCount()); + EXPECT_TRUE(observer->ImageNotifyFinishedCalled()); + EXPECT_FALSE(image_resource->GetContent()->GetImage()->IsBitmapImage()); + EXPECT_EQ(200, image_resource->GetContent()->GetImage()->width()); + EXPECT_EQ(200, image_resource->GetContent()->GetImage()->height()); + + image_resource->SetRevalidatingRequest(ResourceRequest(url)); + ReceiveResponse(image_resource, url, "image/jpeg", + reinterpret_cast<const char*>(kJpegImage), + sizeof(kJpegImage)); + + EXPECT_FALSE(image_resource->ErrorOccurred()); + ASSERT_TRUE(image_resource->GetContent()->HasImage()); + EXPECT_FALSE(image_resource->GetContent()->GetImage()->IsNull()); + EXPECT_EQ(3, observer->ImageChangedCount()); + EXPECT_TRUE(observer->ImageNotifyFinishedCalled()); + EXPECT_TRUE(image_resource->GetContent()->GetImage()->IsBitmapImage()); + EXPECT_EQ(kJpegImageWidth, image_resource->GetContent()->GetImage()->width()); + EXPECT_EQ(kJpegImageHeight, + image_resource->GetContent()->GetImage()->height()); +} + +TEST(ImageResourceTest, FailedRevalidationSvgToSvg) { + KURL url("http://127.0.0.1:8000/foo"); + ImageResource* image_resource = ImageResource::CreateForTest(url); + std::unique_ptr<MockImageResourceObserver> observer = + MockImageResourceObserver::Create(image_resource->GetContent()); + + ReceiveResponse(image_resource, url, "image/svg+xml", kSvgImage, + strlen(kSvgImage)); + + EXPECT_FALSE(image_resource->ErrorOccurred()); + ASSERT_TRUE(image_resource->GetContent()->HasImage()); + EXPECT_FALSE(image_resource->GetContent()->GetImage()->IsNull()); + EXPECT_EQ(1, observer->ImageChangedCount()); + EXPECT_TRUE(observer->ImageNotifyFinishedCalled()); + EXPECT_FALSE(image_resource->GetContent()->GetImage()->IsBitmapImage()); + EXPECT_EQ(200, image_resource->GetContent()->GetImage()->width()); + EXPECT_EQ(200, image_resource->GetContent()->GetImage()->height()); + + image_resource->SetRevalidatingRequest(ResourceRequest(url)); + ReceiveResponse(image_resource, url, "image/svg+xml", kSvgImage2, + strlen(kSvgImage2)); + + EXPECT_FALSE(image_resource->ErrorOccurred()); + ASSERT_TRUE(image_resource->GetContent()->HasImage()); + EXPECT_FALSE(image_resource->GetContent()->GetImage()->IsNull()); + EXPECT_EQ(2, observer->ImageChangedCount()); + EXPECT_TRUE(observer->ImageNotifyFinishedCalled()); + EXPECT_FALSE(image_resource->GetContent()->GetImage()->IsBitmapImage()); + EXPECT_EQ(300, image_resource->GetContent()->GetImage()->width()); + EXPECT_EQ(300, image_resource->GetContent()->GetImage()->height()); +} + +// Tests for pruning. + +TEST(ImageResourceTest, Prune) { + KURL url("http://127.0.0.1:8000/foo"); + ImageResource* image_resource = ImageResource::CreateForTest(url); + + ReceiveResponse(image_resource, url, "image/jpeg", + reinterpret_cast<const char*>(kJpegImage), + sizeof(kJpegImage)); + + EXPECT_FALSE(image_resource->ErrorOccurred()); + ASSERT_TRUE(image_resource->GetContent()->HasImage()); + EXPECT_FALSE(image_resource->GetContent()->GetImage()->IsNull()); + EXPECT_EQ(kJpegImageWidth, image_resource->GetContent()->GetImage()->width()); + EXPECT_EQ(kJpegImageHeight, + image_resource->GetContent()->GetImage()->height()); + + EXPECT_FALSE(image_resource->IsAlive()); + + image_resource->Prune(); + + EXPECT_TRUE(image_resource->GetContent()->HasImage()); + + blink::test::RunPendingTasks(); + ASSERT_TRUE(image_resource->GetContent()->HasImage()); + EXPECT_FALSE(image_resource->GetContent()->GetImage()->IsNull()); + EXPECT_EQ(kJpegImageWidth, image_resource->GetContent()->GetImage()->width()); + EXPECT_EQ(kJpegImageHeight, + image_resource->GetContent()->GetImage()->height()); +} + +TEST(ImageResourceTest, CancelOnDecodeError) { + KURL test_url(kTestURL); + ScopedMockedURLLoad scoped_mocked_url_load(test_url, GetTestFilePath()); + + ResourceFetcher* fetcher = CreateFetcher(); + FetchParameters params{ResourceRequest(test_url)}; + ImageResource* image_resource = ImageResource::Fetch(params, fetcher); + std::unique_ptr<MockImageResourceObserver> observer = + MockImageResourceObserver::Create(image_resource->GetContent()); + + image_resource->Loader()->DidReceiveResponse( + WrappedResourceResponse(ResourceResponse(test_url, "image/jpeg", 18)), + nullptr); + + EXPECT_EQ(0, observer->ImageChangedCount()); + + image_resource->Loader()->DidReceiveData("notactuallyanimage", 18); + + EXPECT_EQ(ResourceStatus::kDecodeError, image_resource->GetStatus()); + EXPECT_TRUE(observer->ImageNotifyFinishedCalled()); + EXPECT_EQ(ResourceStatus::kDecodeError, + observer->StatusOnImageNotifyFinished()); + EXPECT_EQ(1, observer->ImageChangedCount()); + EXPECT_FALSE(image_resource->IsLoading()); +} + +TEST(ImageResourceTest, DecodeErrorWithEmptyBody) { + KURL test_url(kTestURL); + ScopedMockedURLLoad scoped_mocked_url_load(test_url, GetTestFilePath()); + + ResourceFetcher* fetcher = CreateFetcher(); + FetchParameters params{ResourceRequest(test_url)}; + ImageResource* image_resource = ImageResource::Fetch(params, fetcher); + std::unique_ptr<MockImageResourceObserver> observer = + MockImageResourceObserver::Create(image_resource->GetContent()); + + image_resource->Loader()->DidReceiveResponse( + WrappedResourceResponse(ResourceResponse(test_url, "image/jpeg")), + nullptr); + + EXPECT_EQ(ResourceStatus::kPending, image_resource->GetStatus()); + EXPECT_FALSE(observer->ImageNotifyFinishedCalled()); + EXPECT_EQ(0, observer->ImageChangedCount()); + + image_resource->Loader()->DidFinishLoading(0.0, 0, 0, 0, false); + + EXPECT_EQ(ResourceStatus::kDecodeError, image_resource->GetStatus()); + EXPECT_TRUE(observer->ImageNotifyFinishedCalled()); + EXPECT_EQ(ResourceStatus::kDecodeError, + observer->StatusOnImageNotifyFinished()); + EXPECT_EQ(1, observer->ImageChangedCount()); + EXPECT_FALSE(image_resource->IsLoading()); +} + +// Testing DecodeError that occurs in didFinishLoading(). +// This is similar to DecodeErrorWithEmptyBody, but with non-empty body. +TEST(ImageResourceTest, PartialContentWithoutDimensions) { + KURL test_url(kTestURL); + ScopedMockedURLLoad scoped_mocked_url_load(test_url, GetTestFilePath()); + + ResourceRequest resource_request(test_url); + resource_request.SetHTTPHeaderField("range", "bytes=0-2"); + FetchParameters params(resource_request); + ResourceFetcher* fetcher = CreateFetcher(); + ImageResource* image_resource = ImageResource::Fetch(params, fetcher); + std::unique_ptr<MockImageResourceObserver> observer = + MockImageResourceObserver::Create(image_resource->GetContent()); + + ResourceResponse partial_response(test_url, "image/jpeg", + kJpegImageSubrangeWithoutDimensionsLength); + partial_response.SetHTTPStatusCode(206); + partial_response.SetHTTPHeaderField( + "content-range", + BuildContentRange(kJpegImageSubrangeWithoutDimensionsLength, + sizeof(kJpegImage))); + + image_resource->Loader()->DidReceiveResponse( + WrappedResourceResponse(partial_response)); + image_resource->Loader()->DidReceiveData( + reinterpret_cast<const char*>(kJpegImage), + kJpegImageSubrangeWithoutDimensionsLength); + + EXPECT_EQ(ResourceStatus::kPending, image_resource->GetStatus()); + EXPECT_FALSE(observer->ImageNotifyFinishedCalled()); + EXPECT_EQ(0, observer->ImageChangedCount()); + + image_resource->Loader()->DidFinishLoading( + 0.0, kJpegImageSubrangeWithoutDimensionsLength, + kJpegImageSubrangeWithoutDimensionsLength, + kJpegImageSubrangeWithoutDimensionsLength, false); + + EXPECT_EQ(ResourceStatus::kDecodeError, image_resource->GetStatus()); + EXPECT_TRUE(observer->ImageNotifyFinishedCalled()); + EXPECT_EQ(ResourceStatus::kDecodeError, + observer->StatusOnImageNotifyFinished()); + EXPECT_EQ(1, observer->ImageChangedCount()); + EXPECT_FALSE(image_resource->IsLoading()); +} + +TEST(ImageResourceTest, FetchDisallowPlaceholder) { + KURL test_url(kTestURL); + ScopedMockedURLLoad scoped_mocked_url_load(test_url, GetTestFilePath()); + + FetchParameters params{ResourceRequest(test_url)}; + ImageResource* image_resource = ImageResource::Fetch(params, CreateFetcher()); + EXPECT_EQ(FetchParameters::kDisallowPlaceholder, + params.GetPlaceholderImageRequestType()); + std::unique_ptr<MockImageResourceObserver> observer = + MockImageResourceObserver::Create(image_resource->GetContent()); + + TestThatIsNotPlaceholderRequestAndServeResponse(test_url, image_resource, + observer.get()); +} + +TEST(ImageResourceTest, FetchAllowPlaceholderDataURL) { + KURL test_url("data:image/jpeg;base64," + + Base64Encode(reinterpret_cast<const char*>(kJpegImage), + sizeof(kJpegImage))); + FetchParameters params{ResourceRequest(test_url)}; + params.SetAllowImagePlaceholder(); + ImageResource* image_resource = ImageResource::Fetch(params, CreateFetcher()); + EXPECT_EQ(FetchParameters::kDisallowPlaceholder, + params.GetPlaceholderImageRequestType()); + EXPECT_EQ(g_null_atom, + image_resource->GetResourceRequest().HttpHeaderField("range")); + EXPECT_FALSE(image_resource->ShouldShowPlaceholder()); +} + +TEST(ImageResourceTest, FetchAllowPlaceholderPostRequest) { + KURL test_url(kTestURL); + ScopedMockedURLLoad scoped_mocked_url_load(test_url, GetTestFilePath()); + ResourceRequest resource_request(test_url); + resource_request.SetHTTPMethod(HTTPNames::POST); + FetchParameters params(resource_request); + params.SetAllowImagePlaceholder(); + ImageResource* image_resource = ImageResource::Fetch(params, CreateFetcher()); + EXPECT_EQ(FetchParameters::kDisallowPlaceholder, + params.GetPlaceholderImageRequestType()); + EXPECT_EQ(g_null_atom, + image_resource->GetResourceRequest().HttpHeaderField("range")); + EXPECT_FALSE(image_resource->ShouldShowPlaceholder()); + + image_resource->Loader()->Cancel(); +} + +TEST(ImageResourceTest, FetchAllowPlaceholderExistingRangeHeader) { + KURL test_url(kTestURL); + ScopedMockedURLLoad scoped_mocked_url_load(test_url, GetTestFilePath()); + ResourceRequest resource_request(test_url); + resource_request.SetHTTPHeaderField("range", "bytes=128-255"); + FetchParameters params(resource_request); + params.SetAllowImagePlaceholder(); + ImageResource* image_resource = ImageResource::Fetch(params, CreateFetcher()); + EXPECT_EQ(FetchParameters::kDisallowPlaceholder, + params.GetPlaceholderImageRequestType()); + EXPECT_EQ("bytes=128-255", + image_resource->GetResourceRequest().HttpHeaderField("range")); + EXPECT_FALSE(image_resource->ShouldShowPlaceholder()); + + image_resource->Loader()->Cancel(); +} + +TEST(ImageResourceTest, FetchAllowPlaceholderSuccessful) { + KURL test_url(kTestURL); + ScopedMockedURLLoad scoped_mocked_url_load(test_url, GetTestFilePath()); + + FetchParameters params{ResourceRequest(test_url)}; + params.SetAllowImagePlaceholder(); + ImageResource* image_resource = ImageResource::Fetch(params, CreateFetcher()); + EXPECT_EQ(FetchParameters::kAllowPlaceholder, + params.GetPlaceholderImageRequestType()); + std::unique_ptr<MockImageResourceObserver> observer = + MockImageResourceObserver::Create(image_resource->GetContent()); + + TestThatIsPlaceholderRequestAndServeResponse(test_url, image_resource, + observer.get()); +} + +TEST(ImageResourceTest, FetchAllowPlaceholderUnsuccessful) { + KURL test_url(kTestURL); + ScopedMockedURLLoad scoped_mocked_url_load(test_url, GetTestFilePath()); + + FetchParameters params{ResourceRequest(test_url)}; + params.SetAllowImagePlaceholder(); + ImageResource* image_resource = ImageResource::Fetch(params, CreateFetcher()); + EXPECT_EQ(FetchParameters::kAllowPlaceholder, + params.GetPlaceholderImageRequestType()); + EXPECT_EQ("bytes=0-2047", + image_resource->GetResourceRequest().HttpHeaderField("range")); + EXPECT_TRUE(image_resource->ShouldShowPlaceholder()); + std::unique_ptr<MockImageResourceObserver> observer = + MockImageResourceObserver::Create(image_resource->GetContent()); + + const char kBadData[] = "notanimageresponse"; + + ResourceResponse bad_response(test_url, "image/jpeg", sizeof(kBadData)); + bad_response.SetHTTPStatusCode(206); + bad_response.SetHTTPHeaderField( + "content-range", BuildContentRange(sizeof(kBadData), sizeof(kJpegImage))); + + image_resource->Loader()->DidReceiveResponse( + WrappedResourceResponse(bad_response)); + + EXPECT_EQ(0, observer->ImageChangedCount()); + + image_resource->Loader()->DidReceiveData(kBadData, sizeof(kBadData)); + + // The dimensions could not be extracted, so the full original image should be + // loading. + EXPECT_FALSE(observer->ImageNotifyFinishedCalled()); + EXPECT_EQ(2, observer->ImageChangedCount()); + EXPECT_FALSE(image_resource->ShouldShowPlaceholder()); + + TestThatReloadIsStartedThenServeReload( + test_url, image_resource, image_resource->GetContent(), observer.get(), + mojom::FetchCacheMode::kBypassCache, false); +} + +TEST(ImageResourceTest, FetchAllowPlaceholderUnsuccessfulClientLoFi) { + KURL test_url(kTestURL); + ScopedMockedURLLoad scoped_mocked_url_load(test_url, GetTestFilePath()); + + ResourceRequest request = ResourceRequest(test_url); + request.SetPreviewsState(WebURLRequest::kClientLoFiOn); + FetchParameters params{request}; + params.SetAllowImagePlaceholder(); + ImageResource* image_resource = ImageResource::Fetch(params, CreateFetcher()); + EXPECT_EQ(FetchParameters::kAllowPlaceholder, + params.GetPlaceholderImageRequestType()); + EXPECT_EQ("bytes=0-2047", + image_resource->GetResourceRequest().HttpHeaderField("range")); + EXPECT_TRUE(image_resource->ShouldShowPlaceholder()); + std::unique_ptr<MockImageResourceObserver> observer = + MockImageResourceObserver::Create(image_resource->GetContent()); + + const char kBadData[] = "notanimageresponse"; + + ResourceResponse bad_response(test_url, "image/jpeg", sizeof(kBadData)); + bad_response.SetHTTPStatusCode(206); + bad_response.SetHTTPHeaderField( + "content-range", BuildContentRange(sizeof(kBadData), sizeof(kJpegImage))); + + image_resource->Loader()->DidReceiveResponse( + WrappedResourceResponse(bad_response)); + + EXPECT_EQ(0, observer->ImageChangedCount()); + + image_resource->Loader()->DidReceiveData(kBadData, sizeof(kBadData)); + + // The dimensions could not be extracted, so the full original image should be + // loading. + EXPECT_FALSE(observer->ImageNotifyFinishedCalled()); + EXPECT_EQ(2, observer->ImageChangedCount()); + + TestThatReloadIsStartedThenServeReload( + test_url, image_resource, image_resource->GetContent(), observer.get(), + mojom::FetchCacheMode::kBypassCache, true); + + EXPECT_FALSE(image_resource->GetContent()->GetImage()->IsBitmapImage()); + EXPECT_TRUE(image_resource->ShouldShowPlaceholder()); +} + +TEST(ImageResourceTest, FetchAllowPlaceholderPartialContentWithoutDimensions) { + const struct { + WebURLRequest::PreviewsState initial_previews_state; + WebURLRequest::PreviewsState expected_reload_previews_state; + bool placeholder_before_reload; + bool placeholder_after_reload; + } tests[] = { + {WebURLRequest::kPreviewsUnspecified, WebURLRequest::kPreviewsNoTransform, + false}, + {WebURLRequest::kClientLoFiOn, + WebURLRequest::kPreviewsNoTransform | + WebURLRequest::kClientLoFiAutoReload, + true}, + }; + + for (const auto& test : tests) { + KURL test_url(kTestURL); + ScopedMockedURLLoad scoped_mocked_url_load(test_url, GetTestFilePath()); + + ResourceRequest resource_request(test_url); + resource_request.SetPreviewsState(test.initial_previews_state); + FetchParameters params(resource_request); + + params.SetAllowImagePlaceholder(); + ImageResource* image_resource = + ImageResource::Fetch(params, CreateFetcher()); + EXPECT_EQ(FetchParameters::kAllowPlaceholder, + params.GetPlaceholderImageRequestType()); + EXPECT_EQ("bytes=0-2047", + image_resource->GetResourceRequest().HttpHeaderField("range")); + EXPECT_TRUE(image_resource->ShouldShowPlaceholder()); + std::unique_ptr<MockImageResourceObserver> observer = + MockImageResourceObserver::Create(image_resource->GetContent()); + + // TODO(hiroshige): Make the range request header and partial content length + // consistent. https://crbug.com/689760. + ResourceResponse partial_response( + test_url, "image/jpeg", kJpegImageSubrangeWithoutDimensionsLength); + partial_response.SetHTTPStatusCode(206); + partial_response.SetHTTPHeaderField( + "content-range", + BuildContentRange(kJpegImageSubrangeWithoutDimensionsLength, + sizeof(kJpegImage))); + + image_resource->Loader()->DidReceiveResponse( + WrappedResourceResponse(partial_response)); + image_resource->Loader()->DidReceiveData( + reinterpret_cast<const char*>(kJpegImage), + kJpegImageSubrangeWithoutDimensionsLength); + + EXPECT_EQ(0, observer->ImageChangedCount()); + + image_resource->Loader()->DidFinishLoading( + 0.0, kJpegImageSubrangeWithoutDimensionsLength, + kJpegImageSubrangeWithoutDimensionsLength, + kJpegImageSubrangeWithoutDimensionsLength, false); + + EXPECT_FALSE(observer->ImageNotifyFinishedCalled()); + EXPECT_EQ(2, observer->ImageChangedCount()); + + TestThatReloadIsStartedThenServeReload( + test_url, image_resource, image_resource->GetContent(), observer.get(), + mojom::FetchCacheMode::kBypassCache, test.placeholder_before_reload); + + EXPECT_EQ(test.expected_reload_previews_state, + image_resource->GetResourceRequest().GetPreviewsState()); + } +} + +TEST(ImageResourceTest, FetchAllowPlaceholderThenDisallowPlaceholder) { + KURL test_url(kTestURL); + ScopedMockedURLLoad scoped_mocked_url_load(test_url, GetTestFilePath()); + + ResourceFetcher* fetcher = CreateFetcher(); + + FetchParameters placeholder_params{ResourceRequest(test_url)}; + placeholder_params.SetAllowImagePlaceholder(); + ImageResource* image_resource = + ImageResource::Fetch(placeholder_params, fetcher); + std::unique_ptr<MockImageResourceObserver> observer = + MockImageResourceObserver::Create(image_resource->GetContent()); + + FetchParameters non_placeholder_params{ResourceRequest(test_url)}; + ImageResource* image_resource2 = + ImageResource::Fetch(non_placeholder_params, fetcher); + std::unique_ptr<MockImageResourceObserver> observer2 = + MockImageResourceObserver::Create(image_resource2->GetContent()); + + ImageResource* image_resource3 = + ImageResource::Fetch(non_placeholder_params, fetcher); + std::unique_ptr<MockImageResourceObserver> observer3 = + MockImageResourceObserver::Create(image_resource3->GetContent()); + + // |imageResource| remains a placeholder, while following non-placeholder + // requests start non-placeholder loading with a separate ImageResource. + ASSERT_NE(image_resource, image_resource2); + ASSERT_NE(image_resource->Loader(), image_resource2->Loader()); + ASSERT_NE(image_resource->GetContent(), image_resource2->GetContent()); + ASSERT_EQ(image_resource2, image_resource3); + + EXPECT_FALSE(observer->ImageNotifyFinishedCalled()); + EXPECT_FALSE(observer2->ImageNotifyFinishedCalled()); + EXPECT_FALSE(observer3->ImageNotifyFinishedCalled()); + + // Checks that |imageResource2| (and |imageResource3|) loads a + // non-placeholder image. + TestThatIsNotPlaceholderRequestAndServeResponse(test_url, image_resource2, + observer2.get()); + EXPECT_TRUE(observer3->ImageNotifyFinishedCalled()); + + // Checks that |imageResource| will loads a placeholder image. + TestThatIsPlaceholderRequestAndServeResponse(test_url, image_resource, + observer.get()); + + // |imageResource2| is still a non-placeholder image. + EXPECT_FALSE(image_resource2->ShouldShowPlaceholder()); + EXPECT_TRUE(image_resource2->GetContent()->GetImage()->IsBitmapImage()); +} + +TEST(ImageResourceTest, + FetchAllowPlaceholderThenDisallowPlaceholderAfterLoaded) { + KURL test_url(kTestURL); + ScopedMockedURLLoad scoped_mocked_url_load(test_url, GetTestFilePath()); + + ResourceFetcher* fetcher = CreateFetcher(); + FetchParameters placeholder_params{ResourceRequest(test_url)}; + placeholder_params.SetAllowImagePlaceholder(); + ImageResource* image_resource = + ImageResource::Fetch(placeholder_params, fetcher); + std::unique_ptr<MockImageResourceObserver> observer = + MockImageResourceObserver::Create(image_resource->GetContent()); + + TestThatIsPlaceholderRequestAndServeResponse(test_url, image_resource, + observer.get()); + + FetchParameters non_placeholder_params{ResourceRequest(test_url)}; + ImageResource* image_resource2 = + ImageResource::Fetch(non_placeholder_params, fetcher); + std::unique_ptr<MockImageResourceObserver> observer2 = + MockImageResourceObserver::Create(image_resource2->GetContent()); + + ImageResource* image_resource3 = + ImageResource::Fetch(non_placeholder_params, fetcher); + std::unique_ptr<MockImageResourceObserver> observer3 = + MockImageResourceObserver::Create(image_resource3->GetContent()); + + EXPECT_FALSE(observer2->ImageNotifyFinishedCalled()); + EXPECT_FALSE(observer3->ImageNotifyFinishedCalled()); + + // |imageResource| remains a placeholder, while following non-placeholder + // requests start non-placeholder loading with a separate ImageResource. + ASSERT_NE(image_resource, image_resource2); + ASSERT_EQ(image_resource2, image_resource3); + + TestThatIsNotPlaceholderRequestAndServeResponse(test_url, image_resource2, + observer2.get()); + EXPECT_TRUE(observer3->ImageNotifyFinishedCalled()); +} + +TEST(ImageResourceTest, FetchAllowPlaceholderFullResponseDecodeSuccess) { + const struct { + int status_code; + AtomicString content_range; + } tests[] = { + {200, g_null_atom}, + {404, g_null_atom}, + {206, BuildContentRange(sizeof(kJpegImage), sizeof(kJpegImage))}, + }; + for (const auto& test : tests) { + KURL test_url(kTestURL); + ScopedMockedURLLoad scoped_mocked_url_load(test_url, GetTestFilePath()); + + FetchParameters params{ResourceRequest(test_url)}; + params.SetAllowImagePlaceholder(); + ImageResource* image_resource = + ImageResource::Fetch(params, CreateFetcher()); + EXPECT_EQ(FetchParameters::kAllowPlaceholder, + params.GetPlaceholderImageRequestType()); + EXPECT_EQ("bytes=0-2047", + image_resource->GetResourceRequest().HttpHeaderField("range")); + EXPECT_TRUE(image_resource->ShouldShowPlaceholder()); + std::unique_ptr<MockImageResourceObserver> observer = + MockImageResourceObserver::Create(image_resource->GetContent()); + + ResourceResponse response(test_url, "image/jpeg", sizeof(kJpegImage)); + response.SetHTTPStatusCode(test.status_code); + if (test.content_range != g_null_atom) + response.SetHTTPHeaderField("content-range", test.content_range); + image_resource->Loader()->DidReceiveResponse( + WrappedResourceResponse(response)); + image_resource->Loader()->DidReceiveData( + reinterpret_cast<const char*>(kJpegImage), sizeof(kJpegImage)); + image_resource->Loader()->DidFinishLoading( + 0.0, sizeof(kJpegImage), sizeof(kJpegImage), sizeof(kJpegImage), false); + + EXPECT_EQ(ResourceStatus::kCached, image_resource->GetStatus()); + EXPECT_EQ(sizeof(kJpegImage), image_resource->EncodedSize()); + EXPECT_FALSE(image_resource->ShouldShowPlaceholder()); + EXPECT_LT(0, observer->ImageChangedCount()); + EXPECT_EQ(kJpegImageWidth, observer->ImageWidthOnLastImageChanged()); + EXPECT_TRUE(observer->ImageNotifyFinishedCalled()); + EXPECT_EQ(kJpegImageWidth, observer->ImageWidthOnImageNotifyFinished()); + + ASSERT_TRUE(image_resource->GetContent()->HasImage()); + EXPECT_EQ(kJpegImageWidth, + image_resource->GetContent()->GetImage()->width()); + EXPECT_EQ(kJpegImageHeight, + image_resource->GetContent()->GetImage()->height()); + EXPECT_TRUE(image_resource->GetContent()->GetImage()->IsBitmapImage()); + } +} + +TEST(ImageResourceTest, + FetchAllowPlaceholderFullResponseDecodeFailureNoReload) { + static const char kBadImageData[] = "bad image data"; + + const struct { + int status_code; + AtomicString content_range; + size_t data_size; + } tests[] = { + {200, g_null_atom, sizeof(kBadImageData)}, + {206, BuildContentRange(sizeof(kBadImageData), sizeof(kBadImageData)), + sizeof(kBadImageData)}, + {204, g_null_atom, 0}, + }; + for (const auto& test : tests) { + KURL test_url(kTestURL); + ScopedMockedURLLoad scoped_mocked_url_load(test_url, GetTestFilePath()); + + FetchParameters params{ResourceRequest(test_url)}; + params.SetAllowImagePlaceholder(); + ImageResource* image_resource = + ImageResource::Fetch(params, CreateFetcher()); + EXPECT_EQ(FetchParameters::kAllowPlaceholder, + params.GetPlaceholderImageRequestType()); + EXPECT_EQ("bytes=0-2047", + image_resource->GetResourceRequest().HttpHeaderField("range")); + EXPECT_TRUE(image_resource->ShouldShowPlaceholder()); + std::unique_ptr<MockImageResourceObserver> observer = + MockImageResourceObserver::Create(image_resource->GetContent()); + + ResourceResponse response(test_url, "image/jpeg", test.data_size); + response.SetHTTPStatusCode(test.status_code); + if (test.content_range != g_null_atom) + response.SetHTTPHeaderField("content-range", test.content_range); + image_resource->Loader()->DidReceiveResponse( + WrappedResourceResponse(response)); + image_resource->Loader()->DidReceiveData(kBadImageData, test.data_size); + + EXPECT_EQ(ResourceStatus::kDecodeError, image_resource->GetStatus()); + EXPECT_FALSE(image_resource->ShouldShowPlaceholder()); + } +} + +TEST(ImageResourceTest, + FetchAllowPlaceholderFullResponseDecodeFailureWithReload) { + const int kStatusCodes[] = {404, 500}; + for (int status_code : kStatusCodes) { + KURL test_url(kTestURL); + ScopedMockedURLLoad scoped_mocked_url_load(test_url, GetTestFilePath()); + + FetchParameters params{ResourceRequest(test_url)}; + params.SetAllowImagePlaceholder(); + ImageResource* image_resource = + ImageResource::Fetch(params, CreateFetcher()); + EXPECT_EQ(FetchParameters::kAllowPlaceholder, + params.GetPlaceholderImageRequestType()); + EXPECT_EQ("bytes=0-2047", + image_resource->GetResourceRequest().HttpHeaderField("range")); + EXPECT_TRUE(image_resource->ShouldShowPlaceholder()); + std::unique_ptr<MockImageResourceObserver> observer = + MockImageResourceObserver::Create(image_resource->GetContent()); + + static const char kBadImageData[] = "bad image data"; + + ResourceResponse response(test_url, "image/jpeg", sizeof(kBadImageData)); + response.SetHTTPStatusCode(status_code); + image_resource->Loader()->DidReceiveResponse( + WrappedResourceResponse(response)); + image_resource->Loader()->DidReceiveData(kBadImageData, + sizeof(kBadImageData)); + + EXPECT_FALSE(observer->ImageNotifyFinishedCalled()); + + // The dimensions could not be extracted, and the response code was a 4xx + // error, so the full original image should be loading. + TestThatReloadIsStartedThenServeReload( + test_url, image_resource, image_resource->GetContent(), observer.get(), + mojom::FetchCacheMode::kBypassCache, false); + } +} + +TEST(ImageResourceTest, PeriodicFlushTest) { + EmptyChromeClient* chrome_client = new EmptyChromeClient(); + Page::PageClients clients; + FillWithEmptyClients(clients); + clients.chrome_client = chrome_client; + std::unique_ptr<DummyPageHolder> page_holder = DummyPageHolder::Create( + IntSize(800, 600), &clients, EmptyLocalFrameClient::Create(), nullptr); + + ScopedTestingPlatformSupport<TestingPlatformSupportWithMockScheduler> + platform; + KURL test_url(kTestURL); + ScopedMockedURLLoad scoped_mocked_url_load(test_url, GetTestFilePath()); + + MockFetchContext* context = MockFetchContext::Create( + MockFetchContext::LoadPolicy::kShouldLoadNewResource, + page_holder->GetFrame().GetTaskRunner(TaskType::kInternalTest)); + ResourceFetcher* fetcher = ResourceFetcher::Create(context); + ResourceLoadScheduler* scheduler = ResourceLoadScheduler::Create(); + ImageResource* image_resource = ImageResource::CreateForTest(test_url); + + // Ensure that |image_resource| has a loader. + ResourceLoader* loader = + ResourceLoader::Create(fetcher, scheduler, image_resource); + ALLOW_UNUSED_LOCAL(loader); + + image_resource->SetStatus(ResourceStatus::kPending); + image_resource->NotifyStartLoad(); + + std::unique_ptr<MockImageResourceObserver> observer = + MockImageResourceObserver::Create(image_resource->GetContent()); + + // Send the image response. + ResourceResponse resource_response(NullURL(), "image/jpeg", + sizeof(kJpegImage2)); + + image_resource->ResponseReceived(resource_response, nullptr); + + // This is number is sufficiently large amount of bytes necessary for the + // image to be created (since the size is known). This was determined by + // appending one byte at a time (with flushes) until the image was decoded. + size_t meaningful_image_size = 280; + image_resource->AppendData(reinterpret_cast<const char*>(kJpegImage2), + meaningful_image_size); + size_t bytes_sent = meaningful_image_size; + + EXPECT_FALSE(image_resource->ErrorOccurred()); + EXPECT_TRUE(image_resource->GetContent()->HasImage()); + EXPECT_EQ(1, observer->ImageChangedCount()); + + platform->RunForPeriodSeconds(1.); + platform->AdvanceClockSeconds(1.); + + // Sanity check that we created an image after appending |meaningfulImageSize| + // bytes just once. + EXPECT_FALSE(image_resource->ErrorOccurred()); + ASSERT_TRUE(image_resource->GetContent()->HasImage()); + EXPECT_EQ(1, observer->ImageChangedCount()); + + for (int flush_count = 1; flush_count <= 3; ++flush_count) { + // For each of the iteration that appends data, we don't expect + // |imageChangeCount()| to change, since the time is adjusted by 0.2001 + // seconds (it's greater than 0.2 to avoid double precision problems). + // After 5 appends, we breach the flush interval and the flush count + // increases. + for (int i = 0; i < 5; ++i) { + SCOPED_TRACE(i); + image_resource->AppendData( + reinterpret_cast<const char*>(kJpegImage2) + bytes_sent, 1); + + EXPECT_FALSE(image_resource->ErrorOccurred()); + ASSERT_TRUE(image_resource->GetContent()->HasImage()); + EXPECT_EQ(flush_count, observer->ImageChangedCount()); + + ++bytes_sent; + platform->RunForPeriodSeconds(0.2001); + } + } + + // Increasing time by a large number only causes one extra flush. + platform->RunForPeriodSeconds(10.); + platform->AdvanceClockSeconds(10.); + EXPECT_FALSE(image_resource->ErrorOccurred()); + ASSERT_TRUE(image_resource->GetContent()->HasImage()); + EXPECT_FALSE(image_resource->GetContent()->GetImage()->IsNull()); + EXPECT_EQ(4, observer->ImageChangedCount()); + + // Append the rest of the data and finish (which causes another flush). + image_resource->AppendData( + reinterpret_cast<const char*>(kJpegImage2) + bytes_sent, + sizeof(kJpegImage2) - bytes_sent); + image_resource->FinishForTest(); + + EXPECT_FALSE(image_resource->ErrorOccurred()); + ASSERT_TRUE(image_resource->GetContent()->HasImage()); + EXPECT_FALSE(image_resource->GetContent()->GetImage()->IsNull()); + EXPECT_EQ(5, observer->ImageChangedCount()); + EXPECT_TRUE(observer->ImageNotifyFinishedCalled()); + EXPECT_TRUE(image_resource->GetContent()->GetImage()->IsBitmapImage()); + EXPECT_EQ(50, image_resource->GetContent()->GetImage()->width()); + EXPECT_EQ(50, image_resource->GetContent()->GetImage()->height()); + + WTF::SetTimeFunctionsForTesting(nullptr); +} + +TEST(ImageResourceTest, DeferredInvalidation) { + ImageResource* image_resource = ImageResource::CreateForTest(NullURL()); + std::unique_ptr<MockImageResourceObserver> obs = + MockImageResourceObserver::Create(image_resource->GetContent()); + + // Image loaded. + ReceiveResponse(image_resource, NullURL(), "image/jpeg", + reinterpret_cast<const char*>(kJpegImage), + sizeof(kJpegImage)); + EXPECT_EQ(obs->ImageChangedCount(), 2); + EXPECT_EQ(obs->Defer(), ImageResourceObserver::CanDeferInvalidation::kNo); + + // Image animated. + static_cast<ImageObserver*>(image_resource->GetContent()) + ->AnimationAdvanced(image_resource->GetContent()->GetImage()); + EXPECT_EQ(obs->ImageChangedCount(), 3); + EXPECT_EQ(obs->Defer(), ImageResourceObserver::CanDeferInvalidation::kYes); +} + +} // namespace + +class ImageResourceCounterTest : public testing::Test { + public: + ImageResourceCounterTest() = default; + ~ImageResourceCounterTest() = default; + + void CreateImageResource(const char* url_part, bool ua_resource) { + // Create a unique fake data url. + String url("data:image/png;base64,"); + url.append(url_part); + + // Setup the fetcher and request. + ResourceFetcher* fetcher = CreateFetcher(); + KURL test_url(url); + ResourceRequest request = ResourceRequest(test_url); + FetchParameters fetch_params(request); + scheduler::FakeTaskRunner* task_runner = + static_cast<scheduler::FakeTaskRunner*>( + fetcher->Context().GetLoadingTaskRunner().get()); + task_runner->SetTime(1); + + // Mark it as coming from a UA stylesheet (if needed). + if (ua_resource) { + fetch_params.MutableOptions().initiator_info.name = + FetchInitiatorTypeNames::uacss; + } + + // Fetch the ImageResource. + ImageResource::Fetch(fetch_params, fetcher); + task_runner->RunUntilIdle(); + } + + int GetResourceCount() const { + return InstanceCounters::CounterValue(InstanceCounters::kResourceCounter); + } + + int GetUACSSResourceCount() const { + return InstanceCounters::CounterValue( + InstanceCounters::kUACSSResourceCounter); + } +}; + +TEST_F(ImageResourceCounterTest, InstanceCounters) { + // Get the current resource count. + int current_count = GetResourceCount(); + int current_ua_count = GetUACSSResourceCount(); + + // Create a non-UA sourced image. + CreateImageResource("a", false); + + // Check the instance counters have been updated. + EXPECT_EQ(++current_count, GetResourceCount()); + EXPECT_EQ(current_ua_count, GetUACSSResourceCount()); + + // Create another non-UA sourced image. + CreateImageResource("b", false); + + // Check the instance counters have been updated. + EXPECT_EQ(++current_count, GetResourceCount()); + EXPECT_EQ(current_ua_count, GetUACSSResourceCount()); +} + +TEST_F(ImageResourceCounterTest, InstanceCounters_UserAgent) { + // Get the current resource count. + int current_count = GetResourceCount(); + int current_ua_count = GetUACSSResourceCount(); + + // Create a non-UA sourced image. + CreateImageResource("c", false); + + // Check the instance counters have been updated. + EXPECT_EQ(++current_count, GetResourceCount()); + EXPECT_EQ(current_ua_count, GetUACSSResourceCount()); + + // Create a UA sourced image. + CreateImageResource("d", true); + + // Check the instance counters have been updated. + EXPECT_EQ(++current_count, GetResourceCount()); + EXPECT_EQ(++current_ua_count, GetUACSSResourceCount()); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/loader/resource/link_fetch_resource.cc b/chromium/third_party/blink/renderer/core/loader/resource/link_fetch_resource.cc new file mode 100644 index 00000000000..822733359bc --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/resource/link_fetch_resource.cc @@ -0,0 +1,29 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "third_party/blink/renderer/core/loader/resource/link_fetch_resource.h" + +#include "services/network/public/mojom/request_context_frame_type.mojom-blink.h" +#include "third_party/blink/renderer/platform/loader/fetch/fetch_parameters.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h" + +namespace blink { + +Resource* LinkFetchResource::Fetch(Resource::Type type, + FetchParameters& params, + ResourceFetcher* fetcher) { + DCHECK_EQ(type, kLinkPrefetch); + DCHECK_EQ(params.GetResourceRequest().GetFrameType(), + network::mojom::RequestContextFrameType::kNone); + return fetcher->RequestResource(params, LinkResourceFactory(type), nullptr); +} + +LinkFetchResource::LinkFetchResource(const ResourceRequest& request, + Type type, + const ResourceLoaderOptions& options) + : Resource(request, type, options) {} + +LinkFetchResource::~LinkFetchResource() = default; + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/loader/resource/link_fetch_resource.h b/chromium/third_party/blink/renderer/core/loader/resource/link_fetch_resource.h new file mode 100644 index 00000000000..342ce63c833 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/resource/link_fetch_resource.h @@ -0,0 +1,37 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_RESOURCE_LINK_FETCH_RESOURCE_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_RESOURCE_LINK_FETCH_RESOURCE_H_ + +#include "third_party/blink/renderer/platform/loader/fetch/resource.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource_client.h" + +namespace blink { + +class FetchParameters; +class ResourceFetcher; + +class LinkFetchResource final : public Resource { + public: + static Resource* Fetch(Resource::Type, FetchParameters&, ResourceFetcher*); + ~LinkFetchResource() override; + + private: + class LinkResourceFactory : public NonTextResourceFactory { + public: + explicit LinkResourceFactory(Resource::Type type) + : NonTextResourceFactory(type) {} + + Resource* Create(const ResourceRequest& request, + const ResourceLoaderOptions& options) const override { + return new LinkFetchResource(request, GetType(), options); + } + }; + LinkFetchResource(const ResourceRequest&, Type, const ResourceLoaderOptions&); +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_RESOURCE_LINK_FETCH_RESOURCE_H_ diff --git a/chromium/third_party/blink/renderer/core/loader/resource/mock_font_resource_client.cc b/chromium/third_party/blink/renderer/core/loader/resource/mock_font_resource_client.cc new file mode 100644 index 00000000000..0bfcabf4549 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/resource/mock_font_resource_client.cc @@ -0,0 +1,29 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "third_party/blink/renderer/core/loader/resource/mock_font_resource_client.h" + +#include "testing/gtest/include/gtest/gtest.h" + +namespace blink { + +MockFontResourceClient::MockFontResourceClient() + : font_load_short_limit_exceeded_called_(false), + font_load_long_limit_exceeded_called_(false) {} + +MockFontResourceClient::~MockFontResourceClient() = default; + +void MockFontResourceClient::FontLoadShortLimitExceeded(FontResource*) { + ASSERT_FALSE(font_load_short_limit_exceeded_called_); + ASSERT_FALSE(font_load_long_limit_exceeded_called_); + font_load_short_limit_exceeded_called_ = true; +} + +void MockFontResourceClient::FontLoadLongLimitExceeded(FontResource*) { + ASSERT_TRUE(font_load_short_limit_exceeded_called_); + ASSERT_FALSE(font_load_long_limit_exceeded_called_); + font_load_long_limit_exceeded_called_ = true; +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/loader/resource/mock_font_resource_client.h b/chromium/third_party/blink/renderer/core/loader/resource/mock_font_resource_client.h new file mode 100644 index 00000000000..eb5ceed3f0a --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/resource/mock_font_resource_client.h @@ -0,0 +1,44 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_RESOURCE_MOCK_FONT_RESOURCE_CLIENT_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_RESOURCE_MOCK_FONT_RESOURCE_CLIENT_H_ + +#include "third_party/blink/renderer/core/loader/resource/font_resource.h" +#include "third_party/blink/renderer/platform/heap/handle.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource_client.h" + +namespace blink { + +class MockFontResourceClient final + : public GarbageCollectedFinalized<MockFontResourceClient>, + public FontResourceClient { + USING_GARBAGE_COLLECTED_MIXIN(MockFontResourceClient); + + public: + MockFontResourceClient(); + ~MockFontResourceClient() override; + + void FontLoadShortLimitExceeded(FontResource*) override; + void FontLoadLongLimitExceeded(FontResource*) override; + + bool FontLoadShortLimitExceededCalled() const { + return font_load_short_limit_exceeded_called_; + } + + bool FontLoadLongLimitExceededCalled() const { + return font_load_long_limit_exceeded_called_; + } + + String DebugName() const override { return "MockFontResourceClient"; } + + private: + bool font_load_short_limit_exceeded_called_; + bool font_load_long_limit_exceeded_called_; +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_RESOURCE_MOCK_FONT_RESOURCE_CLIENT_H_ diff --git a/chromium/third_party/blink/renderer/core/loader/resource/mock_image_resource_observer.cc b/chromium/third_party/blink/renderer/core/loader/resource/mock_image_resource_observer.cc new file mode 100644 index 00000000000..0ef833c90d7 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/resource/mock_image_resource_observer.cc @@ -0,0 +1,58 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "third_party/blink/renderer/core/loader/resource/mock_image_resource_observer.h" + +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/blink/renderer/core/loader/resource/image_resource.h" +#include "third_party/blink/renderer/core/loader/resource/image_resource_content.h" + +namespace blink { + +MockImageResourceObserver::MockImageResourceObserver( + ImageResourceContent* content) + : content_(content), + image_changed_count_(0), + image_width_on_last_image_changed_(0), + image_notify_finished_count_(0), + image_width_on_image_notify_finished_(0) { + content_->AddObserver(this); +} + +MockImageResourceObserver::~MockImageResourceObserver() { + RemoveAsObserver(); +} + +void MockImageResourceObserver::RemoveAsObserver() { + if (!content_) + return; + content_->RemoveObserver(this); + content_ = nullptr; +} + +void MockImageResourceObserver::ImageChanged(ImageResourceContent* image, + CanDeferInvalidation defer, + const IntRect*) { + image_changed_count_++; + image_width_on_last_image_changed_ = + content_->HasImage() ? content_->GetImage()->width() : 0; + defer_ = defer; +} + +void MockImageResourceObserver::ImageNotifyFinished( + ImageResourceContent* image) { + ASSERT_EQ(0, image_notify_finished_count_); + DCHECK(image->IsLoaded()); + image_notify_finished_count_++; + image_width_on_image_notify_finished_ = + content_->HasImage() ? content_->GetImage()->width() : 0; + status_on_image_notify_finished_ = content_->GetContentStatus(); +} + +bool MockImageResourceObserver::ImageNotifyFinishedCalled() const { + DCHECK_LE(image_notify_finished_count_, 1); + return image_notify_finished_count_; +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/loader/resource/mock_image_resource_observer.h b/chromium/third_party/blink/renderer/core/loader/resource/mock_image_resource_observer.h new file mode 100644 index 00000000000..19bf7238918 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/resource/mock_image_resource_observer.h @@ -0,0 +1,64 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_RESOURCE_MOCK_IMAGE_RESOURCE_OBSERVER_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_RESOURCE_MOCK_IMAGE_RESOURCE_OBSERVER_H_ + +#include <memory> + +#include "base/memory/ptr_util.h" +#include "third_party/blink/renderer/core/loader/resource/image_resource.h" +#include "third_party/blink/renderer/core/loader/resource/image_resource_content.h" +#include "third_party/blink/renderer/core/loader/resource/image_resource_observer.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource_status.h" + +namespace blink { + +class MockImageResourceObserver final : public ImageResourceObserver { + public: + static std::unique_ptr<MockImageResourceObserver> Create( + ImageResourceContent* content) { + return base::WrapUnique(new MockImageResourceObserver(content)); + } + ~MockImageResourceObserver() override; + + void RemoveAsObserver(); + + int ImageChangedCount() const { return image_changed_count_; } + bool ImageNotifyFinishedCalled() const; + + int ImageWidthOnLastImageChanged() const { + return image_width_on_last_image_changed_; + } + int ImageWidthOnImageNotifyFinished() const { + return image_width_on_image_notify_finished_; + } + ResourceStatus StatusOnImageNotifyFinished() const { + return status_on_image_notify_finished_; + } + + CanDeferInvalidation Defer() const { return defer_; } + + private: + explicit MockImageResourceObserver(ImageResourceContent*); + + // ImageResourceObserver overrides. + void ImageNotifyFinished(ImageResourceContent*) override; + void ImageChanged(ImageResourceContent*, + CanDeferInvalidation, + const IntRect*) override; + String DebugName() const override { return "MockImageResourceObserver"; } + + Persistent<ImageResourceContent> content_; + int image_changed_count_; + CanDeferInvalidation defer_; + int image_width_on_last_image_changed_; + int image_notify_finished_count_; + int image_width_on_image_notify_finished_; + ResourceStatus status_on_image_notify_finished_ = ResourceStatus::kNotStarted; +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_RESOURCE_MOCK_IMAGE_RESOURCE_OBSERVER_H_ diff --git a/chromium/third_party/blink/renderer/core/loader/resource/multipart_image_resource_parser.cc b/chromium/third_party/blink/renderer/core/loader/resource/multipart_image_resource_parser.cc new file mode 100644 index 00000000000..d499c831999 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/resource/multipart_image_resource_parser.cc @@ -0,0 +1,190 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "third_party/blink/renderer/core/loader/resource/multipart_image_resource_parser.h" + +#include "third_party/blink/renderer/platform/network/http_parsers.h" +#include "third_party/blink/renderer/platform/wtf/not_found.h" +#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h" + +#include <algorithm> + +namespace blink { + +MultipartImageResourceParser::MultipartImageResourceParser( + const ResourceResponse& response, + const Vector<char>& boundary, + Client* client) + : original_response_(response), boundary_(boundary), client_(client) { + // Some servers report a boundary prefixed with "--". See + // https://crbug.com/5786. + if (boundary_.size() < 2 || boundary_[0] != '-' || boundary_[1] != '-') + boundary_.push_front("--", 2); +} + +void MultipartImageResourceParser::AppendData(const char* bytes, size_t size) { + DCHECK(!IsCancelled()); + // m_sawLastBoundary means that we've already received the final boundary + // token. The server should stop sending us data at this point, but if it + // does, we just throw it away. + if (saw_last_boundary_) + return; + data_.Append(bytes, size); + + if (is_parsing_top_) { + // Eat leading \r\n + size_t pos = SkippableLength(data_, 0); + // +2 for "--" + if (data_.size() < boundary_.size() + 2 + pos) { + // We don't have enough data yet to make a boundary token. Just wait + // until the next chunk of data arrives. + return; + } + if (pos) + data_.EraseAt(0, pos); + + // Some servers don't send a boundary token before the first chunk of + // data. We handle this case anyway (Gecko does too). + if (0 != memcmp(data_.data(), boundary_.data(), boundary_.size())) { + data_.push_front("\n", 1); + data_.PrependVector(boundary_); + } + is_parsing_top_ = false; + } + + // Headers + if (is_parsing_headers_) { + if (!ParseHeaders()) { + // Get more data before trying again. + return; + } + // Successfully parsed headers. + is_parsing_headers_ = false; + if (IsCancelled()) + return; + } + + size_t boundary_position; + while ((boundary_position = FindBoundary(data_, &boundary_)) != kNotFound) { + // Strip out trailing \r\n characters in the buffer preceding the boundary + // on the same lines as does Firefox. + size_t data_size = boundary_position; + if (boundary_position > 0 && data_[boundary_position - 1] == '\n') { + data_size--; + if (boundary_position > 1 && data_[boundary_position - 2] == '\r') { + data_size--; + } + } + if (data_size) { + client_->MultipartDataReceived(data_.data(), data_size); + if (IsCancelled()) + return; + } + size_t boundary_end_position = boundary_position + boundary_.size(); + if (boundary_end_position < data_.size() && + '-' == data_[boundary_end_position]) { + // This was the last boundary so we can stop processing. + saw_last_boundary_ = true; + data_.clear(); + return; + } + + // We can now throw out data up through the boundary + data_.EraseAt(0, boundary_end_position); + + // Ok, back to parsing headers + if (!ParseHeaders()) { + is_parsing_headers_ = true; + break; + } + if (IsCancelled()) + return; + } + + // At this point, we should send over any data we have, but keep enough data + // buffered to handle a boundary that may have been truncated. "+2" for CRLF, + // as we may ignore the last CRLF. + if (!is_parsing_headers_ && data_.size() > boundary_.size() + 2) { + size_t send_length = data_.size() - boundary_.size() - 2; + client_->MultipartDataReceived(data_.data(), send_length); + data_.EraseAt(0, send_length); + } +} + +void MultipartImageResourceParser::Finish() { + DCHECK(!IsCancelled()); + if (saw_last_boundary_) + return; + // If we have any pending data and we're not in a header, go ahead and send + // it to the client. + if (!is_parsing_headers_ && !data_.IsEmpty()) + client_->MultipartDataReceived(data_.data(), data_.size()); + data_.clear(); + saw_last_boundary_ = true; +} + +size_t MultipartImageResourceParser::SkippableLength(const Vector<char>& data, + size_t pos) { + if (data.size() >= pos + 2 && data[pos] == '\r' && data[pos + 1] == '\n') + return 2; + if (data.size() >= pos + 1 && data[pos] == '\n') + return 1; + return 0; +} + +bool MultipartImageResourceParser::ParseHeaders() { + // Eat leading \r\n + size_t pos = SkippableLength(data_, 0); + + // Create a ResourceResponse based on the original set of headers + the + // replacement headers. We only replace the same few headers that gecko does. + // See netwerk/streamconv/converters/nsMultiMixedConv.cpp. + ResourceResponse response(original_response_.Url()); + response.SetWasFetchedViaServiceWorker( + original_response_.WasFetchedViaServiceWorker()); + response.SetResponseTypeViaServiceWorker( + original_response_.ResponseTypeViaServiceWorker()); + for (const auto& header : original_response_.HttpHeaderFields()) + response.AddHTTPHeaderField(header.key, header.value); + + size_t end = 0; + if (!ParseMultipartHeadersFromBody(data_.data() + pos, data_.size() - pos, + &response, &end)) + return false; + data_.EraseAt(0, end + pos); + // Send the response! + client_->OnePartInMultipartReceived(response); + return true; +} + +// Boundaries are supposed to be preceeded with --, but it looks like gecko +// doesn't require the dashes to exist. See nsMultiMixedConv::FindToken. +size_t MultipartImageResourceParser::FindBoundary(const Vector<char>& data, + Vector<char>* boundary) { + auto* it = std::search(data.data(), data.data() + data.size(), + boundary->data(), boundary->data() + boundary->size()); + if (it == data.data() + data.size()) + return kNotFound; + + size_t boundary_position = it - data.data(); + // Back up over -- for backwards compat + // TODO(tc): Don't we only want to do this once? Gecko code doesn't seem to + // care. + if (boundary_position >= 2) { + if (data[boundary_position - 1] == '-' && + data[boundary_position - 2] == '-') { + boundary_position -= 2; + Vector<char> v(2, '-'); + v.AppendVector(*boundary); + *boundary = v; + } + } + return boundary_position; +} + +void MultipartImageResourceParser::Trace(blink::Visitor* visitor) { + visitor->Trace(client_); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/loader/resource/multipart_image_resource_parser.h b/chromium/third_party/blink/renderer/core/loader/resource/multipart_image_resource_parser.h new file mode 100644 index 00000000000..2452ac4ee5a --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/resource/multipart_image_resource_parser.h @@ -0,0 +1,103 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is mozilla.org code. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_RESOURCE_MULTIPART_IMAGE_RESOURCE_PARSER_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_RESOURCE_MULTIPART_IMAGE_RESOURCE_PARSER_H_ + +#include "base/macros.h" +#include "third_party/blink/renderer/core/core_export.h" +#include "third_party/blink/renderer/platform/heap/handle.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource_response.h" +#include "third_party/blink/renderer/platform/wtf/vector.h" + +namespace blink { + +// A parser parsing mlutipart/x-mixed-replace resource. +class CORE_EXPORT MultipartImageResourceParser final + : public GarbageCollectedFinalized<MultipartImageResourceParser> { + public: + class CORE_EXPORT Client : public GarbageCollectedMixin { + public: + virtual ~Client() = default; + virtual void OnePartInMultipartReceived(const ResourceResponse&) = 0; + virtual void MultipartDataReceived(const char* bytes, size_t) = 0; + void Trace(blink::Visitor* visitor) override {} + }; + + MultipartImageResourceParser(const ResourceResponse&, + const Vector<char>& boundary, + Client*); + void AppendData(const char* bytes, size_t); + void Finish(); + void Cancel() { is_cancelled_ = true; } + + void Trace(blink::Visitor*); + + static size_t SkippableLengthForTest(const Vector<char>& data, size_t size) { + return SkippableLength(data, size); + } + static size_t FindBoundaryForTest(const Vector<char>& data, + Vector<char>* boundary) { + return FindBoundary(data, boundary); + } + + private: + bool ParseHeaders(); + bool IsCancelled() const { return is_cancelled_; } + static size_t SkippableLength(const Vector<char>&, size_t); + // This function updates |*boundary|. + static size_t FindBoundary(const Vector<char>& data, Vector<char>* boundary); + + const ResourceResponse original_response_; + Vector<char> boundary_; + Member<Client> client_; + + Vector<char> data_; + bool is_parsing_top_ = true; + bool is_parsing_headers_ = false; + bool saw_last_boundary_ = false; + bool is_cancelled_ = false; + + DISALLOW_COPY_AND_ASSIGN(MultipartImageResourceParser); +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_RESOURCE_MULTIPART_IMAGE_RESOURCE_PARSER_H_ diff --git a/chromium/third_party/blink/renderer/core/loader/resource/multipart_image_resource_parser_test.cc b/chromium/third_party/blink/renderer/core/loader/resource/multipart_image_resource_parser_test.cc new file mode 100644 index 00000000000..233892d029f --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/resource/multipart_image_resource_parser_test.cc @@ -0,0 +1,418 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "third_party/blink/renderer/core/loader/resource/multipart_image_resource_parser.h" + +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource_response.h" + +#include <stddef.h> +#include <stdint.h> +#include <string.h> + +namespace blink { +namespace multipart_image_resource_parser_test { + +String ToString(const Vector<char>& data) { + if (data.IsEmpty()) + return String(""); + return String(data.data(), data.size()); +} + +class MockClient final : public GarbageCollectedFinalized<MockClient>, + public MultipartImageResourceParser::Client { + USING_GARBAGE_COLLECTED_MIXIN(MockClient); + + public: + void OnePartInMultipartReceived(const ResourceResponse& response) override { + responses_.push_back(response); + data_.push_back(Vector<char>()); + } + void MultipartDataReceived(const char* bytes, size_t size) override { + data_.back().Append(bytes, size); + } + + Vector<ResourceResponse> responses_; + Vector<Vector<char>> data_; +}; + +TEST(MultipartResponseTest, SkippableLength) { + struct { + const char* input; + const size_t position; + const size_t expected; + } line_tests[] = { + {"Line", 0, 0}, {"Line", 2, 0}, {"Line", 10, 0}, + {"\r\nLine", 0, 2}, {"\nLine", 0, 1}, {"\n\nLine", 0, 1}, + {"\rLine", 0, 0}, {"Line\r\nLine", 4, 2}, {"Line\nLine", 4, 1}, + {"Line\n\nLine", 4, 1}, {"Line\rLine", 4, 0}, {"Line\r\rLine", 4, 0}, + }; + for (size_t i = 0; i < WTF_ARRAY_LENGTH(line_tests); ++i) { + Vector<char> input; + input.Append(line_tests[i].input, strlen(line_tests[i].input)); + EXPECT_EQ(line_tests[i].expected, + MultipartImageResourceParser::SkippableLengthForTest( + input, line_tests[i].position)); + } +} + +TEST(MultipartResponseTest, FindBoundary) { + struct { + const char* boundary; + const char* data; + const size_t position; + } boundary_tests[] = { + {"bound", "bound", 0}, {"bound", "--bound", 0}, + {"bound", "junkbound", 4}, {"bound", "junk--bound", 4}, + {"foo", "bound", kNotFound}, {"bound", "--boundbound", 0}, + }; + + for (size_t i = 0; i < WTF_ARRAY_LENGTH(boundary_tests); ++i) { + Vector<char> boundary, data; + boundary.Append(boundary_tests[i].boundary, + strlen(boundary_tests[i].boundary)); + data.Append(boundary_tests[i].data, strlen(boundary_tests[i].data)); + EXPECT_EQ( + boundary_tests[i].position, + MultipartImageResourceParser::FindBoundaryForTest(data, &boundary)); + } +} + +TEST(MultipartResponseTest, NoStartBoundary) { + ResourceResponse response(NullURL(), "multipart/x-mixed-replace"); + response.SetHTTPHeaderField("Foo", "Bar"); + response.SetHTTPHeaderField("Content-type", "text/plain"); + MockClient* client = new MockClient; + Vector<char> boundary; + boundary.Append("bound", 5); + + MultipartImageResourceParser* parser = + new MultipartImageResourceParser(response, boundary, client); + const char kData[] = + "Content-type: text/plain\n\n" + "This is a sample response\n" + "--bound--" + "ignore junk after end token --bound\n\nTest2\n"; + parser->AppendData(kData, strlen(kData)); + ASSERT_EQ(1u, client->responses_.size()); + ASSERT_EQ(1u, client->data_.size()); + EXPECT_EQ("This is a sample response", ToString(client->data_[0])); + + parser->Finish(); + ASSERT_EQ(1u, client->responses_.size()); + ASSERT_EQ(1u, client->data_.size()); + EXPECT_EQ("This is a sample response", ToString(client->data_[0])); +} + +TEST(MultipartResponseTest, NoEndBoundary) { + ResourceResponse response(NullURL(), "multipart/x-mixed-replace"); + response.SetHTTPHeaderField("Foo", "Bar"); + response.SetHTTPHeaderField("Content-type", "text/plain"); + MockClient* client = new MockClient; + Vector<char> boundary; + boundary.Append("bound", 5); + + MultipartImageResourceParser* parser = + new MultipartImageResourceParser(response, boundary, client); + const char kData[] = + "bound\nContent-type: text/plain\n\n" + "This is a sample response\n"; + parser->AppendData(kData, strlen(kData)); + ASSERT_EQ(1u, client->responses_.size()); + ASSERT_EQ(1u, client->data_.size()); + EXPECT_EQ("This is a sample ", ToString(client->data_[0])); + + parser->Finish(); + ASSERT_EQ(1u, client->responses_.size()); + ASSERT_EQ(1u, client->data_.size()); + EXPECT_EQ("This is a sample response\n", ToString(client->data_[0])); +} + +TEST(MultipartResponseTest, NoStartAndEndBoundary) { + ResourceResponse response(NullURL(), "multipart/x-mixed-replace"); + response.SetHTTPHeaderField("Foo", "Bar"); + response.SetHTTPHeaderField("Content-type", "text/plain"); + MockClient* client = new MockClient; + Vector<char> boundary; + boundary.Append("bound", 5); + + MultipartImageResourceParser* parser = + new MultipartImageResourceParser(response, boundary, client); + const char kData[] = + "Content-type: text/plain\n\n" + "This is a sample response\n"; + parser->AppendData(kData, strlen(kData)); + ASSERT_EQ(1u, client->responses_.size()); + ASSERT_EQ(1u, client->data_.size()); + EXPECT_EQ("This is a sample ", ToString(client->data_[0])); + + parser->Finish(); + ASSERT_EQ(1u, client->responses_.size()); + ASSERT_EQ(1u, client->data_.size()); + EXPECT_EQ("This is a sample response\n", ToString(client->data_[0])); +} + +TEST(MultipartResponseTest, MalformedBoundary) { + // Some servers send a boundary that is prefixed by "--". See bug 5786. + ResourceResponse response(NullURL(), "multipart/x-mixed-replace"); + response.SetHTTPHeaderField("Foo", "Bar"); + response.SetHTTPHeaderField("Content-type", "text/plain"); + MockClient* client = new MockClient; + Vector<char> boundary; + boundary.Append("--bound", 7); + + MultipartImageResourceParser* parser = + new MultipartImageResourceParser(response, boundary, client); + const char kData[] = + "--bound\n" + "Content-type: text/plain\n\n" + "This is a sample response\n" + "--bound--" + "ignore junk after end token --bound\n\nTest2\n"; + parser->AppendData(kData, strlen(kData)); + ASSERT_EQ(1u, client->responses_.size()); + ASSERT_EQ(1u, client->data_.size()); + EXPECT_EQ("This is a sample response", ToString(client->data_[0])); + + parser->Finish(); + ASSERT_EQ(1u, client->responses_.size()); + ASSERT_EQ(1u, client->data_.size()); + EXPECT_EQ("This is a sample response", ToString(client->data_[0])); +} + +// Used in for tests that break the data in various places. +struct TestChunk { + const int start_position; // offset in data + const int end_position; // end offset in data + const size_t expected_responses; + const char* expected_data; +}; + +void VariousChunkSizesTest(const TestChunk chunks[], + int chunks_size, + size_t responses, + int received_data, + const char* completed_data) { + const char kData[] = + "--bound\n" // 0-7 + "Content-type: image/png\n\n" // 8-32 + "datadatadatadatadata" // 33-52 + "--bound\n" // 53-60 + "Content-type: image/jpg\n\n" // 61-85 + "foofoofoofoofoo" // 86-100 + "--bound--"; // 101-109 + + ResourceResponse response(NullURL(), "multipart/x-mixed-replace"); + MockClient* client = new MockClient; + Vector<char> boundary; + boundary.Append("bound", 5); + + MultipartImageResourceParser* parser = + new MultipartImageResourceParser(response, boundary, client); + + for (int i = 0; i < chunks_size; ++i) { + ASSERT_LT(chunks[i].start_position, chunks[i].end_position); + parser->AppendData(kData + chunks[i].start_position, + chunks[i].end_position - chunks[i].start_position); + EXPECT_EQ(chunks[i].expected_responses, client->responses_.size()); + EXPECT_EQ( + String(chunks[i].expected_data), + client->data_.size() > 0 ? ToString(client->data_.back()) : String("")); + } + // Check final state + parser->Finish(); + EXPECT_EQ(responses, client->responses_.size()); + EXPECT_EQ(completed_data, ToString(client->data_.back())); +} + +template <size_t N> +void VariousChunkSizesTest(const TestChunk (&chunks)[N], + size_t responses, + int received_data, + const char* completed_data) { + VariousChunkSizesTest(chunks, N, responses, received_data, completed_data); +} + +TEST(MultipartResponseTest, BreakInBoundary) { + // Break in the first boundary + const TestChunk kBound1[] = { + {0, 4, 0, ""}, {4, 110, 2, "foofoofoofoofoo"}, + }; + VariousChunkSizesTest(kBound1, 2, 2, "foofoofoofoofoo"); + + // Break in first and second + const TestChunk kBound2[] = { + {0, 4, 0, ""}, + {4, 55, 1, "datadatadatad"}, + {55, 65, 1, "datadatadatadatadata"}, + {65, 110, 2, "foofoofoofoofoo"}, + }; + VariousChunkSizesTest(kBound2, 2, 3, "foofoofoofoofoo"); + + // Break in second only + const TestChunk kBound3[] = { + {0, 55, 1, "datadatadatad"}, {55, 110, 2, "foofoofoofoofoo"}, + }; + VariousChunkSizesTest(kBound3, 2, 3, "foofoofoofoofoo"); +} + +TEST(MultipartResponseTest, BreakInHeaders) { + // Break in first header + const TestChunk kHeader1[] = { + {0, 10, 0, ""}, {10, 35, 1, ""}, {35, 110, 2, "foofoofoofoofoo"}, + }; + VariousChunkSizesTest(kHeader1, 2, 2, "foofoofoofoofoo"); + + // Break in both headers + const TestChunk kHeader2[] = { + {0, 10, 0, ""}, + {10, 65, 1, "datadatadatadatadata"}, + {65, 110, 2, "foofoofoofoofoo"}, + }; + VariousChunkSizesTest(kHeader2, 2, 2, "foofoofoofoofoo"); + + // Break at end of a header + const TestChunk kHeader3[] = { + {0, 33, 1, ""}, + {33, 65, 1, "datadatadatadatadata"}, + {65, 110, 2, "foofoofoofoofoo"}, + }; + VariousChunkSizesTest(kHeader3, 2, 2, "foofoofoofoofoo"); +} + +TEST(MultipartResponseTest, BreakInData) { + // All data as one chunk + const TestChunk kData1[] = { + {0, 110, 2, "foofoofoofoofoo"}, + }; + VariousChunkSizesTest(kData1, 2, 2, "foofoofoofoofoo"); + + // breaks in data segment + const TestChunk kData2[] = { + {0, 35, 1, ""}, + {35, 65, 1, "datadatadatadatadata"}, + {65, 90, 2, ""}, + {90, 110, 2, "foofoofoofoofoo"}, + }; + VariousChunkSizesTest(kData2, 2, 2, "foofoofoofoofoo"); + + // Incomplete send + const TestChunk kData3[] = { + {0, 35, 1, ""}, {35, 90, 2, ""}, + }; + VariousChunkSizesTest(kData3, 2, 2, "foof"); +} + +TEST(MultipartResponseTest, SmallChunk) { + ResourceResponse response(NullURL(), "multipart/x-mixed-replace"); + response.SetHTTPHeaderField("Content-type", "text/plain"); + MockClient* client = new MockClient; + Vector<char> boundary; + boundary.Append("bound", 5); + + MultipartImageResourceParser* parser = + new MultipartImageResourceParser(response, boundary, client); + + // Test chunks of size 1, 2, and 0. + const char kData[] = + "--boundContent-type: text/plain\n\n" + "\n--boundContent-type: text/plain\n\n" + "\n\n--boundContent-type: text/plain\n\n" + "--boundContent-type: text/plain\n\n" + "end--bound--"; + parser->AppendData(kData, strlen(kData)); + ASSERT_EQ(4u, client->responses_.size()); + ASSERT_EQ(4u, client->data_.size()); + EXPECT_EQ("", ToString(client->data_[0])); + EXPECT_EQ("\n", ToString(client->data_[1])); + EXPECT_EQ("", ToString(client->data_[2])); + EXPECT_EQ("end", ToString(client->data_[3])); + + parser->Finish(); + ASSERT_EQ(4u, client->responses_.size()); + ASSERT_EQ(4u, client->data_.size()); + EXPECT_EQ("", ToString(client->data_[0])); + EXPECT_EQ("\n", ToString(client->data_[1])); + EXPECT_EQ("", ToString(client->data_[2])); + EXPECT_EQ("end", ToString(client->data_[3])); +} + +TEST(MultipartResponseTest, MultipleBoundaries) { + // Test multiple boundaries back to back + ResourceResponse response(NullURL(), "multipart/x-mixed-replace"); + MockClient* client = new MockClient; + Vector<char> boundary; + boundary.Append("bound", 5); + + MultipartImageResourceParser* parser = + new MultipartImageResourceParser(response, boundary, client); + + const char kData[] = "--bound\r\n\r\n--bound\r\n\r\nfoofoo--bound--"; + parser->AppendData(kData, strlen(kData)); + ASSERT_EQ(2u, client->responses_.size()); + ASSERT_EQ(2u, client->data_.size()); + EXPECT_EQ("", ToString(client->data_[0])); + EXPECT_EQ("foofoo", ToString(client->data_[1])); +} + +TEST(MultipartResponseTest, EatLeadingLF) { + ResourceResponse response(NullURL(), "multipart/x-mixed-replace"); + MockClient* client = new MockClient; + Vector<char> boundary; + boundary.Append("bound", 5); + + const char kData[] = + "\n\n\n--bound\n\n\ncontent-type: 1\n\n" + "\n\n\n--bound\n\ncontent-type: 2\n\n" + "\n\n\n--bound\ncontent-type: 3\n\n"; + MultipartImageResourceParser* parser = + new MultipartImageResourceParser(response, boundary, client); + + for (size_t i = 0; i < strlen(kData); ++i) + parser->AppendData(&kData[i], 1); + parser->Finish(); + + ASSERT_EQ(4u, client->responses_.size()); + ASSERT_EQ(4u, client->data_.size()); + EXPECT_EQ(String(), client->responses_[0].HttpHeaderField("content-type")); + EXPECT_EQ("", ToString(client->data_[0])); + EXPECT_EQ(String(), client->responses_[1].HttpHeaderField("content-type")); + EXPECT_EQ("\ncontent-type: 1\n\n\n\n", ToString(client->data_[1])); + EXPECT_EQ(String(), client->responses_[2].HttpHeaderField("content-type")); + EXPECT_EQ("content-type: 2\n\n\n\n", ToString(client->data_[2])); + EXPECT_EQ("3", client->responses_[3].HttpHeaderField("content-type")); + EXPECT_EQ("", ToString(client->data_[3])); +} + +TEST(MultipartResponseTest, EatLeadingCRLF) { + ResourceResponse response(NullURL(), "multipart/x-mixed-replace"); + MockClient* client = new MockClient; + Vector<char> boundary; + boundary.Append("bound", 5); + + const char kData[] = + "\r\n\r\n\r\n--bound\r\n\r\n\r\ncontent-type: 1\r\n\r\n" + "\r\n\r\n\r\n--bound\r\n\r\ncontent-type: 2\r\n\r\n" + "\r\n\r\n\r\n--bound\r\ncontent-type: 3\r\n\r\n"; + MultipartImageResourceParser* parser = + new MultipartImageResourceParser(response, boundary, client); + + for (size_t i = 0; i < strlen(kData); ++i) + parser->AppendData(&kData[i], 1); + parser->Finish(); + + ASSERT_EQ(4u, client->responses_.size()); + ASSERT_EQ(4u, client->data_.size()); + EXPECT_EQ(String(), client->responses_[0].HttpHeaderField("content-type")); + EXPECT_EQ("", ToString(client->data_[0])); + EXPECT_EQ(String(), client->responses_[1].HttpHeaderField("content-type")); + EXPECT_EQ("\r\ncontent-type: 1\r\n\r\n\r\n\r\n", ToString(client->data_[1])); + EXPECT_EQ(String(), client->responses_[2].HttpHeaderField("content-type")); + EXPECT_EQ("content-type: 2\r\n\r\n\r\n\r\n", ToString(client->data_[2])); + EXPECT_EQ("3", client->responses_[3].HttpHeaderField("content-type")); + EXPECT_EQ("", ToString(client->data_[3])); +} + +} // namespace multipart_image_resource_parser_test +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/loader/resource/script_resource.cc b/chromium/third_party/blink/renderer/core/loader/resource/script_resource.cc new file mode 100644 index 00000000000..91c831fed3a --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/resource/script_resource.cc @@ -0,0 +1,239 @@ +/* + Copyright (C) 1998 Lars Knoll (knoll@mpi-hd.mpg.de) + Copyright (C) 2001 Dirk Mueller (mueller@kde.org) + Copyright (C) 2002 Waldo Bastian (bastian@kde.org) + Copyright (C) 2006 Samuel Weinig (sam.weinig@gmail.com) + Copyright (C) 2004, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. + + This class provides all functionality needed for loading images, style + sheets and html pages from the web. It has a memory cache for these objects. +*/ + +#include "third_party/blink/renderer/core/loader/resource/script_resource.h" + +#include "services/network/public/mojom/request_context_frame_type.mojom-blink.h" +#include "third_party/blink/renderer/core/dom/document.h" +#include "third_party/blink/renderer/core/loader/subresource_integrity_helper.h" +#include "third_party/blink/renderer/platform/instrumentation/tracing/web_memory_allocator_dump.h" +#include "third_party/blink/renderer/platform/instrumentation/tracing/web_process_memory_dump.h" +#include "third_party/blink/renderer/platform/loader/fetch/cached_metadata.h" +#include "third_party/blink/renderer/platform/loader/fetch/fetch_parameters.h" +#include "third_party/blink/renderer/platform/loader/fetch/integrity_metadata.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource_client_walker.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h" +#include "third_party/blink/renderer/platform/loader/fetch/text_resource_decoder_options.h" +#include "third_party/blink/renderer/platform/loader/subresource_integrity.h" +#include "third_party/blink/renderer/platform/network/mime/mime_type_registry.h" +#include "third_party/blink/renderer/platform/shared_buffer.h" + +namespace blink { + +// SingleCachedMetadataHandlerImpl should be created when a response is +// received, and can be used independently from Resource. - It doesn't have any +// references to Resource. Necessary data are captured +// from Resource when the handler is created. +// - It is not affected by Resource's revalidation on MemoryCache. +// The validity of the handler is solely checked by |response_url_| and +// |response_time_| (not by Resource) by the browser process, and the cached +// metadata written to the handler is rejected if e.g. the disk cache entry +// has been updated and the handler refers to an older response. +class ScriptResource::SingleCachedMetadataHandlerImpl final + : public SingleCachedMetadataHandler { + public: + SingleCachedMetadataHandlerImpl(const WTF::TextEncoding&, + std::unique_ptr<CachedMetadataSender>); + ~SingleCachedMetadataHandlerImpl() override = default; + void Trace(blink::Visitor*) override; + void SetCachedMetadata(uint32_t, const char*, size_t, CacheType) override; + void ClearCachedMetadata(CacheType) override; + scoped_refptr<CachedMetadata> GetCachedMetadata(uint32_t) const override; + + // This returns the encoding at the time of ResponseReceived(). + // Therefore this does NOT reflect encoding detection from body contents, + // but the final encoding after the encoding detection can be determined + // uniquely from Encoding(), provided the body content is the same, + // as we can assume the encoding detection will results in the same final + // encoding. + // TODO(hiroshige): Make this semantics cleaner. + String Encoding() const override { return String(encoding_.GetName()); } + + bool IsServedFromCacheStorage() const override { + return sender_->IsServedFromCacheStorage(); + } + + // Sets the serialized metadata retrieved from the platform's cache. + void SetSerializedCachedMetadata(const char*, size_t); + + private: + void SendToPlatform(); + + scoped_refptr<CachedMetadata> cached_metadata_; + std::unique_ptr<CachedMetadataSender> sender_; + + const WTF::TextEncoding encoding_; +}; + +ScriptResource::SingleCachedMetadataHandlerImpl:: + SingleCachedMetadataHandlerImpl( + const WTF::TextEncoding& encoding, + std::unique_ptr<CachedMetadataSender> sender) + : sender_(std::move(sender)), encoding_(encoding) {} + +void ScriptResource::SingleCachedMetadataHandlerImpl::Trace( + blink::Visitor* visitor) { + CachedMetadataHandler::Trace(visitor); +} + +void ScriptResource::SingleCachedMetadataHandlerImpl::SetCachedMetadata( + uint32_t data_type_id, + const char* data, + size_t size, + CachedMetadataHandler::CacheType cache_type) { + // Currently, only one type of cached metadata per resource is supported. If + // the need arises for multiple types of metadata per resource this could be + // enhanced to store types of metadata in a map. + DCHECK(!cached_metadata_); + cached_metadata_ = CachedMetadata::Create(data_type_id, data, size); + if (cache_type == CachedMetadataHandler::kSendToPlatform) + SendToPlatform(); +} + +void ScriptResource::SingleCachedMetadataHandlerImpl::ClearCachedMetadata( + CachedMetadataHandler::CacheType cache_type) { + cached_metadata_ = nullptr; + if (cache_type == CachedMetadataHandler::kSendToPlatform) + SendToPlatform(); +} + +scoped_refptr<CachedMetadata> +ScriptResource::SingleCachedMetadataHandlerImpl::GetCachedMetadata( + uint32_t data_type_id) const { + if (!cached_metadata_ || cached_metadata_->DataTypeID() != data_type_id) + return nullptr; + return cached_metadata_; +} + +void ScriptResource::SingleCachedMetadataHandlerImpl:: + SetSerializedCachedMetadata(const char* data, size_t size) { + // We only expect to receive cached metadata from the platform once. If this + // triggers, it indicates an efficiency problem which is most likely + // unexpected in code designed to improve performance. + DCHECK(!cached_metadata_); + cached_metadata_ = CachedMetadata::CreateFromSerializedData(data, size); +} + +void ScriptResource::SingleCachedMetadataHandlerImpl::SendToPlatform() { + if (cached_metadata_) { + const Vector<char>& serialized_data = cached_metadata_->SerializedData(); + sender_->Send(serialized_data.data(), serialized_data.size()); + } else { + sender_->Send(nullptr, 0); + } +} + +ScriptResource* ScriptResource::Fetch(FetchParameters& params, + ResourceFetcher* fetcher, + ResourceClient* client) { + DCHECK_EQ(params.GetResourceRequest().GetFrameType(), + network::mojom::RequestContextFrameType::kNone); + params.SetRequestContext(WebURLRequest::kRequestContextScript); + return ToScriptResource( + fetcher->RequestResource(params, ScriptResourceFactory(), client)); +} + +ScriptResource::ScriptResource( + const ResourceRequest& resource_request, + const ResourceLoaderOptions& options, + const TextResourceDecoderOptions& decoder_options) + : TextResource(resource_request, kScript, options, decoder_options) {} + +ScriptResource::~ScriptResource() = default; + +void ScriptResource::OnMemoryDump(WebMemoryDumpLevelOfDetail level_of_detail, + WebProcessMemoryDump* memory_dump) const { + Resource::OnMemoryDump(level_of_detail, memory_dump); + const String name = GetMemoryDumpName() + "/decoded_script"; + auto* dump = memory_dump->CreateMemoryAllocatorDump(name); + dump->AddScalar("size", "bytes", source_text_.CharactersSizeInBytes()); + memory_dump->AddSuballocation( + dump->Guid(), String(WTF::Partitions::kAllocatedObjectPoolName)); +} + +const String& ScriptResource::SourceText() { + DCHECK(IsLoaded()); + + if (source_text_.IsNull() && Data()) { + String source_text = DecodedText(); + ClearData(); + SetDecodedSize(source_text.CharactersSizeInBytes()); + source_text_ = AtomicString(source_text); + } + + return source_text_; +} + +SingleCachedMetadataHandler* ScriptResource::CacheHandler() { + return static_cast<SingleCachedMetadataHandler*>(Resource::CacheHandler()); +} + +CachedMetadataHandler* ScriptResource::CreateCachedMetadataHandler( + std::unique_ptr<CachedMetadataSender> send_callback) { + return new SingleCachedMetadataHandlerImpl(Encoding(), + std::move(send_callback)); +} + +void ScriptResource::SetSerializedCachedMetadata(const char* data, + size_t size) { + Resource::SetSerializedCachedMetadata(data, size); + SingleCachedMetadataHandlerImpl* cache_handler = + static_cast<SingleCachedMetadataHandlerImpl*>(Resource::CacheHandler()); + if (cache_handler) { + cache_handler->SetSerializedCachedMetadata(data, size); + } +} + +void ScriptResource::DestroyDecodedDataForFailedRevalidation() { + source_text_ = AtomicString(); + SetDecodedSize(0); +} + +AccessControlStatus ScriptResource::CalculateAccessControlStatus( + const SecurityOrigin* security_origin) const { + if (GetResponse().WasFetchedViaServiceWorker()) { + if (GetCORSStatus() == CORSStatus::kServiceWorkerOpaque) + return kOpaqueResource; + return kSharableCrossOrigin; + } + + if (security_origin && PassesAccessControlCheck(*security_origin)) + return kSharableCrossOrigin; + + return kNotSharableCrossOrigin; +} + +bool ScriptResource::CanUseCacheValidator() const { + // Do not revalidate until ClassicPendingScript is removed, i.e. the script + // content is retrieved in ScriptLoader::ExecuteScriptBlock(). + // crbug.com/692856 + if (HasClientsOrObservers()) + return false; + + return Resource::CanUseCacheValidator(); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/loader/resource/script_resource.h b/chromium/third_party/blink/renderer/core/loader/resource/script_resource.h new file mode 100644 index 00000000000..9d838a06105 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/resource/script_resource.h @@ -0,0 +1,111 @@ +/* + Copyright (C) 1998 Lars Knoll (knoll@mpi-hd.mpg.de) + Copyright (C) 2001 Dirk Mueller <mueller@kde.org> + Copyright (C) 2006 Samuel Weinig (sam.weinig@gmail.com) + Copyright (C) 2004, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. + + This class provides all functionality needed for loading images, style + sheets and html pages from the web. It has a memory cache for these objects. +*/ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_RESOURCE_SCRIPT_RESOURCE_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_RESOURCE_SCRIPT_RESOURCE_H_ + +#include "third_party/blink/renderer/core/core_export.h" +#include "third_party/blink/renderer/core/loader/resource/text_resource.h" +#include "third_party/blink/renderer/platform/loader/fetch/access_control_status.h" +#include "third_party/blink/renderer/platform/loader/fetch/integrity_metadata.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource_client.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource_loader_options.h" +#include "third_party/blink/renderer/platform/loader/fetch/text_resource_decoder_options.h" + +namespace blink { + +class FetchParameters; +class KURL; +class ResourceFetcher; +class ScriptResource; + +class CORE_EXPORT ScriptResource final : public TextResource { + public: + static ScriptResource* Fetch(FetchParameters&, + ResourceFetcher*, + ResourceClient*); + + // Public for testing + static ScriptResource* CreateForTest(const KURL& url, + const WTF::TextEncoding& encoding) { + ResourceRequest request(url); + request.SetFetchCredentialsMode( + network::mojom::FetchCredentialsMode::kOmit); + ResourceLoaderOptions options; + TextResourceDecoderOptions decoder_options( + TextResourceDecoderOptions::kPlainTextContent, encoding); + return new ScriptResource(request, options, decoder_options); + } + + ~ScriptResource() override; + + void OnMemoryDump(WebMemoryDumpLevelOfDetail, + WebProcessMemoryDump*) const override; + + void DestroyDecodedDataForFailedRevalidation() override; + + void SetSerializedCachedMetadata(const char*, size_t) override; + + const String& SourceText(); + + AccessControlStatus CalculateAccessControlStatus(const SecurityOrigin*) const; + + SingleCachedMetadataHandler* CacheHandler(); + + protected: + CachedMetadataHandler* CreateCachedMetadataHandler( + std::unique_ptr<CachedMetadataSender> send_callback) override; + + private: + class SingleCachedMetadataHandlerImpl; + + class ScriptResourceFactory : public ResourceFactory { + public: + ScriptResourceFactory() + : ResourceFactory(Resource::kScript, + TextResourceDecoderOptions::kPlainTextContent) {} + + Resource* Create( + const ResourceRequest& request, + const ResourceLoaderOptions& options, + const TextResourceDecoderOptions& decoder_options) const override { + return new ScriptResource(request, options, decoder_options); + } + }; + + ScriptResource(const ResourceRequest&, + const ResourceLoaderOptions&, + const TextResourceDecoderOptions&); + + bool CanUseCacheValidator() const override; + + AtomicString source_text_; +}; + +DEFINE_RESOURCE_TYPE_CASTS(Script); + +} // namespace blink + +#endif diff --git a/chromium/third_party/blink/renderer/core/loader/resource/text_resource.cc b/chromium/third_party/blink/renderer/core/loader/resource/text_resource.cc new file mode 100644 index 00000000000..1ce9c9e53f7 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/resource/text_resource.cc @@ -0,0 +1,46 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "third_party/blink/renderer/core/loader/resource/text_resource.h" + +#include "third_party/blink/renderer/core/html/parser/text_resource_decoder.h" +#include "third_party/blink/renderer/platform/loader/fetch/text_resource_decoder_options.h" +#include "third_party/blink/renderer/platform/shared_buffer.h" +#include "third_party/blink/renderer/platform/wtf/text/string_builder.h" + +namespace blink { + +TextResource::TextResource(const ResourceRequest& resource_request, + Resource::Type type, + const ResourceLoaderOptions& options, + const TextResourceDecoderOptions& decoder_options) + : Resource(resource_request, type, options), + decoder_(TextResourceDecoder::Create(decoder_options)) {} + +TextResource::~TextResource() = default; + +void TextResource::SetEncoding(const String& chs) { + decoder_->SetEncoding(WTF::TextEncoding(chs), + TextResourceDecoder::kEncodingFromHTTPHeader); +} + +WTF::TextEncoding TextResource::Encoding() const { + return decoder_->Encoding(); +} + +String TextResource::DecodedText() const { + DCHECK(Data()); + + StringBuilder builder; + const char* segment; + size_t position = 0; + while (size_t length = Data()->GetSomeData(segment, position)) { + builder.Append(decoder_->Decode(segment, length)); + position += length; + } + builder.Append(decoder_->Flush()); + return builder.ToString(); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/loader/resource/text_resource.h b/chromium/third_party/blink/renderer/core/loader/resource/text_resource.h new file mode 100644 index 00000000000..467619cf1a9 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/resource/text_resource.h @@ -0,0 +1,41 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_RESOURCE_TEXT_RESOURCE_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_RESOURCE_TEXT_RESOURCE_H_ + +#include <memory> +#include "third_party/blink/renderer/core/core_export.h" +#include "third_party/blink/renderer/core/html/parser/text_resource_decoder.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource.h" +#include "third_party/blink/renderer/platform/loader/fetch/text_resource_decoder_options.h" + +namespace blink { + +class CORE_EXPORT TextResource : public Resource { + public: + // Returns the decoded data in text form. The data has to be available at + // call time. + String DecodedText() const; + + WTF::TextEncoding Encoding() const override; + + void SetEncodingForTest(const String& encoding) { SetEncoding(encoding); } + + protected: + TextResource(const ResourceRequest&, + Type, + const ResourceLoaderOptions&, + const TextResourceDecoderOptions&); + ~TextResource() override; + + void SetEncoding(const String&) override; + + private: + std::unique_ptr<TextResourceDecoder> decoder_; +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_RESOURCE_TEXT_RESOURCE_H_ diff --git a/chromium/third_party/blink/renderer/core/loader/resource/xsl_style_sheet_resource.cc b/chromium/third_party/blink/renderer/core/loader/resource/xsl_style_sheet_resource.cc new file mode 100644 index 00000000000..c3b376e017c --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/resource/xsl_style_sheet_resource.cc @@ -0,0 +1,84 @@ +/* + Copyright (C) 1998 Lars Knoll (knoll@mpi-hd.mpg.de) + Copyright (C) 2001 Dirk Mueller (mueller@kde.org) + Copyright (C) 2002 Waldo Bastian (bastian@kde.org) + Copyright (C) 2006 Samuel Weinig (sam.weinig@gmail.com) + Copyright (C) 2004, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. + + This class provides all functionality needed for loading images, style + sheets and html pages from the web. It has a memory cache for these objects. +*/ + +#include "third_party/blink/renderer/core/loader/resource/xsl_style_sheet_resource.h" + +#include "third_party/blink/renderer/platform/loader/fetch/fetch_parameters.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h" +#include "third_party/blink/renderer/platform/loader/fetch/text_resource_decoder_options.h" +#include "third_party/blink/renderer/platform/runtime_enabled_features.h" + +namespace blink { + +static void ApplyXSLRequestProperties(FetchParameters& params) { + params.SetRequestContext(WebURLRequest::kRequestContextXSLT); + // TODO(japhet): Accept: headers can be set manually on XHRs from script, in + // the browser process, and... here. The browser process can't tell the + // difference between an XSL stylesheet and a CSS stylesheet, so it assumes + // stylesheets are all CSS unless they already have an Accept: header set. + // Should we teach the browser process the difference? + DEFINE_STATIC_LOCAL(const AtomicString, accept_xslt, + ("text/xml, application/xml, application/xhtml+xml, " + "text/xsl, application/rss+xml, application/atom+xml")); + params.MutableResourceRequest().SetHTTPAccept(accept_xslt); +} + +XSLStyleSheetResource* XSLStyleSheetResource::FetchSynchronously( + FetchParameters& params, + ResourceFetcher* fetcher) { + ApplyXSLRequestProperties(params); + params.MakeSynchronous(); + XSLStyleSheetResource* resource = + ToXSLStyleSheetResource(fetcher->RequestResource( + params, XSLStyleSheetResourceFactory(), nullptr)); + if (resource->Data()) + resource->sheet_ = resource->DecodedText(); + return resource; +} + +XSLStyleSheetResource* XSLStyleSheetResource::Fetch(FetchParameters& params, + ResourceFetcher* fetcher, + ResourceClient* client) { + DCHECK(RuntimeEnabledFeatures::XSLTEnabled()); + ApplyXSLRequestProperties(params); + return ToXSLStyleSheetResource( + fetcher->RequestResource(params, XSLStyleSheetResourceFactory(), client)); +} + +XSLStyleSheetResource::XSLStyleSheetResource( + const ResourceRequest& resource_request, + const ResourceLoaderOptions& options, + const TextResourceDecoderOptions& decoder_options) + : TextResource(resource_request, kXSLStyleSheet, options, decoder_options) { +} + +void XSLStyleSheetResource::NotifyFinished() { + if (Data()) + sheet_ = DecodedText(); + Resource::NotifyFinished(); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/loader/resource/xsl_style_sheet_resource.h b/chromium/third_party/blink/renderer/core/loader/resource/xsl_style_sheet_resource.h new file mode 100644 index 00000000000..8e5a869d50a --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/resource/xsl_style_sheet_resource.h @@ -0,0 +1,74 @@ +/* + Copyright (C) 1998 Lars Knoll (knoll@mpi-hd.mpg.de) + Copyright (C) 2001 Dirk Mueller <mueller@kde.org> + Copyright (C) 2006 Samuel Weinig (sam.weinig@gmail.com) + Copyright (C) 2004, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. + + This class provides all functionality needed for loading images, style + sheets and html pages from the web. It has a memory cache for these objects. +*/ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_RESOURCE_XSL_STYLE_SHEET_RESOURCE_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_RESOURCE_XSL_STYLE_SHEET_RESOURCE_H_ + +#include "third_party/blink/renderer/core/loader/resource/text_resource.h" +#include "third_party/blink/renderer/platform/loader/fetch/text_resource_decoder_options.h" + +namespace blink { + +class FetchParameters; +class ResourceFetcher; + +class XSLStyleSheetResource final : public TextResource { + public: + static XSLStyleSheetResource* FetchSynchronously(FetchParameters&, + ResourceFetcher*); + static XSLStyleSheetResource* Fetch(FetchParameters&, + ResourceFetcher*, + ResourceClient*); + + const String& Sheet() const { return sheet_; } + + private: + class XSLStyleSheetResourceFactory : public ResourceFactory { + public: + XSLStyleSheetResourceFactory() + : ResourceFactory(Resource::kXSLStyleSheet, + TextResourceDecoderOptions::kXMLContent) {} + + Resource* Create( + const ResourceRequest& request, + const ResourceLoaderOptions& options, + const TextResourceDecoderOptions& decoder_options) const override { + return new XSLStyleSheetResource(request, options, decoder_options); + } + }; + XSLStyleSheetResource(const ResourceRequest&, + const ResourceLoaderOptions&, + const TextResourceDecoderOptions&); + + void NotifyFinished() override; + + String sheet_; +}; + +DEFINE_RESOURCE_TYPE_CASTS(XSLStyleSheet); + +} // namespace blink + +#endif |