summaryrefslogtreecommitdiff
path: root/chromium/third_party/blink/renderer/core/loader/resource
diff options
context:
space:
mode:
authorAllan Sandfeld Jensen <allan.jensen@qt.io>2018-05-15 10:20:33 +0200
committerAllan Sandfeld Jensen <allan.jensen@qt.io>2018-05-15 10:28:57 +0000
commitd17ea114e5ef69ad5d5d7413280a13e6428098aa (patch)
tree2c01a75df69f30d27b1432467cfe7c1467a498da /chromium/third_party/blink/renderer/core/loader/resource
parent8c5c43c7b138c9b4b0bf56d946e61d3bbc111bec (diff)
downloadqtwebengine-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')
-rw-r--r--chromium/third_party/blink/renderer/core/loader/resource/css_style_sheet_resource.cc256
-rw-r--r--chromium/third_party/blink/renderer/core/loader/resource/css_style_sheet_resource.h101
-rw-r--r--chromium/third_party/blink/renderer/core/loader/resource/css_style_sheet_resource_test.cc178
-rw-r--r--chromium/third_party/blink/renderer/core/loader/resource/document_resource.cc92
-rw-r--r--chromium/third_party/blink/renderer/core/loader/resource/document_resource.h85
-rw-r--r--chromium/third_party/blink/renderer/core/loader/resource/font_resource.cc243
-rw-r--r--chromium/third_party/blink/renderer/core/loader/resource/font_resource.h138
-rw-r--r--chromium/third_party/blink/renderer/core/loader/resource/font_resource_test.cc167
-rw-r--r--chromium/third_party/blink/renderer/core/loader/resource/image_resource.cc730
-rw-r--r--chromium/third_party/blink/renderer/core/loader/resource/image_resource.h192
-rw-r--r--chromium/third_party/blink/renderer/core/loader/resource/image_resource_content.cc639
-rw-r--r--chromium/third_party/blink/renderer/core/loader/resource/image_resource_content.h236
-rw-r--r--chromium/third_party/blink/renderer/core/loader/resource/image_resource_info.h66
-rw-r--r--chromium/third_party/blink/renderer/core/loader/resource/image_resource_observer.h95
-rw-r--r--chromium/third_party/blink/renderer/core/loader/resource/image_resource_test.cc2016
-rw-r--r--chromium/third_party/blink/renderer/core/loader/resource/link_fetch_resource.cc29
-rw-r--r--chromium/third_party/blink/renderer/core/loader/resource/link_fetch_resource.h37
-rw-r--r--chromium/third_party/blink/renderer/core/loader/resource/mock_font_resource_client.cc29
-rw-r--r--chromium/third_party/blink/renderer/core/loader/resource/mock_font_resource_client.h44
-rw-r--r--chromium/third_party/blink/renderer/core/loader/resource/mock_image_resource_observer.cc58
-rw-r--r--chromium/third_party/blink/renderer/core/loader/resource/mock_image_resource_observer.h64
-rw-r--r--chromium/third_party/blink/renderer/core/loader/resource/multipart_image_resource_parser.cc190
-rw-r--r--chromium/third_party/blink/renderer/core/loader/resource/multipart_image_resource_parser.h103
-rw-r--r--chromium/third_party/blink/renderer/core/loader/resource/multipart_image_resource_parser_test.cc418
-rw-r--r--chromium/third_party/blink/renderer/core/loader/resource/script_resource.cc239
-rw-r--r--chromium/third_party/blink/renderer/core/loader/resource/script_resource.h111
-rw-r--r--chromium/third_party/blink/renderer/core/loader/resource/text_resource.cc46
-rw-r--r--chromium/third_party/blink/renderer/core/loader/resource/text_resource.h41
-rw-r--r--chromium/third_party/blink/renderer/core/loader/resource/xsl_style_sheet_resource.cc84
-rw-r--r--chromium/third_party/blink/renderer/core/loader/resource/xsl_style_sheet_resource.h74
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