diff options
Diffstat (limited to 'chromium/third_party/blink/renderer/core/loader')
147 files changed, 35007 insertions, 0 deletions
diff --git a/chromium/third_party/blink/renderer/core/loader/BUILD.gn b/chromium/third_party/blink/renderer/core/loader/BUILD.gn new file mode 100644 index 00000000000..82f6e8bc9d3 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/BUILD.gn @@ -0,0 +1,132 @@ +# 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. + +import("//third_party/blink/renderer/core/core.gni") + +blink_core_sources("loader") { + sources = [ + "allowed_by_nosniff.cc", + "allowed_by_nosniff.h", + "appcache/application_cache.cc", + "appcache/application_cache.h", + "appcache/application_cache_host.cc", + "appcache/application_cache_host.h", + "base_fetch_context.cc", + "base_fetch_context.h", + "cookie_jar.cc", + "cookie_jar.h", + "document_load_timing.cc", + "document_load_timing.h", + "document_loader.cc", + "document_loader.h", + "document_threadable_loader.cc", + "document_threadable_loader.h", + "document_threadable_loader_client.h", + "empty_clients.cc", + "empty_clients.h", + "form_submission.cc", + "form_submission.h", + "frame_fetch_context.cc", + "frame_fetch_context.h", + "frame_load_request.cc", + "frame_load_request.h", + "frame_loader.cc", + "frame_loader.h", + "frame_loader_state_machine.cc", + "frame_loader_state_machine.h", + "frame_loader_types.h", + "history_item.cc", + "history_item.h", + "http_equiv.cc", + "http_equiv.h", + "idleness_detector.cc", + "idleness_detector.h", + "image_loader.cc", + "image_loader.h", + "interactive_detector.cc", + "interactive_detector.h", + "link_loader.cc", + "link_loader.h", + "link_loader_client.h", + "mixed_content_checker.cc", + "mixed_content_checker.h", + "modulescript/document_module_script_fetcher.cc", + "modulescript/document_module_script_fetcher.h", + "modulescript/module_script_creation_params.h", + "modulescript/module_script_fetch_request.h", + "modulescript/module_script_fetcher.cc", + "modulescript/module_script_fetcher.h", + "modulescript/module_script_loader.cc", + "modulescript/module_script_loader.h", + "modulescript/module_script_loader_client.h", + "modulescript/module_script_loader_registry.cc", + "modulescript/module_script_loader_registry.h", + "modulescript/module_tree_linker.cc", + "modulescript/module_tree_linker.h", + "modulescript/module_tree_linker_registry.cc", + "modulescript/module_tree_linker_registry.h", + "modulescript/worker_or_worklet_module_script_fetcher.cc", + "modulescript/worker_or_worklet_module_script_fetcher.h", + "navigation_policy.cc", + "navigation_policy.h", + "navigation_scheduler.cc", + "navigation_scheduler.h", + "network_hints_interface.h", + "ping_loader.cc", + "ping_loader.h", + "prerenderer_client.cc", + "prerenderer_client.h", + "private/frame_client_hints_preferences_context.cc", + "private/frame_client_hints_preferences_context.h", + "private/prerender_handle.cc", + "private/prerender_handle.h", + "progress_tracker.cc", + "progress_tracker.h", + "resource/css_style_sheet_resource.cc", + "resource/css_style_sheet_resource.h", + "resource/document_resource.cc", + "resource/document_resource.h", + "resource/font_resource.cc", + "resource/font_resource.h", + "resource/image_resource.cc", + "resource/image_resource.h", + "resource/image_resource_content.cc", + "resource/image_resource_content.h", + "resource/image_resource_info.h", + "resource/image_resource_observer.h", + "resource/link_fetch_resource.cc", + "resource/link_fetch_resource.h", + "resource/multipart_image_resource_parser.cc", + "resource/multipart_image_resource_parser.h", + "resource/script_resource.cc", + "resource/script_resource.h", + "resource/text_resource.cc", + "resource/text_resource.h", + "resource/xsl_style_sheet_resource.cc", + "resource/xsl_style_sheet_resource.h", + "scheduled_navigation.cc", + "scheduled_navigation.h", + "subresource_filter.cc", + "subresource_filter.h", + "subresource_integrity_helper.cc", + "subresource_integrity_helper.h", + "text_resource_decoder_builder.cc", + "text_resource_decoder_builder.h", + "text_track_loader.cc", + "text_track_loader.h", + "threadable_loader.cc", + "threadable_loader.h", + "threadable_loader_client.h", + "threadable_loading_context.cc", + "threadable_loading_context.h", + "worker_fetch_context.cc", + "worker_fetch_context.h", + "worker_threadable_loader.cc", + "worker_threadable_loader.h", + ] + + public_deps = [ + "//third_party/blink/renderer/platform", + ] +} diff --git a/chromium/third_party/blink/renderer/core/loader/OWNERS b/chromium/third_party/blink/renderer/core/loader/OWNERS new file mode 100644 index 00000000000..b875f73196a --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/OWNERS @@ -0,0 +1,6 @@ +japhet@chromium.org +mkwst@chromium.org +yhirano@chromium.org + +# TEAM: loading-dev@chromium.org +# COMPONENT: Blink>Loader diff --git a/chromium/third_party/blink/renderer/core/loader/README.md b/chromium/third_party/blink/renderer/core/loader/README.md new file mode 100644 index 00000000000..649540664c4 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/README.md @@ -0,0 +1,6 @@ +High-level fetching code. + +Fetching/loading code is divided into: +- core/fetch: Fetch API +- core/loader: high-level fetching +- platform/loader/fetch: low-level fetching diff --git a/chromium/third_party/blink/renderer/core/loader/allowed_by_nosniff.cc b/chromium/third_party/blink/renderer/core/loader/allowed_by_nosniff.cc new file mode 100644 index 00000000000..5255c3cb131 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/allowed_by_nosniff.cc @@ -0,0 +1,207 @@ +// Copyright 2017 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/allowed_by_nosniff.h" + +#include "third_party/blink/renderer/core/execution_context/execution_context.h" +#include "third_party/blink/renderer/core/frame/use_counter.h" +#include "third_party/blink/renderer/core/inspector/console_message.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource_response.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_origin.h" + +namespace blink { + +namespace { + +// In addition to makeing an allowed/not-allowed decision, +// AllowedByNosniff::MimeTypeAsScript reports common usage patterns to support +// future decisions about which types can be safely be disallowed. Below +// is a number of constants about which use counters to report. + +const WebFeature kApplicationFeatures[2] = { + WebFeature::kCrossOriginApplicationScript, + WebFeature::kSameOriginApplicationScript}; + +const WebFeature kTextFeatures[2] = {WebFeature::kCrossOriginTextScript, + WebFeature::kSameOriginTextScript}; + +const WebFeature kApplicationOctetStreamFeatures[2][2] = { + {WebFeature::kCrossOriginApplicationOctetStream, + WebFeature::kCrossOriginWorkerApplicationOctetStream}, + {WebFeature::kSameOriginApplicationOctetStream, + WebFeature::kSameOriginWorkerApplicationOctetStream}}; + +const WebFeature kApplicationXmlFeatures[2][2] = { + {WebFeature::kCrossOriginApplicationXml, + WebFeature::kCrossOriginWorkerApplicationXml}, + {WebFeature::kSameOriginApplicationXml, + WebFeature::kSameOriginWorkerApplicationXml}}; + +const WebFeature kTextHtmlFeatures[2][2] = { + {WebFeature::kCrossOriginTextHtml, WebFeature::kCrossOriginWorkerTextHtml}, + {WebFeature::kSameOriginTextHtml, WebFeature::kSameOriginWorkerTextHtml}}; + +const WebFeature kTextPlainFeatures[2][2] = { + {WebFeature::kCrossOriginTextPlain, + WebFeature::kCrossOriginWorkerTextPlain}, + {WebFeature::kSameOriginTextPlain, WebFeature::kSameOriginWorkerTextPlain}}; + +const WebFeature kTextXmlFeatures[2][2] = { + {WebFeature::kCrossOriginTextXml, WebFeature::kCrossOriginWorkerTextXml}, + {WebFeature::kSameOriginTextXml, WebFeature::kSameOriginWorkerTextXml}}; + +// Helper function to decide what to do with with a given mime type. This takes +// - a mime type +// - inputs that affect the decision (is_same_origin, is_worker_global_scope). +// +// The return value determines whether this mime should be allowed or blocked. +// Additionally, warn returns whether we should log a console warning about +// expected future blocking of this resource. 'counter' determines which +// Use counter should be used to count this. +bool AllowMimeTypeAsScript(const String& mime_type, + bool same_origin, + bool is_worker_global_scope, + bool& warn, + WebFeature& counter) { + // The common case: A proper JavaScript MIME type + if (MIMETypeRegistry::IsSupportedJavaScriptMIMEType(mime_type)) + return true; + + // Check for certain non-executable MIME types. + // See: + // https://fetch.spec.whatwg.org/#should-response-to-request-be-blocked-due-to-mime-type + if (mime_type.StartsWithIgnoringASCIICase("image/") || + mime_type.StartsWithIgnoringASCIICase("text/csv") || + mime_type.StartsWithIgnoringASCIICase("audio/") || + mime_type.StartsWithIgnoringASCIICase("video/")) { + if (mime_type.StartsWithIgnoringASCIICase("image/")) { + counter = WebFeature::kBlockedSniffingImageToScript; + } else if (mime_type.StartsWithIgnoringASCIICase("audio/")) { + counter = WebFeature::kBlockedSniffingAudioToScript; + } else if (mime_type.StartsWithIgnoringASCIICase("video/")) { + counter = WebFeature::kBlockedSniffingVideoToScript; + } else if (mime_type.StartsWithIgnoringASCIICase("text/csv")) { + counter = WebFeature::kBlockedSniffingCSVToScript; + } + return false; + } + + // Beyond this point we handle legacy MIME types, where it depends whether + // we still wish to accept them (or log them using UseCounter, or add a + // deprecation warning to the console). + + if (!is_worker_global_scope && + mime_type.StartsWithIgnoringASCIICase("text/") && + MIMETypeRegistry::IsLegacySupportedJavaScriptLanguage( + mime_type.Substring(5))) { + return true; + } + + if (mime_type.StartsWithIgnoringASCIICase("application/octet-stream")) { + counter = + kApplicationOctetStreamFeatures[same_origin][is_worker_global_scope]; + } else if (mime_type.StartsWithIgnoringASCIICase("application/xml")) { + counter = kApplicationXmlFeatures[same_origin][is_worker_global_scope]; + } else if (mime_type.StartsWithIgnoringASCIICase("text/html")) { + counter = kTextHtmlFeatures[same_origin][is_worker_global_scope]; + } else if (mime_type.StartsWithIgnoringASCIICase("text/plain")) { + counter = kTextPlainFeatures[same_origin][is_worker_global_scope]; + } else if (mime_type.StartsWithIgnoringCase("text/xml")) { + counter = kTextXmlFeatures[same_origin][is_worker_global_scope]; + } + + // Depending on RuntimeEnabledFeatures, we'll allow, allow-but-warn, or block + // these types when we're in a worker. + bool allow = !is_worker_global_scope || + !RuntimeEnabledFeatures::WorkerNosniffBlockEnabled(); + warn = allow && is_worker_global_scope && + RuntimeEnabledFeatures::WorkerNosniffWarnEnabled(); + return allow; +} + +bool MimeTypeAsScriptImpl(ExecutionContext* execution_context, + const ResourceResponse& response, + bool is_worker_global_scope) { + // Is it a file:-URL? If so, decide based on file suffix. + if (RuntimeEnabledFeatures::WorkerNosniffBlockEnabled() && + is_worker_global_scope && response.Url().IsLocalFile()) { + return response.Url().LastPathComponent().EndsWith(".js"); + } + + String mime_type = response.HttpContentType(); + + // Allowed by nosniff? + if (!(ParseContentTypeOptionsHeader(response.HttpHeaderField( + HTTPNames::X_Content_Type_Options)) != kContentTypeOptionsNosniff || + MIMETypeRegistry::IsSupportedJavaScriptMIMEType(mime_type))) { + execution_context->AddConsoleMessage(ConsoleMessage::Create( + kSecurityMessageSource, kErrorMessageLevel, + "Refused to execute script from '" + response.Url().ElidedString() + + "' because its MIME type ('" + mime_type + + "') is not executable, and " + "strict MIME type checking is " + "enabled.")); + return false; + } + + // Check for certain non-executable MIME types. + // See: + // https://fetch.spec.whatwg.org/#should-response-to-request-be-blocked-due-to-mime-type + + bool same_origin = + execution_context->GetSecurityOrigin()->CanRequest(response.Url()); + + // For any MIME type, we can do three things: accept/reject it, print a + // warning into the console, and count it using a use counter. + const WebFeature kWebFeatureNone = WebFeature::kNumberOfFeatures; + bool warn = false; + WebFeature counter = kWebFeatureNone; + bool allow = AllowMimeTypeAsScript(mime_type, same_origin, + is_worker_global_scope, warn, counter); + + // These record usages for two MIME types (without subtypes), per same/cross + // origin. + if (mime_type.StartsWithIgnoringASCIICase("application/")) { + UseCounter::Count(execution_context, kApplicationFeatures[same_origin]); + } else if (mime_type.StartsWithIgnoringASCIICase("text/")) { + UseCounter::Count(execution_context, kTextFeatures[same_origin]); + } + + // The code above has made a decision and handed down the result in accept, + // warn, and counter. + if (counter != kWebFeatureNone) { + UseCounter::Count(execution_context, counter); + } + if (!allow || warn) { + const char* msg = + allow ? "Deprecated: Future versions will refuse" : "Refused"; + execution_context->AddConsoleMessage(ConsoleMessage::Create( + kSecurityMessageSource, kErrorMessageLevel, + String() + msg + " to execute script from '" + + response.Url().ElidedString() + "' because its MIME type ('" + + mime_type + "') is not executable.")); + } + return allow; +} + +} // namespace + +bool AllowedByNosniff::MimeTypeAsScript(ExecutionContext* execution_context, + const ResourceResponse& response) { + return MimeTypeAsScriptImpl(execution_context, response, + execution_context->IsWorkerGlobalScope()); +} + +bool AllowedByNosniff::MimeTypeAsScriptForTesting( + ExecutionContext* execution_context, + const ResourceResponse& response, + bool is_worker_global_scope) { + return MimeTypeAsScriptImpl(execution_context, response, + is_worker_global_scope); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/loader/allowed_by_nosniff.h b/chromium/third_party/blink/renderer/core/loader/allowed_by_nosniff.h new file mode 100644 index 00000000000..757f0f8cb26 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/allowed_by_nosniff.h @@ -0,0 +1,26 @@ +// Copyright 2017 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_ALLOWED_BY_NOSNIFF_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_ALLOWED_BY_NOSNIFF_H_ + +#include "third_party/blink/renderer/core/core_export.h" + +namespace blink { + +class ExecutionContext; +class ResourceResponse; + +class CORE_EXPORT AllowedByNosniff { + public: + static bool MimeTypeAsScript(ExecutionContext*, const ResourceResponse&); + + // For testing: + static bool MimeTypeAsScriptForTesting(ExecutionContext*, + const ResourceResponse&, + bool is_worker_global_scope); +}; + +} // namespace blink + +#endif diff --git a/chromium/third_party/blink/renderer/core/loader/allowed_by_nosniff_test.cc b/chromium/third_party/blink/renderer/core/loader/allowed_by_nosniff_test.cc new file mode 100644 index 00000000000..227f7a04f2e --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/allowed_by_nosniff_test.cc @@ -0,0 +1,191 @@ +// Copyright 2017 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/allowed_by_nosniff.h" + +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/blink/renderer/core/dom/document.h" +#include "third_party/blink/renderer/core/frame/use_counter.h" +#include "third_party/blink/renderer/core/inspector/console_message_storage.h" +#include "third_party/blink/renderer/core/page/page.h" +#include "third_party/blink/renderer/core/testing/dummy_page_holder.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource_response.h" + +namespace blink { + +class AllowedByNosniffTest : public testing::Test { + public: + void SetUp() override { + // Create a new dummy page holder for each test, so that we get a fresh + // set of counters for each. + dummy_page_holder_ = DummyPageHolder::Create(); + } + + Document* doc() { return &dummy_page_holder_->GetDocument(); } + + size_t ConsoleMessageStoreSize() const { + return dummy_page_holder_->GetPage().GetConsoleMessageStorage().size(); + } + + private: + std::unique_ptr<DummyPageHolder> dummy_page_holder_; +}; + +TEST_F(AllowedByNosniffTest, SanityCheckSetUp) { + // UseCounter counts will be silently swallowed under various conditions, + // e.g. if the document doesn't actually hold a frame. This test is a sanity + // test that UseCounter::Count + UseCounter::IsCounted work at all with the + // current test setup. If this test fails, we know that the setup is wrong, + // rather than the code under test. + WebFeature f = WebFeature::kSameOriginTextScript; + EXPECT_FALSE(UseCounter::IsCounted(*doc(), f)); + UseCounter::Count(doc(), f); + EXPECT_TRUE(UseCounter::IsCounted(*doc(), f)); + + EXPECT_EQ(ConsoleMessageStoreSize(), 0U); +} + +TEST_F(AllowedByNosniffTest, AllowedOrNot) { + struct { + const char* mimetype; + bool allowed; + bool strict_allowed; + } data[] = { + // Supported mimetypes: + {"text/javascript", true, true}, + {"application/javascript", true, true}, + {"text/ecmascript", true, true}, + + // Blocked mimetpyes: + {"image/png", false, false}, + {"text/csv", false, false}, + {"video/mpeg", false, false}, + + // Legacy mimetypes: + {"text/html", true, false}, + {"text/plain", true, false}, + {"application/xml", true, false}, + {"application/octet-stream", true, false}, + + // Potato mimetypes: + {"text/potato", true, false}, + {"potato/text", true, false}, + {"aaa/aaa", true, false}, + {"zzz/zzz", true, false}, + + // Parameterized mime types: + {"text/javascript; charset=utf-8", true, true}, + {"text/javascript;charset=utf-8", true, true}, + {"text/javascript;bla;bla", true, true}, + {"text/csv; charset=utf-8", false, false}, + {"text/csv;charset=utf-8", false, false}, + {"text/csv;bla;bla", false, false}, + + // Funky capitalization: + {"text/html", true, false}, + {"Text/html", true, false}, + {"text/Html", true, false}, + {"TeXt/HtMl", true, false}, + {"TEXT/HTML", true, false}, + }; + + for (auto& testcase : data) { + SCOPED_TRACE(testing::Message() + << "\n mime type: " << testcase.mimetype + << "\n allowed: " << (testcase.allowed ? "true" : "false") + << "\n strict_allowed: " + << (testcase.strict_allowed ? "true" : "false")); + + const KURL url("https://bla.com/"); + doc()->SetSecurityOrigin(SecurityOrigin::Create(url)); + ResourceResponse response(url); + response.SetHTTPHeaderField("Content-Type", testcase.mimetype); + + // Nosniff 'legacy' setting: Both worker + non-worker obey the 'allowed' + // setting. Warnings for any blocked script. + RuntimeEnabledFeatures::SetWorkerNosniffBlockEnabled(false); + RuntimeEnabledFeatures::SetWorkerNosniffWarnEnabled(false); + size_t message_count = ConsoleMessageStoreSize(); + EXPECT_EQ(testcase.allowed, + AllowedByNosniff::MimeTypeAsScript(doc(), response)); + EXPECT_EQ(testcase.allowed, AllowedByNosniff::MimeTypeAsScriptForTesting( + doc(), response, true)); + EXPECT_EQ(ConsoleMessageStoreSize(), message_count + 2 * !testcase.allowed); + + // Nosniff worker blocked: Workers follow the 'strict_allow' setting. + // Warnings for any blocked scripts. + RuntimeEnabledFeatures::SetWorkerNosniffBlockEnabled(true); + RuntimeEnabledFeatures::SetWorkerNosniffWarnEnabled(false); + message_count = ConsoleMessageStoreSize(); + EXPECT_EQ(testcase.allowed, + AllowedByNosniff::MimeTypeAsScript(doc(), response)); + EXPECT_EQ(ConsoleMessageStoreSize(), message_count + !testcase.allowed); + EXPECT_EQ( + testcase.strict_allowed, + AllowedByNosniff::MimeTypeAsScriptForTesting(doc(), response, true)); + EXPECT_EQ(ConsoleMessageStoreSize(), + message_count + !testcase.allowed + !testcase.strict_allowed); + + // Nosniff 'legacy', but with warnings. The allowed setting follows the + // 'allowed' setting, but the warnings follow the 'strict' setting. + RuntimeEnabledFeatures::SetWorkerNosniffBlockEnabled(false); + RuntimeEnabledFeatures::SetWorkerNosniffWarnEnabled(true); + message_count = ConsoleMessageStoreSize(); + EXPECT_EQ(testcase.allowed, + AllowedByNosniff::MimeTypeAsScript(doc(), response)); + EXPECT_EQ(ConsoleMessageStoreSize(), message_count + !testcase.allowed); + EXPECT_EQ(testcase.allowed, AllowedByNosniff::MimeTypeAsScriptForTesting( + doc(), response, true)); + EXPECT_EQ(ConsoleMessageStoreSize(), + message_count + !testcase.allowed + !testcase.strict_allowed); + } +} + +TEST_F(AllowedByNosniffTest, Counters) { + const char* bla = "https://bla.com"; + const char* blubb = "https://blubb.com"; + struct { + const char* url; + const char* origin; + const char* mimetype; + WebFeature expected; + } data[] = { + // Test same- vs cross-origin cases. + {bla, "", "text/plain", WebFeature::kCrossOriginTextScript}, + {bla, "", "text/plain", WebFeature::kCrossOriginTextPlain}, + {bla, blubb, "text/plain", WebFeature::kCrossOriginTextScript}, + {bla, blubb, "text/plain", WebFeature::kCrossOriginTextPlain}, + {bla, bla, "text/plain", WebFeature::kSameOriginTextScript}, + {bla, bla, "text/plain", WebFeature::kSameOriginTextPlain}, + + // Test mime type and subtype handling. + {bla, bla, "text/xml", WebFeature::kSameOriginTextScript}, + {bla, bla, "text/xml", WebFeature::kSameOriginTextXml}, + + // Test mime types from crbug.com/765544, with random cross/same site + // origins. + {bla, bla, "text/plain", WebFeature::kSameOriginTextPlain}, + {bla, blubb, "text/xml", WebFeature::kCrossOriginTextXml}, + {blubb, blubb, "application/octet-stream", + WebFeature::kSameOriginApplicationOctetStream}, + {blubb, bla, "application/xml", WebFeature::kCrossOriginApplicationXml}, + {bla, bla, "text/html", WebFeature::kSameOriginTextHtml}, + }; + + for (auto& testcase : data) { + SetUp(); + SCOPED_TRACE(testing::Message() << "\n url: " << testcase.url + << "\n origin: " << testcase.origin + << "\n mime type: " << testcase.mimetype + << "\n webfeature: " << testcase.expected); + doc()->SetSecurityOrigin(SecurityOrigin::Create(KURL(testcase.origin))); + ResourceResponse response(KURL(testcase.url)); + response.SetHTTPHeaderField("Content-Type", testcase.mimetype); + + AllowedByNosniff::MimeTypeAsScript(doc(), response); + EXPECT_TRUE(UseCounter::IsCounted(*doc(), testcase.expected)); + } +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/loader/appcache/application_cache.cc b/chromium/third_party/blink/renderer/core/loader/appcache/application_cache.cc new file mode 100644 index 00000000000..7c26de4bf4b --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/appcache/application_cache.cc @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2008, 2009 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "third_party/blink/renderer/core/loader/appcache/application_cache.h" + +#include "third_party/blink/renderer/bindings/core/v8/exception_state.h" +#include "third_party/blink/renderer/core/dom/document.h" +#include "third_party/blink/renderer/core/dom/events/event_listener.h" +#include "third_party/blink/renderer/core/dom/exception_code.h" +#include "third_party/blink/renderer/core/frame/deprecation.h" +#include "third_party/blink/renderer/core/frame/hosts_using_features.h" +#include "third_party/blink/renderer/core/frame/local_frame.h" +#include "third_party/blink/renderer/core/frame/use_counter.h" +#include "third_party/blink/renderer/core/loader/document_loader.h" +#include "third_party/blink/renderer/core/loader/frame_loader.h" + +namespace blink { + +ApplicationCache::ApplicationCache(LocalFrame* frame) : DOMWindowClient(frame) { + ApplicationCacheHost* cache_host = GetApplicationCacheHost(); + if (cache_host) + cache_host->SetApplicationCache(this); +} + +void ApplicationCache::Trace(blink::Visitor* visitor) { + EventTargetWithInlineData::Trace(visitor); + DOMWindowClient::Trace(visitor); +} + +ApplicationCacheHost* ApplicationCache::GetApplicationCacheHost() const { + if (!GetFrame() || !GetFrame()->Loader().GetDocumentLoader()) + return nullptr; + return GetFrame()->Loader().GetDocumentLoader()->GetApplicationCacheHost(); +} + +unsigned short ApplicationCache::status() const { + RecordAPIUseType(); + ApplicationCacheHost* cache_host = GetApplicationCacheHost(); + if (!cache_host) + return ApplicationCacheHost::kUncached; + return cache_host->GetStatus(); +} + +void ApplicationCache::update(ExceptionState& exception_state) { + RecordAPIUseType(); + ApplicationCacheHost* cache_host = GetApplicationCacheHost(); + if (!cache_host || !cache_host->Update()) { + exception_state.ThrowDOMException( + kInvalidStateError, "there is no application cache to update."); + } +} + +void ApplicationCache::swapCache(ExceptionState& exception_state) { + RecordAPIUseType(); + ApplicationCacheHost* cache_host = GetApplicationCacheHost(); + if (!cache_host || !cache_host->SwapCache()) { + exception_state.ThrowDOMException( + kInvalidStateError, "there is no newer application cache to swap to."); + } +} + +void ApplicationCache::abort() { + ApplicationCacheHost* cache_host = GetApplicationCacheHost(); + if (cache_host) + cache_host->Abort(); +} + +const AtomicString& ApplicationCache::InterfaceName() const { + return EventTargetNames::ApplicationCache; +} + +ExecutionContext* ApplicationCache::GetExecutionContext() const { + return GetFrame() ? GetFrame()->GetDocument() : nullptr; +} + +const AtomicString& ApplicationCache::ToEventType( + ApplicationCacheHost::EventID id) { + switch (id) { + case ApplicationCacheHost::kCheckingEvent: + return EventTypeNames::checking; + case ApplicationCacheHost::kErrorEvent: + return EventTypeNames::error; + case ApplicationCacheHost::kNoupdateEvent: + return EventTypeNames::noupdate; + case ApplicationCacheHost::kDownloadingEvent: + return EventTypeNames::downloading; + case ApplicationCacheHost::kProgressEvent: + return EventTypeNames::progress; + case ApplicationCacheHost::kUpdatereadyEvent: + return EventTypeNames::updateready; + case ApplicationCacheHost::kCachedEvent: + return EventTypeNames::cached; + case ApplicationCacheHost::kObsoleteEvent: + return EventTypeNames::obsolete; + } + NOTREACHED(); + return EventTypeNames::error; +} + +void ApplicationCache::RecordAPIUseType() const { + if (!GetFrame()) + return; + + Document* document = GetFrame()->GetDocument(); + + if (!document) + return; + + if (document->IsSecureContext()) { + UseCounter::Count(document, WebFeature::kApplicationCacheAPISecureOrigin); + } else { + Deprecation::CountDeprecation( + document, WebFeature::kApplicationCacheAPIInsecureOrigin); + HostsUsingFeatures::CountAnyWorld( + *document, + HostsUsingFeatures::Feature::kApplicationCacheAPIInsecureHost); + } +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/loader/appcache/application_cache.h b/chromium/third_party/blink/renderer/core/loader/appcache/application_cache.h new file mode 100644 index 00000000000..88bd1b95157 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/appcache/application_cache.h @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2008, 2009 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_APPCACHE_APPLICATION_CACHE_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_APPCACHE_APPLICATION_CACHE_H_ + +#include "third_party/blink/renderer/core/dom/context_lifecycle_observer.h" +#include "third_party/blink/renderer/core/dom/events/event_target.h" +#include "third_party/blink/renderer/core/loader/appcache/application_cache_host.h" +#include "third_party/blink/renderer/platform/heap/handle.h" +#include "third_party/blink/renderer/platform/wtf/forward.h" + +namespace blink { + +class ExceptionState; +class LocalFrame; + +class ApplicationCache final : public EventTargetWithInlineData, + public DOMWindowClient { + DEFINE_WRAPPERTYPEINFO(); + USING_GARBAGE_COLLECTED_MIXIN(ApplicationCache); + + public: + static ApplicationCache* Create(LocalFrame* frame) { + return new ApplicationCache(frame); + } + ~ApplicationCache() override = default; + + unsigned short status() const; + void update(ExceptionState&); + void swapCache(ExceptionState&); + void abort(); + + // Explicitly named attribute event listener helpers + + DEFINE_ATTRIBUTE_EVENT_LISTENER(checking); + DEFINE_ATTRIBUTE_EVENT_LISTENER(error); + DEFINE_ATTRIBUTE_EVENT_LISTENER(noupdate); + DEFINE_ATTRIBUTE_EVENT_LISTENER(downloading); + DEFINE_ATTRIBUTE_EVENT_LISTENER(progress); + DEFINE_ATTRIBUTE_EVENT_LISTENER(updateready); + DEFINE_ATTRIBUTE_EVENT_LISTENER(cached); + DEFINE_ATTRIBUTE_EVENT_LISTENER(obsolete); + + const AtomicString& InterfaceName() const override; + ExecutionContext* GetExecutionContext() const override; + + static const AtomicString& ToEventType(ApplicationCacheHost::EventID); + + void Trace(blink::Visitor*) override; + + private: + explicit ApplicationCache(LocalFrame*); + + void RecordAPIUseType() const; + + ApplicationCacheHost* GetApplicationCacheHost() const; +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_APPCACHE_APPLICATION_CACHE_H_ diff --git a/chromium/third_party/blink/renderer/core/loader/appcache/application_cache.idl b/chromium/third_party/blink/renderer/core/loader/appcache/application_cache.idl new file mode 100644 index 00000000000..f173d233d47 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/appcache/application_cache.idl @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2008, 2009 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +// https://html.spec.whatwg.org/#application-cache-api + +[ + DoNotCheckConstants, + SecureContext=RestrictAppCacheToSecureContexts + // TODO(foolip): Exposed=(Window,SharedWorker) +] interface ApplicationCache : EventTarget { + // update status + const unsigned short UNCACHED = 0; + const unsigned short IDLE = 1; + const unsigned short CHECKING = 2; + const unsigned short DOWNLOADING = 3; + const unsigned short UPDATEREADY = 4; + const unsigned short OBSOLETE = 5; + readonly attribute unsigned short status; + + // updates + [RaisesException] void update(); + void abort(); + [RaisesException] void swapCache(); + + // events + attribute EventHandler onchecking; + attribute EventHandler onerror; + attribute EventHandler onnoupdate; + attribute EventHandler ondownloading; + attribute EventHandler onprogress; + attribute EventHandler onupdateready; + attribute EventHandler oncached; + attribute EventHandler onobsolete; +}; diff --git a/chromium/third_party/blink/renderer/core/loader/appcache/application_cache_host.cc b/chromium/third_party/blink/renderer/core/loader/appcache/application_cache_host.cc new file mode 100644 index 00000000000..ce8532abf90 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/appcache/application_cache_host.cc @@ -0,0 +1,395 @@ +/* + * Copyright (C) 2009 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/appcache/application_cache_host.h" + +#include "services/network/public/mojom/request_context_frame_type.mojom-blink.h" +#include "third_party/blink/public/platform/web_application_cache_host.h" +#include "third_party/blink/public/platform/web_url.h" +#include "third_party/blink/public/platform/web_url_error.h" +#include "third_party/blink/public/platform/web_url_request.h" +#include "third_party/blink/public/platform/web_url_response.h" +#include "third_party/blink/public/platform/web_vector.h" +#include "third_party/blink/renderer/bindings/core/v8/exception_state.h" +#include "third_party/blink/renderer/core/events/application_cache_error_event.h" +#include "third_party/blink/renderer/core/events/progress_event.h" +#include "third_party/blink/renderer/core/frame/deprecation.h" +#include "third_party/blink/renderer/core/frame/hosts_using_features.h" +#include "third_party/blink/renderer/core/frame/local_frame.h" +#include "third_party/blink/renderer/core/frame/local_frame_client.h" +#include "third_party/blink/renderer/core/frame/settings.h" +#include "third_party/blink/renderer/core/frame/use_counter.h" +#include "third_party/blink/renderer/core/inspector/InspectorApplicationCacheAgent.h" +#include "third_party/blink/renderer/core/loader/appcache/application_cache.h" +#include "third_party/blink/renderer/core/loader/document_loader.h" +#include "third_party/blink/renderer/core/loader/frame_loader.h" +#include "third_party/blink/renderer/core/page/frame_tree.h" +#include "third_party/blink/renderer/core/page/page.h" +#include "third_party/blink/renderer/core/probe/core_probes.h" +#include "third_party/blink/renderer/platform/exported/wrapped_resource_request.h" +#include "third_party/blink/renderer/platform/exported/wrapped_resource_response.h" +#include "third_party/blink/renderer/platform/weborigin/security_origin.h" +#include "third_party/blink/renderer/platform/wtf/assertions.h" + +namespace blink { + +// We provide a custom implementation of this class that calls out to the +// embedding application instead of using WebCore's built in appcache system. +// This file replaces webcore/appcache/ApplicationCacheHost.cpp in our build. + +ApplicationCacheHost::ApplicationCacheHost(DocumentLoader* document_loader) + : dom_application_cache_(nullptr), + document_loader_(document_loader), + defers_events_(true) { + DCHECK(document_loader_); +} + +ApplicationCacheHost::~ApplicationCacheHost() { + // Verify that detachFromDocumentLoader() has been performed already. + DCHECK(!host_); +} + +void ApplicationCacheHost::WillStartLoading(ResourceRequest& request) { + if (!IsApplicationCacheEnabled()) + return; + + if (request.GetFrameType() == + network::mojom::RequestContextFrameType::kTopLevel || + request.GetFrameType() == + network::mojom::RequestContextFrameType::kNested) + WillStartLoadingMainResource(request.Url(), request.HttpMethod()); + + if (!host_) + return; + + int host_id = host_->GetHostID(); + if (host_id != WebApplicationCacheHost::kAppCacheNoHostId) + request.SetAppCacheHostID(host_id); +} + +void ApplicationCacheHost::WillStartLoadingMainResource(const KURL& url, + const String& method) { + // We defer creating the outer host object to avoid spurious + // creation/destruction around creating empty documents. At this point, we're + // initiating a main resource load for the document, so its for real. + + DCHECK(IsApplicationCacheEnabled()); + + DCHECK(document_loader_->GetFrame()); + LocalFrame& frame = *document_loader_->GetFrame(); + host_ = frame.Client()->CreateApplicationCacheHost(this); + if (!host_) + return; + + const WebApplicationCacheHost* spawning_host = nullptr; + Frame* spawning_frame = frame.Tree().Parent(); + if (!spawning_frame || !spawning_frame->IsLocalFrame()) + spawning_frame = frame.Loader().Opener(); + if (!spawning_frame || !spawning_frame->IsLocalFrame()) + spawning_frame = &frame; + if (DocumentLoader* spawning_doc_loader = + ToLocalFrame(spawning_frame)->Loader().GetDocumentLoader()) { + spawning_host = + spawning_doc_loader->GetApplicationCacheHost() + ? spawning_doc_loader->GetApplicationCacheHost()->host_.get() + : nullptr; + } + + host_->WillStartMainResourceRequest(url, method, spawning_host); + + // NOTE: The semantics of this method, and others in this interface, are + // subtly different than the method names would suggest. For example, in this + // method never returns an appcached response in the SubstituteData out + // argument, instead we return the appcached response thru the usual resource + // loading pipeline. +} + +void ApplicationCacheHost::SelectCacheWithoutManifest() { + if (host_) + host_->SelectCacheWithoutManifest(); +} + +void ApplicationCacheHost::SelectCacheWithManifest(const KURL& manifest_url) { + DCHECK(document_loader_); + + LocalFrame* frame = document_loader_->GetFrame(); + Document* document = frame->GetDocument(); + if (document->IsSandboxed(kSandboxOrigin)) { + // Prevent sandboxes from establishing application caches. + SelectCacheWithoutManifest(); + return; + } + if (document->IsSecureContext()) { + UseCounter::Count(document, + WebFeature::kApplicationCacheManifestSelectSecureOrigin); + UseCounter::CountCrossOriginIframe( + *document, WebFeature::kApplicationCacheManifestSelectSecureOrigin); + } else { + Deprecation::CountDeprecation( + document, WebFeature::kApplicationCacheManifestSelectInsecureOrigin); + Deprecation::CountDeprecationCrossOriginIframe( + *document, WebFeature::kApplicationCacheManifestSelectInsecureOrigin); + HostsUsingFeatures::CountAnyWorld( + *document, HostsUsingFeatures::Feature:: + kApplicationCacheManifestSelectInsecureHost); + } + if (host_ && !host_->SelectCacheWithManifest(manifest_url)) { + // It's a foreign entry, restart the current navigation from the top of the + // navigation algorithm. The navigation will not result in the same resource + // being loaded, because "foreign" entries are never picked during + // navigation. see ApplicationCacheGroup::selectCache() + frame->Navigate(*document, document->Url(), true, UserGestureStatus::kNone); + } +} + +void ApplicationCacheHost::DidReceiveResponseForMainResource( + const ResourceResponse& response) { + if (host_) { + WrappedResourceResponse wrapped(response); + host_->DidReceiveResponseForMainResource(wrapped); + } +} + +void ApplicationCacheHost::MainResourceDataReceived(const char* data, + size_t length) { + if (host_) + host_->DidReceiveDataForMainResource(data, length); +} + +void ApplicationCacheHost::FailedLoadingMainResource() { + if (host_) + host_->DidFinishLoadingMainResource(false); +} + +void ApplicationCacheHost::FinishedLoadingMainResource() { + if (host_) + host_->DidFinishLoadingMainResource(true); +} + +void ApplicationCacheHost::SetApplicationCache( + ApplicationCache* dom_application_cache) { + DCHECK(!dom_application_cache_ || !dom_application_cache); + dom_application_cache_ = dom_application_cache; +} + +void ApplicationCacheHost::DetachFromDocumentLoader() { + // Detach from the owning DocumentLoader and let go of + // WebApplicationCacheHost. + SetApplicationCache(nullptr); + host_.reset(); + document_loader_ = nullptr; +} + +void ApplicationCacheHost::NotifyApplicationCache( + EventID id, + int progress_total, + int progress_done, + WebApplicationCacheHost::ErrorReason error_reason, + const String& error_url, + int error_status, + const String& error_message) { + if (id != kProgressEvent) { + probe::updateApplicationCacheStatus(document_loader_->GetFrame()); + } + + if (defers_events_) { + // Event dispatching is deferred until document.onload has fired. + deferred_events_.push_back(DeferredEvent(id, progress_total, progress_done, + error_reason, error_url, + error_status, error_message)); + return; + } + DispatchDOMEvent(id, progress_total, progress_done, error_reason, error_url, + error_status, error_message); +} + +ApplicationCacheHost::CacheInfo ApplicationCacheHost::ApplicationCacheInfo() { + if (!host_) + return CacheInfo(NullURL(), 0, 0, 0); + + WebApplicationCacheHost::CacheInfo web_info; + host_->GetAssociatedCacheInfo(&web_info); + return CacheInfo(web_info.manifest_url, web_info.creation_time, + web_info.update_time, web_info.total_size); +} + +int ApplicationCacheHost::GetHostID() const { + if (!host_) + return WebApplicationCacheHost::kAppCacheNoHostId; + return host_->GetHostID(); +} + +void ApplicationCacheHost::FillResourceList(ResourceInfoList* resources) { + if (!host_) + return; + + WebVector<WebApplicationCacheHost::ResourceInfo> web_resources; + host_->GetResourceList(&web_resources); + for (size_t i = 0; i < web_resources.size(); ++i) { + resources->push_back( + ResourceInfo(web_resources[i].url, web_resources[i].is_master, + web_resources[i].is_manifest, web_resources[i].is_fallback, + web_resources[i].is_foreign, web_resources[i].is_explicit, + web_resources[i].size)); + } +} + +void ApplicationCacheHost::StopDeferringEvents() { + for (unsigned i = 0; i < deferred_events_.size(); ++i) { + const DeferredEvent& deferred = deferred_events_[i]; + DispatchDOMEvent(deferred.event_id, deferred.progress_total, + deferred.progress_done, deferred.error_reason, + deferred.error_url, deferred.error_status, + deferred.error_message); + } + deferred_events_.clear(); + defers_events_ = false; +} + +void ApplicationCacheHost::DispatchDOMEvent( + EventID id, + int progress_total, + int progress_done, + WebApplicationCacheHost::ErrorReason error_reason, + const String& error_url, + int error_status, + const String& error_message) { + // Don't dispatch an event if the window is detached. + if (!dom_application_cache_ || !dom_application_cache_->DomWindow()) + return; + + const AtomicString& event_type = ApplicationCache::ToEventType(id); + if (event_type.IsEmpty()) + return; + Event* event = nullptr; + if (id == kProgressEvent) { + event = + ProgressEvent::Create(event_type, true, progress_done, progress_total); + } else if (id == kErrorEvent) { + event = ApplicationCacheErrorEvent::Create(error_reason, error_url, + error_status, error_message); + } else { + event = Event::Create(event_type); + } + dom_application_cache_->DispatchEvent(event); +} + +ApplicationCacheHost::Status ApplicationCacheHost::GetStatus() const { + return host_ ? static_cast<Status>(host_->GetStatus()) : kUncached; +} + +bool ApplicationCacheHost::Update() { + return host_ ? host_->StartUpdate() : false; +} + +bool ApplicationCacheHost::SwapCache() { + bool success = host_ ? host_->SwapCache() : false; + if (success) { + probe::updateApplicationCacheStatus(document_loader_->GetFrame()); + } + return success; +} + +void ApplicationCacheHost::Abort() { + if (host_) + host_->Abort(); +} + +bool ApplicationCacheHost::IsApplicationCacheEnabled() { + DCHECK(document_loader_->GetFrame()); + return document_loader_->GetFrame()->GetSettings() && + document_loader_->GetFrame() + ->GetSettings() + ->GetOfflineWebApplicationCacheEnabled(); +} + +void ApplicationCacheHost::DidChangeCacheAssociation() { + // FIXME: Prod the inspector to update its notion of what cache the page is + // using. +} + +void ApplicationCacheHost::NotifyEventListener( + WebApplicationCacheHost::EventID event_id) { + NotifyApplicationCache(static_cast<ApplicationCacheHost::EventID>(event_id), + 0, 0, WebApplicationCacheHost::kUnknownError, String(), + 0, String()); +} + +void ApplicationCacheHost::NotifyProgressEventListener(const WebURL&, + int progress_total, + int progress_done) { + NotifyApplicationCache(kProgressEvent, progress_total, progress_done, + WebApplicationCacheHost::kUnknownError, String(), 0, + String()); +} + +void ApplicationCacheHost::NotifyErrorEventListener( + WebApplicationCacheHost::ErrorReason reason, + const WebURL& url, + int status, + const WebString& message) { + NotifyApplicationCache(kErrorEvent, 0, 0, reason, url.GetString(), status, + message); +} + +void ApplicationCacheHost::Trace(blink::Visitor* visitor) { + visitor->Trace(dom_application_cache_); + visitor->Trace(document_loader_); +} + +STATIC_ASSERT_ENUM(WebApplicationCacheHost::kUncached, + ApplicationCacheHost::kUncached); +STATIC_ASSERT_ENUM(WebApplicationCacheHost::kIdle, ApplicationCacheHost::kIdle); +STATIC_ASSERT_ENUM(WebApplicationCacheHost::kChecking, + ApplicationCacheHost::kChecking); +STATIC_ASSERT_ENUM(WebApplicationCacheHost::kDownloading, + ApplicationCacheHost::kDownloading); +STATIC_ASSERT_ENUM(WebApplicationCacheHost::kUpdateReady, + ApplicationCacheHost::kUpdateready); +STATIC_ASSERT_ENUM(WebApplicationCacheHost::kObsolete, + ApplicationCacheHost::kObsolete); +STATIC_ASSERT_ENUM(WebApplicationCacheHost::kCheckingEvent, + ApplicationCacheHost::kCheckingEvent); +STATIC_ASSERT_ENUM(WebApplicationCacheHost::kErrorEvent, + ApplicationCacheHost::kErrorEvent); +STATIC_ASSERT_ENUM(WebApplicationCacheHost::kNoUpdateEvent, + ApplicationCacheHost::kNoupdateEvent); +STATIC_ASSERT_ENUM(WebApplicationCacheHost::kDownloadingEvent, + ApplicationCacheHost::kDownloadingEvent); +STATIC_ASSERT_ENUM(WebApplicationCacheHost::kProgressEvent, + ApplicationCacheHost::kProgressEvent); +STATIC_ASSERT_ENUM(WebApplicationCacheHost::kUpdateReadyEvent, + ApplicationCacheHost::kUpdatereadyEvent); +STATIC_ASSERT_ENUM(WebApplicationCacheHost::kCachedEvent, + ApplicationCacheHost::kCachedEvent); +STATIC_ASSERT_ENUM(WebApplicationCacheHost::kObsoleteEvent, + ApplicationCacheHost::kObsoleteEvent); + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/loader/appcache/application_cache_host.h b/chromium/third_party/blink/renderer/core/loader/appcache/application_cache_host.h new file mode 100644 index 00000000000..53568a1eab5 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/appcache/application_cache_host.h @@ -0,0 +1,227 @@ +/* + * Copyright (c) 2009, 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. + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_APPCACHE_APPLICATION_CACHE_HOST_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_APPCACHE_APPLICATION_CACHE_HOST_H_ + +#include <memory> + +#include "base/gtest_prod_util.h" +#include "base/macros.h" +#include "third_party/blink/public/platform/web_application_cache_host_client.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/weborigin/kurl.h" +#include "third_party/blink/renderer/platform/wtf/allocator.h" +#include "third_party/blink/renderer/platform/wtf/vector.h" + +namespace blink { +class ApplicationCache; +class DocumentLoader; +class ResourceRequest; +class ResourceResponse; + +class CORE_EXPORT ApplicationCacheHost final + : public GarbageCollectedFinalized<ApplicationCacheHost>, + public WebApplicationCacheHostClient { + public: + static ApplicationCacheHost* Create(DocumentLoader* loader) { + return new ApplicationCacheHost(loader); + } + + ~ApplicationCacheHost() override; + void DetachFromDocumentLoader(); + + // The Status numeric values are specified in the HTML5 spec. + enum Status { + kUncached = 0, + kIdle = 1, + kChecking = 2, + kDownloading = 3, + kUpdateready = 4, + kObsolete = 5 + }; + + enum EventID { + kCheckingEvent = 0, + kErrorEvent, + kNoupdateEvent, + kDownloadingEvent, + kProgressEvent, + kUpdatereadyEvent, + kCachedEvent, + kObsoleteEvent // Must remain the last value, this is used to size arrays. + }; + + struct CacheInfo { + STACK_ALLOCATED(); + CacheInfo(const KURL& manifest, + double creation_time, + double update_time, + long long size) + : manifest_(manifest), + creation_time_(creation_time), + update_time_(update_time), + size_(size) {} + KURL manifest_; + double creation_time_; + double update_time_; + long long size_; + }; + + struct ResourceInfo { + DISALLOW_NEW_EXCEPT_PLACEMENT_NEW(); + ResourceInfo(const KURL& resource, + bool is_master, + bool is_manifest, + bool is_fallback, + bool is_foreign, + bool is_explicit, + long long size) + : resource_(resource), + is_master_(is_master), + is_manifest_(is_manifest), + is_fallback_(is_fallback), + is_foreign_(is_foreign), + is_explicit_(is_explicit), + size_(size) {} + KURL resource_; + bool is_master_; + bool is_manifest_; + bool is_fallback_; + bool is_foreign_; + bool is_explicit_; + long long size_; + }; + + typedef Vector<ResourceInfo> ResourceInfoList; + + void SelectCacheWithoutManifest(); + void SelectCacheWithManifest(const KURL& manifest_url); + + // Annotate request for ApplicationCache. This internally calls + // willStartLoadingMainResource if it's for frame resource or + // willStartLoadingResource for subresource requests. + void WillStartLoading(ResourceRequest&); + + void DidReceiveResponseForMainResource(const ResourceResponse&); + void MainResourceDataReceived(const char* data, size_t length); + void FinishedLoadingMainResource(); + void FailedLoadingMainResource(); + + Status GetStatus() const; + bool Update(); + bool SwapCache(); + void Abort(); + + void SetApplicationCache(ApplicationCache*); + void NotifyApplicationCache(EventID, + int progress_total, + int progress_done, + WebApplicationCacheHost::ErrorReason, + const String& error_url, + int error_status, + const String& error_message); + + void + StopDeferringEvents(); // Also raises the events that have been queued up. + + void FillResourceList(ResourceInfoList*); + CacheInfo ApplicationCacheInfo(); + int GetHostID() const; + + void Trace(blink::Visitor*); + + private: + explicit ApplicationCacheHost(DocumentLoader*); + + void WillStartLoadingMainResource(const KURL&, const String&); + + // WebApplicationCacheHostClient implementation + void DidChangeCacheAssociation() final; + void NotifyEventListener(WebApplicationCacheHost::EventID) final; + void NotifyProgressEventListener(const WebURL&, + int progress_total, + int progress_done) final; + void NotifyErrorEventListener(WebApplicationCacheHost::ErrorReason, + const WebURL&, + int status, + const WebString& message) final; + + bool IsApplicationCacheEnabled(); + DocumentLoader* GetDocumentLoader() const { return document_loader_; } + + struct DeferredEvent { + EventID event_id; + int progress_total; + int progress_done; + WebApplicationCacheHost::ErrorReason error_reason; + String error_url; + int error_status; + String error_message; + DeferredEvent(EventID id, + int progress_total, + int progress_done, + WebApplicationCacheHost::ErrorReason error_reason, + const String& error_url, + int error_status, + const String& error_message) + : event_id(id), + progress_total(progress_total), + progress_done(progress_done), + error_reason(error_reason), + error_url(error_url), + error_status(error_status), + error_message(error_message) {} + }; + + WeakMember<ApplicationCache> dom_application_cache_; + Member<DocumentLoader> document_loader_; + bool defers_events_; // Events are deferred until after document onload. + Vector<DeferredEvent> deferred_events_; + + void DispatchDOMEvent(EventID, + int progress_total, + int progress_done, + WebApplicationCacheHost::ErrorReason, + const String& error_url, + int error_status, + const String& error_message); + + std::unique_ptr<WebApplicationCacheHost> host_; + + FRIEND_TEST_ALL_PREFIXES(DocumentTest, SandboxDisablesAppCache); + + DISALLOW_COPY_AND_ASSIGN(ApplicationCacheHost); +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_APPCACHE_APPLICATION_CACHE_HOST_H_ diff --git a/chromium/third_party/blink/renderer/core/loader/base_fetch_context.cc b/chromium/third_party/blink/renderer/core/loader/base_fetch_context.cc new file mode 100644 index 00000000000..fa62c340061 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/base_fetch_context.cc @@ -0,0 +1,348 @@ +// Copyright 2017 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/base_fetch_context.h" + +#include "services/network/public/mojom/request_context_frame_type.mojom-blink.h" +#include "third_party/blink/renderer/core/execution_context/execution_context.h" +#include "third_party/blink/renderer/core/frame/content_settings_client.h" +#include "third_party/blink/renderer/core/frame/settings.h" +#include "third_party/blink/renderer/core/frame/web_feature.h" +#include "third_party/blink/renderer/core/inspector/console_message.h" +#include "third_party/blink/renderer/core/loader/private/frame_client_hints_preferences_context.h" +#include "third_party/blink/renderer/core/loader/subresource_filter.h" +#include "third_party/blink/renderer/platform/exported/wrapped_resource_request.h" +#include "third_party/blink/renderer/platform/loader/fetch/fetch_initiator_type_names.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource_load_priority.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource_loading_log.h" +#include "third_party/blink/renderer/platform/network/mime/mime_type_registry.h" +#include "third_party/blink/renderer/platform/weborigin/scheme_registry.h" +#include "third_party/blink/renderer/platform/weborigin/security_policy.h" + +namespace blink { + +void BaseFetchContext::AddAdditionalRequestHeaders(ResourceRequest& request, + FetchResourceType type) { + bool is_main_resource = type == kFetchMainResource; + if (!is_main_resource) { + if (!request.DidSetHTTPReferrer()) { + request.SetHTTPReferrer(SecurityPolicy::GenerateReferrer( + GetReferrerPolicy(), request.Url(), GetOutgoingReferrer())); + request.SetHTTPOriginIfNeeded(GetSecurityOrigin()); + } else { + DCHECK_EQ(SecurityPolicy::GenerateReferrer(request.GetReferrerPolicy(), + request.Url(), + request.HttpReferrer()) + .referrer, + request.HttpReferrer()); + request.SetHTTPOriginToMatchReferrerIfNeeded(); + } + } + + auto address_space = GetAddressSpace(); + if (address_space) + request.SetExternalRequestStateFromRequestorAddressSpace(*address_space); +} + +ResourceRequestBlockedReason BaseFetchContext::CanRequest( + Resource::Type type, + const ResourceRequest& resource_request, + const KURL& url, + const ResourceLoaderOptions& options, + SecurityViolationReportingPolicy reporting_policy, + FetchParameters::OriginRestriction origin_restriction, + ResourceRequest::RedirectStatus redirect_status) const { + ResourceRequestBlockedReason blocked_reason = + CanRequestInternal(type, resource_request, url, options, reporting_policy, + origin_restriction, redirect_status); + if (blocked_reason != ResourceRequestBlockedReason::kNone && + reporting_policy == SecurityViolationReportingPolicy::kReport) { + DispatchDidBlockRequest(resource_request, options.initiator_info, + blocked_reason, type); + } + return blocked_reason; +} + +void BaseFetchContext::AddWarningConsoleMessage(const String& message, + LogSource source) const { + // When LogSource is extended, this DCHECK should be replaced with a logic to + // convert LogSource to blink::MessageSource. + DCHECK_EQ(source, kJSSource); + AddConsoleMessage( + ConsoleMessage::Create(kJSMessageSource, kWarningMessageLevel, message)); +} + +void BaseFetchContext::AddErrorConsoleMessage(const String& message, + LogSource source) const { + // When LogSource is extended, this DCHECK should be replaced with a logic to + // convert LogSource to blink::MessageSource. + DCHECK_EQ(source, kJSSource); + AddConsoleMessage( + ConsoleMessage::Create(kJSMessageSource, kErrorMessageLevel, message)); +} + +bool BaseFetchContext::IsAdResource( + const KURL& resource_url, + Resource::Type type, + WebURLRequest::RequestContext request_context) const { + SubresourceFilter* filter = GetSubresourceFilter(); + + // We do not need main document tagging currently so skipping main resources. + if (filter && type != Resource::kMainResource) { + return filter->IsAdResource(resource_url, request_context); + } + return false; +} + +void BaseFetchContext::PrintAccessDeniedMessage(const KURL& url) const { + if (url.IsNull()) + return; + + String message; + if (Url().IsNull()) { + message = "Unsafe attempt to load URL " + url.ElidedString() + '.'; + } else if (url.IsLocalFile() || Url().IsLocalFile()) { + message = "Unsafe attempt to load URL " + url.ElidedString() + + " from frame with URL " + Url().ElidedString() + + ". 'file:' URLs are treated as unique security origins.\n"; + } else { + message = "Unsafe attempt to load URL " + url.ElidedString() + + " from frame with URL " + Url().ElidedString() + + ". Domains, protocols and ports must match.\n"; + } + + AddConsoleMessage(ConsoleMessage::Create(kSecurityMessageSource, + kErrorMessageLevel, message)); +} + +void BaseFetchContext::AddCSPHeaderIfNecessary(Resource::Type type, + ResourceRequest& request) { + const ContentSecurityPolicy* csp = GetContentSecurityPolicy(); + if (!csp) + return; + if (csp->ShouldSendCSPHeader(type)) + request.AddHTTPHeaderField("CSP", "active"); +} + +ResourceRequestBlockedReason BaseFetchContext::CheckCSPForRequest( + WebURLRequest::RequestContext request_context, + const KURL& url, + const ResourceLoaderOptions& options, + SecurityViolationReportingPolicy reporting_policy, + ResourceRequest::RedirectStatus redirect_status) const { + return CheckCSPForRequestInternal( + request_context, url, options, reporting_policy, redirect_status, + ContentSecurityPolicy::CheckHeaderType::kCheckReportOnly); +} + +ResourceRequestBlockedReason BaseFetchContext::CheckCSPForRequestInternal( + WebURLRequest::RequestContext request_context, + const KURL& url, + const ResourceLoaderOptions& options, + SecurityViolationReportingPolicy reporting_policy, + ResourceRequest::RedirectStatus redirect_status, + ContentSecurityPolicy::CheckHeaderType check_header_type) const { + if (ShouldBypassMainWorldCSP() || options.content_security_policy_option == + kDoNotCheckContentSecurityPolicy) { + return ResourceRequestBlockedReason::kNone; + } + + const ContentSecurityPolicy* csp = GetContentSecurityPolicy(); + if (csp && !csp->AllowRequest( + request_context, url, options.content_security_policy_nonce, + options.integrity_metadata, options.parser_disposition, + redirect_status, reporting_policy, check_header_type)) { + return ResourceRequestBlockedReason::kCSP; + } + return ResourceRequestBlockedReason::kNone; +} + +ResourceRequestBlockedReason BaseFetchContext::CanRequestInternal( + Resource::Type type, + const ResourceRequest& resource_request, + const KURL& url, + const ResourceLoaderOptions& options, + SecurityViolationReportingPolicy reporting_policy, + FetchParameters::OriginRestriction origin_restriction, + ResourceRequest::RedirectStatus redirect_status) const { + if (IsDetached()) { + if (!resource_request.GetKeepalive() || + redirect_status == ResourceRequest::RedirectStatus::kNoRedirect) { + return ResourceRequestBlockedReason::kOther; + } + } + + if (ShouldBlockRequestByInspector(resource_request.Url())) + return ResourceRequestBlockedReason::kInspector; + + const SecurityOrigin* security_origin = options.security_origin.get(); + if (!security_origin) + security_origin = GetSecurityOrigin(); + + if (origin_restriction != FetchParameters::kNoOriginRestriction && + security_origin && !security_origin->CanDisplay(url)) { + if (reporting_policy == SecurityViolationReportingPolicy::kReport) { + AddErrorConsoleMessage( + "Not allowed to load local resource: " + url.GetString(), kJSSource); + } + RESOURCE_LOADING_DVLOG(1) << "ResourceFetcher::requestResource URL was not " + "allowed by SecurityOrigin::CanDisplay"; + return ResourceRequestBlockedReason::kOther; + } + + // Some types of resources can be loaded only from the same origin. Other + // types of resources, like Images, Scripts, and CSS, can be loaded from + // any URL. + switch (type) { + case Resource::kMainResource: + case Resource::kImage: + case Resource::kCSSStyleSheet: + case Resource::kScript: + case Resource::kFont: + case Resource::kRaw: + case Resource::kLinkPrefetch: + case Resource::kTextTrack: + case Resource::kImportResource: + case Resource::kAudio: + case Resource::kVideo: + case Resource::kManifest: + case Resource::kMock: + // By default these types of resources can be loaded from any origin. + // FIXME: Are we sure about Resource::kFont? + if (origin_restriction == FetchParameters::kRestrictToSameOrigin && + !security_origin->CanRequest(url)) { + PrintAccessDeniedMessage(url); + return ResourceRequestBlockedReason::kOrigin; + } + break; + case Resource::kXSLStyleSheet: + DCHECK(RuntimeEnabledFeatures::XSLTEnabled()); + FALLTHROUGH; + case Resource::kSVGDocument: + if (!security_origin->CanRequest(url)) { + PrintAccessDeniedMessage(url); + return ResourceRequestBlockedReason::kOrigin; + } + break; + } + + // User Agent CSS stylesheets should only support loading images and should be + // restricted to data urls. + if (options.initiator_info.name == FetchInitiatorTypeNames::uacss) { + if (type == Resource::kImage && url.ProtocolIsData()) { + return ResourceRequestBlockedReason::kNone; + } + return ResourceRequestBlockedReason::kOther; + } + + WebURLRequest::RequestContext request_context = + resource_request.GetRequestContext(); + + // We check the 'report-only' headers before upgrading the request (in + // populateResourceRequest). We check the enforced headers here to ensure we + // block things we ought to block. + if (CheckCSPForRequestInternal( + request_context, url, options, reporting_policy, redirect_status, + ContentSecurityPolicy::CheckHeaderType::kCheckEnforce) == + ResourceRequestBlockedReason::kCSP) { + return ResourceRequestBlockedReason::kCSP; + } + + if (type == Resource::kScript || type == Resource::kImportResource) { + if (!AllowScriptFromSource(url)) { + // TODO(estark): Use a different ResourceRequestBlockedReason here, since + // this check has nothing to do with CSP. https://crbug.com/600795 + return ResourceRequestBlockedReason::kCSP; + } + } + + // SVG Images have unique security rules that prevent all subresource requests + // except for data urls. + if (type != Resource::kMainResource && IsSVGImageChromeClient() && + !url.ProtocolIsData()) + return ResourceRequestBlockedReason::kOrigin; + + network::mojom::RequestContextFrameType frame_type = + resource_request.GetFrameType(); + + // Measure the number of legacy URL schemes ('ftp://') and the number of + // embedded-credential ('http://user:password@...') resources embedded as + // subresources. + if (frame_type != network::mojom::RequestContextFrameType::kTopLevel) { + bool is_subresource = + frame_type == network::mojom::RequestContextFrameType::kNone; + const SecurityOrigin* embedding_origin = + is_subresource ? GetSecurityOrigin() : GetParentSecurityOrigin(); + DCHECK(embedding_origin); + if (SchemeRegistry::ShouldTreatURLSchemeAsLegacy(url.Protocol()) && + !SchemeRegistry::ShouldTreatURLSchemeAsLegacy( + embedding_origin->Protocol())) { + CountDeprecation(WebFeature::kLegacyProtocolEmbeddedAsSubresource); + + return ResourceRequestBlockedReason::kOrigin; + } + + if (ShouldBlockFetchAsCredentialedSubresource(resource_request, url)) + return ResourceRequestBlockedReason::kOrigin; + } + + // Check for mixed content. We do this second-to-last so that when folks block + // mixed content via CSP, they don't get a mixed content warning, but a CSP + // warning instead. + if (ShouldBlockFetchByMixedContentCheck(request_context, frame_type, + resource_request.GetRedirectStatus(), + url, reporting_policy)) + return ResourceRequestBlockedReason::kMixedContent; + + if (url.PotentiallyDanglingMarkup() && url.ProtocolIsInHTTPFamily()) { + CountDeprecation(WebFeature::kCanRequestURLHTTPContainingNewline); + if (RuntimeEnabledFeatures::RestrictCanRequestURLCharacterSetEnabled()) + return ResourceRequestBlockedReason::kOther; + } + + // Let the client have the final say into whether or not the load should + // proceed. + if (GetSubresourceFilter() && type != Resource::kMainResource && + type != Resource::kImportResource) { + if (!GetSubresourceFilter()->AllowLoad(url, request_context, + reporting_policy)) { + return ResourceRequestBlockedReason::kSubresourceFilter; + } + } + + return ResourceRequestBlockedReason::kNone; +} + +ResourceRequestBlockedReason BaseFetchContext::CheckResponseNosniff( + WebURLRequest::RequestContext request_context, + const ResourceResponse& response) const { + bool sniffing_allowed = + ParseContentTypeOptionsHeader(response.HttpHeaderField( + HTTPNames::X_Content_Type_Options)) != kContentTypeOptionsNosniff; + if (sniffing_allowed) + return ResourceRequestBlockedReason::kNone; + + String mime_type = response.HttpContentType(); + if (request_context == WebURLRequest::kRequestContextStyle && + !MIMETypeRegistry::IsSupportedStyleSheetMIMEType(mime_type)) { + AddConsoleMessage(ConsoleMessage::Create( + kSecurityMessageSource, kErrorMessageLevel, + "Refused to apply style from '" + response.Url().ElidedString() + + "' because its MIME type ('" + mime_type + "') " + + "is not a supported stylesheet MIME type, and strict MIME checking " + "is enabled.")); + return ResourceRequestBlockedReason::kContentType; + } + // TODO(mkwst): Move the 'nosniff' bit of 'AllowedByNosniff::MimeTypeAsScript' + // here alongside the style checks, and put its use counters somewhere else. + + return ResourceRequestBlockedReason::kNone; +} + +void BaseFetchContext::Trace(blink::Visitor* visitor) { + FetchContext::Trace(visitor); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/loader/base_fetch_context.h b/chromium/third_party/blink/renderer/core/loader/base_fetch_context.h new file mode 100644 index 00000000000..b167dff8084 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/base_fetch_context.h @@ -0,0 +1,122 @@ +// Copyright 2017 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_BASE_FETCH_CONTEXT_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_BASE_FETCH_CONTEXT_H_ + +#include "third_party/blink/public/mojom/net/ip_address_space.mojom-blink.h" +#include "third_party/blink/public/platform/web_url_request.h" +#include "third_party/blink/renderer/core/core_export.h" +#include "third_party/blink/renderer/core/frame/csp/content_security_policy.h" +#include "third_party/blink/renderer/core/frame/web_feature_forward.h" +#include "third_party/blink/renderer/platform/heap/handle.h" +#include "third_party/blink/renderer/platform/loader/fetch/fetch_context.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource_request.h" +#include "third_party/blink/renderer/platform/weborigin/referrer_policy.h" +#include "third_party/blink/renderer/platform/wtf/optional.h" + +namespace blink { + +class ConsoleMessage; +class KURL; +class SecurityOrigin; +class SubresourceFilter; + +// A core-level implementaiton of FetchContext that does not depend on +// Frame. This class provides basic default implementation for some methods. +class CORE_EXPORT BaseFetchContext : public FetchContext { + public: + void AddAdditionalRequestHeaders(ResourceRequest&, + FetchResourceType) override; + ResourceRequestBlockedReason CanRequest( + Resource::Type, + const ResourceRequest&, + const KURL&, + const ResourceLoaderOptions&, + SecurityViolationReportingPolicy, + FetchParameters::OriginRestriction, + ResourceRequest::RedirectStatus) const override; + ResourceRequestBlockedReason CheckCSPForRequest( + WebURLRequest::RequestContext, + const KURL&, + const ResourceLoaderOptions&, + SecurityViolationReportingPolicy, + ResourceRequest::RedirectStatus) const override; + ResourceRequestBlockedReason CheckResponseNosniff( + WebURLRequest::RequestContext, + const ResourceResponse&) const override; + + void Trace(blink::Visitor*) override; + + virtual KURL GetSiteForCookies() const = 0; + virtual SubresourceFilter* GetSubresourceFilter() const = 0; + virtual void CountUsage(WebFeature) const = 0; + virtual void CountDeprecation(WebFeature) const = 0; + virtual bool ShouldBlockWebSocketByMixedContentCheck(const KURL&) const = 0; + + void AddWarningConsoleMessage(const String&, LogSource) const override; + void AddErrorConsoleMessage(const String&, LogSource) const override; + bool IsAdResource(const KURL&, + Resource::Type, + WebURLRequest::RequestContext) const override; + + protected: + // Used for security checks. + virtual bool AllowScriptFromSource(const KURL&) const = 0; + + // Note: subclasses are expected to override following methods. + // Used in the default implementation for CanRequest, CanFollowRedirect + // and AllowResponse. + virtual bool ShouldBlockRequestByInspector(const KURL&) const = 0; + virtual void DispatchDidBlockRequest(const ResourceRequest&, + const FetchInitiatorInfo&, + ResourceRequestBlockedReason, + Resource::Type) const = 0; + virtual bool ShouldBypassMainWorldCSP() const = 0; + virtual bool IsSVGImageChromeClient() const = 0; + virtual bool ShouldBlockFetchByMixedContentCheck( + WebURLRequest::RequestContext, + network::mojom::RequestContextFrameType, + ResourceRequest::RedirectStatus, + const KURL&, + SecurityViolationReportingPolicy) const = 0; + virtual bool ShouldBlockFetchAsCredentialedSubresource(const ResourceRequest&, + const KURL&) const = 0; + virtual ReferrerPolicy GetReferrerPolicy() const = 0; + virtual String GetOutgoingReferrer() const = 0; + virtual const KURL& Url() const = 0; + virtual const SecurityOrigin* GetParentSecurityOrigin() const = 0; + virtual Optional<mojom::IPAddressSpace> GetAddressSpace() const = 0; + virtual const ContentSecurityPolicy* GetContentSecurityPolicy() const = 0; + + virtual void AddConsoleMessage(ConsoleMessage*) const = 0; + + // Utility method that can be used to implement other methods. + void PrintAccessDeniedMessage(const KURL&) const; + void AddCSPHeaderIfNecessary(Resource::Type, ResourceRequest&); + + private: + // Utility methods that are used in default implement for CanRequest, + // CanFollowRedirect and AllowResponse. + ResourceRequestBlockedReason CanRequestInternal( + Resource::Type, + const ResourceRequest&, + const KURL&, + const ResourceLoaderOptions&, + SecurityViolationReportingPolicy, + FetchParameters::OriginRestriction, + ResourceRequest::RedirectStatus) const; + + ResourceRequestBlockedReason CheckCSPForRequestInternal( + WebURLRequest::RequestContext, + const KURL&, + const ResourceLoaderOptions&, + SecurityViolationReportingPolicy, + ResourceRequest::RedirectStatus, + ContentSecurityPolicy::CheckHeaderType) const; +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_BASE_FETCH_CONTEXT_H_ diff --git a/chromium/third_party/blink/renderer/core/loader/base_fetch_context_test.cc b/chromium/third_party/blink/renderer/core/loader/base_fetch_context_test.cc new file mode 100644 index 00000000000..eec7efb7867 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/base_fetch_context_test.cc @@ -0,0 +1,447 @@ +/* + * Copyright (c) 2015, 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/base_fetch_context.h" + +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/blink/renderer/core/testing/null_execution_context.h" +#include "third_party/blink/renderer/platform/loader/fetch/fetch_initiator_type_names.h" +#include "third_party/blink/renderer/platform/testing/runtime_enabled_features_test_helpers.h" + +namespace blink { + +class MockBaseFetchContext final : public BaseFetchContext { + public: + explicit MockBaseFetchContext(ExecutionContext* execution_context) + : execution_context_(execution_context) {} + ~MockBaseFetchContext() override = default; + + // BaseFetchContext overrides: + KURL GetSiteForCookies() const override { return KURL(); } + bool AllowScriptFromSource(const KURL&) const override { return false; } + SubresourceFilter* GetSubresourceFilter() const override { return nullptr; } + bool ShouldBlockRequestByInspector(const KURL&) const override { + return false; + } + void DispatchDidBlockRequest(const ResourceRequest&, + const FetchInitiatorInfo&, + ResourceRequestBlockedReason, + Resource::Type) const override {} + bool ShouldBypassMainWorldCSP() const override { return false; } + bool IsSVGImageChromeClient() const override { return false; } + void CountUsage(WebFeature) const override {} + void CountDeprecation(WebFeature) const override {} + bool ShouldBlockWebSocketByMixedContentCheck(const KURL&) const override { + return false; + } + bool ShouldBlockFetchByMixedContentCheck( + WebURLRequest::RequestContext, + network::mojom::RequestContextFrameType, + ResourceRequest::RedirectStatus, + const KURL&, + SecurityViolationReportingPolicy) const override { + return false; + } + bool ShouldBlockFetchAsCredentialedSubresource(const ResourceRequest&, + const KURL&) const override { + return false; + } + ReferrerPolicy GetReferrerPolicy() const override { + return execution_context_->GetReferrerPolicy(); + } + String GetOutgoingReferrer() const override { + return execution_context_->OutgoingReferrer(); + } + const KURL& Url() const override { return execution_context_->Url(); } + + const SecurityOrigin* GetSecurityOrigin() const override { + return execution_context_->GetSecurityOrigin(); + } + const SecurityOrigin* GetParentSecurityOrigin() const override { + return nullptr; + } + Optional<mojom::IPAddressSpace> GetAddressSpace() const override { + return WTF::make_optional( + execution_context_->GetSecurityContext().AddressSpace()); + } + const ContentSecurityPolicy* GetContentSecurityPolicy() const override { + return execution_context_->GetContentSecurityPolicy(); + } + void AddConsoleMessage(ConsoleMessage*) const override {} + + void Trace(blink::Visitor* visitor) override { + visitor->Trace(execution_context_); + BaseFetchContext::Trace(visitor); + } + + bool IsDetached() const override { return is_detached_; } + void SetIsDetached(bool is_detached) { is_detached_ = is_detached; } + + private: + Member<ExecutionContext> execution_context_; + bool is_detached_ = false; +}; + +class BaseFetchContextTest : public testing::Test { + protected: + void SetUp() override { + execution_context_ = new NullExecutionContext(); + static_cast<NullExecutionContext*>(execution_context_.Get()) + ->SetUpSecurityContext(); + fetch_context_ = new MockBaseFetchContext(execution_context_); + } + + Persistent<ExecutionContext> execution_context_; + Persistent<MockBaseFetchContext> fetch_context_; +}; + +TEST_F(BaseFetchContextTest, SetIsExternalRequestForPublicContext) { + EXPECT_EQ(mojom::IPAddressSpace::kPublic, + execution_context_->GetSecurityContext().AddressSpace()); + + struct TestCase { + const char* url; + bool is_external_expectation; + } cases[] = { + {"data:text/html,whatever", false}, {"file:///etc/passwd", false}, + {"blob:http://example.com/", false}, + + {"http://example.com/", false}, {"https://example.com/", false}, + + {"http://192.168.1.1:8000/", true}, {"http://10.1.1.1:8000/", true}, + + {"http://localhost/", true}, {"http://127.0.0.1/", true}, + {"http://127.0.0.1:8000/", true}}; + { + ScopedCorsRFC1918ForTest cors_rfc1918(false); + for (const auto& test : cases) { + SCOPED_TRACE(test.url); + ResourceRequest main_request(test.url); + fetch_context_->AddAdditionalRequestHeaders(main_request, + kFetchMainResource); + EXPECT_FALSE(main_request.IsExternalRequest()); + + ResourceRequest sub_request(test.url); + fetch_context_->AddAdditionalRequestHeaders(sub_request, + kFetchSubresource); + EXPECT_FALSE(sub_request.IsExternalRequest()); + } + } + + { + ScopedCorsRFC1918ForTest cors_rfc1918(true); + for (const auto& test : cases) { + SCOPED_TRACE(test.url); + ResourceRequest main_request(test.url); + fetch_context_->AddAdditionalRequestHeaders(main_request, + kFetchMainResource); + EXPECT_EQ(test.is_external_expectation, main_request.IsExternalRequest()); + + ResourceRequest sub_request(test.url); + fetch_context_->AddAdditionalRequestHeaders(sub_request, + kFetchSubresource); + EXPECT_EQ(test.is_external_expectation, sub_request.IsExternalRequest()); + } + } +} + +TEST_F(BaseFetchContextTest, SetIsExternalRequestForPrivateContext) { + execution_context_->GetSecurityContext().SetAddressSpace( + mojom::IPAddressSpace::kPrivate); + EXPECT_EQ(mojom::IPAddressSpace::kPrivate, + execution_context_->GetSecurityContext().AddressSpace()); + + struct TestCase { + const char* url; + bool is_external_expectation; + } cases[] = { + {"data:text/html,whatever", false}, {"file:///etc/passwd", false}, + {"blob:http://example.com/", false}, + + {"http://example.com/", false}, {"https://example.com/", false}, + + {"http://192.168.1.1:8000/", false}, {"http://10.1.1.1:8000/", false}, + + {"http://localhost/", true}, {"http://127.0.0.1/", true}, + {"http://127.0.0.1:8000/", true}}; + { + ScopedCorsRFC1918ForTest cors_rfc1918(false); + for (const auto& test : cases) { + SCOPED_TRACE(test.url); + ResourceRequest main_request(test.url); + fetch_context_->AddAdditionalRequestHeaders(main_request, + kFetchMainResource); + EXPECT_FALSE(main_request.IsExternalRequest()); + + ResourceRequest sub_request(test.url); + fetch_context_->AddAdditionalRequestHeaders(sub_request, + kFetchSubresource); + EXPECT_FALSE(sub_request.IsExternalRequest()); + } + } + + { + ScopedCorsRFC1918ForTest cors_rfc1918(true); + for (const auto& test : cases) { + SCOPED_TRACE(test.url); + ResourceRequest main_request(test.url); + fetch_context_->AddAdditionalRequestHeaders(main_request, + kFetchMainResource); + EXPECT_EQ(test.is_external_expectation, main_request.IsExternalRequest()); + + ResourceRequest sub_request(test.url); + fetch_context_->AddAdditionalRequestHeaders(sub_request, + kFetchSubresource); + EXPECT_EQ(test.is_external_expectation, sub_request.IsExternalRequest()); + } + } +} + +TEST_F(BaseFetchContextTest, SetIsExternalRequestForLocalContext) { + execution_context_->GetSecurityContext().SetAddressSpace( + mojom::IPAddressSpace::kLocal); + EXPECT_EQ(mojom::IPAddressSpace::kLocal, + execution_context_->GetSecurityContext().AddressSpace()); + + struct TestCase { + const char* url; + bool is_external_expectation; + } cases[] = { + {"data:text/html,whatever", false}, {"file:///etc/passwd", false}, + {"blob:http://example.com/", false}, + + {"http://example.com/", false}, {"https://example.com/", false}, + + {"http://192.168.1.1:8000/", false}, {"http://10.1.1.1:8000/", false}, + + {"http://localhost/", false}, {"http://127.0.0.1/", false}, + {"http://127.0.0.1:8000/", false}}; + { + ScopedCorsRFC1918ForTest cors_rfc1918(false); + for (const auto& test : cases) { + ResourceRequest main_request(test.url); + fetch_context_->AddAdditionalRequestHeaders(main_request, + kFetchMainResource); + EXPECT_FALSE(main_request.IsExternalRequest()); + + ResourceRequest sub_request(test.url); + fetch_context_->AddAdditionalRequestHeaders(sub_request, + kFetchSubresource); + EXPECT_FALSE(sub_request.IsExternalRequest()); + } + } + + { + ScopedCorsRFC1918ForTest cors_rfc1918(true); + for (const auto& test : cases) { + ResourceRequest main_request(test.url); + fetch_context_->AddAdditionalRequestHeaders(main_request, + kFetchMainResource); + EXPECT_EQ(test.is_external_expectation, main_request.IsExternalRequest()); + + ResourceRequest sub_request(test.url); + fetch_context_->AddAdditionalRequestHeaders(sub_request, + kFetchSubresource); + EXPECT_EQ(test.is_external_expectation, sub_request.IsExternalRequest()); + } + } +} + +// Tests that CanRequest() checks the enforced CSP headers. +TEST_F(BaseFetchContextTest, CanRequest) { + ContentSecurityPolicy* policy = + execution_context_->GetContentSecurityPolicy(); + policy->DidReceiveHeader("script-src https://foo.test", + kContentSecurityPolicyHeaderTypeEnforce, + kContentSecurityPolicyHeaderSourceHTTP); + policy->DidReceiveHeader("script-src https://bar.test", + kContentSecurityPolicyHeaderTypeReport, + kContentSecurityPolicyHeaderSourceHTTP); + + KURL url(NullURL(), "http://baz.test"); + ResourceRequest resource_request(url); + resource_request.SetRequestContext(WebURLRequest::kRequestContextScript); + resource_request.SetFetchCredentialsMode( + network::mojom::FetchCredentialsMode::kOmit); + + ResourceLoaderOptions options; + + EXPECT_EQ(ResourceRequestBlockedReason::kCSP, + fetch_context_->CanRequest( + Resource::kScript, resource_request, url, options, + SecurityViolationReportingPolicy::kReport, + FetchParameters::kUseDefaultOriginRestrictionForType, + ResourceRequest::RedirectStatus::kFollowedRedirect)); + EXPECT_EQ(1u, policy->violation_reports_sent_.size()); +} + +// Tests that CheckCSPForRequest() checks the report-only CSP headers. +TEST_F(BaseFetchContextTest, CheckCSPForRequest) { + ContentSecurityPolicy* policy = + execution_context_->GetContentSecurityPolicy(); + policy->DidReceiveHeader("script-src https://foo.test", + kContentSecurityPolicyHeaderTypeEnforce, + kContentSecurityPolicyHeaderSourceHTTP); + policy->DidReceiveHeader("script-src https://bar.test", + kContentSecurityPolicyHeaderTypeReport, + kContentSecurityPolicyHeaderSourceHTTP); + + KURL url(NullURL(), "http://baz.test"); + + ResourceLoaderOptions options; + + EXPECT_EQ(ResourceRequestBlockedReason::kNone, + fetch_context_->CheckCSPForRequest( + WebURLRequest::kRequestContextScript, url, options, + SecurityViolationReportingPolicy::kReport, + ResourceRequest::RedirectStatus::kFollowedRedirect)); + EXPECT_EQ(1u, policy->violation_reports_sent_.size()); +} + +TEST_F(BaseFetchContextTest, CanRequestWhenDetached) { + KURL url(NullURL(), "http://www.example.com/"); + ResourceRequest request(url); + ResourceRequest keepalive_request(url); + keepalive_request.SetKeepalive(true); + + EXPECT_EQ(ResourceRequestBlockedReason::kNone, + fetch_context_->CanRequest( + Resource::kRaw, request, url, ResourceLoaderOptions(), + SecurityViolationReportingPolicy::kSuppressReporting, + FetchParameters::kNoOriginRestriction, + ResourceRequest::RedirectStatus::kNoRedirect)); + + EXPECT_EQ(ResourceRequestBlockedReason::kNone, + fetch_context_->CanRequest( + Resource::kRaw, keepalive_request, url, ResourceLoaderOptions(), + SecurityViolationReportingPolicy::kSuppressReporting, + FetchParameters::kNoOriginRestriction, + ResourceRequest::RedirectStatus::kNoRedirect)); + + EXPECT_EQ(ResourceRequestBlockedReason::kNone, + fetch_context_->CanRequest( + Resource::kRaw, request, url, ResourceLoaderOptions(), + SecurityViolationReportingPolicy::kSuppressReporting, + FetchParameters::kNoOriginRestriction, + ResourceRequest::RedirectStatus::kFollowedRedirect)); + + EXPECT_EQ(ResourceRequestBlockedReason::kNone, + fetch_context_->CanRequest( + Resource::kRaw, keepalive_request, url, ResourceLoaderOptions(), + SecurityViolationReportingPolicy::kSuppressReporting, + FetchParameters::kNoOriginRestriction, + ResourceRequest::RedirectStatus::kFollowedRedirect)); + + fetch_context_->SetIsDetached(true); + + EXPECT_EQ(ResourceRequestBlockedReason::kOther, + fetch_context_->CanRequest( + Resource::kRaw, request, url, ResourceLoaderOptions(), + SecurityViolationReportingPolicy::kSuppressReporting, + FetchParameters::kNoOriginRestriction, + ResourceRequest::RedirectStatus::kNoRedirect)); + + EXPECT_EQ(ResourceRequestBlockedReason::kOther, + fetch_context_->CanRequest( + Resource::kRaw, keepalive_request, url, ResourceLoaderOptions(), + SecurityViolationReportingPolicy::kSuppressReporting, + FetchParameters::kNoOriginRestriction, + ResourceRequest::RedirectStatus::kNoRedirect)); + + EXPECT_EQ(ResourceRequestBlockedReason::kOther, + fetch_context_->CanRequest( + Resource::kRaw, request, url, ResourceLoaderOptions(), + SecurityViolationReportingPolicy::kSuppressReporting, + FetchParameters::kNoOriginRestriction, + ResourceRequest::RedirectStatus::kFollowedRedirect)); + + EXPECT_EQ(ResourceRequestBlockedReason::kNone, + fetch_context_->CanRequest( + Resource::kRaw, keepalive_request, url, ResourceLoaderOptions(), + SecurityViolationReportingPolicy::kSuppressReporting, + FetchParameters::kNoOriginRestriction, + ResourceRequest::RedirectStatus::kFollowedRedirect)); +} + +// Test that User Agent CSS can only load images with data urls. +TEST_F(BaseFetchContextTest, UACSSTest) { + KURL test_url("https://example.com"); + KURL data_url("data:image/png;base64,test"); + + ResourceRequest resource_request(test_url); + ResourceLoaderOptions options; + options.initiator_info.name = FetchInitiatorTypeNames::uacss; + + EXPECT_EQ(ResourceRequestBlockedReason::kOther, + fetch_context_->CanRequest( + Resource::kScript, resource_request, test_url, options, + SecurityViolationReportingPolicy::kReport, + FetchParameters::kUseDefaultOriginRestrictionForType, + ResourceRequest::RedirectStatus::kFollowedRedirect)); + + EXPECT_EQ(ResourceRequestBlockedReason::kOther, + fetch_context_->CanRequest( + Resource::kImage, resource_request, test_url, options, + SecurityViolationReportingPolicy::kReport, + FetchParameters::kUseDefaultOriginRestrictionForType, + ResourceRequest::RedirectStatus::kFollowedRedirect)); + + EXPECT_EQ(ResourceRequestBlockedReason::kNone, + fetch_context_->CanRequest( + Resource::kImage, resource_request, data_url, options, + SecurityViolationReportingPolicy::kReport, + FetchParameters::kUseDefaultOriginRestrictionForType, + ResourceRequest::RedirectStatus::kFollowedRedirect)); +} + +// Test that User Agent CSS can bypass CSP to load embedded images. +TEST_F(BaseFetchContextTest, UACSSTest_BypassCSP) { + ContentSecurityPolicy* policy = + execution_context_->GetContentSecurityPolicy(); + policy->DidReceiveHeader("default-src 'self'", + kContentSecurityPolicyHeaderTypeEnforce, + kContentSecurityPolicyHeaderSourceHTTP); + + KURL data_url("data:image/png;base64,test"); + + ResourceRequest resource_request(data_url); + ResourceLoaderOptions options; + options.initiator_info.name = FetchInitiatorTypeNames::uacss; + + EXPECT_EQ(ResourceRequestBlockedReason::kNone, + fetch_context_->CanRequest( + Resource::kImage, resource_request, data_url, options, + SecurityViolationReportingPolicy::kReport, + FetchParameters::kUseDefaultOriginRestrictionForType, + ResourceRequest::RedirectStatus::kFollowedRedirect)); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/loader/cookie_jar.cc b/chromium/third_party/blink/renderer/core/loader/cookie_jar.cc new file mode 100644 index 00000000000..b8fb8ff1d8f --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/cookie_jar.cc @@ -0,0 +1,76 @@ +/* + * 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/cookie_jar.h" + +#include "third_party/blink/public/platform/platform.h" +#include "third_party/blink/public/platform/web_cookie_jar.h" +#include "third_party/blink/public/platform/web_url.h" +#include "third_party/blink/renderer/core/dom/document.h" +#include "third_party/blink/renderer/core/frame/local_frame.h" +#include "third_party/blink/renderer/core/frame/local_frame_client.h" +#include "third_party/blink/renderer/platform/histogram.h" + +namespace blink { + +static WebCookieJar* ToCookieJar(const Document* document) { + if (!document || !document->GetFrame()) + return nullptr; + return document->GetFrame()->Client()->CookieJar(); +} + +String Cookies(const Document* document, const KURL& url) { + WebCookieJar* cookie_jar = ToCookieJar(document); + if (!cookie_jar) + return String(); + + SCOPED_BLINK_UMA_HISTOGRAM_TIMER("Blink.CookieJar.SyncCookiesTime"); + return cookie_jar->Cookies(url, document->SiteForCookies()); +} + +void SetCookies(Document* document, + const KURL& url, + const String& cookie_string) { + WebCookieJar* cookie_jar = ToCookieJar(document); + if (!cookie_jar) + return; + SCOPED_BLINK_UMA_HISTOGRAM_TIMER("Blink.CookieJar.SyncCookiesSetTime"); + cookie_jar->SetCookie(url, document->SiteForCookies(), cookie_string); +} + +bool CookiesEnabled(const Document* document) { + WebCookieJar* cookie_jar = ToCookieJar(document); + if (!cookie_jar) + return false; + return cookie_jar->CookiesEnabled(document->CookieURL(), + document->SiteForCookies()); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/loader/cookie_jar.h b/chromium/third_party/blink/renderer/core/loader/cookie_jar.h new file mode 100644 index 00000000000..ea658d18184 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/cookie_jar.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2003, 2006, 2008, 2012 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_COOKIE_JAR_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_COOKIE_JAR_H_ + +#include "third_party/blink/renderer/platform/wtf/forward.h" +#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h" + +namespace blink { + +class Document; +class KURL; + +String Cookies(const Document*, const KURL&); +void SetCookies(Document*, const KURL&, const String& cookie_string); +bool CookiesEnabled(const Document*); + +} // namespace blink + +#endif diff --git a/chromium/third_party/blink/renderer/core/loader/document_load_timing.cc b/chromium/third_party/blink/renderer/core/loader/document_load_timing.cc new file mode 100644 index 00000000000..c07aa85f0a5 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/document_load_timing.cc @@ -0,0 +1,227 @@ +/* + * Copyright (C) 2011 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: + * 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 GOOGLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GOOGLE 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/document_load_timing.h" + +#include "base/memory/scoped_refptr.h" +#include "third_party/blink/renderer/core/frame/local_frame.h" +#include "third_party/blink/renderer/core/loader/document_loader.h" +#include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h" +#include "third_party/blink/renderer/platform/weborigin/security_origin.h" + +namespace blink { + +DocumentLoadTiming::DocumentLoadTiming(DocumentLoader& document_loader) + : reference_wall_time_(0.0), + redirect_count_(0), + has_cross_origin_redirect_(false), + has_same_origin_as_previous_document_(false), + document_loader_(document_loader) {} + +void DocumentLoadTiming::Trace(blink::Visitor* visitor) { + visitor->Trace(document_loader_); +} + +// TODO(csharrison): Remove the null checking logic in a later patch. +LocalFrame* DocumentLoadTiming::GetFrame() const { + return document_loader_ ? document_loader_->GetFrame() : nullptr; +} + +void DocumentLoadTiming::NotifyDocumentTimingChanged() { + if (document_loader_) + document_loader_->DidChangePerformanceTiming(); +} + +void DocumentLoadTiming::EnsureReferenceTimesSet() { + if (!reference_wall_time_) + reference_wall_time_ = CurrentTime(); + if (reference_monotonic_time_.is_null()) + reference_monotonic_time_ = CurrentTimeTicks(); +} + +double DocumentLoadTiming::MonotonicTimeToZeroBasedDocumentTime( + TimeTicks monotonic_time) const { + if (monotonic_time.is_null() || reference_monotonic_time_.is_null()) + return 0.0; + return (monotonic_time - reference_monotonic_time_).InSecondsF(); +} + +double DocumentLoadTiming::MonotonicTimeToPseudoWallTime( + TimeTicks monotonic_time) const { + if (monotonic_time.is_null() || reference_monotonic_time_.is_null()) + return 0.0; + return (monotonic_time + TimeDelta::FromSecondsD(reference_wall_time_) - + reference_monotonic_time_) + .InSecondsF(); +} + +TimeTicks DocumentLoadTiming::PseudoWallTimeToMonotonicTime( + double pseudo_wall_time) const { + if (!pseudo_wall_time) + return TimeTicks(); + DCHECK_GE(TimeTicksInSeconds(reference_monotonic_time_) + pseudo_wall_time - + reference_wall_time_, + 0); + return reference_monotonic_time_ + + TimeDelta::FromSecondsD(pseudo_wall_time - reference_wall_time_); +} + +void DocumentLoadTiming::MarkNavigationStart() { + // Allow the embedder to override navigationStart before we record it if + // they have a more accurate timestamp. + if (!navigation_start_.is_null()) { + DCHECK(!reference_monotonic_time_.is_null()); + DCHECK(reference_wall_time_); + return; + } + DCHECK(reference_monotonic_time_.is_null()); + DCHECK(!reference_wall_time_); + EnsureReferenceTimesSet(); + navigation_start_ = reference_monotonic_time_; + TRACE_EVENT_MARK_WITH_TIMESTAMP2( + "blink.user_timing", "navigationStart", navigation_start_, "frame", + ToTraceValue(GetFrame()), "data", GetNavigationStartTracingData()); + NotifyDocumentTimingChanged(); +} + +std::unique_ptr<TracedValue> DocumentLoadTiming::GetNavigationStartTracingData() + const { + std::unique_ptr<TracedValue> data = TracedValue::Create(); + data->SetString("documentLoaderURL", + document_loader_ ? document_loader_->Url().GetString() : ""); + data->SetBoolean("isLoadingMainFrame", + GetFrame() ? GetFrame()->IsMainFrame() : false); + return data; +} + +void DocumentLoadTiming::SetNavigationStart(TimeTicks navigation_start) { + // |m_referenceMonotonicTime| and |m_referenceWallTime| represent + // navigationStart. We must set these to the current time if they haven't + // been set yet in order to have a valid reference time in both units. + EnsureReferenceTimesSet(); + navigation_start_ = navigation_start; + TRACE_EVENT_MARK_WITH_TIMESTAMP2( + "blink.user_timing", "navigationStart", navigation_start_, "frame", + ToTraceValue(GetFrame()), "data", GetNavigationStartTracingData()); + + // The reference times are adjusted based on the embedder's navigationStart. + DCHECK(!reference_monotonic_time_.is_null()); + DCHECK(reference_wall_time_); + reference_wall_time_ = MonotonicTimeToPseudoWallTime(navigation_start); + reference_monotonic_time_ = navigation_start; + NotifyDocumentTimingChanged(); +} + +void DocumentLoadTiming::AddRedirect(const KURL& redirecting_url, + const KURL& redirected_url) { + redirect_count_++; + + // Note: we update load timings for redirects in WebDocumentLoaderImpl:: + // UpdateNavigation, hence updating no timings here. + + // Check if the redirected url is allowed to access the redirecting url's + // timing information. + scoped_refptr<const SecurityOrigin> redirected_security_origin = + SecurityOrigin::Create(redirected_url); + has_cross_origin_redirect_ |= + !redirected_security_origin->CanRequest(redirecting_url); +} + +void DocumentLoadTiming::SetRedirectStart(TimeTicks redirect_start) { + redirect_start_ = redirect_start; + TRACE_EVENT_MARK_WITH_TIMESTAMP1("blink.user_timing", "redirectStart", + redirect_start_, "frame", + ToTraceValue(GetFrame())); + NotifyDocumentTimingChanged(); +} + +void DocumentLoadTiming::SetRedirectEnd(TimeTicks redirect_end) { + redirect_end_ = redirect_end; + TRACE_EVENT_MARK_WITH_TIMESTAMP1("blink.user_timing", "redirectEnd", + redirect_end_, "frame", + ToTraceValue(GetFrame())); + NotifyDocumentTimingChanged(); +} + +void DocumentLoadTiming::MarkUnloadEventStart(TimeTicks start_time) { + unload_event_start_ = start_time; + TRACE_EVENT_MARK_WITH_TIMESTAMP1("blink.user_timing", "unloadEventStart", + start_time, "frame", + ToTraceValue(GetFrame())); + NotifyDocumentTimingChanged(); +} + +void DocumentLoadTiming::MarkUnloadEventEnd(TimeTicks end_time) { + unload_event_end_ = end_time; + TRACE_EVENT_MARK_WITH_TIMESTAMP1("blink.user_timing", "unloadEventEnd", + end_time, "frame", ToTraceValue(GetFrame())); + NotifyDocumentTimingChanged(); +} + +void DocumentLoadTiming::MarkFetchStart() { + SetFetchStart(CurrentTimeTicks()); +} + +void DocumentLoadTiming::SetFetchStart(TimeTicks fetch_start) { + fetch_start_ = fetch_start; + TRACE_EVENT_MARK_WITH_TIMESTAMP1("blink.user_timing", "fetchStart", + fetch_start_, "frame", + ToTraceValue(GetFrame())); + NotifyDocumentTimingChanged(); +} + +void DocumentLoadTiming::SetResponseEnd(TimeTicks response_end) { + response_end_ = response_end; + TRACE_EVENT_MARK_WITH_TIMESTAMP1("blink.user_timing", "responseEnd", + response_end_, "frame", + ToTraceValue(GetFrame())); + NotifyDocumentTimingChanged(); +} + +void DocumentLoadTiming::MarkLoadEventStart() { + load_event_start_ = CurrentTimeTicks(); + TRACE_EVENT_MARK_WITH_TIMESTAMP1("blink.user_timing", "loadEventStart", + load_event_start_, "frame", + ToTraceValue(GetFrame())); + NotifyDocumentTimingChanged(); +} + +void DocumentLoadTiming::MarkLoadEventEnd() { + load_event_end_ = CurrentTimeTicks(); + TRACE_EVENT_MARK_WITH_TIMESTAMP1("blink.user_timing", "loadEventEnd", + load_event_end_, "frame", + ToTraceValue(GetFrame())); + NotifyDocumentTimingChanged(); +} + +void DocumentLoadTiming::MarkRedirectEnd() { + redirect_end_ = CurrentTimeTicks(); + TRACE_EVENT_MARK_WITH_TIMESTAMP1("blink.user_timing", "redirectEnd", + redirect_end_, "frame", + ToTraceValue(GetFrame())); + NotifyDocumentTimingChanged(); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/loader/document_load_timing.h b/chromium/third_party/blink/renderer/core/loader/document_load_timing.h new file mode 100644 index 00000000000..20e23aea5f2 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/document_load_timing.h @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2010 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: + * 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 GOOGLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GOOGLE 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_DOCUMENT_LOAD_TIMING_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_DOCUMENT_LOAD_TIMING_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/instrumentation/tracing/traced_value.h" +#include "third_party/blink/renderer/platform/wtf/time.h" + +namespace blink { + +class DocumentLoader; +class KURL; +class LocalFrame; + +class CORE_EXPORT DocumentLoadTiming final { + DISALLOW_NEW(); + + public: + explicit DocumentLoadTiming(DocumentLoader&); + + double MonotonicTimeToZeroBasedDocumentTime(TimeTicks) const; + double MonotonicTimeToPseudoWallTime(TimeTicks) const; + TimeTicks PseudoWallTimeToMonotonicTime(double) const; + + void MarkNavigationStart(); + void SetNavigationStart(TimeTicks); + + void AddRedirect(const KURL& redirecting_url, const KURL& redirected_url); + void SetRedirectStart(TimeTicks); + void SetRedirectEnd(TimeTicks); + void SetRedirectCount(short value) { redirect_count_ = value; } + void SetHasCrossOriginRedirect(bool value) { + has_cross_origin_redirect_ = value; + } + + void MarkUnloadEventStart(TimeTicks); + void MarkUnloadEventEnd(TimeTicks); + + void MarkFetchStart(); + void SetFetchStart(TimeTicks); + + void SetResponseEnd(TimeTicks); + + void MarkLoadEventStart(); + void MarkLoadEventEnd(); + + void SetHasSameOriginAsPreviousDocument(bool value) { + has_same_origin_as_previous_document_ = value; + } + + TimeTicks NavigationStart() const { return navigation_start_; } + TimeTicks UnloadEventStart() const { return unload_event_start_; } + TimeTicks UnloadEventEnd() const { return unload_event_end_; } + TimeTicks RedirectStart() const { return redirect_start_; } + TimeTicks RedirectEnd() const { return redirect_end_; } + short RedirectCount() const { return redirect_count_; } + TimeTicks FetchStart() const { return fetch_start_; } + TimeTicks ResponseEnd() const { return response_end_; } + TimeTicks LoadEventStart() const { return load_event_start_; } + TimeTicks LoadEventEnd() const { return load_event_end_; } + bool HasCrossOriginRedirect() const { return has_cross_origin_redirect_; } + bool HasSameOriginAsPreviousDocument() const { + return has_same_origin_as_previous_document_; + } + + TimeTicks ReferenceMonotonicTime() const { return reference_monotonic_time_; } + + void Trace(blink::Visitor*); + + private: + void MarkRedirectEnd(); + void NotifyDocumentTimingChanged(); + void EnsureReferenceTimesSet(); + LocalFrame* GetFrame() const; + std::unique_ptr<TracedValue> GetNavigationStartTracingData() const; + + TimeTicks reference_monotonic_time_; + double reference_wall_time_; + TimeTicks navigation_start_; + TimeTicks unload_event_start_; + TimeTicks unload_event_end_; + TimeTicks redirect_start_; + TimeTicks redirect_end_; + short redirect_count_; + TimeTicks fetch_start_; + TimeTicks response_end_; + TimeTicks load_event_start_; + TimeTicks load_event_end_; + bool has_cross_origin_redirect_; + bool has_same_origin_as_previous_document_; + + Member<DocumentLoader> document_loader_; +}; + +} // namespace blink + +#endif diff --git a/chromium/third_party/blink/renderer/core/loader/document_load_timing_test.cc b/chromium/third_party/blink/renderer/core/loader/document_load_timing_test.cc new file mode 100644 index 00000000000..e1264bdb000 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/document_load_timing_test.cc @@ -0,0 +1,57 @@ +// 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/document_load_timing.h" + +#include <memory> +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/blink/renderer/core/loader/document_loader.h" +#include "third_party/blink/renderer/core/testing/dummy_page_holder.h" + +namespace blink { + +class DocumentLoadTimingTest : public testing::Test {}; + +TEST_F(DocumentLoadTimingTest, ensureValidNavigationStartAfterEmbedder) { + std::unique_ptr<DummyPageHolder> dummy_page = DummyPageHolder::Create(); + DocumentLoadTiming timing(*(dummy_page->GetDocument().Loader())); + + double delta = -1000; + double embedder_navigation_start = CurrentTimeTicksInSeconds() + delta; + timing.SetNavigationStart(TimeTicksFromSeconds(embedder_navigation_start)); + + double real_wall_time = CurrentTime(); + double adjusted_wall_time = + timing.MonotonicTimeToPseudoWallTime(timing.NavigationStart()); + + EXPECT_NEAR(adjusted_wall_time, real_wall_time + delta, .001); +} + +TEST_F(DocumentLoadTimingTest, correctTimingDeltas) { + std::unique_ptr<DummyPageHolder> dummy_page = DummyPageHolder::Create(); + DocumentLoadTiming timing(*(dummy_page->GetDocument().Loader())); + + double navigation_start_delta = -456; + double current_monotonic_time = CurrentTimeTicksInSeconds(); + double embedder_navigation_start = + current_monotonic_time + navigation_start_delta; + + timing.SetNavigationStart(TimeTicksFromSeconds(embedder_navigation_start)); + + // Super quick load! Expect the wall time reported by this event to be + // dominated by the navigationStartDelta, but similar to currentTime(). + timing.MarkLoadEventEnd(); + double real_wall_load_event_end = CurrentTime(); + double adjusted_load_event_end = + timing.MonotonicTimeToPseudoWallTime(timing.LoadEventEnd()); + + EXPECT_NEAR(adjusted_load_event_end, real_wall_load_event_end, .001); + + double adjusted_navigation_start = + timing.MonotonicTimeToPseudoWallTime(timing.NavigationStart()); + EXPECT_NEAR(adjusted_load_event_end - adjusted_navigation_start, + -navigation_start_delta, .001); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/loader/document_loader.cc b/chromium/third_party/blink/renderer/core/loader/document_loader.cc new file mode 100644 index 00000000000..590e571692f --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/document_loader.cc @@ -0,0 +1,1207 @@ +/* + * Copyright (C) 2006, 2007, 2008 Apple Inc. All rights reserved. + * Copyright (C) 2011 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: + * + * 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") 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 APPLE AND ITS 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 APPLE OR ITS 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/document_loader.h" + +#include <memory> +#include "third_party/blink/public/platform/modules/serviceworker/web_service_worker_network_provider.h" +#include "third_party/blink/public/platform/platform.h" +#include "third_party/blink/public/web/web_history_commit_type.h" +#include "third_party/blink/renderer/core/dom/document.h" +#include "third_party/blink/renderer/core/dom/document_parser.h" +#include "third_party/blink/renderer/core/dom/events/event.h" +#include "third_party/blink/renderer/core/dom/scriptable_document_parser.h" +#include "third_party/blink/renderer/core/dom/user_gesture_indicator.h" +#include "third_party/blink/renderer/core/dom/weak_identifier_map.h" +#include "third_party/blink/renderer/core/frame/csp/content_security_policy.h" +#include "third_party/blink/renderer/core/frame/deprecation.h" +#include "third_party/blink/renderer/core/frame/frame_console.h" +#include "third_party/blink/renderer/core/frame/local_dom_window.h" +#include "third_party/blink/renderer/core/frame/local_frame.h" +#include "third_party/blink/renderer/core/frame/local_frame_client.h" +#include "third_party/blink/renderer/core/frame/settings.h" +#include "third_party/blink/renderer/core/html/html_frame_owner_element.h" +#include "third_party/blink/renderer/core/html/parser/css_preload_scanner.h" +#include "third_party/blink/renderer/core/html/parser/html_parser_idioms.h" +#include "third_party/blink/renderer/core/html/parser/text_resource_decoder.h" +#include "third_party/blink/renderer/core/inspector/InspectorTraceEvents.h" +#include "third_party/blink/renderer/core/inspector/console_message.h" +#include "third_party/blink/renderer/core/inspector/main_thread_debugger.h" +#include "third_party/blink/renderer/core/loader/appcache/application_cache_host.h" +#include "third_party/blink/renderer/core/loader/frame_fetch_context.h" +#include "third_party/blink/renderer/core/loader/frame_loader.h" +#include "third_party/blink/renderer/core/loader/idleness_detector.h" +#include "third_party/blink/renderer/core/loader/interactive_detector.h" +#include "third_party/blink/renderer/core/loader/link_loader.h" +#include "third_party/blink/renderer/core/loader/network_hints_interface.h" +#include "third_party/blink/renderer/core/loader/progress_tracker.h" +#include "third_party/blink/renderer/core/loader/resource/css_style_sheet_resource.h" +#include "third_party/blink/renderer/core/loader/resource/font_resource.h" +#include "third_party/blink/renderer/core/loader/resource/image_resource.h" +#include "third_party/blink/renderer/core/loader/resource/script_resource.h" +#include "third_party/blink/renderer/core/loader/subresource_filter.h" +#include "third_party/blink/renderer/core/origin_trials/origin_trial_context.h" +#include "third_party/blink/renderer/core/page/frame_tree.h" +#include "third_party/blink/renderer/core/page/page.h" +#include "third_party/blink/renderer/core/probe/core_probes.h" +#include "third_party/blink/renderer/core/timing/dom_window_performance.h" +#include "third_party/blink/renderer/core/timing/window_performance.h" +#include "third_party/blink/renderer/platform/feature_policy/feature_policy.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/fetch_utils.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/resource_timing_info.h" +#include "third_party/blink/renderer/platform/mhtml/archive_resource.h" +#include "third_party/blink/renderer/platform/mhtml/mhtml_archive.h" +#include "third_party/blink/renderer/platform/network/content_security_policy_response_headers.h" +#include "third_party/blink/renderer/platform/network/http_names.h" +#include "third_party/blink/renderer/platform/network/http_parsers.h" +#include "third_party/blink/renderer/platform/network/mime/mime_type_registry.h" +#include "third_party/blink/renderer/platform/network/network_utils.h" +#include "third_party/blink/renderer/platform/plugins/plugin_data.h" +#include "third_party/blink/renderer/platform/scheduler/public/frame_scheduler.h" +#include "third_party/blink/renderer/platform/weborigin/scheme_registry.h" +#include "third_party/blink/renderer/platform/weborigin/security_policy.h" +#include "third_party/blink/renderer/platform/wtf/assertions.h" +#include "third_party/blink/renderer/platform/wtf/auto_reset.h" +#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h" + +namespace blink { + +// The MHTML mime type should be same as the one we check in the browser +// process's IsDownload (navigation_url_loader_network_service.cc). +static bool IsArchiveMIMEType(const String& mime_type) { + return DeprecatedEqualIgnoringCase("multipart/related", mime_type) || + DeprecatedEqualIgnoringCase("message/rfc822", mime_type); +} + +DocumentLoader::DocumentLoader( + LocalFrame* frame, + const ResourceRequest& req, + const SubstituteData& substitute_data, + ClientRedirectPolicy client_redirect_policy, + const base::UnguessableToken& devtools_navigation_token) + : frame_(frame), + fetcher_(FrameFetchContext::CreateFetcherFromDocumentLoader(this)), + original_request_(req), + substitute_data_(substitute_data), + request_(req), + load_type_(kFrameLoadTypeStandard), + is_client_redirect_(client_redirect_policy == + ClientRedirectPolicy::kClientRedirect), + replaces_current_history_item_(false), + data_received_(false), + navigation_type_(kNavigationTypeOther), + document_load_timing_(*this), + application_cache_host_(ApplicationCacheHost::Create(this)), + was_blocked_after_csp_(false), + state_(kNotStarted), + committed_data_buffer_(nullptr), + in_data_received_(false), + data_buffer_(SharedBuffer::Create()), + devtools_navigation_token_(devtools_navigation_token), + user_activated_(false) { + DCHECK(frame_); + + // The document URL needs to be added to the head of the list as that is + // where the redirects originated. + if (is_client_redirect_) + AppendRedirect(frame_->GetDocument()->Url()); +} + +FrameLoader& DocumentLoader::GetFrameLoader() const { + DCHECK(frame_); + return frame_->Loader(); +} + +LocalFrameClient& DocumentLoader::GetLocalFrameClient() const { + DCHECK(frame_); + LocalFrameClient* client = frame_->Client(); + // LocalFrame clears its |m_client| only after detaching all DocumentLoaders + // (i.e. calls detachFromFrame() which clears |frame_|) owned by the + // LocalFrame's FrameLoader. So, if |frame_| is non nullptr, |client| is + // also non nullptr. + DCHECK(client); + return *client; +} + +DocumentLoader::~DocumentLoader() { + DCHECK(!frame_); + DCHECK(!GetResource()); + DCHECK(!application_cache_host_); + DCHECK_EQ(state_, kSentDidFinishLoad); +} + +void DocumentLoader::Trace(blink::Visitor* visitor) { + visitor->Trace(frame_); + visitor->Trace(fetcher_); + visitor->Trace(history_item_); + visitor->Trace(parser_); + visitor->Trace(subresource_filter_); + visitor->Trace(document_load_timing_); + visitor->Trace(application_cache_host_); + visitor->Trace(content_security_policy_); + RawResourceClient::Trace(visitor); +} + +unsigned long DocumentLoader::MainResourceIdentifier() const { + return GetResource() ? GetResource()->Identifier() : 0; +} + +ResourceTimingInfo* DocumentLoader::GetNavigationTimingInfo() const { + DCHECK(Fetcher()); + return Fetcher()->GetNavigationTimingInfo(); +} + +const ResourceRequest& DocumentLoader::OriginalRequest() const { + return original_request_; +} + +const ResourceRequest& DocumentLoader::GetRequest() const { + return request_; +} + +void DocumentLoader::SetSubresourceFilter( + SubresourceFilter* subresource_filter) { + subresource_filter_ = subresource_filter; +} + +const KURL& DocumentLoader::Url() const { + return request_.Url(); +} + +Resource* DocumentLoader::StartPreload(Resource::Type type, + FetchParameters& params, + CSSPreloaderResourceClient* client) { + Resource* resource = nullptr; + DCHECK(!client || type == Resource::kCSSStyleSheet); + switch (type) { + case Resource::kImage: + if (frame_) + frame_->MaybeAllowImagePlaceholder(params); + resource = ImageResource::Fetch(params, Fetcher()); + break; + case Resource::kScript: + resource = ScriptResource::Fetch(params, Fetcher(), nullptr); + break; + case Resource::kCSSStyleSheet: + resource = CSSStyleSheetResource::Fetch(params, Fetcher(), client); + break; + case Resource::kFont: + resource = FontResource::Fetch(params, Fetcher(), nullptr); + break; + case Resource::kAudio: + case Resource::kVideo: + resource = RawResource::FetchMedia(params, Fetcher(), nullptr); + break; + case Resource::kTextTrack: + resource = RawResource::FetchTextTrack(params, Fetcher(), nullptr); + break; + case Resource::kImportResource: + resource = RawResource::FetchImport(params, Fetcher(), nullptr); + break; + case Resource::kRaw: + resource = RawResource::Fetch(params, Fetcher(), nullptr); + break; + default: + NOTREACHED(); + } + + return resource; +} + +void DocumentLoader::SetServiceWorkerNetworkProvider( + std::unique_ptr<WebServiceWorkerNetworkProvider> provider) { + service_worker_network_provider_ = std::move(provider); +} + +void DocumentLoader::SetSourceLocation( + std::unique_ptr<SourceLocation> source_location) { + source_location_ = std::move(source_location); +} + +std::unique_ptr<SourceLocation> DocumentLoader::CopySourceLocation() const { + return source_location_ ? source_location_->Clone() : nullptr; +} + +void DocumentLoader::DispatchLinkHeaderPreloads( + ViewportDescriptionWrapper* viewport, + LinkLoader::MediaPreloadPolicy media_policy) { + DCHECK_GE(state_, kCommitted); + LinkLoader::LoadLinksFromHeader( + GetResponse().HttpHeaderField(HTTPNames::Link), GetResponse().Url(), + *frame_, frame_->GetDocument(), NetworkHintsInterfaceImpl(), + LinkLoader::kOnlyLoadResources, media_policy, viewport); +} + +void DocumentLoader::DidChangePerformanceTiming() { + if (frame_ && state_ >= kCommitted) { + GetLocalFrameClient().DidChangePerformanceTiming(); + } +} + +void DocumentLoader::DidObserveLoadingBehavior( + WebLoadingBehaviorFlag behavior) { + if (frame_) { + DCHECK_GE(state_, kCommitted); + GetLocalFrameClient().DidObserveLoadingBehavior(behavior); + } +} + +void DocumentLoader::MarkAsCommitted() { + DCHECK_LT(state_, kCommitted); + state_ = kCommitted; +} + +static HistoryCommitType LoadTypeToCommitType(FrameLoadType type) { + switch (type) { + case kFrameLoadTypeStandard: + return kStandardCommit; + case kFrameLoadTypeInitialInChildFrame: + case kFrameLoadTypeInitialHistoryLoad: + return kInitialCommitInChildFrame; + case kFrameLoadTypeBackForward: + return kBackForwardCommit; + default: + break; + } + return kHistoryInertCommit; +} + +void DocumentLoader::UpdateForSameDocumentNavigation( + const KURL& new_url, + SameDocumentNavigationSource same_document_navigation_source, + scoped_refptr<SerializedScriptValue> data, + HistoryScrollRestorationType scroll_restoration_type, + FrameLoadType type, + Document* initiating_document) { + if (type == kFrameLoadTypeStandard && initiating_document && + !initiating_document->CanCreateHistoryEntry()) { + type = kFrameLoadTypeReplaceCurrentItem; + } + + KURL old_url = request_.Url(); + original_request_.SetURL(new_url); + request_.SetURL(new_url); + SetReplacesCurrentHistoryItem(type != kFrameLoadTypeStandard); + if (same_document_navigation_source == kSameDocumentNavigationHistoryApi) { + request_.SetHTTPMethod(HTTPNames::GET); + request_.SetHTTPBody(nullptr); + } + ClearRedirectChain(); + if (is_client_redirect_) + AppendRedirect(old_url); + AppendRedirect(new_url); + + SetHistoryItemStateForCommit( + history_item_.Get(), type, + same_document_navigation_source == kSameDocumentNavigationHistoryApi + ? HistoryNavigationType::kHistoryApi + : HistoryNavigationType::kFragment); + history_item_->SetDocumentState(frame_->GetDocument()->FormElementsState()); + if (same_document_navigation_source == kSameDocumentNavigationHistoryApi) { + history_item_->SetStateObject(std::move(data)); + history_item_->SetScrollRestorationType(scroll_restoration_type); + } + HistoryCommitType commit_type = LoadTypeToCommitType(type); + frame_->GetFrameScheduler()->DidCommitProvisionalLoad( + commit_type == kHistoryInertCommit, type == kFrameLoadTypeReload, + frame_->IsLocalRoot()); + GetLocalFrameClient().DispatchDidNavigateWithinPage( + history_item_.Get(), commit_type, initiating_document); + probe::didNavigateWithinDocument(frame_); +} + +const KURL& DocumentLoader::UrlForHistory() const { + return UnreachableURL().IsEmpty() ? Url() : UnreachableURL(); +} + +void DocumentLoader::SetHistoryItemStateForCommit( + HistoryItem* old_item, + FrameLoadType load_type, + HistoryNavigationType navigation_type) { + if (!history_item_ || !IsBackForwardLoadType(load_type)) + history_item_ = HistoryItem::Create(); + + history_item_->SetURL(UrlForHistory()); + history_item_->SetReferrer(SecurityPolicy::GenerateReferrer( + request_.GetReferrerPolicy(), history_item_->Url(), + request_.HttpReferrer())); + history_item_->SetFormInfoFromRequest(request_); + + // Don't propagate state from the old item to the new item if there isn't an + // old item (obviously), or if this is a back/forward navigation, since we + // explicitly want to restore the state we just committed. + if (!old_item || IsBackForwardLoadType(load_type)) + return; + // Don't propagate state from the old item if this is a different-document + // navigation, unless the before and after pages are logically related. This + // means they have the same url (ignoring fragment) and the new item was + // loaded via reload or client redirect. + HistoryCommitType history_commit_type = LoadTypeToCommitType(load_type); + if (navigation_type == HistoryNavigationType::kDifferentDocument && + (history_commit_type != kHistoryInertCommit || + !EqualIgnoringFragmentIdentifier(old_item->Url(), history_item_->Url()))) + return; + history_item_->SetDocumentSequenceNumber(old_item->DocumentSequenceNumber()); + + history_item_->CopyViewStateFrom(old_item); + history_item_->SetScrollRestorationType(old_item->ScrollRestorationType()); + + // The item sequence number determines whether items are "the same", such + // back/forward navigation between items with the same item sequence number is + // a no-op. Only treat this as identical if the navigation did not create a + // back/forward entry and the url is identical or it was loaded via + // history.replaceState(). + if (history_commit_type == kHistoryInertCommit && + (navigation_type == HistoryNavigationType::kHistoryApi || + old_item->Url() == history_item_->Url())) { + history_item_->SetStateObject(old_item->StateObject()); + history_item_->SetItemSequenceNumber(old_item->ItemSequenceNumber()); + } +} + +void DocumentLoader::NotifyFinished(Resource* resource) { + DCHECK_EQ(GetResource(), resource); + DCHECK(GetResource()); + + if (!resource->ErrorOccurred() && !resource->WasCanceled()) { + FinishedLoading(TimeTicksFromSeconds(resource->LoadFinishTime())); + return; + } + + if (application_cache_host_) + application_cache_host_->FailedLoadingMainResource(); + + if (resource->GetResourceError().WasBlockedByResponse()) { + probe::CanceledAfterReceivedResourceResponse( + frame_, this, MainResourceIdentifier(), resource->GetResponse(), + resource); + } + + LoadFailed(resource->GetResourceError()); + ClearResource(); +} + +void DocumentLoader::LoadFailed(const ResourceError& error) { + if (!error.IsCancellation() && frame_->Owner()) + frame_->Owner()->RenderFallbackContent(); + fetcher_->ClearResourcesFromPreviousFetcher(); + + HistoryCommitType history_commit_type = LoadTypeToCommitType(load_type_); + switch (state_) { + case kNotStarted: + probe::frameClearedScheduledClientNavigation(frame_); + FALLTHROUGH; + case kProvisional: + state_ = kSentDidFinishLoad; + GetLocalFrameClient().DispatchDidFailProvisionalLoad(error, + history_commit_type); + if (frame_) + GetFrameLoader().DetachProvisionalDocumentLoader(this); + break; + case kCommitted: + if (frame_->GetDocument()->Parser()) + frame_->GetDocument()->Parser()->StopParsing(); + state_ = kSentDidFinishLoad; + GetLocalFrameClient().DispatchDidFailLoad(error, history_commit_type); + GetFrameLoader().DidFinishNavigation(); + break; + case kSentDidFinishLoad: + NOTREACHED(); + break; + } + DCHECK_EQ(kSentDidFinishLoad, state_); +} + +void DocumentLoader::SetUserActivated() { + user_activated_ = true; +} + +const AtomicString& DocumentLoader::RequiredCSP() { + return GetFrameLoader().RequiredCSP(); +} + +void DocumentLoader::FinishedLoading(TimeTicks finish_time) { + DCHECK(frame_->Loader().StateMachine()->CreatingInitialEmptyDocument() || + !frame_->GetPage()->Paused() || + MainThreadDebugger::Instance()->IsPaused()); + + TimeTicks response_end_time = finish_time; + if (response_end_time.is_null()) + response_end_time = time_of_last_data_received_; + if (response_end_time.is_null()) + response_end_time = CurrentTimeTicks(); + GetTiming().SetResponseEnd(response_end_time); + if (!MaybeCreateArchive()) { + // If this is an empty document, it will not have actually been created yet. + // Force a commit so that the Document actually gets created. + if (state_ == kProvisional) + CommitData(nullptr, 0); + } + + if (!frame_) + return; + + application_cache_host_->FinishedLoadingMainResource(); + if (parser_) { + if (parser_blocked_count_) { + finished_loading_ = true; + } else { + parser_->Finish(); + parser_.Clear(); + } + } + ClearResource(); +} + +bool DocumentLoader::RedirectReceived( + Resource* resource, + const ResourceRequest& request, + const ResourceResponse& redirect_response) { + DCHECK(frame_); + DCHECK_EQ(resource, GetResource()); + DCHECK(!redirect_response.IsNull()); + request_ = request; + + // If the redirecting url is not allowed to display content from the target + // origin, then block the redirect. + const KURL& request_url = request_.Url(); + scoped_refptr<const SecurityOrigin> redirecting_origin = + SecurityOrigin::Create(redirect_response.Url()); + if (!redirecting_origin->CanDisplay(request_url)) { + frame_->Console().AddMessage(ConsoleMessage::Create( + kSecurityMessageSource, kErrorMessageLevel, + "Not allowed to load local resource: " + request_url.GetString())); + fetcher_->StopFetching(); + return false; + } + if (GetFrameLoader().ShouldContinueForRedirectNavigationPolicy( + request_, SubstituteData(), this, kCheckContentSecurityPolicy, + navigation_type_, kNavigationPolicyCurrentTab, load_type_, + IsClientRedirect(), nullptr) != kNavigationPolicyCurrentTab) { + fetcher_->StopFetching(); + return false; + } + + DCHECK(!GetTiming().FetchStart().is_null()); + AppendRedirect(request_url); + GetTiming().AddRedirect(redirect_response.Url(), request_url); + + // If a redirection happens during a back/forward navigation, don't restore + // any state from the old HistoryItem. There is a provisional history item for + // back/forward navigation only. In the other case, clearing it is a no-op. + history_item_.Clear(); + + GetLocalFrameClient().DispatchDidReceiveServerRedirectForProvisionalLoad(); + + return true; +} + +static bool CanShowMIMEType(const String& mime_type, LocalFrame* frame) { + if (MIMETypeRegistry::IsSupportedMIMEType(mime_type)) + return true; + PluginData* plugin_data = frame->GetPluginData(); + return !mime_type.IsEmpty() && plugin_data && + plugin_data->SupportsMimeType(mime_type); +} + +bool DocumentLoader::ShouldContinueForResponse() const { + if (substitute_data_.IsValid()) + return true; + + int status_code = response_.HttpStatusCode(); + if (status_code == 204 || status_code == 205) { + // The server does not want us to replace the page contents. + return false; + } + + if (IsContentDispositionAttachment( + response_.HttpHeaderField(HTTPNames::Content_Disposition))) { + // The server wants us to download instead of replacing the page contents. + // Downloading is handled by the embedder, but we still get the initial + // response so that we can ignore it and clean up properly. + return false; + } + + if (!CanShowMIMEType(response_.MimeType(), frame_)) + return false; + return true; +} + +void DocumentLoader::CancelLoadAfterCSPDenied( + const ResourceResponse& response) { + probe::CanceledAfterReceivedResourceResponse( + frame_, this, MainResourceIdentifier(), response, GetResource()); + + SetWasBlockedAfterCSP(); + + // Pretend that this was an empty HTTP 200 response. Don't reuse the original + // URL for the empty page (https://crbug.com/622385). + // + // TODO(mkwst): Remove this once XFO moves to the browser. + // https://crbug.com/555418. + ClearResource(); + content_security_policy_.Clear(); + KURL blocked_url = SecurityOrigin::UrlWithUniqueSecurityOrigin(); + original_request_.SetURL(blocked_url); + request_.SetURL(blocked_url); + redirect_chain_.pop_back(); + AppendRedirect(blocked_url); + response_ = ResourceResponse(blocked_url, "text/html"); + FinishedLoading(CurrentTimeTicks()); + + return; +} + +void DocumentLoader::ResponseReceived( + Resource* resource, + const ResourceResponse& response, + std::unique_ptr<WebDataConsumerHandle> handle) { + DCHECK_EQ(GetResource(), resource); + DCHECK(!handle); + DCHECK(frame_); + + application_cache_host_->DidReceiveResponseForMainResource(response); + + // The memory cache doesn't understand the application cache or its caching + // rules. So if a main resource is served from the application cache, ensure + // we don't save the result for future use. All responses loaded from appcache + // will have a non-zero appCacheID(). + if (response.AppCacheID()) + GetMemoryCache()->Remove(resource); + + content_security_policy_ = ContentSecurityPolicy::Create(); + content_security_policy_->SetOverrideURLForSelf(response.Url()); + if (!frame_->GetSettings()->BypassCSP()) { + content_security_policy_->DidReceiveHeaders( + ContentSecurityPolicyResponseHeaders(response)); + } + if (!content_security_policy_->AllowAncestors(frame_, response.Url())) { + CancelLoadAfterCSPDenied(response); + return; + } + + if (!frame_->GetSettings()->BypassCSP() && + RuntimeEnabledFeatures::EmbedderCSPEnforcementEnabled() && + !GetFrameLoader().RequiredCSP().IsEmpty()) { + const SecurityOrigin* parent_security_origin = + frame_->Tree().Parent()->GetSecurityContext()->GetSecurityOrigin(); + if (ContentSecurityPolicy::ShouldEnforceEmbeddersPolicy( + response, parent_security_origin)) { + content_security_policy_->AddPolicyFromHeaderValue( + GetFrameLoader().RequiredCSP(), + kContentSecurityPolicyHeaderTypeEnforce, + kContentSecurityPolicyHeaderSourceHTTP); + } else { + ContentSecurityPolicy* required_csp = ContentSecurityPolicy::Create(); + required_csp->AddPolicyFromHeaderValue( + GetFrameLoader().RequiredCSP(), + kContentSecurityPolicyHeaderTypeEnforce, + kContentSecurityPolicyHeaderSourceHTTP); + if (!required_csp->Subsumes(*content_security_policy_)) { + String message = "Refused to display '" + + response.Url().ElidedString() + + "' because it has not opted-into the following policy " + "required by its embedder: '" + + GetFrameLoader().RequiredCSP() + "'."; + ConsoleMessage* console_message = ConsoleMessage::CreateForRequest( + kSecurityMessageSource, kErrorMessageLevel, message, response.Url(), + this, MainResourceIdentifier()); + frame_->GetDocument()->AddConsoleMessage(console_message); + CancelLoadAfterCSPDenied(response); + return; + } + } + } + + DCHECK(!frame_->GetPage()->Paused()); + + if (response.DidServiceWorkerNavigationPreload()) + UseCounter::Count(frame_, WebFeature::kServiceWorkerNavigationPreload); + response_ = response; + + if (IsArchiveMIMEType(response_.MimeType()) && + resource->GetDataBufferingPolicy() != kBufferData) + resource->SetDataBufferingPolicy(kBufferData); + + if (!ShouldContinueForResponse()) { + probe::ContinueWithPolicyIgnore(frame_, this, resource->Identifier(), + response_, resource); + fetcher_->StopFetching(); + return; + } + + if (frame_->Owner() && response_.IsHTTP() && + !FetchUtils::IsOkStatus(response_.HttpStatusCode())) + frame_->Owner()->RenderFallbackContent(); +} + +void DocumentLoader::CommitNavigation(const AtomicString& mime_type, + const KURL& overriding_url) { + if (state_ != kProvisional) + return; + + // Set history state before commitProvisionalLoad() so that we still have + // access to the previous committed DocumentLoader's HistoryItem, in case we + // need to copy state from it. + if (!GetFrameLoader().StateMachine()->CreatingInitialEmptyDocument()) { + SetHistoryItemStateForCommit( + GetFrameLoader().GetDocumentLoader()->GetHistoryItem(), load_type_, + HistoryNavigationType::kDifferentDocument); + } + + DCHECK_EQ(state_, kProvisional); + GetFrameLoader().CommitProvisionalLoad(); + if (!frame_) + return; + + const AtomicString& encoding = GetResponse().TextEncodingName(); + + // Prepare a DocumentInit before clearing the frame, because it may need to + // inherit an aliased security context. + Document* owner_document = nullptr; + // TODO(dcheng): This differs from the behavior of both IE and Firefox: the + // origin is inherited from the document that loaded the URL. + if (Document::ShouldInheritSecurityOriginFromOwner(Url())) { + Frame* owner_frame = frame_->Tree().Parent(); + if (!owner_frame) + owner_frame = frame_->Loader().Opener(); + if (owner_frame && owner_frame->IsLocalFrame()) + owner_document = ToLocalFrame(owner_frame)->GetDocument(); + } + DCHECK(frame_->GetPage()); + + ParserSynchronizationPolicy parsing_policy = kAllowAsynchronousParsing; + if (!Document::ThreadedParsingEnabledForTesting()) + parsing_policy = kForceSynchronousParsing; + + InstallNewDocument(Url(), owner_document, + frame_->ShouldReuseDefaultView(Url()) + ? WebGlobalObjectReusePolicy::kUseExisting + : WebGlobalObjectReusePolicy::kCreateNew, + mime_type, encoding, InstallNewDocumentReason::kNavigation, + parsing_policy, overriding_url); + parser_->SetDocumentWasLoadedAsPartOfNavigation(); + if (request_.WasDiscarded()) + frame_->GetDocument()->SetWasDiscarded(true); + frame_->GetDocument()->MaybeHandleHttpRefresh( + response_.HttpHeaderField(HTTPNames::Refresh), + Document::kHttpRefreshFromHeader); +} + +void DocumentLoader::CommitData(const char* bytes, size_t length) { + CommitNavigation(response_.MimeType()); + DCHECK_GE(state_, kCommitted); + + // This can happen if document.close() is called by an event handler while + // there's still pending incoming data. + if (!frame_ || !frame_->GetDocument()->Parsing()) + return; + + if (length) + data_received_ = true; + + if (parser_blocked_count_) { + if (!committed_data_buffer_) + committed_data_buffer_ = SharedBuffer::Create(); + committed_data_buffer_->Append(bytes, length); + } else { + parser_->AppendBytes(bytes, length); + } +} + +void DocumentLoader::DataReceived(Resource* resource, + const char* data, + size_t length) { + DCHECK(data); + DCHECK(length); + DCHECK_EQ(resource, GetResource()); + DCHECK(!response_.IsNull()); + DCHECK(!frame_->GetPage()->Paused()); + + if (in_data_received_) { + // If this function is reentered, defer processing of the additional data to + // the top-level invocation. Reentrant calls can occur because of web + // platform (mis-)features that require running a nested run loop: + // - alert(), confirm(), prompt() + // - Detach of plugin elements. + // - Synchronous XMLHTTPRequest + data_buffer_->Append(data, length); + return; + } + + AutoReset<bool> reentrancy_protector(&in_data_received_, true); + ProcessData(data, length); + ProcessDataBuffer(); +} + +void DocumentLoader::ProcessDataBuffer() { + // Process data received in reentrant invocations. Note that the invocations + // of processData() may queue more data in reentrant invocations, so iterate + // until it's empty. + const char* segment; + size_t pos = 0; + while (size_t length = data_buffer_->GetSomeData(segment, pos)) { + ProcessData(segment, length); + pos += length; + } + // All data has been consumed, so flush the buffer. + data_buffer_->Clear(); +} + +void DocumentLoader::ProcessData(const char* data, size_t length) { + application_cache_host_->MainResourceDataReceived(data, length); + time_of_last_data_received_ = CurrentTimeTicks(); + + if (IsArchiveMIMEType(GetResponse().MimeType())) + return; + CommitData(data, length); + + // If we are sending data to MediaDocument, we should stop here and cancel the + // request. + if (frame_ && frame_->GetDocument()->IsMediaDocument()) + fetcher_->StopFetching(); +} + +void DocumentLoader::ClearRedirectChain() { + redirect_chain_.clear(); +} + +void DocumentLoader::AppendRedirect(const KURL& url) { + redirect_chain_.push_back(url); +} + +void DocumentLoader::StopLoading() { + fetcher_->StopFetching(); + if (frame_ && !SentDidFinishLoad()) + LoadFailed(ResourceError::CancelledError(Url())); +} + +void DocumentLoader::DetachFromFrame() { + DCHECK(frame_); + StopLoading(); + fetcher_->ClearContext(); + + // If that load cancellation triggered another detach, leave. + // (fast/frames/detach-frame-nested-no-crash.html is an example of this.) + if (!frame_) + return; + + application_cache_host_->DetachFromDocumentLoader(); + application_cache_host_.Clear(); + service_worker_network_provider_ = nullptr; + WeakIdentifierMap<DocumentLoader>::NotifyObjectDestroyed(this); + ClearResource(); + frame_ = nullptr; +} + +bool DocumentLoader::MaybeCreateArchive() { + // Give the archive machinery a crack at this document. If the MIME type is + // not an archive type, it will return 0. + if (!IsArchiveMIMEType(response_.MimeType())) + return false; + + DCHECK(GetResource()); + ArchiveResource* main_resource = fetcher_->CreateArchive(GetResource()); + if (!main_resource) + return false; + // The origin is the MHTML file, we need to set the base URL to the document + // encoded in the MHTML so relative URLs are resolved properly. + CommitNavigation(main_resource->MimeType(), main_resource->Url()); + if (!frame_) + return false; + + scoped_refptr<SharedBuffer> data(main_resource->Data()); + data->ForEachSegment( + [this](const char* segment, size_t segment_size, size_t segment_offset) { + CommitData(segment, segment_size); + return true; + }); + return true; +} + +const KURL& DocumentLoader::UnreachableURL() const { + return substitute_data_.FailingURL(); +} + +bool DocumentLoader::MaybeLoadEmpty() { + bool should_load_empty = !substitute_data_.IsValid() && + (request_.Url().IsEmpty() || + SchemeRegistry::ShouldLoadURLSchemeAsEmptyDocument( + request_.Url().Protocol())); + if (!should_load_empty) + return false; + + if (request_.Url().IsEmpty() && + !GetFrameLoader().StateMachine()->CreatingInitialEmptyDocument()) + request_.SetURL(BlankURL()); + response_ = ResourceResponse(request_.Url(), "text/html"); + FinishedLoading(CurrentTimeTicks()); + return true; +} + +void DocumentLoader::StartLoading() { + GetTiming().MarkNavigationStart(); + DCHECK(!GetResource()); + DCHECK_EQ(state_, kNotStarted); + state_ = kProvisional; + + if (MaybeLoadEmpty()) + return; + + DCHECK(!GetTiming().NavigationStart().is_null()); + + // PlzNavigate: + // The fetch has already started in the browser. Don't mark it again. + if (!frame_->GetSettings()->GetBrowserSideNavigationEnabled()) { + DCHECK(GetTiming().FetchStart().is_null()); + GetTiming().MarkFetchStart(); + } + + ResourceLoaderOptions options; + options.data_buffering_policy = kDoNotBufferData; + options.initiator_info.name = FetchInitiatorTypeNames::document; + FetchParameters fetch_params(request_, options); + RawResource::FetchMainResource(fetch_params, Fetcher(), this, + substitute_data_); + // A bunch of headers are set when the underlying resource load begins, and + // request_ needs to include those. Even when using a cached resource, we may + // make some modification to the request, e.g. adding the referer header. + request_ = GetResource()->IsLoading() ? GetResource()->GetResourceRequest() + : fetch_params.GetResourceRequest(); +} + +void DocumentLoader::DidInstallNewDocument(Document* document) { + document->SetReadyState(Document::kLoading); + if (content_security_policy_) { + document->InitContentSecurityPolicy(content_security_policy_.Release()); + } + + if (history_item_ && IsBackForwardLoadType(load_type_)) + document->SetStateForNewFormElements(history_item_->GetDocumentState()); + + document->GetClientHintsPreferences().UpdateFrom(client_hints_preferences_); + + // TODO(japhet): There's no reason to wait until commit to set these bits. + Settings* settings = document->GetSettings(); + fetcher_->SetImagesEnabled(settings->GetImagesEnabled()); + fetcher_->SetAutoLoadImages(settings->GetLoadsImagesAutomatically()); + + const AtomicString& dns_prefetch_control = + response_.HttpHeaderField(HTTPNames::X_DNS_Prefetch_Control); + if (!dns_prefetch_control.IsEmpty()) + document->ParseDNSPrefetchControlHeader(dns_prefetch_control); + + String header_content_language = + response_.HttpHeaderField(HTTPNames::Content_Language); + if (!header_content_language.IsEmpty()) { + size_t comma_index = header_content_language.find(','); + // kNotFound == -1 == don't truncate + header_content_language.Truncate(comma_index); + header_content_language = + header_content_language.StripWhiteSpace(IsHTMLSpace<UChar>); + if (!header_content_language.IsEmpty()) + document->SetContentLanguage(AtomicString(header_content_language)); + } + + String referrer_policy_header = + response_.HttpHeaderField(HTTPNames::Referrer_Policy); + if (!referrer_policy_header.IsNull()) { + UseCounter::Count(*document, WebFeature::kReferrerPolicyHeader); + document->ParseAndSetReferrerPolicy(referrer_policy_header); + } + + GetLocalFrameClient().DidCreateNewDocument(); +} + +void DocumentLoader::WillCommitNavigation() { + if (GetFrameLoader().StateMachine()->CreatingInitialEmptyDocument()) + return; + probe::willCommitLoad(frame_, this); + frame_->GetIdlenessDetector()->WillCommitLoad(); +} + +void DocumentLoader::DidCommitNavigation( + WebGlobalObjectReusePolicy global_object_reuse_policy) { + if (GetFrameLoader().StateMachine()->CreatingInitialEmptyDocument()) + return; + + if (!frame_->Loader().StateMachine()->CommittedMultipleRealLoads() && + load_type_ == kFrameLoadTypeStandard) { + frame_->Loader().StateMachine()->AdvanceTo( + FrameLoaderStateMachine::kCommittedMultipleRealLoads); + } + + HistoryCommitType commit_type = LoadTypeToCommitType(load_type_); + frame_->GetFrameScheduler()->DidCommitProvisionalLoad( + commit_type == kHistoryInertCommit, load_type_ == kFrameLoadTypeReload, + frame_->IsLocalRoot()); + GetLocalFrameClient().DispatchDidCommitLoad(history_item_.Get(), commit_type, + global_object_reuse_policy); + + // When the embedder gets notified (above) that the new navigation has + // committed, the embedder will drop the old Content Security Policy and + // therefore now is a good time to report to the embedder the Content + // Security Policies that have accumulated so far for the new navigation. + frame_->GetSecurityContext() + ->GetContentSecurityPolicy() + ->ReportAccumulatedHeaders(&GetLocalFrameClient()); + + // didObserveLoadingBehavior() must be called after dispatchDidCommitLoad() is + // called for the metrics tracking logic to handle it properly. + if (service_worker_network_provider_ && + service_worker_network_provider_->HasControllerServiceWorker()) { + GetLocalFrameClient().DidObserveLoadingBehavior( + kWebLoadingBehaviorServiceWorkerControlled); + } + + // Links with media values need more information (like viewport information). + // This happens after the first chunk is parsed in HTMLDocumentParser. + DispatchLinkHeaderPreloads(nullptr, LinkLoader::kOnlyLoadNonMedia); + + Document* document = frame_->GetDocument(); + InteractiveDetector* interactive_detector = + InteractiveDetector::From(*document); + if (interactive_detector) + interactive_detector->SetNavigationStartTime(GetTiming().NavigationStart()); + + TRACE_EVENT1("devtools.timeline", "CommitLoad", "data", + InspectorCommitLoadEvent::Data(frame_)); + probe::didCommitLoad(frame_, this); + frame_->GetPage()->DidCommitLoad(frame_); + + // Report legacy Symantec certificates after Page::DidCommitLoad, because the + // latter clears the console. + if (response_.IsLegacySymantecCert()) { + GetLocalFrameClient().ReportLegacySymantecCert(response_.Url(), + false /* did_fail */); + } +} + +// static +bool DocumentLoader::ShouldClearWindowName( + const LocalFrame& frame, + const SecurityOrigin* previous_security_origin, + const Document& new_document) { + if (!previous_security_origin) + return false; + if (!frame.IsMainFrame()) + return false; + if (frame.Loader().Opener()) + return false; + + return !new_document.GetSecurityOrigin()->IsSameSchemeHostPort( + previous_security_origin); +} + +void DocumentLoader::InstallNewDocument( + const KURL& url, + Document* owner_document, + WebGlobalObjectReusePolicy global_object_reuse_policy, + const AtomicString& mime_type, + const AtomicString& encoding, + InstallNewDocumentReason reason, + ParserSynchronizationPolicy parsing_policy, + const KURL& overriding_url) { + DCHECK(!frame_->GetDocument() || !frame_->GetDocument()->IsActive()); + DCHECK_EQ(frame_->Tree().ChildCount(), 0u); + if (GetFrameLoader().StateMachine()->IsDisplayingInitialEmptyDocument()) { + GetFrameLoader().StateMachine()->AdvanceTo( + FrameLoaderStateMachine::kCommittedFirstRealLoad); + } + + const SecurityOrigin* previous_security_origin = nullptr; + if (frame_->GetDocument()) + previous_security_origin = frame_->GetDocument()->GetSecurityOrigin(); + + // In some rare cases, we'll re-use a LocalDOMWindow for a new Document. For + // example, when a script calls window.open("..."), the browser gives + // JavaScript a window synchronously but kicks off the load in the window + // asynchronously. Web sites expect that modifications that they make to the + // window object synchronously won't be blown away when the network load + // commits. To make that happen, we "securely transition" the existing + // LocalDOMWindow to the Document that results from the network load. See also + // Document::IsSecureTransitionTo. + if (global_object_reuse_policy != WebGlobalObjectReusePolicy::kUseExisting) + frame_->SetDOMWindow(LocalDOMWindow::Create(*frame_)); + + if (reason == InstallNewDocumentReason::kNavigation) + WillCommitNavigation(); + + Document* document = frame_->DomWindow()->InstallNewDocument( + mime_type, + DocumentInit::Create() + .WithFrame(frame_) + .WithURL(url) + .WithOwnerDocument(owner_document) + .WithNewRegistrationContext(), + false); + + // Clear the user activation state. + // TODO(crbug.com/736415): Clear this bit unconditionally for all frames. + if (frame_->IsMainFrame()) + frame_->ClearActivation(); + + // The DocumentLoader was flagged as activated if it needs to notify the frame + // that it was activated before navigation. Update the frame state based on + // the new value. + if (frame_->HasReceivedUserGestureBeforeNavigation() != user_activated_) { + frame_->SetDocumentHasReceivedUserGestureBeforeNavigation(user_activated_); + GetLocalFrameClient().SetHasReceivedUserGestureBeforeNavigation( + user_activated_); + } + + if (ShouldClearWindowName(*frame_, previous_security_origin, *document)) { + // TODO(andypaicu): experimentalSetNullName will just record the fact + // that the name would be nulled and if the name is accessed after we will + // fire a UseCounter. If we decide to move forward with this change, we'd + // actually clean the name here. + // frame_->tree().setName(g_null_atom); + frame_->Tree().ExperimentalSetNulledName(); + } + + if (!overriding_url.IsEmpty()) + document->SetBaseURLOverride(overriding_url); + DidInstallNewDocument(document); + + // This must be called before the document is opened, otherwise HTML parser + // will use stale values from HTMLParserOption. + if (reason == InstallNewDocumentReason::kNavigation) + DidCommitNavigation(global_object_reuse_policy); + + // Initializing origin trials might force window proxy initialization, + // which later triggers CHECK when swapping in via WebFrame::Swap(). + // We can safely omit installing original trials on initial empty document + // and wait for the real load. + if (GetFrameLoader().StateMachine()->CommittedFirstRealDocumentLoad()) { + if (document->GetSettings() + ->GetForceTouchEventFeatureDetectionForInspector()) { + OriginTrialContext::FromOrCreate(document)->AddFeature( + "ForceTouchEventFeatureDetectionForInspector"); + } + OriginTrialContext::AddTokensFromHeader( + document, response_.HttpHeaderField(HTTPNames::Origin_Trial)); + } + + parser_ = document->OpenForNavigation(parsing_policy, mime_type, encoding); + + // If this is a scriptable parser and there is a resource, register the + // resource's cache handler with the parser. + ScriptableDocumentParser* scriptable_parser = + parser_->AsScriptableDocumentParser(); + if (scriptable_parser && GetResource()) { + scriptable_parser->SetInlineScriptCacheHandler( + ToRawResource(GetResource())->CacheHandler()); + } + + // FeaturePolicy is reset in the browser process on commit, so this needs to + // be initialized and replicated to the browser process after commit messages + // are sent in didCommitNavigation(). + document->ApplyFeaturePolicyFromHeader( + response_.HttpHeaderField(HTTPNames::Feature_Policy)); + + GetFrameLoader().DispatchDidClearDocumentOfWindowObject(); +} + +const AtomicString& DocumentLoader::MimeType() const { + if (fetcher_->Archive()) + return fetcher_->Archive()->MainResource()->MimeType(); + return response_.MimeType(); +} + +// This is only called by +// FrameLoader::ReplaceDocumentWhileExecutingJavaScriptURL() +void DocumentLoader::ReplaceDocumentWhileExecutingJavaScriptURL( + const KURL& url, + Document* owner_document, + WebGlobalObjectReusePolicy global_object_reuse_policy, + const String& source) { + InstallNewDocument(url, owner_document, global_object_reuse_policy, + MimeType(), response_.TextEncodingName(), + InstallNewDocumentReason::kJavascriptURL, + kForceSynchronousParsing, NullURL()); + + if (!source.IsNull()) { + frame_->GetDocument()->SetCompatibilityMode(Document::kNoQuirksMode); + parser_->Append(source); + } + + // Append() might lead to a detach. + if (parser_) + parser_->Finish(); +} + +void DocumentLoader::BlockParser() { + parser_blocked_count_++; +} + +void DocumentLoader::ResumeParser() { + parser_blocked_count_--; + DCHECK_GE(parser_blocked_count_, 0); + + if (parser_blocked_count_ != 0) + return; + + if (committed_data_buffer_ && !committed_data_buffer_->IsEmpty()) { + // Don't recursively process data. + AutoReset<bool> reentrancy_protector(&in_data_received_, true); + + // Append data to the parser that may have been received while the parser + // was blocked. + const char* segment; + size_t pos = 0; + while (size_t length = committed_data_buffer_->GetSomeData(segment, pos)) { + parser_->AppendBytes(segment, length); + pos += length; + } + committed_data_buffer_->Clear(); + + // DataReceived may be called in a nested message loop. + ProcessDataBuffer(); + } + + if (finished_loading_) { + finished_loading_ = false; + parser_->Finish(); + parser_.Clear(); + } +} + +DEFINE_WEAK_IDENTIFIER_MAP(DocumentLoader); + +STATIC_ASSERT_ENUM(kWebStandardCommit, kStandardCommit); +STATIC_ASSERT_ENUM(kWebBackForwardCommit, kBackForwardCommit); +STATIC_ASSERT_ENUM(kWebInitialCommitInChildFrame, kInitialCommitInChildFrame); +STATIC_ASSERT_ENUM(kWebHistoryInertCommit, kHistoryInertCommit); + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/loader/document_loader.h b/chromium/third_party/blink/renderer/core/loader/document_loader.h new file mode 100644 index 00000000000..ae797c37588 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/document_loader.h @@ -0,0 +1,409 @@ +/* + * Copyright (C) 2006, 2007, 2008, 2009 Apple Inc. All rights reserved. + * Copyright (C) 2011 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: + * + * 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") 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 APPLE AND ITS 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 APPLE OR ITS 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_DOCUMENT_LOADER_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_DOCUMENT_LOADER_H_ + +#include <memory> +#include "base/memory/scoped_refptr.h" +#include "base/unguessable_token.h" +#include "third_party/blink/public/platform/web_loading_behavior_flag.h" +#include "third_party/blink/public/web/web_global_object_reuse_policy.h" +#include "third_party/blink/renderer/bindings/core/v8/source_location.h" +#include "third_party/blink/renderer/core/core_export.h" +#include "third_party/blink/renderer/core/dom/viewport_description.h" +#include "third_party/blink/renderer/core/dom/weak_identifier_map.h" +#include "third_party/blink/renderer/core/frame/csp/content_security_policy.h" +#include "third_party/blink/renderer/core/frame/frame_types.h" +#include "third_party/blink/renderer/core/html/parser/parser_synchronization_policy.h" +#include "third_party/blink/renderer/core/loader/document_load_timing.h" +#include "third_party/blink/renderer/core/loader/frame_loader_types.h" +#include "third_party/blink/renderer/core/loader/link_loader.h" +#include "third_party/blink/renderer/core/loader/navigation_policy.h" +#include "third_party/blink/renderer/platform/loader/fetch/client_hints_preferences.h" +#include "third_party/blink/renderer/platform/loader/fetch/raw_resource.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource_error.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/fetch/substitute_data.h" +#include "third_party/blink/renderer/platform/shared_buffer.h" +#include "third_party/blink/renderer/platform/wtf/hash_set.h" + +#include <memory> + +namespace blink { + +class ApplicationCacheHost; +class CSSPreloaderResourceClient; +class Document; +class DocumentParser; +class FrameLoader; +class HistoryItem; +class LocalFrame; +class LocalFrameClient; +class ResourceFetcher; +class ResourceTimingInfo; +class SerializedScriptValue; +class SubresourceFilter; +class WebServiceWorkerNetworkProvider; +struct ViewportDescriptionWrapper; + +// The DocumentLoader fetches a main resource and handles the result. +class CORE_EXPORT DocumentLoader + : public GarbageCollectedFinalized<DocumentLoader>, + private RawResourceClient { + USING_GARBAGE_COLLECTED_MIXIN(DocumentLoader); + + public: + static DocumentLoader* Create( + LocalFrame* frame, + const ResourceRequest& request, + const SubstituteData& data, + ClientRedirectPolicy client_redirect_policy, + const base::UnguessableToken& devtools_navigation_token) { + DCHECK(frame); + + return new DocumentLoader(frame, request, data, client_redirect_policy, + devtools_navigation_token); + } + ~DocumentLoader() override; + + LocalFrame* GetFrame() const { return frame_; } + + ResourceTimingInfo* GetNavigationTimingInfo() const; + + virtual void DetachFromFrame(); + + unsigned long MainResourceIdentifier() const; + + void ReplaceDocumentWhileExecutingJavaScriptURL(const KURL&, + Document* owner_document, + WebGlobalObjectReusePolicy, + const String& source); + + const AtomicString& MimeType() const; + + const ResourceRequest& OriginalRequest() const; + + const ResourceRequest& GetRequest() const; + + ResourceFetcher* Fetcher() const { return fetcher_.Get(); } + + void SetSubresourceFilter(SubresourceFilter*); + SubresourceFilter* GetSubresourceFilter() const { + return subresource_filter_.Get(); + } + + const SubstituteData& GetSubstituteData() const { return substitute_data_; } + + const KURL& Url() const; + const KURL& UnreachableURL() const; + const KURL& UrlForHistory() const; + + void DidChangePerformanceTiming(); + void DidObserveLoadingBehavior(WebLoadingBehaviorFlag); + void UpdateForSameDocumentNavigation(const KURL&, + SameDocumentNavigationSource, + scoped_refptr<SerializedScriptValue>, + HistoryScrollRestorationType, + FrameLoadType, + Document*); + const ResourceResponse& GetResponse() const { return response_; } + bool IsClientRedirect() const { return is_client_redirect_; } + void SetIsClientRedirect(bool is_client_redirect) { + is_client_redirect_ = is_client_redirect; + } + bool ReplacesCurrentHistoryItem() const { + return replaces_current_history_item_; + } + void SetReplacesCurrentHistoryItem(bool replaces_current_history_item) { + replaces_current_history_item_ = replaces_current_history_item; + } + + bool IsCommittedButEmpty() const { + return state_ >= kCommitted && !data_received_; + } + + // Without PlzNavigate, this is only false for a narrow window during + // navigation start. For PlzNavigate, a navigation sent to the browser will + // leave a dummy DocumentLoader in the NotStarted state until the navigation + // is actually handled in the renderer. + bool DidStart() const { return state_ != kNotStarted; } + + void MarkAsCommitted(); + void SetSentDidFinishLoad() { state_ = kSentDidFinishLoad; } + bool SentDidFinishLoad() const { return state_ == kSentDidFinishLoad; } + + FrameLoadType LoadType() const { return load_type_; } + void SetLoadType(FrameLoadType load_type) { load_type_ = load_type; } + + NavigationType GetNavigationType() const { return navigation_type_; } + void SetNavigationType(NavigationType navigation_type) { + navigation_type_ = navigation_type; + } + + void SetItemForHistoryNavigation(HistoryItem* item) { history_item_ = item; } + HistoryItem* GetHistoryItem() const { return history_item_; } + + void StartLoading(); + void StopLoading(); + + DocumentLoadTiming& GetTiming() { return document_load_timing_; } + const DocumentLoadTiming& GetTiming() const { return document_load_timing_; } + + ApplicationCacheHost* GetApplicationCacheHost() const { + return application_cache_host_.Get(); + } + + void ClearRedirectChain(); + void AppendRedirect(const KURL&); + + ClientHintsPreferences& GetClientHintsPreferences() { + return client_hints_preferences_; + } + + struct InitialScrollState { + DISALLOW_NEW(); + InitialScrollState() + : was_scrolled_by_user(false), did_restore_from_history(false) {} + + bool was_scrolled_by_user; + bool was_scrolled_by_js; + bool did_restore_from_history; + }; + InitialScrollState& GetInitialScrollState() { return initial_scroll_state_; } + + void SetWasBlockedAfterCSP() { was_blocked_after_csp_ = true; } + bool WasBlockedAfterCSP() { return was_blocked_after_csp_; } + + void DispatchLinkHeaderPreloads(ViewportDescriptionWrapper*, + LinkLoader::MediaPreloadPolicy); + + Resource* StartPreload(Resource::Type, + FetchParameters&, + CSSPreloaderResourceClient*); + + void SetServiceWorkerNetworkProvider( + std::unique_ptr<WebServiceWorkerNetworkProvider>); + + // May return null before the first HTML tag is inserted by the + // parser (before didCreateDataSource is called), after the document + // is detached from frame, or in tests. + WebServiceWorkerNetworkProvider* GetServiceWorkerNetworkProvider() { + return service_worker_network_provider_.get(); + } + + std::unique_ptr<SourceLocation> CopySourceLocation() const; + void SetSourceLocation(std::unique_ptr<SourceLocation>); + + void LoadFailed(const ResourceError&); + + void SetUserActivated(); + + const AtomicString& RequiredCSP(); + + void Trace(blink::Visitor*) override; + + // For automation driver-initiated navigations over the devtools protocol, + // |devtools_navigation_token_| is used to tag the navigation. This navigation + // token is then sent into the renderer and lands on the DocumentLoader. That + // way subsequent Blink-level frame lifecycle events can be associated with + // the concrete navigation. + // - The value should not be sent back to the browser. + // - The value on DocumentLoader may be generated in the renderer in some + // cases, and thus shouldn't be trusted. + // TODO(crbug.com/783506): Replace devtools navigation token with the generic + // navigation token that can be passed from renderer to the browser. + const base::UnguessableToken& GetDevToolsNavigationToken() { + return devtools_navigation_token_; + } + + // Can be used to temporarily suspend feeding the parser with new data. The + // parser will be allowed to read new data when ResumeParser() is called the + // same number of time than BlockParser(). + void BlockParser(); + void ResumeParser(); + + protected: + DocumentLoader(LocalFrame*, + const ResourceRequest&, + const SubstituteData&, + ClientRedirectPolicy, + const base::UnguessableToken& devtools_navigation_token); + + static bool ShouldClearWindowName( + const LocalFrame&, + const SecurityOrigin* previous_security_origin, + const Document& new_document); + + Vector<KURL> redirect_chain_; + + private: + // installNewDocument() does the work of creating a Document and + // DocumentParser, as well as creating a new LocalDOMWindow if needed. It also + // initalizes a bunch of state on the Document (e.g., the state based on + // response headers). + enum class InstallNewDocumentReason { kNavigation, kJavascriptURL }; + void InstallNewDocument(const KURL&, + Document* owner_document, + WebGlobalObjectReusePolicy, + const AtomicString& mime_type, + const AtomicString& encoding, + InstallNewDocumentReason, + ParserSynchronizationPolicy, + const KURL& overriding_url); + void DidInstallNewDocument(Document*); + void WillCommitNavigation(); + void DidCommitNavigation(WebGlobalObjectReusePolicy); + + void CommitNavigation(const AtomicString& mime_type, + const KURL& overriding_url = KURL()); + + // Use these method only where it's guaranteed that |m_frame| hasn't been + // cleared. + FrameLoader& GetFrameLoader() const; + LocalFrameClient& GetLocalFrameClient() const; + + void CommitData(const char* bytes, size_t length); + + bool MaybeCreateArchive(); + + void FinishedLoading(TimeTicks finish_time); + void CancelLoadAfterCSPDenied(const ResourceResponse&); + + enum class HistoryNavigationType { + kDifferentDocument, + kFragment, + kHistoryApi + }; + void SetHistoryItemStateForCommit(HistoryItem* old_item, + FrameLoadType, + HistoryNavigationType); + + // RawResourceClient implementation + bool RedirectReceived(Resource*, + const ResourceRequest&, + const ResourceResponse&) final; + void ResponseReceived(Resource*, + const ResourceResponse&, + std::unique_ptr<WebDataConsumerHandle>) final; + void DataReceived(Resource*, const char* data, size_t length) final; + + // ResourceClient implementation + void NotifyFinished(Resource*) final; + String DebugName() const override { return "DocumentLoader"; } + + void ProcessData(const char* data, size_t length); + + bool MaybeLoadEmpty(); + + bool IsRedirectAfterPost(const ResourceRequest&, const ResourceResponse&); + + bool ShouldContinueForResponse() const; + + // Processes the data stored in the data_buffer_, used to avoid appending data + // to the parser in a nested message loop. + void ProcessDataBuffer(); + + Member<LocalFrame> frame_; + Member<ResourceFetcher> fetcher_; + + Member<HistoryItem> history_item_; + + // The parser that was created when the current Document was installed. + // document.open() may create a new parser at a later point, but this + // will not be updated. + Member<DocumentParser> parser_; + + Member<SubresourceFilter> subresource_filter_; + + // A reference to actual request used to create the data source. + // The only part of this request that should change is the url, and + // that only in the case of a same-document navigation. + ResourceRequest original_request_; + + SubstituteData substitute_data_; + + // The 'working' request. It may be mutated + // several times from the original request to include additional + // headers, cookie information, canonicalization and redirects. + ResourceRequest request_; + + ResourceResponse response_; + + FrameLoadType load_type_; + + bool is_client_redirect_; + bool replaces_current_history_item_; + bool data_received_; + + NavigationType navigation_type_; + + DocumentLoadTiming document_load_timing_; + + TimeTicks time_of_last_data_received_; + + Member<ApplicationCacheHost> application_cache_host_; + + std::unique_ptr<WebServiceWorkerNetworkProvider> + service_worker_network_provider_; + + Member<ContentSecurityPolicy> content_security_policy_; + ClientHintsPreferences client_hints_preferences_; + InitialScrollState initial_scroll_state_; + + bool was_blocked_after_csp_; + + // PlzNavigate: set when committing a navigation. The data has originally been + // captured when the navigation was sent to the browser process, and it is + // sent back at commit time. + std::unique_ptr<SourceLocation> source_location_; + + enum State { kNotStarted, kProvisional, kCommitted, kSentDidFinishLoad }; + State state_; + + // Used to block the parser. + int parser_blocked_count_ = 0; + bool finished_loading_ = false; + scoped_refptr<SharedBuffer> committed_data_buffer_; + + // Used to protect against reentrancy into dataReceived(). + bool in_data_received_; + scoped_refptr<SharedBuffer> data_buffer_; + base::UnguessableToken devtools_navigation_token_; + + // Whether this load request comes from a user activation. + bool user_activated_; +}; + +DECLARE_WEAK_IDENTIFIER_MAP(DocumentLoader); + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_DOCUMENT_LOADER_H_ diff --git a/chromium/third_party/blink/renderer/core/loader/document_loader_test.cc b/chromium/third_party/blink/renderer/core/loader/document_loader_test.cc new file mode 100644 index 00000000000..8f5ea0cfc8a --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/document_loader_test.cc @@ -0,0 +1,196 @@ +// 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/document_loader.h" + +#include <queue> +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/blink/public/platform/platform.h" +#include "third_party/blink/public/platform/web_url_loader_client.h" +#include "third_party/blink/public/platform/web_url_loader_mock_factory.h" +#include "third_party/blink/renderer/core/frame/frame_test_helpers.h" +#include "third_party/blink/renderer/core/frame/web_local_frame_impl.h" +#include "third_party/blink/renderer/core/page/page.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/wtf/auto_reset.h" + +namespace blink { + +// TODO(dcheng): Ideally, enough of FrameTestHelpers would be in core/ that +// placing a test for a core/ class in web/ wouldn't be necessary. +class DocumentLoaderTest : public testing::Test { + protected: + void SetUp() override { + web_view_helper_.Initialize(); + URLTestHelpers::RegisterMockedURLLoad( + URLTestHelpers::ToKURL("https://example.com/foo.html"), + test::CoreTestDataPath("foo.html")); + } + + void TearDown() override { + Platform::Current() + ->GetURLLoaderMockFactory() + ->UnregisterAllURLsAndClearMemoryCache(); + } + + WebLocalFrameImpl* MainFrame() { return web_view_helper_.LocalMainFrame(); } + + FrameTestHelpers::WebViewHelper web_view_helper_; +}; + +TEST_F(DocumentLoaderTest, SingleChunk) { + class TestDelegate : public WebURLLoaderTestDelegate { + public: + void DidReceiveData(WebURLLoaderClient* original_client, + const char* data, + int data_length) override { + EXPECT_EQ(34, data_length) << "foo.html was not served in a single chunk"; + original_client->DidReceiveData(data, data_length); + } + } delegate; + + Platform::Current()->GetURLLoaderMockFactory()->SetLoaderDelegate(&delegate); + FrameTestHelpers::LoadFrame(MainFrame(), "https://example.com/foo.html"); + Platform::Current()->GetURLLoaderMockFactory()->SetLoaderDelegate(nullptr); + + // TODO(dcheng): How should the test verify that the original callback is + // invoked? The test currently still passes even if the test delegate + // forgets to invoke the callback. +} + +// Test normal case of DocumentLoader::dataReceived(): data in multiple chunks, +// with no reentrancy. +TEST_F(DocumentLoaderTest, MultiChunkNoReentrancy) { + class TestDelegate : public WebURLLoaderTestDelegate { + public: + void DidReceiveData(WebURLLoaderClient* original_client, + const char* data, + int data_length) override { + EXPECT_EQ(34, data_length) << "foo.html was not served in a single chunk"; + // Chunk the reply into one byte chunks. + for (int i = 0; i < data_length; ++i) + original_client->DidReceiveData(&data[i], 1); + } + } delegate; + + Platform::Current()->GetURLLoaderMockFactory()->SetLoaderDelegate(&delegate); + FrameTestHelpers::LoadFrame(MainFrame(), "https://example.com/foo.html"); + Platform::Current()->GetURLLoaderMockFactory()->SetLoaderDelegate(nullptr); +} + +// Finally, test reentrant callbacks to DocumentLoader::dataReceived(). +TEST_F(DocumentLoaderTest, MultiChunkWithReentrancy) { + // This test delegate chunks the response stage into three distinct stages: + // 1. The first dataReceived() callback, which triggers frame detach due to + // commiting a provisional load. + // 2. The middle part of the response, which is dispatched to + // dataReceived() reentrantly. + // 3. The final chunk, which is dispatched normally at the top-level. + class ChildDelegate : public WebURLLoaderTestDelegate, + public FrameTestHelpers::TestWebFrameClient { + public: + // WebURLLoaderTestDelegate overrides: + void DidReceiveData(WebURLLoaderClient* original_client, + const char* data, + int data_length) override { + EXPECT_EQ(34, data_length) << "foo.html was not served in a single chunk"; + + loader_client_ = original_client; + for (int i = 0; i < data_length; ++i) + data_.push(data[i]); + + { + // Serve the first byte to the real WebURLLoaderCLient, which + // should trigger frameDetach() due to committing a provisional + // load. + AutoReset<bool> dispatching(&dispatching_did_receive_data_, true); + DispatchOneByte(); + } + // Serve the remaining bytes to complete the load. + EXPECT_FALSE(data_.empty()); + while (!data_.empty()) + DispatchOneByte(); + } + + // WebFrameClient overrides: + void FrameDetached(DetachType detach_type) override { + if (dispatching_did_receive_data_) { + // This should be called by the first didReceiveData() call, since + // it should commit the provisional load. + EXPECT_GT(data_.size(), 10u); + // Dispatch dataReceived() callbacks for part of the remaining + // data, saving the rest to be dispatched at the top-level as + // normal. + while (data_.size() > 10) + DispatchOneByte(); + served_reentrantly_ = true; + } + TestWebFrameClient::FrameDetached(detach_type); + } + + void DispatchOneByte() { + char c = data_.front(); + data_.pop(); + loader_client_->DidReceiveData(&c, 1); + } + + bool ServedReentrantly() const { return served_reentrantly_; } + + private: + WebURLLoaderClient* loader_client_ = nullptr; + std::queue<char> data_; + bool dispatching_did_receive_data_ = false; + bool served_reentrantly_ = false; + }; + + class MainFrameClient : public FrameTestHelpers::TestWebFrameClient { + public: + explicit MainFrameClient(TestWebFrameClient& child_client) + : child_client_(child_client) {} + WebLocalFrame* CreateChildFrame(WebLocalFrame* parent, + WebTreeScopeType scope, + const WebString& name, + const WebString& fallback_name, + WebSandboxFlags, + const ParsedFeaturePolicy&, + const WebFrameOwnerProperties&) override { + return CreateLocalChild(*parent, scope, &child_client_); + } + + private: + TestWebFrameClient& child_client_; + }; + + ChildDelegate child_delegate; + MainFrameClient main_frame_client(child_delegate); + web_view_helper_.Initialize(&main_frame_client); + + // This doesn't go through the mocked URL load path: it's just intended to + // setup a situation where didReceiveData() can be invoked reentrantly. + FrameTestHelpers::LoadHTMLString(MainFrame(), "<iframe></iframe>", + URLTestHelpers::ToKURL("about:blank")); + + Platform::Current()->GetURLLoaderMockFactory()->SetLoaderDelegate( + &child_delegate); + FrameTestHelpers::LoadFrame(MainFrame(), "https://example.com/foo.html"); + Platform::Current()->GetURLLoaderMockFactory()->SetLoaderDelegate(nullptr); + + EXPECT_TRUE(child_delegate.ServedReentrantly()); + + // delegate is a WebFrameClient and stack-allocated, so manually reset() the + // WebViewHelper here. + web_view_helper_.Reset(); +} + +TEST_F(DocumentLoaderTest, isCommittedButEmpty) { + WebViewImpl* web_view_impl = + web_view_helper_.InitializeAndLoad("about:blank"); + EXPECT_TRUE(ToLocalFrame(web_view_impl->GetPage()->MainFrame()) + ->Loader() + .GetDocumentLoader() + ->IsCommittedButEmpty()); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/loader/document_threadable_loader.cc b/chromium/third_party/blink/renderer/core/loader/document_threadable_loader.cc new file mode 100644 index 00000000000..642558fb545 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/document_threadable_loader.cc @@ -0,0 +1,1306 @@ +/* + * Copyright (C) 2011, 2012 Google Inc. All rights reserved. + * Copyright (C) 2013, Intel Corporation + * + * 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/document_threadable_loader.h" + +#include <memory> +#include "base/memory/weak_ptr.h" +#include "services/network/public/mojom/cors.mojom-blink.h" +#include "services/network/public/mojom/fetch_api.mojom-blink.h" +#include "third_party/blink/public/platform/platform.h" +#include "third_party/blink/public/platform/task_type.h" +#include "third_party/blink/public/platform/web_cors.h" +#include "third_party/blink/public/platform/web_security_origin.h" +#include "third_party/blink/public/platform/web_url_request.h" +#include "third_party/blink/renderer/core/dom/document.h" +#include "third_party/blink/renderer/core/frame/frame_console.h" +#include "third_party/blink/renderer/core/frame/local_frame.h" +#include "third_party/blink/renderer/core/frame/local_frame_client.h" +#include "third_party/blink/renderer/core/frame/web_feature.h" +#include "third_party/blink/renderer/core/inspector/InspectorNetworkAgent.h" +#include "third_party/blink/renderer/core/inspector/InspectorTraceEvents.h" +#include "third_party/blink/renderer/core/inspector/console_message.h" +#include "third_party/blink/renderer/core/loader/base_fetch_context.h" +#include "third_party/blink/renderer/core/loader/document_threadable_loader_client.h" +#include "third_party/blink/renderer/core/loader/frame_loader.h" +#include "third_party/blink/renderer/core/loader/threadable_loader_client.h" +#include "third_party/blink/renderer/core/loader/threadable_loading_context.h" +#include "third_party/blink/renderer/core/probe/core_probes.h" +#include "third_party/blink/renderer/platform/exported/wrapped_resource_request.h" +#include "third_party/blink/renderer/platform/loader/cors/cors.h" +#include "third_party/blink/renderer/platform/loader/cors/cors_error_string.h" +#include "third_party/blink/renderer/platform/loader/fetch/fetch_parameters.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource.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_request.h" +#include "third_party/blink/renderer/platform/shared_buffer.h" +#include "third_party/blink/renderer/platform/weborigin/scheme_registry.h" +#include "third_party/blink/renderer/platform/weborigin/security_origin.h" +#include "third_party/blink/renderer/platform/weborigin/security_policy.h" +#include "third_party/blink/renderer/platform/wtf/assertions.h" + +namespace blink { + +namespace { + +// Fetch API Spec: https://fetch.spec.whatwg.org/#cors-preflight-fetch-0 +AtomicString CreateAccessControlRequestHeadersHeader( + const HTTPHeaderMap& headers) { + Vector<String> filtered_headers; + for (const auto& header : headers) { + // Exclude CORS-safelisted headers. + if (CORS::IsCORSSafelistedHeader(header.key, header.value)) + continue; + // Calling a deprecated function, but eventually this function, + // |CreateAccessControlRequestHeadersHeader| will be removed. + // When the request is from a Worker, referrer header was added by + // WorkerThreadableLoader. But it should not be added to + // Access-Control-Request-Headers header. + if (DeprecatedEqualIgnoringCase(header.key, "referer")) + continue; + filtered_headers.push_back(header.key.DeprecatedLower()); + } + if (!filtered_headers.size()) + return g_null_atom; + + // Sort header names lexicographically. + std::sort(filtered_headers.begin(), filtered_headers.end(), + WTF::CodePointCompareLessThan); + StringBuilder header_buffer; + for (const String& header : filtered_headers) { + if (!header_buffer.IsEmpty()) + header_buffer.Append(","); + header_buffer.Append(header); + } + + return header_buffer.ToAtomicString(); +} + +class EmptyDataHandle final : public WebDataConsumerHandle { + private: + class EmptyDataReader final : public WebDataConsumerHandle::Reader { + public: + explicit EmptyDataReader( + WebDataConsumerHandle::Client* client, + scoped_refptr<base::SingleThreadTaskRunner> task_runner) + : factory_(this) { + task_runner->PostTask( + FROM_HERE, WTF::Bind(&EmptyDataReader::Notify, factory_.GetWeakPtr(), + WTF::Unretained(client))); + } + + private: + Result BeginRead(const void** buffer, + WebDataConsumerHandle::Flags, + size_t* available) override { + *available = 0; + *buffer = nullptr; + return kDone; + } + Result EndRead(size_t) override { + return WebDataConsumerHandle::kUnexpectedError; + } + void Notify(WebDataConsumerHandle::Client* client) { + client->DidGetReadable(); + } + base::WeakPtrFactory<EmptyDataReader> factory_; + }; + + std::unique_ptr<Reader> ObtainReader( + Client* client, + scoped_refptr<base::SingleThreadTaskRunner> task_runner) override { + return std::make_unique<EmptyDataReader>(client, std::move(task_runner)); + } + const char* DebugName() const override { return "EmptyDataHandle"; } +}; + +} // namespace + +// Max number of CORS redirects handled in DocumentThreadableLoader. Same number +// as net/url_request/url_request.cc, and same number as +// https://fetch.spec.whatwg.org/#concept-http-fetch, Step 4. +// FIXME: currently the number of redirects is counted and limited here and in +// net/url_request/url_request.cc separately. +static const int kMaxCORSRedirects = 20; + +// static +void DocumentThreadableLoader::LoadResourceSynchronously( + ThreadableLoadingContext& loading_context, + const ResourceRequest& request, + ThreadableLoaderClient& client, + const ThreadableLoaderOptions& options, + const ResourceLoaderOptions& resource_loader_options) { + (new DocumentThreadableLoader(loading_context, &client, kLoadSynchronously, + options, resource_loader_options)) + ->Start(request); +} + +// static +std::unique_ptr<ResourceRequest> +DocumentThreadableLoader::CreateAccessControlPreflightRequest( + const ResourceRequest& request, + const SecurityOrigin* origin) { + const KURL& request_url = request.Url(); + + DCHECK(request_url.User().IsEmpty()); + DCHECK(request_url.Pass().IsEmpty()); + + std::unique_ptr<ResourceRequest> preflight_request = + std::make_unique<ResourceRequest>(request_url); + preflight_request->SetHTTPMethod(HTTPNames::OPTIONS); + preflight_request->SetHTTPHeaderField( + HTTPNames::Access_Control_Request_Method, request.HttpMethod()); + preflight_request->SetPriority(request.Priority()); + preflight_request->SetRequestContext(request.GetRequestContext()); + preflight_request->SetFetchCredentialsMode( + network::mojom::FetchCredentialsMode::kOmit); + preflight_request->SetSkipServiceWorker(true); + preflight_request->SetHTTPReferrer( + Referrer(request.HttpReferrer(), request.GetReferrerPolicy())); + + if (request.IsExternalRequest()) { + preflight_request->SetHTTPHeaderField( + HTTPNames::Access_Control_Request_External, "true"); + } + + const AtomicString request_headers = + CreateAccessControlRequestHeadersHeader(request.HttpHeaderFields()); + if (request_headers != g_null_atom) { + preflight_request->SetHTTPHeaderField( + HTTPNames::Access_Control_Request_Headers, request_headers); + } + + if (origin) + preflight_request->SetHTTPOrigin(origin); + + return preflight_request; +} + +// static +std::unique_ptr<ResourceRequest> +DocumentThreadableLoader::CreateAccessControlPreflightRequestForTesting( + const ResourceRequest& request) { + return CreateAccessControlPreflightRequest(request, nullptr); +} + +// static +DocumentThreadableLoader* DocumentThreadableLoader::Create( + ThreadableLoadingContext& loading_context, + ThreadableLoaderClient* client, + const ThreadableLoaderOptions& options, + const ResourceLoaderOptions& resource_loader_options) { + return new DocumentThreadableLoader(loading_context, client, + kLoadAsynchronously, options, + resource_loader_options); +} + +DocumentThreadableLoader::DocumentThreadableLoader( + ThreadableLoadingContext& loading_context, + ThreadableLoaderClient* client, + BlockingBehavior blocking_behavior, + const ThreadableLoaderOptions& options, + const ResourceLoaderOptions& resource_loader_options) + : client_(client), + loading_context_(&loading_context), + options_(options), + resource_loader_options_(resource_loader_options), + out_of_blink_cors_(RuntimeEnabledFeatures::OutOfBlinkCORSEnabled()), + cors_flag_(false), + security_origin_(resource_loader_options_.security_origin), + is_using_data_consumer_handle_(false), + async_(blocking_behavior == kLoadAsynchronously), + request_context_(WebURLRequest::kRequestContextUnspecified), + fetch_request_mode_(network::mojom::FetchRequestMode::kSameOrigin), + fetch_credentials_mode_(network::mojom::FetchCredentialsMode::kOmit), + timeout_timer_( + GetExecutionContext()->GetTaskRunner(TaskType::kNetworking), + this, + &DocumentThreadableLoader::DidTimeout), + request_started_seconds_(0.0), + cors_redirect_limit_(0), + redirect_mode_(network::mojom::FetchRedirectMode::kFollow), + override_referrer_(false) { + DCHECK(client); +} + +void DocumentThreadableLoader::Start(const ResourceRequest& request) { + // Setting an outgoing referer is only supported in the async code path. + DCHECK(async_ || request.HttpReferrer().IsEmpty()); + + bool cors_enabled = + CORS::IsCORSEnabledRequestMode(request.GetFetchRequestMode()); + + // kPreventPreflight can be used only when the CORS is enabled. + DCHECK(request.CORSPreflightPolicy() == + network::mojom::CORSPreflightPolicy::kConsiderPreflight || + cors_enabled); + + if (cors_enabled) + cors_redirect_limit_ = kMaxCORSRedirects; + + request_context_ = request.GetRequestContext(); + fetch_request_mode_ = request.GetFetchRequestMode(); + fetch_credentials_mode_ = request.GetFetchCredentialsMode(); + redirect_mode_ = request.GetFetchRedirectMode(); + + if (request.GetFetchRequestMode() == + network::mojom::FetchRequestMode::kNoCORS) { + SECURITY_CHECK(WebCORS::IsNoCORSAllowedContext(request_context_)); + } else { + cors_flag_ = !GetSecurityOrigin()->CanRequest(request.Url()); + } + + // The CORS flag variable is not yet used at the step in the spec that + // corresponds to this line, but divert |cors_flag_| here for convenience. + if (cors_flag_ && request.GetFetchRequestMode() == + network::mojom::FetchRequestMode::kSameOrigin) { + probe::documentThreadableLoaderFailedToStartLoadingForClient( + GetExecutionContext(), client_); + ThreadableLoaderClient* client = client_; + Clear(); + ResourceError error = ResourceError::CancelledDueToAccessCheckError( + request.Url(), ResourceRequestBlockedReason::kOther, + CORS::GetErrorString( + CORS::ErrorParameter::CreateForDisallowedByMode(request.Url()))); + GetExecutionContext()->AddConsoleMessage(ConsoleMessage::Create( + kJSMessageSource, kErrorMessageLevel, error.LocalizedDescription())); + client->DidFail(error); + return; + } + + request_started_seconds_ = CurrentTimeTicksInSeconds(); + + // Save any headers on the request here. If this request redirects + // cross-origin, we cancel the old request create a new one, and copy these + // headers. + request_headers_ = request.HttpHeaderFields(); + + ResourceRequest new_request(request); + + // Set the service worker mode to none if "bypass for network" in DevTools is + // enabled. + bool should_bypass_service_worker = false; + probe::shouldBypassServiceWorker(GetExecutionContext(), + &should_bypass_service_worker); + if (should_bypass_service_worker) + new_request.SetSkipServiceWorker(true); + + // Process the CORS protocol inside the DocumentThreadableLoader for the + // following cases: + // + // - When the request is sync or the protocol is unsupported since we can + // assume that any service worker (SW) is skipped for such requests by + // content/ code. + // - When |skip_service_worker| is true, any SW will be skipped. + // - If we're not yet controlled by a SW, then we're sure that this + // request won't be intercepted by a SW. In case we end up with + // sending a CORS preflight request, the actual request to be sent later + // may be intercepted. This is taken care of in LoadPreflightRequest() by + // setting |skip_service_worker| to true. + // + // From the above analysis, you can see that the request can never be + // intercepted by a SW inside this if-block. It's because: + // - |skip_service_worker| needs to be false, and + // - we're controlled by a SW at this point + // to allow a SW to intercept the request. Even when the request gets issued + // asynchronously after performing the CORS preflight, it doesn't get + // intercepted since LoadPreflightRequest() sets the flag to kNone in advance. + if (!async_ || new_request.GetSkipServiceWorker() || + !SchemeRegistry::ShouldTreatURLSchemeAsAllowingServiceWorkers( + new_request.Url().Protocol()) || + !loading_context_->GetResourceFetcher()->IsControlledByServiceWorker()) { + DispatchInitialRequest(new_request); + return; + } + + if (CORS::IsCORSEnabledRequestMode(request.GetFetchRequestMode())) { + // Save the request to fallback_request_for_service_worker to use when the + // service worker doesn't handle (call respondWith()) a CORS enabled + // request. + fallback_request_for_service_worker_ = ResourceRequest(request); + // Skip the service worker for the fallback request. + fallback_request_for_service_worker_.SetSkipServiceWorker(true); + } + + LoadRequest(new_request, resource_loader_options_); +} + +void DocumentThreadableLoader::DispatchInitialRequest( + ResourceRequest& request) { + if (!request.IsExternalRequest() && !cors_flag_) { + LoadRequest(request, resource_loader_options_); + return; + } + + DCHECK(CORS::IsCORSEnabledRequestMode(request.GetFetchRequestMode()) || + request.IsExternalRequest()); + + MakeCrossOriginAccessRequest(request); +} + +void DocumentThreadableLoader::PrepareCrossOriginRequest( + ResourceRequest& request) const { + if (GetSecurityOrigin()) + request.SetHTTPOrigin(GetSecurityOrigin()); + if (override_referrer_) + request.SetHTTPReferrer(referrer_after_redirect_); +} + +void DocumentThreadableLoader::LoadPreflightRequest( + const ResourceRequest& actual_request, + const ResourceLoaderOptions& actual_options) { + std::unique_ptr<ResourceRequest> preflight_request = + CreateAccessControlPreflightRequest(actual_request, GetSecurityOrigin()); + + actual_request_ = actual_request; + actual_options_ = actual_options; + + // Explicitly set |skip_service_worker| to true here. Although the page is + // not controlled by a SW at this point, a new SW may be controlling the + // page when this actual request gets sent later. We should not send the + // actual request to the SW. See https://crbug.com/604583. + actual_request_.SetSkipServiceWorker(true); + + // Create a ResourceLoaderOptions for preflight. + ResourceLoaderOptions preflight_options = actual_options; + + LoadRequest(*preflight_request, preflight_options); +} + +void DocumentThreadableLoader::MakeCrossOriginAccessRequest( + const ResourceRequest& request) { + DCHECK(CORS::IsCORSEnabledRequestMode(request.GetFetchRequestMode()) || + request.IsExternalRequest()); + DCHECK(client_); + DCHECK(!GetResource()); + + // Cross-origin requests are only allowed certain registered schemes. We would + // catch this when checking response headers later, but there is no reason to + // send a request, preflighted or not, that's guaranteed to be denied. + if (!SchemeRegistry::ShouldTreatURLSchemeAsCORSEnabled( + request.Url().Protocol())) { + probe::documentThreadableLoaderFailedToStartLoadingForClient( + GetExecutionContext(), client_); + DispatchDidFailAccessControlCheck( + ResourceError::CancelledDueToAccessCheckError( + request.Url(), ResourceRequestBlockedReason::kOther, + String::Format( + "Cross origin requests are only supported for " + "protocol schemes: %s.", + WebCORS::ListOfCORSEnabledURLSchemes().Ascii().c_str()))); + return; + } + + // Non-secure origins may not make "external requests": + // https://wicg.github.io/cors-rfc1918/#integration-fetch + String error_message; + if (!GetExecutionContext()->IsSecureContext(error_message) && + request.IsExternalRequest()) { + DispatchDidFailAccessControlCheck( + ResourceError::CancelledDueToAccessCheckError( + request.Url(), ResourceRequestBlockedReason::kOrigin, + "Requests to internal network resources are not allowed " + "from non-secure contexts (see https://goo.gl/Y0ZkNV). " + "This is an experimental restriction which is part of " + "'https://mikewest.github.io/cors-rfc1918/'.")); + return; + } + + ResourceRequest cross_origin_request(request); + ResourceLoaderOptions cross_origin_options(resource_loader_options_); + + cross_origin_request.RemoveUserAndPassFromURL(); + + // Enforce the CORS preflight for checking the Access-Control-Allow-External + // header. The CORS preflight cache doesn't help for this purpose. + if (request.IsExternalRequest()) { + LoadPreflightRequest(cross_origin_request, cross_origin_options); + return; + } + + if (request.GetFetchRequestMode() != + network::mojom::FetchRequestMode::kCORSWithForcedPreflight) { + if (request.CORSPreflightPolicy() == + network::mojom::CORSPreflightPolicy::kPreventPreflight) { + PrepareCrossOriginRequest(cross_origin_request); + LoadRequest(cross_origin_request, cross_origin_options); + return; + } + + DCHECK_EQ(request.CORSPreflightPolicy(), + network::mojom::CORSPreflightPolicy::kConsiderPreflight); + + // We use ContainsOnlyCORSSafelistedOrForbiddenHeaders() here since + // |request| may have been modified in the process of loading (not from + // the user's input). For example, referrer. We need to accept them. For + // security, we must reject forbidden headers/methods at the point we + // accept user's input. Not here. + if (CORS::IsCORSSafelistedMethod(request.HttpMethod()) && + WebCORS::ContainsOnlyCORSSafelistedOrForbiddenHeaders( + request.HttpHeaderFields())) { + PrepareCrossOriginRequest(cross_origin_request); + LoadRequest(cross_origin_request, cross_origin_options); + return; + } + } + + // Now, we need to check that the request passes the CORS preflight either by + // issuing a CORS preflight or based on an entry in the CORS preflight cache. + + bool should_ignore_preflight_cache = false; + // Prevent use of the CORS preflight cache when instructed by the DevTools + // not to use caches. + probe::shouldForceCORSPreflight(GetExecutionContext(), + &should_ignore_preflight_cache); + if (should_ignore_preflight_cache || + !CORS::CheckIfRequestCanSkipPreflight( + GetSecurityOrigin()->ToString(), cross_origin_request.Url(), + cross_origin_request.GetFetchCredentialsMode(), + cross_origin_request.HttpMethod(), + cross_origin_request.HttpHeaderFields())) { + LoadPreflightRequest(cross_origin_request, cross_origin_options); + return; + } + + // We don't want any requests that could involve a CORS preflight to get + // intercepted by a foreign SW, even if we have the result of the preflight + // cached already. See https://crbug.com/674370. + cross_origin_request.SetSkipServiceWorker(true); + + PrepareCrossOriginRequest(cross_origin_request); + LoadRequest(cross_origin_request, cross_origin_options); +} + +DocumentThreadableLoader::~DocumentThreadableLoader() { + CHECK(!client_); + DCHECK(!GetResource()); +} + +void DocumentThreadableLoader::OverrideTimeout( + unsigned long timeout_milliseconds) { + DCHECK(async_); + + // |m_requestStartedSeconds| == 0.0 indicates loading is already finished and + // |m_timeoutTimer| is already stopped, and thus we do nothing for such cases. + // See https://crbug.com/551663 for details. + if (request_started_seconds_ <= 0.0) + return; + + timeout_timer_.Stop(); + // At the time of this method's implementation, it is only ever called by + // XMLHttpRequest, when the timeout attribute is set after sending the + // request. + // + // The XHR request says to resolve the time relative to when the request + // was initially sent, however other uses of this method may need to + // behave differently, in which case this should be re-arranged somehow. + if (timeout_milliseconds) { + double elapsed_time = + CurrentTimeTicksInSeconds() - request_started_seconds_; + double next_fire = timeout_milliseconds / 1000.0; + double resolved_time = std::max(next_fire - elapsed_time, 0.0); + timeout_timer_.StartOneShot(resolved_time, FROM_HERE); + } +} + +void DocumentThreadableLoader::Cancel() { + // Cancel can re-enter, and therefore |resource()| might be null here as a + // result. + if (!client_ || !GetResource()) { + Clear(); + return; + } + + DispatchDidFail(ResourceError::CancelledError(GetResource()->Url())); +} + +void DocumentThreadableLoader::Detach() { + Resource* resource = GetResource(); + if (resource) + resource->SetDetachable(); + Clear(); +} + +void DocumentThreadableLoader::SetDefersLoading(bool value) { + if (GetResource() && GetResource()->Loader()) + GetResource()->Loader()->SetDefersLoading(value); +} + +void DocumentThreadableLoader::Clear() { + client_ = nullptr; + timeout_timer_.Stop(); + request_started_seconds_ = 0.0; + if (GetResource()) + checker_.WillRemoveClient(); + ClearResource(); +} + +// In this method, we can clear |request| to tell content::WebURLLoaderImpl of +// Chromium not to follow the redirect. This works only when this method is +// called by RawResource::willSendRequest(). If called by +// RawResource::didAddClient(), clearing |request| won't be propagated to +// content::WebURLLoaderImpl. So, this loader must also get detached from the +// resource by calling clearResource(). +// TODO(toyoshim): Implement OOR-CORS mode specific redirect code. +bool DocumentThreadableLoader::RedirectReceived( + Resource* resource, + const ResourceRequest& new_request, + const ResourceResponse& redirect_response) { + DCHECK(client_); + DCHECK_EQ(resource, GetResource()); + DCHECK(async_); + + checker_.RedirectReceived(); + + const KURL& new_url = new_request.Url(); + const KURL& original_url = redirect_response.Url(); + + if (!actual_request_.IsNull()) { + ReportResponseReceived(resource->Identifier(), redirect_response); + + HandlePreflightFailure(original_url, + "Response for preflight is invalid (redirect)"); + + return false; + } + + if (redirect_mode_ == network::mojom::FetchRedirectMode::kManual) { + // We use |redirect_mode_| to check the original redirect mode. + // |new_request| is a new request for redirect. So we don't set the + // redirect mode of it in WebURLLoaderImpl::Context::OnReceivedRedirect(). + DCHECK(new_request.UseStreamOnResponse()); + // There is no need to read the body of redirect response because there is + // no way to read the body of opaque-redirect filtered response's internal + // response. + // TODO(horo): If we support any API which expose the internal body, we will + // have to read the body. And also HTTPCache changes will be needed because + // it doesn't store the body of redirect responses. + ResponseReceived(resource, redirect_response, + std::make_unique<EmptyDataHandle>()); + + if (client_) { + DCHECK(actual_request_.IsNull()); + NotifyFinished(resource); + } + + return false; + } + + if (redirect_mode_ == network::mojom::FetchRedirectMode::kError) { + ThreadableLoaderClient* client = client_; + Clear(); + client->DidFailRedirectCheck(); + + return false; + } + + // Allow same origin requests to continue after allowing clients to audit the + // redirect. + if (IsAllowedRedirect(new_request.GetFetchRequestMode(), new_url)) { + client_->DidReceiveRedirectTo(new_url); + if (client_->IsDocumentThreadableLoaderClient()) { + return static_cast<DocumentThreadableLoaderClient*>(client_) + ->WillFollowRedirect(new_url, redirect_response); + } + return true; + } + + if (cors_redirect_limit_ <= 0) { + ThreadableLoaderClient* client = client_; + Clear(); + client->DidFailRedirectCheck(); + return false; + } + + --cors_redirect_limit_; + + probe::didReceiveCORSRedirectResponse( + GetExecutionContext(), resource->Identifier(), + GetDocument() && GetDocument()->GetFrame() + ? GetDocument()->GetFrame()->Loader().GetDocumentLoader() + : nullptr, + redirect_response, resource); + + WTF::Optional<network::mojom::CORSError> redirect_error = + CORS::CheckRedirectLocation(new_url); + if (redirect_error) { + DispatchDidFailAccessControlCheck( + ResourceError::CancelledDueToAccessCheckError( + original_url, ResourceRequestBlockedReason::kOther, + CORS::GetErrorString(CORS::ErrorParameter::CreateForRedirectCheck( + *redirect_error, original_url, new_url)))); + return false; + } + + if (cors_flag_) { + // The redirect response must pass the access control check if the CORS + // flag is set. + WTF::Optional<network::mojom::CORSError> access_error = CORS::CheckAccess( + original_url, redirect_response.HttpStatusCode(), + redirect_response.HttpHeaderFields(), + new_request.GetFetchCredentialsMode(), *GetSecurityOrigin()); + if (access_error) { + DispatchDidFailAccessControlCheck( + ResourceError::CancelledDueToAccessCheckError( + original_url, ResourceRequestBlockedReason::kOther, + CORS::GetErrorString(CORS::ErrorParameter::CreateForAccessCheck( + *access_error, original_url, + redirect_response.HttpStatusCode(), + redirect_response.HttpHeaderFields(), *GetSecurityOrigin(), + request_context_, new_url)))); + return false; + } + } + + client_->DidReceiveRedirectTo(new_url); + + // FIXME: consider combining this with CORS redirect handling performed by + // CrossOriginAccessControl::handleRedirect(). + if (GetResource()) + checker_.WillRemoveClient(); + ClearResource(); + + // If + // - CORS flag is set, and + // - the origin of the redirect target URL is not same origin with the origin + // of the current request's URL + // set the source origin to a unique opaque origin. + // + // See https://fetch.spec.whatwg.org/#http-redirect-fetch. + if (cors_flag_) { + scoped_refptr<const SecurityOrigin> original_origin = + SecurityOrigin::Create(original_url); + scoped_refptr<const SecurityOrigin> new_origin = + SecurityOrigin::Create(new_url); + if (!original_origin->IsSameSchemeHostPort(new_origin.get())) + security_origin_ = SecurityOrigin::CreateUnique(); + } + + // Set |cors_flag_| so that further logic (corresponds to the main fetch in + // the spec) will be performed with CORS flag set. + // See https://fetch.spec.whatwg.org/#http-redirect-fetch. + cors_flag_ = true; + + // Save the referrer to use when following the redirect. + override_referrer_ = true; + referrer_after_redirect_ = + Referrer(new_request.HttpReferrer(), new_request.GetReferrerPolicy()); + + ResourceRequest cross_origin_request(new_request); + + // Remove any headers that may have been added by the network layer that cause + // access control to fail. + cross_origin_request.ClearHTTPReferrer(); + cross_origin_request.ClearHTTPOrigin(); + cross_origin_request.ClearHTTPUserAgent(); + // Add any request headers which we previously saved from the + // original request. + for (const auto& header : request_headers_) + cross_origin_request.SetHTTPHeaderField(header.key, header.value); + MakeCrossOriginAccessRequest(cross_origin_request); + + return false; +} + +void DocumentThreadableLoader::RedirectBlocked() { + checker_.RedirectBlocked(); + + // Tells the client that a redirect was received but not followed (for an + // unknown reason). + ThreadableLoaderClient* client = client_; + Clear(); + client->DidFailRedirectCheck(); +} + +void DocumentThreadableLoader::DataSent( + Resource* resource, + unsigned long long bytes_sent, + unsigned long long total_bytes_to_be_sent) { + DCHECK(client_); + DCHECK_EQ(resource, GetResource()); + DCHECK(async_); + + checker_.DataSent(); + client_->DidSendData(bytes_sent, total_bytes_to_be_sent); +} + +void DocumentThreadableLoader::DataDownloaded(Resource* resource, + int data_length) { + DCHECK(client_); + DCHECK_EQ(resource, GetResource()); + DCHECK(actual_request_.IsNull()); + DCHECK(async_); + + checker_.DataDownloaded(); + client_->DidDownloadData(data_length); +} + +void DocumentThreadableLoader::DidReceiveResourceTiming( + Resource* resource, + const ResourceTimingInfo& info) { + DCHECK(client_); + DCHECK_EQ(resource, GetResource()); + DCHECK(async_); + + client_->DidReceiveResourceTiming(info); +} + +void DocumentThreadableLoader::DidDownloadToBlob( + Resource* resource, + scoped_refptr<BlobDataHandle> blob) { + DCHECK(client_); + DCHECK_EQ(resource, GetResource()); + DCHECK(async_); + + checker_.DidDownloadToBlob(); + client_->DidDownloadToBlob(std::move(blob)); +} + +void DocumentThreadableLoader::ResponseReceived( + Resource* resource, + const ResourceResponse& response, + std::unique_ptr<WebDataConsumerHandle> handle) { + DCHECK_EQ(resource, GetResource()); + DCHECK(async_); + + checker_.ResponseReceived(); + + if (handle) + is_using_data_consumer_handle_ = true; + + HandleResponse(resource->Identifier(), fetch_request_mode_, + fetch_credentials_mode_, response, std::move(handle)); +} + +void DocumentThreadableLoader::HandlePreflightResponse( + const ResourceResponse& response) { + WTF::Optional<network::mojom::CORSError> cors_error = CORS::CheckAccess( + response.Url(), response.HttpStatusCode(), response.HttpHeaderFields(), + actual_request_.GetFetchCredentialsMode(), *GetSecurityOrigin()); + if (cors_error) { + StringBuilder builder; + builder.Append( + "Response to preflight request doesn't pass access " + "control check: "); + builder.Append( + CORS::GetErrorString(CORS::ErrorParameter::CreateForAccessCheck( + *cors_error, response.Url(), response.HttpStatusCode(), + response.HttpHeaderFields(), *GetSecurityOrigin(), + request_context_))); + HandlePreflightFailure(response.Url(), builder.ToString()); + return; + } + + WTF::Optional<network::mojom::CORSError> preflight_error = + CORS::CheckPreflight(response.HttpStatusCode()); + if (preflight_error) { + HandlePreflightFailure( + response.Url(), CORS::GetErrorString( + CORS::ErrorParameter::CreateForPreflightStatusCheck( + response.HttpStatusCode()))); + return; + } + + if (actual_request_.IsExternalRequest()) { + WTF::Optional<network::mojom::CORSError> external_preflight_status = + CORS::CheckExternalPreflight(response.HttpHeaderFields()); + if (external_preflight_status) { + HandlePreflightFailure( + response.Url(), + CORS::GetErrorString( + CORS::ErrorParameter::CreateForExternalPreflightCheck( + *external_preflight_status, response.HttpHeaderFields()))); + return; + } + } + + String access_control_error_description; + if (!CORS::EnsurePreflightResultAndCacheOnSuccess( + response.HttpHeaderFields(), GetSecurityOrigin()->ToString(), + actual_request_.Url(), actual_request_.HttpMethod(), + actual_request_.HttpHeaderFields(), + actual_request_.GetFetchCredentialsMode(), + &access_control_error_description)) { + HandlePreflightFailure(response.Url(), access_control_error_description); + } +} + +void DocumentThreadableLoader::ReportResponseReceived( + unsigned long identifier, + const ResourceResponse& response) { + LocalFrame* frame = GetDocument() ? GetDocument()->GetFrame() : nullptr; + if (!frame) + return; + DocumentLoader* loader = frame->Loader().GetDocumentLoader(); + probe::didReceiveResourceResponse(GetExecutionContext(), identifier, loader, + response, GetResource()); + frame->Console().ReportResourceResponseReceived(loader, identifier, response); +} + +void DocumentThreadableLoader::HandleResponse( + unsigned long identifier, + network::mojom::FetchRequestMode request_mode, + network::mojom::FetchCredentialsMode credentials_mode, + const ResourceResponse& response, + std::unique_ptr<WebDataConsumerHandle> handle) { + DCHECK(client_); + + // TODO(toyoshim): Support OOR-CORS preflight and Service Worker case. + // https://crbug.com/736308. + if (out_of_blink_cors_ && actual_request_.IsNull() && + !response.WasFetchedViaServiceWorker()) { + client_->DidReceiveResponse(identifier, response, std::move(handle)); + return; + } + + // Code path for legacy Blink CORS. + if (!actual_request_.IsNull()) { + ReportResponseReceived(identifier, response); + HandlePreflightResponse(response); + return; + } + + if (response.WasFetchedViaServiceWorker()) { + if (response.WasFallbackRequiredByServiceWorker()) { + // At this point we must have m_fallbackRequestForServiceWorker. (For + // SharedWorker the request won't be CORS or CORS-with-preflight, + // therefore fallback-to-network is handled in the browser process when + // the ServiceWorker does not call respondWith().) + DCHECK(!fallback_request_for_service_worker_.IsNull()); + ReportResponseReceived(identifier, response); + LoadFallbackRequestForServiceWorker(); + return; + } + + // It's possible that we issue a fetch with request with non "no-cors" + // mode but get an opaque filtered response if a service worker is involved. + // We dispatch a CORS failure for the case. + // TODO(yhirano): This is probably not spec conformant. Fix it after + // https://github.com/w3c/preload/issues/100 is addressed. + if (request_mode != network::mojom::FetchRequestMode::kNoCORS && + response.ResponseTypeViaServiceWorker() == + network::mojom::FetchResponseType::kOpaque) { + DispatchDidFailAccessControlCheck( + ResourceError::CancelledDueToAccessCheckError( + response.Url(), ResourceRequestBlockedReason::kOther, + CORS::GetErrorString( + CORS::ErrorParameter::CreateForInvalidResponse( + response.Url(), *GetSecurityOrigin())))); + return; + } + + fallback_request_for_service_worker_ = ResourceRequest(); + client_->DidReceiveResponse(identifier, response, std::move(handle)); + return; + } + + // Even if the request met the conditions to get handled by a Service Worker + // in the constructor of this class (and therefore + // |m_fallbackRequestForServiceWorker| is set), the Service Worker may skip + // processing the request. Only if the request is same origin, the skipped + // response may come here (wasFetchedViaServiceWorker() returns false) since + // such a request doesn't have to go through the CORS algorithm by calling + // loadFallbackRequestForServiceWorker(). + DCHECK(fallback_request_for_service_worker_.IsNull() || + GetSecurityOrigin()->CanRequest( + fallback_request_for_service_worker_.Url())); + fallback_request_for_service_worker_ = ResourceRequest(); + + if (CORS::IsCORSEnabledRequestMode(request_mode) && cors_flag_) { + WTF::Optional<network::mojom::CORSError> access_error = CORS::CheckAccess( + response.Url(), response.HttpStatusCode(), response.HttpHeaderFields(), + credentials_mode, *GetSecurityOrigin()); + if (access_error) { + ReportResponseReceived(identifier, response); + DispatchDidFailAccessControlCheck( + ResourceError::CancelledDueToAccessCheckError( + response.Url(), ResourceRequestBlockedReason::kOther, + CORS::GetErrorString(CORS::ErrorParameter::CreateForAccessCheck( + *access_error, response.Url(), response.HttpStatusCode(), + response.HttpHeaderFields(), *GetSecurityOrigin(), + request_context_)))); + return; + } + } + + client_->DidReceiveResponse(identifier, response, std::move(handle)); +} + +void DocumentThreadableLoader::SetSerializedCachedMetadata(Resource*, + const char* data, + size_t size) { + checker_.SetSerializedCachedMetadata(); + + if (!actual_request_.IsNull()) + return; + client_->DidReceiveCachedMetadata(data, size); +} + +void DocumentThreadableLoader::DataReceived(Resource* resource, + const char* data, + size_t data_length) { + DCHECK_EQ(resource, GetResource()); + DCHECK(async_); + + checker_.DataReceived(); + + if (is_using_data_consumer_handle_) + return; + + // TODO(junov): Fix the ThreadableLoader ecosystem to use size_t. Until then, + // we use safeCast to trap potential overflows. + HandleReceivedData(data, SafeCast<unsigned>(data_length)); +} + +void DocumentThreadableLoader::HandleReceivedData(const char* data, + size_t data_length) { + DCHECK(client_); + + // Preflight data should be invisible to clients. + if (!actual_request_.IsNull()) + return; + + DCHECK(fallback_request_for_service_worker_.IsNull()); + + client_->DidReceiveData(data, data_length); +} + +void DocumentThreadableLoader::NotifyFinished(Resource* resource) { + DCHECK(client_); + DCHECK_EQ(resource, GetResource()); + DCHECK(async_); + + checker_.NotifyFinished(resource); + + if (resource->ErrorOccurred()) { + DispatchDidFail(resource->GetResourceError()); + } else { + HandleSuccessfulFinish(resource->Identifier(), resource->LoadFinishTime()); + } +} + +void DocumentThreadableLoader::HandleSuccessfulFinish(unsigned long identifier, + double finish_time) { + DCHECK(fallback_request_for_service_worker_.IsNull()); + + if (!actual_request_.IsNull()) { + DCHECK(actual_request_.IsExternalRequest() || cors_flag_); + LoadActualRequest(); + return; + } + + ThreadableLoaderClient* client = client_; + // Protect the resource in |didFinishLoading| in order not to release the + // downloaded file. + Persistent<Resource> protect = GetResource(); + Clear(); + client->DidFinishLoading(identifier, finish_time); +} + +void DocumentThreadableLoader::DidTimeout(TimerBase* timer) { + DCHECK(async_); + DCHECK_EQ(timer, &timeout_timer_); + // clearResource() may be called in clear() and some other places. clear() + // calls stop() on |m_timeoutTimer|. In the other places, the resource is set + // again. If the creation fails, clear() is called. So, here, resource() is + // always non-nullptr. + DCHECK(GetResource()); + // When |m_client| is set to nullptr only in clear() where |m_timeoutTimer| + // is stopped. So, |m_client| is always non-nullptr here. + DCHECK(client_); + + DispatchDidFail(ResourceError::TimeoutError(GetResource()->Url())); +} + +void DocumentThreadableLoader::LoadFallbackRequestForServiceWorker() { + if (GetResource()) + checker_.WillRemoveClient(); + ClearResource(); + ResourceRequest fallback_request(fallback_request_for_service_worker_); + fallback_request_for_service_worker_ = ResourceRequest(); + DispatchInitialRequest(fallback_request); +} + +void DocumentThreadableLoader::LoadActualRequest() { + ResourceRequest actual_request = actual_request_; + ResourceLoaderOptions actual_options = actual_options_; + actual_request_ = ResourceRequest(); + actual_options_ = ResourceLoaderOptions(); + + if (GetResource()) + checker_.WillRemoveClient(); + ClearResource(); + + PrepareCrossOriginRequest(actual_request); + LoadRequest(actual_request, actual_options); +} + +void DocumentThreadableLoader::HandlePreflightFailure( + const KURL& url, + const String& error_description) { + // Prevent handleSuccessfulFinish() from bypassing access check. + actual_request_ = ResourceRequest(); + + DispatchDidFailAccessControlCheck( + ResourceError::CancelledDueToAccessCheckError( + url, ResourceRequestBlockedReason::kOther, error_description)); +} + +void DocumentThreadableLoader::DispatchDidFailAccessControlCheck( + const ResourceError& error) { + const String message = "Failed to load " + error.FailingURL() + ": " + + error.LocalizedDescription(); + GetExecutionContext()->AddConsoleMessage( + ConsoleMessage::Create(kJSMessageSource, kErrorMessageLevel, message)); + + ThreadableLoaderClient* client = client_; + Clear(); + client->DidFail(error); +} + +void DocumentThreadableLoader::DispatchDidFail(const ResourceError& error) { + if (error.CORSErrorStatus()) { + DCHECK(out_of_blink_cors_); + // TODO(toyoshim): Should consider to pass correct arguments instead of + // WebURL() and WebHTTPHeaderMap() to GetErrorString(). + // We still need plumbing required information. + const int response_code = + error.CORSErrorStatus()->related_response_headers + ? error.CORSErrorStatus()->related_response_headers->response_code() + : 0; + GetExecutionContext()->AddConsoleMessage(ConsoleMessage::Create( + kJSMessageSource, kErrorMessageLevel, + "Failed to load " + error.FailingURL() + ": " + + CORS::GetErrorString(CORS::ErrorParameter::Create( + error.CORSErrorStatus()->cors_error, + KURL(error.FailingURL()), KURL(), + response_code, HTTPHeaderMap(), + *GetSecurityOrigin(), request_context_)) + .Utf8() + .data())); + } + ThreadableLoaderClient* client = client_; + Clear(); + client->DidFail(error); +} + +void DocumentThreadableLoader::LoadRequestAsync( + const ResourceRequest& request, + ResourceLoaderOptions resource_loader_options) { + if (!actual_request_.IsNull()) + resource_loader_options.data_buffering_policy = kBufferData; + + // The timer can be active if this is the actual request of a + // CORS-with-preflight request. + if (options_.timeout_milliseconds > 0 && !timeout_timer_.IsActive()) { + timeout_timer_.StartOneShot(options_.timeout_milliseconds / 1000.0, + FROM_HERE); + } + + FetchParameters new_params(request, resource_loader_options); + if (request.GetFetchRequestMode() == + network::mojom::FetchRequestMode::kNoCORS) { + new_params.SetOriginRestriction(FetchParameters::kNoOriginRestriction); + } + DCHECK(!GetResource()); + + ResourceFetcher* fetcher = loading_context_->GetResourceFetcher(); + if (request.GetRequestContext() == WebURLRequest::kRequestContextVideo || + request.GetRequestContext() == WebURLRequest::kRequestContextAudio) { + RawResource::FetchMedia(new_params, fetcher, this); + } else if (request.GetRequestContext() == + WebURLRequest::kRequestContextManifest) { + RawResource::FetchManifest(new_params, fetcher, this); + } else { + RawResource::Fetch(new_params, fetcher, this); + } + checker_.WillAddClient(); + + if (GetResource()->IsLoading()) { + unsigned long identifier = GetResource()->Identifier(); + probe::documentThreadableLoaderStartedLoadingForClient( + GetExecutionContext(), identifier, client_); + } else { + probe::documentThreadableLoaderFailedToStartLoadingForClient( + GetExecutionContext(), client_); + } +} + +void DocumentThreadableLoader::LoadRequestSync( + const ResourceRequest& request, + ResourceLoaderOptions resource_loader_options) { + FetchParameters fetch_params(request, resource_loader_options); + if (request.GetFetchRequestMode() == + network::mojom::FetchRequestMode::kNoCORS) { + fetch_params.SetOriginRestriction(FetchParameters::kNoOriginRestriction); + } + if (options_.timeout_milliseconds > 0) { + fetch_params.MutableResourceRequest().SetTimeoutInterval( + options_.timeout_milliseconds / 1000.0); + } + RawResource* resource = RawResource::FetchSynchronously( + fetch_params, loading_context_->GetResourceFetcher()); + ResourceResponse response = resource->GetResponse(); + unsigned long identifier = resource->Identifier(); + probe::documentThreadableLoaderStartedLoadingForClient(GetExecutionContext(), + identifier, client_); + ThreadableLoaderClient* client = client_; + const KURL& request_url = request.Url(); + + // No exception for file:/// resources, see <rdar://problem/4962298>. Also, if + // we have an HTTP response, then it wasn't a network error in fact. + if (resource->LoadFailedOrCanceled() && !request_url.IsLocalFile() && + response.HttpStatusCode() <= 0) { + client_ = nullptr; + client->DidFail(resource->GetResourceError()); + return; + } + + // FIXME: A synchronous request does not tell us whether a redirect happened + // or not, so we guess by comparing the request and response URLs. This isn't + // a perfect test though, since a server can serve a redirect to the same URL + // that was requested. Also comparing the request and response URLs as strings + // will fail if the requestURL still has its credentials. + if (request_url != response.Url() && + !IsAllowedRedirect(request.GetFetchRequestMode(), response.Url())) { + client_ = nullptr; + client->DidFailRedirectCheck(); + return; + } + + HandleResponse(identifier, request.GetFetchRequestMode(), + request.GetFetchCredentialsMode(), response, nullptr); + + // HandleResponse() may detect an error. In such a case (check |m_client| as + // it gets reset by clear() call), skip the rest. + // + // |this| is alive here since loadResourceSynchronously() keeps it alive until + // the end of the function. + if (!client_) + return; + + if (scoped_refptr<const SharedBuffer> data = resource->ResourceBuffer()) { + data->ForEachSegment([this](const char* segment, size_t segment_size, + size_t segment_offset) -> bool { + HandleReceivedData(segment, segment_size); + // The client may cancel this loader in handleReceivedData(). + return client_; + }); + } + + // The client may cancel this loader in handleReceivedData(). In such a case, + // skip the rest. + if (!client_) + return; + + WTF::Optional<int64_t> downloaded_file_length = + resource->DownloadedFileLength(); + if (downloaded_file_length) { + client_->DidDownloadData(*downloaded_file_length); + } + if (request.DownloadToBlob()) { + if (resource->DownloadedBlob()) + client_->DidDownloadData(resource->DownloadedBlob()->size()); + client_->DidDownloadToBlob(resource->DownloadedBlob()); + } + + HandleSuccessfulFinish(identifier, 0.0); +} + +void DocumentThreadableLoader::LoadRequest( + ResourceRequest& request, + ResourceLoaderOptions resource_loader_options) { + resource_loader_options.cors_handling_by_resource_fetcher = + kDisableCORSHandlingByResourceFetcher; + + bool allow_stored_credentials = false; + switch (request.GetFetchCredentialsMode()) { + case network::mojom::FetchCredentialsMode::kOmit: + break; + case network::mojom::FetchCredentialsMode::kSameOrigin: + // TODO(toyoshim): It's wrong to use |cors_flag| here. Fix it to use the + // response tainting. + // + // TODO(toyoshim): The credentials mode must work even when the "no-cors" + // mode is in use. See the following issues: + // - https://github.com/whatwg/fetch/issues/130 + // - https://github.com/whatwg/fetch/issues/169 + allow_stored_credentials = !cors_flag_; + break; + case network::mojom::FetchCredentialsMode::kInclude: + allow_stored_credentials = true; + break; + } + request.SetAllowStoredCredentials(allow_stored_credentials); + + resource_loader_options.security_origin = security_origin_; + if (async_) + LoadRequestAsync(request, resource_loader_options); + else + LoadRequestSync(request, resource_loader_options); +} + +bool DocumentThreadableLoader::IsAllowedRedirect( + network::mojom::FetchRequestMode fetch_request_mode, + const KURL& url) const { + if (fetch_request_mode == network::mojom::FetchRequestMode::kNoCORS) + return true; + + return !cors_flag_ && GetSecurityOrigin()->CanRequest(url); +} + +const SecurityOrigin* DocumentThreadableLoader::GetSecurityOrigin() const { + return security_origin_ + ? security_origin_.get() + : loading_context_->GetFetchContext()->GetSecurityOrigin(); +} + +Document* DocumentThreadableLoader::GetDocument() const { + ExecutionContext* context = GetExecutionContext(); + if (context->IsDocument()) + return ToDocument(context); + return nullptr; +} + +ExecutionContext* DocumentThreadableLoader::GetExecutionContext() const { + DCHECK(loading_context_); + return loading_context_->GetExecutionContext(); +} + +void DocumentThreadableLoader::Trace(blink::Visitor* visitor) { + visitor->Trace(loading_context_); + ThreadableLoader::Trace(visitor); + RawResourceClient::Trace(visitor); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/loader/document_threadable_loader.h b/chromium/third_party/blink/renderer/core/loader/document_threadable_loader.h new file mode 100644 index 00000000000..2349bcf9ec0 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/document_threadable_loader.h @@ -0,0 +1,256 @@ +/* + * Copyright (C) 2009, 2012 Google Inc. All rights reserved. + * Copyright (C) 2013, Intel Corporation + * + * 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. + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_DOCUMENT_THREADABLE_LOADER_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_DOCUMENT_THREADABLE_LOADER_H_ + +#include <memory> +#include "services/network/public/mojom/fetch_api.mojom-blink.h" +#include "third_party/blink/renderer/core/core_export.h" +#include "third_party/blink/renderer/core/loader/threadable_loader.h" +#include "third_party/blink/renderer/platform/heap/handle.h" +#include "third_party/blink/renderer/platform/loader/fetch/raw_resource.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource_error.h" +#include "third_party/blink/renderer/platform/network/http_header_map.h" +#include "third_party/blink/renderer/platform/timer.h" +#include "third_party/blink/renderer/platform/weborigin/referrer.h" +#include "third_party/blink/renderer/platform/wtf/forward.h" +#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h" + +namespace blink { + +class Document; +class KURL; +class ResourceRequest; +class SecurityOrigin; +class ThreadableLoaderClient; +class ThreadableLoadingContext; + +// TODO(horo): We are using this class not only in documents, but also in +// workers. We should change the name to ThreadableLoaderImpl. +class CORE_EXPORT DocumentThreadableLoader final : public ThreadableLoader, + private RawResourceClient { + USING_GARBAGE_COLLECTED_MIXIN(DocumentThreadableLoader); + + public: + static void LoadResourceSynchronously(ThreadableLoadingContext&, + const ResourceRequest&, + ThreadableLoaderClient&, + const ThreadableLoaderOptions&, + const ResourceLoaderOptions&); + + // Exposed for testing. Code outside this class should not call this function. + static std::unique_ptr<ResourceRequest> + CreateAccessControlPreflightRequestForTesting(const ResourceRequest&); + + static DocumentThreadableLoader* Create(ThreadableLoadingContext&, + ThreadableLoaderClient*, + const ThreadableLoaderOptions&, + const ResourceLoaderOptions&); + ~DocumentThreadableLoader() override; + + void Start(const ResourceRequest&) override; + + void OverrideTimeout(unsigned long timeout) override; + + void Cancel() override; + void Detach() override; + void SetDefersLoading(bool); + + void Trace(blink::Visitor*) override; + + private: + enum BlockingBehavior { kLoadSynchronously, kLoadAsynchronously }; + + static std::unique_ptr<ResourceRequest> CreateAccessControlPreflightRequest( + const ResourceRequest&, + const SecurityOrigin*); + + DocumentThreadableLoader(ThreadableLoadingContext&, + ThreadableLoaderClient*, + BlockingBehavior, + const ThreadableLoaderOptions&, + const ResourceLoaderOptions&); + + void Clear(); + + // ResourceClient + void NotifyFinished(Resource*) override; + + String DebugName() const override { return "DocumentThreadableLoader"; } + + // RawResourceClient + void DataSent(Resource*, + unsigned long long bytes_sent, + unsigned long long total_bytes_to_be_sent) override; + void ResponseReceived(Resource*, + const ResourceResponse&, + std::unique_ptr<WebDataConsumerHandle>) override; + void SetSerializedCachedMetadata(Resource*, const char*, size_t) override; + void DataReceived(Resource*, const char* data, size_t data_length) override; + bool RedirectReceived(Resource*, + const ResourceRequest&, + const ResourceResponse&) override; + void RedirectBlocked() override; + void DataDownloaded(Resource*, int) override; + void DidReceiveResourceTiming(Resource*, const ResourceTimingInfo&) override; + void DidDownloadToBlob(Resource*, scoped_refptr<BlobDataHandle>) override; + + // Notify Inspector and log to console about resource response. Use this + // method if response is not going to be finished normally. + void ReportResponseReceived(unsigned long identifier, + const ResourceResponse&); + + // Methods containing code to handle resource fetch results which are common + // to both sync and async mode. + // + // The FetchCredentialsMode argument must be the request's credentials mode. + // It's used for CORS check. + void HandleResponse(unsigned long identifier, + network::mojom::FetchRequestMode, + network::mojom::FetchCredentialsMode, + const ResourceResponse&, + std::unique_ptr<WebDataConsumerHandle>); + void HandleReceivedData(const char* data, size_t data_length); + void HandleSuccessfulFinish(unsigned long identifier, double finish_time); + + void DidTimeout(TimerBase*); + // Calls the appropriate loading method according to policy and data about + // origin. Only for handling the initial load (including fallback after + // consulting ServiceWorker). + void DispatchInitialRequest(ResourceRequest&); + void MakeCrossOriginAccessRequest(const ResourceRequest&); + + // Loads m_fallbackRequestForServiceWorker. + void LoadFallbackRequestForServiceWorker(); + // Issues a CORS preflight. + void LoadPreflightRequest(const ResourceRequest&, + const ResourceLoaderOptions&); + // Loads actual_request_. + void LoadActualRequest(); + // Clears actual_request_ and reports access control check failure to + // m_client. + void HandlePreflightFailure(const KURL&, const String& error_description); + // Investigates the response for the preflight request. If successful, + // the actual request will be made later in handleSuccessfulFinish(). + void HandlePreflightResponse(const ResourceResponse&); + + void DispatchDidFailAccessControlCheck(const ResourceError&); + void DispatchDidFail(const ResourceError&); + + void LoadRequestAsync(const ResourceRequest&, ResourceLoaderOptions); + void LoadRequestSync(const ResourceRequest&, ResourceLoaderOptions); + + void PrepareCrossOriginRequest(ResourceRequest&) const; + + // This method modifies the ResourceRequest by calling + // SetAllowStoredCredentials() on it based on same-origin-ness and the + // credentials mode. + // + // This method configures the ResourceLoaderOptions so that the underlying + // ResourceFetcher doesn't perform some part of the CORS logic since this + // class performs it by itself. + void LoadRequest(ResourceRequest&, ResourceLoaderOptions); + bool IsAllowedRedirect(network::mojom::FetchRequestMode, const KURL&) const; + + const SecurityOrigin* GetSecurityOrigin() const; + + // Returns null if the loader is not associated with Document. + // TODO(kinuko): Remove dependency to document. + Document* GetDocument() const; + + ExecutionContext* GetExecutionContext() const; + + ThreadableLoaderClient* client_; + Member<ThreadableLoadingContext> loading_context_; + + const ThreadableLoaderOptions options_; + // Some items may be overridden by m_forceDoNotAllowStoredCredentials and + // m_securityOrigin. In such a case, build a ResourceLoaderOptions with + // up-to-date values from them and this variable, and use it. + const ResourceLoaderOptions resource_loader_options_; + + // True when feature OutOfBlinkCORS is enabled (https://crbug.com/736308). + bool out_of_blink_cors_; + + // Corresponds to the CORS flag in the Fetch spec. + bool cors_flag_; + scoped_refptr<const SecurityOrigin> security_origin_; + + // Set to true when the response data is given to a data consumer handle. + bool is_using_data_consumer_handle_; + + const bool async_; + + // Holds the original request context (used for sanity checks). + WebURLRequest::RequestContext request_context_; + + // Saved so that we can use the original value for the modes in + // ResponseReceived() where |resource| might be a reused one (e.g. preloaded + // resource) which can have different modes. + network::mojom::FetchRequestMode fetch_request_mode_; + network::mojom::FetchCredentialsMode fetch_credentials_mode_; + + // Holds the original request for fallback in case the Service Worker + // does not respond. + ResourceRequest fallback_request_for_service_worker_; + + // Holds the original request and options for it during preflight request + // handling phase. + ResourceRequest actual_request_; + ResourceLoaderOptions actual_options_; + + // stores request headers in case of a cross-origin redirect. + HTTPHeaderMap request_headers_; + + TaskRunnerTimer<DocumentThreadableLoader> timeout_timer_; + double request_started_seconds_; // Time an asynchronous fetch request is + // started + + // Max number of times that this DocumentThreadableLoader can follow + // cross-origin redirects. This is used to limit the number of redirects. But + // this value is not the max number of total redirects allowed, because + // same-origin redirects are not counted here. + int cors_redirect_limit_; + + network::mojom::FetchRedirectMode redirect_mode_; + + // Holds the referrer after a redirect response was received. This referrer is + // used to populate the HTTP Referer header when following the redirect. + bool override_referrer_; + Referrer referrer_after_redirect_; + + RawResourceClientStateChecker checker_; +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_DOCUMENT_THREADABLE_LOADER_H_ diff --git a/chromium/third_party/blink/renderer/core/loader/document_threadable_loader_client.h b/chromium/third_party/blink/renderer/core/loader/document_threadable_loader_client.h new file mode 100644 index 00000000000..9554ac3dea4 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/document_threadable_loader_client.h @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2011 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. + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_DOCUMENT_THREADABLE_LOADER_CLIENT_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_DOCUMENT_THREADABLE_LOADER_CLIENT_H_ + +#include "base/macros.h" +#include "third_party/blink/renderer/core/loader/threadable_loader_client.h" + +namespace blink { + +class KURL; +class ResourceResponse; + +class DocumentThreadableLoaderClient : public ThreadableLoaderClient { + USING_FAST_MALLOC(DocumentThreadableLoaderClient); + + public: + bool IsDocumentThreadableLoaderClient() final { return true; } + + virtual bool WillFollowRedirect(const KURL& new_url, + const ResourceResponse&) { + return true; + } + + protected: + DocumentThreadableLoaderClient() = default; + + DISALLOW_COPY_AND_ASSIGN(DocumentThreadableLoaderClient); +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_DOCUMENT_THREADABLE_LOADER_CLIENT_H_ diff --git a/chromium/third_party/blink/renderer/core/loader/document_threadable_loader_test.cc b/chromium/third_party/blink/renderer/core/loader/document_threadable_loader_test.cc new file mode 100644 index 00000000000..b0371724551 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/document_threadable_loader_test.cc @@ -0,0 +1,92 @@ +// 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/document_threadable_loader.h" + +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/blink/public/platform/web_cors.h" +#include "third_party/blink/renderer/platform/exported/wrapped_resource_response.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource_response.h" + +namespace blink { + +namespace { + +TEST(DocumentThreadableLoaderCreatePreflightRequestTest, LexicographicalOrder) { + ResourceRequest request; + request.AddHTTPHeaderField("Orange", "Orange"); + request.AddHTTPHeaderField("Apple", "Red"); + request.AddHTTPHeaderField("Kiwifruit", "Green"); + request.AddHTTPHeaderField("Content-Type", "application/octet-stream"); + request.AddHTTPHeaderField("Strawberry", "Red"); + + std::unique_ptr<ResourceRequest> preflight = + DocumentThreadableLoader::CreateAccessControlPreflightRequestForTesting( + request); + + EXPECT_EQ("apple,content-type,kiwifruit,orange,strawberry", + preflight->HttpHeaderField("Access-Control-Request-Headers")); +} + +TEST(DocumentThreadableLoaderCreatePreflightRequestTest, ExcludeSimpleHeaders) { + ResourceRequest request; + request.AddHTTPHeaderField("Accept", "everything"); + request.AddHTTPHeaderField("Accept-Language", "everything"); + request.AddHTTPHeaderField("Content-Language", "everything"); + request.AddHTTPHeaderField("Save-Data", "on"); + + std::unique_ptr<ResourceRequest> preflight = + DocumentThreadableLoader::CreateAccessControlPreflightRequestForTesting( + request); + + // Do not emit empty-valued headers; an empty list of non-"CORS safelisted" + // request headers should cause "Access-Control-Request-Headers:" to be + // left out in the preflight request. + EXPECT_EQ(g_null_atom, + preflight->HttpHeaderField("Access-Control-Request-Headers")); +} + +TEST(DocumentThreadableLoaderCreatePreflightRequestTest, + ExcludeSimpleContentTypeHeader) { + ResourceRequest request; + request.AddHTTPHeaderField("Content-Type", "text/plain"); + + std::unique_ptr<ResourceRequest> preflight = + DocumentThreadableLoader::CreateAccessControlPreflightRequestForTesting( + request); + + // Empty list also; see comment in test above. + EXPECT_EQ(g_null_atom, + preflight->HttpHeaderField("Access-Control-Request-Headers")); +} + +TEST(DocumentThreadableLoaderCreatePreflightRequestTest, + IncludeNonSimpleHeader) { + ResourceRequest request; + request.AddHTTPHeaderField("X-Custom-Header", "foobar"); + + std::unique_ptr<ResourceRequest> preflight = + DocumentThreadableLoader::CreateAccessControlPreflightRequestForTesting( + request); + + EXPECT_EQ("x-custom-header", + preflight->HttpHeaderField("Access-Control-Request-Headers")); +} + +TEST(DocumentThreadableLoaderCreatePreflightRequestTest, + IncludeNonSimpleContentTypeHeader) { + ResourceRequest request; + request.AddHTTPHeaderField("Content-Type", "application/octet-stream"); + + std::unique_ptr<ResourceRequest> preflight = + DocumentThreadableLoader::CreateAccessControlPreflightRequestForTesting( + request); + + EXPECT_EQ("content-type", + preflight->HttpHeaderField("Access-Control-Request-Headers")); +} + +} // namespace + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/loader/empty_clients.cc b/chromium/third_party/blink/renderer/core/loader/empty_clients.cc new file mode 100644 index 00000000000..738a10c9749 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/empty_clients.cc @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2006 Eric Seidel <eric@webkit.org> + * Copyright (C) 2008, 2009, 2012 Apple Inc. All rights reserved. + * Copyright (C) Research In Motion Limited 2011. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "third_party/blink/renderer/core/loader/empty_clients.h" + +#include <memory> +#include "third_party/blink/public/platform/modules/serviceworker/web_service_worker_provider.h" +#include "third_party/blink/public/platform/modules/serviceworker/web_service_worker_provider_client.h" +#include "third_party/blink/public/platform/platform.h" +#include "third_party/blink/public/platform/web_application_cache_host.h" +#include "third_party/blink/public/platform/web_media_player.h" +#include "third_party/blink/renderer/core/frame/content_settings_client.h" +#include "third_party/blink/renderer/core/frame/local_frame.h" +#include "third_party/blink/renderer/core/frame/visual_viewport.h" +#include "third_party/blink/renderer/core/html/forms/color_chooser.h" +#include "third_party/blink/renderer/core/html/forms/date_time_chooser.h" +#include "third_party/blink/renderer/core/html/forms/file_chooser.h" +#include "third_party/blink/renderer/core/html/forms/html_form_element.h" +#include "third_party/blink/renderer/core/loader/document_loader.h" +#include "third_party/blink/renderer/platform/scheduler/child/worker_scheduler_proxy.h" + +namespace blink { + +void FillWithEmptyClients(Page::PageClients& page_clients) { + DEFINE_STATIC_LOCAL(ChromeClient, dummy_chrome_client, + (EmptyChromeClient::Create())); + page_clients.chrome_client = &dummy_chrome_client; +} + +class EmptyPopupMenu : public PopupMenu { + public: + void Show() override {} + void Hide() override {} + void UpdateFromElement(UpdateReason) override {} + void DisconnectClient() override {} +}; + +PopupMenu* EmptyChromeClient::OpenPopupMenu(LocalFrame&, HTMLSelectElement&) { + return new EmptyPopupMenu(); +} + +ColorChooser* EmptyChromeClient::OpenColorChooser(LocalFrame*, + ColorChooserClient*, + const Color&) { + return nullptr; +} + +DateTimeChooser* EmptyChromeClient::OpenDateTimeChooser( + DateTimeChooserClient*, + const DateTimeChooserParameters&) { + return nullptr; +} + +void EmptyChromeClient::OpenTextDataListChooser(HTMLInputElement&) {} + +void EmptyChromeClient::OpenFileChooser(LocalFrame*, + scoped_refptr<FileChooser>) {} + +void EmptyChromeClient::AttachRootGraphicsLayer(GraphicsLayer* layer, + LocalFrame* local_root) { + Page* page = local_root ? local_root->GetPage() : nullptr; + if (!page) + return; + page->GetVisualViewport().AttachLayerTree(layer); +} + +String EmptyChromeClient::AcceptLanguages() { + return String(); +} + +NavigationPolicy EmptyLocalFrameClient::DecidePolicyForNavigation( + const ResourceRequest&, + Document* origin_document, + DocumentLoader*, + NavigationType, + NavigationPolicy, + bool, + bool, + WebTriggeringEventInfo, + HTMLFormElement*, + ContentSecurityPolicyDisposition, + mojom::blink::BlobURLTokenPtr) { + return kNavigationPolicyIgnore; +} + +void EmptyLocalFrameClient::DispatchWillSendSubmitEvent(HTMLFormElement*) {} + +void EmptyLocalFrameClient::DispatchWillSubmitForm(HTMLFormElement*) {} + +DocumentLoader* EmptyLocalFrameClient::CreateDocumentLoader( + LocalFrame* frame, + const ResourceRequest& request, + const SubstituteData& substitute_data, + ClientRedirectPolicy client_redirect_policy, + const base::UnguessableToken& devtools_navigation_token) { + DCHECK(frame); + + return DocumentLoader::Create(frame, request, substitute_data, + client_redirect_policy, + devtools_navigation_token); +} + +LocalFrame* EmptyLocalFrameClient::CreateFrame(const AtomicString&, + HTMLFrameOwnerElement*) { + return nullptr; +} + +WebPluginContainerImpl* EmptyLocalFrameClient::CreatePlugin( + HTMLPlugInElement&, + const KURL&, + const Vector<String>&, + const Vector<String>&, + const String&, + bool, + DetachedPluginPolicy) { + return nullptr; +} + +std::unique_ptr<WebMediaPlayer> EmptyLocalFrameClient::CreateWebMediaPlayer( + HTMLMediaElement&, + const WebMediaPlayerSource&, + WebMediaPlayerClient*, + WebLayerTreeView*) { + return nullptr; +} + +WebRemotePlaybackClient* EmptyLocalFrameClient::CreateWebRemotePlaybackClient( + HTMLMediaElement&) { + return nullptr; +} + +WebTextCheckClient* EmptyLocalFrameClient::GetTextCheckerClient() const { + return text_check_client_; +} + +void EmptyLocalFrameClient::SetTextCheckerClientForTesting( + WebTextCheckClient* client) { + text_check_client_ = client; +} + +Frame* EmptyLocalFrameClient::FindFrame(const AtomicString& name) const { + return nullptr; +} + +std::unique_ptr<WebServiceWorkerProvider> +EmptyLocalFrameClient::CreateServiceWorkerProvider() { + return nullptr; +} + +ContentSettingsClient& EmptyLocalFrameClient::GetContentSettingsClient() { + return content_settings_client_; +} + +std::unique_ptr<WebApplicationCacheHost> +EmptyLocalFrameClient::CreateApplicationCacheHost( + WebApplicationCacheHostClient*) { + return nullptr; +} + +EmptyRemoteFrameClient::EmptyRemoteFrameClient() = default; + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/loader/empty_clients.h b/chromium/third_party/blink/renderer/core/loader/empty_clients.h new file mode 100644 index 00000000000..779ae421294 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/empty_clients.h @@ -0,0 +1,453 @@ +/* + * Copyright (C) 2006 Eric Seidel (eric@webkit.org) + * Copyright (C) 2008, 2009, 2010, 2011, 2012 Apple Inc. All rights reserved. + * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). + * Copyright (C) 2012 Samsung Electronics. 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_EMPTY_CLIENTS_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_EMPTY_CLIENTS_H_ + +#include <memory> + +#include "base/macros.h" +#include "services/service_manager/public/cpp/interface_provider.h" +#include "third_party/blink/public/platform/platform.h" +#include "third_party/blink/public/platform/web_canvas.h" +#include "third_party/blink/public/platform/web_focus_type.h" +#include "third_party/blink/public/platform/web_menu_source_type.h" +#include "third_party/blink/public/platform/web_screen_info.h" +#include "third_party/blink/public/platform/web_spell_check_panel_host_client.h" +#include "third_party/blink/public/platform/web_url_loader.h" +#include "third_party/blink/renderer/core/core_export.h" +#include "third_party/blink/renderer/core/frame/content_settings_client.h" +#include "third_party/blink/renderer/core/frame/local_frame_client.h" +#include "third_party/blink/renderer/core/frame/remote_frame_client.h" +#include "third_party/blink/renderer/core/page/chrome_client.h" +#include "third_party/blink/renderer/core/page/page.h" +#include "third_party/blink/renderer/platform/drag_image.h" +#include "third_party/blink/renderer/platform/exported/wrapped_resource_request.h" +#include "third_party/blink/renderer/platform/geometry/float_point.h" +#include "third_party/blink/renderer/platform/geometry/float_rect.h" +#include "third_party/blink/renderer/platform/geometry/int_rect.h" +#include "third_party/blink/renderer/platform/graphics/touch_action.h" +#include "third_party/blink/renderer/platform/heap/handle.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource_error.h" +#include "third_party/blink/renderer/platform/wtf/forward.h" +#include "v8/include/v8.h" + +/* + This file holds empty Client stubs for use by WebCore. + + Viewless element needs to create a dummy Page->LocalFrame->FrameView tree for + use in parsing or executing JavaScript. This tree depends heavily on Clients + (usually provided by WebKit classes). + + This file was first created for SVGImage as it had no way to access the current + Page (nor should it, since Images are not tied to a page). See + http://bugs.webkit.org/show_bug.cgi?id=5971 for the original discussion about + this file. + + Ideally, whenever you change a Client class, you should add a stub here. + Brittle, yes. Unfortunate, yes. Hopefully temporary. +*/ + +namespace blink { + +class CORE_EXPORT EmptyChromeClient : public ChromeClient { + public: + static EmptyChromeClient* Create() { return new EmptyChromeClient; } + + ~EmptyChromeClient() override = default; + void ChromeDestroyed() override {} + + WebViewImpl* GetWebView() const override { return nullptr; } + void SetWindowRect(const IntRect&, LocalFrame&) override {} + IntRect RootWindowRect() override { return IntRect(); } + + IntRect PageRect() override { return IntRect(); } + + void Focus(LocalFrame*) override {} + + bool CanTakeFocus(WebFocusType) override { return false; } + void TakeFocus(WebFocusType) override {} + + void FocusedNodeChanged(Node*, Node*) override {} + Page* CreateWindow(LocalFrame*, + const FrameLoadRequest&, + const WebWindowFeatures&, + NavigationPolicy, + SandboxFlags) override { + return nullptr; + } + void Show(NavigationPolicy) override {} + + void DidOverscroll(const FloatSize&, + const FloatSize&, + const FloatPoint&, + const FloatSize&, + const WebOverscrollBehavior&) override {} + + void BeginLifecycleUpdates() override {} + + bool HadFormInteraction() const override { return false; } + + void StartDragging(LocalFrame*, + const WebDragData&, + WebDragOperationsMask, + const WebImage& drag_image, + const WebPoint& drag_image_offset) override {} + bool AcceptsLoadDrops() const override { return true; } + + bool ShouldReportDetailedMessageForSource(LocalFrame&, + const String&) override { + return false; + } + void AddMessageToConsole(LocalFrame*, + MessageSource, + MessageLevel, + const String&, + unsigned, + const String&, + const String&) override {} + + bool CanOpenBeforeUnloadConfirmPanel() override { return false; } + bool OpenBeforeUnloadConfirmPanelDelegate(LocalFrame*, bool) override { + return true; + } + + void CloseWindowSoon() override {} + + bool OpenJavaScriptAlertDelegate(LocalFrame*, const String&) override { + return false; + } + bool OpenJavaScriptConfirmDelegate(LocalFrame*, const String&) override { + return false; + } + bool OpenJavaScriptPromptDelegate(LocalFrame*, + const String&, + const String&, + String&) override { + return false; + } + + bool HasOpenedPopup() const override { return false; } + PopupMenu* OpenPopupMenu(LocalFrame&, HTMLSelectElement&) override; + PagePopup* OpenPagePopup(PagePopupClient*) override { return nullptr; } + void ClosePagePopup(PagePopup*) override {} + DOMWindow* PagePopupWindowForTesting() const override { return nullptr; } + + bool TabsToLinks() override { return false; } + + void InvalidateRect(const IntRect&) override {} + void ScheduleAnimation(const PlatformFrameView*) override {} + + IntRect ViewportToScreen(const IntRect& r, + const PlatformFrameView*) const override { + return r; + } + float WindowToViewportScalar(const float s) const override { return s; } + WebScreenInfo GetScreenInfo() const override { return WebScreenInfo(); } + void ContentsSizeChanged(LocalFrame*, const IntSize&) const override {} + + void ShowMouseOverURL(const HitTestResult&) override {} + + void SetToolTip(LocalFrame&, const String&, TextDirection) override {} + + void PrintDelegate(LocalFrame*) override {} + + void EnumerateChosenDirectory(FileChooser*) override {} + + ColorChooser* OpenColorChooser(LocalFrame*, + ColorChooserClient*, + const Color&) override; + DateTimeChooser* OpenDateTimeChooser( + DateTimeChooserClient*, + const DateTimeChooserParameters&) override; + void OpenTextDataListChooser(HTMLInputElement&) override; + + void OpenFileChooser(LocalFrame*, scoped_refptr<FileChooser>) override; + + void SetCursor(const Cursor&, LocalFrame* local_root) override {} + void SetCursorOverridden(bool) override {} + Cursor LastSetCursorForTesting() const override { return PointerCursor(); } + + void AttachRootGraphicsLayer(GraphicsLayer*, LocalFrame* local_root) override; + void AttachRootLayer(WebLayer*, LocalFrame* local_root) override {} + + void SetEventListenerProperties(LocalFrame*, + WebEventListenerClass, + WebEventListenerProperties) override {} + WebEventListenerProperties EventListenerProperties( + LocalFrame*, + WebEventListenerClass event_class) const override { + return WebEventListenerProperties::kNothing; + } + void SetHasScrollEventHandlers(LocalFrame*, bool) override {} + void SetNeedsLowLatencyInput(LocalFrame*, bool) override {} + void RequestUnbufferedInputEvents(LocalFrame*) override {} + void SetTouchAction(LocalFrame*, TouchAction) override {} + + void DidAssociateFormControlsAfterLoad(LocalFrame*) override {} + + String AcceptLanguages() override; + + void RegisterPopupOpeningObserver(PopupOpeningObserver*) override {} + void UnregisterPopupOpeningObserver(PopupOpeningObserver*) override {} + void NotifyPopupOpeningObservers() const override {} + + void SetCursorForPlugin(const WebCursorInfo&, LocalFrame*) override {} + + void InstallSupplements(LocalFrame&) override {} +}; + +class CORE_EXPORT EmptyLocalFrameClient : public LocalFrameClient { + public: + static EmptyLocalFrameClient* Create() { return new EmptyLocalFrameClient; } + ~EmptyLocalFrameClient() override = default; + + bool HasWebView() const override { return true; } // mainly for assertions + + bool InShadowTree() const override { return false; } + + Frame* Opener() const override { return nullptr; } + void SetOpener(Frame*) override {} + + Frame* Parent() const override { return nullptr; } + Frame* Top() const override { return nullptr; } + Frame* NextSibling() const override { return nullptr; } + Frame* FirstChild() const override { return nullptr; } + void WillBeDetached() override {} + void Detached(FrameDetachType) override {} + void FrameFocused() const override {} + + void DispatchWillSendRequest(ResourceRequest&) override {} + void DispatchDidReceiveResponse(const ResourceResponse&) override {} + void DispatchDidLoadResourceFromMemoryCache( + const ResourceRequest&, + const ResourceResponse&) override {} + + void DispatchDidHandleOnloadEvents() override {} + void DispatchDidReceiveServerRedirectForProvisionalLoad() override {} + void DispatchWillCommitProvisionalLoad() override {} + void DispatchDidStartProvisionalLoad(DocumentLoader*, + ResourceRequest&) override {} + void DispatchDidReceiveTitle(const String&) override {} + void DispatchDidChangeIcons(IconType) override {} + void DispatchDidCommitLoad(HistoryItem*, + HistoryCommitType, + WebGlobalObjectReusePolicy) override {} + void DispatchDidFailProvisionalLoad(const ResourceError&, + HistoryCommitType) override {} + void DispatchDidFailLoad(const ResourceError&, HistoryCommitType) override {} + void DispatchDidFinishDocumentLoad() override {} + void DispatchDidFinishLoad() override {} + void DispatchDidChangeThemeColor() override {} + + NavigationPolicy DecidePolicyForNavigation( + const ResourceRequest&, + Document* origin_document, + DocumentLoader*, + NavigationType, + NavigationPolicy, + bool, + bool, + WebTriggeringEventInfo, + HTMLFormElement*, + ContentSecurityPolicyDisposition, + mojom::blink::BlobURLTokenPtr) override; + + void DispatchWillSendSubmitEvent(HTMLFormElement*) override; + void DispatchWillSubmitForm(HTMLFormElement*) override; + + void DidStartLoading(LoadStartType) override {} + void ProgressEstimateChanged(double) override {} + void DidStopLoading() override {} + + void ForwardResourceTimingToParent(const WebResourceTimingInfo&) override {} + + void DownloadURL(const ResourceRequest&) override {} + void LoadErrorPage(int reason) override {} + + DocumentLoader* CreateDocumentLoader( + LocalFrame*, + const ResourceRequest&, + const SubstituteData&, + ClientRedirectPolicy, + const base::UnguessableToken& devtools_navigation_token) override; + + String UserAgent() override { return ""; } + + String DoNotTrackValue() override { return String(); } + + void TransitionToCommittedForNewPage() override {} + + bool NavigateBackForward(int offset) const override { return false; } + void DidDisplayInsecureContent() override {} + void DidContainInsecureFormAction() override {} + void DidRunInsecureContent(const SecurityOrigin*, const KURL&) override {} + void DidDetectXSS(const KURL&, bool) override {} + void DidDispatchPingLoader(const KURL&) override {} + void DidDisplayContentWithCertificateErrors() override {} + void DidRunContentWithCertificateErrors() override {} + void SelectorMatchChanged(const Vector<String>&, + const Vector<String>&) override {} + LocalFrame* CreateFrame(const AtomicString&, HTMLFrameOwnerElement*) override; + WebPluginContainerImpl* CreatePlugin(HTMLPlugInElement&, + const KURL&, + const Vector<String>&, + const Vector<String>&, + const String&, + bool, + DetachedPluginPolicy) override; + bool CanCreatePluginWithoutRenderer(const String& mime_type) const override { + return false; + } + std::unique_ptr<WebMediaPlayer> CreateWebMediaPlayer( + HTMLMediaElement&, + const WebMediaPlayerSource&, + WebMediaPlayerClient*, + WebLayerTreeView*) override; + WebRemotePlaybackClient* CreateWebRemotePlaybackClient( + HTMLMediaElement&) override; + + void DidCreateNewDocument() override {} + void DispatchDidClearWindowObjectInMainWorld() override {} + void DocumentElementAvailable() override {} + void RunScriptsAtDocumentElementAvailable() override {} + void RunScriptsAtDocumentReady(bool) override {} + void RunScriptsAtDocumentIdle() override {} + + void DidCreateScriptContext(v8::Local<v8::Context>, int world_id) override {} + void WillReleaseScriptContext(v8::Local<v8::Context>, int world_id) override { + } + bool AllowScriptExtensions() override { return false; } + + WebCookieJar* CookieJar() const override { return nullptr; } + + service_manager::InterfaceProvider* GetInterfaceProvider() override { + return &interface_provider_; + } + + WebSpellCheckPanelHostClient* SpellCheckPanelHostClient() const override { + return nullptr; + } + + std::unique_ptr<WebServiceWorkerProvider> CreateServiceWorkerProvider() + override; + ContentSettingsClient& GetContentSettingsClient() override; + std::unique_ptr<WebApplicationCacheHost> CreateApplicationCacheHost( + WebApplicationCacheHostClient*) override; + + void SetTextCheckerClientForTesting(WebTextCheckClient*); + WebTextCheckClient* GetTextCheckerClient() const override; + + std::unique_ptr<WebURLLoaderFactory> CreateURLLoaderFactory() override { + return Platform::Current()->CreateDefaultURLLoaderFactory(); + } + + void AnnotatedRegionsChanged() override {} + base::UnguessableToken GetDevToolsFrameToken() const override { + return base::UnguessableToken::Create(); + }; + String evaluateInInspectorOverlayForTesting(const String& script) override { + return g_empty_string; + } + + Frame* FindFrame(const AtomicString& name) const override; + + protected: + EmptyLocalFrameClient() = default; + + // Not owned + WebTextCheckClient* text_check_client_; + + ContentSettingsClient content_settings_client_; + service_manager::InterfaceProvider interface_provider_; + + DISALLOW_COPY_AND_ASSIGN(EmptyLocalFrameClient); +}; + +class EmptySpellCheckPanelHostClient : public WebSpellCheckPanelHostClient { + USING_FAST_MALLOC(EmptySpellCheckPanelHostClient); + + public: + EmptySpellCheckPanelHostClient() = default; + + void ShowSpellingUI(bool) override {} + bool IsShowingSpellingUI() override { return false; } + void UpdateSpellingUIWithMisspelledWord(const WebString&) override {} + + DISALLOW_COPY_AND_ASSIGN(EmptySpellCheckPanelHostClient); +}; + +class CORE_EXPORT EmptyRemoteFrameClient : public RemoteFrameClient { + public: + EmptyRemoteFrameClient(); + + // RemoteFrameClient implementation. + void Navigate(const ResourceRequest&, + bool should_replace_current_entry) override {} + void Reload(FrameLoadType, ClientRedirectPolicy) override {} + unsigned BackForwardLength() override { return 0; } + void CheckCompleted() override {} + void ForwardPostMessage(MessageEvent*, + scoped_refptr<const SecurityOrigin> target, + LocalFrame* source_frame, + bool has_user_gesture) const override {} + void FrameRectsChanged(const IntRect& local_frame_rect, + const IntRect& transformed_frame_rect) override {} + void UpdateRemoteViewportIntersection( + const IntRect& viewport_intersection) override {} + void AdvanceFocus(WebFocusType, LocalFrame* source) override {} + void VisibilityChanged(bool visible) override {} + void SetIsInert(bool) override {} + void UpdateRenderThrottlingStatus(bool is_throttled, + bool subtree_throttled) override {} + uint32_t Print(const IntRect& rect, WebCanvas* canvas) const override { + return 0; + } + + // FrameClient implementation. + bool InShadowTree() const override { return false; } + void Detached(FrameDetachType) override {} + Frame* Opener() const override { return nullptr; } + void SetOpener(Frame*) override {} + Frame* Parent() const override { return nullptr; } + Frame* Top() const override { return nullptr; } + Frame* NextSibling() const override { return nullptr; } + Frame* FirstChild() const override { return nullptr; } + void FrameFocused() const override {} + base::UnguessableToken GetDevToolsFrameToken() const override { + return base::UnguessableToken::Create(); + }; + + DISALLOW_COPY_AND_ASSIGN(EmptyRemoteFrameClient); +}; + +CORE_EXPORT void FillWithEmptyClients(Page::PageClients&); + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_EMPTY_CLIENTS_H_ diff --git a/chromium/third_party/blink/renderer/core/loader/form_submission.cc b/chromium/third_party/blink/renderer/core/loader/form_submission.cc new file mode 100644 index 00000000000..172589e342b --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/form_submission.cc @@ -0,0 +1,298 @@ +/* + * Copyright (C) 2010 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/form_submission.h" + +#include "third_party/blink/public/platform/web_insecure_request_policy.h" +#include "third_party/blink/renderer/core/dom/document.h" +#include "third_party/blink/renderer/core/dom/events/event.h" +#include "third_party/blink/renderer/core/frame/use_counter.h" +#include "third_party/blink/renderer/core/html/forms/form_data.h" +#include "third_party/blink/renderer/core/html/forms/html_form_control_element.h" +#include "third_party/blink/renderer/core/html/forms/html_form_element.h" +#include "third_party/blink/renderer/core/html/parser/html_parser_idioms.h" +#include "third_party/blink/renderer/core/html_names.h" +#include "third_party/blink/renderer/core/loader/frame_load_request.h" +#include "third_party/blink/renderer/core/loader/frame_loader.h" +#include "third_party/blink/renderer/platform/heap/handle.h" +#include "third_party/blink/renderer/platform/network/encoded_form_data.h" +#include "third_party/blink/renderer/platform/network/form_data_encoder.h" +#include "third_party/blink/renderer/platform/network/http_names.h" +#include "third_party/blink/renderer/platform/wtf/text/string_builder.h" +#include "third_party/blink/renderer/platform/wtf/text/text_encoding.h" +#include "third_party/blink/renderer/platform/wtf/time.h" + +namespace blink { + +using namespace HTMLNames; + +static int64_t GenerateFormDataIdentifier() { + // Initialize to the current time to reduce the likelihood of generating + // identifiers that overlap with those from past/future browser sessions. + static int64_t next_identifier = + static_cast<int64_t>(CurrentTime() * 1000000.0); + return ++next_identifier; +} + +static void AppendMailtoPostFormDataToURL(KURL& url, + const EncodedFormData& data, + const String& encoding_type) { + String body = data.FlattenToString(); + + if (DeprecatedEqualIgnoringCase(encoding_type, "text/plain")) { + // Convention seems to be to decode, and s/&/\r\n/. Also, spaces are encoded + // as %20. + body = DecodeURLEscapeSequences( + body.Replace('&', "\r\n").Replace('+', ' ') + "\r\n"); + } + + Vector<char> body_data; + body_data.Append("body=", 5); + FormDataEncoder::EncodeStringAsFormData(body_data, body.Utf8(), + FormDataEncoder::kNormalizeCRLF); + body = String(body_data.data(), body_data.size()).Replace('+', "%20"); + + StringBuilder query; + query.Append(url.Query()); + if (!query.IsEmpty()) + query.Append('&'); + query.Append(body); + url.SetQuery(query.ToString()); +} + +void FormSubmission::Attributes::ParseAction(const String& action) { + // m_action cannot be converted to KURL (bug https://crbug.com/388664) + action_ = StripLeadingAndTrailingHTMLSpaces(action); +} + +AtomicString FormSubmission::Attributes::ParseEncodingType(const String& type) { + if (DeprecatedEqualIgnoringCase(type, "multipart/form-data")) + return AtomicString("multipart/form-data"); + if (DeprecatedEqualIgnoringCase(type, "text/plain")) + return AtomicString("text/plain"); + return AtomicString("application/x-www-form-urlencoded"); +} + +void FormSubmission::Attributes::UpdateEncodingType(const String& type) { + encoding_type_ = ParseEncodingType(type); + is_multi_part_form_ = (encoding_type_ == "multipart/form-data"); +} + +FormSubmission::SubmitMethod FormSubmission::Attributes::ParseMethodType( + const String& type) { + if (DeprecatedEqualIgnoringCase(type, "post")) + return FormSubmission::kPostMethod; + if (DeprecatedEqualIgnoringCase(type, "dialog")) + return FormSubmission::kDialogMethod; + return FormSubmission::kGetMethod; +} + +void FormSubmission::Attributes::UpdateMethodType(const String& type) { + method_ = ParseMethodType(type); +} + +String FormSubmission::Attributes::MethodString(SubmitMethod method) { + switch (method) { + case kGetMethod: + return "get"; + case kPostMethod: + return "post"; + case kDialogMethod: + return "dialog"; + } + NOTREACHED(); + return g_empty_string; +} + +void FormSubmission::Attributes::CopyFrom(const Attributes& other) { + method_ = other.method_; + is_multi_part_form_ = other.is_multi_part_form_; + + action_ = other.action_; + target_ = other.target_; + encoding_type_ = other.encoding_type_; + accept_charset_ = other.accept_charset_; +} + +inline FormSubmission::FormSubmission(SubmitMethod method, + const KURL& action, + const AtomicString& target, + const AtomicString& content_type, + HTMLFormElement* form, + scoped_refptr<EncodedFormData> data, + const String& boundary, + Event* event) + : method_(method), + action_(action), + target_(target), + content_type_(content_type), + form_(form), + form_data_(std::move(data)), + boundary_(boundary), + event_(event) {} + +inline FormSubmission::FormSubmission(const String& result) + : method_(kDialogMethod), result_(result) {} + +FormSubmission* FormSubmission::Create(HTMLFormElement* form, + const Attributes& attributes, + Event* event, + HTMLFormControlElement* submit_button) { + DCHECK(form); + + FormSubmission::Attributes copied_attributes; + copied_attributes.CopyFrom(attributes); + if (submit_button) { + AtomicString attribute_value; + if (!(attribute_value = submit_button->FastGetAttribute(formactionAttr)) + .IsNull()) + copied_attributes.ParseAction(attribute_value); + if (!(attribute_value = submit_button->FastGetAttribute(formenctypeAttr)) + .IsNull()) + copied_attributes.UpdateEncodingType(attribute_value); + if (!(attribute_value = submit_button->FastGetAttribute(formmethodAttr)) + .IsNull()) + copied_attributes.UpdateMethodType(attribute_value); + if (!(attribute_value = submit_button->FastGetAttribute(formtargetAttr)) + .IsNull()) + copied_attributes.SetTarget(attribute_value); + } + + if (copied_attributes.Method() == kDialogMethod) { + if (submit_button) + return new FormSubmission(submit_button->ResultForDialogSubmit()); + return new FormSubmission(""); + } + + Document& document = form->GetDocument(); + KURL action_url = document.CompleteURL(copied_attributes.Action().IsEmpty() + ? document.Url().GetString() + : copied_attributes.Action()); + + if (document.GetInsecureRequestPolicy() & kUpgradeInsecureRequests && + action_url.ProtocolIs("http")) { + UseCounter::Count(document, + WebFeature::kUpgradeInsecureRequestsUpgradedRequest); + action_url.SetProtocol("https"); + if (action_url.Port() == 80) + action_url.SetPort(443); + } + + bool is_mailto_form = action_url.ProtocolIs("mailto"); + bool is_multi_part_form = false; + AtomicString encoding_type = copied_attributes.EncodingType(); + + if (copied_attributes.Method() == kPostMethod) { + is_multi_part_form = copied_attributes.IsMultiPartForm(); + if (is_multi_part_form && is_mailto_form) { + encoding_type = AtomicString("application/x-www-form-urlencoded"); + is_multi_part_form = false; + } + } + WTF::TextEncoding data_encoding = + is_mailto_form + ? UTF8Encoding() + : FormDataEncoder::EncodingFromAcceptCharset( + copied_attributes.AcceptCharset(), document.Encoding()); + FormData* dom_form_data = + FormData::Create(data_encoding.EncodingForFormSubmission()); + form->ConstructFormDataSet(submit_button, *dom_form_data); + + scoped_refptr<EncodedFormData> form_data; + String boundary; + + if (is_multi_part_form) { + form_data = dom_form_data->EncodeMultiPartFormData(); + boundary = form_data->Boundary().data(); + } else { + form_data = dom_form_data->EncodeFormData( + attributes.Method() == kGetMethod + ? EncodedFormData::kFormURLEncoded + : EncodedFormData::ParseEncodingType(encoding_type)); + if (copied_attributes.Method() == kPostMethod && is_mailto_form) { + // Convert the form data into a string that we put into the URL. + AppendMailtoPostFormDataToURL(action_url, *form_data, encoding_type); + form_data = EncodedFormData::Create(); + } + } + + form_data->SetIdentifier(GenerateFormDataIdentifier()); + form_data->SetContainsPasswordData(dom_form_data->ContainsPasswordData()); + AtomicString target_or_base_target = copied_attributes.Target().IsEmpty() + ? document.BaseTarget() + : copied_attributes.Target(); + return new FormSubmission(copied_attributes.Method(), action_url, + target_or_base_target, encoding_type, form, + std::move(form_data), boundary, event); +} + +void FormSubmission::Trace(blink::Visitor* visitor) { + visitor->Trace(form_); + visitor->Trace(event_); +} + +KURL FormSubmission::RequestURL() const { + if (method_ == FormSubmission::kPostMethod) + return action_; + + KURL request_url(action_); + request_url.SetQuery(form_data_->FlattenToString()); + return request_url; +} + +FrameLoadRequest FormSubmission::CreateFrameLoadRequest( + Document* origin_document) { + FrameLoadRequest frame_request(origin_document); + + if (!target_.IsEmpty()) + frame_request.SetFrameName(target_); + + if (method_ == FormSubmission::kPostMethod) { + frame_request.GetResourceRequest().SetHTTPMethod(HTTPNames::POST); + frame_request.GetResourceRequest().SetHTTPBody(form_data_); + + // construct some user headers if necessary + if (boundary_.IsEmpty()) { + frame_request.GetResourceRequest().SetHTTPContentType(content_type_); + } else { + frame_request.GetResourceRequest().SetHTTPContentType( + content_type_ + "; boundary=" + boundary_); + } + } + + frame_request.GetResourceRequest().SetURL(RequestURL()); + + frame_request.SetTriggeringEvent(event_); + frame_request.SetForm(form_); + + return frame_request; +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/loader/form_submission.h b/chromium/third_party/blink/renderer/core/loader/form_submission.h new file mode 100644 index 00000000000..4c2f4ff68f8 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/form_submission.h @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2010 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. + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_FORM_SUBMISSION_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_FORM_SUBMISSION_H_ + +#include "base/macros.h" +#include "third_party/blink/renderer/core/loader/frame_load_request.h" +#include "third_party/blink/renderer/platform/heap/handle.h" +#include "third_party/blink/renderer/platform/weborigin/kurl.h" +#include "third_party/blink/renderer/platform/weborigin/referrer.h" +#include "third_party/blink/renderer/platform/wtf/allocator.h" + +namespace blink { + +class Document; +class EncodedFormData; +class Event; +class HTMLFormControlElement; +class HTMLFormElement; + +class FormSubmission : public GarbageCollectedFinalized<FormSubmission> { + public: + enum SubmitMethod { kGetMethod, kPostMethod, kDialogMethod }; + + class Attributes { + DISALLOW_NEW(); + + public: + Attributes() + : method_(kGetMethod), + is_multi_part_form_(false), + encoding_type_("application/x-www-form-urlencoded") {} + + SubmitMethod Method() const { return method_; } + static SubmitMethod ParseMethodType(const String&); + void UpdateMethodType(const String&); + static String MethodString(SubmitMethod); + + const String& Action() const { return action_; } + void ParseAction(const String&); + + const AtomicString& Target() const { return target_; } + void SetTarget(const AtomicString& target) { target_ = target; } + + const AtomicString& EncodingType() const { return encoding_type_; } + static AtomicString ParseEncodingType(const String&); + void UpdateEncodingType(const String&); + bool IsMultiPartForm() const { return is_multi_part_form_; } + + const String& AcceptCharset() const { return accept_charset_; } + void SetAcceptCharset(const String& value) { accept_charset_ = value; } + + void CopyFrom(const Attributes&); + + private: + SubmitMethod method_; + bool is_multi_part_form_; + + String action_; + AtomicString target_; + AtomicString encoding_type_; + String accept_charset_; + + DISALLOW_COPY_AND_ASSIGN(Attributes); + }; + + static FormSubmission* Create(HTMLFormElement*, + const Attributes&, + Event*, + HTMLFormControlElement* submit_button); + void Trace(blink::Visitor*); + + FrameLoadRequest CreateFrameLoadRequest(Document* origin_document); + + KURL RequestURL() const; + + SubmitMethod Method() const { return method_; } + const KURL& Action() const { return action_; } + const AtomicString& Target() const { return target_; } + void ClearTarget() { target_ = g_null_atom; } + HTMLFormElement* Form() const { return form_.Get(); } + EncodedFormData* Data() const { return form_data_.get(); } + + const String& Result() const { return result_; } + + private: + FormSubmission(SubmitMethod, + const KURL& action, + const AtomicString& target, + const AtomicString& content_type, + HTMLFormElement*, + scoped_refptr<EncodedFormData>, + const String& boundary, + Event*); + // FormSubmission for DialogMethod + explicit FormSubmission(const String& result); + + // FIXME: Hold an instance of Attributes instead of individual members. + SubmitMethod method_; + KURL action_; + AtomicString target_; + AtomicString content_type_; + Member<HTMLFormElement> form_; + scoped_refptr<EncodedFormData> form_data_; + String boundary_; + Member<Event> event_; + String result_; +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_FORM_SUBMISSION_H_ diff --git a/chromium/third_party/blink/renderer/core/loader/frame_fetch_context.cc b/chromium/third_party/blink/renderer/core/loader/frame_fetch_context.cc new file mode 100644 index 00000000000..b9f29902c57 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/frame_fetch_context.cc @@ -0,0 +1,1429 @@ +/* + * 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/frame_fetch_context.h" + +#include <algorithm> +#include <memory> +#include "services/network/public/mojom/request_context_frame_type.mojom-blink.h" +#include "third_party/blink/public/common/client_hints/client_hints.h" +#include "third_party/blink/public/common/device_memory/approximated_device_memory.h" +#include "third_party/blink/public/platform/modules/fetch/fetch_api_request.mojom-shared.h" +#include "third_party/blink/public/platform/modules/serviceworker/web_service_worker_network_provider.h" +#include "third_party/blink/public/platform/platform.h" +#include "third_party/blink/public/platform/web_application_cache_host.h" +#include "third_party/blink/public/platform/web_effective_connection_type.h" +#include "third_party/blink/public/platform/web_insecure_request_policy.h" +#include "third_party/blink/renderer/bindings/core/v8/script_controller.h" +#include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h" +#include "third_party/blink/renderer/core/dom/document.h" +#include "third_party/blink/renderer/core/fileapi/public_url_manager.h" +#include "third_party/blink/renderer/core/frame/content_settings_client.h" +#include "third_party/blink/renderer/core/frame/deprecation.h" +#include "third_party/blink/renderer/core/frame/frame_console.h" +#include "third_party/blink/renderer/core/frame/local_dom_window.h" +#include "third_party/blink/renderer/core/frame/local_frame.h" +#include "third_party/blink/renderer/core/frame/local_frame_client.h" +#include "third_party/blink/renderer/core/frame/local_frame_view.h" +#include "third_party/blink/renderer/core/frame/settings.h" +#include "third_party/blink/renderer/core/html/html_frame_owner_element.h" +#include "third_party/blink/renderer/core/html/imports/html_imports_controller.h" +#include "third_party/blink/renderer/core/inspector/InspectorTraceEvents.h" +#include "third_party/blink/renderer/core/inspector/console_message.h" +#include "third_party/blink/renderer/core/inspector/identifiers_factory.h" +#include "third_party/blink/renderer/core/leak_detector/blink_leak_detector.h" +#include "third_party/blink/renderer/core/loader/appcache/application_cache_host.h" +#include "third_party/blink/renderer/core/loader/document_loader.h" +#include "third_party/blink/renderer/core/loader/frame_loader.h" +#include "third_party/blink/renderer/core/loader/idleness_detector.h" +#include "third_party/blink/renderer/core/loader/interactive_detector.h" +#include "third_party/blink/renderer/core/loader/mixed_content_checker.h" +#include "third_party/blink/renderer/core/loader/network_hints_interface.h" +#include "third_party/blink/renderer/core/loader/ping_loader.h" +#include "third_party/blink/renderer/core/loader/private/frame_client_hints_preferences_context.h" +#include "third_party/blink/renderer/core/loader/progress_tracker.h" +#include "third_party/blink/renderer/core/loader/subresource_filter.h" +#include "third_party/blink/renderer/core/page/page.h" +#include "third_party/blink/renderer/core/paint/first_meaningful_paint_detector.h" +#include "third_party/blink/renderer/core/probe/core_probes.h" +#include "third_party/blink/renderer/core/svg/graphics/svg_image_chrome_client.h" +#include "third_party/blink/renderer/core/timing/dom_window_performance.h" +#include "third_party/blink/renderer/core/timing/performance.h" +#include "third_party/blink/renderer/core/timing/window_performance.h" +#include "third_party/blink/renderer/platform/bindings/v8_dom_activity_logger.h" +#include "third_party/blink/renderer/platform/exported/wrapped_resource_request.h" +#include "third_party/blink/renderer/platform/histogram.h" +#include "third_party/blink/renderer/platform/instrumentation/tracing/traced_value.h" +#include "third_party/blink/renderer/platform/loader/fetch/client_hints_preferences.h" +#include "third_party/blink/renderer/platform/loader/fetch/fetch_initiator_type_names.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource_load_priority.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource_loading_log.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource_timing_info.h" +#include "third_party/blink/renderer/platform/loader/fetch/unique_identifier.h" +#include "third_party/blink/renderer/platform/mhtml/mhtml_archive.h" +#include "third_party/blink/renderer/platform/network/http_names.h" +#include "third_party/blink/renderer/platform/network/network_state_notifier.h" +#include "third_party/blink/renderer/platform/network/network_utils.h" +#include "third_party/blink/renderer/platform/weborigin/scheme_registry.h" +#include "third_party/blink/renderer/platform/wtf/optional.h" +#include "third_party/blink/renderer/platform/wtf/vector.h" + +namespace blink { + +namespace { + +enum class RequestMethod { kIsPost, kIsNotPost }; +enum class RequestType { kIsConditional, kIsNotConditional }; +enum class ResourceType { kIsMainResource, kIsNotMainResource }; + +void MaybeRecordCTPolicyComplianceUseCounter( + LocalFrame* frame, + Resource::Type resource_type, + ResourceResponse::CTPolicyCompliance compliance) { + if (compliance != ResourceResponse::kCTPolicyDoesNotComply) + return; + // Exclude main-frame navigation requests; those are tracked elsewhere. + if (!frame->Tree().Parent() && resource_type == Resource::kMainResource) + return; + UseCounter::Count( + frame, + frame->Tree().Parent() + ? WebFeature::kCertificateTransparencyNonCompliantResourceInSubframe + : WebFeature:: + kCertificateTransparencyNonCompliantSubresourceInMainFrame); +} + +// Determines FetchCacheMode for a main resource, or FetchCacheMode that is +// corresponding to FrameLoadType. +// TODO(toyoshim): Probably, we should split FrameLoadType to FetchCacheMode +// conversion logic into a separate function. +mojom::FetchCacheMode DetermineCacheMode(RequestMethod method, + RequestType request_type, + ResourceType resource_type, + FrameLoadType load_type) { + switch (load_type) { + case kFrameLoadTypeStandard: + case kFrameLoadTypeReplaceCurrentItem: + case kFrameLoadTypeInitialInChildFrame: + return (request_type == RequestType::kIsConditional || + method == RequestMethod::kIsPost) + ? mojom::FetchCacheMode::kValidateCache + : mojom::FetchCacheMode::kDefault; + case kFrameLoadTypeBackForward: + case kFrameLoadTypeInitialHistoryLoad: + // Mutates the policy for POST requests to avoid form resubmission. + return method == RequestMethod::kIsPost + ? mojom::FetchCacheMode::kOnlyIfCached + : mojom::FetchCacheMode::kForceCache; + case kFrameLoadTypeReload: + return resource_type == ResourceType::kIsMainResource + ? mojom::FetchCacheMode::kValidateCache + : mojom::FetchCacheMode::kDefault; + case kFrameLoadTypeReloadBypassingCache: + return mojom::FetchCacheMode::kBypassCache; + } + NOTREACHED(); + return mojom::FetchCacheMode::kDefault; +} + +// Determines FetchCacheMode for |frame|. This FetchCacheMode should be a base +// policy to consider one of each resource belonging to the frame, and should +// not count resource specific conditions in. +// TODO(toyoshim): Remove |resourceType| to realize the design described above. +// See also comments in resourceRequestCachePolicy(). +mojom::FetchCacheMode DetermineFrameCacheMode(Frame* frame, + ResourceType resource_type) { + if (!frame) + return mojom::FetchCacheMode::kDefault; + if (!frame->IsLocalFrame()) + return DetermineFrameCacheMode(frame->Tree().Parent(), resource_type); + + // Does not propagate cache policy for subresources after the load event. + // TODO(toyoshim): We should be able to remove following parents' policy check + // if each frame has a relevant FrameLoadType for reload and history + // navigations. + if (resource_type == ResourceType::kIsNotMainResource && + ToLocalFrame(frame)->GetDocument()->LoadEventFinished()) { + return mojom::FetchCacheMode::kDefault; + } + + // Respects BypassingCache rather than parent's policy. + FrameLoadType load_type = + ToLocalFrame(frame)->Loader().GetDocumentLoader()->LoadType(); + if (load_type == kFrameLoadTypeReloadBypassingCache) + return mojom::FetchCacheMode::kBypassCache; + + // Respects parent's policy if it has a special one. + mojom::FetchCacheMode parent_cache_mode = + DetermineFrameCacheMode(frame->Tree().Parent(), resource_type); + if (parent_cache_mode != mojom::FetchCacheMode::kDefault) + return parent_cache_mode; + + // Otherwise, follows FrameLoadType. Use kIsNotPost, kIsNotConditional, and + // kIsNotMainResource to obtain a representative policy for the frame. + return DetermineCacheMode(RequestMethod::kIsNotPost, + RequestType::kIsNotConditional, + ResourceType::kIsNotMainResource, load_type); +} + +} // namespace + +struct FrameFetchContext::FrozenState final + : GarbageCollectedFinalized<FrozenState> { + FrozenState(ReferrerPolicy referrer_policy, + const String& outgoing_referrer, + const KURL& url, + scoped_refptr<const SecurityOrigin> security_origin, + scoped_refptr<const SecurityOrigin> parent_security_origin, + const Optional<mojom::IPAddressSpace>& address_space, + const ContentSecurityPolicy* content_security_policy, + KURL site_for_cookies, + scoped_refptr<const SecurityOrigin> requestor_origin, + const ClientHintsPreferences& client_hints_preferences, + float device_pixel_ratio, + const String& user_agent, + bool is_main_frame, + bool is_svg_image_chrome_client) + : referrer_policy(referrer_policy), + outgoing_referrer(outgoing_referrer), + url(url), + security_origin(std::move(security_origin)), + parent_security_origin(std::move(parent_security_origin)), + address_space(address_space), + content_security_policy(content_security_policy), + site_for_cookies(site_for_cookies), + requestor_origin(requestor_origin), + client_hints_preferences(client_hints_preferences), + device_pixel_ratio(device_pixel_ratio), + user_agent(user_agent), + is_main_frame(is_main_frame), + is_svg_image_chrome_client(is_svg_image_chrome_client) {} + + const ReferrerPolicy referrer_policy; + const String outgoing_referrer; + const KURL url; + const scoped_refptr<const SecurityOrigin> security_origin; + const scoped_refptr<const SecurityOrigin> parent_security_origin; + const Optional<mojom::IPAddressSpace> address_space; + const Member<const ContentSecurityPolicy> content_security_policy; + const KURL site_for_cookies; + const scoped_refptr<const SecurityOrigin> requestor_origin; + const ClientHintsPreferences client_hints_preferences; + const float device_pixel_ratio; + const String user_agent; + const bool is_main_frame; + const bool is_svg_image_chrome_client; + + void Trace(blink::Visitor* visitor) { + visitor->Trace(content_security_policy); + } +}; + +ResourceFetcher* FrameFetchContext::CreateFetcher(DocumentLoader* loader, + Document* document) { + FrameFetchContext* context = new FrameFetchContext(loader, document); + ResourceFetcher* fetcher = ResourceFetcher::Create(context); + BlinkLeakDetector::Instance().RegisterResourceFetcher(fetcher); + + if (loader && context->GetSettings()->GetSavePreviousDocumentResources() != + SavePreviousDocumentResources::kNever) { + if (Document* previous_document = context->GetFrame()->GetDocument()) { + if (previous_document->IsSecureTransitionTo(loader->Url())) { + fetcher->HoldResourcesFromPreviousFetcher( + previous_document->Loader()->Fetcher()); + } + } + } + + return fetcher; +} + +FrameFetchContext::FrameFetchContext(DocumentLoader* loader, Document* document) + : document_loader_(loader), + document_(document), + save_data_enabled_(GetNetworkStateNotifier().SaveDataEnabled()) { + DCHECK(GetFrame()); +} + +void FrameFetchContext::ProvideDocumentToContext(FetchContext& context, + Document* document) { + DCHECK(document); + CHECK(context.IsFrameFetchContext()); + static_cast<FrameFetchContext&>(context).document_ = document; +} + +FrameFetchContext::~FrameFetchContext() { + document_loader_ = nullptr; +} + +LocalFrame* FrameFetchContext::FrameOfImportsController() const { + DCHECK(document_); + DCHECK(!IsDetached()); + + // It's guaranteed that imports_controller is not nullptr since: + // - only ClearImportsController() clears it + // - ClearImportsController() also calls ClearContext() on this + // FrameFetchContext() making IsDetached() return false + HTMLImportsController* imports_controller = document_->ImportsController(); + DCHECK(imports_controller); + + // It's guaranteed that Master() is not yet Shutdown()-ed since when Master() + // is Shutdown()-ed: + // - Master()'s HTMLImportsController is disposed. + // - All the HTMLImportLoader instances of the HTMLImportsController are + // disposed. + // - ClearImportsController() is called on the Document of the + // HTMLImportLoader to detach this context which makes IsDetached() return + // true. + // HTMLImportsController is created only when the master Document's + // GetFrame() doesn't return nullptr, this is guaranteed to be not nullptr + // here. + LocalFrame* frame = imports_controller->Master()->GetFrame(); + DCHECK(frame); + return frame; +} + +scoped_refptr<base::SingleThreadTaskRunner> +FrameFetchContext::GetLoadingTaskRunner() { + if (IsDetached()) + return FetchContext::GetLoadingTaskRunner(); + return GetFrame()->GetTaskRunner(TaskType::kNetworking); +} + +FrameScheduler* FrameFetchContext::GetFrameScheduler() const { + if (IsDetached()) + return nullptr; + return GetFrame()->GetFrameScheduler(); +} + +KURL FrameFetchContext::GetSiteForCookies() const { + if (IsDetached()) + return frozen_state_->site_for_cookies; + + // Use document_ for subresource or nested frame cases, + // GetFrame()->GetDocument() otherwise. + Document* document = document_ ? document_.Get() : GetFrame()->GetDocument(); + return document->SiteForCookies(); +} + +SubresourceFilter* FrameFetchContext::GetSubresourceFilter() const { + if (IsDetached()) + return nullptr; + DocumentLoader* document_loader = MasterDocumentLoader(); + return document_loader ? document_loader->GetSubresourceFilter() : nullptr; +} + +LocalFrame* FrameFetchContext::GetFrame() const { + DCHECK(!IsDetached()); + + if (!document_loader_) + return FrameOfImportsController(); + + LocalFrame* frame = document_loader_->GetFrame(); + DCHECK(frame); + return frame; +} + +LocalFrameClient* FrameFetchContext::GetLocalFrameClient() const { + return GetFrame()->Client(); +} + +void FrameFetchContext::AddAdditionalRequestHeaders(ResourceRequest& request, + FetchResourceType type) { + BaseFetchContext::AddAdditionalRequestHeaders(request, type); + + // The remaining modifications are only necessary for HTTP and HTTPS. + if (!request.Url().IsEmpty() && !request.Url().ProtocolIsInHTTPFamily()) + return; + + if (IsDetached()) + return; + + // Reload should reflect the current data saver setting. + if (IsReloadLoadType(MasterDocumentLoader()->LoadType())) + request.ClearHTTPHeaderField(HTTPNames::Save_Data); + + if (save_data_enabled_) + request.SetHTTPHeaderField(HTTPNames::Save_Data, "on"); + + if (GetLocalFrameClient()->GetPreviewsStateForFrame() & + WebURLRequest::kNoScriptOn) { + request.AddHTTPHeaderField( + "Intervention", + "<https://www.chromestatus.com/features/4775088607985664>; " + "level=\"warning\""); + } + + if (GetLocalFrameClient()->GetPreviewsStateForFrame() & + WebURLRequest::kClientLoFiOn) { + request.AddHTTPHeaderField( + "Intervention", + "<https://www.chromestatus.com/features/6072546726248448>; " + "level=\"warning\""); + } +} + +// TODO(toyoshim, arthursonzogni): PlzNavigate doesn't use this function to set +// the ResourceRequest's cache policy. The cache policy determination needs to +// be factored out from FrameFetchContext and moved to the FrameLoader for +// instance. +mojom::FetchCacheMode FrameFetchContext::ResourceRequestCachePolicy( + const ResourceRequest& request, + Resource::Type type, + FetchParameters::DeferOption defer) const { + if (IsDetached()) + return mojom::FetchCacheMode::kDefault; + + DCHECK(GetFrame()); + if (type == Resource::kMainResource) { + const auto cache_mode = DetermineCacheMode( + request.HttpMethod() == HTTPNames::POST ? RequestMethod::kIsPost + : RequestMethod::kIsNotPost, + request.IsConditional() ? RequestType::kIsConditional + : RequestType::kIsNotConditional, + ResourceType::kIsMainResource, MasterDocumentLoader()->LoadType()); + // Follows the parent frame's policy. + // TODO(toyoshim): Probably, FrameLoadType for each frame should have a + // right type for reload or history navigations, and should not need to + // check parent's frame policy here. Once it has a right FrameLoadType, + // we can remove Resource::Type argument from determineFrameCacheMode. + // See also crbug.com/332602. + if (cache_mode != mojom::FetchCacheMode::kDefault) + return cache_mode; + return DetermineFrameCacheMode(GetFrame()->Tree().Parent(), + ResourceType::kIsMainResource); + } + + const auto cache_mode = + DetermineFrameCacheMode(GetFrame(), ResourceType::kIsNotMainResource); + + // TODO(toyoshim): Revisit to consider if this clause can be merged to + // determineWebCachePolicy or determineFrameCacheMode. + if (cache_mode == mojom::FetchCacheMode::kDefault && + request.IsConditional()) { + return mojom::FetchCacheMode::kValidateCache; + } + return cache_mode; +} + +inline DocumentLoader* FrameFetchContext::MasterDocumentLoader() const { + DCHECK(!IsDetached()); + + if (document_loader_) + return document_loader_.Get(); + + // GetDocumentLoader() here always returns a non-nullptr value that is the + // DocumentLoader for |document_| because: + // - A Document is created with a LocalFrame only after the + // DocumentLoader is committed + // - When another DocumentLoader is committed, the FrameLoader + // Shutdown()-s |document_| making IsDetached() return false + return FrameOfImportsController()->Loader().GetDocumentLoader(); +} + +void FrameFetchContext::DispatchDidChangeResourcePriority( + unsigned long identifier, + ResourceLoadPriority load_priority, + int intra_priority_value) { + if (IsDetached()) + return; + TRACE_EVENT1("devtools.timeline", "ResourceChangePriority", "data", + InspectorChangeResourcePriorityEvent::Data( + MasterDocumentLoader(), identifier, load_priority)); + probe::didChangeResourcePriority(GetFrame(), MasterDocumentLoader(), + identifier, load_priority); +} + +void FrameFetchContext::PrepareRequest(ResourceRequest& request, + RedirectType redirect_type) { + SetFirstPartyCookieAndRequestorOrigin(request); + + String user_agent = GetUserAgent(); + request.SetHTTPUserAgent(AtomicString(user_agent)); + + if (IsDetached()) + return; + GetLocalFrameClient()->DispatchWillSendRequest(request); + + // ServiceWorker hook ups. + if (MasterDocumentLoader()->GetServiceWorkerNetworkProvider()) { + WrappedResourceRequest webreq(request); + MasterDocumentLoader()->GetServiceWorkerNetworkProvider()->WillSendRequest( + webreq); + } + + // If it's not for redirect, hook up ApplicationCache here too. + if (redirect_type == FetchContext::RedirectType::kNotForRedirect && + document_loader_ && !document_loader_->Fetcher()->Archive() && + request.Url().IsValid()) { + document_loader_->GetApplicationCacheHost()->WillStartLoading(request); + } +} + +void FrameFetchContext::DispatchWillSendRequest( + unsigned long identifier, + ResourceRequest& request, + const ResourceResponse& redirect_response, + Resource::Type resource_type, + const FetchInitiatorInfo& initiator_info) { + if (IsDetached()) + return; + + if (redirect_response.IsNull()) { + // Progress doesn't care about redirects, only notify it when an + // initial request is sent. + GetFrame()->Loader().Progress().WillStartLoading(identifier, + request.Priority()); + } + probe::willSendRequest(GetFrame()->GetDocument(), identifier, + MasterDocumentLoader(), request, redirect_response, + initiator_info, resource_type); + if (IdlenessDetector* idleness_detector = GetFrame()->GetIdlenessDetector()) + idleness_detector->OnWillSendRequest(MasterDocumentLoader()->Fetcher()); + if (document_) { + InteractiveDetector* interactive_detector( + InteractiveDetector::From(*document_)); + if (interactive_detector) { + interactive_detector->OnResourceLoadBegin(WTF::nullopt); + } + } +} + +void FrameFetchContext::DispatchDidReceiveResponse( + unsigned long identifier, + const ResourceResponse& response, + network::mojom::RequestContextFrameType frame_type, + WebURLRequest::RequestContext request_context, + Resource* resource, + ResourceResponseType response_type) { + if (IsDetached()) + return; + + MaybeRecordCTPolicyComplianceUseCounter(GetFrame(), resource->GetType(), + response.GetCTPolicyCompliance()); + + if (response_type == ResourceResponseType::kFromMemoryCache) { + // Note: probe::willSendRequest needs to precede before this probe method. + probe::markResourceAsCached(GetFrame(), MasterDocumentLoader(), identifier); + if (response.IsNull()) + return; + } + + MixedContentChecker::CheckMixedPrivatePublic(GetFrame(), + response.RemoteIPAddress()); + LinkLoader::CanLoadResources resource_loading_policy = + response_type == ResourceResponseType::kFromMemoryCache + ? LinkLoader::kDoNotLoadResources + : LinkLoader::kLoadResourcesAndPreconnect; + if (document_loader_ && + document_loader_ == document_loader_->GetFrame() + ->Loader() + .GetProvisionalDocumentLoader()) { + FrameClientHintsPreferencesContext hints_context(GetFrame()); + document_loader_->GetClientHintsPreferences() + .UpdateFromAcceptClientHintsHeader( + response.HttpHeaderField(HTTPNames::Accept_CH), response.Url(), + &hints_context); + + // When response is received with a provisional docloader, the resource + // haven't committed yet, and we cannot load resources, only preconnect. + resource_loading_policy = LinkLoader::kDoNotLoadResources; + } + // Client hints preferences should be persisted only from responses that were + // served by the same host as the host of the document-level origin. + KURL frame_url = Url(); + if (frame_url == NullURL()) + frame_url = document_loader_->Url(); + + // Check if |response| belongs to a resource in the main frame, and if belongs + // to the same origin as frame top request. + if (SecurityOrigin::AreSameSchemeHostPort(response.Url(), frame_url) && + GetFrame()->IsMainFrame()) { + ParseAndPersistClientHints(response); + } + + LinkLoader::LoadLinksFromHeader( + response.HttpHeaderField(HTTPNames::Link), response.Url(), *GetFrame(), + document_, NetworkHintsInterfaceImpl(), resource_loading_policy, + LinkLoader::kLoadAll, nullptr); + + if (response.HasMajorCertificateErrors()) { + MixedContentChecker::HandleCertificateError(GetFrame(), response, + frame_type, request_context); + } + + if (response.IsLegacySymantecCert()) { + GetLocalFrameClient()->ReportLegacySymantecCert(response.Url(), + false /* did_fail */); + } + + GetFrame()->Loader().Progress().IncrementProgress(identifier, response); + GetLocalFrameClient()->DispatchDidReceiveResponse(response); + DocumentLoader* document_loader = MasterDocumentLoader(); + probe::didReceiveResourceResponse(GetFrame()->GetDocument(), identifier, + document_loader, response, resource); + // It is essential that inspector gets resource response BEFORE console. + GetFrame()->Console().ReportResourceResponseReceived(document_loader, + identifier, response); +} + +void FrameFetchContext::DispatchDidReceiveData(unsigned long identifier, + const char* data, + int data_length) { + if (IsDetached()) + return; + + GetFrame()->Loader().Progress().IncrementProgress(identifier, data_length); + probe::didReceiveData(GetFrame()->GetDocument(), identifier, + MasterDocumentLoader(), data, data_length); +} + +void FrameFetchContext::DispatchDidReceiveEncodedData(unsigned long identifier, + int encoded_data_length) { + if (IsDetached()) + return; + probe::didReceiveEncodedDataLength(GetFrame()->GetDocument(), + MasterDocumentLoader(), identifier, + encoded_data_length); +} + +void FrameFetchContext::DispatchDidDownloadData(unsigned long identifier, + int data_length, + int encoded_data_length) { + if (IsDetached()) + return; + + GetFrame()->Loader().Progress().IncrementProgress(identifier, data_length); + probe::didReceiveData(GetFrame()->GetDocument(), identifier, + MasterDocumentLoader(), nullptr, data_length); + probe::didReceiveEncodedDataLength(GetFrame()->GetDocument(), + MasterDocumentLoader(), identifier, + encoded_data_length); +} + +void FrameFetchContext::DispatchDidDownloadToBlob(unsigned long identifier, + BlobDataHandle* blob) { + if (IsDetached() || !blob) + return; + + probe::didReceiveBlob(GetFrame()->GetDocument(), identifier, + MasterDocumentLoader(), blob); +} + +void FrameFetchContext::DispatchDidFinishLoading( + unsigned long identifier, + double finish_time, + int64_t encoded_data_length, + int64_t decoded_body_length, + bool blocked_cross_site_document) { + if (IsDetached()) + return; + + GetFrame()->Loader().Progress().CompleteProgress(identifier); + probe::didFinishLoading(GetFrame()->GetDocument(), identifier, + MasterDocumentLoader(), finish_time, + encoded_data_length, decoded_body_length, + blocked_cross_site_document); + if (document_) { + InteractiveDetector* interactive_detector( + InteractiveDetector::From(*document_)); + if (interactive_detector) { + interactive_detector->OnResourceLoadEnd( + TimeTicksFromSeconds(finish_time)); + } + } +} + +void FrameFetchContext::DispatchDidFail(const KURL& url, + unsigned long identifier, + const ResourceError& error, + int64_t encoded_data_length, + bool is_internal_request) { + if (IsDetached()) + return; + + if (NetworkUtils::IsCertificateTransparencyRequiredError(error.ErrorCode())) { + UseCounter::Count( + GetFrame()->GetDocument(), + WebFeature::kCertificateTransparencyRequiredErrorOnResourceLoad); + } + + if (NetworkUtils::IsLegacySymantecCertError(error.ErrorCode())) { + UseCounter::Count(GetFrame()->GetDocument(), + WebFeature::kDistrustedLegacySymantecSubresource); + GetLocalFrameClient()->ReportLegacySymantecCert(url, true /* did_fail */); + } + + GetFrame()->Loader().Progress().CompleteProgress(identifier); + probe::didFailLoading(GetFrame()->GetDocument(), identifier, + MasterDocumentLoader(), error); + if (document_) { + InteractiveDetector* interactive_detector( + InteractiveDetector::From(*document_)); + if (interactive_detector) { + // We have not yet recorded load_finish_time. Pass nullopt here; we will + // call CurrentTimeTicksInSeconds lazily when we need it. + interactive_detector->OnResourceLoadEnd(WTF::nullopt); + } + } + // Notification to FrameConsole should come AFTER InspectorInstrumentation + // call, DevTools front-end relies on this. + if (!is_internal_request) { + GetFrame()->Console().DidFailLoading(MasterDocumentLoader(), identifier, + error); + } +} + +void FrameFetchContext::DispatchDidLoadResourceFromMemoryCache( + unsigned long identifier, + const ResourceRequest& resource_request, + const ResourceResponse& resource_response) { + if (IsDetached()) + return; + + GetLocalFrameClient()->DispatchDidLoadResourceFromMemoryCache( + resource_request, resource_response); +} + +bool FrameFetchContext::ShouldLoadNewResource(Resource::Type type) const { + if (!document_loader_) + return true; + + if (IsDetached()) + return false; + + FrameLoader& loader = document_loader_->GetFrame()->Loader(); + if (type == Resource::kMainResource) + return document_loader_ == loader.GetProvisionalDocumentLoader(); + return document_loader_ == loader.GetDocumentLoader(); +} + +void FrameFetchContext::RecordLoadingActivity( + const ResourceRequest& request, + Resource::Type type, + const AtomicString& fetch_initiator_name) { + if (!document_loader_ || document_loader_->Fetcher()->Archive() || + !request.Url().IsValid()) + return; + V8DOMActivityLogger* activity_logger = nullptr; + if (fetch_initiator_name == FetchInitiatorTypeNames::xmlhttprequest) { + activity_logger = V8DOMActivityLogger::CurrentActivityLogger(); + } else { + activity_logger = + V8DOMActivityLogger::CurrentActivityLoggerIfIsolatedWorld(); + } + + if (activity_logger) { + Vector<String> argv; + argv.push_back(Resource::ResourceTypeToString(type, fetch_initiator_name)); + argv.push_back(request.Url()); + activity_logger->LogEvent("blinkRequestResource", argv.size(), argv.data()); + } +} + +void FrameFetchContext::DidLoadResource(Resource* resource) { + if (!document_) + return; + FirstMeaningfulPaintDetector::From(*document_).CheckNetworkStable(); + if (LocalFrame* local_frame = document_->GetFrame()) { + if (IdlenessDetector* idleness_detector = + local_frame->GetIdlenessDetector()) { + idleness_detector->OnDidLoadResource(); + } + } + + if (resource->IsLoadEventBlockingResourceType()) + document_->CheckCompleted(); +} + +void FrameFetchContext::AddResourceTiming(const ResourceTimingInfo& info) { + // Normally, |document_| is cleared on Document shutdown. However, Documents + // for HTML imports will also not have a LocalFrame set: in that case, also + // early return, as there is nothing to report the resource timing to. + if (!document_) + return; + LocalFrame* frame = document_->GetFrame(); + if (!frame) + return; + + if (info.IsMainResource()) { + DCHECK(frame->Owner()); + // Main resource timing information is reported through the owner to be + // passed to the parent frame, if appropriate. + frame->Owner()->AddResourceTiming(info); + frame->DidSendResourceTimingInfoToParent(); + return; + } + + // All other resources are reported to the corresponding Document. + DOMWindowPerformance::performance(*document_->domWindow()) + ->GenerateAndAddResourceTiming(info); +} + +bool FrameFetchContext::AllowImage(bool images_enabled, const KURL& url) const { + if (IsDetached()) + return true; + + return GetContentSettingsClient()->AllowImage(images_enabled, url); +} + +bool FrameFetchContext::IsControlledByServiceWorker() const { + if (IsDetached()) + return false; + + DCHECK(MasterDocumentLoader()); + + auto* service_worker_network_provider = + MasterDocumentLoader()->GetServiceWorkerNetworkProvider(); + return service_worker_network_provider && + service_worker_network_provider->HasControllerServiceWorker(); +} + +int64_t FrameFetchContext::ServiceWorkerID() const { + DCHECK(IsControlledByServiceWorker()); + DCHECK(MasterDocumentLoader()); + auto* service_worker_network_provider = + MasterDocumentLoader()->GetServiceWorkerNetworkProvider(); + return service_worker_network_provider + ? service_worker_network_provider->ControllerServiceWorkerID() + : -1; +} + +int FrameFetchContext::ApplicationCacheHostID() const { + if (!document_loader_) + return WebApplicationCacheHost::kAppCacheNoHostId; + return document_loader_->GetApplicationCacheHost()->GetHostID(); +} + +bool FrameFetchContext::IsMainFrame() const { + if (IsDetached()) + return frozen_state_->is_main_frame; + return GetFrame()->IsMainFrame(); +} + +bool FrameFetchContext::DefersLoading() const { + return IsDetached() ? false : GetFrame()->GetPage()->Paused(); +} + +bool FrameFetchContext::IsLoadComplete() const { + if (IsDetached()) + return true; + + return document_ && document_->LoadEventFinished(); +} + +bool FrameFetchContext::UpdateTimingInfoForIFrameNavigation( + ResourceTimingInfo* info) { + if (IsDetached()) + return false; + + // <iframe>s should report the initial navigation requested by the parent + // document, but not subsequent navigations. + if (!GetFrame()->Owner()) + return false; + // Note that this can be racy since this information is forwarded over IPC + // when crossing process boundaries. + if (!GetFrame()->should_send_resource_timing_info_to_parent()) + return false; + // Do not report iframe navigation that restored from history, since its + // location may have been changed after initial navigation. + if (MasterDocumentLoader()->LoadType() == kFrameLoadTypeInitialHistoryLoad) + return false; + return true; +} + +const SecurityOrigin* FrameFetchContext::GetSecurityOrigin() const { + if (IsDetached()) + return frozen_state_->security_origin.get(); + return document_ ? document_->GetSecurityOrigin() : nullptr; +} + +void FrameFetchContext::ModifyRequestForCSP(ResourceRequest& resource_request) { + if (IsDetached()) + return; + + // Record the latest requiredCSP value that will be used when sending this + // request. + GetFrame()->Loader().RecordLatestRequiredCSP(); + GetFrame()->Loader().ModifyRequestForCSP(resource_request, document_); +} + +void FrameFetchContext::AddClientHintsIfNecessary( + const ClientHintsPreferences& hints_preferences, + const FetchParameters::ResourceWidth& resource_width, + ResourceRequest& request) { + WebEnabledClientHints enabled_hints; + if (blink::RuntimeEnabledFeatures::ClientHintsPersistentEnabled()) { + // If the feature is enabled, then client hints are allowed only on secure + // URLs. + if (!ClientHintsPreferences::IsClientHintsAllowed(request.Url())) + return; + + // Check if |url| is allowed to run JavaScript. If not, client hints are not + // attached to the requests that initiate on the render side. + if (!AllowScriptFromSourceWithoutNotifying(request.Url())) { + return; + } + + if (IsDetached()) + return; + + if (!GetFrame() + ->Tree() + .Top() + .GetSecurityContext() + ->GetSecurityOrigin() + ->IsSameSchemeHostPort( + SecurityOrigin::Create(request.Url()).get())) { + // No client hints for 3p origins. + return; + } + if (GetContentSettingsClient()) { + GetContentSettingsClient()->GetAllowedClientHintsFromSource( + request.Url(), &enabled_hints); + } + } + + if (ShouldSendClientHint(mojom::WebClientHintsType::kDeviceMemory, + hints_preferences, enabled_hints)) { + request.AddHTTPHeaderField( + "Device-Memory", + AtomicString(String::Number( + ApproximatedDeviceMemory::GetApproximatedDeviceMemory()))); + } + + float dpr = GetDevicePixelRatio(); + if (ShouldSendClientHint(mojom::WebClientHintsType::kDpr, hints_preferences, + enabled_hints)) { + request.AddHTTPHeaderField("DPR", AtomicString(String::Number(dpr))); + } + + if (ShouldSendClientHint(mojom::WebClientHintsType::kResourceWidth, + hints_preferences, enabled_hints)) { + if (resource_width.is_set) { + float physical_width = resource_width.width * dpr; + request.AddHTTPHeaderField( + "Width", AtomicString(String::Number(ceil(physical_width)))); + } + } + + if (ShouldSendClientHint(mojom::WebClientHintsType::kViewportWidth, + hints_preferences, enabled_hints) && + !IsDetached() && GetFrame()->View()) { + request.AddHTTPHeaderField( + "Viewport-Width", + AtomicString(String::Number(GetFrame()->View()->ViewportWidth()))); + } + + if (ShouldSendClientHint(mojom::WebClientHintsType::kRtt, hints_preferences, + enabled_hints)) { + unsigned long rtt = GetNetworkStateNotifier().RoundRtt( + request.Url().Host(), GetNetworkStateNotifier().HttpRtt()); + request.AddHTTPHeaderField( + blink::kClientHintsHeaderMapping[static_cast<size_t>( + mojom::WebClientHintsType::kRtt)], + AtomicString(String::Number(rtt))); + } + + if (ShouldSendClientHint(mojom::WebClientHintsType::kDownlink, + hints_preferences, enabled_hints)) { + double mbps = GetNetworkStateNotifier().RoundMbps( + request.Url().Host(), + GetNetworkStateNotifier().DownlinkThroughputMbps()); + request.AddHTTPHeaderField( + blink::kClientHintsHeaderMapping[static_cast<size_t>( + mojom::WebClientHintsType::kDownlink)], + AtomicString(String::Number(mbps))); + } + + if (ShouldSendClientHint(mojom::WebClientHintsType::kEct, hints_preferences, + enabled_hints)) { + request.AddHTTPHeaderField( + blink::kClientHintsHeaderMapping[static_cast<size_t>( + mojom::WebClientHintsType::kEct)], + AtomicString(NetworkStateNotifier::EffectiveConnectionTypeToString( + GetNetworkStateNotifier().EffectiveType()))); + } +} + +void FrameFetchContext::PopulateResourceRequest( + Resource::Type type, + const ClientHintsPreferences& hints_preferences, + const FetchParameters::ResourceWidth& resource_width, + ResourceRequest& request) { + ModifyRequestForCSP(request); + AddClientHintsIfNecessary(hints_preferences, resource_width, request); + AddCSPHeaderIfNecessary(type, request); +} + +void FrameFetchContext::SetFirstPartyCookieAndRequestorOrigin( + ResourceRequest& request) { + // Set the first party for cookies url if it has not been set yet (new + // requests). This value will be updated during redirects, consistent with + // https://tools.ietf.org/html/draft-ietf-httpbis-cookie-same-site-00#section-2.1.1? + if (request.SiteForCookies().IsNull()) { + if (request.GetFrameType() == + network::mojom::RequestContextFrameType::kTopLevel) { + request.SetSiteForCookies(request.Url()); + } else { + request.SetSiteForCookies(GetSiteForCookies()); + } + } + + // * For subresources, the initiator origin is the origin of the + // |document_| that contains them. + // * For loading a new document in the frame, the initiator is not set here. + // In most of the cases, it is set in the FrameLoadRequest constructor. + // Otherwise, it must be set immediately after the request has been created. + // See the calls to ResourceRequest::SetRequestorOrigin(). + if (request.GetFrameType() == + network::mojom::RequestContextFrameType::kNone) { + if (!request.RequestorOrigin()) + request.SetRequestorOrigin(GetRequestorOrigin()); + } +} + +MHTMLArchive* FrameFetchContext::Archive() const { + DCHECK(!IsMainFrame()); + // TODO(nasko): How should this work with OOPIF? + // The MHTMLArchive is parsed as a whole, but can be constructed from frames + // in multiple processes. In that case, which process should parse it and how + // should the output be spread back across multiple processes? + if (IsDetached() || !GetFrame()->Tree().Parent()->IsLocalFrame()) + return nullptr; + return ToLocalFrame(GetFrame()->Tree().Parent()) + ->Loader() + .GetDocumentLoader() + ->Fetcher() + ->Archive(); +} + +bool FrameFetchContext::AllowScriptFromSource(const KURL& url) const { + if (AllowScriptFromSourceWithoutNotifying(url)) + return true; + ContentSettingsClient* settings_client = GetContentSettingsClient(); + if (settings_client) + settings_client->DidNotAllowScript(); + return false; +} + +bool FrameFetchContext::AllowScriptFromSourceWithoutNotifying( + const KURL& url) const { + ContentSettingsClient* settings_client = GetContentSettingsClient(); + Settings* settings = GetSettings(); + if (settings_client && !settings_client->AllowScriptFromSource( + !settings || settings->GetScriptEnabled(), url)) { + return false; + } + return true; +} + +bool FrameFetchContext::ShouldBlockRequestByInspector(const KURL& url) const { + if (IsDetached()) + return false; + bool should_block_request = false; + probe::shouldBlockRequest(GetFrame()->GetDocument(), url, + &should_block_request); + return should_block_request; +} + +void FrameFetchContext::DispatchDidBlockRequest( + const ResourceRequest& resource_request, + const FetchInitiatorInfo& fetch_initiator_info, + ResourceRequestBlockedReason blocked_reason, + Resource::Type resource_type) const { + if (IsDetached()) + return; + probe::didBlockRequest(GetFrame()->GetDocument(), resource_request, + MasterDocumentLoader(), fetch_initiator_info, + blocked_reason, resource_type); +} + +bool FrameFetchContext::ShouldBypassMainWorldCSP() const { + if (IsDetached()) + return false; + + return GetFrame()->GetScriptController().ShouldBypassMainWorldCSP(); +} + +bool FrameFetchContext::IsSVGImageChromeClient() const { + if (IsDetached()) + return frozen_state_->is_svg_image_chrome_client; + + return GetFrame()->GetChromeClient().IsSVGImageChromeClient(); +} + +void FrameFetchContext::CountUsage(WebFeature feature) const { + if (IsDetached()) + return; + UseCounter::Count(GetFrame(), feature); +} + +void FrameFetchContext::CountDeprecation(WebFeature feature) const { + if (IsDetached()) + return; + Deprecation::CountDeprecation(GetFrame(), feature); +} + +bool FrameFetchContext::ShouldBlockWebSocketByMixedContentCheck( + const KURL& url) const { + if (IsDetached()) { + // TODO(yhirano): Implement the detached case. + return false; + } + return !MixedContentChecker::IsWebSocketAllowed(GetFrame(), url); +} + +bool FrameFetchContext::ShouldBlockFetchByMixedContentCheck( + WebURLRequest::RequestContext request_context, + network::mojom::RequestContextFrameType frame_type, + ResourceRequest::RedirectStatus redirect_status, + const KURL& url, + SecurityViolationReportingPolicy reporting_policy) const { + if (IsDetached()) { + // TODO(yhirano): Implement the detached case. + return false; + } + return MixedContentChecker::ShouldBlockFetch(GetFrame(), request_context, + frame_type, redirect_status, url, + reporting_policy); +} + +bool FrameFetchContext::ShouldBlockFetchAsCredentialedSubresource( + const ResourceRequest& resource_request, + const KURL& url) const { + // BlockCredentialedSubresources has already been checked on the + // browser-side. It should not be checked a second time here because the + // renderer-side implementation suffers from https://crbug.com/756846. + if (!resource_request.CheckForBrowserSideNavigation()) + return false; + + // URLs with no embedded credentials should load correctly. + if (url.User().IsEmpty() && url.Pass().IsEmpty()) + return false; + + if (resource_request.GetRequestContext() == + WebURLRequest::kRequestContextXMLHttpRequest) { + return false; + } + + // Relative URLs on top-level pages that were loaded with embedded credentials + // should load correctly. + // TODO(mkwst): This doesn't work when the subresource is an iframe. + // See https://crbug.com/756846. + if (Url().User() == url.User() && Url().Pass() == url.Pass() && + SecurityOrigin::Create(url)->IsSameSchemeHostPort(GetSecurityOrigin())) { + return false; + } + + CountDeprecation(WebFeature::kRequestedSubresourceWithEmbeddedCredentials); + + // TODO(mkwst): Remove the runtime check one way or the other once we're + // sure it's going to stick (or that it's not). + return RuntimeEnabledFeatures::BlockCredentialedSubresourcesEnabled(); +} + +ReferrerPolicy FrameFetchContext::GetReferrerPolicy() const { + if (IsDetached()) + return frozen_state_->referrer_policy; + return document_->GetReferrerPolicy(); +} + +String FrameFetchContext::GetOutgoingReferrer() const { + if (IsDetached()) + return frozen_state_->outgoing_referrer; + return document_->OutgoingReferrer(); +} + +const KURL& FrameFetchContext::Url() const { + if (IsDetached()) + return frozen_state_->url; + if (!document_) + return NullURL(); + return document_->Url(); +} + +const SecurityOrigin* FrameFetchContext::GetParentSecurityOrigin() const { + if (IsDetached()) + return frozen_state_->parent_security_origin.get(); + Frame* parent = GetFrame()->Tree().Parent(); + if (!parent) + return nullptr; + return parent->GetSecurityContext()->GetSecurityOrigin(); +} + +Optional<mojom::IPAddressSpace> FrameFetchContext::GetAddressSpace() const { + if (IsDetached()) + return frozen_state_->address_space; + if (!document_) + return WTF::nullopt; + ExecutionContext* context = document_; + return WTF::make_optional(context->GetSecurityContext().AddressSpace()); +} + +const ContentSecurityPolicy* FrameFetchContext::GetContentSecurityPolicy() + const { + if (IsDetached()) + return frozen_state_->content_security_policy; + return document_ ? document_->GetContentSecurityPolicy() : nullptr; +} + +void FrameFetchContext::AddConsoleMessage(ConsoleMessage* message) const { + if (IsDetached()) + return; + + // Route the console message through Document if it's attached, so + // that script line numbers can be included. Otherwise, route directly to the + // FrameConsole, to ensure we never drop a message. + if (document_ && document_->GetFrame()) + document_->AddConsoleMessage(message); + else + GetFrame()->Console().AddMessage(message); +} + +ContentSettingsClient* FrameFetchContext::GetContentSettingsClient() const { + if (IsDetached()) + return nullptr; + return GetFrame()->GetContentSettingsClient(); +} + +Settings* FrameFetchContext::GetSettings() const { + if (IsDetached()) + return nullptr; + DCHECK(GetFrame()); + return GetFrame()->GetSettings(); +} + +String FrameFetchContext::GetUserAgent() const { + if (IsDetached()) + return frozen_state_->user_agent; + return GetFrame()->Loader().UserAgent(); +} + +scoped_refptr<const SecurityOrigin> FrameFetchContext::GetRequestorOrigin() { + if (IsDetached()) + return frozen_state_->requestor_origin; + + // Should not be called during the document load, and |document_| should be + // always set beforehand for a subresource loading. + DCHECK(document_); + + // If sandbox is enabled and allow-same-origin is not set in the attribute, + // |document|'s SecurityOrigin is set to the unique opaque origin, and + // FrameFetchContext::GetSecurityOrigin also respects the unique origin. + // But, we still need to set the unveiled document origin to the requestor + // origin. See also sandbox's spec; + // https://html.spec.whatwg.org/multipage/iframe-embed-object.html#attr-iframe-sandbox. + if (document_->IsSandboxed(kSandboxOrigin)) + return SecurityOrigin::Create(document_->Url()); + + return GetSecurityOrigin(); +} + +ClientHintsPreferences FrameFetchContext::GetClientHintsPreferences() const { + if (IsDetached()) + return frozen_state_->client_hints_preferences; + + if (!document_) + return ClientHintsPreferences(); + + return document_->GetClientHintsPreferences(); +} + +float FrameFetchContext::GetDevicePixelRatio() const { + if (IsDetached()) + return frozen_state_->device_pixel_ratio; + + if (!document_) { + // Note that this value is not used because the preferences object returned + // by GetClientHintsPreferences() doesn't allow to use it. + return 1.0; + } + + return document_->DevicePixelRatio(); +} + +bool FrameFetchContext::ShouldSendClientHint( + mojom::WebClientHintsType type, + const ClientHintsPreferences& hints_preferences, + const WebEnabledClientHints& enabled_hints) const { + return GetClientHintsPreferences().ShouldSend(type) || + hints_preferences.ShouldSend(type) || enabled_hints.IsEnabled(type); +} + +void FrameFetchContext::ParseAndPersistClientHints( + const ResourceResponse& response) { + ClientHintsPreferences hints_preferences; + WebEnabledClientHints enabled_client_hints; + TimeDelta persist_duration; + FrameClientHintsPreferencesContext hints_context(GetFrame()); + hints_preferences.UpdatePersistentHintsFromHeaders( + response, &hints_context, enabled_client_hints, &persist_duration); + + if (persist_duration.InSeconds() <= 0) + return; + + if (!AllowScriptFromSourceWithoutNotifying(response.Url())) { + // Do not persist client hint preferences if the JavaScript is disabled. + return; + } + + GetContentSettingsClient()->PersistClientHints( + enabled_client_hints, persist_duration, response.Url()); +} + +std::unique_ptr<WebURLLoader> FrameFetchContext::CreateURLLoader( + const ResourceRequest& request, + scoped_refptr<base::SingleThreadTaskRunner> task_runner, + const ResourceLoaderOptions& options) { + DCHECK(!IsDetached()); + WrappedResourceRequest webreq(request); + + network::mojom::blink::URLLoaderFactoryPtr url_loader_factory; + if (options.url_loader_factory) { + options.url_loader_factory->data->Clone(MakeRequest(&url_loader_factory)); + } + // Resolve any blob: URLs that haven't been resolved yet. The XHR and fetch() + // API implementations resolve blob URLs earlier because there can be + // arbitrarily long delays between creating requests with those APIs and + // actually creating the URL loader here. Other subresource loading will + // immediately create the URL loader so resolving those blob URLs here is + // simplest. + if (document_ && request.Url().ProtocolIs("blob") && + RuntimeEnabledFeatures::MojoBlobURLsEnabled() && !url_loader_factory) { + document_->GetPublicURLManager().Resolve(request.Url(), + MakeRequest(&url_loader_factory)); + } + if (url_loader_factory) { + return Platform::Current() + ->WrapURLLoaderFactory(url_loader_factory.PassInterface().PassHandle()) + ->CreateURLLoader(webreq, task_runner); + } + + if (MasterDocumentLoader()->GetServiceWorkerNetworkProvider()) { + auto loader = MasterDocumentLoader() + ->GetServiceWorkerNetworkProvider() + ->CreateURLLoader(webreq, task_runner); + if (loader) + return loader; + } + return GetFrame()->GetURLLoaderFactory()->CreateURLLoader(webreq, + task_runner); +} + +FetchContext* FrameFetchContext::Detach() { + if (IsDetached()) + return this; + + if (document_) { + frozen_state_ = new FrozenState( + GetReferrerPolicy(), GetOutgoingReferrer(), Url(), GetSecurityOrigin(), + GetParentSecurityOrigin(), GetAddressSpace(), + GetContentSecurityPolicy(), GetSiteForCookies(), GetRequestorOrigin(), + GetClientHintsPreferences(), GetDevicePixelRatio(), GetUserAgent(), + IsMainFrame(), IsSVGImageChromeClient()); + } else { + // Some getters are unavailable in this case. + frozen_state_ = new FrozenState( + kReferrerPolicyDefault, String(), NullURL(), GetSecurityOrigin(), + GetParentSecurityOrigin(), GetAddressSpace(), + GetContentSecurityPolicy(), GetSiteForCookies(), + SecurityOrigin::CreateUnique(), GetClientHintsPreferences(), + GetDevicePixelRatio(), GetUserAgent(), IsMainFrame(), + IsSVGImageChromeClient()); + } + + // This is needed to break a reference cycle in which off-heap + // ComputedStyle is involved. See https://crbug.com/383860 for details. + document_ = nullptr; + + return this; +} + +void FrameFetchContext::Trace(blink::Visitor* visitor) { + visitor->Trace(document_loader_); + visitor->Trace(document_); + visitor->Trace(frozen_state_); + BaseFetchContext::Trace(visitor); +} + +void FrameFetchContext::RecordDataUriWithOctothorpe() { + CountDeprecation(WebFeature::kDataUriHasOctothorpe); +} + +ResourceLoadPriority FrameFetchContext::ModifyPriorityForExperiments( + ResourceLoadPriority priority) const { + if (!GetSettings()) + return priority; + + WebEffectiveConnectionType max_effective_connection_type_threshold = + GetSettings()->GetLowPriorityIframesThreshold(); + + if (max_effective_connection_type_threshold <= + WebEffectiveConnectionType::kTypeOffline) { + return priority; + } + + WebEffectiveConnectionType effective_connection_type = + GetNetworkStateNotifier().EffectiveType(); + + if (effective_connection_type <= WebEffectiveConnectionType::kTypeOffline) { + return priority; + } + + if (effective_connection_type > max_effective_connection_type_threshold) { + // Network is not slow enough. + return priority; + } + + if (GetFrame()->IsMainFrame()) { + DEFINE_STATIC_LOCAL(EnumerationHistogram, main_frame_priority_histogram, + ("LowPriorityIframes.MainFrameRequestPriority", + static_cast<int>(ResourceLoadPriority::kHighest) + 1)); + main_frame_priority_histogram.Count(static_cast<int>(priority)); + return priority; + } + + DEFINE_STATIC_LOCAL(EnumerationHistogram, iframe_priority_histogram, + ("LowPriorityIframes.IframeRequestPriority", + static_cast<int>(ResourceLoadPriority::kHighest) + 1)); + iframe_priority_histogram.Count(static_cast<int>(priority)); + // When enabled, the priority of all resources in subframe is dropped. + // Non-delayable resources are assigned a priority of kLow, and the rest of + // them are assigned a priority of kLowest. This ensures that if the webpage + // fetches most of its primary content using iframes, then high priority + // requests within the iframe go on the network first. + if (priority >= ResourceLoadPriority::kHigh) + return ResourceLoadPriority::kLow; + return ResourceLoadPriority::kLowest; +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/loader/frame_fetch_context.h b/chromium/third_party/blink/renderer/core/loader/frame_fetch_context.h new file mode 100644 index 00000000000..5d115f0cc12 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/frame_fetch_context.h @@ -0,0 +1,268 @@ +/* + * 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. + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_FRAME_FETCH_CONTEXT_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_FRAME_FETCH_CONTEXT_H_ + +#include "base/single_thread_task_runner.h" +#include "third_party/blink/renderer/core/core_export.h" +#include "third_party/blink/renderer/core/frame/csp/content_security_policy.h" +#include "third_party/blink/renderer/core/loader/base_fetch_context.h" +#include "third_party/blink/renderer/platform/heap/handle.h" +#include "third_party/blink/renderer/platform/loader/fetch/client_hints_preferences.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/resource_request.h" +#include "third_party/blink/renderer/platform/network/content_security_policy_parsers.h" +#include "third_party/blink/renderer/platform/wtf/forward.h" + +namespace blink { + +class ClientHintsPreferences; +class ContentSettingsClient; +class Document; +class DocumentLoader; +class LocalFrame; +class LocalFrameClient; +class ResourceError; +class ResourceResponse; +class Settings; +struct WebEnabledClientHints; + +class CORE_EXPORT FrameFetchContext final : public BaseFetchContext { + public: + static ResourceFetcher* CreateFetcherFromDocumentLoader( + DocumentLoader* loader) { + return CreateFetcher(loader, nullptr); + } + // Used for creating a FrameFetchContext for an imported Document. + // |document_loader_| will be set to nullptr. + static ResourceFetcher* CreateFetcherFromDocument(Document* document) { + return CreateFetcher(nullptr, document); + } + + static void ProvideDocumentToContext(FetchContext&, Document*); + + ~FrameFetchContext() override; + + bool IsFrameFetchContext() override { return true; } + + void RecordDataUriWithOctothorpe() override; + + void AddAdditionalRequestHeaders(ResourceRequest&, + FetchResourceType) override; + mojom::FetchCacheMode ResourceRequestCachePolicy( + const ResourceRequest&, + Resource::Type, + FetchParameters::DeferOption) const override; + void DispatchDidChangeResourcePriority(unsigned long identifier, + ResourceLoadPriority, + int intra_priority_value) override; + void PrepareRequest(ResourceRequest&, RedirectType) override; + void DispatchWillSendRequest( + unsigned long identifier, + ResourceRequest&, + const ResourceResponse& redirect_response, + Resource::Type, + const FetchInitiatorInfo& = FetchInitiatorInfo()) override; + void DispatchDidLoadResourceFromMemoryCache(unsigned long identifier, + const ResourceRequest&, + const ResourceResponse&) override; + void DispatchDidReceiveResponse(unsigned long identifier, + const ResourceResponse&, + network::mojom::RequestContextFrameType, + WebURLRequest::RequestContext, + Resource*, + ResourceResponseType) override; + void DispatchDidReceiveData(unsigned long identifier, + const char* data, + int data_length) override; + void DispatchDidReceiveEncodedData(unsigned long identifier, + int encoded_data_length) override; + void DispatchDidDownloadData(unsigned long identifier, + int data_length, + int encoded_data_length) override; + void DispatchDidDownloadToBlob(unsigned long identifier, + BlobDataHandle*) override; + void DispatchDidFinishLoading(unsigned long identifier, + double finish_time, + int64_t encoded_data_length, + int64_t decoded_body_length, + bool blocked_cross_site_document) override; + void DispatchDidFail(const KURL&, + unsigned long identifier, + const ResourceError&, + int64_t encoded_data_length, + bool is_internal_request) override; + + bool ShouldLoadNewResource(Resource::Type) const override; + void RecordLoadingActivity(const ResourceRequest&, + Resource::Type, + const AtomicString& fetch_initiator_name) override; + void DidLoadResource(Resource*) override; + + void AddResourceTiming(const ResourceTimingInfo&) override; + bool AllowImage(bool images_enabled, const KURL&) const override; + bool IsControlledByServiceWorker() const override; + int64_t ServiceWorkerID() const override; + int ApplicationCacheHostID() const override; + + bool IsMainFrame() const override; + bool DefersLoading() const override; + bool IsLoadComplete() const override; + bool UpdateTimingInfoForIFrameNavigation(ResourceTimingInfo*) override; + + const SecurityOrigin* GetSecurityOrigin() const override; + + void PopulateResourceRequest(Resource::Type, + const ClientHintsPreferences&, + const FetchParameters::ResourceWidth&, + ResourceRequest&) override; + + // Exposed for testing. + void ModifyRequestForCSP(ResourceRequest&); + void AddClientHintsIfNecessary(const ClientHintsPreferences&, + const FetchParameters::ResourceWidth&, + ResourceRequest&); + + MHTMLArchive* Archive() const override; + + std::unique_ptr<WebURLLoader> CreateURLLoader( + const ResourceRequest&, + scoped_refptr<base::SingleThreadTaskRunner>, + const ResourceLoaderOptions&) override; + + ResourceLoadScheduler::ThrottlingPolicy InitialLoadThrottlingPolicy() + const override { + // Frame loading should normally start with |kTight| throttling, as the + // frame will be in layout-blocking state until the <body> tag is inserted. + return ResourceLoadScheduler::ThrottlingPolicy::kTight; + } + + bool IsDetached() const override { return frozen_state_; } + + FetchContext* Detach() override; + + void Trace(blink::Visitor*) override; + + ResourceLoadPriority ModifyPriorityForExperiments( + ResourceLoadPriority) const override; + + private: + friend class FrameFetchContextTest; + + struct FrozenState; + + static ResourceFetcher* CreateFetcher(DocumentLoader*, Document*); + + FrameFetchContext(DocumentLoader*, Document*); + + // Convenient accessors below can be used to transparently access the + // relevant document loader or frame in either cases without null-checks. + // + // TODO(kinuko): Remove constness, these return non-const members. + DocumentLoader* MasterDocumentLoader() const; + LocalFrame* GetFrame() const; + LocalFrameClient* GetLocalFrameClient() const; + LocalFrame* FrameOfImportsController() const; + + // FetchContext overrides: + FrameScheduler* GetFrameScheduler() const override; + scoped_refptr<base::SingleThreadTaskRunner> GetLoadingTaskRunner() override; + + // BaseFetchContext overrides: + KURL GetSiteForCookies() const override; + SubresourceFilter* GetSubresourceFilter() const override; + bool AllowScriptFromSource(const KURL&) const override; + bool ShouldBlockRequestByInspector(const KURL&) const override; + void DispatchDidBlockRequest(const ResourceRequest&, + const FetchInitiatorInfo&, + ResourceRequestBlockedReason, + Resource::Type) const override; + bool ShouldBypassMainWorldCSP() const override; + bool IsSVGImageChromeClient() const override; + void CountUsage(WebFeature) const override; + void CountDeprecation(WebFeature) const override; + bool ShouldBlockWebSocketByMixedContentCheck(const KURL&) const override; + bool ShouldBlockFetchByMixedContentCheck( + WebURLRequest::RequestContext, + network::mojom::RequestContextFrameType, + ResourceRequest::RedirectStatus, + const KURL&, + SecurityViolationReportingPolicy) const override; + bool ShouldBlockFetchAsCredentialedSubresource(const ResourceRequest&, + const KURL&) const override; + + ReferrerPolicy GetReferrerPolicy() const override; + String GetOutgoingReferrer() const override; + const KURL& Url() const override; + const SecurityOrigin* GetParentSecurityOrigin() const override; + Optional<mojom::IPAddressSpace> GetAddressSpace() const override; + const ContentSecurityPolicy* GetContentSecurityPolicy() const override; + void AddConsoleMessage(ConsoleMessage*) const override; + + ContentSettingsClient* GetContentSettingsClient() const; + Settings* GetSettings() const; + String GetUserAgent() const; + scoped_refptr<const SecurityOrigin> GetRequestorOrigin(); + ClientHintsPreferences GetClientHintsPreferences() const; + float GetDevicePixelRatio() const; + bool ShouldSendClientHint(mojom::WebClientHintsType, + const ClientHintsPreferences&, + const WebEnabledClientHints&) const; + // Checks if the origin requested persisting the client hints, and notifies + // the |ContentSettingsClient| with the list of client hints and the + // persistence duration. + void ParseAndPersistClientHints(const ResourceResponse&); + void SetFirstPartyCookieAndRequestorOrigin(ResourceRequest&); + + // Returns true if execution of scripts from the url are allowed. Compared to + // AllowScriptFromSource(), this method does not generate any + // notification to the |ContentSettingsClient| that the execution of the + // script was blocked. This method should be called only when there is a need + // to check the settings, and where blocked setting doesn't really imply that + // JavaScript was blocked from being executed. + bool AllowScriptFromSourceWithoutNotifying(const KURL&) const; + + Member<DocumentLoader> document_loader_; + Member<Document> document_; + + // The value of |save_data_enabled_| is read once per frame from + // NetworkStateNotifier, which is guarded by a mutex lock, and cached locally + // here for performance. + const bool save_data_enabled_; + + // Non-null only when detached. + Member<const FrozenState> frozen_state_; +}; + +} // namespace blink + +#endif diff --git a/chromium/third_party/blink/renderer/core/loader/frame_fetch_context_test.cc b/chromium/third_party/blink/renderer/core/loader/frame_fetch_context_test.cc new file mode 100644 index 00000000000..14f0a826725 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/frame_fetch_context_test.cc @@ -0,0 +1,1733 @@ +/* + * Copyright (c) 2015, 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/frame_fetch_context.h" + +#include <memory> +#include "services/network/public/mojom/request_context_frame_type.mojom-blink.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/blink/public/common/device_memory/approximated_device_memory.h" +#include "third_party/blink/public/mojom/net/ip_address_space.mojom-blink.h" +#include "third_party/blink/public/platform/modules/fetch/fetch_api_request.mojom-shared.h" +#include "third_party/blink/public/platform/web_client_hints_type.h" +#include "third_party/blink/public/platform/web_document_subresource_filter.h" +#include "third_party/blink/public/platform/web_insecure_request_policy.h" +#include "third_party/blink/public/platform/web_runtime_features.h" +#include "third_party/blink/renderer/core/dom/document.h" +#include "third_party/blink/renderer/core/frame/AdTracker.h" +#include "third_party/blink/renderer/core/frame/frame_owner.h" +#include "third_party/blink/renderer/core/frame/frame_types.h" +#include "third_party/blink/renderer/core/frame/local_frame_view.h" +#include "third_party/blink/renderer/core/frame/settings.h" +#include "third_party/blink/renderer/core/frame/use_counter.h" +#include "third_party/blink/renderer/core/html/html_iframe_element.h" +#include "third_party/blink/renderer/core/loader/document_loader.h" +#include "third_party/blink/renderer/core/loader/empty_clients.h" +#include "third_party/blink/renderer/core/loader/subresource_filter.h" +#include "third_party/blink/renderer/core/page/page.h" +#include "third_party/blink/renderer/core/testing/dummy_page_holder.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/resource_loader_options.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource_request.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource_timing_info.h" +#include "third_party/blink/renderer/platform/loader/fetch/unique_identifier.h" +#include "third_party/blink/renderer/platform/loader/testing/mock_resource.h" +#include "third_party/blink/renderer/platform/network/network_state_notifier.h" +#include "third_party/blink/renderer/platform/testing/histogram_tester.h" +#include "third_party/blink/renderer/platform/weborigin/kurl.h" +#include "third_party/blink/renderer/platform/weborigin/security_violation_reporting_policy.h" + +namespace blink { + +using Checkpoint = testing::StrictMock<testing::MockFunction<void(int)>>; + +class StubLocalFrameClientWithParent final : public EmptyLocalFrameClient { + public: + static StubLocalFrameClientWithParent* Create(Frame* parent) { + return new StubLocalFrameClientWithParent(parent); + } + + void Trace(blink::Visitor* visitor) override { + visitor->Trace(parent_); + EmptyLocalFrameClient::Trace(visitor); + } + + Frame* Parent() const override { return parent_.Get(); } + + private: + explicit StubLocalFrameClientWithParent(Frame* parent) : parent_(parent) {} + + Member<Frame> parent_; +}; + +class FrameFetchContextMockLocalFrameClient : public EmptyLocalFrameClient { + public: + FrameFetchContextMockLocalFrameClient() : EmptyLocalFrameClient() {} + MOCK_METHOD0(DidDisplayContentWithCertificateErrors, void()); + MOCK_METHOD2(DispatchDidLoadResourceFromMemoryCache, + void(const ResourceRequest&, const ResourceResponse&)); + MOCK_METHOD0(UserAgent, String()); + MOCK_METHOD0(MayUseClientLoFiForImageRequests, bool()); + MOCK_CONST_METHOD0(GetPreviewsStateForFrame, WebURLRequest::PreviewsState()); +}; + +class FixedPolicySubresourceFilter : public WebDocumentSubresourceFilter { + public: + FixedPolicySubresourceFilter(LoadPolicy policy, + int* filtered_load_counter, + bool is_associated_with_ad_subframe) + : policy_(policy), + filtered_load_counter_(filtered_load_counter), + is_associated_with_ad_subframe_(is_associated_with_ad_subframe) {} + + LoadPolicy GetLoadPolicy(const WebURL& resource_url, + WebURLRequest::RequestContext) override { + return policy_; + } + + LoadPolicy GetLoadPolicyForWebSocketConnect(const WebURL& url) override { + return policy_; + } + + void ReportDisallowedLoad() override { ++*filtered_load_counter_; } + + bool ShouldLogToConsole() override { return false; } + + bool GetIsAssociatedWithAdSubframe() const override { + return is_associated_with_ad_subframe_; + } + + private: + const LoadPolicy policy_; + int* filtered_load_counter_; + bool is_associated_with_ad_subframe_; +}; + +class FrameFetchContextTest : public testing::Test { + protected: + void SetUp() override { RecreateFetchContext(); } + + void RecreateFetchContext() { + dummy_page_holder = DummyPageHolder::Create(IntSize(500, 500)); + dummy_page_holder->GetPage().SetDeviceScaleFactorDeprecated(1.0); + document = &dummy_page_holder->GetDocument(); + fetch_context = + static_cast<FrameFetchContext*>(&document->Fetcher()->Context()); + owner = DummyFrameOwner::Create(); + FrameFetchContext::ProvideDocumentToContext(*fetch_context, document.Get()); + } + + void TearDown() override { + if (child_frame) + child_frame->Detach(FrameDetachType::kRemove); + } + + FrameFetchContext* CreateChildFrame() { + child_client = StubLocalFrameClientWithParent::Create(document->GetFrame()); + child_frame = LocalFrame::Create( + child_client.Get(), *document->GetFrame()->GetPage(), owner.Get()); + child_frame->SetView( + LocalFrameView::Create(*child_frame, IntSize(500, 500))); + child_frame->Init(); + child_document = child_frame->GetDocument(); + FrameFetchContext* child_fetch_context = static_cast<FrameFetchContext*>( + &child_frame->Loader().GetDocumentLoader()->Fetcher()->Context()); + FrameFetchContext::ProvideDocumentToContext(*child_fetch_context, + child_document.Get()); + return child_fetch_context; + } + + // Call the method for the actual test cases as only this fixture is specified + // as a friend class. + void SetFirstPartyCookieAndRequestorOrigin(ResourceRequest& request) { + fetch_context->SetFirstPartyCookieAndRequestorOrigin(request); + } + + std::unique_ptr<DummyPageHolder> dummy_page_holder; + // We don't use the DocumentLoader directly in any tests, but need to keep it + // around as long as the ResourceFetcher and Document live due to indirect + // usage. + Persistent<Document> document; + Persistent<FrameFetchContext> fetch_context; + + Persistent<StubLocalFrameClientWithParent> child_client; + Persistent<LocalFrame> child_frame; + Persistent<Document> child_document; + Persistent<DummyFrameOwner> owner; +}; + +class FrameFetchContextSubresourceFilterTest : public FrameFetchContextTest { + protected: + void SetUp() override { + FrameFetchContextTest::SetUp(); + filtered_load_callback_counter_ = 0; + } + + void TearDown() override { + document->Loader()->SetSubresourceFilter(nullptr); + FrameFetchContextTest::TearDown(); + } + + int GetFilteredLoadCallCount() const { + return filtered_load_callback_counter_; + } + + void SetFilterPolicy(WebDocumentSubresourceFilter::LoadPolicy policy, + bool is_associated_with_ad_subframe = false) { + document->Loader()->SetSubresourceFilter(SubresourceFilter::Create( + *document, std::make_unique<FixedPolicySubresourceFilter>( + policy, &filtered_load_callback_counter_, + is_associated_with_ad_subframe))); + } + + ResourceRequestBlockedReason CanRequest() { + return CanRequestInternal(SecurityViolationReportingPolicy::kReport); + } + + ResourceRequestBlockedReason CanRequestPreload() { + return CanRequestInternal( + SecurityViolationReportingPolicy::kSuppressReporting); + } + + ResourceRequestBlockedReason CanRequestAndVerifyIsAd(bool expect_is_ad) { + ResourceRequestBlockedReason reason = + CanRequestInternal(SecurityViolationReportingPolicy::kReport); + const KURL url("http://example.com/"); + EXPECT_EQ(expect_is_ad, fetch_context->IsAdResource( + url, Resource::kMock, + WebURLRequest::kRequestContextUnspecified)); + return reason; + } + + bool DispatchWillSendRequestAndVerifyIsAd(const KURL& url) { + ResourceRequest request(url); + ResourceResponse response; + FetchInitiatorInfo initiator_info; + + fetch_context->DispatchWillSendRequest(1, request, response, + Resource::kImage, initiator_info); + return request.IsAdResource(); + } + + void AppendExecutingScriptToAdTracker(const String& url) { + AdTracker* ad_tracker = document->GetFrame()->GetAdTracker(); + ad_tracker->WillExecuteScript(url); + } + + void AppendAdScriptToAdTracker(const KURL& ad_script_url) { + AdTracker* ad_tracker = document->GetFrame()->GetAdTracker(); + ad_tracker->AppendToKnownAdScripts(ad_script_url); + } + + private: + ResourceRequestBlockedReason CanRequestInternal( + SecurityViolationReportingPolicy reporting_policy) { + const KURL input_url("http://example.com/"); + ResourceRequest resource_request(input_url); + resource_request.SetFetchCredentialsMode( + network::mojom::FetchCredentialsMode::kOmit); + ResourceLoaderOptions options; + return fetch_context->CanRequest( + Resource::kImage, resource_request, input_url, options, + reporting_policy, FetchParameters::kUseDefaultOriginRestrictionForType, + ResourceRequest::RedirectStatus::kNoRedirect); + } + + int filtered_load_callback_counter_; +}; + +// This test class sets up a mock frame loader client. +class FrameFetchContextMockedLocalFrameClientTest + : public FrameFetchContextTest { + protected: + void SetUp() override { + url = KURL("https://example.test/foo"); + http_url = KURL("http://example.test/foo"); + main_resource_url = KURL("https://example.test"); + different_host_url = KURL("https://different.example.test/foo"); + client = new testing::NiceMock<FrameFetchContextMockLocalFrameClient>(); + dummy_page_holder = + DummyPageHolder::Create(IntSize(500, 500), nullptr, client); + dummy_page_holder->GetPage().SetDeviceScaleFactorDeprecated(1.0); + document = &dummy_page_holder->GetDocument(); + document->SetURL(main_resource_url); + fetch_context = + static_cast<FrameFetchContext*>(&document->Fetcher()->Context()); + owner = DummyFrameOwner::Create(); + FrameFetchContext::ProvideDocumentToContext(*fetch_context, document.Get()); + } + + KURL url; + KURL http_url; + KURL main_resource_url; + KURL different_host_url; + + Persistent<testing::NiceMock<FrameFetchContextMockLocalFrameClient>> client; +}; + +class FrameFetchContextModifyRequestTest : public FrameFetchContextTest { + public: + FrameFetchContextModifyRequestTest() + : example_origin(SecurityOrigin::Create(KURL("https://example.test/"))), + secure_origin(SecurityOrigin::Create( + KURL("https://secureorigin.test/image.png"))) {} + + protected: + void ExpectUpgrade(const char* input, const char* expected) { + ExpectUpgrade(input, WebURLRequest::kRequestContextScript, + network::mojom::RequestContextFrameType::kNone, expected); + } + + void ExpectUpgrade(const char* input, + WebURLRequest::RequestContext request_context, + network::mojom::RequestContextFrameType frame_type, + const char* expected) { + const KURL input_url(input); + const KURL expected_url(expected); + + ResourceRequest resource_request(input_url); + resource_request.SetRequestContext(request_context); + resource_request.SetFrameType(frame_type); + + fetch_context->ModifyRequestForCSP(resource_request); + + EXPECT_EQ(expected_url.GetString(), resource_request.Url().GetString()); + EXPECT_EQ(expected_url.Protocol(), resource_request.Url().Protocol()); + EXPECT_EQ(expected_url.Host(), resource_request.Url().Host()); + EXPECT_EQ(expected_url.Port(), resource_request.Url().Port()); + EXPECT_EQ(expected_url.HasPort(), resource_request.Url().HasPort()); + EXPECT_EQ(expected_url.GetPath(), resource_request.Url().GetPath()); + } + + void ExpectUpgradeInsecureRequestHeader( + const char* input, + network::mojom::RequestContextFrameType frame_type, + bool should_prefer) { + const KURL input_url(input); + + ResourceRequest resource_request(input_url); + resource_request.SetRequestContext(WebURLRequest::kRequestContextScript); + resource_request.SetFrameType(frame_type); + + fetch_context->ModifyRequestForCSP(resource_request); + + EXPECT_EQ( + should_prefer ? String("1") : String(), + resource_request.HttpHeaderField(HTTPNames::Upgrade_Insecure_Requests)); + + // Calling modifyRequestForCSP more than once shouldn't affect the + // header. + if (should_prefer) { + fetch_context->ModifyRequestForCSP(resource_request); + EXPECT_EQ("1", resource_request.HttpHeaderField( + HTTPNames::Upgrade_Insecure_Requests)); + } + } + + void ExpectSetRequiredCSPRequestHeader( + const char* input, + network::mojom::RequestContextFrameType frame_type, + const AtomicString& expected_required_csp) { + const KURL input_url(input); + ResourceRequest resource_request(input_url); + resource_request.SetRequestContext(WebURLRequest::kRequestContextScript); + resource_request.SetFrameType(frame_type); + + fetch_context->ModifyRequestForCSP(resource_request); + + EXPECT_EQ(expected_required_csp, + resource_request.HttpHeaderField(HTTPNames::Sec_Required_CSP)); + } + + void SetFrameOwnerBasedOnFrameType( + network::mojom::RequestContextFrameType frame_type, + HTMLIFrameElement* iframe, + const AtomicString& potential_value) { + if (frame_type != network::mojom::RequestContextFrameType::kNested) { + document->GetFrame()->SetOwner(nullptr); + return; + } + + iframe->setAttribute(HTMLNames::cspAttr, potential_value); + document->GetFrame()->SetOwner(iframe); + } + + scoped_refptr<const SecurityOrigin> example_origin; + scoped_refptr<SecurityOrigin> secure_origin; +}; + +TEST_F(FrameFetchContextModifyRequestTest, UpgradeInsecureResourceRequests) { + struct TestCase { + const char* original; + const char* upgraded; + } tests[] = { + {"http://example.test/image.png", "https://example.test/image.png"}, + {"http://example.test:80/image.png", + "https://example.test:443/image.png"}, + {"http://example.test:1212/image.png", + "https://example.test:1212/image.png"}, + + {"https://example.test/image.png", "https://example.test/image.png"}, + {"https://example.test:80/image.png", + "https://example.test:80/image.png"}, + {"https://example.test:1212/image.png", + "https://example.test:1212/image.png"}, + + {"ftp://example.test/image.png", "ftp://example.test/image.png"}, + {"ftp://example.test:21/image.png", "ftp://example.test:21/image.png"}, + {"ftp://example.test:1212/image.png", + "ftp://example.test:1212/image.png"}, + }; + + FrameFetchContext::ProvideDocumentToContext(*fetch_context, document.Get()); + document->SetInsecureRequestPolicy(kUpgradeInsecureRequests); + + for (const auto& test : tests) { + document->InsecureNavigationsToUpgrade()->clear(); + + // We always upgrade for FrameTypeNone. + ExpectUpgrade(test.original, WebURLRequest::kRequestContextScript, + network::mojom::RequestContextFrameType::kNone, + test.upgraded); + + // We never upgrade for FrameTypeNested. This is done on the browser + // process. + ExpectUpgrade(test.original, WebURLRequest::kRequestContextScript, + network::mojom::RequestContextFrameType::kNested, + test.original); + + // We do not upgrade for FrameTypeTopLevel or FrameTypeAuxiliary... + ExpectUpgrade(test.original, WebURLRequest::kRequestContextScript, + network::mojom::RequestContextFrameType::kTopLevel, + test.original); + ExpectUpgrade(test.original, WebURLRequest::kRequestContextScript, + network::mojom::RequestContextFrameType::kAuxiliary, + test.original); + + // unless the request context is RequestContextForm. + ExpectUpgrade(test.original, WebURLRequest::kRequestContextForm, + network::mojom::RequestContextFrameType::kTopLevel, + test.upgraded); + ExpectUpgrade(test.original, WebURLRequest::kRequestContextForm, + network::mojom::RequestContextFrameType::kAuxiliary, + test.upgraded); + + // Or unless the host of the resource is in the document's + // InsecureNavigationsSet: + document->AddInsecureNavigationUpgrade( + example_origin->Host().Impl()->GetHash()); + ExpectUpgrade(test.original, WebURLRequest::kRequestContextScript, + network::mojom::RequestContextFrameType::kTopLevel, + test.upgraded); + ExpectUpgrade(test.original, WebURLRequest::kRequestContextScript, + network::mojom::RequestContextFrameType::kAuxiliary, + test.upgraded); + } +} + +TEST_F(FrameFetchContextModifyRequestTest, + DoNotUpgradeInsecureResourceRequests) { + FrameFetchContext::ProvideDocumentToContext(*fetch_context, document.Get()); + document->SetSecurityOrigin(secure_origin); + document->SetInsecureRequestPolicy(kLeaveInsecureRequestsAlone); + + ExpectUpgrade("http://example.test/image.png", + "http://example.test/image.png"); + ExpectUpgrade("http://example.test:80/image.png", + "http://example.test:80/image.png"); + ExpectUpgrade("http://example.test:1212/image.png", + "http://example.test:1212/image.png"); + + ExpectUpgrade("https://example.test/image.png", + "https://example.test/image.png"); + ExpectUpgrade("https://example.test:80/image.png", + "https://example.test:80/image.png"); + ExpectUpgrade("https://example.test:1212/image.png", + "https://example.test:1212/image.png"); + + ExpectUpgrade("ftp://example.test/image.png", "ftp://example.test/image.png"); + ExpectUpgrade("ftp://example.test:21/image.png", + "ftp://example.test:21/image.png"); + ExpectUpgrade("ftp://example.test:1212/image.png", + "ftp://example.test:1212/image.png"); +} + +TEST_F(FrameFetchContextModifyRequestTest, SendUpgradeInsecureRequestHeader) { + struct TestCase { + const char* to_request; + network::mojom::RequestContextFrameType frame_type; + bool should_prefer; + } tests[] = {{"http://example.test/page.html", + network::mojom::RequestContextFrameType::kAuxiliary, true}, + {"http://example.test/page.html", + network::mojom::RequestContextFrameType::kNested, true}, + {"http://example.test/page.html", + network::mojom::RequestContextFrameType::kNone, false}, + {"http://example.test/page.html", + network::mojom::RequestContextFrameType::kTopLevel, true}, + {"https://example.test/page.html", + network::mojom::RequestContextFrameType::kAuxiliary, true}, + {"https://example.test/page.html", + network::mojom::RequestContextFrameType::kNested, true}, + {"https://example.test/page.html", + network::mojom::RequestContextFrameType::kNone, false}, + {"https://example.test/page.html", + network::mojom::RequestContextFrameType::kTopLevel, true}}; + + // This should work correctly both when the FrameFetchContext has a Document, + // and when it doesn't (e.g. during main frame navigations), so run through + // the tests both before and after providing a document to the context. + for (const auto& test : tests) { + document->SetInsecureRequestPolicy(kLeaveInsecureRequestsAlone); + ExpectUpgradeInsecureRequestHeader(test.to_request, test.frame_type, + test.should_prefer); + + document->SetInsecureRequestPolicy(kUpgradeInsecureRequests); + ExpectUpgradeInsecureRequestHeader(test.to_request, test.frame_type, + test.should_prefer); + } + + FrameFetchContext::ProvideDocumentToContext(*fetch_context, document.Get()); + + for (const auto& test : tests) { + document->SetInsecureRequestPolicy(kLeaveInsecureRequestsAlone); + ExpectUpgradeInsecureRequestHeader(test.to_request, test.frame_type, + test.should_prefer); + + document->SetInsecureRequestPolicy(kUpgradeInsecureRequests); + ExpectUpgradeInsecureRequestHeader(test.to_request, test.frame_type, + test.should_prefer); + } +} + +TEST_F(FrameFetchContextModifyRequestTest, SendRequiredCSPHeader) { + struct TestCase { + const char* to_request; + network::mojom::RequestContextFrameType frame_type; + } tests[] = {{"https://example.test/page.html", + network::mojom::RequestContextFrameType::kAuxiliary}, + {"https://example.test/page.html", + network::mojom::RequestContextFrameType::kNested}, + {"https://example.test/page.html", + network::mojom::RequestContextFrameType::kNone}, + {"https://example.test/page.html", + network::mojom::RequestContextFrameType::kTopLevel}}; + + HTMLIFrameElement* iframe = HTMLIFrameElement::Create(*document); + const AtomicString& required_csp = AtomicString("default-src 'none'"); + const AtomicString& another_required_csp = AtomicString("default-src 'self'"); + + for (const auto& test : tests) { + SetFrameOwnerBasedOnFrameType(test.frame_type, iframe, required_csp); + ExpectSetRequiredCSPRequestHeader( + test.to_request, test.frame_type, + test.frame_type == network::mojom::RequestContextFrameType::kNested + ? required_csp + : g_null_atom); + + SetFrameOwnerBasedOnFrameType(test.frame_type, iframe, + another_required_csp); + ExpectSetRequiredCSPRequestHeader( + test.to_request, test.frame_type, + test.frame_type == network::mojom::RequestContextFrameType::kNested + ? another_required_csp + : g_null_atom); + } +} + +class FrameFetchContextHintsTest : public FrameFetchContextTest { + public: + FrameFetchContextHintsTest() = default; + + void SetUp() override { + FrameFetchContextTest::SetUp(); + // Set the document URL to a secure document. + document->SetURL(KURL("https://www.example.com/")); + document->SetSecurityOrigin( + SecurityOrigin::Create(KURL("https://www.example.com/"))); + Settings* settings = document->GetSettings(); + settings->SetScriptEnabled(true); + } + + protected: + void ExpectHeader(const char* input, + const char* header_name, + bool is_present, + const char* header_value, + float width = 0) { + ClientHintsPreferences hints_preferences; + + FetchParameters::ResourceWidth resource_width; + if (width > 0) { + resource_width.width = width; + resource_width.is_set = true; + } + + const KURL input_url(input); + ResourceRequest resource_request(input_url); + + fetch_context->AddClientHintsIfNecessary(hints_preferences, resource_width, + resource_request); + + EXPECT_EQ(is_present ? String(header_value) : String(), + resource_request.HttpHeaderField(header_name)); + } + + String GetHeaderValue(const char* input, const char* header_name) { + ClientHintsPreferences hints_preferences; + FetchParameters::ResourceWidth resource_width; + const KURL input_url(input); + ResourceRequest resource_request(input_url); + fetch_context->AddClientHintsIfNecessary(hints_preferences, resource_width, + resource_request); + return resource_request.HttpHeaderField(header_name); + } +}; + +// Verify that the client hints should be attached for subresources fetched +// over secure transport. Tests when the persistent client hint feature is +// enabled. +TEST_F(FrameFetchContextHintsTest, MonitorDeviceMemorySecureTransport) { + ExpectHeader("https://www.example.com/1.gif", "Device-Memory", false, ""); + ClientHintsPreferences preferences; + preferences.SetShouldSendForTesting(mojom::WebClientHintsType::kDeviceMemory); + document->GetClientHintsPreferences().UpdateFrom(preferences); + ApproximatedDeviceMemory::SetPhysicalMemoryMBForTesting(4096); + ExpectHeader("https://www.example.com/1.gif", "Device-Memory", true, "4"); + ExpectHeader("https://www.example.com/1.gif", "DPR", false, ""); + ExpectHeader("https://www.example.com/1.gif", "Width", false, ""); + ExpectHeader("https://www.example.com/1.gif", "Viewport-Width", false, ""); + // The origin of the resource does not match the origin of the main frame + // resource. Client hint should not be attached. + ExpectHeader("https://www.someother-example.com/1.gif", "Device-Memory", + false, ""); +} + +// Verify that the client hints should be attached for subresources fetched +// over secure transport. Tests when the persistent client hint feature is not +// enabled. +TEST_F(FrameFetchContextHintsTest, + MonitorDeviceMemorySecureTransportPersistentHintsDisabled) { + WebRuntimeFeatures::EnableClientHintsPersistent(false); + ExpectHeader("https://www.example.com/1.gif", "Device-Memory", false, ""); + ClientHintsPreferences preferences; + preferences.SetShouldSendForTesting(mojom::WebClientHintsType::kDeviceMemory); + document->GetClientHintsPreferences().UpdateFrom(preferences); + ApproximatedDeviceMemory::SetPhysicalMemoryMBForTesting(4096); + ExpectHeader("https://www.example.com/1.gif", "Device-Memory", true, "4"); + ExpectHeader("https://www.example.com/1.gif", "DPR", false, ""); + ExpectHeader("https://www.example.com/1.gif", "Width", false, ""); + ExpectHeader("https://www.example.com/1.gif", "Viewport-Width", false, ""); + // The origin of the resource does not match the origin of the main frame + // resource. Client hint should be attached since the persisten client hint + // feature is not enabled. + ExpectHeader("https://www.someother-example.com/1.gif", "Device-Memory", true, + "4"); +} + +// Verify that client hints are not attched when the resources do not belong to +// a secure context. +TEST_F(FrameFetchContextHintsTest, MonitorDeviceMemoryHintsInsecureContext) { + WebRuntimeFeatures::EnableClientHintsPersistent(false); + ExpectHeader("http://www.example.com/1.gif", "Device-Memory", false, ""); + + { + ClientHintsPreferences preferences; + preferences.SetShouldSendForTesting( + mojom::WebClientHintsType::kDeviceMemory); + document->GetClientHintsPreferences().UpdateFrom(preferences); + ApproximatedDeviceMemory::SetPhysicalMemoryMBForTesting(4096); + ExpectHeader("http://www.example.com/1.gif", "Device-Memory", true, "4"); + ExpectHeader("http://www.example.com/1.gif", "DPR", false, ""); + ExpectHeader("http://www.example.com/1.gif", "Width", false, ""); + ExpectHeader("http://www.example.com/1.gif", "Viewport-Width", false, ""); + } + + { + // Verify that client hints are not attched when the resources do not belong + // to a secure context and the persistent client hint features is enabled. + WebRuntimeFeatures::EnableClientHintsPersistent(true); + ExpectHeader("http://www.example.com/1.gif", "Device-Memory", false, ""); + ClientHintsPreferences preferences; + preferences.SetShouldSendForTesting( + mojom::WebClientHintsType::kDeviceMemory); + document->GetClientHintsPreferences().UpdateFrom(preferences); + ApproximatedDeviceMemory::SetPhysicalMemoryMBForTesting(4096); + ExpectHeader("http://www.example.com/1.gif", "Device-Memory", false, ""); + ExpectHeader("http://www.example.com/1.gif", "DPR", false, ""); + ExpectHeader("http://www.example.com/1.gif", "Width", false, ""); + ExpectHeader("http://www.example.com/1.gif", "Viewport-Width", false, ""); + } +} + +// Verify that client hints are attched when the resources belong to a local +// context. +TEST_F(FrameFetchContextHintsTest, MonitorDeviceMemoryHintsLocalContext) { + document->SetURL(KURL("http://localhost/")); + document->SetSecurityOrigin( + SecurityOrigin::Create(KURL("http://localhost/"))); + ExpectHeader("http://localhost/1.gif", "Device-Memory", false, ""); + ClientHintsPreferences preferences; + preferences.SetShouldSendForTesting(mojom::WebClientHintsType::kDeviceMemory); + document->GetClientHintsPreferences().UpdateFrom(preferences); + ApproximatedDeviceMemory::SetPhysicalMemoryMBForTesting(4096); + ExpectHeader("http://localhost/1.gif", "Device-Memory", true, "4"); + ExpectHeader("http://localhost/1.gif", "DPR", false, ""); + ExpectHeader("http://localhost/1.gif", "Width", false, ""); + ExpectHeader("http://localhost/1.gif", "Viewport-Width", false, ""); +} + +TEST_F(FrameFetchContextHintsTest, MonitorDeviceMemoryHints) { + ExpectHeader("https://www.example.com/1.gif", "Device-Memory", false, ""); + ClientHintsPreferences preferences; + preferences.SetShouldSendForTesting(mojom::WebClientHintsType::kDeviceMemory); + document->GetClientHintsPreferences().UpdateFrom(preferences); + ApproximatedDeviceMemory::SetPhysicalMemoryMBForTesting(4096); + ExpectHeader("https://www.example.com/1.gif", "Device-Memory", true, "4"); + ApproximatedDeviceMemory::SetPhysicalMemoryMBForTesting(2048); + ExpectHeader("https://www.example.com/1.gif", "Device-Memory", true, "2"); + ApproximatedDeviceMemory::SetPhysicalMemoryMBForTesting(64385); + ExpectHeader("https://www.example.com/1.gif", "Device-Memory", true, "8"); + ApproximatedDeviceMemory::SetPhysicalMemoryMBForTesting(768); + ExpectHeader("https://www.example.com/1.gif", "Device-Memory", true, "0.5"); + ExpectHeader("https://www.example.com/1.gif", "DPR", false, ""); + ExpectHeader("https://www.example.com/1.gif", "Width", false, ""); + ExpectHeader("https://www.example.com/1.gif", "Viewport-Width", false, ""); +} + +TEST_F(FrameFetchContextHintsTest, MonitorDPRHints) { + ExpectHeader("https://www.example.com/1.gif", "DPR", false, ""); + ClientHintsPreferences preferences; + preferences.SetShouldSendForTesting(mojom::WebClientHintsType::kDpr); + document->GetClientHintsPreferences().UpdateFrom(preferences); + ExpectHeader("https://www.example.com/1.gif", "DPR", true, "1"); + dummy_page_holder->GetPage().SetDeviceScaleFactorDeprecated(2.5); + ExpectHeader("https://www.example.com/1.gif", "DPR", true, "2.5"); + ExpectHeader("https://www.example.com/1.gif", "Width", false, ""); + ExpectHeader("https://www.example.com/1.gif", "Viewport-Width", false, ""); +} + +TEST_F(FrameFetchContextHintsTest, MonitorDPRHintsInsecureTransport) { + WebRuntimeFeatures::EnableClientHintsPersistent(false); + ExpectHeader("http://www.example.com/1.gif", "DPR", false, ""); + + { + ClientHintsPreferences preferences; + preferences.SetShouldSendForTesting(mojom::WebClientHintsType::kDpr); + document->GetClientHintsPreferences().UpdateFrom(preferences); + ExpectHeader("http://www.example.com/1.gif", "DPR", true, "1"); + dummy_page_holder->GetPage().SetDeviceScaleFactorDeprecated(2.5); + ExpectHeader("http://www.example.com/1.gif", "DPR", true, "2.5"); + ExpectHeader("http://www.example.com/1.gif", "Width", false, ""); + ExpectHeader("http://www.example.com/1.gif", "Viewport-Width", false, ""); + } + + { + WebRuntimeFeatures::EnableClientHintsPersistent(true); + ExpectHeader("http://www.example.com/1.gif", "DPR", false, ""); + dummy_page_holder->GetPage().SetDeviceScaleFactorDeprecated(2.5); + ExpectHeader("http://www.example.com/1.gif", "DPR", false, " "); + ExpectHeader("http://www.example.com/1.gif", "Width", false, ""); + ExpectHeader("http://www.example.com/1.gif", "Viewport-Width", false, ""); + } +} + +TEST_F(FrameFetchContextHintsTest, MonitorResourceWidthHints) { + ExpectHeader("https://www.example.com/1.gif", "Width", false, ""); + ClientHintsPreferences preferences; + preferences.SetShouldSendForTesting( + mojom::WebClientHintsType::kResourceWidth); + document->GetClientHintsPreferences().UpdateFrom(preferences); + ExpectHeader("https://www.example.com/1.gif", "Width", true, "500", 500); + ExpectHeader("https://www.example.com/1.gif", "Width", true, "667", 666.6666); + ExpectHeader("https://www.example.com/1.gif", "DPR", false, ""); + dummy_page_holder->GetPage().SetDeviceScaleFactorDeprecated(2.5); + ExpectHeader("https://www.example.com/1.gif", "Width", true, "1250", 500); + ExpectHeader("https://www.example.com/1.gif", "Width", true, "1667", + 666.6666); +} + +TEST_F(FrameFetchContextHintsTest, MonitorViewportWidthHints) { + ExpectHeader("https://www.example.com/1.gif", "Viewport-Width", false, ""); + ClientHintsPreferences preferences; + preferences.SetShouldSendForTesting( + mojom::WebClientHintsType::kViewportWidth); + document->GetClientHintsPreferences().UpdateFrom(preferences); + ExpectHeader("https://www.example.com/1.gif", "Viewport-Width", true, "500"); + dummy_page_holder->GetFrameView().SetLayoutSizeFixedToFrameSize(false); + dummy_page_holder->GetFrameView().SetLayoutSize(IntSize(800, 800)); + ExpectHeader("https://www.example.com/1.gif", "Viewport-Width", true, "800"); + ExpectHeader("https://www.example.com/1.gif", "Viewport-Width", true, "800", + 666.6666); + ExpectHeader("https://www.example.com/1.gif", "DPR", false, ""); +} + +TEST_F(FrameFetchContextHintsTest, MonitorAllHints) { + ExpectHeader("https://www.example.com/1.gif", "Device-Memory", false, ""); + ExpectHeader("https://www.example.com/1.gif", "DPR", false, ""); + ExpectHeader("https://www.example.com/1.gif", "Viewport-Width", false, ""); + ExpectHeader("https://www.example.com/1.gif", "Width", false, ""); + ExpectHeader("https://www.example.com/1.gif", "rtt", false, ""); + ExpectHeader("https://www.example.com/1.gif", "downlink", false, ""); + ExpectHeader("https://www.example.com/1.gif", "ect", false, ""); + + ClientHintsPreferences preferences; + preferences.SetShouldSendForTesting(mojom::WebClientHintsType::kDeviceMemory); + preferences.SetShouldSendForTesting(mojom::WebClientHintsType::kDpr); + preferences.SetShouldSendForTesting( + mojom::WebClientHintsType::kResourceWidth); + preferences.SetShouldSendForTesting( + mojom::WebClientHintsType::kViewportWidth); + preferences.SetShouldSendForTesting(mojom::WebClientHintsType::kRtt); + preferences.SetShouldSendForTesting(mojom::WebClientHintsType::kDownlink); + preferences.SetShouldSendForTesting(mojom::WebClientHintsType::kEct); + ApproximatedDeviceMemory::SetPhysicalMemoryMBForTesting(4096); + document->GetClientHintsPreferences().UpdateFrom(preferences); + ExpectHeader("https://www.example.com/1.gif", "Device-Memory", true, "4"); + ExpectHeader("https://www.example.com/1.gif", "DPR", true, "1"); + ExpectHeader("https://www.example.com/1.gif", "Width", true, "400", 400); + ExpectHeader("https://www.example.com/1.gif", "Viewport-Width", true, "500"); + + // Value of network quality client hints may vary, so only check if the + // header is present and the values are non-negative/non-empty. + bool conversion_ok = false; + int rtt_header_value = GetHeaderValue("https://www.example.com/1.gif", "rtt") + .ToIntStrict(&conversion_ok); + EXPECT_TRUE(conversion_ok); + EXPECT_LE(0, rtt_header_value); + + float downlink_header_value = + GetHeaderValue("https://www.example.com/1.gif", "downlink") + .ToFloat(&conversion_ok); + EXPECT_TRUE(conversion_ok); + EXPECT_LE(0, downlink_header_value); + + EXPECT_LT( + 0u, + GetHeaderValue("https://www.example.com/1.gif", "ect").Ascii().length()); +} + +TEST_F(FrameFetchContextTest, MainResourceCachePolicy) { + // Default case + ResourceRequest request("http://www.example.com"); + EXPECT_EQ(mojom::FetchCacheMode::kDefault, + fetch_context->ResourceRequestCachePolicy( + request, Resource::kMainResource, FetchParameters::kNoDefer)); + + // Post + ResourceRequest post_request("http://www.example.com"); + post_request.SetHTTPMethod(HTTPNames::POST); + EXPECT_EQ( + mojom::FetchCacheMode::kValidateCache, + fetch_context->ResourceRequestCachePolicy( + post_request, Resource::kMainResource, FetchParameters::kNoDefer)); + + // Re-post + document->Loader()->SetLoadType(kFrameLoadTypeBackForward); + EXPECT_EQ( + mojom::FetchCacheMode::kOnlyIfCached, + fetch_context->ResourceRequestCachePolicy( + post_request, Resource::kMainResource, FetchParameters::kNoDefer)); + + // FrameLoadTypeReload + document->Loader()->SetLoadType(kFrameLoadTypeReload); + EXPECT_EQ(mojom::FetchCacheMode::kValidateCache, + fetch_context->ResourceRequestCachePolicy( + request, Resource::kMainResource, FetchParameters::kNoDefer)); + + // Conditional request + document->Loader()->SetLoadType(kFrameLoadTypeStandard); + ResourceRequest conditional("http://www.example.com"); + conditional.SetHTTPHeaderField(HTTPNames::If_Modified_Since, "foo"); + EXPECT_EQ( + mojom::FetchCacheMode::kValidateCache, + fetch_context->ResourceRequestCachePolicy( + conditional, Resource::kMainResource, FetchParameters::kNoDefer)); + + // FrameLoadTypeReloadBypassingCache + document->Loader()->SetLoadType(kFrameLoadTypeReloadBypassingCache); + EXPECT_EQ(mojom::FetchCacheMode::kBypassCache, + fetch_context->ResourceRequestCachePolicy( + request, Resource::kMainResource, FetchParameters::kNoDefer)); + + // FrameLoadTypeReloadBypassingCache with a conditional request + document->Loader()->SetLoadType(kFrameLoadTypeReloadBypassingCache); + EXPECT_EQ( + mojom::FetchCacheMode::kBypassCache, + fetch_context->ResourceRequestCachePolicy( + conditional, Resource::kMainResource, FetchParameters::kNoDefer)); + + // FrameLoadTypeReloadBypassingCache with a post request + document->Loader()->SetLoadType(kFrameLoadTypeReloadBypassingCache); + EXPECT_EQ( + mojom::FetchCacheMode::kBypassCache, + fetch_context->ResourceRequestCachePolicy( + post_request, Resource::kMainResource, FetchParameters::kNoDefer)); + + // Set up a child frame + FrameFetchContext* child_fetch_context = CreateChildFrame(); + + // Child frame as part of back/forward + document->Loader()->SetLoadType(kFrameLoadTypeBackForward); + EXPECT_EQ(mojom::FetchCacheMode::kForceCache, + child_fetch_context->ResourceRequestCachePolicy( + request, Resource::kMainResource, FetchParameters::kNoDefer)); + + // Child frame as part of reload + document->Loader()->SetLoadType(kFrameLoadTypeReload); + EXPECT_EQ(mojom::FetchCacheMode::kDefault, + child_fetch_context->ResourceRequestCachePolicy( + request, Resource::kMainResource, FetchParameters::kNoDefer)); + + // Child frame as part of reload bypassing cache + document->Loader()->SetLoadType(kFrameLoadTypeReloadBypassingCache); + EXPECT_EQ(mojom::FetchCacheMode::kBypassCache, + child_fetch_context->ResourceRequestCachePolicy( + request, Resource::kMainResource, FetchParameters::kNoDefer)); + + // Per-frame bypassing reload, but parent load type is different. + // This is not the case users can trigger through user interfaces, but for + // checking code correctness and consistency. + document->Loader()->SetLoadType(kFrameLoadTypeReload); + child_frame->Loader().GetDocumentLoader()->SetLoadType( + kFrameLoadTypeReloadBypassingCache); + EXPECT_EQ(mojom::FetchCacheMode::kBypassCache, + child_fetch_context->ResourceRequestCachePolicy( + request, Resource::kMainResource, FetchParameters::kNoDefer)); +} + +TEST_F(FrameFetchContextTest, SubResourceCachePolicy) { + // Reset load event state: if the load event is finished, we ignore the + // DocumentLoader load type. + document->open(); + ASSERT_FALSE(document->LoadEventFinished()); + + // Default case + ResourceRequest request("http://www.example.com/mock"); + EXPECT_EQ(mojom::FetchCacheMode::kDefault, + fetch_context->ResourceRequestCachePolicy( + request, Resource::kMock, FetchParameters::kNoDefer)); + + // FrameLoadTypeReload should not affect sub-resources + document->Loader()->SetLoadType(kFrameLoadTypeReload); + EXPECT_EQ(mojom::FetchCacheMode::kDefault, + fetch_context->ResourceRequestCachePolicy( + request, Resource::kMock, FetchParameters::kNoDefer)); + + // Conditional request + document->Loader()->SetLoadType(kFrameLoadTypeStandard); + ResourceRequest conditional("http://www.example.com/mock"); + conditional.SetHTTPHeaderField(HTTPNames::If_Modified_Since, "foo"); + EXPECT_EQ(mojom::FetchCacheMode::kValidateCache, + fetch_context->ResourceRequestCachePolicy( + conditional, Resource::kMock, FetchParameters::kNoDefer)); + + // FrameLoadTypeReloadBypassingCache + document->Loader()->SetLoadType(kFrameLoadTypeReloadBypassingCache); + EXPECT_EQ(mojom::FetchCacheMode::kBypassCache, + fetch_context->ResourceRequestCachePolicy( + request, Resource::kMock, FetchParameters::kNoDefer)); + + // FrameLoadTypeReloadBypassingCache with a conditional request + document->Loader()->SetLoadType(kFrameLoadTypeReloadBypassingCache); + EXPECT_EQ(mojom::FetchCacheMode::kBypassCache, + fetch_context->ResourceRequestCachePolicy( + conditional, Resource::kMock, FetchParameters::kNoDefer)); + + // Back/forward navigation + document->Loader()->SetLoadType(kFrameLoadTypeBackForward); + EXPECT_EQ(mojom::FetchCacheMode::kForceCache, + fetch_context->ResourceRequestCachePolicy( + request, Resource::kMock, FetchParameters::kNoDefer)); + + // Back/forward navigation with a conditional request + document->Loader()->SetLoadType(kFrameLoadTypeBackForward); + EXPECT_EQ(mojom::FetchCacheMode::kForceCache, + fetch_context->ResourceRequestCachePolicy( + conditional, Resource::kMock, FetchParameters::kNoDefer)); +} + +TEST_F(FrameFetchContextTest, SetFirstPartyCookieAndRequestorOrigin) { + struct TestCase { + const char* document_url; + bool document_sandboxed; + const char* requestor_origin; // "" => null + network::mojom::RequestContextFrameType frame_type; + const char* serialized_origin; // "" => null + } cases[] = { + // No document origin => unique request origin + {"", false, "", network::mojom::RequestContextFrameType::kNone, "null"}, + {"", true, "", network::mojom::RequestContextFrameType::kNone, "null"}, + + // Document origin => request origin + {"http://example.test", false, "", + network::mojom::RequestContextFrameType::kNone, "http://example.test"}, + {"http://example.test", true, "", + network::mojom::RequestContextFrameType::kNone, "http://example.test"}, + + // If the request already has a requestor origin, then + // 'SetFirstPartyCookieAndRequestorOrigin' leaves it alone: + {"http://example.test", false, "http://not-example.test", + network::mojom::RequestContextFrameType::kNone, + "http://not-example.test"}, + {"http://example.test", true, "http://not-example.test", + network::mojom::RequestContextFrameType::kNone, + "http://not-example.test"}, + }; + + int index = 0; + for (const auto& test : cases) { + SCOPED_TRACE(testing::Message() << index++ << " " << test.document_url + << " => " << test.serialized_origin); + // Set up a new document to ensure sandbox flags are cleared: + dummy_page_holder = DummyPageHolder::Create(IntSize(500, 500)); + dummy_page_holder->GetPage().SetDeviceScaleFactorDeprecated(1.0); + document = &dummy_page_holder->GetDocument(); + fetch_context = + static_cast<FrameFetchContext*>(&document->Fetcher()->Context()); + FrameFetchContext::ProvideDocumentToContext(*fetch_context, document.Get()); + + // Setup the test: + document->SetURL(KURL(test.document_url)); + document->SetSecurityOrigin(SecurityOrigin::Create(document->Url())); + + if (test.document_sandboxed) + document->EnforceSandboxFlags(kSandboxOrigin); + + ResourceRequest request("http://example.test/"); + request.SetFrameType(test.frame_type); + if (strlen(test.requestor_origin) > 0) { + request.SetRequestorOrigin( + SecurityOrigin::Create(KURL(test.requestor_origin))); + } + + // Compare the populated |requestorOrigin| against |test.serializedOrigin| + SetFirstPartyCookieAndRequestorOrigin(request); + if (strlen(test.serialized_origin) == 0) { + EXPECT_TRUE(!request.RequestorOrigin()); + } else { + EXPECT_EQ(String(test.serialized_origin), + request.RequestorOrigin()->ToString()); + } + + EXPECT_EQ(document->SiteForCookies(), request.SiteForCookies()); + } +} + +TEST_F(FrameFetchContextTest, ModifyPriorityForLowPriorityIframes) { + Settings* settings = document->GetSettings(); + FrameFetchContext* childFetchContext = CreateChildFrame(); + GetNetworkStateNotifier().SetNetworkConnectionInfoOverride( + true, WebConnectionType::kWebConnectionTypeCellular3G, + WebEffectiveConnectionType::kType3G, 1 /* http_rtt_msec */, + 10.0 /* max_bandwidth_mbps */); + + // Experiment is not enabled, expect default values. + EXPECT_EQ(ResourceLoadPriority::kVeryHigh, + fetch_context->ModifyPriorityForExperiments( + ResourceLoadPriority::kVeryHigh)); + EXPECT_EQ(ResourceLoadPriority::kVeryHigh, + childFetchContext->ModifyPriorityForExperiments( + ResourceLoadPriority::kVeryHigh)); + EXPECT_EQ(ResourceLoadPriority::kMedium, + childFetchContext->ModifyPriorityForExperiments( + ResourceLoadPriority::kMedium)); + + // Low priority iframes enabled but network is not slow enough. Expect default + // values. + settings->SetLowPriorityIframesThreshold(WebEffectiveConnectionType::kType2G); + EXPECT_EQ(ResourceLoadPriority::kVeryHigh, + fetch_context->ModifyPriorityForExperiments( + ResourceLoadPriority::kVeryHigh)); + EXPECT_EQ(ResourceLoadPriority::kVeryHigh, + childFetchContext->ModifyPriorityForExperiments( + ResourceLoadPriority::kVeryHigh)); + EXPECT_EQ(ResourceLoadPriority::kMedium, + childFetchContext->ModifyPriorityForExperiments( + ResourceLoadPriority::kMedium)); + + // Low priority iframes enabled and network is slow, main frame request's + // priorities should not change. + GetNetworkStateNotifier().SetNetworkConnectionInfoOverride( + true, WebConnectionType::kWebConnectionTypeCellular3G, + WebEffectiveConnectionType::kType2G, 1 /* http_rtt_msec */, + 10.0 /* max_bandwidth_mbps */); + EXPECT_EQ(ResourceLoadPriority::kVeryHigh, + fetch_context->ModifyPriorityForExperiments( + ResourceLoadPriority::kVeryHigh)); + // Low priority iframes enabled, everything in child frame should be low + // priority. + EXPECT_EQ(ResourceLoadPriority::kLow, + childFetchContext->ModifyPriorityForExperiments( + ResourceLoadPriority::kVeryHigh)); + EXPECT_EQ(ResourceLoadPriority::kVeryLow, + childFetchContext->ModifyPriorityForExperiments( + ResourceLoadPriority::kMedium)); +} + +// Tests if "Save-Data" header is correctly added on the first load and reload. +TEST_F(FrameFetchContextTest, EnableDataSaver) { + GetNetworkStateNotifier().SetSaveDataEnabledOverride(true); + // Recreate the fetch context so that the updated save data settings are read. + RecreateFetchContext(); + + ResourceRequest resource_request("http://www.example.com"); + fetch_context->AddAdditionalRequestHeaders(resource_request, + kFetchMainResource); + EXPECT_EQ("on", resource_request.HttpHeaderField("Save-Data")); + + // Subsequent call to addAdditionalRequestHeaders should not append to the + // save-data header. + fetch_context->AddAdditionalRequestHeaders(resource_request, + kFetchMainResource); + EXPECT_EQ("on", resource_request.HttpHeaderField("Save-Data")); +} + +// Tests if "Save-Data" header is not added when the data saver is disabled. +TEST_F(FrameFetchContextTest, DisabledDataSaver) { + GetNetworkStateNotifier().SetSaveDataEnabledOverride(false); + // Recreate the fetch context so that the updated save data settings are read. + RecreateFetchContext(); + + ResourceRequest resource_request("http://www.example.com"); + fetch_context->AddAdditionalRequestHeaders(resource_request, + kFetchMainResource); + EXPECT_EQ(String(), resource_request.HttpHeaderField("Save-Data")); +} + +// Tests if reload variants can reflect the current data saver setting. +TEST_F(FrameFetchContextTest, ChangeDataSaverConfig) { + GetNetworkStateNotifier().SetSaveDataEnabledOverride(true); + // Recreate the fetch context so that the updated save data settings are read. + RecreateFetchContext(); + ResourceRequest resource_request("http://www.example.com"); + fetch_context->AddAdditionalRequestHeaders(resource_request, + kFetchMainResource); + EXPECT_EQ("on", resource_request.HttpHeaderField("Save-Data")); + + GetNetworkStateNotifier().SetSaveDataEnabledOverride(false); + RecreateFetchContext(); + document->Loader()->SetLoadType(kFrameLoadTypeReload); + fetch_context->AddAdditionalRequestHeaders(resource_request, + kFetchMainResource); + EXPECT_EQ(String(), resource_request.HttpHeaderField("Save-Data")); + + GetNetworkStateNotifier().SetSaveDataEnabledOverride(true); + RecreateFetchContext(); + fetch_context->AddAdditionalRequestHeaders(resource_request, + kFetchMainResource); + EXPECT_EQ("on", resource_request.HttpHeaderField("Save-Data")); + + GetNetworkStateNotifier().SetSaveDataEnabledOverride(false); + RecreateFetchContext(); + document->Loader()->SetLoadType(kFrameLoadTypeReload); + fetch_context->AddAdditionalRequestHeaders(resource_request, + kFetchMainResource); + EXPECT_EQ(String(), resource_request.HttpHeaderField("Save-Data")); +} + +// Tests that the embedder gets correct notification when a resource is loaded +// from the memory cache. +TEST_F(FrameFetchContextMockedLocalFrameClientTest, + DispatchDidLoadResourceFromMemoryCache) { + ResourceRequest resource_request(url); + resource_request.SetRequestContext(WebURLRequest::kRequestContextImage); + resource_request.SetFetchCredentialsMode( + network::mojom::FetchCredentialsMode::kOmit); + Resource* resource = MockResource::Create(resource_request); + EXPECT_CALL( + *client, + DispatchDidLoadResourceFromMemoryCache( + testing::AllOf( + testing::Property(&ResourceRequest::Url, url), + testing::Property(&ResourceRequest::GetFrameType, + network::mojom::RequestContextFrameType::kNone), + testing::Property(&ResourceRequest::GetRequestContext, + WebURLRequest::kRequestContextImage)), + ResourceResponse())); + fetch_context->DispatchDidLoadResourceFromMemoryCache( + CreateUniqueIdentifier(), resource_request, resource->GetResponse()); +} + +// Tests that the client hints lifetime header is parsed correctly only when the +// frame belongs to a secure context. +// TODO(lunalu): remove this test when blink side use counter is removed +// (crbug.com/811948). +TEST_F(FrameFetchContextMockedLocalFrameClientTest, + PersistClientHintsSecureContext) { + HistogramTester histogram_tester; + + { + ASSERT_EQ(url.Host(), main_resource_url.Host()); + ASSERT_EQ("https", url.Protocol()); + ResourceRequest resource_request(url); + resource_request.SetRequestContext(WebURLRequest::kRequestContextImage); + resource_request.SetFetchCredentialsMode( + network::mojom::FetchCredentialsMode::kOmit); + ResourceResponse response(url); + response.SetHTTPHeaderField("accept-ch", "dpr"); + response.SetHTTPHeaderField("accept-ch-lifetime", "3600"); + Resource* resource = MockResource::Create(resource_request); + resource->SetResponse(response); + fetch_context->DispatchDidReceiveResponse( + CreateUniqueIdentifier(), response, resource_request.GetFrameType(), + resource_request.GetRequestContext(), resource, + FetchContext::ResourceResponseType::kNotFromMemoryCache); + + histogram_tester.ExpectBucketCount( + "Blink.UseCounter.Features_Legacy", + static_cast<int>(WebFeature::kPersistentClientHintHeader), 1); + } + + { + // Try with a different resource that has a different origin than the main + // frame. + ASSERT_NE(different_host_url.Host(), main_resource_url.Host()); + ASSERT_EQ("https", different_host_url.Protocol()); + ResourceRequest resource_request(different_host_url); + resource_request.SetRequestContext(WebURLRequest::kRequestContextImage); + resource_request.SetFetchCredentialsMode( + network::mojom::FetchCredentialsMode::kOmit); + ResourceResponse response(different_host_url); + response.SetHTTPHeaderField("accept-ch", "dpr"); + response.SetHTTPHeaderField("accept-ch-lifetime", "3600"); + Resource* resource = MockResource::Create(resource_request); + resource->SetResponse(response); + fetch_context->DispatchDidReceiveResponse( + CreateUniqueIdentifier(), response, resource_request.GetFrameType(), + resource_request.GetRequestContext(), resource, + FetchContext::ResourceResponseType::kNotFromMemoryCache); + + // There should not be a change in the usage count. + histogram_tester.ExpectBucketCount( + "Blink.UseCounter.Features_Legacy", + static_cast<int>(WebFeature::kPersistentClientHintHeader), 1); + } + + { + // Next, try with a HTTP URL. + ASSERT_EQ(http_url.Host(), main_resource_url.Host()); + ASSERT_EQ("http", http_url.Protocol()); + ResourceRequest resource_request(http_url); + resource_request.SetRequestContext(WebURLRequest::kRequestContextImage); + resource_request.SetFetchCredentialsMode( + network::mojom::FetchCredentialsMode::kOmit); + + ResourceResponse response(http_url); + response.SetHTTPHeaderField("accept-ch", "dpr"); + response.SetHTTPHeaderField("accept-ch-lifetime", "3600"); + Resource* resource = MockResource::Create(resource_request); + resource->SetResponse(response); + fetch_context->DispatchDidReceiveResponse( + CreateUniqueIdentifier(), response, resource_request.GetFrameType(), + resource_request.GetRequestContext(), resource, + FetchContext::ResourceResponseType::kNotFromMemoryCache); + + // There should not be a change in the usage count. + histogram_tester.ExpectBucketCount( + "Blink.UseCounter.Features_Legacy", + static_cast<int>(WebFeature::kPersistentClientHintHeader), 1); + } +} + +// Tests that when a resource with certificate errors is loaded from the memory +// cache, the embedder is notified. +TEST_F(FrameFetchContextMockedLocalFrameClientTest, + MemoryCacheCertificateError) { + ResourceRequest resource_request(url); + resource_request.SetRequestContext(WebURLRequest::kRequestContextImage); + resource_request.SetFetchCredentialsMode( + network::mojom::FetchCredentialsMode::kOmit); + ResourceResponse response(url); + response.SetHasMajorCertificateErrors(true); + Resource* resource = MockResource::Create(resource_request); + resource->SetResponse(response); + EXPECT_CALL(*client, DidDisplayContentWithCertificateErrors()); + fetch_context->DispatchDidLoadResourceFromMemoryCache( + CreateUniqueIdentifier(), resource_request, resource->GetResponse()); +} + +TEST_F(FrameFetchContextSubresourceFilterTest, Filter) { + SetFilterPolicy(WebDocumentSubresourceFilter::kDisallow); + + EXPECT_EQ(ResourceRequestBlockedReason::kSubresourceFilter, + CanRequestAndVerifyIsAd(true)); + EXPECT_EQ(1, GetFilteredLoadCallCount()); + + EXPECT_EQ(ResourceRequestBlockedReason::kSubresourceFilter, + CanRequestAndVerifyIsAd(true)); + EXPECT_EQ(2, GetFilteredLoadCallCount()); + + EXPECT_EQ(ResourceRequestBlockedReason::kSubresourceFilter, + CanRequestPreload()); + EXPECT_EQ(2, GetFilteredLoadCallCount()); + + EXPECT_EQ(ResourceRequestBlockedReason::kSubresourceFilter, + CanRequestAndVerifyIsAd(true)); + EXPECT_EQ(3, GetFilteredLoadCallCount()); +} + +TEST_F(FrameFetchContextSubresourceFilterTest, Allow) { + SetFilterPolicy(WebDocumentSubresourceFilter::kAllow); + + EXPECT_EQ(ResourceRequestBlockedReason::kNone, + CanRequestAndVerifyIsAd(false)); + EXPECT_EQ(0, GetFilteredLoadCallCount()); + + EXPECT_EQ(ResourceRequestBlockedReason::kNone, CanRequestPreload()); + EXPECT_EQ(0, GetFilteredLoadCallCount()); +} + +TEST_F(FrameFetchContextSubresourceFilterTest, WouldDisallow) { + SetFilterPolicy(WebDocumentSubresourceFilter::kWouldDisallow); + + EXPECT_EQ(ResourceRequestBlockedReason::kNone, CanRequestAndVerifyIsAd(true)); + EXPECT_EQ(0, GetFilteredLoadCallCount()); + + EXPECT_EQ(ResourceRequestBlockedReason::kNone, CanRequestPreload()); + EXPECT_EQ(0, GetFilteredLoadCallCount()); +} + +// Tests that if a subresource is allowed as per subresource filter ruleset but +// is fetched from a frame that is tagged as an ad, then the subresource should +// be tagged as well. +TEST_F(FrameFetchContextSubresourceFilterTest, AdTaggingBasedOnFrame) { + SetFilterPolicy(WebDocumentSubresourceFilter::kAllow, + true /* is_associated_with_ad_subframe */); + + EXPECT_EQ(ResourceRequestBlockedReason::kNone, CanRequestAndVerifyIsAd(true)); + EXPECT_EQ(0, GetFilteredLoadCallCount()); +} + +// Tests that if a subresource is allowed as per subresource filter ruleset and +// is not fetched from a frame that is tagged as an ad, then the subresource +// should be tagged as ad if one of the executing scripts is tagged as an ad. +TEST_F(FrameFetchContextSubresourceFilterTest, + AdTaggingBasedOnExecutingScript) { + SetFilterPolicy(WebDocumentSubresourceFilter::kAllow, + false /* is_associated_with_ad_subframe */); + + KURL ad_script_url("https://example.com/bar.js"); + AppendAdScriptToAdTracker(ad_script_url); + AppendExecutingScriptToAdTracker(ad_script_url.GetString()); + + EXPECT_EQ(ResourceRequestBlockedReason::kNone, + CanRequestAndVerifyIsAd(false)); + EXPECT_EQ(0, GetFilteredLoadCallCount()); + + // After WillSendRequest probe, it should be marked as an ad. + EXPECT_TRUE(DispatchWillSendRequestAndVerifyIsAd( + KURL("https://www.example.com/image.jpg"))); +} + +TEST_F(FrameFetchContextTest, AddAdditionalRequestHeadersWhenDetached) { + const KURL document_url("https://www2.example.com/fuga/hoge.html"); + const String origin = "https://www2.example.com"; + ResourceRequest request(KURL("https://localhost/")); + request.SetHTTPMethod("PUT"); + + GetNetworkStateNotifier().SetSaveDataEnabledOverride(true); + document->SetSecurityOrigin(SecurityOrigin::Create(KURL(origin))); + document->SetURL(document_url); + document->SetReferrerPolicy(kReferrerPolicyOrigin); + document->SetAddressSpace(mojom::IPAddressSpace::kPublic); + + dummy_page_holder = nullptr; + + fetch_context->AddAdditionalRequestHeaders(request, kFetchSubresource); + + EXPECT_EQ(origin, request.HttpHeaderField(HTTPNames::Origin)); + EXPECT_EQ(String(origin + "/"), request.HttpHeaderField(HTTPNames::Referer)); + EXPECT_EQ(String(), request.HttpHeaderField("Save-Data")); +} + +TEST_F(FrameFetchContextTest, ResourceRequestCachePolicyWhenDetached) { + ResourceRequest request(KURL("https://localhost/")); + + dummy_page_holder = nullptr; + + EXPECT_EQ(mojom::FetchCacheMode::kDefault, + fetch_context->ResourceRequestCachePolicy( + request, Resource::kRaw, FetchParameters::kNoDefer)); +} + +TEST_F(FrameFetchContextTest, DispatchDidChangePriorityWhenDetached) { + dummy_page_holder = nullptr; + + fetch_context->DispatchDidChangeResourcePriority( + 2, ResourceLoadPriority::kLow, 3); + // Should not crash. +} + +TEST_F(FrameFetchContextMockedLocalFrameClientTest, + PrepareRequestWhenDetached) { + Checkpoint checkpoint; + + EXPECT_CALL(checkpoint, Call(1)); + EXPECT_CALL(*client, UserAgent()).WillOnce(testing::Return(String("hi"))); + EXPECT_CALL(checkpoint, Call(2)); + + checkpoint.Call(1); + dummy_page_holder = nullptr; + checkpoint.Call(2); + + ResourceRequest request(KURL("https://localhost/")); + fetch_context->PrepareRequest(request, + FetchContext::RedirectType::kNotForRedirect); + + EXPECT_EQ("hi", request.HttpHeaderField(HTTPNames::User_Agent)); +} + +TEST_F(FrameFetchContextTest, DispatchWillSendRequestWhenDetached) { + ResourceRequest request(KURL("https://www.example.com/")); + ResourceResponse response; + FetchInitiatorInfo initiator_info; + + dummy_page_holder = nullptr; + + fetch_context->DispatchWillSendRequest(1, request, response, Resource::kRaw, + initiator_info); + // Should not crash. +} + +TEST_F(FrameFetchContextTest, + DispatchDidLoadResourceFromMemoryCacheWhenDetached) { + ResourceRequest request(KURL("https://www.example.com/")); + ResourceResponse response; + FetchInitiatorInfo initiator_info; + + dummy_page_holder = nullptr; + + fetch_context->DispatchDidLoadResourceFromMemoryCache(8, request, response); + // Should not crash. +} + +TEST_F(FrameFetchContextTest, DispatchDidReceiveResponseWhenDetached) { + ResourceRequest request(KURL("https://www.example.com/")); + request.SetFetchCredentialsMode(network::mojom::FetchCredentialsMode::kOmit); + Resource* resource = MockResource::Create(request); + ResourceResponse response; + + dummy_page_holder = nullptr; + + fetch_context->DispatchDidReceiveResponse( + 3, response, network::mojom::RequestContextFrameType::kTopLevel, + WebURLRequest::kRequestContextFetch, resource, + FetchContext::ResourceResponseType::kNotFromMemoryCache); + // Should not crash. +} + +TEST_F(FrameFetchContextTest, DispatchDidReceiveDataWhenDetached) { + dummy_page_holder = nullptr; + + fetch_context->DispatchDidReceiveData(3, "abcd", 4); + // Should not crash. +} + +TEST_F(FrameFetchContextTest, DispatchDidReceiveEncodedDataWhenDetached) { + dummy_page_holder = nullptr; + + fetch_context->DispatchDidReceiveEncodedData(8, 9); + // Should not crash. +} + +TEST_F(FrameFetchContextTest, DispatchDidDownloadDataWhenDetached) { + dummy_page_holder = nullptr; + + fetch_context->DispatchDidDownloadData(4, 7, 9); + // Should not crash. +} + +TEST_F(FrameFetchContextTest, DispatchDidFinishLoadingWhenDetached) { + dummy_page_holder = nullptr; + + fetch_context->DispatchDidFinishLoading(4, 0.3, 8, 10, false); + // Should not crash. +} + +TEST_F(FrameFetchContextTest, DispatchDidFailWhenDetached) { + dummy_page_holder = nullptr; + + fetch_context->DispatchDidFail(KURL(), 8, ResourceError::Failure(NullURL()), + 5, false); + // Should not crash. +} + +TEST_F(FrameFetchContextTest, ShouldLoadNewResourceWhenDetached) { + dummy_page_holder = nullptr; + + EXPECT_FALSE(fetch_context->ShouldLoadNewResource(Resource::kImage)); + EXPECT_FALSE(fetch_context->ShouldLoadNewResource(Resource::kRaw)); + EXPECT_FALSE(fetch_context->ShouldLoadNewResource(Resource::kScript)); + EXPECT_FALSE(fetch_context->ShouldLoadNewResource(Resource::kMainResource)); +} + +TEST_F(FrameFetchContextTest, RecordLoadingActivityWhenDetached) { + ResourceRequest request(KURL("https://www.example.com/")); + + dummy_page_holder = nullptr; + + fetch_context->RecordLoadingActivity(request, Resource::kRaw, + FetchInitiatorTypeNames::xmlhttprequest); + // Should not crash. + + fetch_context->RecordLoadingActivity(request, Resource::kRaw, + FetchInitiatorTypeNames::document); + // Should not crash. +} + +TEST_F(FrameFetchContextTest, DidLoadResourceWhenDetached) { + ResourceRequest request(KURL("https://www.example.com/")); + request.SetFetchCredentialsMode(network::mojom::FetchCredentialsMode::kOmit); + Resource* resource = MockResource::Create(request); + + dummy_page_holder = nullptr; + + fetch_context->DidLoadResource(resource); + // Should not crash. +} + +TEST_F(FrameFetchContextTest, AddResourceTimingWhenDetached) { + scoped_refptr<ResourceTimingInfo> info = + ResourceTimingInfo::Create("type", 0.3, false); + + dummy_page_holder = nullptr; + + fetch_context->AddResourceTiming(*info); + // Should not crash. +} + +TEST_F(FrameFetchContextTest, AllowImageWhenDetached) { + const KURL url("https://www.example.com/"); + + dummy_page_holder = nullptr; + + EXPECT_TRUE(fetch_context->AllowImage(true, url)); + EXPECT_TRUE(fetch_context->AllowImage(false, url)); +} + +TEST_F(FrameFetchContextTest, IsControlledByServiceWorkerWhenDetached) { + dummy_page_holder = nullptr; + + EXPECT_FALSE(fetch_context->IsControlledByServiceWorker()); +} + +TEST_F(FrameFetchContextTest, IsMainFrameWhenDetached) { + FetchContext* child_fetch_context = CreateChildFrame(); + + EXPECT_TRUE(fetch_context->IsMainFrame()); + EXPECT_FALSE(child_fetch_context->IsMainFrame()); + + dummy_page_holder = nullptr; + + EXPECT_TRUE(fetch_context->IsMainFrame()); + EXPECT_FALSE(child_fetch_context->IsMainFrame()); +} + +TEST_F(FrameFetchContextTest, DefersLoadingWhenDetached) { + EXPECT_FALSE(fetch_context->DefersLoading()); +} + +TEST_F(FrameFetchContextTest, IsLoadCompleteWhenDetached_1) { + document->open(); + EXPECT_FALSE(fetch_context->IsLoadComplete()); + + dummy_page_holder = nullptr; + + EXPECT_TRUE(fetch_context->IsLoadComplete()); +} + +TEST_F(FrameFetchContextTest, IsLoadCompleteWhenDetached_2) { + EXPECT_TRUE(fetch_context->IsLoadComplete()); + + dummy_page_holder = nullptr; + + EXPECT_TRUE(fetch_context->IsLoadComplete()); +} + +TEST_F(FrameFetchContextTest, UpdateTimingInfoForIFrameNavigationWhenDetached) { + scoped_refptr<ResourceTimingInfo> info = + ResourceTimingInfo::Create("type", 0.3, false); + + dummy_page_holder = nullptr; + + fetch_context->UpdateTimingInfoForIFrameNavigation(info.get()); + // Should not crash. +} + +TEST_F(FrameFetchContextTest, AddConsoleMessageWhenDetached) { + dummy_page_holder = nullptr; + + fetch_context->AddWarningConsoleMessage("foobar", FetchContext::kJSSource); + // Should not crash. +} + +TEST_F(FrameFetchContextTest, GetSecurityOriginWhenDetached) { + scoped_refptr<SecurityOrigin> origin = + SecurityOrigin::Create(KURL("https://www.example.com")); + document->SetSecurityOrigin(origin); + + dummy_page_holder = nullptr; + EXPECT_EQ(origin.get(), fetch_context->GetSecurityOrigin()); +} + +TEST_F(FrameFetchContextTest, PopulateResourceRequestWhenDetached) { + const KURL url("https://www.example.com/"); + ResourceRequest request(url); + request.SetFetchCredentialsMode(network::mojom::FetchCredentialsMode::kOmit); + + ClientHintsPreferences client_hints_preferences; + client_hints_preferences.SetShouldSendForTesting( + mojom::WebClientHintsType::kDeviceMemory); + client_hints_preferences.SetShouldSendForTesting( + mojom::WebClientHintsType::kDpr); + client_hints_preferences.SetShouldSendForTesting( + mojom::WebClientHintsType::kResourceWidth); + client_hints_preferences.SetShouldSendForTesting( + mojom::WebClientHintsType::kViewportWidth); + + FetchParameters::ResourceWidth resource_width; + ResourceLoaderOptions options; + + document->GetClientHintsPreferences().SetShouldSendForTesting( + mojom::WebClientHintsType::kDeviceMemory); + document->GetClientHintsPreferences().SetShouldSendForTesting( + mojom::WebClientHintsType::kDpr); + document->GetClientHintsPreferences().SetShouldSendForTesting( + mojom::WebClientHintsType::kResourceWidth); + document->GetClientHintsPreferences().SetShouldSendForTesting( + mojom::WebClientHintsType::kViewportWidth); + + dummy_page_holder = nullptr; + + fetch_context->PopulateResourceRequest( + Resource::kRaw, client_hints_preferences, resource_width, request); + // Should not crash. +} + +TEST_F(FrameFetchContextTest, + SetFirstPartyCookieAndRequestorOriginWhenDetached) { + const KURL url("https://www.example.com/hoge/fuga"); + ResourceRequest request(url); + const KURL document_url("https://www2.example.com/foo/bar"); + scoped_refptr<SecurityOrigin> origin = SecurityOrigin::Create(document_url); + + document->SetSecurityOrigin(origin); + document->SetURL(document_url); + + dummy_page_holder = nullptr; + + SetFirstPartyCookieAndRequestorOrigin(request); + + EXPECT_EQ(document_url, request.SiteForCookies()); + EXPECT_EQ(origin, request.RequestorOrigin()); +} + +TEST_F(FrameFetchContextTest, ArchiveWhenDetached) { + FetchContext* child_fetch_context = CreateChildFrame(); + + dummy_page_holder = nullptr; + child_frame->Detach(FrameDetachType::kRemove); + child_frame = nullptr; + + EXPECT_EQ(nullptr, child_fetch_context->Archive()); +} + +// Tests if "Intervention" header is added for frame with Client Lo-Fi enabled. +TEST_F(FrameFetchContextMockedLocalFrameClientTest, + ClientLoFiInterventionHeader) { + // Verify header not added if Lo-Fi not active. + EXPECT_CALL(*client, GetPreviewsStateForFrame()) + .WillRepeatedly(testing::Return(WebURLRequest::kPreviewsOff)); + ResourceRequest resource_request("http://www.example.com/style.css"); + fetch_context->AddAdditionalRequestHeaders(resource_request, + kFetchMainResource); + EXPECT_EQ(g_null_atom, resource_request.HttpHeaderField("Intervention")); + + // Verify header is added if Lo-Fi is active. + EXPECT_CALL(*client, GetPreviewsStateForFrame()) + .WillRepeatedly(testing::Return(WebURLRequest::kClientLoFiOn)); + fetch_context->AddAdditionalRequestHeaders(resource_request, + kFetchSubresource); + EXPECT_EQ( + "<https://www.chromestatus.com/features/6072546726248448>; " + "level=\"warning\"", + resource_request.HttpHeaderField("Intervention")); + + // Verify appended to an existing "Intervention" header value. + ResourceRequest resource_request2("http://www.example.com/getad.js"); + resource_request2.SetHTTPHeaderField("Intervention", + "<https://otherintervention.org>"); + fetch_context->AddAdditionalRequestHeaders(resource_request2, + kFetchSubresource); + EXPECT_EQ( + "<https://otherintervention.org>, " + "<https://www.chromestatus.com/features/6072546726248448>; " + "level=\"warning\"", + resource_request2.HttpHeaderField("Intervention")); +} + +// Tests if "Intervention" header is added for frame with NoScript enabled. +TEST_F(FrameFetchContextMockedLocalFrameClientTest, + NoScriptInterventionHeader) { + // Verify header not added if NoScript not active. + EXPECT_CALL(*client, GetPreviewsStateForFrame()) + .WillRepeatedly(testing::Return(WebURLRequest::kPreviewsOff)); + ResourceRequest resource_request("http://www.example.com/style.css"); + fetch_context->AddAdditionalRequestHeaders(resource_request, + kFetchMainResource); + EXPECT_EQ(g_null_atom, resource_request.HttpHeaderField("Intervention")); + + // Verify header is added if NoScript is active. + EXPECT_CALL(*client, GetPreviewsStateForFrame()) + .WillRepeatedly(testing::Return(WebURLRequest::kNoScriptOn)); + fetch_context->AddAdditionalRequestHeaders(resource_request, + kFetchSubresource); + EXPECT_EQ( + "<https://www.chromestatus.com/features/4775088607985664>; " + "level=\"warning\"", + resource_request.HttpHeaderField("Intervention")); + + // Verify appended to an existing "Intervention" header value. + ResourceRequest resource_request2("http://www.example.com/getad.js"); + resource_request2.SetHTTPHeaderField("Intervention", + "<https://otherintervention.org>"); + fetch_context->AddAdditionalRequestHeaders(resource_request2, + kFetchSubresource); + EXPECT_EQ( + "<https://otherintervention.org>, " + "<https://www.chromestatus.com/features/4775088607985664>; " + "level=\"warning\"", + resource_request2.HttpHeaderField("Intervention")); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/loader/frame_load_request.cc b/chromium/third_party/blink/renderer/core/loader/frame_load_request.cc new file mode 100644 index 00000000000..878cec12e4e --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/frame_load_request.cc @@ -0,0 +1,108 @@ +// 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/frame_load_request.h" + +#include "third_party/blink/public/platform/web_url_request.h" +#include "third_party/blink/renderer/core/fileapi/public_url_manager.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource_request.h" +#include "third_party/blink/renderer/platform/wtf/text/atomic_string.h" + +namespace blink { + +FrameLoadRequest::FrameLoadRequest(Document* origin_document) + : FrameLoadRequest(origin_document, ResourceRequest()) {} + +FrameLoadRequest::FrameLoadRequest(Document* origin_document, + const ResourceRequest& resource_request) + : FrameLoadRequest(origin_document, resource_request, AtomicString()) {} + +FrameLoadRequest::FrameLoadRequest(Document* origin_document, + const ResourceRequest& resource_request, + const AtomicString& frame_name) + : FrameLoadRequest(origin_document, + resource_request, + frame_name, + kCheckContentSecurityPolicy, + base::UnguessableToken::Create()) {} + +FrameLoadRequest::FrameLoadRequest(Document* origin_document, + const ResourceRequest& resource_request, + const SubstituteData& substitute_data) + : FrameLoadRequest(origin_document, + resource_request, + AtomicString(), + substitute_data, + kCheckContentSecurityPolicy, + base::UnguessableToken::Create()) {} + +FrameLoadRequest::FrameLoadRequest( + Document* origin_document, + const ResourceRequest& resource_request, + const AtomicString& frame_name, + ContentSecurityPolicyDisposition + should_check_main_world_content_security_policy) + : FrameLoadRequest(origin_document, + resource_request, + frame_name, + should_check_main_world_content_security_policy, + base::UnguessableToken::Create()) {} + +FrameLoadRequest::FrameLoadRequest( + Document* origin_document, + const ResourceRequest& resource_request, + const AtomicString& frame_name, + ContentSecurityPolicyDisposition + should_check_main_world_content_security_policy, + const base::UnguessableToken& devtools_navigation_token) + : FrameLoadRequest(origin_document, + resource_request, + frame_name, + SubstituteData(), + should_check_main_world_content_security_policy, + devtools_navigation_token) {} + +FrameLoadRequest::FrameLoadRequest( + Document* origin_document, + const ResourceRequest& resource_request, + const AtomicString& frame_name, + const SubstituteData& substitute_data, + ContentSecurityPolicyDisposition + should_check_main_world_content_security_policy, + const base::UnguessableToken& devtools_navigation_token) + : origin_document_(origin_document), + resource_request_(resource_request), + frame_name_(frame_name), + substitute_data_(substitute_data), + replaces_current_item_(false), + client_redirect_(ClientRedirectPolicy::kNotClientRedirect), + should_send_referrer_(kMaybeSendReferrer), + should_set_opener_(kMaybeSetOpener), + should_check_main_world_content_security_policy_( + should_check_main_world_content_security_policy), + devtools_navigation_token_(devtools_navigation_token) { + // These flags are passed to a service worker which controls the page. + resource_request_.SetFetchRequestMode( + network::mojom::FetchRequestMode::kNavigate); + resource_request_.SetFetchCredentialsMode( + network::mojom::FetchCredentialsMode::kInclude); + resource_request_.SetFetchRedirectMode( + network::mojom::FetchRedirectMode::kManual); + + if (origin_document) { + DCHECK(!resource_request_.RequestorOrigin()); + resource_request_.SetRequestorOrigin( + SecurityOrigin::Create(origin_document->Url())); + + if (resource_request.Url().ProtocolIs("blob") && + RuntimeEnabledFeatures::MojoBlobURLsEnabled()) { + blob_url_token_ = base::MakeRefCounted< + base::RefCountedData<mojom::blink::BlobURLTokenPtr>>(); + origin_document->GetPublicURLManager().Resolve( + resource_request.Url(), MakeRequest(&blob_url_token_->data)); + } + } +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/loader/frame_load_request.h b/chromium/third_party/blink/renderer/core/loader/frame_load_request.h new file mode 100644 index 00000000000..ff502218a33 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/frame_load_request.h @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2003, 2006, 2010 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_FRAME_LOAD_REQUEST_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_FRAME_LOAD_REQUEST_H_ + +#include "base/unguessable_token.h" +#include "third_party/blink/renderer/core/dom/document.h" +#include "third_party/blink/renderer/core/dom/events/event.h" +#include "third_party/blink/renderer/core/loader/frame_loader_types.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource_loader_options.h" +#include "third_party/blink/renderer/platform/loader/fetch/substitute_data.h" + +namespace blink { + +class HTMLFormElement; +class ResourceRequest; +class SubstituteData; + +struct CORE_EXPORT FrameLoadRequest { + STACK_ALLOCATED(); + + public: + explicit FrameLoadRequest(Document* origin_document); + FrameLoadRequest(Document* origin_document, const ResourceRequest&); + FrameLoadRequest(Document* origin_document, + const ResourceRequest&, + const AtomicString& frame_name); + FrameLoadRequest(Document* origin_document, + const ResourceRequest&, + const SubstituteData&); + FrameLoadRequest(Document* origin_document, + const ResourceRequest&, + const AtomicString& frame_name, + ContentSecurityPolicyDisposition); + FrameLoadRequest(Document* origin_document, + const ResourceRequest&, + const AtomicString& frame_name, + ContentSecurityPolicyDisposition, + const base::UnguessableToken& devtools_navigation_token); + + Document* OriginDocument() const { return origin_document_.Get(); } + + ResourceRequest& GetResourceRequest() { return resource_request_; } + const ResourceRequest& GetResourceRequest() const { + return resource_request_; + } + + const AtomicString& FrameName() const { return frame_name_; } + void SetFrameName(const AtomicString& frame_name) { + frame_name_ = frame_name; + } + + const SubstituteData& GetSubstituteData() const { return substitute_data_; } + + bool ReplacesCurrentItem() const { return replaces_current_item_; } + void SetReplacesCurrentItem(bool replaces_current_item) { + replaces_current_item_ = replaces_current_item; + } + + ClientRedirectPolicy ClientRedirect() const { return client_redirect_; } + void SetClientRedirect(ClientRedirectPolicy client_redirect) { + client_redirect_ = client_redirect; + } + + Event* TriggeringEvent() const { return triggering_event_.Get(); } + void SetTriggeringEvent(Event* triggering_event) { + triggering_event_ = triggering_event; + } + + HTMLFormElement* Form() const { return form_.Get(); } + void SetForm(HTMLFormElement* form) { form_ = form; } + + ShouldSendReferrer GetShouldSendReferrer() const { + return should_send_referrer_; + } + void SetShouldSendReferrer(ShouldSendReferrer should_send_referrer) { + should_send_referrer_ = should_send_referrer; + } + + ShouldSetOpener GetShouldSetOpener() const { return should_set_opener_; } + void SetShouldSetOpener(ShouldSetOpener should_set_opener) { + should_set_opener_ = should_set_opener; + } + + ContentSecurityPolicyDisposition ShouldCheckMainWorldContentSecurityPolicy() + const { + return should_check_main_world_content_security_policy_; + } + + // See DocumentLoader::devtools_navigation_token_ for documentation. + const base::UnguessableToken& GetDevToolsNavigationToken() const { + return devtools_navigation_token_; + } + + // Sets the BlobURLToken that should be used when fetching the resource. This + // is needed for blob URLs, because the blob URL might be revoked before the + // actual fetch happens, which would result in incorrect failures to fetch. + // The token lets the browser process securely resolves the blob URL even + // after the url has been revoked. + // FrameFetchRequest initializes this in its constructor, but in some cases + // FrameFetchRequest is created asynchronously rather than when a navigation + // is scheduled, so in those cases NavigationScheduler needs to override the + // blob FrameLoadRequest might have found. + void SetBlobURLToken(mojom::blink::BlobURLTokenPtr blob_url_token) { + DCHECK(blob_url_token); + blob_url_token_ = base::MakeRefCounted< + base::RefCountedData<mojom::blink::BlobURLTokenPtr>>( + std::move(blob_url_token)); + } + + mojom::blink::BlobURLTokenPtr GetBlobURLToken() const { + if (!blob_url_token_) + return nullptr; + mojom::blink::BlobURLTokenPtr result; + blob_url_token_->data->Clone(MakeRequest(&result)); + return result; + } + + private: + FrameLoadRequest(Document* origin_document, + const ResourceRequest&, + const AtomicString& frame_name, + const SubstituteData&, + ContentSecurityPolicyDisposition, + const base::UnguessableToken& devtools_navigation_token); + + Member<Document> origin_document_; + ResourceRequest resource_request_; + AtomicString frame_name_; + SubstituteData substitute_data_; + bool replaces_current_item_; + ClientRedirectPolicy client_redirect_; + Member<Event> triggering_event_; + Member<HTMLFormElement> form_; + ShouldSendReferrer should_send_referrer_; + ShouldSetOpener should_set_opener_; + ContentSecurityPolicyDisposition + should_check_main_world_content_security_policy_; + base::UnguessableToken devtools_navigation_token_; + scoped_refptr<base::RefCountedData<mojom::blink::BlobURLTokenPtr>> + blob_url_token_; +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_FRAME_LOAD_REQUEST_H_ diff --git a/chromium/third_party/blink/renderer/core/loader/frame_loader.cc b/chromium/third_party/blink/renderer/core/loader/frame_loader.cc new file mode 100644 index 00000000000..ceb5c16fcf4 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/frame_loader.cc @@ -0,0 +1,1889 @@ +/* + * Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011 Apple Inc. All rights + * reserved. + * Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies) + * Copyright (C) 2008, 2009 Torch Mobile Inc. All rights reserved. + * (http://www.torchmobile.com/) + * Copyright (C) 2008 Alp Toker <alp@atoker.com> + * Copyright (C) Research In Motion Limited 2009. All rights reserved. + * Copyright (C) 2011 Kris Jordan <krisjordan@gmail.com> + * Copyright (C) 2011 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: + * + * 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") 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 APPLE AND ITS 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 APPLE OR ITS 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/frame_loader.h" + +#include <memory> +#include "services/network/public/mojom/request_context_frame_type.mojom-blink.h" +#include "third_party/blink/public/platform/modules/fetch/fetch_api_request.mojom-shared.h" +#include "third_party/blink/public/platform/modules/serviceworker/web_service_worker_network_provider.h" +#include "third_party/blink/public/platform/task_type.h" +#include "third_party/blink/public/platform/web_url_request.h" +#include "third_party/blink/public/web/web_frame_load_type.h" +#include "third_party/blink/public/web/web_history_item.h" +#include "third_party/blink/renderer/bindings/core/v8/script_controller.h" +#include "third_party/blink/renderer/bindings/core/v8/serialization/serialized_script_value.h" +#include "third_party/blink/renderer/core/dom/document.h" +#include "third_party/blink/renderer/core/dom/element.h" +#include "third_party/blink/renderer/core/dom/events/event.h" +#include "third_party/blink/renderer/core/dom/viewport_description.h" +#include "third_party/blink/renderer/core/events/gesture_event.h" +#include "third_party/blink/renderer/core/events/keyboard_event.h" +#include "third_party/blink/renderer/core/events/mouse_event.h" +#include "third_party/blink/renderer/core/events/page_transition_event.h" +#include "third_party/blink/renderer/core/frame/content_settings_client.h" +#include "third_party/blink/renderer/core/frame/csp/content_security_policy.h" +#include "third_party/blink/renderer/core/frame/local_dom_window.h" +#include "third_party/blink/renderer/core/frame/local_frame.h" +#include "third_party/blink/renderer/core/frame/local_frame_client.h" +#include "third_party/blink/renderer/core/frame/local_frame_view.h" +#include "third_party/blink/renderer/core/frame/settings.h" +#include "third_party/blink/renderer/core/frame/visual_viewport.h" +#include "third_party/blink/renderer/core/html/forms/html_form_element.h" +#include "third_party/blink/renderer/core/html/html_frame_owner_element.h" +#include "third_party/blink/renderer/core/html_names.h" +#include "third_party/blink/renderer/core/input/event_handler.h" +#include "third_party/blink/renderer/core/inspector/console_message.h" +#include "third_party/blink/renderer/core/inspector/identifiers_factory.h" +#include "third_party/blink/renderer/core/loader/appcache/application_cache_host.h" +#include "third_party/blink/renderer/core/loader/document_load_timing.h" +#include "third_party/blink/renderer/core/loader/document_loader.h" +#include "third_party/blink/renderer/core/loader/form_submission.h" +#include "third_party/blink/renderer/core/loader/frame_load_request.h" +#include "third_party/blink/renderer/core/loader/link_loader.h" +#include "third_party/blink/renderer/core/loader/navigation_scheduler.h" +#include "third_party/blink/renderer/core/loader/network_hints_interface.h" +#include "third_party/blink/renderer/core/loader/progress_tracker.h" +#include "third_party/blink/renderer/core/page/chrome_client.h" +#include "third_party/blink/renderer/core/page/create_window.h" +#include "third_party/blink/renderer/core/page/frame_tree.h" +#include "third_party/blink/renderer/core/page/page.h" +#include "third_party/blink/renderer/core/page/scrolling/scrolling_coordinator.h" +#include "third_party/blink/renderer/core/probe/core_probes.h" +#include "third_party/blink/renderer/core/svg/graphics/svg_image.h" +#include "third_party/blink/renderer/core/xml/parser/xml_document_parser.h" +#include "third_party/blink/renderer/platform/bindings/dom_wrapper_world.h" +#include "third_party/blink/renderer/platform/bindings/script_forbidden_scope.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/resource_fetcher.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource_request.h" +#include "third_party/blink/renderer/platform/network/http_parsers.h" +#include "third_party/blink/renderer/platform/network/network_utils.h" +#include "third_party/blink/renderer/platform/plugins/plugin_script_forbidden_scope.h" +#include "third_party/blink/renderer/platform/scheduler/public/frame_scheduler.h" +#include "third_party/blink/renderer/platform/scroll/scroll_animator_base.h" +#include "third_party/blink/renderer/platform/weborigin/scheme_registry.h" +#include "third_party/blink/renderer/platform/weborigin/security_origin.h" +#include "third_party/blink/renderer/platform/weborigin/security_policy.h" +#include "third_party/blink/renderer/platform/wtf/assertions.h" +#include "third_party/blink/renderer/platform/wtf/auto_reset.h" +#include "third_party/blink/renderer/platform/wtf/text/cstring.h" +#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h" + +using blink::WebURLRequest; + +namespace blink { + +using namespace HTMLNames; + +bool IsBackForwardLoadType(FrameLoadType type) { + return type == kFrameLoadTypeBackForward || + type == kFrameLoadTypeInitialHistoryLoad; +} + +bool IsReloadLoadType(FrameLoadType type) { + return type == kFrameLoadTypeReload || + type == kFrameLoadTypeReloadBypassingCache; +} + +static bool NeedsHistoryItemRestore(FrameLoadType type) { + // FrameLoadtypeInitialHistoryLoad is intentionally excluded. + return type == kFrameLoadTypeBackForward || IsReloadLoadType(type); +} + +static NavigationPolicy MaybeCheckCSP( + const ResourceRequest& request, + NavigationType type, + LocalFrame* frame, + NavigationPolicy policy, + bool should_check_main_world_content_security_policy, + bool browser_side_navigation_enabled, + ContentSecurityPolicy::CheckHeaderType check_header_type) { + // If we're loading content into |frame| (NavigationPolicyCurrentTab), check + // against the parent's Content Security Policy and kill the load if that + // check fails, unless we should bypass the main world's CSP. + if (policy == kNavigationPolicyCurrentTab && + should_check_main_world_content_security_policy && + // TODO(arthursonzogni): 'frame-src' check is disabled on the + // renderer side with browser-side-navigation, but is enforced on the + // browser side. See http://crbug.com/692595 for understanding why it + // can't be enforced on both sides instead. + !browser_side_navigation_enabled) { + Frame* parent_frame = frame->Tree().Parent(); + if (parent_frame) { + ContentSecurityPolicy* parent_policy = + parent_frame->GetSecurityContext()->GetContentSecurityPolicy(); + if (!parent_policy->AllowFrameFromSource( + request.Url(), request.GetRedirectStatus(), + SecurityViolationReportingPolicy::kReport, check_header_type)) { + // Fire a load event, as timing attacks would otherwise reveal that the + // frame was blocked. This way, it looks like every other cross-origin + // page load. + frame->GetDocument()->EnforceSandboxFlags(kSandboxOrigin); + frame->Owner()->DispatchLoad(); + return kNavigationPolicyIgnore; + } + } + } + + bool is_form_submission = type == kNavigationTypeFormSubmitted || + type == kNavigationTypeFormResubmitted; + if (is_form_submission && + // 'form-action' check in the frame that is navigating is disabled on the + // renderer side when PlzNavigate is enabled, but is enforced on the + // browser side instead. + // N.B. check in the frame that initiates the navigation stills occurs in + // blink and is not enforced on the browser-side. + // TODO(arthursonzogni) The 'form-action' check should be fully disabled + // in blink when browser side navigation is enabled, except when the form + // submission doesn't trigger a navigation(i.e. javascript urls). Please + // see https://crbug.com/701749 + !browser_side_navigation_enabled && + !frame->GetDocument()->GetContentSecurityPolicy()->AllowFormAction( + request.Url(), request.GetRedirectStatus(), + SecurityViolationReportingPolicy::kReport, check_header_type)) { + return kNavigationPolicyIgnore; + } + + return policy; +} + +static SinglePageAppNavigationType CategorizeSinglePageAppNavigation( + SameDocumentNavigationSource same_document_navigation_source, + FrameLoadType frame_load_type) { + // |SinglePageAppNavigationType| falls into this grid according to different + // combinations of |FrameLoadType| and |SameDocumentNavigationSource|: + // + // HistoryApi Default + // kFrameLoadTypeBackForward illegal otherFragmentNav + // !kFrameLoadTypeBackForward sameDocBack/Forward historyPushOrReplace + switch (same_document_navigation_source) { + case kSameDocumentNavigationDefault: + if (frame_load_type == kFrameLoadTypeBackForward) { + return kSPANavTypeSameDocumentBackwardOrForward; + } + return kSPANavTypeOtherFragmentNavigation; + case kSameDocumentNavigationHistoryApi: + // It's illegal to have both kSameDocumentNavigationHistoryApi and + // kFrameLoadTypeBackForward. + DCHECK(frame_load_type != kFrameLoadTypeBackForward); + return kSPANavTypeHistoryPushStateOrReplaceState; + } + NOTREACHED(); + return kSPANavTypeSameDocumentBackwardOrForward; +} + +ResourceRequest FrameLoader::ResourceRequestForReload( + FrameLoadType frame_load_type, + const KURL& override_url, + ClientRedirectPolicy client_redirect_policy) { + DCHECK(IsReloadLoadType(frame_load_type)); + const auto cache_mode = frame_load_type == kFrameLoadTypeReloadBypassingCache + ? mojom::FetchCacheMode::kBypassCache + : mojom::FetchCacheMode::kValidateCache; + if (!document_loader_ || !document_loader_->GetHistoryItem()) + return ResourceRequest(); + ResourceRequest request = + document_loader_->GetHistoryItem()->GenerateResourceRequest(cache_mode); + + // Set requestor origin to be the current URL's origin. + request.SetRequestorOrigin(SecurityOrigin::Create(request.Url())); + + // ClientRedirectPolicy is an indication that this load was triggered by some + // direct interaction with the page. If this reload is not a client redirect, + // we should reuse the referrer from the original load of the current + // document. If this reload is a client redirect (e.g., location.reload()), it + // was initiated by something in the current document and should therefore + // show the current document's url as the referrer. + if (client_redirect_policy == ClientRedirectPolicy::kClientRedirect) { + request.SetHTTPReferrer(SecurityPolicy::GenerateReferrer( + frame_->GetDocument()->GetReferrerPolicy(), + frame_->GetDocument()->Url(), + frame_->GetDocument()->OutgoingReferrer())); + } + + if (!override_url.IsEmpty()) { + request.SetURL(override_url); + request.ClearHTTPReferrer(); + } + request.SetSkipServiceWorker(frame_load_type == + kFrameLoadTypeReloadBypassingCache); + return request; +} + +FrameLoader::FrameLoader(LocalFrame* frame) + : frame_(frame), + progress_tracker_(ProgressTracker::Create(frame)), + in_stop_all_loaders_(false), + in_restore_scroll_(false), + forced_sandbox_flags_(kSandboxNone), + dispatching_did_clear_window_object_in_main_world_(false), + protect_provisional_loader_(false), + detached_(false) { + DCHECK(frame_); + + TRACE_EVENT_OBJECT_CREATED_WITH_ID("loading", "FrameLoader", this); + TakeObjectSnapshot(); +} + +FrameLoader::~FrameLoader() { + DCHECK(detached_); +} + +void FrameLoader::Trace(blink::Visitor* visitor) { + visitor->Trace(frame_); + visitor->Trace(progress_tracker_); + visitor->Trace(document_loader_); + visitor->Trace(provisional_document_loader_); +} + +void FrameLoader::Init() { + ScriptForbiddenScope forbid_scripts; + + ResourceRequest initial_request{KURL(g_empty_string)}; + initial_request.SetRequestContext(WebURLRequest::kRequestContextInternal); + initial_request.SetFrameType( + frame_->IsMainFrame() ? network::mojom::RequestContextFrameType::kTopLevel + : network::mojom::RequestContextFrameType::kNested); + + provisional_document_loader_ = + Client()->CreateDocumentLoader(frame_, initial_request, SubstituteData(), + ClientRedirectPolicy::kNotClientRedirect, + base::UnguessableToken::Create()); + provisional_document_loader_->StartLoading(); + + frame_->GetDocument()->CancelParsing(); + + state_machine_.AdvanceTo( + FrameLoaderStateMachine::kDisplayingInitialEmptyDocument); + + // Suppress finish notifications for initial empty documents, since they don't + // generate start notifications. + document_loader_->SetSentDidFinishLoad(); + if (frame_->GetPage()->Paused()) + SetDefersLoading(true); + + TakeObjectSnapshot(); +} + +LocalFrameClient* FrameLoader::Client() const { + return frame_->Client(); +} + +void FrameLoader::SetDefersLoading(bool defers) { + if (provisional_document_loader_) + provisional_document_loader_->Fetcher()->SetDefersLoading(defers); + + if (Document* document = frame_->GetDocument()) { + document->Fetcher()->SetDefersLoading(defers); + if (defers) + document->PauseScheduledTasks(); + else + document->UnpauseScheduledTasks(); + } + + if (!defers) + frame_->GetNavigationScheduler().StartTimer(); +} + +bool FrameLoader::ShouldSerializeScrollAnchor() { + return frame_ && frame_->View() && + RuntimeEnabledFeatures::ScrollAnchorSerializationEnabled() && + frame_->View()->ShouldPerformScrollAnchoring(); +} + +void FrameLoader::SaveScrollAnchor() { + if (!ShouldSerializeScrollAnchor()) + return; + + if (!document_loader_ || !document_loader_->GetHistoryItem() || + !frame_->View()) + return; + + // Shouldn't clobber anything if we might still restore later. + if (NeedsHistoryItemRestore(document_loader_->LoadType()) && + !document_loader_->GetInitialScrollState().was_scrolled_by_user) + return; + + HistoryItem* history_item = document_loader_->GetHistoryItem(); + if (ScrollableArea* layout_scrollable_area = + frame_->View()->LayoutViewportScrollableArea()) { + ScrollAnchor* scroll_anchor = layout_scrollable_area->GetScrollAnchor(); + DCHECK(scroll_anchor); + + const SerializedAnchor& serialized_anchor = + scroll_anchor->GetSerializedAnchor(); + if (serialized_anchor.IsValid()) { + history_item->SetScrollAnchorData( + {serialized_anchor.selector, + WebFloatPoint(serialized_anchor.relative_offset.X(), + serialized_anchor.relative_offset.Y()), + serialized_anchor.simhash}); + } + } +} + +void FrameLoader::SaveScrollState() { + if (!document_loader_ || !document_loader_->GetHistoryItem() || + !frame_->View()) + return; + + // Shouldn't clobber anything if we might still restore later. + if (NeedsHistoryItemRestore(document_loader_->LoadType()) && + !document_loader_->GetInitialScrollState().was_scrolled_by_user) + return; + + HistoryItem* history_item = document_loader_->GetHistoryItem(); + if (ScrollableArea* layout_scrollable_area = + frame_->View()->LayoutViewportScrollableArea()) + history_item->SetScrollOffset(layout_scrollable_area->GetScrollOffset()); + history_item->SetVisualViewportScrollOffset(ToScrollOffset( + frame_->GetPage()->GetVisualViewport().VisibleRect().Location())); + + if (frame_->IsMainFrame()) + history_item->SetPageScaleFactor(frame_->GetPage()->PageScaleFactor()); + + Client()->DidUpdateCurrentHistoryItem(); +} + +void FrameLoader::DispatchUnloadEvent() { + FrameNavigationDisabler navigation_disabler(*frame_); + + // If the frame is unloading, the provisional loader should no longer be + // protected. It will be detached soon. + protect_provisional_loader_ = false; + SaveScrollState(); + + if (frame_->GetDocument() && !SVGImage::IsInSVGImage(frame_->GetDocument())) + frame_->GetDocument()->DispatchUnloadEvents(); +} + +void FrameLoader::DidExplicitOpen() { + // Calling document.open counts as committing the first real document load. + if (!state_machine_.CommittedFirstRealDocumentLoad()) + state_machine_.AdvanceTo(FrameLoaderStateMachine::kCommittedFirstRealLoad); + + // Only model a document.open() as part of a navigation if its parent is not + // done or in the process of completing. + if (Frame* parent = frame_->Tree().Parent()) { + if ((parent->IsLocalFrame() && + ToLocalFrame(parent)->GetDocument()->LoadEventStillNeeded()) || + (parent->IsRemoteFrame() && parent->IsLoading())) { + progress_tracker_->ProgressStarted(document_loader_->LoadType()); + } + } + + // Prevent window.open(url) -- eg window.open("about:blank") -- from blowing + // away results from a subsequent window.document.open / window.document.write + // call. Canceling redirection here works for all cases because document.open + // implicitly precedes document.write. + frame_->GetNavigationScheduler().Cancel(); +} + +// This is only called by ScriptController::executeScriptIfJavaScriptURL and +// always contains the result of evaluating a javascript: url. This is the +// <iframe src="javascript:'html'"> case. +void FrameLoader::ReplaceDocumentWhileExecutingJavaScriptURL( + const String& source, + Document* owner_document) { + Document* document = frame_->GetDocument(); + if (!document_loader_ || + document->PageDismissalEventBeingDispatched() != Document::kNoDismissal) + return; + + UseCounter::Count(*document, WebFeature::kReplaceDocumentViaJavaScriptURL); + + const KURL& url = document->Url(); + + // Compute this before clearing the frame, because it may need to inherit an + // aliased security context. + WebGlobalObjectReusePolicy global_object_reuse_policy = + frame_->ShouldReuseDefaultView(url) + ? WebGlobalObjectReusePolicy::kUseExisting + : WebGlobalObjectReusePolicy::kCreateNew; + + StopAllLoaders(); + // Don't allow any new child frames to load in this frame: attaching a new + // child frame during or after detaching children results in an attached + // frame on a detached DOM tree, which is bad. + SubframeLoadingDisabler disabler(document); + frame_->DetachChildren(); + + // detachChildren() potentially detaches or navigates this frame. The load + // cannot continue in those cases. + if (!frame_->IsAttached() || document != frame_->GetDocument()) + return; + + frame_->GetDocument()->Shutdown(); + Client()->TransitionToCommittedForNewPage(); + document_loader_->ReplaceDocumentWhileExecutingJavaScriptURL( + url, owner_document, global_object_reuse_policy, source); +} + +void FrameLoader::FinishedParsing() { + if (state_machine_.CreatingInitialEmptyDocument()) + return; + + progress_tracker_->FinishedParsing(); + + if (Client()) { + ScriptForbiddenScope forbid_scripts; + Client()->DispatchDidFinishDocumentLoad(); + } + + if (Client()) { + Client()->RunScriptsAtDocumentReady( + document_loader_ ? document_loader_->IsCommittedButEmpty() : true); + } + + frame_->GetDocument()->CheckCompleted(); + + if (!frame_->View()) + return; + + // Check if the scrollbars are really needed for the content. If not, remove + // them, relayout, and repaint. + frame_->View()->RestoreScrollbar(); + ProcessFragment(frame_->GetDocument()->Url(), document_loader_->LoadType(), + kNavigationToDifferentDocument); +} + +bool FrameLoader::AllAncestorsAreComplete() const { + for (Frame* ancestor = frame_; ancestor; + ancestor = ancestor->Tree().Parent()) { + if (ancestor->IsLoading()) + return false; + } + return true; +} + +void FrameLoader::DidFinishNavigation() { + // We should have either finished the provisional or committed navigation if + // this is called. Only delcare the whole frame finished if neither is in + // progress. + DCHECK((document_loader_ && document_loader_->SentDidFinishLoad()) || + !HasProvisionalNavigation()); + if (!document_loader_ || !document_loader_->SentDidFinishLoad() || + HasProvisionalNavigation()) { + return; + } + + if (frame_->IsLoading()) { + progress_tracker_->ProgressCompleted(); + // Retry restoring scroll offset since finishing loading disables content + // size clamping. + RestoreScrollPositionAndViewState(); + if (document_loader_) + document_loader_->SetLoadType(kFrameLoadTypeStandard); + frame_->DomWindow()->FinishedLoading(); + } + + Frame* parent = frame_->Tree().Parent(); + if (parent) + parent->CheckCompleted(); +} + +Frame* FrameLoader::Opener() { + return Client() ? Client()->Opener() : nullptr; +} + +void FrameLoader::SetOpener(LocalFrame* opener) { + // If the frame is already detached, the opener has already been cleared. + if (Client()) + Client()->SetOpener(opener); +} + +bool FrameLoader::AllowPlugins(ReasonForCallingAllowPlugins reason) { + // With Oilpan, a FrameLoader might be accessed after the Page has been + // detached. FrameClient will not be accessible, so bail early. + if (!Client()) + return false; + Settings* settings = frame_->GetSettings(); + bool allowed = settings && settings->GetPluginsEnabled(); + if (!allowed && reason == kAboutToInstantiatePlugin) + frame_->GetContentSettingsClient()->DidNotAllowPlugins(); + return allowed; +} + +void FrameLoader::UpdateForSameDocumentNavigation( + const KURL& new_url, + SameDocumentNavigationSource same_document_navigation_source, + scoped_refptr<SerializedScriptValue> data, + HistoryScrollRestorationType scroll_restoration_type, + FrameLoadType type, + Document* initiating_document) { + SinglePageAppNavigationType single_page_app_navigation_type = + CategorizeSinglePageAppNavigation(same_document_navigation_source, type); + UMA_HISTOGRAM_ENUMERATION( + "RendererScheduler.UpdateForSameDocumentNavigationCount", + single_page_app_navigation_type, kSPANavTypeCount); + + TRACE_EVENT1("blink", "FrameLoader::updateForSameDocumentNavigation", "url", + new_url.GetString().Ascii().data()); + + // Generate start and stop notifications only when loader is completed so that + // we don't fire them for fragment redirection that happens in window.onload + // handler. See https://bugs.webkit.org/show_bug.cgi?id=31838 + // Do not fire the notifications if the frame is concurrently navigating away + // from the document, since a new document is already loading. + bool was_loading = frame_->IsLoading(); + if (!was_loading) + Client()->DidStartLoading(kNavigationWithinSameDocument); + + // Update the data source's request with the new URL to fake the URL change + frame_->GetDocument()->SetURL(new_url); + GetDocumentLoader()->UpdateForSameDocumentNavigation( + new_url, same_document_navigation_source, std::move(data), + scroll_restoration_type, type, initiating_document); + if (!was_loading) + Client()->DidStopLoading(); +} + +void FrameLoader::DetachDocumentLoader(Member<DocumentLoader>& loader) { + if (!loader) + return; + + FrameNavigationDisabler navigation_disabler(*frame_); + loader->DetachFromFrame(); + loader = nullptr; +} + +void FrameLoader::ClearInitialScrollState() { + document_loader_->GetInitialScrollState().was_scrolled_by_user = false; + document_loader_->GetInitialScrollState().was_scrolled_by_js = false; +} + +void FrameLoader::LoadInSameDocument( + const KURL& url, + scoped_refptr<SerializedScriptValue> state_object, + FrameLoadType frame_load_type, + HistoryItem* history_item, + ClientRedirectPolicy client_redirect, + Document* initiating_document) { + // If we have a state object, we cannot also be a new navigation. + DCHECK(!state_object || frame_load_type == kFrameLoadTypeBackForward); + + // If we have a provisional request for a different document, a fragment + // scroll should cancel it. + DetachDocumentLoader(provisional_document_loader_); + + if (!frame_->GetPage()) + return; + SaveScrollState(); + + KURL old_url = frame_->GetDocument()->Url(); + bool hash_change = EqualIgnoringFragmentIdentifier(url, old_url) && + url.FragmentIdentifier() != old_url.FragmentIdentifier(); + if (hash_change) { + // If we were in the autoscroll/middleClickAutoscroll mode we want to stop + // it before following the link to the anchor + frame_->GetEventHandler().StopAutoscroll(); + frame_->DomWindow()->EnqueueHashchangeEvent(old_url, url); + } + document_loader_->SetIsClientRedirect(client_redirect == + ClientRedirectPolicy::kClientRedirect); + if (history_item) + document_loader_->SetItemForHistoryNavigation(history_item); + UpdateForSameDocumentNavigation(url, kSameDocumentNavigationDefault, nullptr, + kScrollRestorationAuto, frame_load_type, + initiating_document); + + ClearInitialScrollState(); + + frame_->GetDocument()->CheckCompleted(); + + // onpopstate might change view state, so stash for later restore. + std::unique_ptr<HistoryItem::ViewState> view_state; + if (history_item && history_item->GetViewState()) { + view_state = + std::make_unique<HistoryItem::ViewState>(*history_item->GetViewState()); + } + + frame_->DomWindow()->StatePopped(state_object + ? std::move(state_object) + : SerializedScriptValue::NullValue()); + + if (history_item) { + RestoreScrollPositionAndViewState(frame_load_type, kHistorySameDocumentLoad, + view_state.get(), + history_item->ScrollRestorationType()); + } + + // We need to scroll to the fragment whether or not a hash change occurred, + // since the user might have scrolled since the previous navigation. + ProcessFragment(url, frame_load_type, kNavigationWithinSameDocument); + + TakeObjectSnapshot(); +} + +// static +void FrameLoader::SetReferrerForFrameRequest(FrameLoadRequest& frame_request) { + ResourceRequest& request = frame_request.GetResourceRequest(); + Document* origin_document = frame_request.OriginDocument(); + + if (!origin_document) + return; + // Anchor elements with the 'referrerpolicy' attribute will have already set + // the referrer on the request. + if (request.DidSetHTTPReferrer()) + return; + if (frame_request.GetShouldSendReferrer() == kNeverSendReferrer) + return; + + // Always use the initiating document to generate the referrer. We need to + // generateReferrer(), because we haven't enforced ReferrerPolicy or + // https->http referrer suppression yet. + Referrer referrer = SecurityPolicy::GenerateReferrer( + origin_document->GetReferrerPolicy(), request.Url(), + origin_document->OutgoingReferrer()); + + request.SetHTTPReferrer(referrer); + request.SetHTTPOriginToMatchReferrerIfNeeded(); +} + +FrameLoadType FrameLoader::DetermineFrameLoadType( + const FrameLoadRequest& request) { + if (frame_->Tree().Parent() && + !state_machine_.CommittedFirstRealDocumentLoad()) + return kFrameLoadTypeInitialInChildFrame; + if (!frame_->Tree().Parent() && !Client()->BackForwardLength()) { + if (Opener() && request.GetResourceRequest().Url().IsEmpty()) + return kFrameLoadTypeReplaceCurrentItem; + return kFrameLoadTypeStandard; + } + if (request.GetResourceRequest().GetCacheMode() == + mojom::FetchCacheMode::kValidateCache) + return kFrameLoadTypeReload; + if (request.GetResourceRequest().GetCacheMode() == + mojom::FetchCacheMode::kBypassCache) + return kFrameLoadTypeReloadBypassingCache; + // From the HTML5 spec for location.assign(): + // "If the browsing context's session history contains only one Document, + // and that was the about:blank Document created when the browsing context + // was created, then the navigation must be done with replacement enabled." + if (request.ReplacesCurrentItem() || + (!state_machine_.CommittedMultipleRealLoads() && + DeprecatedEqualIgnoringCase(frame_->GetDocument()->Url(), BlankURL()))) + return kFrameLoadTypeReplaceCurrentItem; + + if (request.GetResourceRequest().Url() == document_loader_->UrlForHistory()) { + if (request.GetResourceRequest().HttpMethod() == HTTPNames::POST) + return kFrameLoadTypeStandard; + if (!request.OriginDocument()) + return kFrameLoadTypeReload; + return kFrameLoadTypeReplaceCurrentItem; + } + + if (request.GetSubstituteData().FailingURL() == + document_loader_->UrlForHistory() && + document_loader_->LoadType() == kFrameLoadTypeReload) + return kFrameLoadTypeReload; + + if (request.GetResourceRequest().Url().IsEmpty() && + request.GetSubstituteData().FailingURL().IsEmpty()) { + return kFrameLoadTypeReplaceCurrentItem; + } + + if (request.OriginDocument() && + !request.OriginDocument()->CanCreateHistoryEntry()) + return kFrameLoadTypeReplaceCurrentItem; + + return kFrameLoadTypeStandard; +} + +bool FrameLoader::PrepareRequestForThisFrame(FrameLoadRequest& request) { + // If no origin Document* was specified, skip remaining security checks and + // assume the caller has fully initialized the FrameLoadRequest. + if (!request.OriginDocument()) + return true; + + KURL url = request.GetResourceRequest().Url(); + if (frame_->GetScriptController().ExecuteScriptIfJavaScriptURL(url, nullptr)) + return false; + + if (!request.OriginDocument()->GetSecurityOrigin()->CanDisplay(url)) { + request.OriginDocument()->AddConsoleMessage(ConsoleMessage::Create( + kSecurityMessageSource, kErrorMessageLevel, + "Not allowed to load local resource: " + url.ElidedString())); + return false; + } + + // Block renderer-initiated loads of data URLs in the top frame. If the mime + // type of the data URL is supported, the URL will eventually be rendered, so + // block it here. Otherwise, the load might be handled by a plugin or end up + // as a download, so allow it to let the embedder figure out what to do with + // it. + if (frame_->IsMainFrame() && + !request.GetResourceRequest().IsSameDocumentNavigation() && + !frame_->Client()->AllowContentInitiatedDataUrlNavigations( + request.OriginDocument()->Url()) && + !request.GetResourceRequest().GetSuggestedFilename().has_value() && + url.ProtocolIsData() && NetworkUtils::IsDataURLMimeTypeSupported(url)) { + frame_->GetDocument()->AddConsoleMessage(ConsoleMessage::Create( + kSecurityMessageSource, kErrorMessageLevel, + "Not allowed to navigate top frame to data URL: " + + url.ElidedString())); + return false; + } + + if (!request.Form() && request.FrameName().IsEmpty()) + request.SetFrameName(frame_->GetDocument()->BaseTarget()); + return true; +} + +static bool ShouldNavigateTargetFrame(NavigationPolicy policy) { + switch (policy) { + case kNavigationPolicyCurrentTab: + return true; + + // Navigation will target a *new* frame (e.g. because of a ctrl-click), + // so the target frame can be ignored. + case kNavigationPolicyNewBackgroundTab: + case kNavigationPolicyNewForegroundTab: + case kNavigationPolicyNewWindow: + case kNavigationPolicyNewPopup: + return false; + + // Navigation won't really target any specific frame, + // so the target frame can be ignored. + case kNavigationPolicyIgnore: + case kNavigationPolicyDownload: + return false; + + case kNavigationPolicyHandledByClient: + // Impossible, because at this point we shouldn't yet have called + // client()->decidePolicyForNavigation(...). + NOTREACHED(); + return true; + + default: + NOTREACHED() << policy; + return true; + } +} + +static NavigationType DetermineNavigationType(FrameLoadType frame_load_type, + bool is_form_submission, + bool have_event) { + bool is_reload = IsReloadLoadType(frame_load_type); + bool is_back_forward = IsBackForwardLoadType(frame_load_type); + if (is_form_submission) { + return (is_reload || is_back_forward) ? kNavigationTypeFormResubmitted + : kNavigationTypeFormSubmitted; + } + if (have_event) + return kNavigationTypeLinkClicked; + if (is_reload) + return kNavigationTypeReload; + if (is_back_forward) + return kNavigationTypeBackForward; + return kNavigationTypeOther; +} + +static WebURLRequest::RequestContext DetermineRequestContextFromNavigationType( + const NavigationType navigation_type) { + switch (navigation_type) { + case kNavigationTypeLinkClicked: + return WebURLRequest::kRequestContextHyperlink; + + case kNavigationTypeOther: + return WebURLRequest::kRequestContextLocation; + + case kNavigationTypeFormResubmitted: + case kNavigationTypeFormSubmitted: + return WebURLRequest::kRequestContextForm; + + case kNavigationTypeBackForward: + case kNavigationTypeReload: + return WebURLRequest::kRequestContextInternal; + } + NOTREACHED(); + return WebURLRequest::kRequestContextHyperlink; +} + +static NavigationPolicy NavigationPolicyForRequest( + const FrameLoadRequest& request) { + NavigationPolicy policy = kNavigationPolicyCurrentTab; + Event* event = request.TriggeringEvent(); + if (!event) + return policy; + + if (request.Form() && event->UnderlyingEvent()) + event = event->UnderlyingEvent(); + + if (event->IsMouseEvent()) { + MouseEvent* mouse_event = ToMouseEvent(event); + NavigationPolicyFromMouseEvent( + mouse_event->button(), mouse_event->ctrlKey(), mouse_event->shiftKey(), + mouse_event->altKey(), mouse_event->metaKey(), &policy); + } else if (event->IsKeyboardEvent()) { + // The click is simulated when triggering the keypress event. + KeyboardEvent* key_event = ToKeyboardEvent(event); + NavigationPolicyFromMouseEvent(0, key_event->ctrlKey(), + key_event->shiftKey(), key_event->altKey(), + key_event->metaKey(), &policy); + } else if (event->IsGestureEvent()) { + // The click is simulated when triggering the gesture-tap event + GestureEvent* gesture_event = ToGestureEvent(event); + NavigationPolicyFromMouseEvent( + 0, gesture_event->ctrlKey(), gesture_event->shiftKey(), + gesture_event->altKey(), gesture_event->metaKey(), &policy); + } + return policy; +} + +void FrameLoader::Load(const FrameLoadRequest& passed_request, + FrameLoadType frame_load_type, + HistoryItem* history_item, + HistoryLoadType history_load_type) { + DCHECK(frame_->GetDocument()); + + if (HTMLFrameOwnerElement* element = frame_->DeprecatedLocalOwner()) + element->CancelPendingLazyLoad(); + + if (IsBackForwardLoadType(frame_load_type) && !frame_->IsNavigationAllowed()) + return; + + if (in_stop_all_loaders_) + return; + + FrameLoadRequest request(passed_request); + request.GetResourceRequest().SetHasUserGesture( + Frame::HasTransientUserActivation(frame_)); + + if (!PrepareRequestForThisFrame(request)) + return; + + // Form submissions appear to need their special-case of finding the target at + // schedule rather than at fire. + Frame* target_frame = request.Form() + ? nullptr + : frame_->FindFrameForNavigation( + AtomicString(request.FrameName()), *frame_, + request.GetResourceRequest().Url()); + + NavigationPolicy policy = NavigationPolicyForRequest(request); + if (target_frame && target_frame != frame_ && + ShouldNavigateTargetFrame(policy)) { + if (target_frame->IsLocalFrame() && + !ToLocalFrame(target_frame)->IsNavigationAllowed()) { + return; + } + + bool was_in_same_page = target_frame->GetPage() == frame_->GetPage(); + + request.SetFrameName("_self"); + target_frame->Navigate(request); + Page* page = target_frame->GetPage(); + if (!was_in_same_page && page) + page->GetChromeClient().Focus(nullptr); + return; + } + + SetReferrerForFrameRequest(request); + + if (!target_frame && !request.FrameName().IsEmpty()) { + if (policy == kNavigationPolicyDownload) { + Client()->DownloadURL(request.GetResourceRequest()); + return; // Navigation/download will be handled by the client. + } else if (ShouldNavigateTargetFrame(policy)) { + request.GetResourceRequest().SetFrameType( + network::mojom::RequestContextFrameType::kAuxiliary); + CreateWindowForRequest(request, *frame_, policy); + return; // Navigation will be handled by the new frame/window. + } + } + + if (!frame_->IsNavigationAllowed()) + return; + + const KURL& url = request.GetResourceRequest().Url(); + FrameLoadType new_load_type = (frame_load_type == kFrameLoadTypeStandard) + ? DetermineFrameLoadType(request) + : frame_load_type; + + bool same_document_history_navigation = + IsBackForwardLoadType(new_load_type) && + history_load_type == kHistorySameDocumentLoad; + bool same_document_navigation = + policy == kNavigationPolicyCurrentTab && + ShouldPerformFragmentNavigation(request.Form(), + request.GetResourceRequest().HttpMethod(), + new_load_type, url); + + // Perform same document navigation. + if (same_document_history_navigation || same_document_navigation) { + CommitSameDocumentNavigation( + request.GetResourceRequest().Url(), new_load_type, history_item, + request.ClientRedirect(), request.OriginDocument(), + request.TriggeringEvent()); + return; + } + + // PlzNavigate + // If the loader classifies this navigation as a different document navigation + // while the browser intended the navigation to be same-document, it means + // that a different navigation must have committed while the IPC was sent. + // This navigation is no more same-document. The navigation is simply dropped. + if (request.GetResourceRequest().IsSameDocumentNavigation()) + return; + + StartLoad(request, new_load_type, policy, history_item); +} + +mojom::CommitResult FrameLoader::CommitSameDocumentNavigation( + const KURL& url, + FrameLoadType frame_load_type, + HistoryItem* history_item, + ClientRedirectPolicy client_redirect_policy, + Document* origin_document, + Event* triggering_event) { + DCHECK(!IsReloadLoadType(frame_load_type)); + DCHECK(frame_->GetDocument()); + + if (in_stop_all_loaders_) + return mojom::CommitResult::Aborted; + + bool history_navigation = IsBackForwardLoadType(frame_load_type); + + if (!frame_->IsNavigationAllowed() && history_navigation) + return mojom::CommitResult::Aborted; + + if (!history_navigation) { + // In the case of non-history navigations, check that this is a + // same-document navigation. If not, the navigation should restart as a + // cross-document navigation. + if (!url.HasFragmentIdentifier() || + !EqualIgnoringFragmentIdentifier(frame_->GetDocument()->Url(), url) || + frame_->GetDocument()->IsFrameSet()) { + return mojom::CommitResult::RestartCrossDocument; + } + } + + DCHECK(history_item || !history_navigation); + scoped_refptr<SerializedScriptValue> state_object = + history_navigation ? history_item->StateObject() : nullptr; + + if (!history_navigation) { + document_loader_->SetNavigationType( + DetermineNavigationType(frame_load_type, false, triggering_event)); + if (ShouldTreatURLAsSameAsCurrent(url)) + frame_load_type = kFrameLoadTypeReplaceCurrentItem; + } + + // Perform the same-document navigation. + LoadInSameDocument(url, state_object, frame_load_type, history_item, + client_redirect_policy, origin_document); + return mojom::CommitResult::Ok; +} + +SubstituteData FrameLoader::DefaultSubstituteDataForURL(const KURL& url) { + if (!ShouldTreatURLAsSrcdocDocument(url)) + return SubstituteData(); + String srcdoc = frame_->DeprecatedLocalOwner()->FastGetAttribute(srcdocAttr); + DCHECK(!srcdoc.IsNull()); + CString encoded_srcdoc = srcdoc.Utf8(); + return SubstituteData( + SharedBuffer::Create(encoded_srcdoc.data(), encoded_srcdoc.length()), + "text/html", "UTF-8", NullURL()); +} + +void FrameLoader::StopAllLoaders() { + if (frame_->GetDocument()->PageDismissalEventBeingDispatched() != + Document::kNoDismissal) + return; + + // If this method is called from within this method, infinite recursion can + // occur (3442218). Avoid this. + if (in_stop_all_loaders_) + return; + + AutoReset<bool> in_stop_all_loaders(&in_stop_all_loaders_, true); + + for (Frame* child = frame_->Tree().FirstChild(); child; + child = child->Tree().NextSibling()) { + if (child->IsLocalFrame()) + ToLocalFrame(child)->Loader().StopAllLoaders(); + } + + frame_->GetDocument()->CancelParsing(); + if (document_loader_) + document_loader_->StopLoading(); + if (!protect_provisional_loader_) + DetachDocumentLoader(provisional_document_loader_); + frame_->GetNavigationScheduler().Cancel(); + DidFinishNavigation(); + + TakeObjectSnapshot(); +} + +void FrameLoader::DidAccessInitialDocument() { + // We only need to notify the client for the main frame. + if (IsLoadingMainFrame()) { + // Forbid script execution to prevent re-entering V8, since this is called + // from a binding security check. + ScriptForbiddenScope forbid_scripts; + if (Client()) + Client()->DidAccessInitialDocument(); + } +} + +bool FrameLoader::PrepareForCommit() { + PluginScriptForbiddenScope forbid_plugin_destructor_scripting; + DocumentLoader* pdl = provisional_document_loader_; + + if (frame_->GetDocument()) { + unsigned node_count = 0; + for (Frame* frame = frame_; frame; frame = frame->Tree().TraverseNext()) { + if (frame->IsLocalFrame()) { + LocalFrame* local_frame = ToLocalFrame(frame); + node_count += local_frame->GetDocument()->NodeCount(); + } + } + unsigned total_node_count = + InstanceCounters::CounterValue(InstanceCounters::kNodeCounter); + float ratio = static_cast<float>(node_count) / total_node_count; + ThreadState::Current()->SchedulePageNavigationGCIfNeeded(ratio); + } + + // Don't allow any new child frames to load in this frame: attaching a new + // child frame during or after detaching children results in an attached frame + // on a detached DOM tree, which is bad. + SubframeLoadingDisabler disabler(frame_->GetDocument()); + if (document_loader_) { + Client()->DispatchWillCommitProvisionalLoad(); + DispatchUnloadEvent(); + } + frame_->DetachChildren(); + // The previous calls to dispatchUnloadEvent() and detachChildren() can + // execute arbitrary script via things like unload events. If the executed + // script intiates a new load or causes the current frame to be detached, we + // need to abandon the current load. + if (pdl != provisional_document_loader_) + return false; + // detachFromFrame() will abort XHRs that haven't completed, which can trigger + // event listeners for 'abort'. These event listeners might call + // window.stop(), which will in turn detach the provisional document loader. + // At this point, the provisional document loader should not detach, because + // then the FrameLoader would not have any attached DocumentLoaders. + if (document_loader_) { + AutoReset<bool> in_detach_document_loader(&protect_provisional_loader_, + true); + DetachDocumentLoader(document_loader_); + } + // 'abort' listeners can also detach the frame. + if (!frame_->Client()) + return false; + DCHECK_EQ(provisional_document_loader_, pdl); + // No more events will be dispatched so detach the Document. + // TODO(yoav): Should we also be nullifying domWindow's document (or + // domWindow) since the doc is now detached? + if (frame_->GetDocument()) + frame_->GetDocument()->Shutdown(); + document_loader_ = provisional_document_loader_.Release(); + if (document_loader_) + document_loader_->MarkAsCommitted(); + + TakeObjectSnapshot(); + + return true; +} + +void FrameLoader::CommitProvisionalLoad() { + DCHECK(Client()->HasWebView()); + + // Check if the destination page is allowed to access the previous page's + // timing information. + if (frame_->GetDocument()) { + scoped_refptr<const SecurityOrigin> security_origin = + SecurityOrigin::Create(provisional_document_loader_->Url()); + provisional_document_loader_->GetTiming() + .SetHasSameOriginAsPreviousDocument( + security_origin->CanRequest(frame_->GetDocument()->Url())); + } + + if (!PrepareForCommit()) + return; + + // If we are loading a local root, it is important to explicitly set the event + // listener properties to Nothing as this triggers notifications to the + // client. Clients may assume the presence of handlers for touch and wheel + // events, so these notifications tell it there are (presently) no handlers. + if (frame_->IsLocalRoot()) { + frame_->GetPage()->GetChromeClient().SetEventListenerProperties( + frame_, WebEventListenerClass::kTouchStartOrMove, + WebEventListenerProperties::kNothing); + frame_->GetPage()->GetChromeClient().SetEventListenerProperties( + frame_, WebEventListenerClass::kMouseWheel, + WebEventListenerProperties::kNothing); + frame_->GetPage()->GetChromeClient().SetEventListenerProperties( + frame_, WebEventListenerClass::kTouchEndOrCancel, + WebEventListenerProperties::kNothing); + } + + Client()->TransitionToCommittedForNewPage(); + + frame_->GetNavigationScheduler().Cancel(); +} + +bool FrameLoader::IsLoadingMainFrame() const { + return frame_->IsMainFrame(); +} + +void FrameLoader::RestoreScrollPositionAndViewState() { + if (!frame_->GetPage() || !GetDocumentLoader() || + !GetDocumentLoader()->GetHistoryItem() || in_restore_scroll_) { + return; + } + AutoReset<bool> in_restore_scroll(&in_restore_scroll_, true); + RestoreScrollPositionAndViewState( + GetDocumentLoader()->LoadType(), kHistoryDifferentDocumentLoad, + GetDocumentLoader()->GetHistoryItem()->GetViewState(), + GetDocumentLoader()->GetHistoryItem()->ScrollRestorationType()); +} + +void FrameLoader::RestoreScrollPositionAndViewState( + FrameLoadType load_type, + HistoryLoadType history_load_type, + HistoryItem::ViewState* view_state, + HistoryScrollRestorationType scroll_restoration_type) { + LocalFrameView* view = frame_->View(); + if (!view || !view->LayoutViewportScrollableArea() || + !state_machine_.CommittedFirstRealDocumentLoad() || + !frame_->IsAttached()) { + return; + } + if (!NeedsHistoryItemRestore(load_type) || !view_state) + return; + + bool should_restore_scroll = + scroll_restoration_type != kScrollRestorationManual; + bool should_restore_scale = view_state->page_scale_factor_; + + // This tries to balance: + // 1. restoring as soon as possible. + // 2. not overriding user scroll (TODO(majidvp): also respect user scale). + // 3. detecting clamping to avoid repeatedly popping the scroll position down + // as the page height increases. + // 4. forcing a layout if necessary to avoid clamping. + // 5. ignoring clamp detection if scroll state is not being restored, if load + // is complete, or if the navigation is same-document (as the new page may + // be smaller than the previous page). + bool can_restore_without_clamping = + view->LayoutViewportScrollableArea()->ClampScrollOffset( + view_state->scroll_offset_) == view_state->scroll_offset_; + + bool should_force_clamping = + !frame_->IsLoading() || history_load_type == kHistorySameDocumentLoad; + // Here |can_restore_without_clamping| is false, but layout might be necessary + // to ensure correct content size. + if (!can_restore_without_clamping && should_force_clamping) + frame_->GetDocument()->UpdateStyleAndLayout(); + + bool can_restore_without_annoying_user = + !GetDocumentLoader()->GetInitialScrollState().was_scrolled_by_user && + (can_restore_without_clamping || should_force_clamping || + !should_restore_scroll); + if (!can_restore_without_annoying_user) + return; + + if (should_restore_scroll) { + ScrollOffset previous_offset = + view->LayoutViewportScrollableArea()->GetScrollOffset(); + + // TODO(pnoland): attempt to restore the anchor in more places than this. + // Anchor-based restore should allow for earlier restoration. + bool did_restore = + ShouldSerializeScrollAnchor() && + view->LayoutViewportScrollableArea()->RestoreScrollAnchor( + {view_state->scroll_anchor_data_.selector_, + LayoutPoint(view_state->scroll_anchor_data_.offset_.x, + view_state->scroll_anchor_data_.offset_.y), + view_state->scroll_anchor_data_.simhash_}); + if (!did_restore) { + view->LayoutViewportScrollableArea()->SetScrollOffset( + view_state->scroll_offset_, kProgrammaticScroll); + } + + did_restore |= (previous_offset != + view->LayoutViewportScrollableArea()->GetScrollOffset()); + + // Measure how many successful scroll restoration may impacted if we allow + // using js scroll to prevent browser scroll restoration. + if (did_restore) { + UMA_HISTOGRAM_BOOLEAN( + "Layout.ScrollRestoration.PrecededByJsScroll", + GetDocumentLoader()->GetInitialScrollState().was_scrolled_by_js); + } + } + + // For main frame restore scale and visual viewport position + if (frame_->IsMainFrame()) { + ScrollOffset visual_viewport_offset( + view_state->visual_viewport_scroll_offset_); + + // If the visual viewport's offset is (-1, -1) it means the history item + // is an old version of HistoryItem so distribute the scroll between + // the main frame and the visual viewport as best as we can. + if (visual_viewport_offset.Width() == -1 && + visual_viewport_offset.Height() == -1) { + visual_viewport_offset = + view_state->scroll_offset_ - + view->LayoutViewportScrollableArea()->GetScrollOffset(); + } + + VisualViewport& visual_viewport = frame_->GetPage()->GetVisualViewport(); + if (should_restore_scale && should_restore_scroll) { + visual_viewport.SetScaleAndLocation(view_state->page_scale_factor_, + FloatPoint(visual_viewport_offset)); + } else if (should_restore_scale) { + visual_viewport.SetScale(view_state->page_scale_factor_); + } else if (should_restore_scroll) { + visual_viewport.SetLocation(FloatPoint(visual_viewport_offset)); + } + + if (ScrollingCoordinator* scrolling_coordinator = + frame_->GetPage()->GetScrollingCoordinator()) + scrolling_coordinator->FrameViewRootLayerDidChange(view); + } + + GetDocumentLoader()->GetInitialScrollState().did_restore_from_history = true; +} + +String FrameLoader::UserAgent() const { + String user_agent = Client()->UserAgent(); + probe::applyUserAgentOverride(frame_->GetDocument(), &user_agent); + return user_agent; +} + +void FrameLoader::Detach() { + DetachDocumentLoader(document_loader_); + DetachDocumentLoader(provisional_document_loader_); + + if (progress_tracker_) { + progress_tracker_->Dispose(); + progress_tracker_.Clear(); + } + + TRACE_EVENT_OBJECT_DELETED_WITH_ID("loading", "FrameLoader", this); + detached_ = true; +} + +void FrameLoader::DetachProvisionalDocumentLoader(DocumentLoader* loader) { + DCHECK_EQ(loader, provisional_document_loader_); + DetachDocumentLoader(provisional_document_loader_); + DidFinishNavigation(); +} + +bool FrameLoader::ShouldPerformFragmentNavigation(bool is_form_submission, + const String& http_method, + FrameLoadType load_type, + const KURL& url) { + // We don't do this if we are submitting a form with method other than "GET", + // explicitly reloading, currently displaying a frameset, or if the URL does + // not have a fragment. + return DeprecatedEqualIgnoringCase(http_method, HTTPNames::GET) && + !IsReloadLoadType(load_type) && + load_type != kFrameLoadTypeBackForward && + url.HasFragmentIdentifier() && + // For provisional LocalFrame, there is no real document loaded and + // the initial empty document should not be considered, so there is + // no way to get a same-document load in this case. + !frame_->IsProvisional() && + EqualIgnoringFragmentIdentifier(frame_->GetDocument()->Url(), url) + // We don't want to just scroll if a link from within a frameset is + // trying to reload the frameset into _top. + && !frame_->GetDocument()->IsFrameSet(); +} + +void FrameLoader::ProcessFragment(const KURL& url, + FrameLoadType frame_load_type, + LoadStartType load_start_type) { + LocalFrameView* view = frame_->View(); + if (!view) + return; + + // Leaking scroll position to a cross-origin ancestor would permit the + // so-called "framesniffing" attack. + Frame* boundary_frame = + url.HasFragmentIdentifier() + ? frame_->FindUnsafeParentScrollPropagationBoundary() + : nullptr; + + // FIXME: Handle RemoteFrames + if (boundary_frame && boundary_frame->IsLocalFrame()) { + ToLocalFrame(boundary_frame) + ->View() + ->SetSafeToPropagateScrollToParent(false); + } + + // If scroll position is restored from history fragment or scroll + // restoration type is manual, then we should not override it unless this + // is a same document reload. + bool should_scroll_to_fragment = + (load_start_type == kNavigationWithinSameDocument && + !IsBackForwardLoadType(frame_load_type)) || + (!GetDocumentLoader()->GetInitialScrollState().did_restore_from_history && + !(GetDocumentLoader()->GetHistoryItem() && + GetDocumentLoader()->GetHistoryItem()->ScrollRestorationType() == + kScrollRestorationManual)); + + view->ProcessUrlFragment(url, should_scroll_to_fragment + ? LocalFrameView::kUrlFragmentScroll + : LocalFrameView::kUrlFragmentDontScroll); + + if (boundary_frame && boundary_frame->IsLocalFrame()) + ToLocalFrame(boundary_frame) + ->View() + ->SetSafeToPropagateScrollToParent(true); +} + +bool FrameLoader::ShouldClose(bool is_reload) { + Page* page = frame_->GetPage(); + if (!page || !page->GetChromeClient().CanOpenBeforeUnloadConfirmPanel()) + return true; + + // Store all references to each subframe in advance since beforeunload's event + // handler may modify frame + HeapVector<Member<LocalFrame>> target_frames; + target_frames.push_back(frame_); + for (Frame* child = frame_->Tree().FirstChild(); child; + child = child->Tree().TraverseNext(frame_)) { + // FIXME: There is not yet any way to dispatch events to out-of-process + // frames. + if (child->IsLocalFrame()) + target_frames.push_back(ToLocalFrame(child)); + } + + bool should_close = false; + { + NavigationDisablerForBeforeUnload navigation_disabler; + size_t i; + + bool did_allow_navigation = false; + for (i = 0; i < target_frames.size(); i++) { + if (!target_frames[i]->Tree().IsDescendantOf(frame_)) + continue; + if (!target_frames[i]->GetDocument()->DispatchBeforeUnloadEvent( + page->GetChromeClient(), is_reload, did_allow_navigation)) + break; + } + + if (i == target_frames.size()) + should_close = true; + } + + return should_close; +} + +NavigationPolicy FrameLoader::ShouldContinueForNavigationPolicy( + const ResourceRequest& request, + Document* origin_document, + const SubstituteData& substitute_data, + DocumentLoader* loader, + ContentSecurityPolicyDisposition + should_check_main_world_content_security_policy, + NavigationType type, + NavigationPolicy policy, + FrameLoadType frame_load_type, + bool is_client_redirect, + WebTriggeringEventInfo triggering_event_info, + HTMLFormElement* form, + mojom::blink::BlobURLTokenPtr blob_url_token) { + // Don't ask if we are loading an empty URL. + if (request.Url().IsEmpty() || substitute_data.IsValid()) + return kNavigationPolicyCurrentTab; + + // Check for non-escaped new lines in the url. + if (request.Url().PotentiallyDanglingMarkup() && + request.Url().ProtocolIsInHTTPFamily()) { + Deprecation::CountDeprecation( + frame_, WebFeature::kCanRequestURLHTTPContainingNewline); + if (RuntimeEnabledFeatures::RestrictCanRequestURLCharacterSetEnabled()) + return kNavigationPolicyIgnore; + } + + Settings* settings = frame_->GetSettings(); + if (MaybeCheckCSP(request, type, frame_, policy, + should_check_main_world_content_security_policy == + kCheckContentSecurityPolicy, + settings && settings->GetBrowserSideNavigationEnabled(), + ContentSecurityPolicy::CheckHeaderType::kCheckEnforce) == + kNavigationPolicyIgnore) { + return kNavigationPolicyIgnore; + } + + bool replaces_current_history_item = + frame_load_type == kFrameLoadTypeReplaceCurrentItem; + policy = Client()->DecidePolicyForNavigation( + request, origin_document, loader, type, policy, + replaces_current_history_item, is_client_redirect, triggering_event_info, + form, should_check_main_world_content_security_policy, + std::move(blob_url_token)); + DCHECK(policy == kNavigationPolicyCurrentTab || + policy == kNavigationPolicyIgnore || + policy == kNavigationPolicyHandledByClient || + policy == kNavigationPolicyHandledByClientForInitialHistory) + << policy; + return policy; +} + +NavigationPolicy FrameLoader::ShouldContinueForRedirectNavigationPolicy( + const ResourceRequest& request, + const SubstituteData& substitute_data, + DocumentLoader* loader, + ContentSecurityPolicyDisposition + should_check_main_world_content_security_policy, + NavigationType type, + NavigationPolicy policy, + FrameLoadType frame_load_type, + bool is_client_redirect, + HTMLFormElement* form) { + Settings* settings = frame_->GetSettings(); + // Check report-only CSP policies, which are not checked by + // ShouldContinueForNavigationPolicy. + MaybeCheckCSP(request, type, frame_, policy, + should_check_main_world_content_security_policy == + kCheckContentSecurityPolicy, + settings && settings->GetBrowserSideNavigationEnabled(), + ContentSecurityPolicy::CheckHeaderType::kCheckReportOnly); + + return ShouldContinueForNavigationPolicy( + request, + // |origin_document| is not set. It doesn't really matter here. It is + // useful for PlzNavigate (aka browser-side-navigation). It is used + // during the first navigation and not during redirects. + nullptr, // origin_document + substitute_data, loader, should_check_main_world_content_security_policy, + type, policy, frame_load_type, is_client_redirect, + WebTriggeringEventInfo::kNotFromEvent, form, + nullptr /* blob_url_token */); +} + +void FrameLoader::ClientDroppedNavigation() { + if (!provisional_document_loader_ || provisional_document_loader_->DidStart()) + return; + + DetachProvisionalDocumentLoader(provisional_document_loader_); +} + +NavigationPolicy FrameLoader::CheckLoadCanStart( + FrameLoadRequest& frame_load_request, + FrameLoadType type, + NavigationPolicy navigation_policy, + NavigationType navigation_type) { + if (frame_->GetDocument()->PageDismissalEventBeingDispatched() != + Document::kNoDismissal) { + return kNavigationPolicyIgnore; + } + + // Record the latest requiredCSP value that will be used when sending this + // request. + ResourceRequest& resource_request = frame_load_request.GetResourceRequest(); + RecordLatestRequiredCSP(); + // Before modifying the request, check report-only CSP headers to give the + // site owner a chance to learn about requests that need to be modified. + Settings* settings = frame_->GetSettings(); + MaybeCheckCSP( + resource_request, navigation_type, frame_, navigation_policy, + frame_load_request.ShouldCheckMainWorldContentSecurityPolicy() == + kCheckContentSecurityPolicy, + settings && settings->GetBrowserSideNavigationEnabled(), + ContentSecurityPolicy::CheckHeaderType::kCheckReportOnly); + ModifyRequestForCSP(resource_request, frame_load_request.OriginDocument()); + + WebTriggeringEventInfo triggering_event_info = + WebTriggeringEventInfo::kNotFromEvent; + if (frame_load_request.TriggeringEvent()) { + triggering_event_info = frame_load_request.TriggeringEvent()->isTrusted() + ? WebTriggeringEventInfo::kFromTrustedEvent + : WebTriggeringEventInfo::kFromUntrustedEvent; + } + return ShouldContinueForNavigationPolicy( + resource_request, frame_load_request.OriginDocument(), + frame_load_request.GetSubstituteData(), nullptr, + frame_load_request.ShouldCheckMainWorldContentSecurityPolicy(), + navigation_type, navigation_policy, type, + frame_load_request.ClientRedirect() == + ClientRedirectPolicy::kClientRedirect, + triggering_event_info, frame_load_request.Form(), + frame_load_request.GetBlobURLToken()); +} + +void FrameLoader::StartLoad(FrameLoadRequest& frame_load_request, + FrameLoadType type, + NavigationPolicy navigation_policy, + HistoryItem* history_item) { + DCHECK(Client()->HasWebView()); + ResourceRequest& resource_request = frame_load_request.GetResourceRequest(); + NavigationType navigation_type = DetermineNavigationType( + type, resource_request.HttpBody() || frame_load_request.Form(), + frame_load_request.TriggeringEvent()); + resource_request.SetRequestContext( + DetermineRequestContextFromNavigationType(navigation_type)); + resource_request.SetFrameType( + frame_->IsMainFrame() ? network::mojom::RequestContextFrameType::kTopLevel + : network::mojom::RequestContextFrameType::kNested); + + bool had_placeholder_client_document_loader = + provisional_document_loader_ && !provisional_document_loader_->DidStart(); + navigation_policy = CheckLoadCanStart(frame_load_request, type, + navigation_policy, navigation_type); + if (navigation_policy == kNavigationPolicyIgnore) { + if (had_placeholder_client_document_loader && + !resource_request.CheckForBrowserSideNavigation()) { + DetachDocumentLoader(provisional_document_loader_); + } + return; + } + + // For PlzNavigate placeholder DocumentLoaders, don't send failure callbacks + // for a placeholder simply being replaced with a new DocumentLoader. + if (had_placeholder_client_document_loader) + provisional_document_loader_->SetSentDidFinishLoad(); + frame_->GetDocument()->CancelParsing(); + + // If we're starting a regular navigation on a regular document (i.e., there + // was no placeholder DocumentLoader), it's not enough to cancel parsing, but + // we also have to check whether the document was completed, so it's in a + // defined state should the navigation fail. + if (!had_placeholder_client_document_loader && + type == kFrameLoadTypeStandard && + (navigation_policy == kNavigationPolicyCurrentTab || + navigation_policy == kNavigationPolicyHandledByClient)) { + frame_->GetDocument()->CheckCompleted(); + } + DetachDocumentLoader(provisional_document_loader_); + + // beforeunload fired above, and detaching a DocumentLoader can fire events, + // which can detach this frame. + if (!frame_->GetPage()) + return; + + progress_tracker_->ProgressStarted(type); + // TODO(japhet): This case wants to flag the frame as loading and do nothing + // else. It'd be nice if it could go through the placeholder DocumentLoader + // path, too. + if (navigation_policy == kNavigationPolicyHandledByClientForInitialHistory) + return; + DCHECK(navigation_policy == kNavigationPolicyCurrentTab || + navigation_policy == kNavigationPolicyHandledByClient); + + provisional_document_loader_ = CreateDocumentLoader( + resource_request, frame_load_request, type, navigation_type); + + // PlzNavigate: We need to ensure that script initiated navigations are + // honored. + if (!had_placeholder_client_document_loader || + navigation_policy == kNavigationPolicyHandledByClient) { + frame_->GetNavigationScheduler().Cancel(); + } + + if (frame_load_request.Form()) + Client()->DispatchWillSubmitForm(frame_load_request.Form()); + + provisional_document_loader_->AppendRedirect( + provisional_document_loader_->Url()); + + if (IsBackForwardLoadType(type)) { + DCHECK(history_item); + provisional_document_loader_->SetItemForHistoryNavigation(history_item); + } + + DCHECK(!frame_load_request.GetResourceRequest().IsSameDocumentNavigation()); + frame_->GetFrameScheduler()->DidStartProvisionalLoad(frame_->IsMainFrame()); + + // TODO(ananta): + // We should get rid of the dependency on the DocumentLoader in consumers of + // the DidStartProvisionalLoad() notification. + Client()->DispatchDidStartProvisionalLoad(provisional_document_loader_, + resource_request); + DCHECK(provisional_document_loader_); + + if (navigation_policy == kNavigationPolicyCurrentTab) { + provisional_document_loader_->StartLoading(); + // This should happen after the request is sent, so that the state + // the inspector stored in the matching frameScheduledClientNavigation() + // is available while sending the request. + probe::frameClearedScheduledClientNavigation(frame_); + } else { + probe::frameScheduledClientNavigation(frame_); + } + + TakeObjectSnapshot(); +} + +bool FrameLoader::ShouldTreatURLAsSameAsCurrent(const KURL& url) const { + return document_loader_->GetHistoryItem() && + url == document_loader_->GetHistoryItem()->Url(); +} + +bool FrameLoader::ShouldTreatURLAsSrcdocDocument(const KURL& url) const { + if (!url.IsAboutSrcdocURL()) + return false; + HTMLFrameOwnerElement* owner_element = frame_->DeprecatedLocalOwner(); + if (!IsHTMLIFrameElement(owner_element)) + return false; + return owner_element->FastHasAttribute(srcdocAttr); +} + +void FrameLoader::DispatchDocumentElementAvailable() { + ScriptForbiddenScope forbid_scripts; + Client()->DocumentElementAvailable(); +} + +void FrameLoader::RunScriptsAtDocumentElementAvailable() { + Client()->RunScriptsAtDocumentElementAvailable(); + // The frame might be detached at this point. +} + +void FrameLoader::DispatchDidClearDocumentOfWindowObject() { + DCHECK(frame_->GetDocument()); + if (state_machine_.CreatingInitialEmptyDocument()) + return; + if (!frame_->GetDocument()->CanExecuteScripts(kNotAboutToExecuteScript)) + return; + + Settings* settings = frame_->GetSettings(); + if (settings && settings->GetForceMainWorldInitialization()) { + // Forcibly instantiate WindowProxy. + frame_->GetScriptController().WindowProxy(DOMWrapperWorld::MainWorld()); + } + probe::didClearDocumentOfWindowObject(frame_); + + if (dispatching_did_clear_window_object_in_main_world_) + return; + AutoReset<bool> in_did_clear_window_object( + &dispatching_did_clear_window_object_in_main_world_, true); + // We just cleared the document, not the entire window object, but for the + // embedder that's close enough. + Client()->DispatchDidClearWindowObjectInMainWorld(); +} + +void FrameLoader::DispatchDidClearWindowObjectInMainWorld() { + DCHECK(frame_->GetDocument()); + if (!frame_->GetDocument()->CanExecuteScripts(kNotAboutToExecuteScript)) + return; + + if (dispatching_did_clear_window_object_in_main_world_) + return; + AutoReset<bool> in_did_clear_window_object( + &dispatching_did_clear_window_object_in_main_world_, true); + Client()->DispatchDidClearWindowObjectInMainWorld(); +} + +SandboxFlags FrameLoader::EffectiveSandboxFlags() const { + SandboxFlags flags = forced_sandbox_flags_; + if (FrameOwner* frame_owner = frame_->Owner()) + flags |= frame_owner->GetSandboxFlags(); + // Frames need to inherit the sandbox flags of their parent frame. + if (Frame* parent_frame = frame_->Tree().Parent()) + flags |= parent_frame->GetSecurityContext()->GetSandboxFlags(); + return flags; +} + +WebInsecureRequestPolicy FrameLoader::GetInsecureRequestPolicy() const { + Frame* parent_frame = frame_->Tree().Parent(); + if (!parent_frame) + return kLeaveInsecureRequestsAlone; + + return parent_frame->GetSecurityContext()->GetInsecureRequestPolicy(); +} + +SecurityContext::InsecureNavigationsSet* +FrameLoader::InsecureNavigationsToUpgrade() const { + DCHECK(frame_); + Frame* parent_frame = frame_->Tree().Parent(); + if (!parent_frame) + return nullptr; + + return parent_frame->GetSecurityContext()->InsecureNavigationsToUpgrade(); +} + +void FrameLoader::ModifyRequestForCSP(ResourceRequest& resource_request, + Document* origin_document) const { + if (RuntimeEnabledFeatures::EmbedderCSPEnforcementEnabled() && + !RequiredCSP().IsEmpty()) { + DCHECK( + ContentSecurityPolicy::IsValidCSPAttr(RequiredCSP().GetString(), "")); + resource_request.SetHTTPHeaderField(HTTPNames::Sec_Required_CSP, + RequiredCSP()); + } + + // Tack an 'Upgrade-Insecure-Requests' header to outgoing navigational + // requests, as described in + // https://w3c.github.io/webappsec-upgrade-insecure-requests/#feature-detect + if (resource_request.GetFrameType() != + network::mojom::RequestContextFrameType::kNone) { + // Early return if the request has already been upgraded. + if (!resource_request.HttpHeaderField(HTTPNames::Upgrade_Insecure_Requests) + .IsNull()) { + return; + } + + resource_request.SetHTTPHeaderField(HTTPNames::Upgrade_Insecure_Requests, + "1"); + } + + UpgradeInsecureRequest(resource_request, origin_document); +} + +// static +void FrameLoader::UpgradeInsecureRequest(ResourceRequest& resource_request, + Document* origin_document) { + // We always upgrade requests that meet any of the following criteria: + // 1. Are for subresources. + // 2. Are for nested frames. + // 3. Are form submissions. + // 4. Whose hosts are contained in the origin_document's upgrade insecure + // navigations set. + + // This happens for: + // * Browser initiated main document loading. No upgrade required. + // * Navigation initiated by a frame in another process. URL should have + // already been upgraded in the initiator's process. + if (!origin_document) + return; + + if (!(origin_document->GetInsecureRequestPolicy() & kUpgradeInsecureRequests)) + return; + + // Nested frames are always upgraded on the browser process. + if (resource_request.GetFrameType() == + network::mojom::RequestContextFrameType::kNested) { + return; + } + + KURL url = resource_request.Url(); + if (!url.ProtocolIs("http")) + return; + + if (resource_request.GetFrameType() == + network::mojom::RequestContextFrameType::kNone || + resource_request.GetRequestContext() == + WebURLRequest::kRequestContextForm || + (!url.Host().IsNull() && + origin_document->InsecureNavigationsToUpgrade()->Contains( + url.Host().Impl()->GetHash()))) { + UseCounter::Count(origin_document, + WebFeature::kUpgradeInsecureRequestsUpgradedRequest); + url.SetProtocol("https"); + if (url.Port() == 80) + url.SetPort(443); + resource_request.SetURL(url); + } +} + +void FrameLoader::RecordLatestRequiredCSP() { + required_csp_ = + frame_->Owner() ? frame_->Owner()->RequiredCsp() : g_null_atom; +} + +std::unique_ptr<TracedValue> FrameLoader::ToTracedValue() const { + std::unique_ptr<TracedValue> traced_value = TracedValue::Create(); + traced_value->BeginDictionary("frame"); + traced_value->SetString("id_ref", IdentifiersFactory::FrameId(frame_.Get())); + traced_value->EndDictionary(); + traced_value->SetBoolean("isLoadingMainFrame", IsLoadingMainFrame()); + traced_value->SetString("stateMachine", state_machine_.ToString()); + traced_value->SetString("provisionalDocumentLoaderURL", + provisional_document_loader_ + ? provisional_document_loader_->Url().GetString() + : String()); + traced_value->SetString( + "documentLoaderURL", + document_loader_ ? document_loader_->Url().GetString() : String()); + return traced_value; +} + +inline void FrameLoader::TakeObjectSnapshot() const { + if (detached_) { + // We already logged TRACE_EVENT_OBJECT_DELETED_WITH_ID in detach(). + return; + } + TRACE_EVENT_OBJECT_SNAPSHOT_WITH_ID("loading", "FrameLoader", this, + ToTracedValue()); +} + +DocumentLoader* FrameLoader::CreateDocumentLoader( + const ResourceRequest& request, + const FrameLoadRequest& frame_load_request, + FrameLoadType load_type, + NavigationType navigation_type) { + DocumentLoader* loader = Client()->CreateDocumentLoader( + frame_, request, + frame_load_request.GetSubstituteData().IsValid() + ? frame_load_request.GetSubstituteData() + : DefaultSubstituteDataForURL(request.Url()), + frame_load_request.ClientRedirect(), + frame_load_request.GetDevToolsNavigationToken()); + + loader->SetLoadType(load_type); + loader->SetNavigationType(navigation_type); + // TODO(japhet): This is needed because the browser process DCHECKs if the + // first entry we commit in a new frame has replacement set. It's unclear + // whether the DCHECK is right, investigate removing this special case. + bool replace_current_item = load_type == kFrameLoadTypeReplaceCurrentItem && + (!Opener() || !request.Url().IsEmpty()); + loader->SetReplacesCurrentHistoryItem(replace_current_item); + + probe::lifecycleEvent(frame_, loader, "init", CurrentTimeTicksInSeconds()); + return loader; +} + +STATIC_ASSERT_ENUM(kWebHistorySameDocumentLoad, kHistorySameDocumentLoad); +STATIC_ASSERT_ENUM(kWebHistoryDifferentDocumentLoad, + kHistoryDifferentDocumentLoad); + +STATIC_ASSERT_ENUM(kWebHistoryScrollRestorationManual, + kScrollRestorationManual); +STATIC_ASSERT_ENUM(kWebHistoryScrollRestorationAuto, kScrollRestorationAuto); + +STATIC_ASSERT_ENUM(WebFrameLoadType::kStandard, kFrameLoadTypeStandard); +STATIC_ASSERT_ENUM(WebFrameLoadType::kBackForward, kFrameLoadTypeBackForward); +STATIC_ASSERT_ENUM(WebFrameLoadType::kReload, kFrameLoadTypeReload); +STATIC_ASSERT_ENUM(WebFrameLoadType::kReplaceCurrentItem, + kFrameLoadTypeReplaceCurrentItem); +STATIC_ASSERT_ENUM(WebFrameLoadType::kInitialInChildFrame, + kFrameLoadTypeInitialInChildFrame); +STATIC_ASSERT_ENUM(WebFrameLoadType::kInitialHistoryLoad, + kFrameLoadTypeInitialHistoryLoad); +STATIC_ASSERT_ENUM(WebFrameLoadType::kReloadBypassingCache, + kFrameLoadTypeReloadBypassingCache); + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/loader/frame_loader.h b/chromium/third_party/blink/renderer/core/loader/frame_loader.h new file mode 100644 index 00000000000..851a1bd2aeb --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/frame_loader.h @@ -0,0 +1,323 @@ +/* + * Copyright (C) 2006, 2007, 2008, 2009, 2011 Apple Inc. All rights reserved. + * Copyright (C) 2008, 2009 Torch Mobile Inc. All rights reserved. + * (http://www.torchmobile.com/) + * Copyright (C) Research In Motion Limited 2009. All rights reserved. + * Copyright (C) 2011 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: + * + * 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") 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 APPLE AND ITS 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 APPLE OR ITS 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_FRAME_LOADER_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_FRAME_LOADER_H_ + +#include "base/macros.h" +#include "third_party/blink/public/mojom/blob/blob_url_store.mojom-blink.h" +#include "third_party/blink/public/platform/web_insecure_request_policy.h" +#include "third_party/blink/public/web/commit_result.mojom-shared.h" +#include "third_party/blink/public/web/web_triggering_event_info.h" +#include "third_party/blink/renderer/core/core_export.h" +#include "third_party/blink/renderer/core/dom/icon_url.h" +#include "third_party/blink/renderer/core/execution_context/security_context.h" +#include "third_party/blink/renderer/core/frame/frame_types.h" +#include "third_party/blink/renderer/core/frame/sandbox_flags.h" +#include "third_party/blink/renderer/core/loader/frame_loader_state_machine.h" +#include "third_party/blink/renderer/core/loader/frame_loader_types.h" +#include "third_party/blink/renderer/core/loader/history_item.h" +#include "third_party/blink/renderer/core/loader/navigation_policy.h" +#include "third_party/blink/renderer/platform/heap/handle.h" +#include "third_party/blink/renderer/platform/instrumentation/tracing/traced_value.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource_loader_options.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource_request.h" +#include "third_party/blink/renderer/platform/wtf/forward.h" +#include "third_party/blink/renderer/platform/wtf/hash_set.h" + +#include <memory> + +namespace blink { + +class Document; +class DocumentLoader; +class Event; +class HTMLFormElement; +class LocalFrame; +class Frame; +class LocalFrameClient; +class ProgressTracker; +class ResourceError; +class SerializedScriptValue; +class SubstituteData; +struct FrameLoadRequest; + +CORE_EXPORT bool IsBackForwardLoadType(FrameLoadType); +CORE_EXPORT bool IsReloadLoadType(FrameLoadType); + +class CORE_EXPORT FrameLoader final { + DISALLOW_NEW(); + + public: + explicit FrameLoader(LocalFrame*); + ~FrameLoader(); + + void Init(); + + ResourceRequest ResourceRequestForReload( + FrameLoadType, + const KURL& override_url = KURL(), + ClientRedirectPolicy = ClientRedirectPolicy::kNotClientRedirect); + + ProgressTracker& Progress() const { return *progress_tracker_; } + + // Starts a load. It will eventually call StartLoad() or LoadInSameDocument(). + // For history navigations or reloads, an appropriate FrameLoadType should be + // given. Otherwise, FrameLoadTypeStandard should be used (and the final + // FrameLoadType will be computed). For history navigations, a history item + // and a HistoryLoadType should also be provided. + void Load(const FrameLoadRequest&, + FrameLoadType = kFrameLoadTypeStandard, + HistoryItem* = nullptr, + HistoryLoadType = kHistoryDifferentDocumentLoad); + + // Called when the browser process has asked this renderer process to commit a + // same document navigation in that frame. Returns false if the navigation + // cannot commit, true otherwise. + mojom::CommitResult CommitSameDocumentNavigation( + const KURL&, + FrameLoadType, + HistoryItem*, + ClientRedirectPolicy, + Document* origin_document = nullptr, + Event* triggering_event = nullptr); + + // Warning: stopAllLoaders can and will detach the LocalFrame out from under + // you. All callers need to either protect the LocalFrame or guarantee they + // won't in any way access the LocalFrame after stopAllLoaders returns. + void StopAllLoaders(); + + void ReplaceDocumentWhileExecutingJavaScriptURL(const String& source, + Document* owner_document); + + // Notifies the client that the initial empty document has been accessed, and + // thus it is no longer safe to show a provisional URL above the document + // without risking a URL spoof. The client must not call back into JavaScript. + void DidAccessInitialDocument(); + + DocumentLoader* GetDocumentLoader() const { return document_loader_.Get(); } + DocumentLoader* GetProvisionalDocumentLoader() const { + return provisional_document_loader_.Get(); + } + + void LoadFailed(DocumentLoader*, const ResourceError&); + + bool IsLoadingMainFrame() const; + + bool ShouldTreatURLAsSameAsCurrent(const KURL&) const; + bool ShouldTreatURLAsSrcdocDocument(const KURL&) const; + + void SetDefersLoading(bool); + + void DidExplicitOpen(); + + String UserAgent() const; + + void DispatchDidClearWindowObjectInMainWorld(); + void DispatchDidClearDocumentOfWindowObject(); + void DispatchDocumentElementAvailable(); + void RunScriptsAtDocumentElementAvailable(); + + // The following sandbox flags will be forced, regardless of changes to the + // sandbox attribute of any parent frames. + void ForceSandboxFlags(SandboxFlags flags) { forced_sandbox_flags_ |= flags; } + SandboxFlags EffectiveSandboxFlags() const; + + WebInsecureRequestPolicy GetInsecureRequestPolicy() const; + SecurityContext::InsecureNavigationsSet* InsecureNavigationsToUpgrade() const; + void ModifyRequestForCSP(ResourceRequest&, Document*) const; + + Frame* Opener(); + void SetOpener(LocalFrame*); + + const AtomicString& RequiredCSP() const { return required_csp_; } + void RecordLatestRequiredCSP(); + + void Detach(); + + void FinishedParsing(); + void DidFinishNavigation(); + + // This prepares the FrameLoader for the next commit. It will dispatch unload + // events, abort XHR requests and detach the document. Returns true if the + // frame is ready to receive the next commit, or false otherwise. + bool PrepareForCommit(); + + void CommitProvisionalLoad(); + + FrameLoaderStateMachine* StateMachine() const { return &state_machine_; } + + bool AllAncestorsAreComplete() const; // including this + + bool ShouldClose(bool is_reload = false); + void DispatchUnloadEvent(); + + bool AllowPlugins(ReasonForCallingAllowPlugins); + + void UpdateForSameDocumentNavigation(const KURL&, + SameDocumentNavigationSource, + scoped_refptr<SerializedScriptValue>, + HistoryScrollRestorationType, + FrameLoadType, + Document*); + + bool ShouldSerializeScrollAnchor(); + void SaveScrollAnchor(); + void SaveScrollState(); + void RestoreScrollPositionAndViewState(); + + // The navigation should only be continued immediately in this frame if this + // returns NavigationPolicyCurrentTab. + NavigationPolicy ShouldContinueForNavigationPolicy( + const ResourceRequest&, + Document* origin_document, + const SubstituteData&, + DocumentLoader*, + ContentSecurityPolicyDisposition, + NavigationType, + NavigationPolicy, + FrameLoadType, + bool is_client_redirect, + WebTriggeringEventInfo, + HTMLFormElement*, + mojom::blink::BlobURLTokenPtr); + + // Like ShouldContinueForNavigationPolicy, but should be used when following + // redirects. + NavigationPolicy ShouldContinueForRedirectNavigationPolicy( + const ResourceRequest&, + const SubstituteData&, + DocumentLoader*, + ContentSecurityPolicyDisposition, + NavigationType, + NavigationPolicy, + FrameLoadType, + bool is_client_redirect, + HTMLFormElement*); + + // Note: When a PlzNavigtate navigation is handled by the client, we will + // have created a dummy provisional DocumentLoader, so this will return true + // while the client handles the navigation. + bool HasProvisionalNavigation() const { + return GetProvisionalDocumentLoader(); + } + + void DetachProvisionalDocumentLoader(DocumentLoader*); + + void Trace(blink::Visitor*); + + static void SetReferrerForFrameRequest(FrameLoadRequest&); + static void UpgradeInsecureRequest(ResourceRequest&, Document*); + + void ClientDroppedNavigation(); + + private: + bool PrepareRequestForThisFrame(FrameLoadRequest&); + FrameLoadType DetermineFrameLoadType(const FrameLoadRequest&); + + SubstituteData DefaultSubstituteDataForURL(const KURL&); + + bool ShouldPerformFragmentNavigation(bool is_form_submission, + const String& http_method, + FrameLoadType, + const KURL&); + void ProcessFragment(const KURL&, FrameLoadType, LoadStartType); + + NavigationPolicy CheckLoadCanStart(FrameLoadRequest&, + FrameLoadType, + NavigationPolicy, + NavigationType); + void StartLoad(FrameLoadRequest&, + FrameLoadType, + NavigationPolicy, + HistoryItem*); + + void ClearInitialScrollState(); + + void LoadInSameDocument(const KURL&, + scoped_refptr<SerializedScriptValue> state_object, + FrameLoadType, + HistoryItem*, + ClientRedirectPolicy, + Document*); + void RestoreScrollPositionAndViewState(FrameLoadType, + HistoryLoadType, + HistoryItem::ViewState*, + HistoryScrollRestorationType); + + void ScheduleCheckCompleted(); + + void DetachDocumentLoader(Member<DocumentLoader>&); + + std::unique_ptr<TracedValue> ToTracedValue() const; + void TakeObjectSnapshot() const; + + DocumentLoader* CreateDocumentLoader(const ResourceRequest&, + const FrameLoadRequest&, + FrameLoadType, + NavigationType); + + LocalFrameClient* Client() const; + + Member<LocalFrame> frame_; + AtomicString required_csp_; + + // FIXME: These should be std::unique_ptr<T> to reduce build times and + // simplify header dependencies unless performance testing proves otherwise. + // Some of these could be lazily created for memory savings on devices. + mutable FrameLoaderStateMachine state_machine_; + + Member<ProgressTracker> progress_tracker_; + + // Document loaders for the three phases of frame loading. Note that while a + // new request is being loaded, the old document loader may still be + // referenced. E.g. while a new request is in the "policy" state, the old + // document loader may be consulted in particular as it makes sense to imply + // certain settings on the new loader. + Member<DocumentLoader> document_loader_; + Member<DocumentLoader> provisional_document_loader_; + + bool in_stop_all_loaders_; + bool in_restore_scroll_; + + SandboxFlags forced_sandbox_flags_; + + bool dispatching_did_clear_window_object_in_main_world_; + bool protect_provisional_loader_; + bool detached_; + + DISALLOW_COPY_AND_ASSIGN(FrameLoader); +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_FRAME_LOADER_H_ diff --git a/chromium/third_party/blink/renderer/core/loader/frame_loader_state_machine.cc b/chromium/third_party/blink/renderer/core/loader/frame_loader_state_machine.cc new file mode 100644 index 00000000000..147466e8bdb --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/frame_loader_state_machine.cc @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2010 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: + * + * 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. + * 3. 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 APPLE OR ITS 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/frame_loader_state_machine.h" + +#include "third_party/blink/renderer/platform/wtf/assertions.h" +#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h" + +namespace blink { + +FrameLoaderStateMachine::FrameLoaderStateMachine() + : state_(kCreatingInitialEmptyDocument) {} + +bool FrameLoaderStateMachine::CommittedFirstRealDocumentLoad() const { + return state_ >= kCommittedFirstRealLoad; +} + +bool FrameLoaderStateMachine::CreatingInitialEmptyDocument() const { + return state_ == kCreatingInitialEmptyDocument; +} + +bool FrameLoaderStateMachine::CommittedMultipleRealLoads() const { + return state_ == kCommittedMultipleRealLoads; +} + +bool FrameLoaderStateMachine::IsDisplayingInitialEmptyDocument() const { + return state_ >= kDisplayingInitialEmptyDocument && + state_ < kCommittedFirstRealLoad; +} + +void FrameLoaderStateMachine::AdvanceTo(State state) { + DCHECK_LT(state_, state); + state_ = state; +} + +String FrameLoaderStateMachine::ToString() const { + switch (state_) { + case kCreatingInitialEmptyDocument: + return "CreatingInitialEmptyDocument"; + case kDisplayingInitialEmptyDocument: + return "DisplayingInitialEmptyDocument"; + case kCommittedFirstRealLoad: + return "CommittedFirstRealLoad"; + case kCommittedMultipleRealLoads: + return "CommittedMultipleRealLoads"; + default: + NOTREACHED(); + } + return ""; +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/loader/frame_loader_state_machine.h b/chromium/third_party/blink/renderer/core/loader/frame_loader_state_machine.h new file mode 100644 index 00000000000..30913b05573 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/frame_loader_state_machine.h @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2010 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: + * + * 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. + * 3. 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 APPLE OR ITS 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_FRAME_LOADER_STATE_MACHINE_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_FRAME_LOADER_STATE_MACHINE_H_ + +#include "base/macros.h" +#include "third_party/blink/renderer/core/core_export.h" +#include "third_party/blink/renderer/platform/wtf/allocator.h" +#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h" + +namespace blink { + +// Encapsulates a state machine for FrameLoader. Note that this is different +// from FrameState, which stores the state of the current load that FrameLoader +// is executing. +class CORE_EXPORT FrameLoaderStateMachine { + DISALLOW_NEW(); + + public: + FrameLoaderStateMachine(); + + // Once a load has been committed, the state may alternate between + // CommittedFirstRealLoad and FirstLayoutDone. Otherwise, the states only go + // down the list. + enum State { + kCreatingInitialEmptyDocument, + kDisplayingInitialEmptyDocument, + kCommittedFirstRealLoad, + kCommittedMultipleRealLoads + }; + + bool CommittedFirstRealDocumentLoad() const; + bool CreatingInitialEmptyDocument() const; + bool IsDisplayingInitialEmptyDocument() const; + bool CommittedMultipleRealLoads() const; + void AdvanceTo(State); + + String ToString() const; + + private: + State state_; + + DISALLOW_COPY_AND_ASSIGN(FrameLoaderStateMachine); +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_FRAME_LOADER_STATE_MACHINE_H_ diff --git a/chromium/third_party/blink/renderer/core/loader/frame_loader_types.h b/chromium/third_party/blink/renderer/core/loader/frame_loader_types.h new file mode 100644 index 00000000000..99755a7e847 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/frame_loader_types.h @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2006 Apple Computer, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") 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 APPLE AND ITS 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 APPLE OR ITS 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_FRAME_LOADER_TYPES_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_FRAME_LOADER_TYPES_H_ + +namespace blink { + +// See WebFrameLoadType in public/web/WebFrameLoadType.h for details. +enum FrameLoadType { + kFrameLoadTypeStandard, + kFrameLoadTypeBackForward, + kFrameLoadTypeReload, + kFrameLoadTypeReplaceCurrentItem, + kFrameLoadTypeInitialInChildFrame, + kFrameLoadTypeInitialHistoryLoad, + kFrameLoadTypeReloadBypassingCache, +}; + +enum NavigationType { + kNavigationTypeLinkClicked, + kNavigationTypeFormSubmitted, + kNavigationTypeBackForward, + kNavigationTypeReload, + kNavigationTypeFormResubmitted, + kNavigationTypeOther +}; + +enum ShouldSendReferrer { kMaybeSendReferrer, kNeverSendReferrer }; + +enum ShouldSetOpener { kMaybeSetOpener, kNeverSetOpener }; + +enum ReasonForCallingAllowPlugins { + kAboutToInstantiatePlugin, + kNotAboutToInstantiatePlugin +}; + +enum LoadStartType { + kNavigationToDifferentDocument, + kNavigationWithinSameDocument +}; + +enum SameDocumentNavigationSource { + kSameDocumentNavigationDefault, + kSameDocumentNavigationHistoryApi, +}; + +enum HistoryLoadType { + kHistorySameDocumentLoad, + kHistoryDifferentDocumentLoad +}; + +enum HistoryCommitType { + kStandardCommit, + kBackForwardCommit, + kInitialCommitInChildFrame, + kHistoryInertCommit +}; + +enum HistoryScrollRestorationType { + kScrollRestorationAuto, + kScrollRestorationManual +}; + +enum class SavePreviousDocumentResources { + kNever, + kUntilOnDOMContentLoaded, + kUntilOnLoad +}; + +// This enum is used to index different kinds of single-page-application +// navigations for UMA enum histogram. New enum values can be added, but +// existing enums must never be renumbered or deleted and reused. +// This enum should be consistent with SinglePageAppNavigationType in +// tools/metrics/histograms/enums.xml. +enum SinglePageAppNavigationType { + kSPANavTypeHistoryPushStateOrReplaceState = 0, + kSPANavTypeSameDocumentBackwardOrForward = 1, + kSPANavTypeOtherFragmentNavigation = 2, + kSPANavTypeCount +}; +} // namespace blink + +#endif diff --git a/chromium/third_party/blink/renderer/core/loader/history_item.cc b/chromium/third_party/blink/renderer/core/loader/history_item.cc new file mode 100644 index 00000000000..4be64ce2a29 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/history_item.cc @@ -0,0 +1,181 @@ +/* + * Copyright (C) 2005, 2006, 2008, 2011 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. + */ + +#include "third_party/blink/renderer/core/loader/history_item.h" + +#include <memory> +#include <utility> + +#include "third_party/blink/renderer/core/html/forms/form_controller.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource_request.h" +#include "third_party/blink/renderer/platform/weborigin/security_policy.h" +#include "third_party/blink/renderer/platform/wtf/assertions.h" +#include "third_party/blink/renderer/platform/wtf/text/cstring.h" +#include "third_party/blink/renderer/platform/wtf/time.h" + +namespace blink { + +static long long GenerateSequenceNumber() { + // Initialize to the current time to reduce the likelihood of generating + // identifiers that overlap with those from past/future browser sessions. + static long long next = static_cast<long long>(CurrentTime() * 1000000.0); + return ++next; +} + +HistoryItem::HistoryItem() + : item_sequence_number_(GenerateSequenceNumber()), + document_sequence_number_(GenerateSequenceNumber()), + scroll_restoration_type_(kScrollRestorationAuto) {} + +HistoryItem::~HistoryItem() = default; + +const String& HistoryItem::UrlString() const { + return url_string_; +} + +KURL HistoryItem::Url() const { + return KURL(url_string_); +} + +const Referrer& HistoryItem::GetReferrer() const { + return referrer_; +} + +void HistoryItem::SetURLString(const String& url_string) { + if (url_string_ != url_string) + url_string_ = url_string; +} + +void HistoryItem::SetURL(const KURL& url) { + SetURLString(url.GetString()); +} + +void HistoryItem::SetReferrer(const Referrer& referrer) { + // This should be a CHECK. + referrer_ = SecurityPolicy::GenerateReferrer(referrer.referrer_policy, Url(), + referrer.referrer); +} + +void HistoryItem::SetVisualViewportScrollOffset(const ScrollOffset& offset) { + if (!view_state_) + view_state_ = std::make_unique<ViewState>(); + view_state_->visual_viewport_scroll_offset_ = offset; +} + +void HistoryItem::SetScrollOffset(const ScrollOffset& offset) { + if (!view_state_) + view_state_ = std::make_unique<ViewState>(); + view_state_->scroll_offset_ = offset; +} + +void HistoryItem::SetPageScaleFactor(float scale_factor) { + if (!view_state_) + view_state_ = std::make_unique<ViewState>(); + view_state_->page_scale_factor_ = scale_factor; +} + +void HistoryItem::SetScrollAnchorData( + const ScrollAnchorData& scroll_anchor_data) { + if (!view_state_) + view_state_ = std::make_unique<ViewState>(); + view_state_->scroll_anchor_data_ = scroll_anchor_data; +} + +void HistoryItem::SetDocumentState(const Vector<String>& state) { + DCHECK(!document_state_); + document_state_vector_ = state; +} + +void HistoryItem::SetDocumentState(DocumentState* state) { + document_state_ = state; +} + +const Vector<String>& HistoryItem::GetDocumentState() { + if (document_state_) + document_state_vector_ = document_state_->ToStateVector(); + return document_state_vector_; +} + +Vector<String> HistoryItem::GetReferencedFilePaths() { + return FormController::GetReferencedFilePaths(GetDocumentState()); +} + +void HistoryItem::ClearDocumentState() { + document_state_.Clear(); + document_state_vector_.clear(); +} + +void HistoryItem::SetStateObject(scoped_refptr<SerializedScriptValue> object) { + state_object_ = std::move(object); +} + +const AtomicString& HistoryItem::FormContentType() const { + return form_content_type_; +} + +void HistoryItem::SetFormInfoFromRequest(const ResourceRequest& request) { + if (DeprecatedEqualIgnoringCase(request.HttpMethod(), "POST")) { + // FIXME: Eventually we have to make this smart enough to handle the case + // where we have a stream for the body to handle the "data interspersed with + // files" feature. + form_data_ = request.HttpBody(); + form_content_type_ = request.HttpContentType(); + } else { + form_data_ = nullptr; + form_content_type_ = g_null_atom; + } +} + +void HistoryItem::SetFormData(scoped_refptr<EncodedFormData> form_data) { + form_data_ = std::move(form_data); +} + +void HistoryItem::SetFormContentType(const AtomicString& form_content_type) { + form_content_type_ = form_content_type; +} + +EncodedFormData* HistoryItem::FormData() { + return form_data_.get(); +} + +ResourceRequest HistoryItem::GenerateResourceRequest( + mojom::FetchCacheMode cache_mode) { + ResourceRequest request(url_string_); + request.SetHTTPReferrer(referrer_); + request.SetCacheMode(cache_mode); + if (form_data_) { + request.SetHTTPMethod(HTTPNames::POST); + request.SetHTTPBody(form_data_); + request.SetHTTPContentType(form_content_type_); + request.SetHTTPOriginToMatchReferrerIfNeeded(); + } + return request; +} + +void HistoryItem::Trace(blink::Visitor* visitor) { + visitor->Trace(document_state_); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/loader/history_item.h b/chromium/third_party/blink/renderer/core/loader/history_item.h new file mode 100644 index 00000000000..2c3263e35f6 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/history_item.h @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2006, 2008, 2011 Apple Inc. All rights reserved. + * Copyright (C) 2012 Research In Motion Limited. 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_HISTORY_ITEM_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_HISTORY_ITEM_H_ + +#include "third_party/blink/public/platform/modules/fetch/fetch_api_request.mojom-shared.h" +#include "third_party/blink/public/platform/web_scroll_anchor_data.h" +#include "third_party/blink/renderer/bindings/core/v8/serialization/serialized_script_value.h" +#include "third_party/blink/renderer/core/core_export.h" +#include "third_party/blink/renderer/core/loader/frame_loader_types.h" +#include "third_party/blink/renderer/platform/geometry/float_point.h" +#include "third_party/blink/renderer/platform/geometry/int_point.h" +#include "third_party/blink/renderer/platform/geometry/layout_point.h" +#include "third_party/blink/renderer/platform/heap/handle.h" +#include "third_party/blink/renderer/platform/scroll/scroll_types.h" +#include "third_party/blink/renderer/platform/weborigin/referrer.h" +#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h" + +namespace blink { + +class DocumentState; +class EncodedFormData; +class KURL; +class ResourceRequest; + +class CORE_EXPORT HistoryItem final + : public GarbageCollectedFinalized<HistoryItem> { + public: + static HistoryItem* Create() { return new HistoryItem; } + ~HistoryItem(); + + const String& UrlString() const; + KURL Url() const; + + const Referrer& GetReferrer() const; + + EncodedFormData* FormData(); + const AtomicString& FormContentType() const; + + class ViewState { + public: + ViewState() : page_scale_factor_(0) {} + ViewState(const ViewState&) = default; + + ScrollOffset visual_viewport_scroll_offset_; + ScrollOffset scroll_offset_; + float page_scale_factor_; + ScrollAnchorData scroll_anchor_data_; + }; + + ViewState* GetViewState() const { return view_state_.get(); } + void ClearViewState() { view_state_.reset(); } + void CopyViewStateFrom(HistoryItem* other) { + if (other->view_state_) + view_state_ = std::make_unique<ViewState>(*other->view_state_.get()); + else + view_state_.reset(); + } + + void SetVisualViewportScrollOffset(const ScrollOffset&); + void SetScrollOffset(const ScrollOffset&); + void SetPageScaleFactor(float); + + Vector<String> GetReferencedFilePaths(); + const Vector<String>& GetDocumentState(); + void SetDocumentState(const Vector<String>&); + void SetDocumentState(DocumentState*); + void ClearDocumentState(); + + void SetURL(const KURL&); + void SetURLString(const String&); + void SetReferrer(const Referrer&); + + void SetStateObject(scoped_refptr<SerializedScriptValue>); + SerializedScriptValue* StateObject() const { return state_object_.get(); } + + void SetItemSequenceNumber(long long number) { + item_sequence_number_ = number; + } + long long ItemSequenceNumber() const { return item_sequence_number_; } + + void SetDocumentSequenceNumber(long long number) { + document_sequence_number_ = number; + } + long long DocumentSequenceNumber() const { return document_sequence_number_; } + + void SetScrollRestorationType(HistoryScrollRestorationType type) { + scroll_restoration_type_ = type; + } + HistoryScrollRestorationType ScrollRestorationType() { + return scroll_restoration_type_; + } + + void SetScrollAnchorData(const ScrollAnchorData&); + + void SetFormInfoFromRequest(const ResourceRequest&); + void SetFormData(scoped_refptr<EncodedFormData>); + void SetFormContentType(const AtomicString&); + + ResourceRequest GenerateResourceRequest(mojom::FetchCacheMode); + + void Trace(blink::Visitor*); + + private: + HistoryItem(); + + String url_string_; + Referrer referrer_; + + Vector<String> document_state_vector_; + Member<DocumentState> document_state_; + + std::unique_ptr<ViewState> view_state_; + + // If two HistoryItems have the same item sequence number, then they are + // clones of one another. Traversing history from one such HistoryItem to + // another is a no-op. HistoryItem clones are created for parent and + // sibling frames when only a subframe navigates. + int64_t item_sequence_number_; + + // If two HistoryItems have the same document sequence number, then they + // refer to the same instance of a document. Traversing history from one + // such HistoryItem to another preserves the document. + int64_t document_sequence_number_; + + // Type of the scroll restoration for the history item determines if scroll + // position should be restored when it is loaded during history traversal. + HistoryScrollRestorationType scroll_restoration_type_; + + // Support for HTML5 History + scoped_refptr<SerializedScriptValue> state_object_; + + // info used to repost form data + scoped_refptr<EncodedFormData> form_data_; + AtomicString form_content_type_; +}; // class HistoryItem + +} // namespace blink + +#endif // HISTORYITEM_H diff --git a/chromium/third_party/blink/renderer/core/loader/http_equiv.cc b/chromium/third_party/blink/renderer/core/loader/http_equiv.cc new file mode 100644 index 00000000000..5a936c70d53 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/http_equiv.cc @@ -0,0 +1,143 @@ +// 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/http_equiv.h" + +#include "third_party/blink/renderer/core/css/style_engine.h" +#include "third_party/blink/renderer/core/dom/document.h" +#include "third_party/blink/renderer/core/dom/scriptable_document_parser.h" +#include "third_party/blink/renderer/core/frame/csp/content_security_policy.h" +#include "third_party/blink/renderer/core/frame/deprecation.h" +#include "third_party/blink/renderer/core/frame/local_frame.h" +#include "third_party/blink/renderer/core/frame/settings.h" +#include "third_party/blink/renderer/core/frame/use_counter.h" +#include "third_party/blink/renderer/core/inspector/console_message.h" +#include "third_party/blink/renderer/core/loader/document_loader.h" +#include "third_party/blink/renderer/core/loader/private/frame_client_hints_preferences_context.h" +#include "third_party/blink/renderer/core/origin_trials/origin_trial_context.h" +#include "third_party/blink/renderer/platform/loader/fetch/client_hints_preferences.h" +#include "third_party/blink/renderer/platform/network/http_names.h" +#include "third_party/blink/renderer/platform/network/http_parsers.h" +#include "third_party/blink/renderer/platform/weborigin/kurl.h" +#include "third_party/blink/renderer/platform/weborigin/security_violation_reporting_policy.h" + +namespace blink { + +void HttpEquiv::Process(Document& document, + const AtomicString& equiv, + const AtomicString& content, + bool in_document_head_element, + Element* element) { + DCHECK(!equiv.IsNull()); + DCHECK(!content.IsNull()); + + if (EqualIgnoringASCIICase(equiv, "default-style")) { + ProcessHttpEquivDefaultStyle(document, content); + } else if (EqualIgnoringASCIICase(equiv, "refresh")) { + ProcessHttpEquivRefresh(document, content, element); + } else if (EqualIgnoringASCIICase(equiv, "set-cookie")) { + ProcessHttpEquivSetCookie(document, content, element); + } else if (EqualIgnoringASCIICase(equiv, "content-language")) { + document.SetContentLanguage(content); + } else if (EqualIgnoringASCIICase(equiv, "x-dns-prefetch-control")) { + document.ParseDNSPrefetchControlHeader(content); + } else if (EqualIgnoringASCIICase(equiv, "x-frame-options")) { + document.AddConsoleMessage(ConsoleMessage::Create( + kSecurityMessageSource, kErrorMessageLevel, + "X-Frame-Options may only be set via an HTTP header sent along with a " + "document. It may not be set inside <meta>.")); + } else if (EqualIgnoringASCIICase(equiv, "accept-ch")) { + ProcessHttpEquivAcceptCH(document, content); + } else if (EqualIgnoringASCIICase(equiv, "content-security-policy") || + EqualIgnoringASCIICase(equiv, + "content-security-policy-report-only")) { + if (in_document_head_element) + ProcessHttpEquivContentSecurityPolicy(document, equiv, content); + else + document.GetContentSecurityPolicy()->ReportMetaOutsideHead(content); + } else if (EqualIgnoringASCIICase(equiv, HTTPNames::Origin_Trial)) { + if (in_document_head_element) + OriginTrialContext::FromOrCreate(&document)->AddToken(content); + } +} + +void HttpEquiv::ProcessHttpEquivContentSecurityPolicy( + Document& document, + const AtomicString& equiv, + const AtomicString& content) { + if (document.ImportLoader()) + return; + if (document.GetSettings() && document.GetSettings()->BypassCSP()) + return; + if (EqualIgnoringASCIICase(equiv, "content-security-policy")) { + document.GetContentSecurityPolicy()->DidReceiveHeader( + content, kContentSecurityPolicyHeaderTypeEnforce, + kContentSecurityPolicyHeaderSourceMeta); + } else if (EqualIgnoringASCIICase(equiv, + "content-security-policy-report-only")) { + document.GetContentSecurityPolicy()->DidReceiveHeader( + content, kContentSecurityPolicyHeaderTypeReport, + kContentSecurityPolicyHeaderSourceMeta); + } else { + NOTREACHED(); + } +} + +void HttpEquiv::ProcessHttpEquivAcceptCH(Document& document, + const AtomicString& content) { + if (!document.GetFrame()) + return; + + UseCounter::Count(document, WebFeature::kClientHintsMetaAcceptCH); + FrameClientHintsPreferencesContext hints_context(document.GetFrame()); + document.GetClientHintsPreferences().UpdateFromAcceptClientHintsHeader( + content, document.Url(), &hints_context); +} + +void HttpEquiv::ProcessHttpEquivDefaultStyle(Document& document, + const AtomicString& content) { + document.GetStyleEngine().SetHttpDefaultStyle(content); +} + +void HttpEquiv::ProcessHttpEquivRefresh(Document& document, + const AtomicString& content, + Element* element) { + UseCounter::Count(document, WebFeature::kMetaRefresh); + if (!document.GetContentSecurityPolicy()->AllowInlineScript( + element, NullURL(), "", OrdinalNumber(), "", + ContentSecurityPolicy::InlineType::kBlock, + SecurityViolationReportingPolicy::kSuppressReporting)) { + UseCounter::Count(document, + WebFeature::kMetaRefreshWhenCSPBlocksInlineScript); + } + + document.MaybeHandleHttpRefresh(content, Document::kHttpRefreshFromMetaTag); +} + +void HttpEquiv::ProcessHttpEquivSetCookie(Document& document, + const AtomicString& content, + Element* element) { + Deprecation::CountDeprecation(document, WebFeature::kMetaSetCookie); + + if (!document.GetContentSecurityPolicy()->AllowInlineScript( + element, NullURL(), "", OrdinalNumber(), "", + ContentSecurityPolicy::InlineType::kBlock, + SecurityViolationReportingPolicy::kSuppressReporting)) { + UseCounter::Count(document, + WebFeature::kMetaSetCookieWhenCSPBlocksInlineScript); + } + + if (!RuntimeEnabledFeatures::BlockMetaSetCookieEnabled()) { + // Exception (for sandboxed documents) ignored. + document.setCookie(content, IGNORE_EXCEPTION_FOR_TESTING); + return; + } + + document.AddConsoleMessage(ConsoleMessage::Create( + kSecurityMessageSource, kErrorMessageLevel, + String::Format("Blocked setting the `%s` cookie from a `<meta>` tag.", + content.Utf8().data()))); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/loader/http_equiv.h b/chromium/third_party/blink/renderer/core/loader/http_equiv.h new file mode 100644 index 00000000000..8c7cd0a2aa4 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/http_equiv.h @@ -0,0 +1,52 @@ +// 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_HTTP_EQUIV_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_HTTP_EQUIV_H_ + +#include "third_party/blink/renderer/platform/wtf/allocator.h" +#include "third_party/blink/renderer/platform/wtf/text/atomic_string.h" + +namespace blink { + +class Document; +class Element; + +/** + * Handles a HTTP header equivalent set by a meta tag using + * <meta http-equiv="..." content="...">. This is called when a meta tag is + * encountered during document parsing, and also when a script dynamically + * changes or adds a meta tag. This enables scripts to use meta tags to perform + * refreshes and set expiry dates in addition to them being specified in a HTML + * file. + */ +class HttpEquiv { + STATIC_ONLY(HttpEquiv); + + public: + static void Process(Document&, + const AtomicString& equiv, + const AtomicString& content, + bool in_document_head_element, + Element*); + + private: + static void ProcessHttpEquivDefaultStyle(Document&, + const AtomicString& content); + static void ProcessHttpEquivRefresh(Document&, + const AtomicString& content, + Element*); + static void ProcessHttpEquivSetCookie(Document&, + const AtomicString& content, + Element*); + static void ProcessHttpEquivContentSecurityPolicy( + Document&, + const AtomicString& equiv, + const AtomicString& content); + static void ProcessHttpEquivAcceptCH(Document&, const AtomicString& content); +}; + +} // namespace blink + +#endif diff --git a/chromium/third_party/blink/renderer/core/loader/idleness_detector.cc b/chromium/third_party/blink/renderer/core/loader/idleness_detector.cc new file mode 100644 index 00000000000..62499cfbe49 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/idleness_detector.cc @@ -0,0 +1,198 @@ +// Copyright 2017 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/idleness_detector.h" + +#include "services/resource_coordinator/public/cpp/resource_coordinator_features.h" +#include "third_party/blink/public/platform/platform.h" +#include "third_party/blink/public/platform/task_type.h" +#include "third_party/blink/renderer/core/dom/document.h" +#include "third_party/blink/renderer/core/frame/local_frame.h" +#include "third_party/blink/renderer/core/probe/core_probes.h" +#include "third_party/blink/renderer/platform/instrumentation/resource_coordinator/frame_resource_coordinator.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h" + +namespace blink { + +constexpr TimeDelta IdlenessDetector::kNetworkQuietWindow; +constexpr TimeDelta IdlenessDetector::kNetworkQuietWatchdog; + +void IdlenessDetector::Shutdown() { + Stop(); + local_frame_ = nullptr; +} + +void IdlenessDetector::WillCommitLoad() { + in_network_2_quiet_period_ = false; + in_network_0_quiet_period_ = false; + network_2_quiet_ = TimeTicks(); + network_0_quiet_ = TimeTicks(); + network_2_quiet_start_time_ = TimeTicks(); + network_0_quiet_start_time_ = TimeTicks(); +} + +void IdlenessDetector::DomContentLoadedEventFired() { + if (!local_frame_) + return; + + if (!task_observer_added_) { + Platform::Current()->CurrentThread()->AddTaskTimeObserver(this); + task_observer_added_ = true; + } + + in_network_2_quiet_period_ = true; + in_network_0_quiet_period_ = true; + network_2_quiet_ = TimeTicks(); + network_0_quiet_ = TimeTicks(); + + if (::resource_coordinator::IsPageAlmostIdleSignalEnabled()) { + if (auto* frame_resource_coordinator = + local_frame_->GetFrameResourceCoordinator()) { + frame_resource_coordinator->SetNetworkAlmostIdle(false); + } + } + OnDidLoadResource(); +} + +void IdlenessDetector::OnWillSendRequest(ResourceFetcher* fetcher) { + // If |fetcher| is not the current fetcher of the Document, then that means + // it's a new navigation, bail out in this case since it shouldn't affect the + // current idleness of the local frame. + if (!local_frame_ || fetcher != local_frame_->GetDocument()->Fetcher()) + return; + + // When OnWillSendRequest is called, the new loader hasn't been added to the + // fetcher, thus we need to add 1 as the total request count. + int request_count = fetcher->ActiveRequestCount() + 1; + // If we are above the allowed number of active requests, reset timers. + if (in_network_2_quiet_period_ && request_count > 2) + network_2_quiet_ = TimeTicks(); + if (in_network_0_quiet_period_ && request_count > 0) + network_0_quiet_ = TimeTicks(); +} + +// This function is called when the number of active connections is decreased. +// Note that the number of active connections doesn't decrease monotonically. +void IdlenessDetector::OnDidLoadResource() { + if (!local_frame_) + return; + + // Document finishes parsing after DomContentLoadedEventEnd is fired, + // check the status in order to avoid false signals. + if (!local_frame_->GetDocument()->HasFinishedParsing()) + return; + + // If we already reported quiet time, bail out. + if (!in_network_0_quiet_period_ && !in_network_2_quiet_period_) + return; + + int request_count = + local_frame_->GetDocument()->Fetcher()->ActiveRequestCount(); + // If we did not achieve either 0 or 2 active connections, bail out. + if (request_count > 2) + return; + + TimeTicks timestamp = CurrentTimeTicks(); + // Arriving at =2 updates the quiet_2 base timestamp. + // Arriving at <2 sets the quiet_2 base timestamp only if + // it was not already set. + if (request_count == 2 && in_network_2_quiet_period_) { + network_2_quiet_ = timestamp; + network_2_quiet_start_time_ = timestamp; + } else if (request_count < 2 && in_network_2_quiet_period_ && + network_2_quiet_.is_null()) { + network_2_quiet_ = timestamp; + network_2_quiet_start_time_ = timestamp; + } + + if (request_count == 0 && in_network_0_quiet_period_) { + network_0_quiet_ = timestamp; + network_0_quiet_start_time_ = timestamp; + } + + if (!network_quiet_timer_.IsActive()) { + network_quiet_timer_.StartOneShot(kNetworkQuietWatchdog, FROM_HERE); + } +} + +TimeTicks IdlenessDetector::GetNetworkAlmostIdleTime() { + return network_2_quiet_start_time_; +} + +TimeTicks IdlenessDetector::GetNetworkIdleTime() { + return network_0_quiet_start_time_; +} + +void IdlenessDetector::WillProcessTask(double start_time_seconds) { + // If we have idle time and we are kNetworkQuietWindow seconds past it, emit + // idle signals. + TimeTicks start_time = TimeTicksFromSeconds(start_time_seconds); + DocumentLoader* loader = local_frame_->Loader().GetDocumentLoader(); + if (in_network_2_quiet_period_ && !network_2_quiet_.is_null() && + start_time - network_2_quiet_ > kNetworkQuietWindow) { + probe::lifecycleEvent(local_frame_, loader, "networkAlmostIdle", + TimeTicksInSeconds(network_2_quiet_start_time_)); + if (::resource_coordinator::IsPageAlmostIdleSignalEnabled()) { + if (auto* frame_resource_coordinator = + local_frame_->GetFrameResourceCoordinator()) { + frame_resource_coordinator->SetNetworkAlmostIdle(true); + } + } + local_frame_->GetDocument()->Fetcher()->OnNetworkQuiet(); + in_network_2_quiet_period_ = false; + network_2_quiet_ = TimeTicks(); + } + + if (in_network_0_quiet_period_ && !network_0_quiet_.is_null() && + start_time - network_0_quiet_ > kNetworkQuietWindow) { + probe::lifecycleEvent(local_frame_, loader, "networkIdle", + TimeTicksInSeconds(network_0_quiet_start_time_)); + in_network_0_quiet_period_ = false; + network_0_quiet_ = TimeTicks(); + } + + if (!in_network_0_quiet_period_ && !in_network_2_quiet_period_) + Stop(); +} + +void IdlenessDetector::DidProcessTask(double start_time_seconds, + double end_time_seconds) { + TimeTicks start_time = TimeTicksFromSeconds(start_time_seconds); + TimeTicks end_time = TimeTicksFromSeconds(end_time_seconds); + + // Shift idle timestamps with the duration of the task, we were not idle. + if (in_network_2_quiet_period_ && !network_2_quiet_.is_null()) + network_2_quiet_ += end_time - start_time; + if (in_network_0_quiet_period_ && !network_0_quiet_.is_null()) + network_0_quiet_ += end_time - start_time; +} + +IdlenessDetector::IdlenessDetector(LocalFrame* local_frame) + : local_frame_(local_frame), + task_observer_added_(false), + network_quiet_timer_(local_frame->GetTaskRunner(TaskType::kUnthrottled), + this, + &IdlenessDetector::NetworkQuietTimerFired) {} + +void IdlenessDetector::Stop() { + network_quiet_timer_.Stop(); + if (!task_observer_added_) + return; + Platform::Current()->CurrentThread()->RemoveTaskTimeObserver(this); + task_observer_added_ = false; +} + +void IdlenessDetector::NetworkQuietTimerFired(TimerBase*) { + // TODO(lpy) Reduce the number of timers. + if ((in_network_0_quiet_period_ && !network_0_quiet_.is_null()) || + (in_network_2_quiet_period_ && !network_2_quiet_.is_null())) { + network_quiet_timer_.StartOneShot(kNetworkQuietWatchdog, FROM_HERE); + } +} + +void IdlenessDetector::Trace(blink::Visitor* visitor) { + visitor->Trace(local_frame_); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/loader/idleness_detector.h b/chromium/third_party/blink/renderer/core/loader/idleness_detector.h new file mode 100644 index 00000000000..acf50d9a266 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/idleness_detector.h @@ -0,0 +1,79 @@ +// Copyright 2017 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_IDLENESS_DETECTOR_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_IDLENESS_DETECTOR_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/scheduler/base/task_time_observer.h" +#include "third_party/blink/renderer/platform/timer.h" + +namespace blink { + +class LocalFrame; +class ResourceFetcher; + +// IdlenessDetector observes network request count everytime a load is +// finshed after DOMContentLoadedEventEnd is fired, and emit network almost idle +// signal when there are no more than 2 network connection active in 0.5 second, +// and emit network idle signal when there is 0 network connection active in 0.5 +// second. +class CORE_EXPORT IdlenessDetector + : public GarbageCollectedFinalized<IdlenessDetector>, + public scheduler::TaskTimeObserver { + public: + explicit IdlenessDetector(LocalFrame*); + + void Shutdown(); + void WillCommitLoad(); + void DomContentLoadedEventFired(); + // TODO(lpy) Don't need to pass in fetcher once the command line of disabling + // PlzNavigate is removed. + void OnWillSendRequest(ResourceFetcher*); + void OnDidLoadResource(); + + TimeTicks GetNetworkAlmostIdleTime(); + TimeTicks GetNetworkIdleTime(); + + void Trace(blink::Visitor*); + + private: + friend class IdlenessDetectorTest; + + // The page is quiet if there are no more than 2 active network requests for + // this duration of time. + static constexpr TimeDelta kNetworkQuietWindow = + TimeDelta::FromMilliseconds(500); + static constexpr TimeDelta kNetworkQuietWatchdog = TimeDelta::FromSeconds(2); + static constexpr int kNetworkQuietMaximumConnections = 2; + + // scheduler::TaskTimeObserver implementation + void WillProcessTask(double start_time) override; + void DidProcessTask(double start_time, double end_time) override; + + void Stop(); + void NetworkQuietTimerFired(TimerBase*); + + Member<LocalFrame> local_frame_; + bool task_observer_added_; + + bool in_network_0_quiet_period_ = true; + bool in_network_2_quiet_period_ = true; + + // Store the accumulated time of network quiet. + TimeTicks network_0_quiet_; + TimeTicks network_2_quiet_; + // Record the actual start time of network quiet. + TimeTicks network_0_quiet_start_time_; + TimeTicks network_2_quiet_start_time_; + TaskRunnerTimer<IdlenessDetector> network_quiet_timer_; + + DISALLOW_COPY_AND_ASSIGN(IdlenessDetector); +}; + +} // namespace blink + +#endif diff --git a/chromium/third_party/blink/renderer/core/loader/idleness_detector_test.cc b/chromium/third_party/blink/renderer/core/loader/idleness_detector_test.cc new file mode 100644 index 00000000000..0e87fdba3dd --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/idleness_detector_test.cc @@ -0,0 +1,91 @@ +// Copyright 2017 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/idleness_detector.h" + +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/blink/renderer/core/testing/page_test_base.h" +#include "third_party/blink/renderer/platform/testing/testing_platform_support_with_mock_scheduler.h" + +namespace blink { + +class IdlenessDetectorTest : public PageTestBase { + protected: + void SetUp() override { + platform_time_ = 1; + platform_->AdvanceClockSeconds(platform_time_); + PageTestBase::SetUp(); + } + + IdlenessDetector* Detector() { return GetFrame().GetIdlenessDetector(); } + + bool IsNetworkQuietTimerActive() { + return Detector()->network_quiet_timer_.IsActive(); + } + + bool HadNetworkQuiet() { + return !Detector()->in_network_2_quiet_period_ && + !Detector()->in_network_0_quiet_period_; + } + + void WillProcessTask(double start_time) { + DCHECK(start_time >= platform_time_); + platform_->AdvanceClockSeconds(start_time - platform_time_); + platform_time_ = start_time; + Detector()->WillProcessTask(start_time); + } + + void DidProcessTask(double start_time, double end_time) { + DCHECK(start_time < end_time); + platform_->AdvanceClockSeconds(end_time - start_time); + platform_time_ = end_time; + Detector()->DidProcessTask(start_time, end_time); + } + + protected: + ScopedTestingPlatformSupport<TestingPlatformSupportWithMockScheduler> + platform_; + + private: + double platform_time_; +}; + +TEST_F(IdlenessDetectorTest, NetworkQuietBasic) { + EXPECT_TRUE(IsNetworkQuietTimerActive()); + + WillProcessTask(1); + DidProcessTask(1, 1.01); + + WillProcessTask(1.52); + EXPECT_TRUE(HadNetworkQuiet()); + DidProcessTask(1.52, 1.53); +} + +TEST_F(IdlenessDetectorTest, NetworkQuietWithLongTask) { + EXPECT_TRUE(IsNetworkQuietTimerActive()); + + WillProcessTask(1); + DidProcessTask(1, 1.01); + + WillProcessTask(1.02); + DidProcessTask(1.02, 1.6); + EXPECT_FALSE(HadNetworkQuiet()); + + WillProcessTask(2.11); + EXPECT_TRUE(HadNetworkQuiet()); + DidProcessTask(2.11, 2.12); +} + +TEST_F(IdlenessDetectorTest, NetworkQuietWatchdogTimerFired) { + EXPECT_TRUE(IsNetworkQuietTimerActive()); + + WillProcessTask(1); + DidProcessTask(1, 1.01); + + platform_->RunForPeriodSeconds(3); + EXPECT_FALSE(IsNetworkQuietTimerActive()); + EXPECT_TRUE(HadNetworkQuiet()); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/loader/image_loader.cc b/chromium/third_party/blink/renderer/core/loader/image_loader.cc new file mode 100644 index 00000000000..8c6f2406e97 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/image_loader.cc @@ -0,0 +1,838 @@ +/* + * Copyright (C) 1999 Lars Knoll (knoll@kde.org) + * (C) 1999 Antti Koivisto (koivisto@kde.org) + * Copyright (C) 2004, 2005, 2006, 2007, 2009, 2010 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/image_loader.h" + +#include <memory> +#include <utility> + +#include "third_party/blink/public/platform/modules/fetch/fetch_api_request.mojom-shared.h" +#include "third_party/blink/public/platform/web_client_hints_type.h" +#include "third_party/blink/public/platform/web_url_request.h" +#include "third_party/blink/renderer/bindings/core/v8/exception_state.h" +#include "third_party/blink/renderer/bindings/core/v8/script_controller.h" +#include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h" +#include "third_party/blink/renderer/core/dom/document.h" +#include "third_party/blink/renderer/core/dom/element.h" +#include "third_party/blink/renderer/core/dom/events/event.h" +#include "third_party/blink/renderer/core/dom/increment_load_event_delay_count.h" +#include "third_party/blink/renderer/core/frame/local_frame.h" +#include "third_party/blink/renderer/core/frame/settings.h" +#include "third_party/blink/renderer/core/frame/use_counter.h" +#include "third_party/blink/renderer/core/html/cross_origin_attribute.h" +#include "third_party/blink/renderer/core/html/html_image_element.h" +#include "third_party/blink/renderer/core/html/parser/html_parser_idioms.h" +#include "third_party/blink/renderer/core/layout/layout_image.h" +#include "third_party/blink/renderer/core/layout/layout_video.h" +#include "third_party/blink/renderer/core/layout/svg/layout_svg_image.h" +#include "third_party/blink/renderer/core/probe/core_probes.h" +#include "third_party/blink/renderer/core/svg/graphics/svg_image.h" +#include "third_party/blink/renderer/platform/bindings/microtask.h" +#include "third_party/blink/renderer/platform/bindings/script_state.h" +#include "third_party/blink/renderer/platform/bindings/v8_per_isolate_data.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_loading_log.h" +#include "third_party/blink/renderer/platform/weborigin/security_origin.h" +#include "third_party/blink/renderer/platform/weborigin/security_policy.h" + +namespace blink { + +static ImageLoader::BypassMainWorldBehavior ShouldBypassMainWorldCSP( + ImageLoader* loader) { + DCHECK(loader); + DCHECK(loader->GetElement()); + if (loader->GetElement()->GetDocument().GetFrame() && + loader->GetElement() + ->GetDocument() + .GetFrame() + ->GetScriptController() + .ShouldBypassMainWorldCSP()) + return ImageLoader::kBypassMainWorldCSP; + return ImageLoader::kDoNotBypassMainWorldCSP; +} + +class ImageLoader::Task { + public: + static std::unique_ptr<Task> Create(ImageLoader* loader, + UpdateFromElementBehavior update_behavior, + ReferrerPolicy referrer_policy) { + return std::make_unique<Task>(loader, update_behavior, referrer_policy); + } + + Task(ImageLoader* loader, + UpdateFromElementBehavior update_behavior, + ReferrerPolicy referrer_policy) + : loader_(loader), + should_bypass_main_world_csp_(ShouldBypassMainWorldCSP(loader)), + update_behavior_(update_behavior), + weak_factory_(this), + referrer_policy_(referrer_policy) { + ExecutionContext& context = loader_->GetElement()->GetDocument(); + probe::AsyncTaskScheduled(&context, "Image", this); + v8::Isolate* isolate = V8PerIsolateData::MainThreadIsolate(); + v8::HandleScope scope(isolate); + // If we're invoked from C++ without a V8 context on the stack, we should + // run the microtask in the context of the element's document's main world. + if (!isolate->GetCurrentContext().IsEmpty()) { + script_state_ = ScriptState::Current(isolate); + } else { + script_state_ = ToScriptStateForMainWorld( + loader->GetElement()->GetDocument().GetFrame()); + DCHECK(script_state_); + } + request_url_ = + loader->ImageSourceToKURL(loader->GetElement()->ImageSourceURL()); + } + + void Run() { + if (!loader_) + return; + ExecutionContext& context = loader_->GetElement()->GetDocument(); + probe::AsyncTask async_task(&context, this); + if (script_state_->ContextIsValid()) { + ScriptState::Scope scope(script_state_.get()); + loader_->DoUpdateFromElement(should_bypass_main_world_csp_, + update_behavior_, request_url_, + referrer_policy_); + } else { + loader_->DoUpdateFromElement(should_bypass_main_world_csp_, + update_behavior_, request_url_, + referrer_policy_); + } + } + + void ClearLoader() { + loader_ = nullptr; + script_state_ = nullptr; + } + + base::WeakPtr<Task> GetWeakPtr() { return weak_factory_.GetWeakPtr(); } + + private: + WeakPersistent<ImageLoader> loader_; + BypassMainWorldBehavior should_bypass_main_world_csp_; + UpdateFromElementBehavior update_behavior_; + scoped_refptr<ScriptState> script_state_; + base::WeakPtrFactory<Task> weak_factory_; + ReferrerPolicy referrer_policy_; + KURL request_url_; +}; + +ImageLoader::ImageLoader(Element* element) + : element_(element), + image_complete_(true), + loading_image_document_(false), + suppress_error_events_(false) { + RESOURCE_LOADING_DVLOG(1) << "new ImageLoader " << this; +} + +ImageLoader::~ImageLoader() = default; + +void ImageLoader::Dispose() { + RESOURCE_LOADING_DVLOG(1) + << "~ImageLoader " << this + << "; has pending load event=" << pending_load_event_.IsActive() + << ", has pending error event=" << pending_error_event_.IsActive(); + + if (image_content_) { + image_content_->RemoveObserver(this); + image_content_ = nullptr; + image_resource_for_image_document_ = nullptr; + delay_until_image_notify_finished_ = nullptr; + } +} + +void ImageLoader::DispatchDecodeRequestsIfComplete() { + // If the current image isn't complete, then we can't dispatch any decodes. + // This function will be called again when the current image completes. + if (!image_complete_) + return; + + bool is_active = GetElement()->GetDocument().IsActive(); + // If any of the following conditions hold, we either have an inactive + // document or a broken/non-existent image. In those cases, we reject any + // pending decodes. + if (!is_active || !GetContent() || GetContent()->ErrorOccurred()) { + RejectPendingDecodes(); + return; + } + + LocalFrame* frame = GetElement()->GetDocument().GetFrame(); + for (auto& request : decode_requests_) { + // If the image already in kDispatched state or still in kPEndingMicrotask + // state, then we don't dispatch decodes for it. So, the only case to handle + // is if we're in kPendingLoad state. + if (request->state() != DecodeRequest::kPendingLoad) + continue; + Image* image = GetContent()->GetImage(); + frame->GetChromeClient().RequestDecode( + frame, image->PaintImageForCurrentFrame(), + WTF::Bind(&ImageLoader::DecodeRequestFinished, + WrapCrossThreadWeakPersistent(this), request->request_id())); + request->NotifyDecodeDispatched(); + } +} + +void ImageLoader::DecodeRequestFinished(uint64_t request_id, bool success) { + // First we find the corresponding request id, then we either resolve or + // reject it and remove it from the list. + for (auto* it = decode_requests_.begin(); it != decode_requests_.end(); + ++it) { + auto& request = *it; + if (request->request_id() != request_id) + continue; + + if (success) + request->Resolve(); + else + request->Reject(); + decode_requests_.erase(it); + break; + } +} + +void ImageLoader::RejectPendingDecodes(UpdateType update_type) { + // Normally, we only reject pending decodes that have passed the + // kPendingMicrotask state, since pending mutation requests still have an + // outstanding microtask that will run and might act on a different image than + // the current one. However, as an optimization, there are cases where we + // synchronously update the image (see UpdateFromElement). In those cases, we + // have to reject even the pending mutation requests because conceptually they + // would have been scheduled before the synchronous update ran, so they + // referred to the old image. + for (auto* it = decode_requests_.begin(); it != decode_requests_.end();) { + auto& request = *it; + if (update_type == UpdateType::kAsync && + request->state() == DecodeRequest::kPendingMicrotask) { + ++it; + continue; + } + request->Reject(); + it = decode_requests_.erase(it); + } +} + +void ImageLoader::Trace(blink::Visitor* visitor) { + visitor->Trace(image_content_); + visitor->Trace(image_resource_for_image_document_); + visitor->Trace(element_); + visitor->Trace(decode_requests_); +} + +void ImageLoader::SetImageForTest(ImageResourceContent* new_image) { + DCHECK(new_image); + SetImageWithoutConsideringPendingLoadEvent(new_image); +} + +void ImageLoader::ClearImage() { + SetImageWithoutConsideringPendingLoadEvent(nullptr); +} + +void ImageLoader::SetImageForImageDocument(ImageResource* new_image_resource) { + DCHECK(loading_image_document_); + DCHECK(new_image_resource); + DCHECK(new_image_resource->GetContent()); + + image_resource_for_image_document_ = new_image_resource; + SetImageWithoutConsideringPendingLoadEvent(new_image_resource->GetContent()); + + // |image_complete_| is always true for ImageDocument loading, while the + // loading is just started. + // TODO(hiroshige): clean up the behavior of flags. https://crbug.com/719759 + image_complete_ = true; +} + +void ImageLoader::SetImageWithoutConsideringPendingLoadEvent( + ImageResourceContent* new_image_content) { + DCHECK(failed_load_url_.IsEmpty()); + ImageResourceContent* old_image_content = image_content_.Get(); + if (new_image_content != old_image_content) { + if (pending_load_event_.IsActive()) + pending_load_event_.Cancel(); + if (pending_error_event_.IsActive()) + pending_error_event_.Cancel(); + UpdateImageState(new_image_content); + if (new_image_content) { + new_image_content->AddObserver(this); + } + if (old_image_content) { + old_image_content->RemoveObserver(this); + } + } + + if (LayoutImageResource* image_resource = GetLayoutImageResource()) + image_resource->ResetAnimation(); +} + +static void ConfigureRequest( + FetchParameters& params, + ImageLoader::BypassMainWorldBehavior bypass_behavior, + Element& element, + const ClientHintsPreferences& client_hints_preferences) { + if (bypass_behavior == ImageLoader::kBypassMainWorldCSP) + params.SetContentSecurityCheck(kDoNotCheckContentSecurityPolicy); + + CrossOriginAttributeValue cross_origin = GetCrossOriginAttributeValue( + element.FastGetAttribute(HTMLNames::crossoriginAttr)); + if (cross_origin != kCrossOriginAttributeNotSet) { + params.SetCrossOriginAccessControl( + element.GetDocument().GetSecurityOrigin(), cross_origin); + } + + if (client_hints_preferences.ShouldSend( + mojom::WebClientHintsType::kResourceWidth) && + IsHTMLImageElement(element)) + params.SetResourceWidth(ToHTMLImageElement(element).GetResourceWidth()); +} + +inline void ImageLoader::DispatchErrorEvent() { + // There can be cases where DispatchErrorEvent() is called when there is + // already a scheduled error event for the previous load attempt. + // In such cases we cancel the previous event (by overwriting + // |pending_error_event_|) and then re-schedule a new error event here. + // crbug.com/722500 + pending_error_event_ = PostCancellableTask( + *GetElement()->GetDocument().GetTaskRunner(TaskType::kDOMManipulation), + FROM_HERE, + WTF::Bind(&ImageLoader::DispatchPendingErrorEvent, WrapPersistent(this), + WTF::Passed(IncrementLoadEventDelayCount::Create( + GetElement()->GetDocument())))); +} + +inline void ImageLoader::CrossSiteOrCSPViolationOccurred( + AtomicString image_source_url) { + failed_load_url_ = image_source_url; +} + +inline void ImageLoader::ClearFailedLoadURL() { + failed_load_url_ = AtomicString(); +} + +inline void ImageLoader::EnqueueImageLoadingMicroTask( + UpdateFromElementBehavior update_behavior, + ReferrerPolicy referrer_policy) { + std::unique_ptr<Task> task = + Task::Create(this, update_behavior, referrer_policy); + pending_task_ = task->GetWeakPtr(); + Microtask::EnqueueMicrotask( + WTF::Bind(&Task::Run, WTF::Passed(std::move(task)))); + delay_until_do_update_from_element_ = + IncrementLoadEventDelayCount::Create(element_->GetDocument()); +} + +void ImageLoader::UpdateImageState(ImageResourceContent* new_image_content) { + image_content_ = new_image_content; + if (!new_image_content) { + image_resource_for_image_document_ = nullptr; + image_complete_ = true; + } else { + image_complete_ = false; + } + delay_until_image_notify_finished_ = nullptr; +} + +void ImageLoader::DoUpdateFromElement(BypassMainWorldBehavior bypass_behavior, + UpdateFromElementBehavior update_behavior, + const KURL& url, + ReferrerPolicy referrer_policy, + UpdateType update_type) { + // FIXME: According to + // http://www.whatwg.org/specs/web-apps/current-work/multipage/embedded-content.html#the-img-element:the-img-element-55 + // When "update image" is called due to environment changes and the load + // fails, onerror should not be called. That is currently not the case. + // + // We don't need to call clearLoader here: Either we were called from the + // task, or our caller updateFromElement cleared the task's loader (and set + // pending_task_ to null). + pending_task_.reset(); + // Make sure to only decrement the count when we exit this function + std::unique_ptr<IncrementLoadEventDelayCount> load_delay_counter; + load_delay_counter.swap(delay_until_do_update_from_element_); + + Document& document = element_->GetDocument(); + if (!document.IsActive()) + return; + + AtomicString image_source_url = element_->ImageSourceURL(); + ImageResourceContent* new_image_content = nullptr; + if (!url.IsNull() && !url.IsEmpty()) { + // Unlike raw <img>, we block mixed content inside of <picture> or + // <img srcset>. + ResourceLoaderOptions resource_loader_options; + resource_loader_options.initiator_info.name = GetElement()->localName(); + ResourceRequest resource_request(url); + if (update_behavior == kUpdateForcedReload) { + resource_request.SetCacheMode(mojom::FetchCacheMode::kBypassCache); + resource_request.SetPreviewsState(WebURLRequest::kPreviewsNoTransform); + } + + if (referrer_policy != kReferrerPolicyDefault) { + resource_request.SetHTTPReferrer(SecurityPolicy::GenerateReferrer( + referrer_policy, url, document.OutgoingReferrer())); + } + + // Correct the RequestContext if necessary. + if (IsHTMLPictureElement(GetElement()->parentNode()) || + !GetElement()->FastGetAttribute(HTMLNames::srcsetAttr).IsNull()) { + resource_request.SetRequestContext( + WebURLRequest::kRequestContextImageSet); + } else if (IsHTMLObjectElement(GetElement())) { + resource_request.SetRequestContext(WebURLRequest::kRequestContextObject); + } else if (IsHTMLEmbedElement(GetElement())) { + resource_request.SetRequestContext(WebURLRequest::kRequestContextEmbed); + } + + bool page_is_being_dismissed = + document.PageDismissalEventBeingDispatched() != Document::kNoDismissal; + if (page_is_being_dismissed) { + resource_request.SetHTTPHeaderField(HTTPNames::Cache_Control, + "max-age=0"); + resource_request.SetKeepalive(true); + resource_request.SetRequestContext(WebURLRequest::kRequestContextPing); + } + + // Plug-ins should not load via service workers as plug-ins may have their + // own origin checking logic that may get confused if service workers + // respond with resources from another origin. + // https://w3c.github.io/ServiceWorker/#implementer-concerns + if (GetElement()->IsHTMLElement() && + ToHTMLElement(GetElement())->IsPluginElement()) { + resource_request.SetSkipServiceWorker(true); + } + + FetchParameters params(resource_request, resource_loader_options); + ConfigureRequest(params, bypass_behavior, *element_, + document.GetClientHintsPreferences()); + + if (update_behavior != kUpdateForcedReload && document.GetFrame()) + document.GetFrame()->MaybeAllowImagePlaceholder(params); + + new_image_content = ImageResourceContent::Fetch(params, document.Fetcher()); + + // If this load is starting while navigating away, treat it as an auditing + // keepalive request, and don't report its results back to the element. + if (page_is_being_dismissed) + new_image_content = nullptr; + + ClearFailedLoadURL(); + } else { + if (!image_source_url.IsNull()) { + // Fire an error event if the url string is not empty, but the KURL is. + DispatchErrorEvent(); + } + NoImageResourceToLoad(); + } + + ImageResourceContent* old_image_content = image_content_.Get(); + if (old_image_content != new_image_content) + RejectPendingDecodes(update_type); + + if (update_behavior == kUpdateSizeChanged && element_->GetLayoutObject() && + element_->GetLayoutObject()->IsImage() && + new_image_content == old_image_content) { + ToLayoutImage(element_->GetLayoutObject())->IntrinsicSizeChanged(); + } else { + if (pending_load_event_.IsActive()) + pending_load_event_.Cancel(); + + // Cancel error events that belong to the previous load, which is now + // cancelled by changing the src attribute. If newImage is null and + // has_pending_error_event_ is true, we know the error event has been just + // posted by this load and we should not cancel the event. + // FIXME: If both previous load and this one got blocked with an error, we + // can receive one error event instead of two. + if (pending_error_event_.IsActive() && new_image_content) + pending_error_event_.Cancel(); + + UpdateImageState(new_image_content); + + UpdateLayoutObject(); + // If newImage exists and is cached, addObserver() will result in the load + // event being queued to fire. Ensure this happens after beforeload is + // dispatched. + if (new_image_content) { + new_image_content->AddObserver(this); + } + if (old_image_content) { + old_image_content->RemoveObserver(this); + } + } + + if (LayoutImageResource* image_resource = GetLayoutImageResource()) + image_resource->ResetAnimation(); +} + +void ImageLoader::UpdateFromElement(UpdateFromElementBehavior update_behavior, + ReferrerPolicy referrer_policy) { + AtomicString image_source_url = element_->ImageSourceURL(); + suppress_error_events_ = (update_behavior == kUpdateSizeChanged); + + if (update_behavior == kUpdateIgnorePreviousError) + ClearFailedLoadURL(); + + if (!failed_load_url_.IsEmpty() && image_source_url == failed_load_url_) + return; + + if (loading_image_document_ && update_behavior == kUpdateForcedReload) { + // Prepares for reloading ImageDocument. + // We turn the ImageLoader into non-ImageDocument here, and proceed to + // reloading just like an ordinary <img> element below. + loading_image_document_ = false; + image_resource_for_image_document_ = nullptr; + ClearImage(); + } + + // Prevent the creation of a ResourceLoader (and therefore a network request) + // for ImageDocument loads. In this case, the image contents have already been + // requested as a main resource and ImageDocumentParser will take care of + // funneling the main resource bytes into |image_content_|, so just create an + // ImageResource to be populated later. + if (loading_image_document_) { + ResourceRequest request(ImageSourceToKURL(element_->ImageSourceURL())); + request.SetFetchCredentialsMode( + network::mojom::FetchCredentialsMode::kOmit); + ImageResource* image_resource = ImageResource::Create(request); + image_resource->SetStatus(ResourceStatus::kPending); + image_resource->NotifyStartLoad(); + SetImageForImageDocument(image_resource); + return; + } + + // If we have a pending task, we have to clear it -- either we're now loading + // immediately, or we need to reset the task's state. + if (pending_task_) { + pending_task_->ClearLoader(); + pending_task_.reset(); + // Here we need to clear delay_until_do_update_from_element to avoid causing + // a memory leak in case it's already created. + delay_until_do_update_from_element_ = nullptr; + } + + KURL url = ImageSourceToKURL(image_source_url); + if (ShouldLoadImmediately(url)) { + DoUpdateFromElement(kDoNotBypassMainWorldCSP, update_behavior, url, + referrer_policy, UpdateType::kSync); + return; + } + // Allow the idiom "img.src=''; img.src='.." to clear down the image before an + // asynchronous load completes. + if (image_source_url.IsEmpty()) { + ImageResourceContent* image = image_content_.Get(); + if (image) { + image->RemoveObserver(this); + } + image_content_ = nullptr; + image_resource_for_image_document_ = nullptr; + delay_until_image_notify_finished_ = nullptr; + } + + // Don't load images for inactive documents. We don't want to slow down the + // raw HTML parsing case by loading images we don't intend to display. + Document& document = element_->GetDocument(); + if (document.IsActive()) + EnqueueImageLoadingMicroTask(update_behavior, referrer_policy); +} + +KURL ImageLoader::ImageSourceToKURL(AtomicString image_source_url) const { + KURL url; + + // Don't load images for inactive documents. We don't want to slow down the + // raw HTML parsing case by loading images we don't intend to display. + Document& document = element_->GetDocument(); + if (!document.IsActive()) + return url; + + // Do not load any image if the 'src' attribute is missing or if it is + // an empty string. + if (!image_source_url.IsNull()) { + String stripped_image_source_url = + StripLeadingAndTrailingHTMLSpaces(image_source_url); + if (!stripped_image_source_url.IsEmpty()) + url = document.CompleteURL(stripped_image_source_url); + } + return url; +} + +bool ImageLoader::ShouldLoadImmediately(const KURL& url) const { + // We force any image loads which might require alt content through the + // asynchronous path so that we can add the shadow DOM for the alt-text + // content when style recalc is over and DOM mutation is allowed again. + if (!url.IsNull()) { + Resource* resource = GetMemoryCache()->ResourceForURL( + url, element_->GetDocument().Fetcher()->GetCacheIdentifier()); + if (resource && !resource->ErrorOccurred()) + return true; + } + return (IsHTMLObjectElement(element_) || IsHTMLEmbedElement(element_)); +} + +void ImageLoader::ImageChanged(ImageResourceContent* content, + CanDeferInvalidation, + const IntRect*) { + DCHECK_EQ(content, image_content_.Get()); + if (image_complete_ || !content->IsLoading() || + delay_until_image_notify_finished_) + return; + + Document& document = element_->GetDocument(); + if (!document.IsActive()) + return; + + delay_until_image_notify_finished_ = + IncrementLoadEventDelayCount::Create(document); +} + +void ImageLoader::ImageNotifyFinished(ImageResourceContent* resource) { + RESOURCE_LOADING_DVLOG(1) + << "ImageLoader::imageNotifyFinished " << this + << "; has pending load event=" << pending_load_event_.IsActive(); + + DCHECK(failed_load_url_.IsEmpty()); + DCHECK_EQ(resource, image_content_.Get()); + + // |image_complete_| is always true for entire ImageDocument loading for + // historical reason. + // DoUpdateFromElement() is not called and SetImageForImageDocument() + // is called instead for ImageDocument loading. + // TODO(hiroshige): Turn the CHECK()s to DCHECK()s before going to beta. + if (loading_image_document_) + CHECK(image_complete_); + else + CHECK(!image_complete_); + + image_complete_ = true; + delay_until_image_notify_finished_ = nullptr; + + // Update ImageAnimationPolicy for image_content_. + if (image_content_) + image_content_->UpdateImageAnimationPolicy(); + + UpdateLayoutObject(); + + if (image_content_ && image_content_->HasImage()) { + Image& image = *image_content_->GetImage(); + if (IsHTMLImageElement(element_)) { + Image::RecordCheckerableImageUMA(image, Image::ImageType::kImg); + } else if (IsSVGImageElement(element_)) { + Image::RecordCheckerableImageUMA(image, Image::ImageType::kSvg); + } + + if (image.IsSVGImage()) { + SVGImage& svg_image = ToSVGImage(image); + // SVG's document should be completely loaded before access control + // checks, which can occur anytime after ImageNotifyFinished() + // (See SVGImage::CurrentFrameHasSingleSecurityOrigin()). + // We check the document is loaded here to catch violation of the + // assumption reliably. + svg_image.CheckLoaded(); + svg_image.UpdateUseCounters(GetElement()->GetDocument()); + } + } + + DispatchDecodeRequestsIfComplete(); + + if (loading_image_document_) { + CHECK(!pending_load_event_.IsActive()); + return; + } + + if (resource->ErrorOccurred()) { + pending_load_event_.Cancel(); + + Optional<ResourceError> error = resource->GetResourceError(); + if (error && error->IsAccessCheck()) + CrossSiteOrCSPViolationOccurred(AtomicString(error->FailingURL())); + + // The error event should not fire if the image data update is a result of + // environment change. + // https://html.spec.whatwg.org/multipage/embedded-content.html#the-img-element:the-img-element-55 + if (!suppress_error_events_) + DispatchErrorEvent(); + return; + } + + CHECK(!pending_load_event_.IsActive()); + pending_load_event_ = PostCancellableTask( + *GetElement()->GetDocument().GetTaskRunner(TaskType::kDOMManipulation), + FROM_HERE, + WTF::Bind(&ImageLoader::DispatchPendingLoadEvent, WrapPersistent(this), + WTF::Passed(IncrementLoadEventDelayCount::Create( + GetElement()->GetDocument())))); +} + +LayoutImageResource* ImageLoader::GetLayoutImageResource() { + LayoutObject* layout_object = element_->GetLayoutObject(); + + if (!layout_object) + return nullptr; + + // We don't return style generated image because it doesn't belong to the + // ImageLoader. See <https://bugs.webkit.org/show_bug.cgi?id=42840> + if (layout_object->IsImage() && + !ToLayoutImage(layout_object)->IsGeneratedContent()) + return ToLayoutImage(layout_object)->ImageResource(); + + if (layout_object->IsSVGImage()) + return ToLayoutSVGImage(layout_object)->ImageResource(); + + if (layout_object->IsVideo()) + return ToLayoutVideo(layout_object)->ImageResource(); + + return nullptr; +} + +void ImageLoader::UpdateLayoutObject() { + LayoutImageResource* image_resource = GetLayoutImageResource(); + + if (!image_resource) + return; + + // Only update the layoutObject if it doesn't have an image or if what we have + // is a complete image. This prevents flickering in the case where a dynamic + // change is happening between two images. + ImageResourceContent* cached_image_content = image_resource->CachedImage(); + if (image_content_ != cached_image_content && + (image_complete_ || !cached_image_content)) + image_resource->SetImageResource(image_content_.Get()); +} + +bool ImageLoader::HasPendingEvent() const { + // Regular image loading is in progress. + if (image_content_ && !image_complete_ && !loading_image_document_) + return true; + + if (pending_load_event_.IsActive() || pending_error_event_.IsActive()) + return true; + + return false; +} + +void ImageLoader::DispatchPendingLoadEvent( + std::unique_ptr<IncrementLoadEventDelayCount> count) { + if (!image_content_) + return; + CHECK(image_complete_); + if (GetElement()->GetDocument().GetFrame()) + DispatchLoadEvent(); + + // Checks Document's load event synchronously here for performance. + // This is safe because DispatchPendingLoadEvent() is called asynchronously. + count->ClearAndCheckLoadEvent(); +} + +void ImageLoader::DispatchPendingErrorEvent( + std::unique_ptr<IncrementLoadEventDelayCount> count) { + if (GetElement()->GetDocument().GetFrame()) + GetElement()->DispatchEvent(Event::Create(EventTypeNames::error)); + + // Checks Document's load event synchronously here for performance. + // This is safe because DispatchPendingErrorEvent() is called asynchronously. + count->ClearAndCheckLoadEvent(); +} + +bool ImageLoader::GetImageAnimationPolicy(ImageAnimationPolicy& policy) { + if (!GetElement()->GetDocument().GetSettings()) + return false; + + policy = GetElement()->GetDocument().GetSettings()->GetImageAnimationPolicy(); + return true; +} + +ScriptPromise ImageLoader::Decode(ScriptState* script_state, + ExceptionState& exception_state) { + // It's possible that |script_state|'s context isn't valid, which means we + // should immediately reject the request. This is possible in situations like + // the document that created this image was already destroyed (like an img + // that comes from iframe.contentDocument.createElement("img") and the iframe + // is destroyed). + if (!script_state->ContextIsValid()) { + exception_state.ThrowDOMException(kEncodingError, + "The source image cannot be decoded."); + return ScriptPromise(); + } + + UseCounter::Count(GetElement()->GetDocument(), WebFeature::kImageDecodeAPI); + + auto* request = + new DecodeRequest(this, ScriptPromiseResolver::Create(script_state)); + Microtask::EnqueueMicrotask( + WTF::Bind(&DecodeRequest::ProcessForTask, WrapWeakPersistent(request))); + decode_requests_.push_back(request); + return request->promise(); +} + +void ImageLoader::ElementDidMoveToNewDocument() { + if (delay_until_do_update_from_element_) { + delay_until_do_update_from_element_->DocumentChanged( + element_->GetDocument()); + } + if (delay_until_image_notify_finished_) { + delay_until_image_notify_finished_->DocumentChanged( + element_->GetDocument()); + } + ClearFailedLoadURL(); + ClearImage(); +} + +// Indicates the next available id that we can use to uniquely identify a decode +// request. +uint64_t ImageLoader::DecodeRequest::s_next_request_id_ = 0; + +ImageLoader::DecodeRequest::DecodeRequest(ImageLoader* loader, + ScriptPromiseResolver* resolver) + : request_id_(s_next_request_id_++), resolver_(resolver), loader_(loader) {} + +void ImageLoader::DecodeRequest::Resolve() { + resolver_->Resolve(); + loader_ = nullptr; +} + +void ImageLoader::DecodeRequest::Reject() { + resolver_->Reject(DOMException::Create( + kEncodingError, "The source image cannot be decoded.")); + loader_ = nullptr; +} + +void ImageLoader::DecodeRequest::ProcessForTask() { + // We could have already processed (ie rejected) this task due to a sync + // update in UpdateFromElement. In that case, there's nothing to do here. + if (!loader_) + return; + + DCHECK_EQ(state_, kPendingMicrotask); + state_ = kPendingLoad; + loader_->DispatchDecodeRequestsIfComplete(); +} + +void ImageLoader::DecodeRequest::NotifyDecodeDispatched() { + DCHECK_EQ(state_, kPendingLoad); + state_ = kDispatched; +} + +void ImageLoader::DecodeRequest::Trace(blink::Visitor* visitor) { + visitor->Trace(resolver_); + visitor->Trace(loader_); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/loader/image_loader.h b/chromium/third_party/blink/renderer/core/loader/image_loader.h new file mode 100644 index 00000000000..decee89e624 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/image_loader.h @@ -0,0 +1,270 @@ +/* + * Copyright (C) 1999 Lars Knoll (knoll@kde.org) + * (C) 1999 Antti Koivisto (koivisto@kde.org) + * Copyright (C) 2004, 2009 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_IMAGE_LOADER_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_IMAGE_LOADER_H_ + +#include <memory> +#include "base/memory/weak_ptr.h" +#include "third_party/blink/public/platform/task_type.h" +#include "third_party/blink/renderer/bindings/core/v8/script_promise.h" +#include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h" +#include "third_party/blink/renderer/core/core_export.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/heap/handle.h" +#include "third_party/blink/renderer/platform/wtf/hash_set.h" +#include "third_party/blink/renderer/platform/wtf/text/atomic_string.h" + +namespace blink { + +class IncrementLoadEventDelayCount; +class Element; +class LayoutImageResource; +class ExceptionState; +class ScriptState; + +class CORE_EXPORT ImageLoader : public GarbageCollectedFinalized<ImageLoader>, + public ImageResourceObserver { + USING_PRE_FINALIZER(ImageLoader, Dispose); + + public: + explicit ImageLoader(Element*); + ~ImageLoader() override; + + void Trace(blink::Visitor*); + + enum UpdateFromElementBehavior { + // This should be the update behavior when the element is attached to a + // document, or when DOM mutations trigger a new load. Starts loading if a + // load hasn't already been started. + kUpdateNormal, + // This should be the update behavior when the resource was changed (via + // 'src', 'srcset' or 'sizes'). Starts a new load even if a previous load of + // the same resource have failed, to match Firefox's behavior. + // FIXME - Verify that this is the right behavior according to the spec. + kUpdateIgnorePreviousError, + // This forces the image to update its intrinsic size, even if the image + // source has not changed. + kUpdateSizeChanged, + // This force the image to refetch and reload the image source, even if it + // has not changed. + kUpdateForcedReload + }; + + enum BypassMainWorldBehavior { + kBypassMainWorldCSP, + kDoNotBypassMainWorldCSP + }; + + void UpdateFromElement(UpdateFromElementBehavior = kUpdateNormal, + ReferrerPolicy = kReferrerPolicyDefault); + + void ElementDidMoveToNewDocument(); + + Element* GetElement() const { return element_; } + bool ImageComplete() const { return image_complete_ && !pending_task_; } + + ImageResourceContent* GetContent() const { return image_content_.Get(); } + + // Cancels pending load events, and doesn't dispatch new ones. + // Note: ClearImage/SetImage.*() are not a simple setter. + // Check the implementation to see what they do. + // TODO(hiroshige): Cleanup these methods. + void ClearImage(); + void SetImageForTest(ImageResourceContent*); + + // Image document loading: + // When |loading_image_document_| is true: + // Loading via ImageDocument. + // |image_resource_for_image_document_| points to a ImageResource that is + // not associated with a ResourceLoader. + // The corresponding ImageDocument is responsible for supplying the response + // and data to |image_resource_for_image_document_| and thus + // |image_content_|. + // Otherwise: + // Normal loading via ResourceFetcher/ResourceLoader. + // |image_resource_for_image_document_| is null. + bool IsLoadingImageDocument() { return loading_image_document_; } + void SetLoadingImageDocument() { loading_image_document_ = true; } + ImageResource* ImageResourceForImageDocument() const { + return image_resource_for_image_document_; + } + + bool HasPendingActivity() const { return HasPendingEvent() || pending_task_; } + + bool HasPendingError() const { return pending_error_event_.IsActive(); } + + bool HadError() const { return !failed_load_url_.IsEmpty(); } + + bool GetImageAnimationPolicy(ImageAnimationPolicy&) final; + + ScriptPromise Decode(ScriptState*, ExceptionState&); + + protected: + void ImageChanged(ImageResourceContent*, + CanDeferInvalidation, + const IntRect*) override; + void ImageNotifyFinished(ImageResourceContent*) override; + + private: + class Task; + + enum class UpdateType { kAsync, kSync }; + + // Called from the task or from updateFromElement to initiate the load. + void DoUpdateFromElement(BypassMainWorldBehavior, + UpdateFromElementBehavior, + const KURL&, + ReferrerPolicy = kReferrerPolicyDefault, + UpdateType = UpdateType::kAsync); + + virtual void DispatchLoadEvent() = 0; + virtual void NoImageResourceToLoad() {} + + bool HasPendingEvent() const; + + void DispatchPendingLoadEvent(std::unique_ptr<IncrementLoadEventDelayCount>); + void DispatchPendingErrorEvent(std::unique_ptr<IncrementLoadEventDelayCount>); + + LayoutImageResource* GetLayoutImageResource(); + void UpdateLayoutObject(); + + // Note: SetImage.*() are not a simple setter. + // Check the implementation to see what they do. + // TODO(hiroshige): Cleanup these methods. + void SetImageForImageDocument(ImageResource*); + void SetImageWithoutConsideringPendingLoadEvent(ImageResourceContent*); + void UpdateImageState(ImageResourceContent*); + + void ClearFailedLoadURL(); + void DispatchErrorEvent(); + void CrossSiteOrCSPViolationOccurred(AtomicString); + void EnqueueImageLoadingMicroTask(UpdateFromElementBehavior, ReferrerPolicy); + + KURL ImageSourceToKURL(AtomicString) const; + + // Used to determine whether to immediately initiate the load or to schedule a + // microtask. + bool ShouldLoadImmediately(const KURL&) const; + + // For Oilpan, we must run dispose() as a prefinalizer and call + // m_image->removeClient(this) (and more.) Otherwise, the ImageResource can + // invoke didAddClient() for the ImageLoader that is about to die in the + // current lazy sweeping, and the didAddClient() can access on-heap objects + // that have already been finalized in the current lazy sweeping. + void Dispose(); + + void DispatchDecodeRequestsIfComplete(); + void RejectPendingDecodes(UpdateType = UpdateType::kAsync); + void DecodeRequestFinished(uint64_t request_id, bool success); + + Member<Element> element_; + Member<ImageResourceContent> image_content_; + Member<ImageResource> image_resource_for_image_document_; + + AtomicString failed_load_url_; + base::WeakPtr<Task> pending_task_; // owned by Microtask + std::unique_ptr<IncrementLoadEventDelayCount> + delay_until_do_update_from_element_; + + // Delaying load event: the timeline should be: + // (0) ImageResource::Fetch() is called. + // (1) ResourceFetcher::StartLoad(): Resource loading is actually started. + // (2) ResourceLoader::DidFinishLoading() etc: + // Resource loading is finished, but SVG document load might be + // incomplete because of asynchronously loaded subresources. + // (3) ImageNotifyFinished(): Image is completely loaded. + // and we delay Document load event from (1) to (3): + // - |ResourceFetcher::loaders_| delays Document load event from (1) to (2). + // - |delay_until_image_notify_finished_| delays Document load event from + // the first ImageChanged() (at some time between (1) and (2)) until (3). + // Ideally, we might want to delay Document load event from (1) to (3), + // but currently we piggyback on ImageChanged() because adding a callback + // hook at (1) might complicate the code. + std::unique_ptr<IncrementLoadEventDelayCount> + delay_until_image_notify_finished_; + + TaskHandle pending_load_event_; + TaskHandle pending_error_event_; + + bool image_complete_ : 1; + bool loading_image_document_ : 1; + bool suppress_error_events_ : 1; + + // DecodeRequest represents a single request to the Decode() function. The + // decode requests have one of the following states: + // + // - kPendingMicrotask: This is the initial state. The caller is responsible + // for scheduling a microtask that would advance the state to the next value. + // Images invalidated by the pending mutations microtask (|pending_task_|) do + // not invalidate decode requests in this state. The exception is synchronous + // updates that do not go through |pending_task_|. + // + // - kPendingLoad: Once the microtask runs, it advances the state to + // kPendingLoad which waits for the image to be complete. If |pending_task_| + // runs and modifies the image, it invalidates any DecodeRequests in this + // state. + // + // - kDispatched: Once the image is loaded and the request to decode it is + // dispatched on behalf of this DecodeRequest, the state changes to + // kDispatched. If |pending_task_| runs and modifies the image, it invalidates + // any DecodeRequests in this state. + class DecodeRequest : public GarbageCollected<DecodeRequest> { + public: + enum State { kPendingMicrotask, kPendingLoad, kDispatched }; + + DecodeRequest(ImageLoader*, ScriptPromiseResolver*); + DecodeRequest(DecodeRequest&&) = default; + ~DecodeRequest() = default; + + void Trace(blink::Visitor*); + + DecodeRequest& operator=(DecodeRequest&&) = default; + + uint64_t request_id() const { return request_id_; } + State state() const { return state_; } + ScriptPromise promise() { return resolver_->Promise(); } + + void Resolve(); + void Reject(); + + void ProcessForTask(); + void NotifyDecodeDispatched(); + + private: + static uint64_t s_next_request_id_; + + uint64_t request_id_ = 0; + State state_ = kPendingMicrotask; + + Member<ScriptPromiseResolver> resolver_; + Member<ImageLoader> loader_; + }; + + HeapVector<Member<DecodeRequest>> decode_requests_; +}; + +} // namespace blink + +#endif diff --git a/chromium/third_party/blink/renderer/core/loader/interactive_detector.cc b/chromium/third_party/blink/renderer/core/loader/interactive_detector.cc new file mode 100644 index 00000000000..e4c6134c525 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/interactive_detector.cc @@ -0,0 +1,478 @@ +// Copyright 2017 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/interactive_detector.h" + +#include "third_party/blink/public/platform/web_input_event.h" +#include "third_party/blink/renderer/core/dom/document.h" +#include "third_party/blink/renderer/core/frame/local_frame.h" +#include "third_party/blink/renderer/core/loader/document_loader.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/loader/fetch/resource_fetcher.h" +#include "third_party/blink/renderer/platform/wtf/time.h" + +namespace blink { + +// Required length of main thread and network quiet window for determining +// Time to Interactive. +constexpr auto kTimeToInteractiveWindow = TimeDelta::FromSeconds(5); +// Network is considered "quiet" if there are no more than 2 active network +// requests for this duration of time. +constexpr int kNetworkQuietMaximumConnections = 2; + +// static +const char InteractiveDetector::kSupplementName[] = "InteractiveDetector"; + +InteractiveDetector* InteractiveDetector::From(Document& document) { + InteractiveDetector* detector = + Supplement<Document>::From<InteractiveDetector>(document); + if (!detector) { + detector = new InteractiveDetector(document, + new NetworkActivityChecker(&document)); + Supplement<Document>::ProvideTo(document, detector); + } + return detector; +} + +const char* InteractiveDetector::SupplementName() { + return "InteractiveDetector"; +} + +InteractiveDetector::InteractiveDetector( + Document& document, + NetworkActivityChecker* network_activity_checker) + : Supplement<Document>(document), + network_activity_checker_(network_activity_checker), + time_to_interactive_timer_( + document.GetTaskRunner(TaskType::kUnspecedTimer), + this, + &InteractiveDetector::TimeToInteractiveTimerFired) {} + +InteractiveDetector::~InteractiveDetector() { + LongTaskDetector::Instance().UnregisterObserver(this); +} + +void InteractiveDetector::SetNavigationStartTime( + TimeTicks navigation_start_time) { + // Should not set nav start twice. + DCHECK(page_event_times_.nav_start.is_null()); + + // Don't record TTI for OOPIFs (yet). + // TODO(crbug.com/808086): enable this case. + if (!GetSupplementable()->IsInMainFrame()) + return; + + LongTaskDetector::Instance().RegisterObserver(this); + page_event_times_.nav_start = navigation_start_time; + TimeTicks initial_timer_fire_time = + navigation_start_time + kTimeToInteractiveWindow; + + active_main_thread_quiet_window_start_ = navigation_start_time; + active_network_quiet_window_start_ = navigation_start_time; + StartOrPostponeCITimer(initial_timer_fire_time); +} + +int InteractiveDetector::NetworkActivityChecker::GetActiveConnections() { + DCHECK(document_); + ResourceFetcher* fetcher = document_->Fetcher(); + return fetcher->BlockingRequestCount() + fetcher->NonblockingRequestCount(); +} + +int InteractiveDetector::ActiveConnections() { + return network_activity_checker_->GetActiveConnections(); +} + +void InteractiveDetector::StartOrPostponeCITimer(TimeTicks timer_fire_time) { + // This function should never be called after Time To Interactive is + // reached. + DCHECK(interactive_time_.is_null()); + + // We give 1ms extra padding to the timer fire time to prevent floating point + // arithmetic pitfalls when comparing window sizes. + timer_fire_time += TimeDelta::FromMilliseconds(1); + + // Return if there is an active timer scheduled to fire later than + // |timer_fire_time|. + if (timer_fire_time < time_to_interactive_timer_fire_time_) + return; + + TimeDelta delay = timer_fire_time - CurrentTimeTicks(); + time_to_interactive_timer_fire_time_ = timer_fire_time; + + if (delay <= TimeDelta()) { + // This argument of this function is never used and only there to fulfill + // the API contract. nullptr should work fine. + TimeToInteractiveTimerFired(nullptr); + } else { + time_to_interactive_timer_.StartOneShot(delay, FROM_HERE); + } +} + +TimeTicks InteractiveDetector::GetInteractiveTime() const { + // TODO(crbug.com/808685) Simplify FMP and TTI input invalidation. + return page_event_times_.first_meaningful_paint_invalidated + ? TimeTicks() + : interactive_time_; +} + +TimeTicks InteractiveDetector::GetInteractiveDetectionTime() const { + // TODO(crbug.com/808685) Simplify FMP and TTI input invalidation. + return page_event_times_.first_meaningful_paint_invalidated + ? TimeTicks() + : interactive_detection_time_; +} + +TimeTicks InteractiveDetector::GetFirstInvalidatingInputTime() const { + return page_event_times_.first_invalidating_input; +} + +TimeDelta InteractiveDetector::GetFirstInputDelay() const { + return page_event_times_.first_input_delay; +} + +TimeTicks InteractiveDetector::GetFirstInputTimestamp() const { + return page_event_times_.first_input_timestamp; +} + +// This is called early enough in the pipeline that we don't need to worry about +// javascript dispatching untrusted input events. +void InteractiveDetector::HandleForFirstInputDelay(const WebInputEvent& event) { + if (!page_event_times_.first_input_delay.is_zero()) + return; + + DCHECK(event.GetType() != WebInputEvent::kTouchStart); + + // We can't report a pointerDown until the pointerUp, in case it turns into a + // scroll. + if (event.GetType() == WebInputEvent::kPointerDown) { + pending_pointerdown_delay_ = TimeDelta::FromSecondsD( + CurrentTimeTicksInSeconds() - event.TimeStampSeconds()); + pending_pointerdown_timestamp_ = + TimeTicksFromSeconds(event.TimeStampSeconds()); + return; + } + + bool event_is_meaningful = + event.GetType() == WebInputEvent::kMouseDown || + event.GetType() == WebInputEvent::kKeyDown || + event.GetType() == WebInputEvent::kRawKeyDown || + // We need to explicitly include tap, as if there are no listeners, we + // won't receive the pointer events. + event.GetType() == WebInputEvent::kGestureTap || + event.GetType() == WebInputEvent::kPointerUp; + + if (!event_is_meaningful) + return; + + TimeDelta delay; + TimeTicks event_timestamp; + if (event.GetType() == WebInputEvent::kPointerUp) { + // It is possible that this pointer up doesn't match with the pointer down + // whose delay is stored in pending_pointerdown_delay_. In this case, the + // user gesture started by this event contained some non-scroll input, so we + // consider it reasonable to use the delay of the initial event. + delay = pending_pointerdown_delay_; + event_timestamp = pending_pointerdown_timestamp_; + } else { + delay = TimeDelta::FromSecondsD(CurrentTimeTicksInSeconds() - + event.TimeStampSeconds()); + event_timestamp = TimeTicksFromSeconds(event.TimeStampSeconds()); + } + + pending_pointerdown_delay_ = base::TimeDelta(); + pending_pointerdown_timestamp_ = base::TimeTicks(); + + page_event_times_.first_input_delay = delay; + page_event_times_.first_input_timestamp = event_timestamp; + + if (GetSupplementable()->Loader()) + GetSupplementable()->Loader()->DidChangePerformanceTiming(); +} + +void InteractiveDetector::BeginNetworkQuietPeriod(TimeTicks current_time) { + // Value of 0.0 indicates there is no currently actively network quiet window. + DCHECK(active_network_quiet_window_start_.is_null()); + active_network_quiet_window_start_ = current_time; + + StartOrPostponeCITimer(current_time + kTimeToInteractiveWindow); +} + +void InteractiveDetector::EndNetworkQuietPeriod(TimeTicks current_time) { + DCHECK(!active_network_quiet_window_start_.is_null()); + + if (current_time - active_network_quiet_window_start_ >= + kTimeToInteractiveWindow) { + network_quiet_windows_.emplace_back(active_network_quiet_window_start_, + current_time); + } + active_network_quiet_window_start_ = TimeTicks(); +} + +// The optional opt_current_time, if provided, saves us a call to +// CurrentTimeTicksInSeconds. +void InteractiveDetector::UpdateNetworkQuietState( + double request_count, + WTF::Optional<TimeTicks> opt_current_time) { + if (request_count <= kNetworkQuietMaximumConnections && + active_network_quiet_window_start_.is_null()) { + // Not using `value_or(CurrentTimeTicksInSeconds())` here because + // arguments to functions are eagerly evaluated, which always call + // CurrentTimeTicksInSeconds. + TimeTicks current_time = + opt_current_time ? opt_current_time.value() : CurrentTimeTicks(); + BeginNetworkQuietPeriod(current_time); + } else if (request_count > kNetworkQuietMaximumConnections && + !active_network_quiet_window_start_.is_null()) { + TimeTicks current_time = + opt_current_time ? opt_current_time.value() : CurrentTimeTicks(); + EndNetworkQuietPeriod(current_time); + } +} + +void InteractiveDetector::OnResourceLoadBegin( + WTF::Optional<TimeTicks> load_begin_time) { + if (!GetSupplementable()) + return; + if (!interactive_time_.is_null()) + return; + // The request that is about to begin is not counted in ActiveConnections(), + // so we add one to it. + UpdateNetworkQuietState(ActiveConnections() + 1, load_begin_time); +} + +// The optional load_finish_time, if provided, saves us a call to +// CurrentTimeTicksInSeconds. +void InteractiveDetector::OnResourceLoadEnd( + WTF::Optional<TimeTicks> load_finish_time) { + if (!GetSupplementable()) + return; + if (!interactive_time_.is_null()) + return; + UpdateNetworkQuietState(ActiveConnections(), load_finish_time); +} + +void InteractiveDetector::OnLongTaskDetected(TimeTicks start_time, + TimeTicks end_time) { + // We should not be receiving long task notifications after Time to + // Interactive has already been reached. + DCHECK(interactive_time_.is_null()); + TimeDelta quiet_window_length = + start_time - active_main_thread_quiet_window_start_; + if (quiet_window_length >= kTimeToInteractiveWindow) { + main_thread_quiet_windows_.emplace_back( + active_main_thread_quiet_window_start_, start_time); + } + active_main_thread_quiet_window_start_ = end_time; + StartOrPostponeCITimer(end_time + kTimeToInteractiveWindow); +} + +void InteractiveDetector::OnFirstMeaningfulPaintDetected( + TimeTicks fmp_time, + FirstMeaningfulPaintDetector::HadUserInput user_input_before_fmp) { + DCHECK(page_event_times_.first_meaningful_paint + .is_null()); // Should not set FMP twice. + page_event_times_.first_meaningful_paint = fmp_time; + page_event_times_.first_meaningful_paint_invalidated = + user_input_before_fmp == FirstMeaningfulPaintDetector::kHadUserInput; + if (CurrentTimeTicks() - fmp_time >= kTimeToInteractiveWindow) { + // We may have reached TTCI already. Check right away. + CheckTimeToInteractiveReached(); + } else { + StartOrPostponeCITimer(page_event_times_.first_meaningful_paint + + kTimeToInteractiveWindow); + } +} + +void InteractiveDetector::OnDomContentLoadedEnd(TimeTicks dcl_end_time) { + // InteractiveDetector should only receive the first DCL event. + DCHECK(page_event_times_.dom_content_loaded_end.is_null()); + page_event_times_.dom_content_loaded_end = dcl_end_time; + CheckTimeToInteractiveReached(); +} + +void InteractiveDetector::OnInvalidatingInputEvent( + TimeTicks invalidation_time) { + if (!page_event_times_.first_invalidating_input.is_null()) + return; + + // In some edge cases (e.g. inaccurate input timestamp provided through remote + // debugging protocol) we might receive an input timestamp that is earlier + // than navigation start. Since invalidating input timestamp before navigation + // start in non-sensical, we clamp it at navigation start. + page_event_times_.first_invalidating_input = + std::max(invalidation_time, page_event_times_.nav_start); + + if (GetSupplementable()->Loader()) + GetSupplementable()->Loader()->DidChangePerformanceTiming(); +} + +void InteractiveDetector::OnFirstInputDelay(TimeDelta delay) { + if (!page_event_times_.first_input_delay.is_zero()) + return; + + page_event_times_.first_input_delay = delay; + if (GetSupplementable()->Loader()) + GetSupplementable()->Loader()->DidChangePerformanceTiming(); +} + +void InteractiveDetector::TimeToInteractiveTimerFired(TimerBase*) { + if (!GetSupplementable() || !interactive_time_.is_null()) + return; + + // Value of 0.0 indicates there is currently no active timer. + time_to_interactive_timer_fire_time_ = TimeTicks(); + CheckTimeToInteractiveReached(); +} + +void InteractiveDetector::AddCurrentlyActiveQuietIntervals( + TimeTicks current_time) { + // Network is currently quiet. + if (!active_network_quiet_window_start_.is_null()) { + if (current_time - active_network_quiet_window_start_ >= + kTimeToInteractiveWindow) { + network_quiet_windows_.emplace_back(active_network_quiet_window_start_, + current_time); + } + } + + // Since this code executes on the main thread, we know that no task is + // currently running on the main thread. We can therefore skip checking. + // main_thread_quiet_window_being != 0.0. + if (current_time - active_main_thread_quiet_window_start_ >= + kTimeToInteractiveWindow) { + main_thread_quiet_windows_.emplace_back( + active_main_thread_quiet_window_start_, current_time); + } +} + +void InteractiveDetector::RemoveCurrentlyActiveQuietIntervals() { + if (!network_quiet_windows_.empty() && + network_quiet_windows_.back().Low() == + active_network_quiet_window_start_) { + network_quiet_windows_.pop_back(); + } + + if (!main_thread_quiet_windows_.empty() && + main_thread_quiet_windows_.back().Low() == + active_main_thread_quiet_window_start_) { + main_thread_quiet_windows_.pop_back(); + } +} + +TimeTicks InteractiveDetector::FindInteractiveCandidate(TimeTicks lower_bound) { + // Main thread iterator. + auto it_mt = main_thread_quiet_windows_.begin(); + // Network iterator. + auto it_net = network_quiet_windows_.begin(); + + while (it_mt < main_thread_quiet_windows_.end() && + it_net < network_quiet_windows_.end()) { + if (it_mt->High() <= lower_bound) { + it_mt++; + continue; + } + if (it_net->High() <= lower_bound) { + it_net++; + continue; + } + + // First handling the no overlap cases. + // [ main thread interval ] + // [ network interval ] + if (it_mt->High() <= it_net->Low()) { + it_mt++; + continue; + } + // [ main thread interval ] + // [ network interval ] + if (it_net->High() <= it_mt->Low()) { + it_net++; + continue; + } + + // At this point we know we have a non-empty overlap after lower_bound. + TimeTicks overlap_start = + std::max({it_mt->Low(), it_net->Low(), lower_bound}); + TimeTicks overlap_end = std::min(it_mt->High(), it_net->High()); + TimeDelta overlap_duration = overlap_end - overlap_start; + if (overlap_duration >= kTimeToInteractiveWindow) { + return std::max(lower_bound, it_mt->Low()); + } + + // The interval with earlier end time will not produce any more overlap, so + // we move on from it. + if (it_mt->High() <= it_net->High()) { + it_mt++; + } else { + it_net++; + } + } + + // Time To Interactive candidate not found. + return TimeTicks(); +} + +void InteractiveDetector::CheckTimeToInteractiveReached() { + // Already detected Time to Interactive. + if (!interactive_time_.is_null()) + return; + + // FMP and DCL have not been detected yet. + if (page_event_times_.first_meaningful_paint.is_null() || + page_event_times_.dom_content_loaded_end.is_null()) + return; + + const TimeTicks current_time = CurrentTimeTicks(); + if (current_time - page_event_times_.first_meaningful_paint < + kTimeToInteractiveWindow) { + // Too close to FMP to determine Time to Interactive. + return; + } + + AddCurrentlyActiveQuietIntervals(current_time); + const TimeTicks interactive_candidate = + FindInteractiveCandidate(page_event_times_.first_meaningful_paint); + RemoveCurrentlyActiveQuietIntervals(); + + // No Interactive Candidate found. + if (interactive_candidate.is_null()) + return; + + interactive_time_ = std::max( + {interactive_candidate, page_event_times_.dom_content_loaded_end}); + interactive_detection_time_ = CurrentTimeTicks(); + OnTimeToInteractiveDetected(); +} + +void InteractiveDetector::OnTimeToInteractiveDetected() { + LongTaskDetector::Instance().UnregisterObserver(this); + main_thread_quiet_windows_.clear(); + network_quiet_windows_.clear(); + + bool had_user_input_before_interactive = + !page_event_times_.first_invalidating_input.is_null() && + page_event_times_.first_invalidating_input < interactive_time_; + + // We log the trace event even if there is user input, but annotate the event + // with whether that happened. + TRACE_EVENT_MARK_WITH_TIMESTAMP2( + "loading,rail", "InteractiveTime", interactive_time_, "frame", + ToTraceValue(GetSupplementable()->GetFrame()), + "had_user_input_before_interactive", had_user_input_before_interactive); + + // We only send TTI to Performance Timing Observers if FMP was not invalidated + // by input. + // TODO(crbug.com/808685) Simplify FMP and TTI input invalidation. + if (!page_event_times_.first_meaningful_paint_invalidated) { + if (GetSupplementable()->Loader()) + GetSupplementable()->Loader()->DidChangePerformanceTiming(); + } +} + +void InteractiveDetector::Trace(Visitor* visitor) { + Supplement<Document>::Trace(visitor); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/loader/interactive_detector.h b/chromium/third_party/blink/renderer/core/loader/interactive_detector.h new file mode 100644 index 00000000000..a1df291da5a --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/interactive_detector.h @@ -0,0 +1,173 @@ +// Copyright 2017 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_INTERACTIVE_DETECTOR_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_INTERACTIVE_DETECTOR_H_ + +#include "base/macros.h" +#include "third_party/blink/renderer/core/core_export.h" +#include "third_party/blink/renderer/core/paint/first_meaningful_paint_detector.h" +#include "third_party/blink/renderer/platform/heap/handle.h" +#include "third_party/blink/renderer/platform/long_task_detector.h" +#include "third_party/blink/renderer/platform/pod_interval.h" +#include "third_party/blink/renderer/platform/supplementable.h" +#include "third_party/blink/renderer/platform/timer.h" +#include "third_party/blink/renderer/platform/wtf/optional.h" + +namespace blink { + +class Document; +class WebInputEvent; + +// Detects when a page reaches First Idle and Time to Interactive. See +// https://goo.gl/SYt55W for detailed description and motivation of First Idle +// and Time to Interactive. +// TODO(crbug.com/631203): This class currently only detects Time to +// Interactive. Implement First Idle. +class CORE_EXPORT InteractiveDetector + : public GarbageCollectedFinalized<InteractiveDetector>, + public Supplement<Document>, + public LongTaskObserver { + USING_GARBAGE_COLLECTED_MIXIN(InteractiveDetector); + + public: + static const char kSupplementName[]; + + // This class can be easily switched out to allow better testing of + // InteractiveDetector. + class CORE_EXPORT NetworkActivityChecker { + public: + NetworkActivityChecker(Document* document) : document_(document) {} + + virtual int GetActiveConnections(); + virtual ~NetworkActivityChecker() = default; + + private: + WeakPersistent<Document> document_; + + DISALLOW_COPY_AND_ASSIGN(NetworkActivityChecker); + }; + + static InteractiveDetector* From(Document&); + // Exposed for tests. See crbug.com/810381. We must use a consistent address + // for the supplement name. + static const char* SupplementName(); + virtual ~InteractiveDetector(); + + // Calls to CurrentTimeTicksInSeconds is expensive, so we try not to call it + // unless we really have to. If we already have the event time available, we + // pass it in as an argument. + void OnResourceLoadBegin(WTF::Optional<TimeTicks> load_begin_time); + void OnResourceLoadEnd(WTF::Optional<TimeTicks> load_finish_time); + + void SetNavigationStartTime(TimeTicks navigation_start_time); + void OnFirstMeaningfulPaintDetected( + TimeTicks fmp_time, + FirstMeaningfulPaintDetector::HadUserInput user_input_before_fmp); + void OnDomContentLoadedEnd(TimeTicks dcl_time); + void OnInvalidatingInputEvent(TimeTicks invalidation_time); + void OnFirstInputDelay(TimeDelta delay_seconds); + + // Returns Interactive Time if already detected, or 0.0 otherwise. + TimeTicks GetInteractiveTime() const; + + // Returns the time when page interactive was detected. The detection time can + // be useful to make decisions about metric invalidation in scenarios like tab + // backgrounding. + TimeTicks GetInteractiveDetectionTime() const; + + // Returns the first time interactive detector received a significant input + // that may cause observers to discard the interactive time value. + TimeTicks GetFirstInvalidatingInputTime() const; + + // The duration between the hardware timestamp and being queued on the main + // thread for the first click, tap, key press, cancelable touchstart, or + // pointer down followed by a pointer up. + TimeDelta GetFirstInputDelay() const; + + // The timestamp of the event whose delay is reported by GetFirstInputDelay(). + TimeTicks GetFirstInputTimestamp() const; + + // Process an input event, updating first_input_delay and + // first_input_timestamp if needed. + void HandleForFirstInputDelay(const WebInputEvent&); + + virtual void Trace(Visitor*); + + private: + friend class InteractiveDetectorTest; + + explicit InteractiveDetector(Document&, NetworkActivityChecker*); + + TimeTicks interactive_time_; + TimeTicks interactive_detection_time_; + + // Page event times that Interactive Detector depends on. + // Null TimeTicks values indicate the event has not been detected yet. + struct { + TimeTicks first_meaningful_paint; + TimeTicks dom_content_loaded_end; + TimeTicks nav_start; + TimeTicks first_invalidating_input; + TimeDelta first_input_delay; + TimeTicks first_input_timestamp; + bool first_meaningful_paint_invalidated = false; + } page_event_times_; + + // Stores sufficiently long quiet windows on main thread and network. + std::vector<PODInterval<TimeTicks>> main_thread_quiet_windows_; + std::vector<PODInterval<TimeTicks>> network_quiet_windows_; + + // Start times of currently active main thread and network quiet windows. + // Values of 0.0 implies main thread or network is not quiet at the moment. + TimeTicks active_main_thread_quiet_window_start_; + TimeTicks active_network_quiet_window_start_; + + // Adds currently active quiet main thread and network quiet windows to the + // vectors. Should be called before calling + // FindInteractiveCandidate. + void AddCurrentlyActiveQuietIntervals(TimeTicks current_time); + // Undoes AddCurrentlyActiveQuietIntervals. + void RemoveCurrentlyActiveQuietIntervals(); + + std::unique_ptr<NetworkActivityChecker> network_activity_checker_; + int ActiveConnections(); + void BeginNetworkQuietPeriod(TimeTicks current_time); + void EndNetworkQuietPeriod(TimeTicks current_time); + // Updates current network quietness tracking information. Opens and closes + // network quiet windows as necessary. + void UpdateNetworkQuietState(double request_count, + WTF::Optional<TimeTicks> current_time); + + TaskRunnerTimer<InteractiveDetector> time_to_interactive_timer_; + TimeTicks time_to_interactive_timer_fire_time_; + void StartOrPostponeCITimer(TimeTicks timer_fire_time); + void TimeToInteractiveTimerFired(TimerBase*); + void CheckTimeToInteractiveReached(); + void OnTimeToInteractiveDetected(); + + // Finds a window of length kTimeToInteractiveWindowSeconds after lower_bound + // such that both main thread and network are quiet. Returns the end of last + // long task before that quiet window, or lower_bound, whichever is bigger - + // this is called the Interactive Candidate. Returns 0.0 if no such quiet + // window is found. + TimeTicks FindInteractiveCandidate(TimeTicks lower_bound); + + // LongTaskObserver implementation + void OnLongTaskDetected(TimeTicks start_time, TimeTicks end_time) override; + + // The duration between the hardware timestamp and when we received the event + // for the previous pointer down. Only non-zero if we've received a pointer + // down event, and haven't yet reported the first input delay. + base::TimeDelta pending_pointerdown_delay_; + // The timestamp of a pending pointerdown event. Valid in the same cases as + // pending_pointerdown_delay_. + base::TimeTicks pending_pointerdown_timestamp_; + + DISALLOW_COPY_AND_ASSIGN(InteractiveDetector); +}; + +} // namespace blink + +#endif diff --git a/chromium/third_party/blink/renderer/core/loader/interactive_detector_test.cc b/chromium/third_party/blink/renderer/core/loader/interactive_detector_test.cc new file mode 100644 index 00000000000..63928280805 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/interactive_detector_test.cc @@ -0,0 +1,515 @@ +// Copyright 2017 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/interactive_detector.h" + +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/blink/renderer/core/dom/document.h" +#include "third_party/blink/renderer/core/paint/first_meaningful_paint_detector.h" +#include "third_party/blink/renderer/core/testing/dummy_page_holder.h" +#include "third_party/blink/renderer/core/testing/page_test_base.h" +#include "third_party/blink/renderer/platform/cross_thread_functional.h" +#include "third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl.h" +#include "third_party/blink/renderer/platform/testing/testing_platform_support_with_mock_scheduler.h" + +namespace blink { + +class NetworkActivityCheckerForTest + : public InteractiveDetector::NetworkActivityChecker { + public: + NetworkActivityCheckerForTest(Document* document) + : InteractiveDetector::NetworkActivityChecker(document) {} + + virtual void SetActiveConnections(int active_connections) { + active_connections_ = active_connections; + }; + int GetActiveConnections() override; + + private: + int active_connections_ = 0; +}; + +int NetworkActivityCheckerForTest::GetActiveConnections() { + return active_connections_; +} + +struct TaskTiming { + double start; + double end; + TaskTiming(double start, double end) : start(start), end(end) {} +}; + +class InteractiveDetectorTest : public testing::Test { + public: + InteractiveDetectorTest() { + platform_->AdvanceClockSeconds(1); + dummy_page_holder_ = DummyPageHolder::Create(); + + Document* document = &dummy_page_holder_->GetDocument(); + + detector_ = new InteractiveDetector( + *document, new NetworkActivityCheckerForTest(document)); + + // By this time, the DummyPageHolder has created an InteractiveDetector, and + // sent DOMContentLoadedEnd. We overwrite it with our new + // InteractiveDetector, which won't have received any timestamps. + Supplement<Document>::ProvideTo(*document, detector_.Get()); + + // Ensure the document is using the injected InteractiveDetector. + DCHECK_EQ(detector_, InteractiveDetector::From(*document)); + } + + // Public because it's executed on a task queue. + void DummyTaskWithDuration(double duration_seconds) { + platform_->AdvanceClockSeconds(duration_seconds); + dummy_task_end_time_ = CurrentTimeTicksInSeconds(); + } + + protected: + InteractiveDetector* GetDetector() { return detector_; } + + double GetDummyTaskEndTime() { return dummy_task_end_time_; } + + NetworkActivityCheckerForTest* GetNetworkActivityChecker() { + // We know in this test context that network_activity_checker_ is an + // instance of NetworkActivityCheckerForTest, so this static_cast is safe. + return static_cast<NetworkActivityCheckerForTest*>( + detector_->network_activity_checker_.get()); + } + + void SimulateNavigationStart(double nav_start_time) { + RunTillTimestamp(nav_start_time); + detector_->SetNavigationStartTime(TimeTicksFromSeconds(nav_start_time)); + } + + void SimulateLongTask(double start, double end) { + CHECK(end - start >= 0.05); + RunTillTimestamp(end); + detector_->OnLongTaskDetected(TimeTicksFromSeconds(start), + TimeTicksFromSeconds(end)); + } + + void SimulateDOMContentLoadedEnd(double dcl_time) { + RunTillTimestamp(dcl_time); + detector_->OnDomContentLoadedEnd(TimeTicksFromSeconds(dcl_time)); + } + + void SimulateFMPDetected(double fmp_time, double detection_time) { + RunTillTimestamp(detection_time); + detector_->OnFirstMeaningfulPaintDetected( + TimeTicksFromSeconds(fmp_time), + FirstMeaningfulPaintDetector::kNoUserInput); + } + + void SimulateInteractiveInvalidatingInput(double timestamp) { + RunTillTimestamp(timestamp); + detector_->OnInvalidatingInputEvent(TimeTicksFromSeconds(timestamp)); + } + + void RunTillTimestamp(double target_time) { + double current_time = CurrentTimeTicksInSeconds(); + platform_->RunForPeriodSeconds(std::max(0.0, target_time - current_time)); + } + + int GetActiveConnections() { + return GetNetworkActivityChecker()->GetActiveConnections(); + } + + void SetActiveConnections(int active_connections) { + GetNetworkActivityChecker()->SetActiveConnections(active_connections); + } + + void SimulateResourceLoadBegin(double load_begin_time) { + RunTillTimestamp(load_begin_time); + detector_->OnResourceLoadBegin(TimeTicksFromSeconds(load_begin_time)); + // ActiveConnections is incremented after detector runs OnResourceLoadBegin; + SetActiveConnections(GetActiveConnections() + 1); + } + + void SimulateResourceLoadEnd(double load_finish_time) { + RunTillTimestamp(load_finish_time); + int active_connections = GetActiveConnections(); + SetActiveConnections(active_connections - 1); + detector_->OnResourceLoadEnd(TimeTicksFromSeconds(load_finish_time)); + } + + double GetInteractiveTime() { + return TimeTicksInSeconds(detector_->GetInteractiveTime()); + } + + ScopedTestingPlatformSupport<TestingPlatformSupportWithMockScheduler> + platform_; + + private: + Persistent<InteractiveDetector> detector_; + std::unique_ptr<DummyPageHolder> dummy_page_holder_; + double dummy_task_end_time_ = 0.0; +}; + +// Note: The tests currently assume kTimeToInteractiveWindowSeconds is 5 +// seconds. The window size is unlikely to change, and this makes the test +// scenarios significantly easier to write. + +// Note: Some of the tests are named W_X_Y_Z, where W, X, Y, Z can any of the +// following events: +// FMP: First Meaningful Paint +// DCL: DomContentLoadedEnd +// FmpDetect: Detection of FMP. FMP is not detected in realtime. +// LT: Long Task +// The name shows the ordering of these events in the test. + +TEST_F(InteractiveDetectorTest, FMP_DCL_FmpDetect) { + double t0 = CurrentTimeTicksInSeconds(); + SimulateNavigationStart(t0); + // Network is forever quiet for this test. + SetActiveConnections(1); + SimulateDOMContentLoadedEnd(t0 + 3.0); + SimulateFMPDetected(/* fmp_time */ t0 + 5.0, /* detection_time */ t0 + 7.0); + // Run until 5 seconds after FMP. + RunTillTimestamp((t0 + 5.0) + 5.0 + 0.1); + // Reached TTI at FMP. + EXPECT_EQ(GetInteractiveTime(), t0 + 5.0); +} + +TEST_F(InteractiveDetectorTest, DCL_FMP_FmpDetect) { + double t0 = CurrentTimeTicksInSeconds(); + SimulateNavigationStart(t0); + // Network is forever quiet for this test. + SetActiveConnections(1); + SimulateDOMContentLoadedEnd(t0 + 5.0); + SimulateFMPDetected(/* fmp_time */ t0 + 3.0, /* detection_time */ t0 + 7.0); + // Run until 5 seconds after FMP. + RunTillTimestamp((t0 + 3.0) + 5.0 + 0.1); + // Reached TTI at DCL. + EXPECT_EQ(GetInteractiveTime(), t0 + 5.0); +} + +TEST_F(InteractiveDetectorTest, InstantDetectionAtFmpDetectIfPossible) { + double t0 = CurrentTimeTicksInSeconds(); + SimulateNavigationStart(t0); + // Network is forever quiet for this test. + SetActiveConnections(1); + SimulateDOMContentLoadedEnd(t0 + 5.0); + SimulateFMPDetected(/* fmp_time */ t0 + 3.0, /* detection_time */ t0 + 10.0); + // Although we just detected FMP, the FMP timestamp is more than + // kTimeToInteractiveWindowSeconds earlier. We should instantaneously + // detect that we reached TTI at DCL. + EXPECT_EQ(GetInteractiveTime(), t0 + 5.0); +} + +TEST_F(InteractiveDetectorTest, FmpDetectFiresAfterLateLongTask) { + double t0 = CurrentTimeTicksInSeconds(); + SimulateNavigationStart(t0); + // Network is forever quiet for this test. + SetActiveConnections(1); + SimulateDOMContentLoadedEnd(t0 + 3.0); + SimulateLongTask(t0 + 9.0, t0 + 9.1); + SimulateFMPDetected(/* fmp_time */ t0 + 3.0, /* detection_time */ t0 + 10.0); + // There is a 5 second quiet window after fmp_time - the long task is 6s + // seconds after fmp_time. We should instantly detect we reached TTI at FMP. + EXPECT_EQ(GetInteractiveTime(), t0 + 3.0); +} + +TEST_F(InteractiveDetectorTest, FMP_FmpDetect_DCL) { + double t0 = CurrentTimeTicksInSeconds(); + SimulateNavigationStart(t0); + // Network is forever quiet for this test. + SetActiveConnections(1); + SimulateFMPDetected(/* fmp_time */ t0 + 3.0, /* detection_time */ t0 + 5.0); + SimulateDOMContentLoadedEnd(t0 + 9.0); + // TTI reached at DCL. + EXPECT_EQ(GetInteractiveTime(), t0 + 9.0); +} + +TEST_F(InteractiveDetectorTest, LongTaskBeforeFMPDoesNotAffectTTI) { + double t0 = CurrentTimeTicksInSeconds(); + SimulateNavigationStart(t0); + // Network is forever quiet for this test. + SetActiveConnections(1); + SimulateDOMContentLoadedEnd(t0 + 3.0); + SimulateLongTask(t0 + 5.1, t0 + 5.2); + SimulateFMPDetected(/* fmp_time */ t0 + 8.0, /* detection_time */ t0 + 9.0); + // Run till 5 seconds after FMP. + RunTillTimestamp((t0 + 8.0) + 5.0 + 0.1); + // TTI reached at FMP. + EXPECT_EQ(GetInteractiveTime(), t0 + 8.0); +} + +TEST_F(InteractiveDetectorTest, DCLDoesNotResetTimer) { + double t0 = CurrentTimeTicksInSeconds(); + SimulateNavigationStart(t0); + // Network is forever quiet for this test. + SetActiveConnections(1); + SimulateFMPDetected(/* fmp_time */ t0 + 3.0, /* detection_time */ t0 + 4.0); + SimulateLongTask(t0 + 5.0, t0 + 5.1); + SimulateDOMContentLoadedEnd(t0 + 8.0); + // Run till 5 seconds after long task end. + RunTillTimestamp((t0 + 5.1) + 5.0 + 0.1); + // TTI Reached at DCL. + EXPECT_EQ(GetInteractiveTime(), t0 + 8.0); +} + +TEST_F(InteractiveDetectorTest, DCL_FMP_FmpDetect_LT) { + double t0 = CurrentTimeTicksInSeconds(); + SimulateNavigationStart(t0); + // Network is forever quiet for this test. + SetActiveConnections(1); + SimulateDOMContentLoadedEnd(t0 + 3.0); + SimulateFMPDetected(/* fmp_time */ t0 + 4.0, /* detection_time */ t0 + 5.0); + SimulateLongTask(t0 + 7.0, t0 + 7.1); + // Run till 5 seconds after long task end. + RunTillTimestamp((t0 + 7.1) + 5.0 + 0.1); + // TTI reached at long task end. + EXPECT_EQ(GetInteractiveTime(), t0 + 7.1); +} + +TEST_F(InteractiveDetectorTest, DCL_FMP_LT_FmpDetect) { + double t0 = CurrentTimeTicksInSeconds(); + SimulateNavigationStart(t0); + // Network is forever quiet for this test. + SetActiveConnections(1); + SimulateDOMContentLoadedEnd(t0 + 3.0); + SimulateLongTask(t0 + 7.0, t0 + 7.1); + SimulateFMPDetected(/* fmp_time */ t0 + 3.0, /* detection_time */ t0 + 5.0); + // Run till 5 seconds after long task end. + RunTillTimestamp((t0 + 7.1) + 5.0 + 0.1); + // TTI reached at long task end. + EXPECT_EQ(GetInteractiveTime(), t0 + 7.1); +} + +TEST_F(InteractiveDetectorTest, FMP_FmpDetect_LT_DCL) { + double t0 = CurrentTimeTicksInSeconds(); + SimulateNavigationStart(t0); + // Network is forever quiet for this test. + SetActiveConnections(1); + SimulateFMPDetected(/* fmp_time */ t0 + 3.0, /* detection_time */ t0 + 4.0); + SimulateLongTask(t0 + 7.0, t0 + 7.1); + SimulateDOMContentLoadedEnd(t0 + 8.0); + // Run till 5 seconds after long task end. + RunTillTimestamp((t0 + 7.1) + 5.0 + 0.1); + // TTI reached at DCL. Note that we do not need to wait for DCL + 5 seconds. + EXPECT_EQ(GetInteractiveTime(), t0 + 8.0); +} + +TEST_F(InteractiveDetectorTest, DclIsMoreThan5sAfterFMP) { + double t0 = CurrentTimeTicksInSeconds(); + SimulateNavigationStart(t0); + // Network is forever quiet for this test. + SetActiveConnections(1); + SimulateFMPDetected(/* fmp_time */ t0 + 3.0, /* detection_time */ t0 + 4.0); + SimulateLongTask(t0 + 7.0, t0 + 7.1); // Long task 1. + SimulateDOMContentLoadedEnd(t0 + 10.0); + // Have not reached TTI yet. + EXPECT_EQ(GetInteractiveTime(), 0.0); + SimulateLongTask(t0 + 11.0, t0 + 11.1); // Long task 2. + // Run till long task 2 end + 5 seconds. + RunTillTimestamp((t0 + 11.1) + 5.0 + 0.1); + // TTI reached at long task 2 end. + EXPECT_EQ(GetInteractiveTime(), (t0 + 11.1)); +} + +TEST_F(InteractiveDetectorTest, NetworkBusyBlocksTTIEvenWhenMainThreadQuiet) { + double t0 = CurrentTimeTicksInSeconds(); + SimulateNavigationStart(t0); + SetActiveConnections(1); + SimulateDOMContentLoadedEnd(t0 + 2.0); + SimulateResourceLoadBegin(t0 + 3.4); // Request 2 start. + SimulateResourceLoadBegin(t0 + 3.5); // Request 3 start. Network busy. + SimulateFMPDetected(/* fmp_time */ t0 + 3.0, /* detection_time */ t0 + 4.0); + SimulateLongTask(t0 + 7.0, t0 + 7.1); // Long task 1. + SimulateResourceLoadEnd(t0 + 12.2); // Network quiet. + // Network busy kept page from reaching TTI.. + EXPECT_EQ(GetInteractiveTime(), 0.0); + SimulateLongTask(t0 + 13.0, t0 + 13.1); // Long task 2. + // Run till 5 seconds after long task 2 end. + RunTillTimestamp((t0 + 13.1) + 5.0 + 0.1); + EXPECT_EQ(GetInteractiveTime(), (t0 + 13.1)); +} + +TEST_F(InteractiveDetectorTest, LongEnoughQuietWindowBetweenFMPAndFmpDetect) { + double t0 = CurrentTimeTicksInSeconds(); + SimulateNavigationStart(t0); + SetActiveConnections(1); + SimulateDOMContentLoadedEnd(t0 + 2.0); + SimulateLongTask(t0 + 2.1, t0 + 2.2); // Long task 1. + SimulateLongTask(t0 + 8.2, t0 + 8.3); // Long task 2. + SimulateResourceLoadBegin(t0 + 8.4); // Request 2 start. + SimulateResourceLoadBegin(t0 + 8.5); // Request 3 start. Network busy. + SimulateFMPDetected(/* fmp_time */ t0 + 3.0, /* detection_time */ t0 + 10.0); + // Even though network is currently busy and we have long task finishing + // recently, we should be able to detect that the page already achieved TTI at + // FMP. + EXPECT_EQ(GetInteractiveTime(), t0 + 3.0); +} + +TEST_F(InteractiveDetectorTest, NetworkBusyEndIsNotTTI) { + double t0 = CurrentTimeTicksInSeconds(); + SimulateNavigationStart(t0); + SetActiveConnections(1); + SimulateDOMContentLoadedEnd(t0 + 2.0); + SimulateResourceLoadBegin(t0 + 3.4); // Request 2 start. + SimulateResourceLoadBegin(t0 + 3.5); // Request 3 start. Network busy. + SimulateFMPDetected(/* fmp_time */ t0 + 3.0, /* detection_time */ t0 + 4.0); + SimulateLongTask(t0 + 7.0, t0 + 7.1); // Long task 1. + SimulateLongTask(t0 + 13.0, t0 + 13.1); // Long task 2. + SimulateResourceLoadEnd(t0 + 14.0); // Network quiet. + // Run till 5 seconds after network busy end. + RunTillTimestamp((t0 + 14.0) + 5.0 + 0.1); + // TTI reached at long task 2 end, NOT at network busy end. + EXPECT_EQ(GetInteractiveTime(), t0 + 13.1); +} + +TEST_F(InteractiveDetectorTest, LateLongTaskWithLateFMPDetection) { + double t0 = CurrentTimeTicksInSeconds(); + SimulateNavigationStart(t0); + SetActiveConnections(1); + SimulateDOMContentLoadedEnd(t0 + 2.0); + SimulateResourceLoadBegin(t0 + 3.4); // Request 2 start. + SimulateResourceLoadBegin(t0 + 3.5); // Request 3 start. Network busy. + SimulateLongTask(t0 + 7.0, t0 + 7.1); // Long task 1. + SimulateResourceLoadEnd(t0 + 8.0); // Network quiet. + SimulateLongTask(t0 + 14.0, t0 + 14.1); // Long task 2. + SimulateFMPDetected(/* fmp_time */ t0 + 3.0, /* detection_time */ t0 + 20.0); + // TTI reached at long task 1 end, NOT at long task 2 end. + EXPECT_EQ(GetInteractiveTime(), t0 + 7.1); +} + +TEST_F(InteractiveDetectorTest, IntermittentNetworkBusyBlocksTTI) { + double t0 = CurrentTimeTicksInSeconds(); + SimulateNavigationStart(t0); + SetActiveConnections(1); + SimulateDOMContentLoadedEnd(t0 + 2.0); + SimulateFMPDetected(/* fmp_time */ t0 + 3.0, /* detection_time */ t0 + 4.0); + SimulateLongTask(t0 + 7.0, t0 + 7.1); // Long task 1. + SimulateResourceLoadBegin(t0 + 7.9); // Active connections: 2 + // Network busy start. + SimulateResourceLoadBegin(t0 + 8.0); // Active connections: 3. + // Network busy end. + SimulateResourceLoadEnd(t0 + 8.5); // Active connections: 2. + // Network busy start. + SimulateResourceLoadBegin(t0 + 11.0); // Active connections: 3. + // Network busy end. + SimulateResourceLoadEnd(t0 + 12.0); // Active connections: 2. + SimulateLongTask(t0 + 14.0, t0 + 14.1); // Long task 2. + // Run till 5 seconds after long task 2 end. + RunTillTimestamp((t0 + 14.1) + 5.0 + 0.1); + // TTI reached at long task 2 end. + EXPECT_EQ(GetInteractiveTime(), t0 + 14.1); +} + +TEST_F(InteractiveDetectorTest, InvalidatingUserInput) { + double t0 = CurrentTimeTicksInSeconds(); + SimulateNavigationStart(t0); + // Network is forever quiet for this test. + SetActiveConnections(1); + SimulateDOMContentLoadedEnd(t0 + 2.0); + SimulateFMPDetected(/* fmp_time */ t0 + 3.0, /* detection_time */ t0 + 4.0); + SimulateInteractiveInvalidatingInput(t0 + 5.0); + SimulateLongTask(t0 + 7.0, t0 + 7.1); // Long task 1. + // Run till 5 seconds after long task 2 end. + RunTillTimestamp((t0 + 7.1) + 5.0 + 0.1); + // We still detect interactive time on the blink side even if there is an + // invalidating user input. Page Load Metrics filters out this value in the + // browser process for UMA reporting. + EXPECT_EQ(GetInteractiveTime(), t0 + 7.1); + EXPECT_EQ(TimeTicksInSeconds(GetDetector()->GetFirstInvalidatingInputTime()), + t0 + 5.0); +} + +TEST_F(InteractiveDetectorTest, InvalidatingUserInputClampedAtNavStart) { + double t0 = CurrentTimeTicksInSeconds(); + SimulateNavigationStart(t0); + // Network is forever quiet for this test. + SetActiveConnections(1); + SimulateDOMContentLoadedEnd(t0 + 2.0); + SimulateFMPDetected(/* fmp_time */ t0 + 3.0, /* detection_time */ t0 + 4.0); + // Invalidating input timestamp is earlier than navigation start. + SimulateInteractiveInvalidatingInput(t0 - 10.0); + // Run till 5 seconds after FMP. + RunTillTimestamp((t0 + 7.1) + 5.0 + 0.1); + EXPECT_EQ(GetInteractiveTime(), t0 + 3.0); // TTI at FMP. + // Invalidating input timestamp is clamped at navigation start. + EXPECT_EQ(TimeTicksInSeconds(GetDetector()->GetFirstInvalidatingInputTime()), + t0); +} + +TEST_F(InteractiveDetectorTest, InvalidatedFMP) { + double t0 = CurrentTimeTicksInSeconds(); + SimulateNavigationStart(t0); + // Network is forever quiet for this test. + SetActiveConnections(1); + SimulateInteractiveInvalidatingInput(t0 + 1.0); + SimulateDOMContentLoadedEnd(t0 + 2.0); + RunTillTimestamp(t0 + 4.0); // FMP Detection time. + GetDetector()->OnFirstMeaningfulPaintDetected( + TimeTicksFromSeconds(t0 + 3.0), + FirstMeaningfulPaintDetector::kHadUserInput); + // Run till 5 seconds after FMP. + RunTillTimestamp((t0 + 3.0) + 5.0 + 0.1); + // Since FMP was invalidated, we do not have TTI or TTI Detection Time. + EXPECT_EQ(GetInteractiveTime(), 0.0); + EXPECT_EQ(TimeTicksInSeconds(GetDetector()->GetInteractiveDetectionTime()), + 0.0); + // Invalidating input timestamp is available. + EXPECT_EQ(TimeTicksInSeconds(GetDetector()->GetFirstInvalidatingInputTime()), + t0 + 1.0); +} + +TEST_F(InteractiveDetectorTest, TaskLongerThan5sBlocksTTI) { + double t0 = CurrentTimeTicksInSeconds(); + GetDetector()->SetNavigationStartTime(TimeTicksFromSeconds(t0)); + + SimulateDOMContentLoadedEnd(t0 + 2.0); + SimulateFMPDetected(t0 + 3.0, t0 + 4.0); + + // Post a task with 6 seconds duration. + PostCrossThreadTask( + *platform_->CurrentThread()->GetTaskRunner(), FROM_HERE, + CrossThreadBind(&InteractiveDetectorTest::DummyTaskWithDuration, + CrossThreadUnretained(this), 6.0)); + + platform_->RunUntilIdle(); + + // We should be able to detect TTI 5s after the end of long task. + platform_->RunForPeriodSeconds(5.1); + EXPECT_EQ(TimeTicksInSeconds(GetDetector()->GetInteractiveTime()), + GetDummyTaskEndTime()); +} + +TEST_F(InteractiveDetectorTest, LongTaskAfterTTIDoesNothing) { + double t0 = CurrentTimeTicksInSeconds(); + GetDetector()->SetNavigationStartTime(TimeTicksFromSeconds(t0)); + + SimulateDOMContentLoadedEnd(2.0); + SimulateFMPDetected(t0 + 3.0, t0 + 4.0); + + // Long task 1. + PostCrossThreadTask( + *platform_->CurrentThread()->GetTaskRunner(), FROM_HERE, + CrossThreadBind(&InteractiveDetectorTest::DummyTaskWithDuration, + CrossThreadUnretained(this), 0.1)); + + platform_->RunUntilIdle(); + + double long_task_1_end_time = GetDummyTaskEndTime(); + // We should be able to detect TTI 5s after the end of long task. + platform_->RunForPeriodSeconds(5.1); + EXPECT_EQ(TimeTicksInSeconds(GetDetector()->GetInteractiveTime()), + long_task_1_end_time); + + // Long task 2. + PostCrossThreadTask( + *platform_->CurrentThread()->GetTaskRunner(), FROM_HERE, + CrossThreadBind(&InteractiveDetectorTest::DummyTaskWithDuration, + CrossThreadUnretained(this), 0.1)); + + platform_->RunUntilIdle(); + // Wait 5 seconds to see if TTI time changes. + platform_->RunForPeriodSeconds(5.1); + // TTI time should not change. + EXPECT_EQ(TimeTicksInSeconds(GetDetector()->GetInteractiveTime()), + long_task_1_end_time); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/loader/link_loader.cc b/chromium/third_party/blink/renderer/core/loader/link_loader.cc new file mode 100644 index 00000000000..6b10c9f53a9 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/link_loader.cc @@ -0,0 +1,703 @@ +/* + * Copyright (C) 2011 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/link_loader.h" + +#include "third_party/blink/public/platform/web_prerender.h" +#include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h" +#include "third_party/blink/renderer/core/css/media_list.h" +#include "third_party/blink/renderer/core/css/media_query_evaluator.h" +#include "third_party/blink/renderer/core/css/parser/sizes_attribute_parser.h" +#include "third_party/blink/renderer/core/dom/document.h" +#include "third_party/blink/renderer/core/frame/frame_console.h" +#include "third_party/blink/renderer/core/frame/local_frame.h" +#include "third_party/blink/renderer/core/frame/settings.h" +#include "third_party/blink/renderer/core/frame/use_counter.h" +#include "third_party/blink/renderer/core/html/cross_origin_attribute.h" +#include "third_party/blink/renderer/core/html/link_rel_attribute.h" +#include "third_party/blink/renderer/core/html/parser/html_preload_scanner.h" +#include "third_party/blink/renderer/core/html/parser/html_srcset_parser.h" +#include "third_party/blink/renderer/core/inspector/console_message.h" +#include "third_party/blink/renderer/core/loader/document_loader.h" +#include "third_party/blink/renderer/core/loader/modulescript/module_script_fetch_request.h" +#include "third_party/blink/renderer/core/loader/network_hints_interface.h" +#include "third_party/blink/renderer/core/loader/private/prerender_handle.h" +#include "third_party/blink/renderer/core/loader/resource/link_fetch_resource.h" +#include "third_party/blink/renderer/core/loader/subresource_integrity_helper.h" +#include "third_party/blink/renderer/core/script/module_script.h" +#include "third_party/blink/renderer/core/script/script_loader.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/resource_client.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_options.h" +#include "third_party/blink/renderer/platform/loader/link_header.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/prerender.h" + +namespace blink { + +static unsigned PrerenderRelTypesFromRelAttribute( + const LinkRelAttribute& rel_attribute, + Document& document) { + unsigned result = 0; + if (rel_attribute.IsLinkPrerender()) { + result |= kPrerenderRelTypePrerender; + UseCounter::Count(document, WebFeature::kLinkRelPrerender); + } + if (rel_attribute.IsLinkNext()) { + result |= kPrerenderRelTypeNext; + UseCounter::Count(document, WebFeature::kLinkRelNext); + } + + return result; +} + +LinkLoadParameters::LinkLoadParameters(const LinkHeader& header, + const KURL& base_url) + : rel(LinkRelAttribute(header.Rel())), + cross_origin(GetCrossOriginAttributeValue(header.CrossOrigin())), + type(header.MimeType()), + as(header.As()), + media(header.Media()), + nonce(header.Nonce()), + integrity(header.Integrity()), + referrer_policy(kReferrerPolicyDefault), + href(KURL(base_url, header.Url())), + srcset(header.Srcset()), + sizes(header.Imgsizes()) {} + +class LinkLoader::FinishObserver final + : public GarbageCollectedFinalized<ResourceFinishObserver>, + public ResourceFinishObserver { + USING_GARBAGE_COLLECTED_MIXIN(FinishObserver); + USING_PRE_FINALIZER(FinishObserver, ClearResource); + + public: + FinishObserver(LinkLoader* loader, Resource* resource) + : loader_(loader), resource_(resource) { + resource_->AddFinishObserver( + this, loader_->client_->GetLoadingTaskRunner().get()); + } + + // ResourceFinishObserver implementation + void NotifyFinished() override { + if (!resource_) + return; + loader_->NotifyFinished(); + ClearResource(); + } + String DebugName() const override { + return "LinkLoader::ResourceFinishObserver"; + } + + Resource* GetResource() { return resource_; } + void ClearResource() { + if (!resource_) + return; + resource_->RemoveFinishObserver(this); + resource_ = nullptr; + } + + void Trace(blink::Visitor* visitor) override { + visitor->Trace(loader_); + visitor->Trace(resource_); + blink::ResourceFinishObserver::Trace(visitor); + } + + private: + Member<LinkLoader> loader_; + Member<Resource> resource_; +}; + +LinkLoader::LinkLoader(LinkLoaderClient* client, + scoped_refptr<base::SingleThreadTaskRunner> task_runner) + : client_(client) { + DCHECK(client_); +} + +LinkLoader::~LinkLoader() = default; + +void LinkLoader::NotifyFinished() { + DCHECK(finish_observer_); + Resource* resource = finish_observer_->GetResource(); + if (resource->ErrorOccurred()) + client_->LinkLoadingErrored(); + else + client_->LinkLoaded(); +} + +// https://html.spec.whatwg.org/#link-type-modulepreload +void LinkLoader::NotifyModuleLoadFinished(ModuleScript* module) { + // Step 11. "If result is null, fire an event named error at the link element, + // and return." [spec text] + // Step 12. "Fire an event named load at the link element." [spec text] + if (!module) + client_->LinkLoadingErrored(); + else + client_->LinkLoaded(); +} + +void LinkLoader::DidStartPrerender() { + client_->DidStartLinkPrerender(); +} + +void LinkLoader::DidStopPrerender() { + client_->DidStopLinkPrerender(); +} + +void LinkLoader::DidSendLoadForPrerender() { + client_->DidSendLoadForLinkPrerender(); +} + +void LinkLoader::DidSendDOMContentLoadedForPrerender() { + client_->DidSendDOMContentLoadedForLinkPrerender(); +} + +enum LinkCaller { + kLinkCalledFromHeader, + kLinkCalledFromMarkup, +}; + +static void SendMessageToConsoleForPossiblyNullDocument( + ConsoleMessage* console_message, + Document* document, + LocalFrame* frame) { + DCHECK(document || frame); + DCHECK(!document || document->GetFrame() == frame); + // Route the console message through Document if possible, so that script line + // numbers can be included. Otherwise, route directly to the FrameConsole, to + // ensure we never drop a message. + if (document) + document->AddConsoleMessage(console_message); + else + frame->Console().AddMessage(console_message); +} + +static void DnsPrefetchIfNeeded( + const LinkLoadParameters& params, + Document* document, + LocalFrame* frame, + const NetworkHintsInterface& network_hints_interface, + LinkCaller caller) { + if (params.rel.IsDNSPrefetch()) { + UseCounter::Count(frame, WebFeature::kLinkRelDnsPrefetch); + if (caller == kLinkCalledFromHeader) + UseCounter::Count(frame, WebFeature::kLinkHeaderDnsPrefetch); + Settings* settings = frame ? frame->GetSettings() : nullptr; + // FIXME: The href attribute of the link element can be in "//hostname" + // form, and we shouldn't attempt to complete that as URL + // <https://bugs.webkit.org/show_bug.cgi?id=48857>. + if (settings && settings->GetDNSPrefetchingEnabled() && + params.href.IsValid() && !params.href.IsEmpty()) { + if (settings->GetLogDnsPrefetchAndPreconnect()) { + SendMessageToConsoleForPossiblyNullDocument( + ConsoleMessage::Create( + kOtherMessageSource, kVerboseMessageLevel, + String("DNS prefetch triggered for " + params.href.Host())), + document, frame); + } + network_hints_interface.DnsPrefetchHost(params.href.Host()); + } + } +} + +static void PreconnectIfNeeded( + const LinkLoadParameters& params, + Document* document, + LocalFrame* frame, + const NetworkHintsInterface& network_hints_interface, + LinkCaller caller) { + if (params.rel.IsPreconnect() && params.href.IsValid() && + params.href.ProtocolIsInHTTPFamily()) { + UseCounter::Count(frame, WebFeature::kLinkRelPreconnect); + if (caller == kLinkCalledFromHeader) + UseCounter::Count(frame, WebFeature::kLinkHeaderPreconnect); + Settings* settings = frame ? frame->GetSettings() : nullptr; + if (settings && settings->GetLogDnsPrefetchAndPreconnect()) { + SendMessageToConsoleForPossiblyNullDocument( + ConsoleMessage::Create( + kOtherMessageSource, kVerboseMessageLevel, + String("Preconnect triggered for ") + params.href.GetString()), + document, frame); + if (params.cross_origin != kCrossOriginAttributeNotSet) { + SendMessageToConsoleForPossiblyNullDocument( + ConsoleMessage::Create(kOtherMessageSource, kVerboseMessageLevel, + String("Preconnect CORS setting is ") + + String((params.cross_origin == + kCrossOriginAttributeAnonymous) + ? "anonymous" + : "use-credentials")), + document, frame); + } + } + network_hints_interface.PreconnectHost(params.href, params.cross_origin); + } +} + +WTF::Optional<Resource::Type> LinkLoader::GetResourceTypeFromAsAttribute( + const String& as) { + DCHECK_EQ(as.DeprecatedLower(), as); + if (as == "image") { + return Resource::kImage; + } else if (as == "script") { + return Resource::kScript; + } else if (as == "style") { + return Resource::kCSSStyleSheet; + } else if (as == "video") { + return Resource::kVideo; + } else if (as == "audio") { + return Resource::kAudio; + } else if (as == "track") { + return Resource::kTextTrack; + } else if (as == "font") { + return Resource::kFont; + } else if (as == "fetch") { + return Resource::kRaw; + } + return WTF::nullopt; +} + +Resource* LinkLoader::GetResourceForTesting() { + return finish_observer_ ? finish_observer_->GetResource() : nullptr; +} + +static bool IsSupportedType(Resource::Type resource_type, + const String& mime_type) { + if (mime_type.IsEmpty()) + return true; + switch (resource_type) { + case Resource::kImage: + return MIMETypeRegistry::IsSupportedImagePrefixedMIMEType(mime_type); + case Resource::kScript: + return MIMETypeRegistry::IsSupportedJavaScriptMIMEType(mime_type); + case Resource::kCSSStyleSheet: + return MIMETypeRegistry::IsSupportedStyleSheetMIMEType(mime_type); + case Resource::kFont: + return MIMETypeRegistry::IsSupportedFontMIMEType(mime_type); + case Resource::kAudio: + case Resource::kVideo: + return MIMETypeRegistry::IsSupportedMediaMIMEType(mime_type, String()); + case Resource::kTextTrack: + return MIMETypeRegistry::IsSupportedTextTrackMIMEType(mime_type); + case Resource::kRaw: + return true; + default: + NOTREACHED(); + } + return false; +} + +static MediaValues* CreateMediaValues( + Document& document, + ViewportDescription* viewport_description) { + MediaValues* media_values = + MediaValues::CreateDynamicIfFrameExists(document.GetFrame()); + if (viewport_description) { + media_values->OverrideViewportDimensions( + viewport_description->max_width.GetFloatValue(), + viewport_description->max_height.GetFloatValue()); + } + return media_values; +} + +static bool MediaMatches(const String& media, MediaValues* media_values) { + scoped_refptr<MediaQuerySet> media_queries = MediaQuerySet::Create(media); + MediaQueryEvaluator evaluator(*media_values); + return evaluator.Eval(*media_queries); +} + +// |base_url| is used in Link HTTP Header based preloads to resolve relative +// URLs in srcset, which should be based on the resource's URL, not the +// document's base URL. If |base_url| is a null URL, relative URLs are resolved +// using |document.CompleteURL()|. +static Resource* PreloadIfNeeded(const LinkLoadParameters& params, + Document& document, + const KURL& base_url, + LinkCaller caller, + ViewportDescription* viewport_description) { + if (!document.Loader() || !params.rel.IsLinkPreload()) + return nullptr; + + Optional<Resource::Type> resource_type = + LinkLoader::GetResourceTypeFromAsAttribute(params.as); + + MediaValues* media_values = nullptr; + KURL url; + if (resource_type == Resource::kImage && !params.srcset.IsEmpty() && + RuntimeEnabledFeatures::PreloadImageSrcSetEnabled()) { + media_values = CreateMediaValues(document, viewport_description); + float source_size = + SizesAttributeParser(media_values, params.sizes).length(); + ImageCandidate candidate = BestFitSourceForImageAttributes( + media_values->DevicePixelRatio(), source_size, params.href, + params.srcset); + url = base_url.IsNull() ? document.CompleteURL(candidate.ToString()) + : KURL(base_url, candidate.ToString()); + } else { + url = params.href; + } + + UseCounter::Count(document, WebFeature::kLinkRelPreload); + if (!url.IsValid() || url.IsEmpty()) { + document.AddConsoleMessage(ConsoleMessage::Create( + kOtherMessageSource, kWarningMessageLevel, + String("<link rel=preload> has an invalid `href` value"))); + return nullptr; + } + + // Preload only if media matches + if (!params.media.IsEmpty()) { + if (!media_values) + media_values = CreateMediaValues(document, viewport_description); + if (!MediaMatches(params.media, media_values)) + return nullptr; + } + + if (caller == kLinkCalledFromHeader) + UseCounter::Count(document, WebFeature::kLinkHeaderPreload); + if (resource_type == WTF::nullopt) { + document.AddConsoleMessage(ConsoleMessage::Create( + kOtherMessageSource, kWarningMessageLevel, + String("<link rel=preload> must have a valid `as` value"))); + return nullptr; + } + + if (!IsSupportedType(resource_type.value(), params.type)) { + document.AddConsoleMessage(ConsoleMessage::Create( + kOtherMessageSource, kWarningMessageLevel, + String("<link rel=preload> has an unsupported `type` value"))); + return nullptr; + } + ResourceRequest resource_request(url); + resource_request.SetRequestContext(ResourceFetcher::DetermineRequestContext( + resource_type.value(), ResourceFetcher::kImageNotImageSet, false)); + + if (params.referrer_policy != kReferrerPolicyDefault) { + resource_request.SetHTTPReferrer(SecurityPolicy::GenerateReferrer( + params.referrer_policy, url, document.OutgoingReferrer())); + } + + ResourceLoaderOptions options; + options.initiator_info.name = FetchInitiatorTypeNames::link; + FetchParameters link_fetch_params(resource_request, options); + link_fetch_params.SetCharset(document.Encoding()); + + if (params.cross_origin != kCrossOriginAttributeNotSet) { + link_fetch_params.SetCrossOriginAccessControl(document.GetSecurityOrigin(), + params.cross_origin); + } + link_fetch_params.SetContentSecurityPolicyNonce(params.nonce); + Settings* settings = document.GetSettings(); + if (settings && settings->GetLogPreload()) { + document.AddConsoleMessage(ConsoleMessage::Create( + kOtherMessageSource, kVerboseMessageLevel, + String("Preload triggered for " + url.Host() + url.GetPath()))); + } + link_fetch_params.SetLinkPreload(true); + return document.Loader()->StartPreload(resource_type.value(), + link_fetch_params, nullptr); +} + +// https://html.spec.whatwg.org/#link-type-modulepreload +static void ModulePreloadIfNeeded(const LinkLoadParameters& params, + Document& document, + ViewportDescription* viewport_description, + LinkLoader* link_loader) { + if (!document.Loader() || !params.rel.IsModulePreload()) + return; + + UseCounter::Count(document, WebFeature::kLinkRelModulePreload); + + // Step 1. "If the href attribute's value is the empty string, then return." + // [spec text] + if (params.href.IsEmpty()) { + document.AddConsoleMessage( + ConsoleMessage::Create(kOtherMessageSource, kWarningMessageLevel, + "<link rel=modulepreload> has no `href` value")); + return; + } + + // Step 2. "Let destination be the current state of the as attribute (a + // destination), or "script" if it is in no state." [spec text] + // Step 3. "If destination is not script-like, then queue a task on the + // networking task source to fire an event named error at the link element, + // and return." [spec text] + // Currently we only support as="script". + if (!params.as.IsEmpty() && params.as != "script") { + document.AddConsoleMessage(ConsoleMessage::Create( + kOtherMessageSource, kWarningMessageLevel, + String("<link rel=modulepreload> has an invalid `as` value " + + params.as))); + if (link_loader) + link_loader->DispatchLinkLoadingErroredAsync(); + return; + } + + // Step 4. "Parse the URL given by the href attribute, relative to the + // element's node document. If that fails, then return. Otherwise, let url be + // the resulting URL record." [spec text] + // |href| is already resolved in caller side. + if (!params.href.IsValid()) { + document.AddConsoleMessage(ConsoleMessage::Create( + kOtherMessageSource, kWarningMessageLevel, + "<link rel=modulepreload> has an invalid `href` value " + + params.href.GetString())); + return; + } + + // Preload only if media matches. + // https://html.spec.whatwg.org/#processing-the-media-attribute + if (!params.media.IsEmpty()) { + MediaValues* media_values = + CreateMediaValues(document, viewport_description); + if (!MediaMatches(params.media, media_values)) + return; + } + + // Step 5. "Let settings object be the link element's node document's relevant + // settings object." [spec text] + // |document| is the node document here, and its context document is the + // relevant settings object. + Document* context_document = document.ContextDocument(); + + Modulator* modulator = + Modulator::From(ToScriptStateForMainWorld(context_document->GetFrame())); + DCHECK(modulator); + if (!modulator) + return; + + // Step 6. "Let credentials mode be the module script credentials mode for the + // crossorigin attribute." [spec text] + network::mojom::FetchCredentialsMode credentials_mode = + ScriptLoader::ModuleScriptCredentialsMode(params.cross_origin); + + // Step 7. "Let cryptographic nonce be the value of the nonce attribute, if it + // is specified, or the empty string otherwise." [spec text] + // |nonce| parameter is the value of the nonce attribute. + + // Step 8. "Let integrity metadata be the value of the integrity attribute, if + // it is specified, or the empty string otherwise." [spec text] + IntegrityMetadataSet integrity_metadata; + if (!params.integrity.IsEmpty()) { + SubresourceIntegrity::IntegrityFeatures integrity_features = + SubresourceIntegrityHelper::GetFeatures(&document); + SubresourceIntegrity::ReportInfo report_info; + SubresourceIntegrity::ParseIntegrityAttribute( + params.integrity, integrity_features, integrity_metadata, &report_info); + SubresourceIntegrityHelper::DoReport(document, report_info); + } + + // Step 9. "Let options be a script fetch options whose cryptographic nonce is + // cryptographic nonce, integrity metadata is integrity metadata, parser + // metadata is "not-parser-inserted", and credentials mode is credentials + // mode." [spec text] + ModuleScriptFetchRequest request( + params.href, params.referrer_policy, + ScriptFetchOptions(params.nonce, integrity_metadata, params.integrity, + kNotParserInserted, credentials_mode)); + + // Step 10. "Fetch a single module script given url, settings object, + // destination, options, settings object, "client", and with the top-level + // module fetch flag set. Wait until algorithm asynchronously completes with + // result." [spec text] + modulator->FetchSingle(request, ModuleGraphLevel::kDependentModuleFetch, + link_loader); + + Settings* settings = document.GetSettings(); + if (settings && settings->GetLogPreload()) { + document.AddConsoleMessage( + ConsoleMessage::Create(kOtherMessageSource, kVerboseMessageLevel, + "Module preload triggered for " + + params.href.Host() + params.href.GetPath())); + } + + // Asynchronously continue processing after + // LinkLoader::NotifyModuleLoadFinished() is called. +} + +static Resource* PrefetchIfNeeded(const LinkLoadParameters& params, + Document& document) { + if (params.rel.IsLinkPrefetch() && params.href.IsValid() && + document.GetFrame()) { + UseCounter::Count(document, WebFeature::kLinkRelPrefetch); + + ResourceRequest resource_request(params.href); + if (params.referrer_policy != kReferrerPolicyDefault) { + resource_request.SetHTTPReferrer(SecurityPolicy::GenerateReferrer( + params.referrer_policy, params.href, document.OutgoingReferrer())); + } + + ResourceLoaderOptions options; + options.initiator_info.name = FetchInitiatorTypeNames::link; + auto service = document.GetFrame()->PrefetchURLLoaderService(); + if (service) { + network::mojom::blink::URLLoaderFactoryPtr prefetch_url_loader_factory; + service->GetFactory(mojo::MakeRequest(&prefetch_url_loader_factory)); + options.url_loader_factory = base::MakeRefCounted< + base::RefCountedData<network::mojom::blink::URLLoaderFactoryPtr>>( + std::move(prefetch_url_loader_factory)); + } + + FetchParameters link_fetch_params(resource_request, options); + if (params.cross_origin != kCrossOriginAttributeNotSet) { + link_fetch_params.SetCrossOriginAccessControl( + document.GetSecurityOrigin(), params.cross_origin); + } + return LinkFetchResource::Fetch(Resource::kLinkPrefetch, link_fetch_params, + document.Fetcher()); + } + return nullptr; +} + +void LinkLoader::LoadLinksFromHeader( + const String& header_value, + const KURL& base_url, + LocalFrame& frame, + Document* document, + const NetworkHintsInterface& network_hints_interface, + CanLoadResources can_load_resources, + MediaPreloadPolicy media_policy, + ViewportDescriptionWrapper* viewport_description_wrapper) { + if (header_value.IsEmpty()) + return; + LinkHeaderSet header_set(header_value); + for (auto& header : header_set) { + if (!header.Valid() || header.Url().IsEmpty() || header.Rel().IsEmpty()) + continue; + + if (media_policy == kOnlyLoadMedia && header.Media().IsEmpty()) + continue; + if (media_policy == kOnlyLoadNonMedia && !header.Media().IsEmpty()) + continue; + + const LinkLoadParameters params(header, base_url); + // Sanity check to avoid re-entrancy here. + if (params.href == base_url) + continue; + if (can_load_resources != kOnlyLoadResources) { + DnsPrefetchIfNeeded(params, document, &frame, network_hints_interface, + kLinkCalledFromHeader); + + PreconnectIfNeeded(params, document, &frame, network_hints_interface, + kLinkCalledFromHeader); + } + if (can_load_resources != kDoNotLoadResources) { + DCHECK(document); + ViewportDescription* viewport_description = + (viewport_description_wrapper && viewport_description_wrapper->set) + ? &(viewport_description_wrapper->description) + : nullptr; + + PreloadIfNeeded(params, *document, base_url, kLinkCalledFromHeader, + viewport_description); + PrefetchIfNeeded(params, *document); + ModulePreloadIfNeeded(params, *document, viewport_description, nullptr); + } + if (params.rel.IsServiceWorker()) { + UseCounter::Count(&frame, WebFeature::kLinkHeaderServiceWorker); + } + // TODO(yoav): Add more supported headers as needed. + } +} + +bool LinkLoader::LoadLink( + const LinkLoadParameters& params, + Document& document, + const NetworkHintsInterface& network_hints_interface) { + // If any loading process is in progress, abort it. + Abort(); + + if (!client_->ShouldLoadLink()) + return false; + + DnsPrefetchIfNeeded(params, &document, document.GetFrame(), + network_hints_interface, kLinkCalledFromMarkup); + + PreconnectIfNeeded(params, &document, document.GetFrame(), + network_hints_interface, kLinkCalledFromMarkup); + + Resource* resource = PreloadIfNeeded(params, document, NullURL(), + kLinkCalledFromMarkup, nullptr); + if (!resource) { + resource = PrefetchIfNeeded(params, document); + } + if (resource) + finish_observer_ = new FinishObserver(this, resource); + + ModulePreloadIfNeeded(params, document, nullptr, this); + + if (const unsigned prerender_rel_types = + PrerenderRelTypesFromRelAttribute(params.rel, document)) { + if (!prerender_) { + prerender_ = PrerenderHandle::Create(document, this, params.href, + prerender_rel_types); + } else if (prerender_->Url() != params.href) { + prerender_->Cancel(); + prerender_ = PrerenderHandle::Create(document, this, params.href, + prerender_rel_types); + } + // TODO(gavinp): Handle changes to rel types of existing prerenders. + } else if (prerender_) { + prerender_->Cancel(); + prerender_.Clear(); + } + return true; +} + +void LinkLoader::DispatchLinkLoadingErroredAsync() { + client_->GetLoadingTaskRunner()->PostTask( + FROM_HERE, WTF::Bind(&LinkLoaderClient::LinkLoadingErrored, + WrapPersistent(client_.Get()))); +} + +void LinkLoader::Abort() { + if (prerender_) { + prerender_->Cancel(); + prerender_.Clear(); + } + if (finish_observer_) { + finish_observer_->ClearResource(); + finish_observer_ = nullptr; + } +} + +void LinkLoader::Trace(blink::Visitor* visitor) { + visitor->Trace(finish_observer_); + visitor->Trace(client_); + visitor->Trace(prerender_); + SingleModuleClient::Trace(visitor); + PrerenderClient::Trace(visitor); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/loader/link_loader.h b/chromium/third_party/blink/renderer/core/loader/link_loader.h new file mode 100644 index 00000000000..1f9eec38ca7 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/link_loader.h @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2011 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. + * + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_LINK_LOADER_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_LINK_LOADER_H_ + +#include "third_party/blink/renderer/core/core_export.h" +#include "third_party/blink/renderer/core/html/link_rel_attribute.h" +#include "third_party/blink/renderer/core/loader/link_loader_client.h" +#include "third_party/blink/renderer/core/script/modulator.h" +#include "third_party/blink/renderer/platform/cross_origin_attribute_value.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource.h" +#include "third_party/blink/renderer/platform/prerender_client.h" +#include "third_party/blink/renderer/platform/wtf/optional.h" + +namespace blink { + +class Document; +class LinkHeader; +class LocalFrame; +class NetworkHintsInterface; +class PrerenderHandle; +struct ViewportDescriptionWrapper; + +// The parameter object for LinkLoader::LoadLink(). +struct LinkLoadParameters { + LinkLoadParameters(const LinkRelAttribute& rel, + const CrossOriginAttributeValue& cross_origin, + const String& type, + const String& as, + const String& media, + const String& nonce, + const String& integrity, + const ReferrerPolicy& referrer_policy, + const KURL& href, + const String& srcset, + const String& sizes) + : rel(rel), + cross_origin(cross_origin), + type(type), + as(as), + media(media), + nonce(nonce), + integrity(integrity), + referrer_policy(referrer_policy), + href(href), + srcset(srcset), + sizes(sizes) {} + LinkLoadParameters(const LinkHeader&, const KURL& base_url); + + LinkRelAttribute rel; + CrossOriginAttributeValue cross_origin; + String type; + String as; + String media; + String nonce; + String integrity; + ReferrerPolicy referrer_policy; + KURL href; + String srcset; + String sizes; +}; + +// The LinkLoader can load link rel types icon, dns-prefetch, prefetch, and +// prerender. +class CORE_EXPORT LinkLoader final : public SingleModuleClient, + public PrerenderClient { + USING_GARBAGE_COLLECTED_MIXIN(LinkLoader); + + public: + static LinkLoader* Create(LinkLoaderClient* client) { + return new LinkLoader(client, client->GetLoadingTaskRunner()); + } + ~LinkLoader() override; + + // from PrerenderClient + void DidStartPrerender() override; + void DidStopPrerender() override; + void DidSendLoadForPrerender() override; + void DidSendDOMContentLoadedForPrerender() override; + + void Abort(); + bool LoadLink(const LinkLoadParameters&, + Document&, + const NetworkHintsInterface&); + void DispatchLinkLoadingErroredAsync(); + + enum CanLoadResources { + kOnlyLoadResources, + kDoNotLoadResources, + kLoadResourcesAndPreconnect + }; + // Media links cannot be preloaded until the first chunk is parsed. The rest + // can be preloaded at commit time. + enum MediaPreloadPolicy { kLoadAll, kOnlyLoadNonMedia, kOnlyLoadMedia }; + static void LoadLinksFromHeader(const String& header_value, + const KURL& base_url, + LocalFrame&, + Document*, // can be nullptr + const NetworkHintsInterface&, + CanLoadResources, + MediaPreloadPolicy, + ViewportDescriptionWrapper*); + static WTF::Optional<Resource::Type> GetResourceTypeFromAsAttribute( + const String& as); + + Resource* GetResourceForTesting(); + + void Trace(blink::Visitor*) override; + + private: + class FinishObserver; + LinkLoader(LinkLoaderClient*, scoped_refptr<base::SingleThreadTaskRunner>); + + void NotifyFinished(); + // SingleModuleClient implementation + void NotifyModuleLoadFinished(ModuleScript*) override; + + Member<FinishObserver> finish_observer_; + Member<LinkLoaderClient> client_; + + Member<PrerenderHandle> prerender_; +}; + +} // namespace blink + +#endif diff --git a/chromium/third_party/blink/renderer/core/loader/link_loader_client.h b/chromium/third_party/blink/renderer/core/loader/link_loader_client.h new file mode 100644 index 00000000000..c08098a08e8 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/link_loader_client.h @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2011 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. + * + */ +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_LINK_LOADER_CLIENT_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_LINK_LOADER_CLIENT_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" + +namespace blink { + +class CORE_EXPORT LinkLoaderClient : public GarbageCollectedMixin { + public: + virtual ~LinkLoaderClient() = default; + void Trace(blink::Visitor* visitor) override {} + + virtual bool ShouldLoadLink() = 0; + + virtual void LinkLoaded() = 0; + virtual void LinkLoadingErrored() = 0; + // There is no notification for cancellation. + + virtual void DidStartLinkPrerender() = 0; + virtual void DidStopLinkPrerender() = 0; + virtual void DidSendLoadForLinkPrerender() = 0; + virtual void DidSendDOMContentLoadedForLinkPrerender() = 0; + + virtual scoped_refptr<base::SingleThreadTaskRunner> + GetLoadingTaskRunner() = 0; +}; + +} // namespace blink + +#endif diff --git a/chromium/third_party/blink/renderer/core/loader/link_loader_test.cc b/chromium/third_party/blink/renderer/core/loader/link_loader_test.cc new file mode 100644 index 00000000000..4cb72fc6339 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/link_loader_test.cc @@ -0,0 +1,663 @@ +// 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/link_loader.h" + +#include <base/macros.h> +#include <memory> +#include "testing/gtest/include/gtest/gtest.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/bindings/core/v8/v8_binding_for_core.h" +#include "third_party/blink/renderer/core/frame/settings.h" +#include "third_party/blink/renderer/core/html/link_rel_attribute.h" +#include "third_party/blink/renderer/core/loader/document_loader.h" +#include "third_party/blink/renderer/core/loader/link_loader_client.h" +#include "third_party/blink/renderer/core/loader/modulescript/module_script_fetch_request.h" +#include "third_party/blink/renderer/core/loader/network_hints_interface.h" +#include "third_party/blink/renderer/core/testing/dummy_modulator.h" +#include "third_party/blink/renderer/core/testing/dummy_page_holder.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_load_priority.h" +#include "third_party/blink/renderer/platform/testing/url_test_helpers.h" + +namespace blink { + +namespace { + +class MockLinkLoaderClient final + : public GarbageCollectedFinalized<MockLinkLoaderClient>, + public LinkLoaderClient { + USING_GARBAGE_COLLECTED_MIXIN(MockLinkLoaderClient); + + public: + static MockLinkLoaderClient* Create(bool should_load) { + return new MockLinkLoaderClient(should_load); + } + + void Trace(blink::Visitor* visitor) override { + LinkLoaderClient::Trace(visitor); + } + + bool ShouldLoadLink() override { return should_load_; } + + void LinkLoaded() override {} + void LinkLoadingErrored() override {} + void DidStartLinkPrerender() override {} + void DidStopLinkPrerender() override {} + void DidSendLoadForLinkPrerender() override {} + void DidSendDOMContentLoadedForLinkPrerender() override {} + + scoped_refptr<base::SingleThreadTaskRunner> GetLoadingTaskRunner() override { + return Platform::Current()->CurrentThread()->GetTaskRunner(); + } + + private: + explicit MockLinkLoaderClient(bool should_load) : should_load_(should_load) {} + + const bool should_load_; +}; + +class NetworkHintsMock : public NetworkHintsInterface { + public: + NetworkHintsMock() = default; + + void DnsPrefetchHost(const String& host) const override { + did_dns_prefetch_ = true; + } + + void PreconnectHost( + const KURL& host, + const CrossOriginAttributeValue cross_origin) const override { + did_preconnect_ = true; + is_https_ = host.ProtocolIs("https"); + is_cross_origin_ = (cross_origin == kCrossOriginAttributeAnonymous); + } + + bool DidDnsPrefetch() { return did_dns_prefetch_; } + bool DidPreconnect() { return did_preconnect_; } + bool IsHTTPS() { return is_https_; } + bool IsCrossOrigin() { return is_cross_origin_; } + + private: + mutable bool did_dns_prefetch_ = false; + mutable bool did_preconnect_ = false; + mutable bool is_https_ = false; + mutable bool is_cross_origin_ = false; +}; + +class LinkLoaderPreloadTestBase : public testing::Test { + public: + struct Expectations { + ResourceLoadPriority priority; + WebURLRequest::RequestContext context; + bool link_loader_should_load_value; + KURL load_url; + ReferrerPolicy referrer_policy; + }; + + LinkLoaderPreloadTestBase() { + dummy_page_holder_ = DummyPageHolder::Create(IntSize(500, 500)); + } + + ~LinkLoaderPreloadTestBase() { + Platform::Current() + ->GetURLLoaderMockFactory() + ->UnregisterAllURLsAndClearMemoryCache(); + } + + protected: + void TestPreload(const LinkLoadParameters& params, + const Expectations& expected) { + ResourceFetcher* fetcher = dummy_page_holder_->GetDocument().Fetcher(); + ASSERT_TRUE(fetcher); + dummy_page_holder_->GetFrame().GetSettings()->SetScriptEnabled(true); + Persistent<MockLinkLoaderClient> loader_client = + MockLinkLoaderClient::Create(expected.link_loader_should_load_value); + LinkLoader* loader = LinkLoader::Create(loader_client.Get()); + URLTestHelpers::RegisterMockedErrorURLLoad(params.href); + loader->LoadLink(params, dummy_page_holder_->GetDocument(), + NetworkHintsMock()); + if (!expected.load_url.IsNull() && + expected.priority != ResourceLoadPriority::kUnresolved) { + ASSERT_EQ(1, fetcher->CountPreloads()); + Resource* resource = loader->GetResourceForTesting(); + ASSERT_NE(resource, nullptr); + EXPECT_EQ(expected.load_url.GetString(), resource->Url().GetString()); + EXPECT_TRUE(fetcher->ContainsAsPreload(resource)); + EXPECT_EQ(expected.priority, resource->GetResourceRequest().Priority()); + EXPECT_EQ(expected.context, + resource->GetResourceRequest().GetRequestContext()); + if (expected.referrer_policy != kReferrerPolicyDefault) { + EXPECT_EQ(expected.referrer_policy, + resource->GetResourceRequest().GetReferrerPolicy()); + } + } else { + ASSERT_EQ(0, fetcher->CountPreloads()); + } + } + std::unique_ptr<DummyPageHolder> dummy_page_holder_; +}; + +struct PreloadTestParams { + const char* href; + const char* as; + const ResourceLoadPriority priority; + const WebURLRequest::RequestContext context; + const bool expecting_load; +}; + +constexpr PreloadTestParams kPreloadTestParams[] = { + {"http://example.test/cat.jpg", "image", ResourceLoadPriority::kLow, + WebURLRequest::kRequestContextImage, true}, + {"http://example.test/cat.js", "script", ResourceLoadPriority::kHigh, + WebURLRequest::kRequestContextScript, true}, + {"http://example.test/cat.css", "style", ResourceLoadPriority::kVeryHigh, + WebURLRequest::kRequestContextStyle, true}, + // TODO(yoav): It doesn't seem like the audio context is ever used. That + // should probably be fixed (or we can consolidate audio and video). + {"http://example.test/cat.wav", "audio", ResourceLoadPriority::kLow, + WebURLRequest::kRequestContextAudio, true}, + {"http://example.test/cat.mp4", "video", ResourceLoadPriority::kLow, + WebURLRequest::kRequestContextVideo, true}, + {"http://example.test/cat.vtt", "track", ResourceLoadPriority::kLow, + WebURLRequest::kRequestContextTrack, true}, + {"http://example.test/cat.woff", "font", ResourceLoadPriority::kHigh, + WebURLRequest::kRequestContextFont, true}, + // TODO(yoav): subresource should be *very* low priority (rather than + // low). + {"http://example.test/cat.empty", "fetch", ResourceLoadPriority::kHigh, + WebURLRequest::kRequestContextSubresource, true}, + {"http://example.test/cat.blob", "blabla", ResourceLoadPriority::kLow, + WebURLRequest::kRequestContextSubresource, false}, + {"http://example.test/cat.blob", "", ResourceLoadPriority::kLow, + WebURLRequest::kRequestContextSubresource, false}, + {"bla://example.test/cat.gif", "image", ResourceLoadPriority::kUnresolved, + WebURLRequest::kRequestContextImage, false}}; + +class LinkLoaderPreloadTest + : public LinkLoaderPreloadTestBase, + public testing::WithParamInterface<PreloadTestParams> {}; + +TEST_P(LinkLoaderPreloadTest, Preload) { + const auto& test_case = GetParam(); + LinkLoadParameters params( + LinkRelAttribute("preload"), kCrossOriginAttributeNotSet, String(), + test_case.as, String(), String(), String(), kReferrerPolicyDefault, + KURL(NullURL(), test_case.href), String(), String()); + Expectations expectations = { + test_case.priority, test_case.context, test_case.expecting_load, + test_case.expecting_load ? params.href : NullURL(), + kReferrerPolicyDefault}; + TestPreload(params, expectations); +} + +INSTANTIATE_TEST_CASE_P(LinkLoaderPreloadTest, + LinkLoaderPreloadTest, + testing::ValuesIn(kPreloadTestParams)); + +struct PreloadMimeTypeTestParams { + const char* href; + const char* as; + const char* type; + const ResourceLoadPriority priority; + const WebURLRequest::RequestContext context; + const bool expecting_load; +}; + +constexpr PreloadMimeTypeTestParams kPreloadMimeTypeTestParams[] = { + {"http://example.test/cat.webp", "image", "image/webp", + ResourceLoadPriority::kLow, WebURLRequest::kRequestContextImage, true}, + {"http://example.test/cat.svg", "image", "image/svg+xml", + ResourceLoadPriority::kLow, WebURLRequest::kRequestContextImage, true}, + {"http://example.test/cat.jxr", "image", "image/jxr", + ResourceLoadPriority::kUnresolved, WebURLRequest::kRequestContextImage, + false}, + {"http://example.test/cat.js", "script", "text/javascript", + ResourceLoadPriority::kHigh, WebURLRequest::kRequestContextScript, true}, + {"http://example.test/cat.js", "script", "text/coffeescript", + ResourceLoadPriority::kUnresolved, WebURLRequest::kRequestContextScript, + false}, + {"http://example.test/cat.css", "style", "text/css", + ResourceLoadPriority::kVeryHigh, WebURLRequest::kRequestContextStyle, + true}, + {"http://example.test/cat.css", "style", "text/sass", + ResourceLoadPriority::kUnresolved, WebURLRequest::kRequestContextStyle, + false}, + {"http://example.test/cat.wav", "audio", "audio/wav", + ResourceLoadPriority::kLow, WebURLRequest::kRequestContextAudio, true}, + {"http://example.test/cat.wav", "audio", "audio/mp57", + ResourceLoadPriority::kUnresolved, WebURLRequest::kRequestContextAudio, + false}, + {"http://example.test/cat.webm", "video", "video/webm", + ResourceLoadPriority::kLow, WebURLRequest::kRequestContextVideo, true}, + {"http://example.test/cat.mp199", "video", "video/mp199", + ResourceLoadPriority::kUnresolved, WebURLRequest::kRequestContextVideo, + false}, + {"http://example.test/cat.vtt", "track", "text/vtt", + ResourceLoadPriority::kLow, WebURLRequest::kRequestContextTrack, true}, + {"http://example.test/cat.vtt", "track", "text/subtitlething", + ResourceLoadPriority::kUnresolved, WebURLRequest::kRequestContextTrack, + false}, + {"http://example.test/cat.woff", "font", "font/woff2", + ResourceLoadPriority::kHigh, WebURLRequest::kRequestContextFont, true}, + {"http://example.test/cat.woff", "font", "font/woff84", + ResourceLoadPriority::kUnresolved, WebURLRequest::kRequestContextFont, + false}, + {"http://example.test/cat.empty", "fetch", "foo/bar", + ResourceLoadPriority::kHigh, WebURLRequest::kRequestContextSubresource, + true}, + {"http://example.test/cat.blob", "blabla", "foo/bar", + ResourceLoadPriority::kLow, WebURLRequest::kRequestContextSubresource, + false}, + {"http://example.test/cat.blob", "", "foo/bar", ResourceLoadPriority::kLow, + WebURLRequest::kRequestContextSubresource, false}}; + +class LinkLoaderPreloadMimeTypeTest + : public LinkLoaderPreloadTestBase, + public testing::WithParamInterface<PreloadMimeTypeTestParams> {}; + +TEST_P(LinkLoaderPreloadMimeTypeTest, Preload) { + const auto& test_case = GetParam(); + LinkLoadParameters params( + LinkRelAttribute("preload"), kCrossOriginAttributeNotSet, test_case.type, + test_case.as, String(), String(), String(), kReferrerPolicyDefault, + KURL(NullURL(), test_case.href), String(), String()); + Expectations expectations = { + test_case.priority, test_case.context, test_case.expecting_load, + test_case.expecting_load ? params.href : NullURL(), + kReferrerPolicyDefault}; + TestPreload(params, expectations); +} + +INSTANTIATE_TEST_CASE_P(LinkLoaderPreloadMimeTypeTest, + LinkLoaderPreloadMimeTypeTest, + testing::ValuesIn(kPreloadMimeTypeTestParams)); + +struct PreloadMediaTestParams { + const char* media; + const ResourceLoadPriority priority; + const bool link_loader_should_load_value; + const bool expecting_load; +}; + +constexpr PreloadMediaTestParams kPreloadMediaTestParams[] = { + {"(max-width: 600px)", ResourceLoadPriority::kLow, true, true}, + {"(max-width: 400px)", ResourceLoadPriority::kUnresolved, true, false}, + {"(max-width: 600px)", ResourceLoadPriority::kLow, false, false}}; + +class LinkLoaderPreloadMediaTest + : public LinkLoaderPreloadTestBase, + public testing::WithParamInterface<PreloadMediaTestParams> {}; + +TEST_P(LinkLoaderPreloadMediaTest, Preload) { + const auto& test_case = GetParam(); + LinkLoadParameters params( + LinkRelAttribute("preload"), kCrossOriginAttributeNotSet, "image/gif", + "image", test_case.media, String(), String(), kReferrerPolicyDefault, + KURL(NullURL(), "http://example.test/cat.gif"), String(), String()); + Expectations expectations = { + test_case.priority, WebURLRequest::kRequestContextImage, + test_case.link_loader_should_load_value, + test_case.expecting_load ? params.href : NullURL(), + kReferrerPolicyDefault}; + TestPreload(params, expectations); +} + +INSTANTIATE_TEST_CASE_P(LinkLoaderPreloadMediaTest, + LinkLoaderPreloadMediaTest, + testing::ValuesIn(kPreloadMediaTestParams)); + +constexpr ReferrerPolicy kPreloadReferrerPolicyTestParams[] = { + kReferrerPolicyOrigin, + kReferrerPolicyOriginWhenCrossOrigin, + kReferrerPolicySameOrigin, + kReferrerPolicyStrictOrigin, + kReferrerPolicyStrictOriginWhenCrossOrigin, + kReferrerPolicyNever}; + +class LinkLoaderPreloadReferrerPolicyTest + : public LinkLoaderPreloadTestBase, + public testing::WithParamInterface<ReferrerPolicy> {}; + +TEST_P(LinkLoaderPreloadReferrerPolicyTest, Preload) { + const ReferrerPolicy referrer_policy = GetParam(); + LinkLoadParameters params( + LinkRelAttribute("preload"), kCrossOriginAttributeNotSet, "image/gif", + "image", String(), String(), String(), referrer_policy, + KURL(NullURL(), "http://example.test/cat.gif"), String(), String()); + Expectations expectations = {ResourceLoadPriority::kLow, + WebURLRequest::kRequestContextImage, true, + params.href, referrer_policy}; + TestPreload(params, expectations); +} + +INSTANTIATE_TEST_CASE_P(LinkLoaderPreloadReferrerPolicyTest, + LinkLoaderPreloadReferrerPolicyTest, + testing::ValuesIn(kPreloadReferrerPolicyTestParams)); + +struct PreloadNonceTestParams { + const char* nonce; + const char* content_security_policy; + const bool expecting_load; +}; + +constexpr PreloadNonceTestParams kPreloadNonceTestParams[] = { + {"abc", "script-src 'nonce-abc'", true}, + {"", "script-src 'nonce-abc'", false}, + {"def", "script-src 'nonce-abc'", false}, +}; + +class LinkLoaderPreloadNonceTest + : public LinkLoaderPreloadTestBase, + public testing::WithParamInterface<PreloadNonceTestParams> {}; + +TEST_P(LinkLoaderPreloadNonceTest, Preload) { + const auto& test_case = GetParam(); + dummy_page_holder_->GetDocument() + .GetContentSecurityPolicy() + ->DidReceiveHeader(test_case.content_security_policy, + kContentSecurityPolicyHeaderTypeEnforce, + kContentSecurityPolicyHeaderSourceHTTP); + LinkLoadParameters params( + LinkRelAttribute("preload"), kCrossOriginAttributeNotSet, String(), + "script", String(), test_case.nonce, String(), kReferrerPolicyDefault, + KURL(NullURL(), "http://example.test/cat.js"), String(), String()); + Expectations expectations = { + ResourceLoadPriority::kHigh, WebURLRequest::kRequestContextScript, + test_case.expecting_load, + test_case.expecting_load ? params.href : NullURL(), + kReferrerPolicyDefault}; + TestPreload(params, expectations); +} + +INSTANTIATE_TEST_CASE_P(LinkLoaderPreloadNonceTest, + LinkLoaderPreloadNonceTest, + testing::ValuesIn(kPreloadNonceTestParams)); + +struct PreloadSrcsetTestParams { + const char* href; + const char* srcset; + const char* sizes; + float scale_factor; + const char* expected_url; +}; + +constexpr PreloadSrcsetTestParams kPreloadSrcsetTestParams[] = { + {"http://example.test/cat.gif", + "http://example.test/cat1x.gif 1x, http://example.test/cat2x.gif 2x", + nullptr, 1.0, "http://example.test/cat1x.gif"}, + {"http://example.test/cat.gif", + "http://example.test/cat1x.gif 1x, http://example.test/cat2x.gif 2x", + nullptr, 2.0, "http://example.test/cat2x.gif"}, + {"http://example.test/cat.gif", + "http://example.test/cat400.gif 400w, http://example.test/cat800.gif 800w", + "400px", 1.0, "http://example.test/cat400.gif"}, + {"http://example.test/cat.gif", + "http://example.test/cat400.gif 400w, http://example.test/cat800.gif 800w", + "400px", 2.0, "http://example.test/cat800.gif"}, + {"http://example.test/cat.gif", + "cat200.gif 200w, cat400.gif 400w, cat800.gif 800w", "200px", 1.0, + "http://example.test/cat200.gif"}, +}; + +class LinkLoaderPreloadSrcsetTest + : public LinkLoaderPreloadTestBase, + public testing::WithParamInterface<PreloadSrcsetTestParams> {}; + +TEST_P(LinkLoaderPreloadSrcsetTest, Preload) { + const auto& test_case = GetParam(); + dummy_page_holder_->GetDocument().SetBaseURLOverride( + KURL("http://example.test/")); + dummy_page_holder_->GetPage().SetDeviceScaleFactorDeprecated( + test_case.scale_factor); + LinkLoadParameters params( + LinkRelAttribute("preload"), kCrossOriginAttributeNotSet, "image/gif", + "image", String(), String(), String(), kReferrerPolicyDefault, + KURL(NullURL(), test_case.href), test_case.srcset, test_case.sizes); + Expectations expectations = { + ResourceLoadPriority::kLow, WebURLRequest::kRequestContextImage, true, + KURL(NullURL(), test_case.expected_url), kReferrerPolicyDefault}; + TestPreload(params, expectations); +} + +INSTANTIATE_TEST_CASE_P(LinkLoaderPreloadSrcsetTest, + LinkLoaderPreloadSrcsetTest, + testing::ValuesIn(kPreloadSrcsetTestParams)); + +struct ModulePreloadTestParams { + const char* href; + const char* nonce; + const char* integrity; + CrossOriginAttributeValue cross_origin; + ReferrerPolicy referrer_policy; + bool expecting_load; + network::mojom::FetchCredentialsMode expected_credentials_mode; +}; + +constexpr ModulePreloadTestParams kModulePreloadTestParams[] = { + {"", nullptr, nullptr, kCrossOriginAttributeNotSet, kReferrerPolicyDefault, + false, network::mojom::FetchCredentialsMode::kOmit}, + {"http://example.test/cat.js", nullptr, nullptr, + kCrossOriginAttributeNotSet, kReferrerPolicyDefault, true, + network::mojom::FetchCredentialsMode::kOmit}, + {"http://example.test/cat.js", nullptr, nullptr, + kCrossOriginAttributeAnonymous, kReferrerPolicyDefault, true, + network::mojom::FetchCredentialsMode::kSameOrigin}, + {"http://example.test/cat.js", "nonce", nullptr, + kCrossOriginAttributeNotSet, kReferrerPolicyNever, true, + network::mojom::FetchCredentialsMode::kOmit}, + {"http://example.test/cat.js", nullptr, "sha384-abc", + kCrossOriginAttributeNotSet, kReferrerPolicyDefault, true, + network::mojom::FetchCredentialsMode::kOmit}}; + +class LinkLoaderModulePreloadTest + : public testing::TestWithParam<ModulePreloadTestParams> {}; + +class ModulePreloadTestModulator final : public DummyModulator { + public: + ModulePreloadTestModulator(const ModulePreloadTestParams* params) + : params_(params), fetched_(false) {} + + void FetchSingle(const ModuleScriptFetchRequest& request, + ModuleGraphLevel, + SingleModuleClient*) override { + fetched_ = true; + + EXPECT_EQ(KURL(NullURL(), params_->href), request.Url()); + EXPECT_EQ(params_->nonce, request.Options().Nonce()); + EXPECT_EQ(kNotParserInserted, request.Options().ParserState()); + EXPECT_EQ(params_->expected_credentials_mode, + request.Options().CredentialsMode()); + EXPECT_EQ(AtomicString(), request.GetReferrer()); + EXPECT_EQ(params_->referrer_policy, request.GetReferrerPolicy()); + EXPECT_EQ(params_->integrity, + request.Options().GetIntegrityAttributeValue()); + } + + bool fetched() const { return fetched_; } + + private: + const ModulePreloadTestParams* params_; + bool fetched_; +}; + +TEST_P(LinkLoaderModulePreloadTest, ModulePreload) { + const auto& test_case = GetParam(); + std::unique_ptr<DummyPageHolder> dummy_page_holder = + DummyPageHolder::Create(); + ModulePreloadTestModulator* modulator = + new ModulePreloadTestModulator(&test_case); + Modulator::SetModulator( + ToScriptStateForMainWorld(dummy_page_holder->GetDocument().GetFrame()), + modulator); + Persistent<MockLinkLoaderClient> loader_client = + MockLinkLoaderClient::Create(true); + LinkLoader* loader = LinkLoader::Create(loader_client.Get()); + KURL href_url = KURL(NullURL(), test_case.href); + LinkLoadParameters params( + LinkRelAttribute("modulepreload"), test_case.cross_origin, + String() /* type */, String() /* as */, String() /* media */, + test_case.nonce, test_case.integrity, test_case.referrer_policy, href_url, + String() /* srcset */, String() /* sizes */); + loader->LoadLink(params, dummy_page_holder->GetDocument(), + NetworkHintsMock()); + ASSERT_EQ(test_case.expecting_load, modulator->fetched()); +} + +INSTANTIATE_TEST_CASE_P(LinkLoaderModulePreloadTest, + LinkLoaderModulePreloadTest, + testing::ValuesIn(kModulePreloadTestParams)); + +TEST(LinkLoaderTest, Prefetch) { + struct TestCase { + const char* href; + // TODO(yoav): Add support for type and media crbug.com/662687 + const char* type; + const char* media; + const ReferrerPolicy referrer_policy; + const bool link_loader_should_load_value; + const bool expecting_load; + const ReferrerPolicy expected_referrer_policy; + } cases[] = { + // Referrer Policy + {"http://example.test/cat.jpg", "image/jpg", "", kReferrerPolicyOrigin, + true, true, kReferrerPolicyOrigin}, + {"http://example.test/cat.jpg", "image/jpg", "", + kReferrerPolicyOriginWhenCrossOrigin, true, true, + kReferrerPolicyOriginWhenCrossOrigin}, + {"http://example.test/cat.jpg", "image/jpg", "", kReferrerPolicyNever, + true, true, kReferrerPolicyNever}, + }; + + // Test the cases with a single header + for (const auto& test_case : cases) { + std::unique_ptr<DummyPageHolder> dummy_page_holder = + DummyPageHolder::Create(IntSize(500, 500)); + dummy_page_holder->GetFrame().GetSettings()->SetScriptEnabled(true); + Persistent<MockLinkLoaderClient> loader_client = + MockLinkLoaderClient::Create(test_case.link_loader_should_load_value); + LinkLoader* loader = LinkLoader::Create(loader_client.Get()); + KURL href_url = KURL(NullURL(), test_case.href); + URLTestHelpers::RegisterMockedErrorURLLoad(href_url); + LinkLoadParameters params( + LinkRelAttribute("prefetch"), kCrossOriginAttributeNotSet, + test_case.type, "", test_case.media, "", "", test_case.referrer_policy, + href_url, String() /* srcset */, String() /* sizes */); + loader->LoadLink(params, dummy_page_holder->GetDocument(), + NetworkHintsMock()); + ASSERT_TRUE(dummy_page_holder->GetDocument().Fetcher()); + Resource* resource = loader->GetResourceForTesting(); + if (test_case.expecting_load) { + EXPECT_TRUE(resource); + } else { + EXPECT_FALSE(resource); + } + if (resource) { + if (test_case.expected_referrer_policy != kReferrerPolicyDefault) { + EXPECT_EQ(test_case.expected_referrer_policy, + resource->GetResourceRequest().GetReferrerPolicy()); + } + } + Platform::Current() + ->GetURLLoaderMockFactory() + ->UnregisterAllURLsAndClearMemoryCache(); + } +} + +TEST(LinkLoaderTest, DNSPrefetch) { + struct { + const char* href; + const bool should_load; + } cases[] = { + {"http://example.com/", true}, + {"https://example.com/", true}, + {"//example.com/", true}, + {"//example.com/", false}, + }; + + // Test the cases with a single header + for (const auto& test_case : cases) { + std::unique_ptr<DummyPageHolder> dummy_page_holder = + DummyPageHolder::Create(IntSize(500, 500)); + dummy_page_holder->GetDocument().GetSettings()->SetDNSPrefetchingEnabled( + true); + Persistent<MockLinkLoaderClient> loader_client = + MockLinkLoaderClient::Create(test_case.should_load); + LinkLoader* loader = LinkLoader::Create(loader_client.Get()); + KURL href_url = KURL(KURL(String("http://example.com")), test_case.href); + NetworkHintsMock network_hints; + LinkLoadParameters params( + LinkRelAttribute("dns-prefetch"), kCrossOriginAttributeNotSet, String(), + String(), String(), String(), String(), kReferrerPolicyDefault, + href_url, String() /* srcset */, String() /* sizes */); + loader->LoadLink(params, dummy_page_holder->GetDocument(), network_hints); + EXPECT_FALSE(network_hints.DidPreconnect()); + EXPECT_EQ(test_case.should_load, network_hints.DidDnsPrefetch()); + } +} + +TEST(LinkLoaderTest, Preconnect) { + struct { + const char* href; + CrossOriginAttributeValue cross_origin; + const bool should_load; + const bool is_https; + const bool is_cross_origin; + } cases[] = { + {"http://example.com/", kCrossOriginAttributeNotSet, true, false, false}, + {"https://example.com/", kCrossOriginAttributeNotSet, true, true, false}, + {"http://example.com/", kCrossOriginAttributeAnonymous, true, false, + true}, + {"//example.com/", kCrossOriginAttributeNotSet, true, false, false}, + {"http://example.com/", kCrossOriginAttributeNotSet, false, false, false}, + }; + + // Test the cases with a single header + for (const auto& test_case : cases) { + std::unique_ptr<DummyPageHolder> dummy_page_holder = + DummyPageHolder::Create(IntSize(500, 500)); + Persistent<MockLinkLoaderClient> loader_client = + MockLinkLoaderClient::Create(test_case.should_load); + LinkLoader* loader = LinkLoader::Create(loader_client.Get()); + KURL href_url = KURL(KURL(String("http://example.com")), test_case.href); + NetworkHintsMock network_hints; + LinkLoadParameters params( + LinkRelAttribute("preconnect"), test_case.cross_origin, String(), + String(), String(), String(), String(), kReferrerPolicyDefault, + href_url, String() /* srcset */, String() /* sizes */); + loader->LoadLink(params, dummy_page_holder->GetDocument(), network_hints); + EXPECT_EQ(test_case.should_load, network_hints.DidPreconnect()); + EXPECT_EQ(test_case.is_https, network_hints.IsHTTPS()); + EXPECT_EQ(test_case.is_cross_origin, network_hints.IsCrossOrigin()); + } +} + +TEST(LinkLoaderTest, PreloadAndPrefetch) { + std::unique_ptr<DummyPageHolder> dummy_page_holder = + DummyPageHolder::Create(IntSize(500, 500)); + ResourceFetcher* fetcher = dummy_page_holder->GetDocument().Fetcher(); + ASSERT_TRUE(fetcher); + dummy_page_holder->GetFrame().GetSettings()->SetScriptEnabled(true); + Persistent<MockLinkLoaderClient> loader_client = + MockLinkLoaderClient::Create(true); + LinkLoader* loader = LinkLoader::Create(loader_client.Get()); + KURL href_url = KURL(KURL(), "https://www.example.com/"); + URLTestHelpers::RegisterMockedErrorURLLoad(href_url); + LinkLoadParameters params( + LinkRelAttribute("preload prefetch"), kCrossOriginAttributeNotSet, + "application/javascript", "script", "", "", "", kReferrerPolicyDefault, + href_url, String() /* srcset */, String() /* sizes */); + loader->LoadLink(params, dummy_page_holder->GetDocument(), + NetworkHintsMock()); + ASSERT_EQ(1, fetcher->CountPreloads()); + Resource* resource = loader->GetResourceForTesting(); + ASSERT_NE(resource, nullptr); + EXPECT_TRUE(resource->IsLinkPreload()); +} + +} // namespace + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/loader/mixed_content_checker.cc b/chromium/third_party/blink/renderer/core/loader/mixed_content_checker.cc new file mode 100644 index 00000000000..1a35040ffc2 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/mixed_content_checker.cc @@ -0,0 +1,748 @@ +/* + * Copyright (C) 2012 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: + * + * 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") 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 APPLE AND ITS 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 APPLE OR ITS 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/mixed_content_checker.h" + +#include "services/network/public/mojom/request_context_frame_type.mojom-blink.h" +#include "third_party/blink/public/mojom/net/ip_address_space.mojom-blink.h" +#include "third_party/blink/public/platform/web_insecure_request_policy.h" +#include "third_party/blink/public/platform/web_mixed_content.h" +#include "third_party/blink/public/platform/web_security_origin.h" +#include "third_party/blink/public/platform/web_worker_fetch_context.h" +#include "third_party/blink/renderer/core/dom/document.h" +#include "third_party/blink/renderer/core/frame/content_settings_client.h" +#include "third_party/blink/renderer/core/frame/frame.h" +#include "third_party/blink/renderer/core/frame/local_frame.h" +#include "third_party/blink/renderer/core/frame/local_frame_client.h" +#include "third_party/blink/renderer/core/frame/settings.h" +#include "third_party/blink/renderer/core/frame/use_counter.h" +#include "third_party/blink/renderer/core/inspector/console_message.h" +#include "third_party/blink/renderer/core/loader/document_loader.h" +#include "third_party/blink/renderer/core/workers/worker_content_settings_client.h" +#include "third_party/blink/renderer/core/workers/worker_global_scope.h" +#include "third_party/blink/renderer/core/workers/worker_or_worklet_global_scope.h" +#include "third_party/blink/renderer/core/workers/worker_settings.h" +#include "third_party/blink/renderer/platform/network/network_utils.h" +#include "third_party/blink/renderer/platform/weborigin/scheme_registry.h" +#include "third_party/blink/renderer/platform/weborigin/security_origin.h" +#include "third_party/blink/renderer/platform/wtf/text/string_builder.h" + +namespace blink { + +namespace { + +// When a frame is local, use its full URL to represent the main resource. When +// the frame is remote, the full URL isn't accessible, so use the origin. This +// function is used, for example, to determine the URL to show in console +// messages about mixed content. +KURL MainResourceUrlForFrame(Frame* frame) { + if (frame->IsRemoteFrame()) { + return KURL(NullURL(), + frame->GetSecurityContext()->GetSecurityOrigin()->ToString()); + } + return ToLocalFrame(frame)->GetDocument()->Url(); +} + +const char* RequestContextName(WebURLRequest::RequestContext context) { + switch (context) { + case WebURLRequest::kRequestContextAudio: + return "audio file"; + case WebURLRequest::kRequestContextBeacon: + return "Beacon endpoint"; + case WebURLRequest::kRequestContextCSPReport: + return "Content Security Policy reporting endpoint"; + case WebURLRequest::kRequestContextDownload: + return "download"; + case WebURLRequest::kRequestContextEmbed: + return "plugin resource"; + case WebURLRequest::kRequestContextEventSource: + return "EventSource endpoint"; + case WebURLRequest::kRequestContextFavicon: + return "favicon"; + case WebURLRequest::kRequestContextFetch: + return "resource"; + case WebURLRequest::kRequestContextFont: + return "font"; + case WebURLRequest::kRequestContextForm: + return "form action"; + case WebURLRequest::kRequestContextFrame: + return "frame"; + case WebURLRequest::kRequestContextHyperlink: + return "resource"; + case WebURLRequest::kRequestContextIframe: + return "frame"; + case WebURLRequest::kRequestContextImage: + return "image"; + case WebURLRequest::kRequestContextImageSet: + return "image"; + case WebURLRequest::kRequestContextImport: + return "HTML Import"; + case WebURLRequest::kRequestContextInternal: + return "resource"; + case WebURLRequest::kRequestContextLocation: + return "resource"; + case WebURLRequest::kRequestContextManifest: + return "manifest"; + case WebURLRequest::kRequestContextObject: + return "plugin resource"; + case WebURLRequest::kRequestContextPing: + return "hyperlink auditing endpoint"; + case WebURLRequest::kRequestContextPlugin: + return "plugin data"; + case WebURLRequest::kRequestContextPrefetch: + return "prefetch resource"; + case WebURLRequest::kRequestContextScript: + return "script"; + case WebURLRequest::kRequestContextServiceWorker: + return "Service Worker script"; + case WebURLRequest::kRequestContextSharedWorker: + return "Shared Worker script"; + case WebURLRequest::kRequestContextStyle: + return "stylesheet"; + case WebURLRequest::kRequestContextSubresource: + return "resource"; + case WebURLRequest::kRequestContextTrack: + return "Text Track"; + case WebURLRequest::kRequestContextUnspecified: + return "resource"; + case WebURLRequest::kRequestContextVideo: + return "video"; + case WebURLRequest::kRequestContextWorker: + return "Worker script"; + case WebURLRequest::kRequestContextXMLHttpRequest: + return "XMLHttpRequest endpoint"; + case WebURLRequest::kRequestContextXSLT: + return "XSLT"; + } + NOTREACHED(); + return "resource"; +} + +// TODO(nhiroki): Consider adding interfaces for Settings/WorkerSettings and +// ContentSettingsClient/WorkerContentSettingsClient to avoid using C++ +// template. +template <typename SettingsType, typename SettingsClientType> +bool IsWebSocketAllowedImpl(ExecutionContext* execution_context, + SecurityContext* security_context, + const SecurityOrigin* security_origin, + SettingsType* settings, + SettingsClientType* settings_client, + const KURL& url) { + UseCounter::Count(execution_context, WebFeature::kMixedContentPresent); + UseCounter::Count(execution_context, WebFeature::kMixedContentWebSocket); + if (ContentSecurityPolicy* policy = + security_context->GetContentSecurityPolicy()) { + policy->ReportMixedContent(url, + ResourceRequest::RedirectStatus::kNoRedirect); + } + + // If we're in strict mode, we'll automagically fail everything, and + // intentionally skip the client checks in order to prevent degrading the + // site's security UI. + bool strict_mode = + security_context->GetInsecureRequestPolicy() & kBlockAllMixedContent || + settings->GetStrictMixedContentChecking(); + if (strict_mode) + return false; + bool allowed_per_settings = + settings && settings->GetAllowRunningOfInsecureContent(); + return settings_client->AllowRunningInsecureContent(allowed_per_settings, + security_origin, url); +} + +} // namespace + +static void MeasureStricterVersionOfIsMixedContent(Frame& frame, + const KURL& url, + const LocalFrame* source) { + // We're currently only checking for mixed content in `https://*` contexts. + // What about other "secure" contexts the SchemeRegistry knows about? We'll + // use this method to measure the occurrence of non-webby mixed content to + // make sure we're not breaking the world without realizing it. + const SecurityOrigin* origin = + frame.GetSecurityContext()->GetSecurityOrigin(); + if (MixedContentChecker::IsMixedContent(origin, url)) { + if (origin->Protocol() != "https") { + UseCounter::Count( + source, + WebFeature::kMixedContentInNonHTTPSFrameThatRestrictsMixedContent); + } + } else if (!SecurityOrigin::IsSecure(url) && + SchemeRegistry::ShouldTreatURLSchemeAsSecure(origin->Protocol())) { + UseCounter::Count( + source, + WebFeature::kMixedContentInSecureFrameThatDoesNotRestrictMixedContent); + } +} + +bool RequestIsSubframeSubresource( + Frame* frame, + network::mojom::RequestContextFrameType frame_type) { + return (frame && frame != frame->Tree().Top() && + frame_type != network::mojom::RequestContextFrameType::kNested); +} + +// static +bool MixedContentChecker::IsMixedContent(const SecurityOrigin* security_origin, + const KURL& url) { + if (!SchemeRegistry::ShouldTreatURLSchemeAsRestrictingMixedContent( + security_origin->Protocol())) + return false; + + // |url| is mixed content if its origin is not potentially trustworthy nor + // secure. We do a quick check against `SecurityOrigin::isSecure` to catch + // things like `about:blank`, which cannot be sanely passed into + // `SecurityOrigin::create` (as their origin depends on their context). + // blob: and filesystem: URLs never hit the network, and access is restricted + // to same-origin contexts, so they are not blocked either. + bool is_allowed = url.ProtocolIs("blob") || url.ProtocolIs("filesystem") || + SecurityOrigin::IsSecure(url) || + SecurityOrigin::Create(url)->IsPotentiallyTrustworthy(); + return !is_allowed; +} + +// static +Frame* MixedContentChecker::InWhichFrameIsContentMixed( + Frame* frame, + network::mojom::RequestContextFrameType frame_type, + const KURL& url, + const LocalFrame* source) { + // We only care about subresource loads; top-level navigations cannot be mixed + // content. Neither can frameless requests. + if (frame_type == network::mojom::RequestContextFrameType::kTopLevel || + !frame) + return nullptr; + + // Check the top frame first. + Frame& top = frame->Tree().Top(); + MeasureStricterVersionOfIsMixedContent(top, url, source); + if (IsMixedContent(top.GetSecurityContext()->GetSecurityOrigin(), url)) + return ⊤ + + MeasureStricterVersionOfIsMixedContent(*frame, url, source); + if (IsMixedContent(frame->GetSecurityContext()->GetSecurityOrigin(), url)) + return frame; + + // No mixed content, no problem. + return nullptr; +} + +// static +void MixedContentChecker::LogToConsoleAboutFetch( + ExecutionContext* execution_context, + const KURL& main_resource_url, + const KURL& url, + WebURLRequest::RequestContext request_context, + bool allowed, + std::unique_ptr<SourceLocation> source_location) { + String message = String::Format( + "Mixed Content: The page at '%s' was loaded over HTTPS, but requested an " + "insecure %s '%s'. %s", + main_resource_url.ElidedString().Utf8().data(), + RequestContextName(request_context), url.ElidedString().Utf8().data(), + allowed ? "This content should also be served over HTTPS." + : "This request has been blocked; the content must be served " + "over HTTPS."); + MessageLevel message_level = + allowed ? kWarningMessageLevel : kErrorMessageLevel; + if (source_location) { + execution_context->AddConsoleMessage( + ConsoleMessage::Create(kSecurityMessageSource, message_level, message, + std::move(source_location))); + } else { + execution_context->AddConsoleMessage( + ConsoleMessage::Create(kSecurityMessageSource, message_level, message)); + } +} + +// static +void MixedContentChecker::Count(Frame* frame, + WebURLRequest::RequestContext request_context, + const LocalFrame* source) { + UseCounter::Count(source, WebFeature::kMixedContentPresent); + + // Roll blockable content up into a single counter, count unblocked types + // individually so we can determine when they can be safely moved to the + // blockable category: + WebMixedContentContextType context_type = + WebMixedContent::ContextTypeFromRequestContext( + request_context, + frame->GetSettings()->GetStrictMixedContentCheckingForPlugin()); + if (context_type == WebMixedContentContextType::kBlockable) { + UseCounter::Count(source, WebFeature::kMixedContentBlockable); + return; + } + + WebFeature feature; + switch (request_context) { + case WebURLRequest::kRequestContextAudio: + feature = WebFeature::kMixedContentAudio; + break; + case WebURLRequest::kRequestContextDownload: + feature = WebFeature::kMixedContentDownload; + break; + case WebURLRequest::kRequestContextFavicon: + feature = WebFeature::kMixedContentFavicon; + break; + case WebURLRequest::kRequestContextImage: + feature = WebFeature::kMixedContentImage; + break; + case WebURLRequest::kRequestContextInternal: + feature = WebFeature::kMixedContentInternal; + break; + case WebURLRequest::kRequestContextPlugin: + feature = WebFeature::kMixedContentPlugin; + break; + case WebURLRequest::kRequestContextPrefetch: + feature = WebFeature::kMixedContentPrefetch; + break; + case WebURLRequest::kRequestContextVideo: + feature = WebFeature::kMixedContentVideo; + break; + + default: + NOTREACHED(); + return; + } + UseCounter::Count(source, feature); +} + +// static +bool MixedContentChecker::ShouldBlockFetch( + LocalFrame* frame, + WebURLRequest::RequestContext request_context, + network::mojom::RequestContextFrameType frame_type, + ResourceRequest::RedirectStatus redirect_status, + const KURL& url, + SecurityViolationReportingPolicy reporting_policy) { + // Frame-level loads are checked by the browser if PlzNavigate is enabled. No + // need to check them again here. + if (frame->GetSettings()->GetBrowserSideNavigationEnabled() && + frame_type != network::mojom::RequestContextFrameType::kNone) { + return false; + } + + Frame* effective_frame = EffectiveFrameForFrameType(frame, frame_type); + Frame* mixed_frame = + InWhichFrameIsContentMixed(effective_frame, frame_type, url, frame); + if (!mixed_frame) + return false; + + MixedContentChecker::Count(mixed_frame, request_context, frame); + if (ContentSecurityPolicy* policy = + frame->GetSecurityContext()->GetContentSecurityPolicy()) + policy->ReportMixedContent(url, redirect_status); + + Settings* settings = mixed_frame->GetSettings(); + // Use the current local frame's client; the embedder doesn't distinguish + // mixed content signals from different frames on the same page. + LocalFrameClient* client = frame->Client(); + ContentSettingsClient* content_settings_client = + frame->GetContentSettingsClient(); + const SecurityOrigin* security_origin = + mixed_frame->GetSecurityContext()->GetSecurityOrigin(); + bool allowed = false; + + // If we're in strict mode, we'll automagically fail everything, and + // intentionally skip the client checks in order to prevent degrading the + // site's security UI. + bool strict_mode = + mixed_frame->GetSecurityContext()->GetInsecureRequestPolicy() & + kBlockAllMixedContent || + settings->GetStrictMixedContentChecking(); + + WebMixedContentContextType context_type = + WebMixedContent::ContextTypeFromRequestContext( + request_context, settings->GetStrictMixedContentCheckingForPlugin()); + + // If we're loading the main resource of a subframe, we need to take a close + // look at the loaded URL. If we're dealing with a CORS-enabled scheme, then + // block mixed frames as active content. Otherwise, treat frames as passive + // content. + // + // FIXME: Remove this temporary hack once we have a reasonable API for + // launching external applications via URLs. http://crbug.com/318788 and + // https://crbug.com/393481 + if (frame_type == network::mojom::RequestContextFrameType::kNested && + !SchemeRegistry::ShouldTreatURLSchemeAsCORSEnabled(url.Protocol())) + context_type = WebMixedContentContextType::kOptionallyBlockable; + + switch (context_type) { + case WebMixedContentContextType::kOptionallyBlockable: + allowed = !strict_mode; + if (allowed) { + content_settings_client->PassiveInsecureContentFound(url); + client->DidDisplayInsecureContent(); + } + break; + + case WebMixedContentContextType::kBlockable: { + // Strictly block subresources that are mixed with respect to their + // subframes, unless all insecure content is allowed. This is to avoid the + // following situation: https://a.com embeds https://b.com, which loads a + // script over insecure HTTP. The user opts to allow the insecure content, + // thinking that they are allowing an insecure script to run on + // https://a.com and not realizing that they are in fact allowing an + // insecure script on https://b.com. + if (!settings->GetAllowRunningOfInsecureContent() && + RequestIsSubframeSubresource(effective_frame, frame_type) && + IsMixedContent(frame->GetSecurityContext()->GetSecurityOrigin(), + url)) { + UseCounter::Count(frame, + WebFeature::kBlockableMixedContentInSubframeBlocked); + allowed = false; + break; + } + + bool should_ask_embedder = + !strict_mode && settings && + (!settings->GetStrictlyBlockBlockableMixedContent() || + settings->GetAllowRunningOfInsecureContent()); + allowed = should_ask_embedder && + content_settings_client->AllowRunningInsecureContent( + settings && settings->GetAllowRunningOfInsecureContent(), + security_origin, url); + if (allowed) { + client->DidRunInsecureContent(security_origin, url); + UseCounter::Count(frame, WebFeature::kMixedContentBlockableAllowed); + } + break; + } + + case WebMixedContentContextType::kShouldBeBlockable: + allowed = !strict_mode; + if (allowed) + client->DidDisplayInsecureContent(); + break; + case WebMixedContentContextType::kNotMixedContent: + NOTREACHED(); + break; + }; + + if (reporting_policy == SecurityViolationReportingPolicy::kReport) { + LogToConsoleAboutFetch(frame->GetDocument(), + MainResourceUrlForFrame(mixed_frame), url, + request_context, allowed, nullptr); + } + return !allowed; +} + +// static +bool MixedContentChecker::ShouldBlockFetchOnWorker( + WorkerOrWorkletGlobalScope* global_scope, + WebWorkerFetchContext* worker_fetch_context, + WebURLRequest::RequestContext request_context, + network::mojom::RequestContextFrameType frame_type, + ResourceRequest::RedirectStatus redirect_status, + const KURL& url, + SecurityViolationReportingPolicy reporting_policy) { + if (!MixedContentChecker::IsMixedContent(global_scope->GetSecurityOrigin(), + url)) { + return false; + } + + UseCounter::Count(global_scope, WebFeature::kMixedContentPresent); + UseCounter::Count(global_scope, WebFeature::kMixedContentBlockable); + if (ContentSecurityPolicy* policy = global_scope->GetContentSecurityPolicy()) + policy->ReportMixedContent(url, redirect_status); + + // Blocks all mixed content request from worklets. + // TODO(horo): Revise this when the spec is updated. + // Worklets spec: https://www.w3.org/TR/worklets-1/#security-considerations + // Spec issue: https://github.com/w3c/css-houdini-drafts/issues/92 + if (!global_scope->IsWorkerGlobalScope()) + return true; + + WorkerGlobalScope* worker_global_scope = ToWorkerGlobalScope(global_scope); + WorkerSettings* settings = worker_global_scope->GetWorkerSettings(); + DCHECK(settings); + bool allowed = false; + if (!settings->GetAllowRunningOfInsecureContent() && + worker_fetch_context->IsOnSubframe()) { + UseCounter::Count(global_scope, + WebFeature::kBlockableMixedContentInSubframeBlocked); + allowed = false; + } else { + bool strict_mode = worker_global_scope->GetInsecureRequestPolicy() & + kBlockAllMixedContent || + settings->GetStrictMixedContentChecking(); + bool should_ask_embedder = + !strict_mode && (!settings->GetStrictlyBlockBlockableMixedContent() || + settings->GetAllowRunningOfInsecureContent()); + allowed = should_ask_embedder && + WorkerContentSettingsClient::From(*global_scope) + ->AllowRunningInsecureContent( + settings->GetAllowRunningOfInsecureContent(), + global_scope->GetSecurityOrigin(), url); + if (allowed) { + worker_fetch_context->DidRunInsecureContent( + WebSecurityOrigin(global_scope->GetSecurityOrigin()), url); + UseCounter::Count(global_scope, + WebFeature::kMixedContentBlockableAllowed); + } + } + + if (reporting_policy == SecurityViolationReportingPolicy::kReport) { + LogToConsoleAboutFetch(global_scope, global_scope->Url(), url, + request_context, allowed, nullptr); + } + return !allowed; +} + +// static +void MixedContentChecker::LogToConsoleAboutWebSocket( + ExecutionContext* execution_context, + const KURL& main_resource_url, + const KURL& url, + bool allowed) { + String message = String::Format( + "Mixed Content: The page at '%s' was loaded over HTTPS, but attempted to " + "connect to the insecure WebSocket endpoint '%s'. %s", + main_resource_url.ElidedString().Utf8().data(), + url.ElidedString().Utf8().data(), + allowed ? "This endpoint should be available via WSS. Insecure access is " + "deprecated." + : "This request has been blocked; this endpoint must be " + "available over WSS."); + MessageLevel message_level = + allowed ? kWarningMessageLevel : kErrorMessageLevel; + execution_context->AddConsoleMessage( + ConsoleMessage::Create(kSecurityMessageSource, message_level, message)); +} + +// static +bool MixedContentChecker::IsWebSocketAllowed(LocalFrame* frame, + const KURL& url) { + Frame* mixed_frame = InWhichFrameIsContentMixed( + frame, network::mojom::RequestContextFrameType::kNone, url, frame); + if (!mixed_frame) + return true; + + Settings* settings = mixed_frame->GetSettings(); + // Use the current local frame's client; the embedder doesn't distinguish + // mixed content signals from different frames on the same page. + ContentSettingsClient* content_settings_client = + frame->GetContentSettingsClient(); + SecurityContext* security_context = mixed_frame->GetSecurityContext(); + const SecurityOrigin* security_origin = security_context->GetSecurityOrigin(); + + bool allowed = IsWebSocketAllowedImpl(frame->GetDocument(), security_context, + security_origin, settings, + content_settings_client, url); + if (allowed) + frame->Client()->DidRunInsecureContent(security_origin, url); + + LogToConsoleAboutWebSocket( + frame->GetDocument(), MainResourceUrlForFrame(mixed_frame), url, allowed); + + return allowed; +} + +// static +bool MixedContentChecker::IsWebSocketAllowed( + WorkerGlobalScope* global_scope, + WebWorkerFetchContext* worker_fetch_context, + const KURL& url) { + if (!MixedContentChecker::IsMixedContent(global_scope->GetSecurityOrigin(), + url)) { + return true; + } + + WorkerSettings* settings = global_scope->GetWorkerSettings(); + WorkerContentSettingsClient* content_settings_client = + WorkerContentSettingsClient::From(*global_scope); + SecurityContext* security_context = &global_scope->GetSecurityContext(); + const SecurityOrigin* security_origin = global_scope->GetSecurityOrigin(); + + bool allowed = + IsWebSocketAllowedImpl(global_scope, security_context, security_origin, + settings, content_settings_client, url); + if (allowed) { + worker_fetch_context->DidRunInsecureContent( + WebSecurityOrigin(security_origin), url); + } + + LogToConsoleAboutWebSocket(global_scope, global_scope->Url(), url, allowed); + + return allowed; +} + +bool MixedContentChecker::IsMixedFormAction( + LocalFrame* frame, + const KURL& url, + SecurityViolationReportingPolicy reporting_policy) { + // For whatever reason, some folks handle forms via JavaScript, and submit to + // `javascript:void(0)` rather than calling `preventDefault()`. We + // special-case `javascript:` URLs here, as they don't introduce MixedContent + // for form submissions. + if (url.ProtocolIs("javascript")) + return false; + + Frame* mixed_frame = InWhichFrameIsContentMixed( + frame, network::mojom::RequestContextFrameType::kNone, url, frame); + if (!mixed_frame) + return false; + + UseCounter::Count(frame, WebFeature::kMixedContentPresent); + + // Use the current local frame's client; the embedder doesn't distinguish + // mixed content signals from different frames on the same page. + frame->Client()->DidContainInsecureFormAction(); + + if (reporting_policy == SecurityViolationReportingPolicy::kReport) { + String message = String::Format( + "Mixed Content: The page at '%s' was loaded over a secure connection, " + "but contains a form that targets an insecure endpoint '%s'. This " + "endpoint should be made available over a secure connection.", + MainResourceUrlForFrame(mixed_frame).ElidedString().Utf8().data(), + url.ElidedString().Utf8().data()); + frame->GetDocument()->AddConsoleMessage(ConsoleMessage::Create( + kSecurityMessageSource, kWarningMessageLevel, message)); + } + + return true; +} + +void MixedContentChecker::CheckMixedPrivatePublic( + LocalFrame* frame, + const AtomicString& resource_ip_address) { + if (!frame || !frame->GetDocument() || !frame->GetDocument()->Loader()) + return; + + // Just count these for the moment, don't block them. + if (NetworkUtils::IsReservedIPAddress(resource_ip_address) && + frame->GetDocument()->AddressSpace() == mojom::IPAddressSpace::kPublic) { + UseCounter::Count(frame->GetDocument(), + WebFeature::kMixedContentPrivateHostnameInPublicHostname); + // We can simplify the IP checks here, as we've already verified that + // |resourceIPAddress| is a reserved IP address, which means it's also a + // valid IP address in a normalized form. + if (resource_ip_address.StartsWith("127.0.0.") || + resource_ip_address == "[::1]") { + UseCounter::Count(frame->GetDocument(), + frame->GetDocument()->IsSecureContext() + ? WebFeature::kLoopbackEmbeddedInSecureContext + : WebFeature::kLoopbackEmbeddedInNonSecureContext); + } + } +} + +Frame* MixedContentChecker::EffectiveFrameForFrameType( + LocalFrame* frame, + network::mojom::RequestContextFrameType frame_type) { + // If we're loading the main resource of a subframe, ensure that we check + // against the parent of the active frame, rather than the frame itself. + if (frame_type != network::mojom::RequestContextFrameType::kNested) + return frame; + + Frame* parent_frame = frame->Tree().Parent(); + DCHECK(parent_frame); + return parent_frame; +} + +void MixedContentChecker::HandleCertificateError( + LocalFrame* frame, + const ResourceResponse& response, + network::mojom::RequestContextFrameType frame_type, + WebURLRequest::RequestContext request_context) { + Frame* effective_frame = EffectiveFrameForFrameType(frame, frame_type); + if (frame_type == network::mojom::RequestContextFrameType::kTopLevel || + !effective_frame) + return; + + // Use the current local frame's client; the embedder doesn't distinguish + // mixed content signals from different frames on the same page. + LocalFrameClient* client = frame->Client(); + bool strict_mixed_content_checking_for_plugin = + effective_frame->GetSettings() && + effective_frame->GetSettings()->GetStrictMixedContentCheckingForPlugin(); + WebMixedContentContextType context_type = + WebMixedContent::ContextTypeFromRequestContext( + request_context, strict_mixed_content_checking_for_plugin); + if (context_type == WebMixedContentContextType::kBlockable) { + client->DidRunContentWithCertificateErrors(); + } else { + // contextTypeFromRequestContext() never returns NotMixedContent (it + // computes the type of mixed content, given that the content is mixed). + DCHECK_NE(context_type, WebMixedContentContextType::kNotMixedContent); + client->DidDisplayContentWithCertificateErrors(); + } +} + +// static +void MixedContentChecker::MixedContentFound( + LocalFrame* frame, + const KURL& main_resource_url, + const KURL& mixed_content_url, + WebURLRequest::RequestContext request_context, + bool was_allowed, + bool had_redirect, + std::unique_ptr<SourceLocation> source_location) { + // Logs to the frame console. + LogToConsoleAboutFetch(frame->GetDocument(), main_resource_url, + mixed_content_url, request_context, was_allowed, + std::move(source_location)); + // Reports to the CSP policy. + ContentSecurityPolicy* policy = + frame->GetSecurityContext()->GetContentSecurityPolicy(); + if (policy) { + policy->ReportMixedContent( + mixed_content_url, + had_redirect ? ResourceRequest::RedirectStatus::kFollowedRedirect + : ResourceRequest::RedirectStatus::kNoRedirect); + } +} + +WebMixedContentContextType MixedContentChecker::ContextTypeForInspector( + LocalFrame* frame, + const ResourceRequest& request) { + Frame* effective_frame = + EffectiveFrameForFrameType(frame, request.GetFrameType()); + + Frame* mixed_frame = InWhichFrameIsContentMixed( + effective_frame, request.GetFrameType(), request.Url(), frame); + if (!mixed_frame) + return WebMixedContentContextType::kNotMixedContent; + + // See comment in ShouldBlockFetch() about loading the main resource of a + // subframe. + if (request.GetFrameType() == + network::mojom::RequestContextFrameType::kNested && + !SchemeRegistry::ShouldTreatURLSchemeAsCORSEnabled( + request.Url().Protocol())) { + return WebMixedContentContextType::kOptionallyBlockable; + } + + bool strict_mixed_content_checking_for_plugin = + mixed_frame->GetSettings() && + mixed_frame->GetSettings()->GetStrictMixedContentCheckingForPlugin(); + return WebMixedContent::ContextTypeFromRequestContext( + request.GetRequestContext(), strict_mixed_content_checking_for_plugin); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/loader/mixed_content_checker.h b/chromium/third_party/blink/renderer/core/loader/mixed_content_checker.h new file mode 100644 index 00000000000..879fa2fec89 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/mixed_content_checker.h @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2012 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. + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_MIXED_CONTENT_CHECKER_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_MIXED_CONTENT_CHECKER_H_ + +#include "base/gtest_prod_util.h" +#include "base/macros.h" +#include "third_party/blink/public/platform/web_mixed_content_context_type.h" +#include "third_party/blink/public/platform/web_url_request.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_request.h" +#include "third_party/blink/renderer/platform/weborigin/security_violation_reporting_policy.h" +#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h" + +namespace blink { + +class ExecutionContext; +class Frame; +class LocalFrame; +class KURL; +class ResourceResponse; +class SecurityOrigin; +class SourceLocation; +class WorkerGlobalScope; +class WorkerOrWorkletGlobalScope; +class WebWorkerFetchContext; + +// Checks resource loads for mixed content. If PlzNavigate is enabled then this +// class only checks for sub-resource loads while frame-level loads are +// delegated to the browser where they are checked by +// MixedContentNavigationThrottle. Changes to this class might need to be +// reflected on its browser counterpart. +// +// Current mixed content W3C draft that drives this implementation: +// https://w3c.github.io/webappsec-mixed-content/ +class CORE_EXPORT MixedContentChecker final { + DISALLOW_NEW(); + + public: + static bool ShouldBlockFetch(LocalFrame*, + WebURLRequest::RequestContext, + network::mojom::RequestContextFrameType, + ResourceRequest::RedirectStatus, + const KURL&, + SecurityViolationReportingPolicy = + SecurityViolationReportingPolicy::kReport); + + static bool ShouldBlockFetchOnWorker(WorkerOrWorkletGlobalScope*, + WebWorkerFetchContext*, + WebURLRequest::RequestContext, + network::mojom::RequestContextFrameType, + ResourceRequest::RedirectStatus, + const KURL&, + SecurityViolationReportingPolicy); + + static bool IsWebSocketAllowed(LocalFrame*, const KURL&); + static bool IsWebSocketAllowed(WorkerGlobalScope*, + WebWorkerFetchContext*, + const KURL&); + + static bool IsMixedContent(const SecurityOrigin*, const KURL&); + static bool IsMixedFormAction(LocalFrame*, + const KURL&, + SecurityViolationReportingPolicy = + SecurityViolationReportingPolicy::kReport); + + static void CheckMixedPrivatePublic(LocalFrame*, + const AtomicString& resource_ip_address); + + static WebMixedContentContextType ContextTypeForInspector( + LocalFrame*, + const ResourceRequest&); + + // Returns the frame that should be considered the effective frame + // for a mixed content check for the given frame type. + static Frame* EffectiveFrameForFrameType( + LocalFrame*, + network::mojom::RequestContextFrameType); + + static void HandleCertificateError(LocalFrame*, + const ResourceResponse&, + network::mojom::RequestContextFrameType, + WebURLRequest::RequestContext); + + // Receive information about mixed content found externally. + static void MixedContentFound(LocalFrame*, + const KURL& main_resource_url, + const KURL& mixed_content_url, + WebURLRequest::RequestContext, + bool was_allowed, + bool had_redirect, + std::unique_ptr<SourceLocation>); + + private: + FRIEND_TEST_ALL_PREFIXES(MixedContentCheckerTest, HandleCertificateError); + + static Frame* InWhichFrameIsContentMixed( + Frame*, + network::mojom::RequestContextFrameType, + const KURL&, + const LocalFrame*); + + static void LogToConsoleAboutFetch(ExecutionContext*, + const KURL&, + const KURL&, + WebURLRequest::RequestContext, + bool allowed, + std::unique_ptr<SourceLocation>); + static void LogToConsoleAboutWebSocket(ExecutionContext*, + const KURL&, + const KURL&, + bool allowed); + static void Count(Frame*, WebURLRequest::RequestContext, const LocalFrame*); + + DISALLOW_COPY_AND_ASSIGN(MixedContentChecker); +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_MIXED_CONTENT_CHECKER_H_ diff --git a/chromium/third_party/blink/renderer/core/loader/mixed_content_checker_test.cc b/chromium/third_party/blink/renderer/core/loader/mixed_content_checker_test.cc new file mode 100644 index 00000000000..270f7cf6e62 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/mixed_content_checker_test.cc @@ -0,0 +1,220 @@ +// 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/mixed_content_checker.h" + +#include <base/macros.h> +#include <memory> +#include "base/memory/scoped_refptr.h" +#include "services/network/public/mojom/request_context_frame_type.mojom-blink.h" +#include "testing/gmock/include/gmock/gmock-generated-function-mockers.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/blink/public/platform/web_mixed_content.h" +#include "third_party/blink/public/platform/web_mixed_content_context_type.h" +#include "third_party/blink/renderer/core/frame/settings.h" +#include "third_party/blink/renderer/core/loader/empty_clients.h" +#include "third_party/blink/renderer/core/testing/dummy_page_holder.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource_response.h" +#include "third_party/blink/renderer/platform/weborigin/kurl.h" +#include "third_party/blink/renderer/platform/weborigin/security_origin.h" + +namespace blink { + +// Tests that MixedContentChecker::isMixedContent correctly detects or ignores +// many cases where there is or there is not mixed content, respectively. +// Note: Renderer side version of +// MixedContentNavigationThrottleTest.IsMixedContent. Must be kept in sync +// manually! +TEST(MixedContentCheckerTest, IsMixedContent) { + struct TestCase { + const char* origin; + const char* target; + bool expectation; + } cases[] = { + {"http://example.com/foo", "http://example.com/foo", false}, + {"http://example.com/foo", "https://example.com/foo", false}, + {"http://example.com/foo", "data:text/html,<p>Hi!</p>", false}, + {"http://example.com/foo", "about:blank", false}, + {"https://example.com/foo", "https://example.com/foo", false}, + {"https://example.com/foo", "wss://example.com/foo", false}, + {"https://example.com/foo", "data:text/html,<p>Hi!</p>", false}, + {"https://example.com/foo", "http://127.0.0.1/", false}, + {"https://example.com/foo", "http://[::1]/", false}, + {"https://example.com/foo", "blob:https://example.com/foo", false}, + {"https://example.com/foo", "blob:http://example.com/foo", false}, + {"https://example.com/foo", "blob:null/foo", false}, + {"https://example.com/foo", "filesystem:https://example.com/foo", false}, + {"https://example.com/foo", "filesystem:http://example.com/foo", false}, + {"https://example.com/foo", "http://localhost/", false}, + {"https://example.com/foo", "http://a.localhost/", false}, + + {"https://example.com/foo", "http://example.com/foo", true}, + {"https://example.com/foo", "http://google.com/foo", true}, + {"https://example.com/foo", "ws://example.com/foo", true}, + {"https://example.com/foo", "ws://google.com/foo", true}, + {"https://example.com/foo", "http://192.168.1.1/", true}, + }; + + for (const auto& test : cases) { + SCOPED_TRACE(testing::Message() + << "Origin: " << test.origin << ", Target: " << test.target + << ", Expectation: " << test.expectation); + KURL origin_url(NullURL(), test.origin); + scoped_refptr<const SecurityOrigin> security_origin( + SecurityOrigin::Create(origin_url)); + KURL target_url(NullURL(), test.target); + EXPECT_EQ(test.expectation, MixedContentChecker::IsMixedContent( + security_origin.get(), target_url)); + } +} + +TEST(MixedContentCheckerTest, ContextTypeForInspector) { + std::unique_ptr<DummyPageHolder> dummy_page_holder = + DummyPageHolder::Create(IntSize(1, 1)); + dummy_page_holder->GetFrame().GetDocument()->SetSecurityOrigin( + SecurityOrigin::CreateFromString("http://example.test")); + + ResourceRequest not_mixed_content("https://example.test/foo.jpg"); + not_mixed_content.SetFrameType( + network::mojom::RequestContextFrameType::kAuxiliary); + not_mixed_content.SetRequestContext(WebURLRequest::kRequestContextScript); + EXPECT_EQ(WebMixedContentContextType::kNotMixedContent, + MixedContentChecker::ContextTypeForInspector( + &dummy_page_holder->GetFrame(), not_mixed_content)); + + dummy_page_holder->GetFrame().GetDocument()->SetSecurityOrigin( + SecurityOrigin::CreateFromString("https://example.test")); + EXPECT_EQ(WebMixedContentContextType::kNotMixedContent, + MixedContentChecker::ContextTypeForInspector( + &dummy_page_holder->GetFrame(), not_mixed_content)); + + ResourceRequest blockable_mixed_content("http://example.test/foo.jpg"); + blockable_mixed_content.SetFrameType( + network::mojom::RequestContextFrameType::kAuxiliary); + blockable_mixed_content.SetRequestContext( + WebURLRequest::kRequestContextScript); + EXPECT_EQ(WebMixedContentContextType::kBlockable, + MixedContentChecker::ContextTypeForInspector( + &dummy_page_holder->GetFrame(), blockable_mixed_content)); + + ResourceRequest optionally_blockable_mixed_content( + "http://example.test/foo.jpg"); + blockable_mixed_content.SetFrameType( + network::mojom::RequestContextFrameType::kAuxiliary); + blockable_mixed_content.SetRequestContext( + WebURLRequest::kRequestContextImage); + EXPECT_EQ(WebMixedContentContextType::kOptionallyBlockable, + MixedContentChecker::ContextTypeForInspector( + &dummy_page_holder->GetFrame(), blockable_mixed_content)); +} + +namespace { + +class MixedContentCheckerMockLocalFrameClient : public EmptyLocalFrameClient { + public: + MixedContentCheckerMockLocalFrameClient() : EmptyLocalFrameClient() {} + MOCK_METHOD0(DidContainInsecureFormAction, void()); + MOCK_METHOD0(DidDisplayContentWithCertificateErrors, void()); + MOCK_METHOD0(DidRunContentWithCertificateErrors, void()); +}; + +} // namespace + +TEST(MixedContentCheckerTest, HandleCertificateError) { + MixedContentCheckerMockLocalFrameClient* client = + new MixedContentCheckerMockLocalFrameClient; + std::unique_ptr<DummyPageHolder> dummy_page_holder = + DummyPageHolder::Create(IntSize(1, 1), nullptr, client); + + KURL main_resource_url(NullURL(), "https://example.test"); + KURL displayed_url(NullURL(), "https://example-displayed.test"); + KURL ran_url(NullURL(), "https://example-ran.test"); + + dummy_page_holder->GetFrame().GetDocument()->SetURL(main_resource_url); + ResourceResponse response1(ran_url); + EXPECT_CALL(*client, DidRunContentWithCertificateErrors()); + MixedContentChecker::HandleCertificateError( + &dummy_page_holder->GetFrame(), response1, + network::mojom::RequestContextFrameType::kNone, + WebURLRequest::kRequestContextScript); + + ResourceResponse response2(displayed_url); + WebURLRequest::RequestContext request_context = + WebURLRequest::kRequestContextImage; + ASSERT_EQ( + WebMixedContentContextType::kOptionallyBlockable, + WebMixedContent::ContextTypeFromRequestContext( + request_context, dummy_page_holder->GetFrame() + .GetSettings() + ->GetStrictMixedContentCheckingForPlugin())); + EXPECT_CALL(*client, DidDisplayContentWithCertificateErrors()); + MixedContentChecker::HandleCertificateError( + &dummy_page_holder->GetFrame(), response2, + network::mojom::RequestContextFrameType::kNone, request_context); +} + +TEST(MixedContentCheckerTest, DetectMixedForm) { + MixedContentCheckerMockLocalFrameClient* client = + new MixedContentCheckerMockLocalFrameClient; + std::unique_ptr<DummyPageHolder> dummy_page_holder = + DummyPageHolder::Create(IntSize(1, 1), nullptr, client); + + KURL main_resource_url(NullURL(), "https://example.test/"); + + KURL http_form_action_url(NullURL(), "http://example-action.test/"); + KURL https_form_action_url(NullURL(), "https://example-action.test/"); + KURL javascript_form_action_url(NullURL(), "javascript:void(0);"); + KURL mailto_form_action_url(NullURL(), "mailto:action@example-action.test"); + + dummy_page_holder->GetFrame().GetDocument()->SetSecurityOrigin( + SecurityOrigin::Create(main_resource_url)); + + // mailto and http are non-secure form targets. + EXPECT_CALL(*client, DidContainInsecureFormAction()).Times(2); + + EXPECT_TRUE(MixedContentChecker::IsMixedFormAction( + &dummy_page_holder->GetFrame(), http_form_action_url, + SecurityViolationReportingPolicy::kSuppressReporting)); + EXPECT_FALSE(MixedContentChecker::IsMixedFormAction( + &dummy_page_holder->GetFrame(), https_form_action_url, + SecurityViolationReportingPolicy::kSuppressReporting)); + EXPECT_FALSE(MixedContentChecker::IsMixedFormAction( + &dummy_page_holder->GetFrame(), javascript_form_action_url, + SecurityViolationReportingPolicy::kSuppressReporting)); + EXPECT_TRUE(MixedContentChecker::IsMixedFormAction( + &dummy_page_holder->GetFrame(), mailto_form_action_url, + SecurityViolationReportingPolicy::kSuppressReporting)); +} + +TEST(MixedContentCheckerTest, DetectMixedFavicon) { + MixedContentCheckerMockLocalFrameClient* client = + new MixedContentCheckerMockLocalFrameClient; + std::unique_ptr<DummyPageHolder> dummy_page_holder = + DummyPageHolder::Create(IntSize(1, 1), nullptr, client); + dummy_page_holder->GetFrame().GetSettings()->SetAllowRunningOfInsecureContent( + false); + + KURL main_resource_url("https://example.test/"); + KURL http_favicon_url("http://example.test/favicon.png"); + KURL https_favicon_url("https://example.test/favicon.png"); + + dummy_page_holder->GetFrame().GetDocument()->SetSecurityOrigin( + SecurityOrigin::Create(main_resource_url)); + + // Test that a mixed content favicon is correctly blocked. + EXPECT_TRUE(MixedContentChecker::ShouldBlockFetch( + &dummy_page_holder->GetFrame(), WebURLRequest::kRequestContextFavicon, + network::mojom::RequestContextFrameType::kNone, + ResourceRequest::RedirectStatus::kNoRedirect, http_favicon_url, + SecurityViolationReportingPolicy::kSuppressReporting)); + + // Test that a secure favicon is not blocked. + EXPECT_FALSE(MixedContentChecker::ShouldBlockFetch( + &dummy_page_holder->GetFrame(), WebURLRequest::kRequestContextFavicon, + network::mojom::RequestContextFrameType::kNone, + ResourceRequest::RedirectStatus::kNoRedirect, https_favicon_url, + SecurityViolationReportingPolicy::kSuppressReporting)); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/loader/modulescript/document_module_script_fetcher.cc b/chromium/third_party/blink/renderer/core/loader/modulescript/document_module_script_fetcher.cc new file mode 100644 index 00000000000..6959f90d4c9 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/modulescript/document_module_script_fetcher.cc @@ -0,0 +1,112 @@ +// Copyright 2017 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/modulescript/document_module_script_fetcher.h" + +#include "third_party/blink/renderer/core/execution_context/execution_context.h" +#include "third_party/blink/renderer/core/inspector/console_message.h" +#include "third_party/blink/renderer/core/loader/subresource_integrity_helper.h" +#include "third_party/blink/renderer/platform/loader/fetch/fetch_utils.h" +#include "third_party/blink/renderer/platform/network/mime/mime_type_registry.h" + +namespace blink { + +namespace { + +bool WasModuleLoadSuccessful( + Resource* resource, + HeapVector<Member<ConsoleMessage>>* error_messages) { + // Implements conditions in Step 7 of + // https://html.spec.whatwg.org/#fetch-a-single-module-script + + DCHECK(error_messages); + + if (resource) { + SubresourceIntegrityHelper::GetConsoleMessages( + resource->IntegrityReportInfo(), error_messages); + } + + // - response's type is "error" + if (!resource || resource->ErrorOccurred() || + resource->IntegrityDisposition() != + ResourceIntegrityDisposition::kPassed) { + return false; + } + + const auto& response = resource->GetResponse(); + // - response's status is not an ok status + if (response.IsHTTP() && !FetchUtils::IsOkStatus(response.HttpStatusCode())) { + return false; + } + + // The result of extracting a MIME type from response's header list + // (ignoring parameters) is not a JavaScript MIME type + // Note: For historical reasons, fetching a classic script does not include + // MIME type checking. In contrast, module scripts will fail to load if they + // are not of a correct MIME type. + // We use ResourceResponse::HttpContentType() instead of MimeType(), as + // MimeType() may be rewritten by mime sniffer. + if (!MIMETypeRegistry::IsSupportedJavaScriptMIMEType( + response.HttpContentType())) { + String message = + "Failed to load module script: The server responded with a " + "non-JavaScript MIME type of \"" + + response.HttpContentType() + + "\". Strict MIME type checking is enforced for module scripts per " + "HTML spec."; + error_messages->push_back(ConsoleMessage::CreateForRequest( + kJSMessageSource, kErrorMessageLevel, message, + response.Url().GetString(), nullptr, resource->Identifier())); + return false; + } + + return true; +} + +} // namespace + +DocumentModuleScriptFetcher::DocumentModuleScriptFetcher( + ResourceFetcher* fetcher) + : fetcher_(fetcher) { + DCHECK(fetcher_); +} + +void DocumentModuleScriptFetcher::Fetch(FetchParameters& fetch_params, + ModuleScriptFetcher::Client* client) { + SetClient(client); + ScriptResource::Fetch(fetch_params, fetcher_, this); +} + +void DocumentModuleScriptFetcher::NotifyFinished(Resource* resource) { + ClearResource(); + + ScriptResource* script_resource = ToScriptResource(resource); + + HeapVector<Member<ConsoleMessage>> error_messages; + if (!WasModuleLoadSuccessful(script_resource, &error_messages)) { + Finalize(WTF::nullopt, error_messages); + return; + } + + ModuleScriptCreationParams params( + script_resource->GetResponse().Url(), script_resource->SourceText(), + script_resource->GetResourceRequest().GetFetchCredentialsMode(), + script_resource->CalculateAccessControlStatus( + fetcher_->Context().GetSecurityOrigin())); + Finalize(params, error_messages); +} + +void DocumentModuleScriptFetcher::Finalize( + const WTF::Optional<ModuleScriptCreationParams>& params, + const HeapVector<Member<ConsoleMessage>>& error_messages) { + NotifyFetchFinished(params, error_messages); +} + +void DocumentModuleScriptFetcher::Trace(blink::Visitor* visitor) { + visitor->Trace(fetcher_); + ResourceClient::Trace(visitor); + ModuleScriptFetcher::Trace(visitor); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/loader/modulescript/document_module_script_fetcher.h b/chromium/third_party/blink/renderer/core/loader/modulescript/document_module_script_fetcher.h new file mode 100644 index 00000000000..2d8d0d5b3ba --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/modulescript/document_module_script_fetcher.h @@ -0,0 +1,52 @@ +// Copyright 2017 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_MODULESCRIPT_DOCUMENT_MODULE_SCRIPT_FETCHER_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_MODULESCRIPT_DOCUMENT_MODULE_SCRIPT_FETCHER_H_ + +#include "third_party/blink/renderer/core/loader/modulescript/module_script_creation_params.h" +#include "third_party/blink/renderer/core/loader/modulescript/module_script_fetcher.h" +#include "third_party/blink/renderer/core/loader/resource/script_resource.h" +#include "third_party/blink/renderer/core/script/modulator.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/weborigin/security_origin.h" +#include "third_party/blink/renderer/platform/wtf/optional.h" + +namespace blink { + +class ConsoleMessage; + +// DocumentModuleScriptFetcher is used to fetch module scripts used in main +// documents (that means, not worker nor worklets). +// +// DocumentModuleScriptFetcher emits FetchParameters to ResourceFetcher +// (via ScriptResource::Fetch). Then, it keeps track of the fetch progress by +// being a ResourceClient. Finally, it returns its client a fetched resource as +// ModuleScriptCreationParams. +class CORE_EXPORT DocumentModuleScriptFetcher : public ModuleScriptFetcher, + public ResourceClient { + USING_GARBAGE_COLLECTED_MIXIN(DocumentModuleScriptFetcher); + + public: + explicit DocumentModuleScriptFetcher(ResourceFetcher*); + + void Fetch(FetchParameters&, ModuleScriptFetcher::Client*) final; + + // Implements ResourceClient + void NotifyFinished(Resource*) final; + String DebugName() const final { return "DocumentModuleScriptFetcher"; } + + void Trace(blink::Visitor*) override; + + private: + void Finalize(const WTF::Optional<ModuleScriptCreationParams>&, + const HeapVector<Member<ConsoleMessage>>& error_messages); + + Member<ResourceFetcher> fetcher_; +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_MODULESCRIPT_DOCUMENT_MODULE_SCRIPT_FETCHER_H_ diff --git a/chromium/third_party/blink/renderer/core/loader/modulescript/module_script_creation_params.h b/chromium/third_party/blink/renderer/core/loader/modulescript/module_script_creation_params.h new file mode 100644 index 00000000000..4d65e5f0628 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/modulescript/module_script_creation_params.h @@ -0,0 +1,61 @@ +// Copyright 2017 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_MODULESCRIPT_MODULE_SCRIPT_CREATION_PARAMS_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_MODULESCRIPT_MODULE_SCRIPT_CREATION_PARAMS_H_ + +#include "third_party/blink/public/platform/web_url_request.h" +#include "third_party/blink/renderer/platform/cross_thread_copier.h" +#include "third_party/blink/renderer/platform/loader/fetch/access_control_status.h" +#include "third_party/blink/renderer/platform/weborigin/kurl.h" +#include "third_party/blink/renderer/platform/wtf/optional.h" +#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h" + +namespace blink { + +// ModuleScriptCreationParams contains parameters for creating ModuleScript. +class ModuleScriptCreationParams { + public: + ModuleScriptCreationParams( + const KURL& response_url, + const String& source_text, + network::mojom::FetchCredentialsMode fetch_credentials_mode, + AccessControlStatus access_control_status) + : response_url_(response_url), + source_text_(source_text), + fetch_credentials_mode_(fetch_credentials_mode), + access_control_status_(access_control_status) {} + ~ModuleScriptCreationParams() = default; + + const KURL& GetResponseUrl() const { return response_url_; }; + const String& GetSourceText() const { return source_text_; } + network::mojom::FetchCredentialsMode GetFetchCredentialsMode() const { + return fetch_credentials_mode_; + } + AccessControlStatus GetAccessControlStatus() const { + return access_control_status_; + } + + private: + const KURL response_url_; + const String source_text_; + const network::mojom::FetchCredentialsMode fetch_credentials_mode_; + const AccessControlStatus access_control_status_; +}; + +// Creates a deep copy because |response_url_| and |source_text_| are not +// cross-thread-transfer-safe. +template <> +struct CrossThreadCopier<ModuleScriptCreationParams> { + static ModuleScriptCreationParams Copy( + const ModuleScriptCreationParams& params) { + return ModuleScriptCreationParams( + params.GetResponseUrl().Copy(), params.GetSourceText().IsolatedCopy(), + params.GetFetchCredentialsMode(), params.GetAccessControlStatus()); + } +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_MODULESCRIPT_MODULE_SCRIPT_CREATION_PARAMS_H_ diff --git a/chromium/third_party/blink/renderer/core/loader/modulescript/module_script_fetch_request.h b/chromium/third_party/blink/renderer/core/loader/modulescript/module_script_fetch_request.h new file mode 100644 index 00000000000..1d707f8fd74 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/modulescript/module_script_fetch_request.h @@ -0,0 +1,61 @@ +// Copyright 2017 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_MODULESCRIPT_MODULE_SCRIPT_FETCH_REQUEST_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_MODULESCRIPT_MODULE_SCRIPT_FETCH_REQUEST_H_ + +#include "third_party/blink/renderer/platform/loader/fetch/script_fetch_options.h" +#include "third_party/blink/renderer/platform/weborigin/kurl.h" +#include "third_party/blink/renderer/platform/weborigin/referrer.h" +#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h" + +namespace blink { + +// A ModuleScriptFetchRequest essentially serves as a "parameter object" for +// Modulator::Fetch{Tree,Single,NewSingle}. +class ModuleScriptFetchRequest final { + STACK_ALLOCATED(); + + public: + ModuleScriptFetchRequest(const KURL& url, + ReferrerPolicy referrer_policy, + const ScriptFetchOptions& options) + : ModuleScriptFetchRequest(url, + options, + Referrer::NoReferrer(), + referrer_policy, + TextPosition::MinimumPosition()) {} + ~ModuleScriptFetchRequest() = default; + + const KURL& Url() const { return url_; } + const ScriptFetchOptions& Options() const { return options_; } + const AtomicString& GetReferrer() const { return referrer_; } + ReferrerPolicy GetReferrerPolicy() const { return referrer_policy_; } + const TextPosition& GetReferrerPosition() const { return referrer_position_; } + + private: + // Referrer is set only for internal module script fetch algorithms triggered + // from ModuleTreeLinker to fetch descendant module scripts. + friend class ModuleTreeLinker; + ModuleScriptFetchRequest(const KURL& url, + const ScriptFetchOptions& options, + const String& referrer, + ReferrerPolicy referrer_policy, + const TextPosition& referrer_position) + : url_(url), + options_(options), + referrer_(referrer), + referrer_policy_(referrer_policy), + referrer_position_(referrer_position) {} + + const KURL url_; + const ScriptFetchOptions options_; + const AtomicString referrer_; + const ReferrerPolicy referrer_policy_; + const TextPosition referrer_position_; +}; + +} // namespace blink + +#endif diff --git a/chromium/third_party/blink/renderer/core/loader/modulescript/module_script_fetcher.cc b/chromium/third_party/blink/renderer/core/loader/modulescript/module_script_fetcher.cc new file mode 100644 index 00000000000..f5fddf105a7 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/modulescript/module_script_fetcher.cc @@ -0,0 +1,24 @@ +// Copyright 2017 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/modulescript/module_script_fetcher.h" + +namespace blink { + +void ModuleScriptFetcher::Trace(blink::Visitor* visitor) { + visitor->Trace(client_); +} + +void ModuleScriptFetcher::NotifyFetchFinished( + const WTF::Optional<ModuleScriptCreationParams>& params, + const HeapVector<Member<ConsoleMessage>>& error_messages) { + client_->NotifyFetchFinished(params, error_messages); +} + +void ModuleScriptFetcher::SetClient(Client* client) { + DCHECK(!client_); + client_ = client; +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/loader/modulescript/module_script_fetcher.h b/chromium/third_party/blink/renderer/core/loader/modulescript/module_script_fetcher.h new file mode 100644 index 00000000000..8280fa98be9 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/modulescript/module_script_fetcher.h @@ -0,0 +1,52 @@ +// Copyright 2017 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_MODULESCRIPT_MODULE_SCRIPT_FETCHER_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_MODULESCRIPT_MODULE_SCRIPT_FETCHER_H_ + +#include "third_party/blink/renderer/core/core_export.h" +#include "third_party/blink/renderer/core/loader/modulescript/module_script_creation_params.h" +#include "third_party/blink/renderer/platform/heap/heap_allocator.h" +#include "third_party/blink/renderer/platform/loader/fetch/fetch_parameters.h" +#include "third_party/blink/renderer/platform/wtf/optional.h" + +namespace blink { + +class ConsoleMessage; + +// ModuleScriptFetcher is an abstract class to fetch module scripts. Derived +// classes are expected to fetch a module script for the given FetchParameters +// and return its client a fetched resource as ModuleScriptCreationParams. +class CORE_EXPORT ModuleScriptFetcher + : public GarbageCollectedFinalized<ModuleScriptFetcher> { + public: + class CORE_EXPORT Client : public GarbageCollectedMixin { + public: + virtual void NotifyFetchFinished( + const WTF::Optional<ModuleScriptCreationParams>&, + const HeapVector<Member<ConsoleMessage>>& error_messages) = 0; + }; + + ModuleScriptFetcher() = default; + virtual ~ModuleScriptFetcher() = default; + + // Takes a non-const reference to FetchParameters because + // ScriptResource::Fetch() requires it. + virtual void Fetch(FetchParameters&, Client*) = 0; + + virtual void Trace(blink::Visitor*); + + protected: + void NotifyFetchFinished(const WTF::Optional<ModuleScriptCreationParams>&, + const HeapVector<Member<ConsoleMessage>>&); + + void SetClient(Client*); + + private: + Member<Client> client_; +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_MODULESCRIPT_MODULE_SCRIPT_FETCHER_H_ diff --git a/chromium/third_party/blink/renderer/core/loader/modulescript/module_script_loader.cc b/chromium/third_party/blink/renderer/core/loader/modulescript/module_script_loader.cc new file mode 100644 index 00000000000..05b5b6b8c39 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/modulescript/module_script_loader.cc @@ -0,0 +1,220 @@ +// Copyright 2017 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/modulescript/module_script_loader.h" + +#include "third_party/blink/renderer/core/execution_context/execution_context.h" +#include "third_party/blink/renderer/core/inspector/console_message.h" +#include "third_party/blink/renderer/core/loader/modulescript/document_module_script_fetcher.h" +#include "third_party/blink/renderer/core/loader/modulescript/module_script_fetcher.h" +#include "third_party/blink/renderer/core/loader/modulescript/module_script_loader_client.h" +#include "third_party/blink/renderer/core/loader/modulescript/module_script_loader_registry.h" +#include "third_party/blink/renderer/core/script/modulator.h" +#include "third_party/blink/renderer/core/script/module_script.h" +#include "third_party/blink/renderer/core/workers/main_thread_worklet_global_scope.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource.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/weborigin/security_policy.h" +#include "third_party/blink/renderer/platform/wtf/text/atomic_string.h" + +namespace blink { + +ModuleScriptLoader::ModuleScriptLoader(Modulator* modulator, + const ScriptFetchOptions& options, + ModuleScriptLoaderRegistry* registry, + ModuleScriptLoaderClient* client) + : modulator_(modulator), + options_(options), + registry_(registry), + client_(client) { + DCHECK(modulator); + DCHECK(registry); + DCHECK(client); +} + +ModuleScriptLoader::~ModuleScriptLoader() = default; + +#if DCHECK_IS_ON() +const char* ModuleScriptLoader::StateToString(ModuleScriptLoader::State state) { + switch (state) { + case State::kInitial: + return "Initial"; + case State::kFetching: + return "Fetching"; + case State::kFinished: + return "Finished"; + } + NOTREACHED(); + return ""; +} +#endif + +void ModuleScriptLoader::AdvanceState(ModuleScriptLoader::State new_state) { + switch (state_) { + case State::kInitial: + DCHECK_EQ(new_state, State::kFetching); + break; + case State::kFetching: + DCHECK_EQ(new_state, State::kFinished); + break; + case State::kFinished: + NOTREACHED(); + break; + } + +#if DCHECK_IS_ON() + RESOURCE_LOADING_DVLOG(1) + << "ModuleLoader[" << url_.GetString() << "]::advanceState(" + << StateToString(state_) << " -> " << StateToString(new_state) << ")"; +#endif + state_ = new_state; + + if (state_ == State::kFinished) { + registry_->ReleaseFinishedLoader(this); + client_->NotifyNewSingleModuleFinished(module_script_); + } +} + +void ModuleScriptLoader::Fetch(const ModuleScriptFetchRequest& module_request, + ModuleGraphLevel level) { + // https://html.spec.whatwg.org/#fetch-a-single-module-script + + // Step 4. "Set moduleMap[url] to "fetching"." [spec text] + AdvanceState(State::kFetching); + + // Step 5. "Let request be a new request whose url is url, ..." [spec text] + ResourceRequest resource_request(module_request.Url()); +#if DCHECK_IS_ON() + url_ = module_request.Url(); +#endif + + ResourceLoaderOptions options; + + // TODO(kouhei): handle "destination is destination," + + // Step 6. "Set up the module script request given request and options." + // [spec text] + // [SMSR] + // https://html.spec.whatwg.org/multipage/webappapis.html#set-up-the-module-script-request + + // [SMSR] "... its parser metadata to options's parser metadata, ..." + // [spec text] + options.parser_disposition = options_.ParserState(); + + // As initiator for module script fetch is not specified in HTML spec, + // we specity "" as initiator per: + // https://fetch.spec.whatwg.org/#concept-request-initiator + options.initiator_info.name = g_empty_atom; + + if (level == ModuleGraphLevel::kDependentModuleFetch) { + options.initiator_info.imported_module_referrer = + module_request.GetReferrer(); + options.initiator_info.position = module_request.GetReferrerPosition(); + } + + // Note: |options| should not be modified after here. + FetchParameters fetch_params(resource_request, options); + + // [SMSR] "... its integrity metadata to options's integrity metadata, ..." + // [spec text] + fetch_params.SetIntegrityMetadata(options_.GetIntegrityMetadata()); + fetch_params.MutableResourceRequest().SetFetchIntegrity( + options_.GetIntegrityAttributeValue()); + + // [SMSR] "Set request's cryptographic nonce metadata to options's + // cryptographic nonce, ..." [spec text] + fetch_params.SetContentSecurityPolicyNonce(options_.Nonce()); + + // Step 5. "... mode is "cors", ..." + // [SMSR] "... and its credentials mode to options's credentials mode." + // [spec text] + fetch_params.SetCrossOriginAccessControl( + modulator_->GetSecurityOriginForFetch(), options_.CredentialsMode()); + + // Step 5. "... referrer is referrer, ..." [spec text] + if (!module_request.GetReferrer().IsNull()) { + fetch_params.MutableResourceRequest().SetHTTPReferrer( + SecurityPolicy::GenerateReferrer(module_request.GetReferrerPolicy(), + module_request.Url(), + module_request.GetReferrer())); + } + + // Step 5. "... and client is fetch client settings object." [spec text] + // -> set by ResourceFetcher + + // Note: The fetch request's "origin" isn't specified in + // https://html.spec.whatwg.org/#fetch-a-single-module-script + // Thus, the "origin" is "client" per + // https://fetch.spec.whatwg.org/#concept-request-origin + + // Module scripts are always defer. + fetch_params.SetDefer(FetchParameters::kLazyLoad); + // [nospec] Unlike defer/async classic scripts, module scripts are fetched at + // High priority. + fetch_params.MutableResourceRequest().SetPriority( + ResourceLoadPriority::kHigh); + + // Use UTF-8, according to Step 9: + // "Let source text be the result of UTF-8 decoding response's body." + // [spec text] + fetch_params.SetDecoderOptions( + TextResourceDecoderOptions::CreateAlwaysUseUTF8ForText()); + + // Step 7. "If the caller specified custom steps to perform the fetch, + // perform them on request, setting the is top-level flag if the top-level + // module fetch flag is set. Return from this algorithm, and when the custom + // perform the fetch steps complete with response response, run the remaining + // steps. + // Otherwise, fetch request. Return from this algorithm, and run the remaining + // steps as part of the fetch's process response for the response response." + // [spec text] + module_fetcher_ = modulator_->CreateModuleScriptFetcher(); + module_fetcher_->Fetch(fetch_params, this); +} + +void ModuleScriptLoader::NotifyFetchFinished( + const WTF::Optional<ModuleScriptCreationParams>& params, + const HeapVector<Member<ConsoleMessage>>& error_messages) { + // [nospec] Abort the steps if the browsing context is discarded. + if (!modulator_->HasValidContext()) { + AdvanceState(State::kFinished); + return; + } + + // Note: "conditions" referred in Step 8 is implemented in + // WasModuleLoadSuccessful() in DocumentModuleScriptFetcher.cpp. + // Step 8. "If any of the following conditions are met, set moduleMap[url] to + // null, asynchronously complete this algorithm with null, and abort these + // steps." [spec text] + if (!params.has_value()) { + for (ConsoleMessage* error_message : error_messages) { + ExecutionContext::From(modulator_->GetScriptState()) + ->AddConsoleMessage(error_message); + } + AdvanceState(State::kFinished); + return; + } + + // Step 9. "Let source text be the result of UTF-8 decoding response's body." + // [spec text] + // Step 10. "Let module script be the result of creating a module script given + // source text, module map settings object, response's url, and options." + // [spec text] + module_script_ = ModuleScript::Create( + params->GetSourceText(), modulator_, params->GetResponseUrl(), + params->GetResponseUrl(), options_, params->GetAccessControlStatus()); + + AdvanceState(State::kFinished); +} + +void ModuleScriptLoader::Trace(blink::Visitor* visitor) { + visitor->Trace(modulator_); + visitor->Trace(module_script_); + visitor->Trace(registry_); + visitor->Trace(client_); + visitor->Trace(module_fetcher_); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/loader/modulescript/module_script_loader.h b/chromium/third_party/blink/renderer/core/loader/modulescript/module_script_loader.h new file mode 100644 index 00000000000..470e799385c --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/modulescript/module_script_loader.h @@ -0,0 +1,96 @@ +// Copyright 2017 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_MODULESCRIPT_MODULE_SCRIPT_LOADER_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_MODULESCRIPT_MODULE_SCRIPT_LOADER_H_ + +#include "base/macros.h" +#include "third_party/blink/public/platform/web_url_request.h" +#include "third_party/blink/renderer/core/core_export.h" +#include "third_party/blink/renderer/core/loader/modulescript/module_script_creation_params.h" +#include "third_party/blink/renderer/core/loader/modulescript/module_script_fetch_request.h" +#include "third_party/blink/renderer/core/loader/modulescript/module_script_fetcher.h" +#include "third_party/blink/renderer/platform/heap/handle.h" +#include "third_party/blink/renderer/platform/weborigin/kurl.h" + +namespace blink { + +class Modulator; +class ModuleScript; +class ModuleScriptLoaderClient; +class ModuleScriptLoaderRegistry; +enum class ModuleGraphLevel; + +// ModuleScriptLoader is responsible for loading a new single ModuleScript. +// +// ModuleScriptLoader constructs FetchParameters and asks ModuleScriptFetcher +// to fetch a script with the parameters. Then, it returns its client a compiled +// ModuleScript. +// +// ModuleScriptLoader(s) should only be used via Modulator and its ModuleMap. +class CORE_EXPORT ModuleScriptLoader final + : public GarbageCollectedFinalized<ModuleScriptLoader>, + public ModuleScriptFetcher::Client { + USING_GARBAGE_COLLECTED_MIXIN(ModuleScriptLoader); + + enum class State { + kInitial, + // FetchParameters is being processed, and ModuleScriptLoader hasn't + // notifyFinished(). + kFetching, + // Finished successfully or w/ error. + kFinished, + }; + + public: + static ModuleScriptLoader* Create(Modulator* modulator, + const ScriptFetchOptions& options, + ModuleScriptLoaderRegistry* registry, + ModuleScriptLoaderClient* client) { + return new ModuleScriptLoader(modulator, options, registry, client); + } + + ~ModuleScriptLoader(); + + void Fetch(const ModuleScriptFetchRequest&, + ModuleGraphLevel); + + // Implements ModuleScriptFetcher::Client. + void NotifyFetchFinished( + const WTF::Optional<ModuleScriptCreationParams>&, + const HeapVector<Member<ConsoleMessage>>& error_messages) override; + + bool IsInitialState() const { return state_ == State::kInitial; } + bool HasFinished() const { return state_ == State::kFinished; } + + void Trace(blink::Visitor*) override; + + private: + ModuleScriptLoader(Modulator*, + const ScriptFetchOptions&, + ModuleScriptLoaderRegistry*, + ModuleScriptLoaderClient*); + + void AdvanceState(State new_state); +#if DCHECK_IS_ON() + static const char* StateToString(State); +#endif + + Member<Modulator> modulator_; + State state_ = State::kInitial; + const ScriptFetchOptions options_; + Member<ModuleScript> module_script_; + Member<ModuleScriptLoaderRegistry> registry_; + Member<ModuleScriptLoaderClient> client_; + Member<ModuleScriptFetcher> module_fetcher_; +#if DCHECK_IS_ON() + KURL url_; +#endif + + DISALLOW_COPY_AND_ASSIGN(ModuleScriptLoader); +}; + +} // namespace blink + +#endif diff --git a/chromium/third_party/blink/renderer/core/loader/modulescript/module_script_loader_client.h b/chromium/third_party/blink/renderer/core/loader/modulescript/module_script_loader_client.h new file mode 100644 index 00000000000..e8f1b954be5 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/modulescript/module_script_loader_client.h @@ -0,0 +1,32 @@ +// Copyright 2017 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_MODULESCRIPT_MODULE_SCRIPT_LOADER_CLIENT_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_MODULESCRIPT_MODULE_SCRIPT_LOADER_CLIENT_H_ + +#include "third_party/blink/renderer/platform/heap/handle.h" + +namespace blink { + +class ModuleScript; + +// A ModuleScriptLoaderClient is notified when a single module script load is +// complete. +// Note: Its corresponding module map entry is typically not yet created at the +// time of callback. +class ModuleScriptLoaderClient : public GarbageCollectedMixin { + public: + virtual ~ModuleScriptLoaderClient() = default; + ; + + private: + friend class ModuleScriptLoader; + friend class ModuleMapTestModulator; + + virtual void NotifyNewSingleModuleFinished(ModuleScript*) = 0; +}; + +} // namespace blink + +#endif diff --git a/chromium/third_party/blink/renderer/core/loader/modulescript/module_script_loader_registry.cc b/chromium/third_party/blink/renderer/core/loader/modulescript/module_script_loader_registry.cc new file mode 100644 index 00000000000..eb8b543ac4a --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/modulescript/module_script_loader_registry.cc @@ -0,0 +1,37 @@ +// Copyright 2017 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/modulescript/module_script_loader_registry.h" + +#include "third_party/blink/renderer/core/loader/modulescript/module_script_loader.h" + +namespace blink { + +void ModuleScriptLoaderRegistry::Trace(blink::Visitor* visitor) { + visitor->Trace(active_loaders_); +} + +ModuleScriptLoader* ModuleScriptLoaderRegistry::Fetch( + const ModuleScriptFetchRequest& request, + ModuleGraphLevel level, + Modulator* modulator, + ModuleScriptLoaderClient* client) { + ModuleScriptLoader* loader = + ModuleScriptLoader::Create(modulator, request.Options(), this, client); + DCHECK(loader->IsInitialState()); + active_loaders_.insert(loader); + loader->Fetch(request, level); + return loader; +} + +void ModuleScriptLoaderRegistry::ReleaseFinishedLoader( + ModuleScriptLoader* loader) { + DCHECK(loader->HasFinished()); + + auto it = active_loaders_.find(loader); + DCHECK_NE(it, active_loaders_.end()); + active_loaders_.erase(it); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/loader/modulescript/module_script_loader_registry.h b/chromium/third_party/blink/renderer/core/loader/modulescript/module_script_loader_registry.h new file mode 100644 index 00000000000..c3dd895bb65 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/modulescript/module_script_loader_registry.h @@ -0,0 +1,46 @@ +// Copyright 2017 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_MODULESCRIPT_MODULE_SCRIPT_LOADER_REGISTRY_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_MODULESCRIPT_MODULE_SCRIPT_LOADER_REGISTRY_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/weborigin/kurl.h" +#include "third_party/blink/renderer/platform/wtf/hash_set.h" + +namespace blink { + +class Modulator; +class ModuleScriptFetchRequest; +class ModuleScriptLoader; +class ModuleScriptLoaderClient; +enum class ModuleGraphLevel; + +// ModuleScriptLoaderRegistry keeps active ModuleLoaders alive. +class CORE_EXPORT ModuleScriptLoaderRegistry final + : public GarbageCollected<ModuleScriptLoaderRegistry> { + public: + static ModuleScriptLoaderRegistry* Create() { + return new ModuleScriptLoaderRegistry; + } + void Trace(blink::Visitor*); + + ModuleScriptLoader* Fetch(const ModuleScriptFetchRequest&, + ModuleGraphLevel, + Modulator*, + ModuleScriptLoaderClient*); + + private: + ModuleScriptLoaderRegistry() = default; + + friend class ModuleScriptLoader; + void ReleaseFinishedLoader(ModuleScriptLoader*); + + HeapHashSet<Member<ModuleScriptLoader>> active_loaders_; +}; + +} // namespace blink + +#endif diff --git a/chromium/third_party/blink/renderer/core/loader/modulescript/module_script_loader_test.cc b/chromium/third_party/blink/renderer/core/loader/modulescript/module_script_loader_test.cc new file mode 100644 index 00000000000..bc8992bf45b --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/modulescript/module_script_loader_test.cc @@ -0,0 +1,363 @@ +// Copyright 2017 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/modulescript/module_script_loader.h" + +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/blink/public/platform/platform.h" +#include "third_party/blink/public/platform/task_type.h" +#include "third_party/blink/public/platform/web_url_loader_mock_factory.h" +#include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h" +#include "third_party/blink/renderer/bindings/core/v8/worker_or_worklet_script_controller.h" +#include "third_party/blink/renderer/core/dom/document.h" +#include "third_party/blink/renderer/core/loader/modulescript/document_module_script_fetcher.h" +#include "third_party/blink/renderer/core/loader/modulescript/module_script_fetch_request.h" +#include "third_party/blink/renderer/core/loader/modulescript/module_script_loader_client.h" +#include "third_party/blink/renderer/core/loader/modulescript/module_script_loader_registry.h" +#include "third_party/blink/renderer/core/loader/modulescript/worker_or_worklet_module_script_fetcher.h" +#include "third_party/blink/renderer/core/origin_trials/origin_trial_context.h" +#include "third_party/blink/renderer/core/script/modulator.h" +#include "third_party/blink/renderer/core/script/module_script.h" +#include "third_party/blink/renderer/core/testing/dummy_modulator.h" +#include "third_party/blink/renderer/core/testing/page_test_base.h" +#include "third_party/blink/renderer/core/workers/global_scope_creation_params.h" +#include "third_party/blink/renderer/core/workers/main_thread_worklet_global_scope.h" +#include "third_party/blink/renderer/core/workers/main_thread_worklet_reporting_proxy.h" +#include "third_party/blink/renderer/core/workers/worklet_module_responses_map.h" +#include "third_party/blink/renderer/platform/heap/handle.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h" +#include "third_party/blink/renderer/platform/loader/testing/fetch_testing_platform_support.h" +#include "third_party/blink/renderer/platform/loader/testing/mock_fetch_context.h" +#include "third_party/blink/renderer/platform/testing/unit_test_helpers.h" +#include "third_party/blink/renderer/platform/testing/url_test_helpers.h" + +namespace blink { + +namespace { + +class TestModuleScriptLoaderClient final + : public GarbageCollectedFinalized<TestModuleScriptLoaderClient>, + public ModuleScriptLoaderClient { + USING_GARBAGE_COLLECTED_MIXIN(TestModuleScriptLoaderClient); + + public: + TestModuleScriptLoaderClient() = default; + ~TestModuleScriptLoaderClient() override = default; + + void Trace(blink::Visitor* visitor) override { + visitor->Trace(module_script_); + } + + void NotifyNewSingleModuleFinished(ModuleScript* module_script) override { + was_notify_finished_ = true; + module_script_ = module_script; + } + + bool WasNotifyFinished() const { return was_notify_finished_; } + ModuleScript* GetModuleScript() { return module_script_; } + + private: + bool was_notify_finished_ = false; + Member<ModuleScript> module_script_; +}; + +class ModuleScriptLoaderTestModulator final : public DummyModulator { + public: + ModuleScriptLoaderTestModulator( + scoped_refptr<ScriptState> script_state, + scoped_refptr<const SecurityOrigin> security_origin, + ResourceFetcher* fetcher) + : script_state_(std::move(script_state)), + security_origin_(std::move(security_origin)), + fetcher_(fetcher) {} + + ~ModuleScriptLoaderTestModulator() override = default; + + const SecurityOrigin* GetSecurityOriginForFetch() override { + return security_origin_.get(); + } + + ScriptState* GetScriptState() override { return script_state_.get(); } + + ScriptModule CompileModule(const String& script, + const KURL& source_url, + const KURL& base_url, + const ScriptFetchOptions& options, + AccessControlStatus access_control_status, + const TextPosition& position, + ExceptionState& exception_state) override { + ScriptState::Scope scope(script_state_.get()); + return ScriptModule::Compile( + script_state_->GetIsolate(), script, source_url, base_url, options, + access_control_status, position, exception_state); + } + + void SetModuleRequests(const Vector<String>& requests) { + requests_.clear(); + for (const String& request : requests) { + requests_.emplace_back(request, TextPosition::MinimumPosition()); + } + } + Vector<ModuleRequest> ModuleRequestsFromScriptModule(ScriptModule) override { + return requests_; + } + + ModuleScriptFetcher* CreateModuleScriptFetcher() override { + auto* execution_context = ExecutionContext::From(script_state_.get()); + if (execution_context->IsDocument()) + return new DocumentModuleScriptFetcher(Fetcher()); + auto* global_scope = ToWorkletGlobalScope(execution_context); + return new WorkerOrWorkletModuleScriptFetcher( + global_scope->ModuleFetchCoordinatorProxy()); + } + + ResourceFetcher* Fetcher() const { return fetcher_.Get(); } + + void Trace(blink::Visitor*) override; + + private: + scoped_refptr<ScriptState> script_state_; + scoped_refptr<const SecurityOrigin> security_origin_; + Member<ResourceFetcher> fetcher_; + Vector<ModuleRequest> requests_; +}; + +void ModuleScriptLoaderTestModulator::Trace(blink::Visitor* visitor) { + visitor->Trace(fetcher_); + DummyModulator::Trace(visitor); +} + +} // namespace + +class ModuleScriptLoaderTest : public PageTestBase { + DISALLOW_COPY_AND_ASSIGN(ModuleScriptLoaderTest); + + public: + ModuleScriptLoaderTest() = default; + void SetUp() override; + + void InitializeForDocument(); + void InitializeForWorklet(); + + void TestFetchDataURL(TestModuleScriptLoaderClient*); + void TestInvalidSpecifier(TestModuleScriptLoaderClient*); + void TestFetchInvalidURL(TestModuleScriptLoaderClient*); + void TestFetchURL(TestModuleScriptLoaderClient*); + + ModuleScriptLoaderTestModulator* GetModulator() { return modulator_.Get(); } + + protected: + ScopedTestingPlatformSupport<FetchTestingPlatformSupport> platform_; + std::unique_ptr<MainThreadWorkletReportingProxy> reporting_proxy_; + Persistent<ModuleScriptLoaderTestModulator> modulator_; + Persistent<MainThreadWorkletGlobalScope> global_scope_; +}; + +void ModuleScriptLoaderTest::SetUp() { + platform_->AdvanceClockSeconds(1.); // For non-zero DocumentParserTimings + PageTestBase::SetUp(IntSize(500, 500)); + GetDocument().SetURL(KURL("https://example.test")); + GetDocument().SetSecurityOrigin(SecurityOrigin::Create(GetDocument().Url())); +} + +void ModuleScriptLoaderTest::InitializeForDocument() { + auto* fetch_context = + MockFetchContext::Create(MockFetchContext::kShouldLoadNewResource); + auto* fetcher = ResourceFetcher::Create(fetch_context); + modulator_ = new ModuleScriptLoaderTestModulator( + ToScriptStateForMainWorld(&GetFrame()), GetDocument().GetSecurityOrigin(), + fetcher); +} + +void ModuleScriptLoaderTest::InitializeForWorklet() { + auto* fetch_context = + MockFetchContext::Create(MockFetchContext::kShouldLoadNewResource); + auto* fetcher = ResourceFetcher::Create(fetch_context); + reporting_proxy_ = + std::make_unique<MainThreadWorkletReportingProxy>(&GetDocument()); + auto creation_params = std::make_unique<GlobalScopeCreationParams>( + GetDocument().Url(), GetDocument().UserAgent(), + nullptr /* content_security_policy_parsed_headers */, + GetDocument().GetReferrerPolicy(), GetDocument().GetSecurityOrigin(), + GetDocument().IsSecureContext(), nullptr /* worker_clients */, + GetDocument().AddressSpace(), + OriginTrialContext::GetTokens(&GetDocument()).get(), + base::UnguessableToken::Create(), nullptr /* worker_settings */, + kV8CacheOptionsDefault, new WorkletModuleResponsesMap(fetcher)); + global_scope_ = new MainThreadWorkletGlobalScope( + &GetFrame(), std::move(creation_params), *reporting_proxy_); + global_scope_->ScriptController()->InitializeContextIfNeeded("Dummy Context"); + modulator_ = new ModuleScriptLoaderTestModulator( + global_scope_->ScriptController()->GetScriptState(), + GetDocument().GetSecurityOrigin(), fetcher); +} + +void ModuleScriptLoaderTest::TestFetchDataURL( + TestModuleScriptLoaderClient* client) { + ModuleScriptLoaderRegistry* registry = ModuleScriptLoaderRegistry::Create(); + KURL url("data:text/javascript,export default 'grapes';"); + ModuleScriptFetchRequest module_request(url, kReferrerPolicyDefault, + ScriptFetchOptions()); + registry->Fetch(module_request, ModuleGraphLevel::kTopLevelModuleFetch, + GetModulator(), client); +} + +TEST_F(ModuleScriptLoaderTest, FetchDataURL) { + InitializeForDocument(); + TestModuleScriptLoaderClient* client = new TestModuleScriptLoaderClient; + TestFetchDataURL(client); + + EXPECT_TRUE(client->WasNotifyFinished()) + << "ModuleScriptLoader should finish synchronously."; + ASSERT_TRUE(client->GetModuleScript()); + EXPECT_FALSE(client->GetModuleScript()->HasEmptyRecord()); + EXPECT_FALSE(client->GetModuleScript()->HasParseError()); +} + +TEST_F(ModuleScriptLoaderTest, FetchDataURL_OnWorklet) { + InitializeForWorklet(); + TestModuleScriptLoaderClient* client1 = new TestModuleScriptLoaderClient; + TestFetchDataURL(client1); + + EXPECT_FALSE(client1->WasNotifyFinished()) + << "ModuleScriptLoader should finish asynchronously."; + platform_->RunUntilIdle(); + + EXPECT_TRUE(client1->WasNotifyFinished()); + ASSERT_TRUE(client1->GetModuleScript()); + EXPECT_FALSE(client1->GetModuleScript()->HasEmptyRecord()); + EXPECT_FALSE(client1->GetModuleScript()->HasParseError()); + + // Try to fetch the same URL again in order to verify the case where + // WorkletModuleResponsesMap serves a cache. + TestModuleScriptLoaderClient* client2 = new TestModuleScriptLoaderClient; + TestFetchDataURL(client2); + + EXPECT_FALSE(client2->WasNotifyFinished()) + << "ModuleScriptLoader should finish asynchronously."; + platform_->RunUntilIdle(); + + EXPECT_TRUE(client2->WasNotifyFinished()); + ASSERT_TRUE(client2->GetModuleScript()); + EXPECT_FALSE(client2->GetModuleScript()->HasEmptyRecord()); + EXPECT_FALSE(client2->GetModuleScript()->HasParseError()); +} + +void ModuleScriptLoaderTest::TestInvalidSpecifier( + TestModuleScriptLoaderClient* client) { + ModuleScriptLoaderRegistry* registry = ModuleScriptLoaderRegistry::Create(); + KURL url("data:text/javascript,import 'invalid';export default 'grapes';"); + ModuleScriptFetchRequest module_request(url, kReferrerPolicyDefault, + ScriptFetchOptions()); + GetModulator()->SetModuleRequests({"invalid"}); + registry->Fetch(module_request, ModuleGraphLevel::kTopLevelModuleFetch, + GetModulator(), client); +} + +TEST_F(ModuleScriptLoaderTest, InvalidSpecifier) { + InitializeForDocument(); + TestModuleScriptLoaderClient* client = new TestModuleScriptLoaderClient; + TestInvalidSpecifier(client); + + EXPECT_TRUE(client->WasNotifyFinished()) + << "ModuleScriptLoader should finish synchronously."; + ASSERT_TRUE(client->GetModuleScript()); + EXPECT_TRUE(client->GetModuleScript()->HasEmptyRecord()); + EXPECT_TRUE(client->GetModuleScript()->HasParseError()); +} + +TEST_F(ModuleScriptLoaderTest, InvalidSpecifier_OnWorklet) { + InitializeForWorklet(); + TestModuleScriptLoaderClient* client = new TestModuleScriptLoaderClient; + TestInvalidSpecifier(client); + + EXPECT_FALSE(client->WasNotifyFinished()) + << "ModuleScriptLoader should finish asynchronously."; + platform_->RunUntilIdle(); + + EXPECT_TRUE(client->WasNotifyFinished()); + ASSERT_TRUE(client->GetModuleScript()); + EXPECT_TRUE(client->GetModuleScript()->HasEmptyRecord()); + EXPECT_TRUE(client->GetModuleScript()->HasParseError()); +} + +void ModuleScriptLoaderTest::TestFetchInvalidURL( + TestModuleScriptLoaderClient* client) { + ModuleScriptLoaderRegistry* registry = ModuleScriptLoaderRegistry::Create(); + KURL url; + EXPECT_FALSE(url.IsValid()); + ModuleScriptFetchRequest module_request(url, kReferrerPolicyDefault, + ScriptFetchOptions()); + registry->Fetch(module_request, ModuleGraphLevel::kTopLevelModuleFetch, + GetModulator(), client); +} + +TEST_F(ModuleScriptLoaderTest, FetchInvalidURL) { + InitializeForDocument(); + TestModuleScriptLoaderClient* client = new TestModuleScriptLoaderClient; + TestFetchInvalidURL(client); + + EXPECT_TRUE(client->WasNotifyFinished()) + << "ModuleScriptLoader should finish synchronously."; + EXPECT_FALSE(client->GetModuleScript()); +} + +TEST_F(ModuleScriptLoaderTest, FetchInvalidURL_OnWorklet) { + InitializeForWorklet(); + TestModuleScriptLoaderClient* client = new TestModuleScriptLoaderClient; + TestFetchInvalidURL(client); + + EXPECT_FALSE(client->WasNotifyFinished()) + << "ModuleScriptLoader should finish asynchronously."; + platform_->RunUntilIdle(); + + EXPECT_TRUE(client->WasNotifyFinished()); + EXPECT_FALSE(client->GetModuleScript()); +} + +void ModuleScriptLoaderTest::TestFetchURL( + TestModuleScriptLoaderClient* client) { + KURL url("https://example.test/module.js"); + URLTestHelpers::RegisterMockedURLLoad( + url, test::CoreTestDataPath("module.js"), "text/javascript"); + + ModuleScriptLoaderRegistry* registry = ModuleScriptLoaderRegistry::Create(); + ModuleScriptFetchRequest module_request(url, kReferrerPolicyDefault, + ScriptFetchOptions()); + registry->Fetch(module_request, ModuleGraphLevel::kTopLevelModuleFetch, + GetModulator(), client); +} + +TEST_F(ModuleScriptLoaderTest, FetchURL) { + InitializeForDocument(); + TestModuleScriptLoaderClient* client = new TestModuleScriptLoaderClient; + TestFetchURL(client); + + EXPECT_FALSE(client->WasNotifyFinished()) + << "ModuleScriptLoader unexpectedly finished synchronously."; + platform_->GetURLLoaderMockFactory()->ServeAsynchronousRequests(); + + EXPECT_TRUE(client->WasNotifyFinished()); + EXPECT_TRUE(client->GetModuleScript()); +} + +TEST_F(ModuleScriptLoaderTest, FetchURL_OnWorklet) { + InitializeForWorklet(); + TestModuleScriptLoaderClient* client = new TestModuleScriptLoaderClient; + TestFetchURL(client); + + EXPECT_FALSE(client->WasNotifyFinished()) + << "ModuleScriptLoader unexpectedly finished synchronously."; + + // Advance until WorkerOrWorkletModuleScriptFetcher finishes looking up a + // cache in WorkletModuleResponsesMap and issues a fetch request so that + // ServeAsynchronousRequests() can serve for the pending request. + platform_->RunUntilIdle(); + platform_->GetURLLoaderMockFactory()->ServeAsynchronousRequests(); + + EXPECT_TRUE(client->WasNotifyFinished()); + EXPECT_TRUE(client->GetModuleScript()); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/loader/modulescript/module_tree_linker.cc b/chromium/third_party/blink/renderer/core/loader/modulescript/module_tree_linker.cc new file mode 100644 index 00000000000..bce42dabd73 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/modulescript/module_tree_linker.cc @@ -0,0 +1,492 @@ +// Copyright 2017 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/modulescript/module_tree_linker.h" + +#include "third_party/blink/renderer/bindings/core/v8/script_module.h" +#include "third_party/blink/renderer/core/loader/modulescript/module_script_fetch_request.h" +#include "third_party/blink/renderer/core/loader/modulescript/module_tree_linker_registry.h" +#include "third_party/blink/renderer/core/script/module_script.h" +#include "third_party/blink/renderer/platform/bindings/v8_throw_exception.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource_loading_log.h" +#include "third_party/blink/renderer/platform/wtf/vector.h" +#include "v8/include/v8.h" + +namespace blink { + +ModuleTreeLinker* ModuleTreeLinker::Fetch( + const ModuleScriptFetchRequest& request, + Modulator* modulator, + ModuleTreeLinkerRegistry* registry, + ModuleTreeClient* client) { + ModuleTreeLinker* fetcher = new ModuleTreeLinker(modulator, registry, client); + fetcher->FetchRoot(request); + return fetcher; +} + +ModuleTreeLinker* ModuleTreeLinker::FetchDescendantsForInlineScript( + ModuleScript* module_script, + Modulator* modulator, + ModuleTreeLinkerRegistry* registry, + ModuleTreeClient* client) { + DCHECK(module_script); + ModuleTreeLinker* fetcher = new ModuleTreeLinker(modulator, registry, client); + fetcher->FetchRootInline(module_script); + return fetcher; +} + +ModuleTreeLinker::ModuleTreeLinker(Modulator* modulator, + ModuleTreeLinkerRegistry* registry, + ModuleTreeClient* client) + : modulator_(modulator), registry_(registry), client_(client) { + CHECK(modulator); + CHECK(registry); + CHECK(client); +} + +void ModuleTreeLinker::Trace(blink::Visitor* visitor) { + visitor->Trace(modulator_); + visitor->Trace(registry_); + visitor->Trace(client_); + visitor->Trace(result_); + SingleModuleClient::Trace(visitor); +} + +void ModuleTreeLinker::TraceWrappers( + const ScriptWrappableVisitor* visitor) const { + visitor->TraceWrappers(result_); + SingleModuleClient::TraceWrappers(visitor); +} + +#if DCHECK_IS_ON() +const char* ModuleTreeLinker::StateToString(ModuleTreeLinker::State state) { + switch (state) { + case State::kInitial: + return "Initial"; + case State::kFetchingSelf: + return "FetchingSelf"; + case State::kFetchingDependencies: + return "FetchingDependencies"; + case State::kInstantiating: + return "Instantiating"; + case State::kFinished: + return "Finished"; + } + NOTREACHED(); + return ""; +} +#endif + +void ModuleTreeLinker::AdvanceState(State new_state) { +#if DCHECK_IS_ON() + RESOURCE_LOADING_DVLOG(1) + << *this << "::advanceState(" << StateToString(state_) << " -> " + << StateToString(new_state) << ")"; +#endif + + switch (state_) { + case State::kInitial: + CHECK_EQ(num_incomplete_fetches_, 0u); + CHECK_EQ(new_state, State::kFetchingSelf); + break; + case State::kFetchingSelf: + CHECK_EQ(num_incomplete_fetches_, 0u); + CHECK(new_state == State::kFetchingDependencies || + new_state == State::kFinished); + break; + case State::kFetchingDependencies: + CHECK(new_state == State::kInstantiating || + new_state == State::kFinished); + break; + case State::kInstantiating: + CHECK_EQ(new_state, State::kFinished); + break; + case State::kFinished: + NOTREACHED(); + break; + } + + state_ = new_state; + + if (state_ == State::kFinished) { +#if DCHECK_IS_ON() + if (result_) { + RESOURCE_LOADING_DVLOG(1) + << *this << " finished with final result " << *result_; + } else { + RESOURCE_LOADING_DVLOG(1) << *this << " finished with nullptr."; + } +#endif + + registry_->ReleaseFinishedFetcher(this); + + // [IMSGF] Step 6. When the appropriate algorithm asynchronously completes + // with final result, asynchronously complete this algorithm with final + // result. + client_->NotifyModuleTreeLoadFinished(result_); + } +} + +void ModuleTreeLinker::FetchRoot(const ModuleScriptFetchRequest& request) { + // https://html.spec.whatwg.org/multipage/webappapis.html#fetch-a-module-script-tree +#if DCHECK_IS_ON() + url_ = request.Url(); + root_is_inline_ = false; +#endif + + AdvanceState(State::kFetchingSelf); + + // Step 1. Let visited set be << url >>. + visited_set_.insert(request.Url()); + + // Step 2. Perform the internal module script graph fetching procedure given + // ... with the top-level module fetch flag set. ... + InitiateInternalModuleScriptGraphFetching( + request, ModuleGraphLevel::kTopLevelModuleFetch); +} + +void ModuleTreeLinker::FetchRootInline(ModuleScript* module_script) { + // Top-level entry point for [FDaI] for an inline module script. + DCHECK(module_script); +#if DCHECK_IS_ON() + url_ = module_script->BaseURL(); + root_is_inline_ = true; +#endif + + AdvanceState(State::kFetchingSelf); + + // Store the |module_script| here which will be used as result of the + // algorithm when success. Also, this ensures that the |module_script| is + // TraceWrappers()ed via ModuleTreeLinker. + result_ = module_script; + AdvanceState(State::kFetchingDependencies); + + modulator_->TaskRunner()->PostTask( + FROM_HERE, + WTF::Bind(&ModuleTreeLinker::FetchDescendants, WrapPersistent(this), + WrapPersistent(module_script))); +} + +void ModuleTreeLinker::InitiateInternalModuleScriptGraphFetching( + const ModuleScriptFetchRequest& request, + ModuleGraphLevel level) { + // [IMSGF] Step 1. Assert: visited set contains url. + DCHECK(visited_set_.Contains(request.Url())); + + ++num_incomplete_fetches_; + + // [IMSGF] Step 2. Fetch a single module script given ... + modulator_->FetchSingle(request, level, this); + + // [IMSGF] Step 3-- are executed when NotifyModuleLoadFinished() is called. +} + +void ModuleTreeLinker::NotifyModuleLoadFinished(ModuleScript* module_script) { + // [IMSGF] Step 3. Return from this algorithm, and run the following steps + // when fetching a single module script asynchronously completes with result: + + CHECK_GT(num_incomplete_fetches_, 0u); + --num_incomplete_fetches_; + +#if DCHECK_IS_ON() + if (module_script) { + RESOURCE_LOADING_DVLOG(1) + << *this << "::NotifyModuleLoadFinished() with " << *module_script; + } else { + RESOURCE_LOADING_DVLOG(1) + << *this << "::NotifyModuleLoadFinished() with nullptr."; + } +#endif + + if (state_ == State::kFetchingSelf) { + // Corresponds to top-level calls to + // https://html.spec.whatwg.org/multipage/webappapis.html#fetch-the-descendants-of-and-instantiate-a-module-script + // i.e. [IMSGF] with the top-level module fetch flag set (external), or + // Step 22 of "prepare a script" (inline). + // |module_script| is the top-level module, and will be instantiated + // and returned later. + result_ = module_script; + AdvanceState(State::kFetchingDependencies); + } + + if (state_ != State::kFetchingDependencies) { + // We may reach here if one of the descendant failed to load, and the other + // descendants fetches were in flight. + return; + } + + // Note: top-level module fetch flag is implemented so that Instantiate() + // is called once after all descendants are fetched, which corresponds to + // the single invocation of "fetch the descendants of and instantiate". + + // [IMSGF] Step 4. If result is null, asynchronously complete this algorithm + // with null, and abort these steps. + if (!module_script) { + result_ = nullptr; + AdvanceState(State::kFinished); + return; + } + + // [IMSGF] Step 5. If the top-level module fetch flag is set, fetch the + // descendants of and instantiate result given destination and visited set. + // Otherwise, fetch the descendants of result given the same arguments. + FetchDescendants(module_script); +} + +void ModuleTreeLinker::FetchDescendants(ModuleScript* module_script) { + DCHECK(module_script); + + // [nospec] Abort the steps if the browsing context is discarded. + if (!modulator_->HasValidContext()) { + result_ = nullptr; + AdvanceState(State::kFinished); + return; + } + + // [FD] Step 2. Let record be module script's record. + ScriptModule record = module_script->Record(); + + // [FD] Step 1. If module script's record is null, then asynchronously + // complete this algorithm with module script and abort these steps. + if (record.IsNull()) { + found_parse_error_ = true; + // We don't early-exit here and wait until all module scripts to be + // loaded, because we might be not sure which error to be reported. + // + // It is possible to determine whether the error to be reported can be + // determined without waiting for loading module scripts, and thus to + // early-exit here if possible. However, the complexity of such early-exit + // implementation might be high, and optimizing error cases with the + // implementation cost might be not worth doing. + FinalizeFetchDescendantsForOneModuleScript(); + return; + } + + // [FD] Step 3. If record.[[RequestedModules]] is empty, asynchronously + // complete this algorithm with module script. + // + // Note: We defer this bail-out until the end of the procedure. The rest of + // the procedure will be no-op anyway if record.[[RequestedModules]] is empty. + + // [FD] Step 4. Let urls be a new empty list. + Vector<KURL> urls; + Vector<TextPosition> positions; + + // [FD] Step 5. For each string requested of record.[[RequestedModules]], + Vector<Modulator::ModuleRequest> module_requests = + modulator_->ModuleRequestsFromScriptModule(record); + for (const auto& module_request : module_requests) { + // [FD] Step 5.1. Let url be the result of resolving a module specifier + // given module script and requested. + KURL url = module_script->ResolveModuleSpecifier(module_request.specifier); + + // [FD] Step 5.2. Assert: url is never failure, because resolving a module + // specifier must have been previously successful with these same two + // arguments. + CHECK(url.IsValid()) << "ModuleScript::ResolveModuleSpecifier() impl must " + "return either a valid url or null."; + + // [FD] Step 5.3. If visited set does not contain url, then: + if (!visited_set_.Contains(url)) { + // [FD] Step 5.3.1. Append url to urls. + urls.push_back(url); + + // [FD] Step 5.3.2. Append url to visited set. + visited_set_.insert(url); + + positions.push_back(module_request.position); + } + } + + if (urls.IsEmpty()) { + // [FD] Step 3. If record.[[RequestedModules]] is empty, asynchronously + // complete this algorithm with module script. + // + // Also, if record.[[RequestedModules]] is not empty but |urls| is + // empty here, we complete this algorithm. + FinalizeFetchDescendantsForOneModuleScript(); + return; + } + + // [FD] Step 6. Let options be the descendant script fetch options for module + // script's fetch options. + // https://html.spec.whatwg.org/multipage/webappapis.html#descendant-script-fetch-options + // the descendant script fetch options are a new script fetch options whose + // items all have the same values, except for the integrity metadata, which is + // instead the empty string. + ScriptFetchOptions options(module_script->FetchOptions().Nonce(), + IntegrityMetadataSet(), String(), + module_script->FetchOptions().ParserState(), + module_script->FetchOptions().CredentialsMode()); + + // [FD] Step 7. For each url in urls, ... + // + // [FD] Step 7. These invocations of the internal module script graph fetching + // procedure should be performed in parallel to each other. + for (size_t i = 0; i < urls.size(); ++i) { + // [FD] Step 7. ... perform the internal module script graph fetching + // procedure given ... with the top-level module fetch flag unset. ... + ModuleScriptFetchRequest request( + urls[i], options, module_script->BaseURL().GetString(), + modulator_->GetReferrerPolicy(), positions[i]); + InitiateInternalModuleScriptGraphFetching( + request, ModuleGraphLevel::kDependentModuleFetch); + } + + // Asynchronously continue processing after NotifyModuleLoadFinished() is + // called num_incomplete_fetches_ times. + CHECK_GT(num_incomplete_fetches_, 0u); +} + +void ModuleTreeLinker::FinalizeFetchDescendantsForOneModuleScript() { + // [FD] of a single module script is completed here: + // + // [FD] Step 7. Otherwise, wait until all of the internal module script graph + // fetching procedure invocations have asynchronously completed. ... + + // And, if |num_incomplete_fetches_| is 0, all the invocations of [FD] + // (called from [FDaI] Step 2) of the root module script is completed here + // and thus we proceed to [FDaI] Step 4 implemented by Instantiate(). + if (num_incomplete_fetches_ == 0) + Instantiate(); +} + +void ModuleTreeLinker::Instantiate() { + // [nospec] Abort the steps if the browsing context is discarded. + if (!modulator_->HasValidContext()) { + result_ = nullptr; + AdvanceState(State::kFinished); + return; + } + + // [FDaI] Step 4. If result is null, then asynchronously complete this + // algorithm with result. + if (!result_) { + AdvanceState(State::kFinished); + return; + } + + // [FDaI] Step 6. If parse error is null, then: + // + // [Optimization] If |found_parse_error_| is false (i.e. no parse errors + // were found during fetching), we are sure that |parse error| is null and + // thus skip FindFirstParseError() call. + if (!found_parse_error_) { +#if DCHECK_IS_ON() + HeapHashSet<Member<ModuleScript>> discovered_set; + DCHECK(FindFirstParseError(result_, &discovered_set).IsEmpty()); +#endif + + // [FDaI] Step 6.1. Let record be result's record. + ScriptModule record = result_->Record(); + + // [FDaI] Step 6.2. Perform record.Instantiate(). + AdvanceState(State::kInstantiating); + ScriptValue instantiation_error = modulator_->InstantiateModule(record); + + // [FDaI] Step 6.2. If this throws an exception, set result's error to + // rethrow to that exception. + if (!instantiation_error.IsEmpty()) + result_->SetErrorToRethrow(instantiation_error); + } else { + // [FDaI] Step 7. Otherwise ... + + // [FFPE] Step 2. If discoveredSet was not given, let it be an empty set. + HeapHashSet<Member<ModuleScript>> discovered_set; + + // [FDaI] Step 5. Let parse error be the result of finding the first parse + // error given result. + ScriptValue parse_error = FindFirstParseError(result_, &discovered_set); + DCHECK(!parse_error.IsEmpty()); + + // [FDaI] Step 7. ... set result's error to rethrow to parse error. + result_->SetErrorToRethrow(parse_error); + } + + // [FDaI] Step 8. Asynchronously complete this algorithm with result. + AdvanceState(State::kFinished); +} + +// [FFPE] https://html.spec.whatwg.org/#finding-the-first-parse-error +// +// This returns non-empty ScriptValue iff a parse error is found. +ScriptValue ModuleTreeLinker::FindFirstParseError( + ModuleScript* module_script, + HeapHashSet<Member<ModuleScript>>* discovered_set) const { + // FindFirstParseError() is called only when there is no fetch errors, i.e. + // all module scripts in the graph are non-null. + DCHECK(module_script); + + // [FFPE] Step 1. Let moduleMap be moduleScript's settings object's module + // map. + // + // This is accessed via |modulator_|. + + // [FFPE] Step 2 is done before calling this in Instantiate(). + + // [FFPE] Step 3. Append moduleScript to discoveredSet. + discovered_set->insert(module_script); + + // [FFPE] Step 4. If moduleScript's record is null, then return moduleScript's + // parse error. + ScriptModule record = module_script->Record(); + if (record.IsNull()) + return module_script->CreateParseError(); + + // [FFPE] Step 5. Let childSpecifiers be the value of moduleScript's record's + // [[RequestedModules]] internal slot. + Vector<Modulator::ModuleRequest> child_specifiers = + modulator_->ModuleRequestsFromScriptModule(record); + + for (const auto& module_request : child_specifiers) { + // [FFPE] Step 6. Let childURLs be the list obtained by calling resolve a + // module specifier once for each item of childSpecifiers, given + // moduleScript and that item. + KURL child_url = + module_script->ResolveModuleSpecifier(module_request.specifier); + + // [FFPE] Step 6. ... (None of these will ever fail, as otherwise + // moduleScript would have been marked as itself having a parse error.) + CHECK(child_url.IsValid()) + << "ModuleScript::ResolveModuleSpecifier() impl must " + "return either a valid url or null."; + + // [FFPE] Step 7. Let childModules be the list obtained by getting each + // value in moduleMap whose key is given by an item of childURLs. + // + // [FFPE] Step 8. For each childModule of childModules: + ModuleScript* child_module = modulator_->GetFetchedModuleScript(child_url); + + // [FFPE] Step 8.1. Assert: childModule is a module script (i.e., it is not + // "fetching" or null) + CHECK(child_module); + + // [FFPE] Step 8.2. If discoveredSet already contains childModule, continue. + if (discovered_set->Contains(child_module)) + continue; + + // [FFPE] Step 8.3. Let childParseError be the result of finding the first + // parse error given childModule and discoveredSet. + ScriptValue child_parse_error = + FindFirstParseError(child_module, discovered_set); + + // [FFPE] Step 8.4. If childParseError is not null, return childParseError. + if (!child_parse_error.IsEmpty()) + return child_parse_error; + } + + // [FFPE] Step 9. Return null. + return ScriptValue(); +} + +#if DCHECK_IS_ON() +std::ostream& operator<<(std::ostream& stream, const ModuleTreeLinker& linker) { + stream << "ModuleTreeLinker[" << &linker + << ", url=" << linker.url_.GetString() + << ", inline=" << linker.root_is_inline_ << "]"; + return stream; +} +#endif + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/loader/modulescript/module_tree_linker.h b/chromium/third_party/blink/renderer/core/loader/modulescript/module_tree_linker.h new file mode 100644 index 00000000000..d6cc520db11 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/modulescript/module_tree_linker.h @@ -0,0 +1,129 @@ +// Copyright 2017 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_MODULESCRIPT_MODULE_TREE_LINKER_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_MODULESCRIPT_MODULE_TREE_LINKER_H_ + +#include "third_party/blink/renderer/core/core_export.h" +#include "third_party/blink/renderer/core/script/modulator.h" +#include "third_party/blink/renderer/platform/bindings/script_wrappable.h" +#include "third_party/blink/renderer/platform/bindings/trace_wrapper_member.h" +#include "third_party/blink/renderer/platform/weborigin/kurl.h" +#include "third_party/blink/renderer/platform/weborigin/kurl_hash.h" +#include "third_party/blink/renderer/platform/wtf/hash_set.h" + +namespace blink { + +class ModuleScriptFetchRequest; +class ModuleTreeLinkerRegistry; + +// A ModuleTreeLinker is responsible for running and keeping intermediate states +// for a top-level [IMSGF] "internal module script graph fetching procedure" or +// a top-level [FDaI] "fetch the descendants of and instantiate", and all the +// invocations of [IMSGF] and [FD] "fetch the descendants" under that. +// +// Spec links: +// [IMSGF] +// https://html.spec.whatwg.org/#internal-module-script-graph-fetching-procedure +// [FD] +// https://html.spec.whatwg.org/#fetch-the-descendants-of-a-module-script +// [FDaI] +// https://html.spec.whatwg.org/#fetch-the-descendants-of-and-instantiate-a-module-script +// [FFPE] +// https://html.spec.whatwg.org/#finding-the-first-parse-error +class CORE_EXPORT ModuleTreeLinker final : public SingleModuleClient { + public: + // https://html.spec.whatwg.org/#fetch-a-module-script-tree + static ModuleTreeLinker* Fetch(const ModuleScriptFetchRequest&, + Modulator*, + ModuleTreeLinkerRegistry*, + ModuleTreeClient*); + + // [FDaI] for an inline script. + static ModuleTreeLinker* FetchDescendantsForInlineScript( + ModuleScript*, + Modulator*, + ModuleTreeLinkerRegistry*, + ModuleTreeClient*); + + ~ModuleTreeLinker() override = default; + void Trace(blink::Visitor*) override; + void TraceWrappers(const ScriptWrappableVisitor*) const override; + + bool IsFetching() const { + return State::kFetchingSelf <= state_ && state_ < State::kFinished; + } + bool HasFinished() const { return state_ == State::kFinished; } + + private: + ModuleTreeLinker(Modulator*, ModuleTreeLinkerRegistry*, ModuleTreeClient*); + + enum class State { + kInitial, + // Running fetch of the module script corresponding to the target node. + kFetchingSelf, + // Running fetch of descendants of the target node. + kFetchingDependencies, + // Instantiating module_script_ and the node descendants. + kInstantiating, + kFinished, + }; +#if DCHECK_IS_ON() + static const char* StateToString(State); +#endif + void AdvanceState(State); + + void FetchRoot(const ModuleScriptFetchRequest&); + void FetchRootInline(ModuleScript*); + + // Steps 1--2 of [IMSGF]. + void InitiateInternalModuleScriptGraphFetching( + const ModuleScriptFetchRequest&, + ModuleGraphLevel); + + // Steps 3--7 of [IMSGF], and [FD]/[FDaI] called from [IMSGF]. + // TODO(hiroshige): Currently + void NotifyModuleLoadFinished(ModuleScript*) override; + void FetchDescendants(ModuleScript*); + + // Completion of [FD]. + void FinalizeFetchDescendantsForOneModuleScript(); + + // [FDaI] Steps 4--8. + void Instantiate(); + + // [FFPE] + ScriptValue FindFirstParseError(ModuleScript*, + HeapHashSet<Member<ModuleScript>>*) const; + + const Member<Modulator> modulator_; + HashSet<KURL> visited_set_; + const Member<ModuleTreeLinkerRegistry> registry_; + const Member<ModuleTreeClient> client_; + State state_ = State::kInitial; + + // Correspond to _result_ in + // https://html.spec.whatwg.org/multipage/webappapis.html#internal-module-script-graph-fetching-procedure + TraceWrapperMember<ModuleScript> result_; + + bool found_parse_error_ = false; + + size_t num_incomplete_fetches_ = 0; + +#if DCHECK_IS_ON() + KURL url_; + bool root_is_inline_; + + friend CORE_EXPORT std::ostream& operator<<(std::ostream&, + const ModuleTreeLinker&); +#endif +}; + +#if DCHECK_IS_ON() +CORE_EXPORT std::ostream& operator<<(std::ostream&, const ModuleTreeLinker&); +#endif + +} // namespace blink + +#endif diff --git a/chromium/third_party/blink/renderer/core/loader/modulescript/module_tree_linker_registry.cc b/chromium/third_party/blink/renderer/core/loader/modulescript/module_tree_linker_registry.cc new file mode 100644 index 00000000000..38e11d76e18 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/modulescript/module_tree_linker_registry.cc @@ -0,0 +1,54 @@ +// Copyright 2017 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/modulescript/module_tree_linker_registry.h" + +#include "third_party/blink/renderer/core/loader/modulescript/module_tree_linker.h" +#include "third_party/blink/renderer/platform/weborigin/kurl.h" +#include "third_party/blink/renderer/platform/weborigin/kurl_hash.h" + +namespace blink { + +void ModuleTreeLinkerRegistry::Trace(blink::Visitor* visitor) { + visitor->Trace(active_tree_linkers_); +} + +void ModuleTreeLinkerRegistry::TraceWrappers( + const ScriptWrappableVisitor* visitor) const { + for (const auto& member : active_tree_linkers_) + visitor->TraceWrappers(member); +} + +ModuleTreeLinker* ModuleTreeLinkerRegistry::Fetch( + const ModuleScriptFetchRequest& request, + Modulator* modulator, + ModuleTreeClient* client) { + ModuleTreeLinker* fetcher = + ModuleTreeLinker::Fetch(request, modulator, this, client); + DCHECK(fetcher->IsFetching()); + active_tree_linkers_.insert(fetcher); + return fetcher; +} + +ModuleTreeLinker* ModuleTreeLinkerRegistry::FetchDescendantsForInlineScript( + ModuleScript* module_script, + Modulator* modulator, + ModuleTreeClient* client) { + ModuleTreeLinker* fetcher = ModuleTreeLinker::FetchDescendantsForInlineScript( + module_script, modulator, this, client); + DCHECK(fetcher->IsFetching()); + active_tree_linkers_.insert(fetcher); + return fetcher; +} + +void ModuleTreeLinkerRegistry::ReleaseFinishedFetcher( + ModuleTreeLinker* fetcher) { + DCHECK(fetcher->HasFinished()); + + auto it = active_tree_linkers_.find(fetcher); + DCHECK_NE(it, active_tree_linkers_.end()); + active_tree_linkers_.erase(it); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/loader/modulescript/module_tree_linker_registry.h b/chromium/third_party/blink/renderer/core/loader/modulescript/module_tree_linker_registry.h new file mode 100644 index 00000000000..1f845491104 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/modulescript/module_tree_linker_registry.h @@ -0,0 +1,53 @@ +// Copyright 2017 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_MODULESCRIPT_MODULE_TREE_LINKER_REGISTRY_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_MODULESCRIPT_MODULE_TREE_LINKER_REGISTRY_H_ + +#include "third_party/blink/renderer/core/core_export.h" +#include "third_party/blink/renderer/platform/bindings/script_wrappable.h" +#include "third_party/blink/renderer/platform/bindings/trace_wrapper_member.h" +#include "third_party/blink/renderer/platform/heap/handle.h" + +namespace blink { + +class Modulator; +class ModuleScriptFetchRequest; +class ModuleTreeClient; +class ModuleTreeLinker; +class ModuleScript; + +// ModuleTreeLinkerRegistry keeps active ModuleTreeLinkers alive. +class CORE_EXPORT ModuleTreeLinkerRegistry + : public GarbageCollected<ModuleTreeLinkerRegistry>, + public TraceWrapperBase { + public: + static ModuleTreeLinkerRegistry* Create() { + return new ModuleTreeLinkerRegistry; + } + void Trace(blink::Visitor*); + void TraceWrappers(const ScriptWrappableVisitor*) const override; + const char* NameInHeapSnapshot() const override { + return "ModuleTreeLinkerRegistry"; + } + + ModuleTreeLinker* Fetch(const ModuleScriptFetchRequest&, + Modulator*, + ModuleTreeClient*); + ModuleTreeLinker* FetchDescendantsForInlineScript(ModuleScript*, + Modulator*, + ModuleTreeClient*); + + private: + ModuleTreeLinkerRegistry() = default; + + friend class ModuleTreeLinker; + void ReleaseFinishedFetcher(ModuleTreeLinker*); + + HeapHashSet<TraceWrapperMember<ModuleTreeLinker>> active_tree_linkers_; +}; + +} // namespace blink + +#endif diff --git a/chromium/third_party/blink/renderer/core/loader/modulescript/module_tree_linker_test.cc b/chromium/third_party/blink/renderer/core/loader/modulescript/module_tree_linker_test.cc new file mode 100644 index 00000000000..6d2103fef64 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/modulescript/module_tree_linker_test.cc @@ -0,0 +1,411 @@ +// Copyright 2017 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/modulescript/module_tree_linker.h" + +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/blink/public/platform/platform.h" +#include "third_party/blink/public/platform/scheduler/web_main_thread_scheduler.h" +#include "third_party/blink/renderer/bindings/core/v8/script_module.h" +#include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h" +#include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_testing.h" +#include "third_party/blink/renderer/core/loader/modulescript/module_script_fetch_request.h" +#include "third_party/blink/renderer/core/loader/modulescript/module_tree_linker_registry.h" +#include "third_party/blink/renderer/core/script/modulator.h" +#include "third_party/blink/renderer/core/script/module_script.h" +#include "third_party/blink/renderer/core/testing/dummy_modulator.h" +#include "third_party/blink/renderer/core/testing/page_test_base.h" +#include "third_party/blink/renderer/platform/bindings/script_state.h" +#include "third_party/blink/renderer/platform/bindings/v8_throw_exception.h" +#include "third_party/blink/renderer/platform/heap/handle.h" +#include "third_party/blink/renderer/platform/testing/unit_test_helpers.h" +#include "third_party/blink/renderer/platform/weborigin/kurl.h" +#include "third_party/blink/renderer/platform/wtf/text/string_builder.h" + +namespace blink { + +namespace { + +class TestModuleTreeClient final : public ModuleTreeClient { + public: + TestModuleTreeClient() = default; + + void Trace(blink::Visitor* visitor) override { + visitor->Trace(module_script_); + ModuleTreeClient::Trace(visitor); + } + + void NotifyModuleTreeLoadFinished(ModuleScript* module_script) override { + was_notify_finished_ = true; + module_script_ = module_script; + } + + bool WasNotifyFinished() const { return was_notify_finished_; } + ModuleScript* GetModuleScript() { return module_script_; } + + private: + bool was_notify_finished_ = false; + Member<ModuleScript> module_script_; +}; + +} // namespace + +class ModuleTreeLinkerTestModulator final : public DummyModulator { + public: + ModuleTreeLinkerTestModulator(scoped_refptr<ScriptState> script_state) + : script_state_(std::move(script_state)) {} + ~ModuleTreeLinkerTestModulator() override = default; + + void Trace(blink::Visitor*) override; + + enum class ResolveResult { kFailure, kSuccess }; + + // Resolve last |Modulator::FetchSingle()| call. + ModuleScript* ResolveSingleModuleScriptFetch( + const KURL& url, + const Vector<String>& dependency_module_specifiers, + bool parse_error = false) { + ScriptState::Scope scope(script_state_.get()); + + StringBuilder source_text; + Vector<ModuleRequest> dependency_module_requests; + dependency_module_requests.ReserveInitialCapacity( + dependency_module_specifiers.size()); + for (const auto& specifier : dependency_module_specifiers) { + dependency_module_requests.emplace_back(specifier, + TextPosition::MinimumPosition()); + source_text.Append("import '"); + source_text.Append(specifier); + source_text.Append("';\n"); + } + source_text.Append("export default 'grapes';"); + + ScriptModule script_module = ScriptModule::Compile( + script_state_->GetIsolate(), source_text.ToString(), url, url, + ScriptFetchOptions(), kSharableCrossOrigin, + TextPosition::MinimumPosition(), ASSERT_NO_EXCEPTION); + auto* module_script = ModuleScript::CreateForTest(this, script_module, url); + auto result_request = dependency_module_requests_map_.insert( + script_module, dependency_module_requests); + EXPECT_TRUE(result_request.is_new_entry); + auto result_map = module_map_.insert(url, module_script); + EXPECT_TRUE(result_map.is_new_entry); + + if (parse_error) { + v8::Local<v8::Value> error = V8ThrowException::CreateError( + script_state_->GetIsolate(), "Parse failure."); + module_script->SetParseErrorAndClearRecord( + ScriptValue(script_state_.get(), error)); + } + + EXPECT_TRUE(pending_clients_.Contains(url)); + pending_clients_.Take(url)->NotifyModuleLoadFinished(module_script); + + return module_script; + } + + void ResolveDependentTreeFetch(const KURL& url, ResolveResult result) { + ResolveSingleModuleScriptFetch(url, Vector<String>(), + result == ResolveResult::kFailure); + } + + void SetInstantiateShouldFail(bool b) { instantiate_should_fail_ = b; } + + bool HasInstantiated(ModuleScript* module_script) const { + return instantiated_records_.Contains(module_script->Record()); + } + + private: + // Implements Modulator: + + ReferrerPolicy GetReferrerPolicy() override { return kReferrerPolicyDefault; } + ScriptState* GetScriptState() override { return script_state_.get(); } + + void FetchSingle(const ModuleScriptFetchRequest& request, + ModuleGraphLevel, + SingleModuleClient* client) override { + EXPECT_FALSE(pending_clients_.Contains(request.Url())); + pending_clients_.Set(request.Url(), client); + } + + ModuleScript* GetFetchedModuleScript(const KURL& url) override { + const auto& it = module_map_.find(url); + if (it == module_map_.end()) + return nullptr; + + return it->value; + } + + ScriptValue InstantiateModule(ScriptModule record) override { + if (instantiate_should_fail_) { + ScriptState::Scope scope(script_state_.get()); + v8::Local<v8::Value> error = V8ThrowException::CreateError( + script_state_->GetIsolate(), "Instantiation failure."); + return ScriptValue(script_state_.get(), error); + } + instantiated_records_.insert(record); + return ScriptValue(); + } + + Vector<ModuleRequest> ModuleRequestsFromScriptModule( + ScriptModule script_module) override { + if (script_module.IsNull()) + return Vector<ModuleRequest>(); + + const auto& it = dependency_module_requests_map_.find(script_module); + if (it == dependency_module_requests_map_.end()) + return Vector<ModuleRequest>(); + + return it->value; + } + + scoped_refptr<ScriptState> script_state_; + HeapHashMap<KURL, Member<SingleModuleClient>> pending_clients_; + HashMap<ScriptModule, Vector<ModuleRequest>> dependency_module_requests_map_; + HeapHashMap<KURL, Member<ModuleScript>> module_map_; + HashSet<ScriptModule> instantiated_records_; + bool instantiate_should_fail_ = false; +}; + +void ModuleTreeLinkerTestModulator::Trace(blink::Visitor* visitor) { + visitor->Trace(pending_clients_); + visitor->Trace(module_map_); + DummyModulator::Trace(visitor); +} + +class ModuleTreeLinkerTest : public PageTestBase { + DISALLOW_COPY_AND_ASSIGN(ModuleTreeLinkerTest); + + public: + ModuleTreeLinkerTest() = default; + void SetUp() override; + + ModuleTreeLinkerTestModulator* GetModulator() { return modulator_.Get(); } + + protected: + Persistent<ModuleTreeLinkerTestModulator> modulator_; +}; + +void ModuleTreeLinkerTest::SetUp() { + PageTestBase::SetUp(IntSize(500, 500)); + scoped_refptr<ScriptState> script_state = + ToScriptStateForMainWorld(&GetFrame()); + modulator_ = new ModuleTreeLinkerTestModulator(script_state); +} + +TEST_F(ModuleTreeLinkerTest, FetchTreeNoDeps) { + ModuleTreeLinkerRegistry* registry = ModuleTreeLinkerRegistry::Create(); + + KURL url("http://example.com/root.js"); + ModuleScriptFetchRequest module_request(url, kReferrerPolicyDefault, + ScriptFetchOptions()); + TestModuleTreeClient* client = new TestModuleTreeClient; + registry->Fetch(module_request, GetModulator(), client); + + EXPECT_FALSE(client->WasNotifyFinished()) + << "ModuleTreeLinker should always finish asynchronously."; + EXPECT_FALSE(client->GetModuleScript()); + + GetModulator()->ResolveSingleModuleScriptFetch(url, {}); + EXPECT_TRUE(client->WasNotifyFinished()); + ASSERT_TRUE(client->GetModuleScript()); + EXPECT_TRUE(GetModulator()->HasInstantiated(client->GetModuleScript())); +} + +TEST_F(ModuleTreeLinkerTest, FetchTreeInstantiationFailure) { + GetModulator()->SetInstantiateShouldFail(true); + + ModuleTreeLinkerRegistry* registry = ModuleTreeLinkerRegistry::Create(); + + KURL url("http://example.com/root.js"); + ModuleScriptFetchRequest module_request(url, kReferrerPolicyDefault, + ScriptFetchOptions()); + TestModuleTreeClient* client = new TestModuleTreeClient; + registry->Fetch(module_request, GetModulator(), client); + + EXPECT_FALSE(client->WasNotifyFinished()) + << "ModuleTreeLinker should always finish asynchronously."; + EXPECT_FALSE(client->GetModuleScript()); + + GetModulator()->ResolveSingleModuleScriptFetch(url, {}); + + // Modulator::InstantiateModule() fails here, as + // we SetInstantiateShouldFail(true) earlier. + + EXPECT_TRUE(client->WasNotifyFinished()); + ASSERT_TRUE(client->GetModuleScript()); + EXPECT_TRUE(client->GetModuleScript()->HasErrorToRethrow()) + << "Expected errored module script but got " + << *client->GetModuleScript(); +} + +TEST_F(ModuleTreeLinkerTest, FetchTreeWithSingleDependency) { + ModuleTreeLinkerRegistry* registry = ModuleTreeLinkerRegistry::Create(); + + KURL url("http://example.com/root.js"); + ModuleScriptFetchRequest module_request(url, kReferrerPolicyDefault, + ScriptFetchOptions()); + TestModuleTreeClient* client = new TestModuleTreeClient; + registry->Fetch(module_request, GetModulator(), client); + + EXPECT_FALSE(client->WasNotifyFinished()) + << "ModuleTreeLinker should always finish asynchronously."; + EXPECT_FALSE(client->GetModuleScript()); + + GetModulator()->ResolveSingleModuleScriptFetch(url, {"./dep1.js"}); + EXPECT_FALSE(client->WasNotifyFinished()); + + KURL url_dep1("http://example.com/dep1.js"); + + GetModulator()->ResolveDependentTreeFetch( + url_dep1, ModuleTreeLinkerTestModulator::ResolveResult::kSuccess); + EXPECT_TRUE(client->WasNotifyFinished()); + + ASSERT_TRUE(client->GetModuleScript()); + EXPECT_TRUE(GetModulator()->HasInstantiated(client->GetModuleScript())); +} + +TEST_F(ModuleTreeLinkerTest, FetchTreeWith3Deps) { + ModuleTreeLinkerRegistry* registry = ModuleTreeLinkerRegistry::Create(); + + KURL url("http://example.com/root.js"); + ModuleScriptFetchRequest module_request(url, kReferrerPolicyDefault, + ScriptFetchOptions()); + TestModuleTreeClient* client = new TestModuleTreeClient; + registry->Fetch(module_request, GetModulator(), client); + + EXPECT_FALSE(client->WasNotifyFinished()) + << "ModuleTreeLinker should always finish asynchronously."; + EXPECT_FALSE(client->GetModuleScript()); + + GetModulator()->ResolveSingleModuleScriptFetch( + url, {"./dep1.js", "./dep2.js", "./dep3.js"}); + EXPECT_FALSE(client->WasNotifyFinished()); + + Vector<KURL> url_deps; + for (int i = 1; i <= 3; ++i) { + StringBuilder url_dep_str; + url_dep_str.Append("http://example.com/dep"); + url_dep_str.AppendNumber(i); + url_dep_str.Append(".js"); + + KURL url_dep(url_dep_str.ToString()); + url_deps.push_back(url_dep); + } + + for (const auto& url_dep : url_deps) { + EXPECT_FALSE(client->WasNotifyFinished()); + GetModulator()->ResolveDependentTreeFetch( + url_dep, ModuleTreeLinkerTestModulator::ResolveResult::kSuccess); + } + + EXPECT_TRUE(client->WasNotifyFinished()); + ASSERT_TRUE(client->GetModuleScript()); + EXPECT_TRUE(GetModulator()->HasInstantiated(client->GetModuleScript())); +} + +TEST_F(ModuleTreeLinkerTest, FetchTreeWith3Deps1Fail) { + ModuleTreeLinkerRegistry* registry = ModuleTreeLinkerRegistry::Create(); + + KURL url("http://example.com/root.js"); + ModuleScriptFetchRequest module_request(url, kReferrerPolicyDefault, + ScriptFetchOptions()); + TestModuleTreeClient* client = new TestModuleTreeClient; + registry->Fetch(module_request, GetModulator(), client); + + EXPECT_FALSE(client->WasNotifyFinished()) + << "ModuleTreeLinker should always finish asynchronously."; + EXPECT_FALSE(client->GetModuleScript()); + + GetModulator()->ResolveSingleModuleScriptFetch( + url, {"./dep1.js", "./dep2.js", "./dep3.js"}); + EXPECT_FALSE(client->WasNotifyFinished()); + + Vector<KURL> url_deps; + for (int i = 1; i <= 3; ++i) { + StringBuilder url_dep_str; + url_dep_str.Append("http://example.com/dep"); + url_dep_str.AppendNumber(i); + url_dep_str.Append(".js"); + + KURL url_dep(url_dep_str.ToString()); + url_deps.push_back(url_dep); + } + + for (const auto& url_dep : url_deps) { + SCOPED_TRACE(url_dep.GetString()); + } + + auto url_dep = url_deps.back(); + url_deps.pop_back(); + GetModulator()->ResolveDependentTreeFetch( + url_dep, ModuleTreeLinkerTestModulator::ResolveResult::kSuccess); + EXPECT_FALSE(client->WasNotifyFinished()); + url_dep = url_deps.back(); + url_deps.pop_back(); + GetModulator()->ResolveDependentTreeFetch( + url_dep, ModuleTreeLinkerTestModulator::ResolveResult::kFailure); + + // TODO(kouhei): This may not hold once we implement early failure reporting. + EXPECT_FALSE(client->WasNotifyFinished()); + + // Check below doesn't crash. + url_dep = url_deps.back(); + url_deps.pop_back(); + GetModulator()->ResolveDependentTreeFetch( + url_dep, ModuleTreeLinkerTestModulator::ResolveResult::kSuccess); + EXPECT_TRUE(url_deps.IsEmpty()); + + EXPECT_TRUE(client->WasNotifyFinished()); + ASSERT_TRUE(client->GetModuleScript()); + EXPECT_FALSE(client->GetModuleScript()->HasParseError()); + EXPECT_TRUE(client->GetModuleScript()->HasErrorToRethrow()); +} + +TEST_F(ModuleTreeLinkerTest, FetchDependencyTree) { + ModuleTreeLinkerRegistry* registry = ModuleTreeLinkerRegistry::Create(); + + KURL url("http://example.com/depth1.js"); + ModuleScriptFetchRequest module_request(url, kReferrerPolicyDefault, + ScriptFetchOptions()); + TestModuleTreeClient* client = new TestModuleTreeClient; + registry->Fetch(module_request, GetModulator(), client); + + EXPECT_FALSE(client->WasNotifyFinished()) + << "ModuleTreeLinker should always finish asynchronously."; + EXPECT_FALSE(client->GetModuleScript()); + + GetModulator()->ResolveSingleModuleScriptFetch(url, {"./depth2.js"}); + + KURL url_dep2("http://example.com/depth2.js"); + + GetModulator()->ResolveDependentTreeFetch( + url_dep2, ModuleTreeLinkerTestModulator::ResolveResult::kSuccess); + + EXPECT_TRUE(client->WasNotifyFinished()); + ASSERT_TRUE(client->GetModuleScript()); + EXPECT_TRUE(GetModulator()->HasInstantiated(client->GetModuleScript())); +} + +TEST_F(ModuleTreeLinkerTest, FetchDependencyOfCyclicGraph) { + ModuleTreeLinkerRegistry* registry = ModuleTreeLinkerRegistry::Create(); + + KURL url("http://example.com/a.js"); + ModuleScriptFetchRequest module_request(url, kReferrerPolicyDefault, + ScriptFetchOptions()); + TestModuleTreeClient* client = new TestModuleTreeClient; + registry->Fetch(module_request, GetModulator(), client); + + EXPECT_FALSE(client->WasNotifyFinished()) + << "ModuleTreeLinker should always finish asynchronously."; + EXPECT_FALSE(client->GetModuleScript()); + + GetModulator()->ResolveSingleModuleScriptFetch(url, {"./a.js"}); + + EXPECT_TRUE(client->WasNotifyFinished()); + ASSERT_TRUE(client->GetModuleScript()); + EXPECT_TRUE(GetModulator()->HasInstantiated(client->GetModuleScript())); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/loader/modulescript/worker_or_worklet_module_script_fetcher.cc b/chromium/third_party/blink/renderer/core/loader/modulescript/worker_or_worklet_module_script_fetcher.cc new file mode 100644 index 00000000000..5cff4b1b293 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/modulescript/worker_or_worklet_module_script_fetcher.cc @@ -0,0 +1,46 @@ +// Copyright 2017 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/modulescript/worker_or_worklet_module_script_fetcher.h" + +#include "third_party/blink/renderer/platform/cross_thread_functional.h" + +namespace blink { + +WorkerOrWorkletModuleScriptFetcher::WorkerOrWorkletModuleScriptFetcher( + WorkerOrWorkletModuleFetchCoordinatorProxy* coordinator_proxy) + : coordinator_proxy_(coordinator_proxy) { + DCHECK(coordinator_proxy_); +} + +void WorkerOrWorkletModuleScriptFetcher::Trace(blink::Visitor* visitor) { + visitor->Trace(coordinator_proxy_); + ModuleScriptFetcher::Trace(visitor); +} + +void WorkerOrWorkletModuleScriptFetcher::Fetch( + FetchParameters& fetch_params, + ModuleScriptFetcher::Client* client) { + SetClient(client); + coordinator_proxy_->Fetch(fetch_params, this); +} + +void WorkerOrWorkletModuleScriptFetcher::OnFetched( + const ModuleScriptCreationParams& params) { + HeapVector<Member<ConsoleMessage>> error_messages; + Finalize(params, error_messages); +} + +void WorkerOrWorkletModuleScriptFetcher::OnFailed() { + HeapVector<Member<ConsoleMessage>> error_messages; + Finalize(WTF::nullopt, error_messages); +} + +void WorkerOrWorkletModuleScriptFetcher::Finalize( + const WTF::Optional<ModuleScriptCreationParams>& params, + const HeapVector<Member<ConsoleMessage>>& error_messages) { + NotifyFetchFinished(params, error_messages); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/loader/modulescript/worker_or_worklet_module_script_fetcher.h b/chromium/third_party/blink/renderer/core/loader/modulescript/worker_or_worklet_module_script_fetcher.h new file mode 100644 index 00000000000..9c4e6fdd806 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/modulescript/worker_or_worklet_module_script_fetcher.h @@ -0,0 +1,45 @@ +// Copyright 2017 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_MODULESCRIPT_WORKER_OR_WORKLET_MODULE_SCRIPT_FETCHER_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_MODULESCRIPT_WORKER_OR_WORKLET_MODULE_SCRIPT_FETCHER_H_ + +#include "third_party/blink/renderer/core/loader/modulescript/module_script_fetcher.h" +#include "third_party/blink/renderer/core/workers/worker_or_worklet_module_fetch_coordinator.h" +#include "third_party/blink/renderer/core/workers/worker_or_worklet_module_fetch_coordinator_proxy.h" +#include "third_party/blink/renderer/platform/wtf/optional.h" + +namespace blink { + +// WorkerOrWorkletModuleScriptFetcher does not initiate module fetch by itself. +// Instead, this delegates it to WorkerOrWorkletModuleFetchCoordinator on the +// main thread via WorkerOrWorkletModuleFetchCoordinatorProxy. +class CORE_EXPORT WorkerOrWorkletModuleScriptFetcher final + : public ModuleScriptFetcher, + public WorkerOrWorkletModuleFetchCoordinator::Client { + USING_GARBAGE_COLLECTED_MIXIN(WorkerOrWorkletModuleScriptFetcher); + + public: + explicit WorkerOrWorkletModuleScriptFetcher( + WorkerOrWorkletModuleFetchCoordinatorProxy*); + + // Implements ModuleScriptFetcher. + void Fetch(FetchParameters&, ModuleScriptFetcher::Client*) override; + + // Implements WorkerOrWorkletModuleFetchCoordinator::Client. + void OnFetched(const ModuleScriptCreationParams&) override; + void OnFailed() override; + + void Trace(blink::Visitor*) override; + + private: + void Finalize(const WTF::Optional<ModuleScriptCreationParams>&, + const HeapVector<Member<ConsoleMessage>>& error_messages); + + Member<WorkerOrWorkletModuleFetchCoordinatorProxy> coordinator_proxy_; +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_MODULESCRIPT_WORKER_OR_WORKLET_MODULE_SCRIPT_FETCHER_H_ diff --git a/chromium/third_party/blink/renderer/core/loader/navigation_policy.cc b/chromium/third_party/blink/renderer/core/loader/navigation_policy.cc new file mode 100644 index 00000000000..7d9f4fc7198 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/navigation_policy.cc @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2009 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/navigation_policy.h" + +#include "build/build_config.h" +#include "third_party/blink/public/web/web_navigation_policy.h" +#include "third_party/blink/renderer/platform/wtf/assertions.h" + +namespace blink { + +bool NavigationPolicyFromMouseEvent(unsigned short button, + bool ctrl, + bool shift, + bool alt, + bool meta, + NavigationPolicy* policy) { +#if defined(OS_MACOSX) + const bool new_tab_modifier = (button == 1) || meta; +#else + const bool new_tab_modifier = (button == 1) || ctrl; +#endif + if (!new_tab_modifier && !shift && !alt) + return false; + + DCHECK(policy); + if (new_tab_modifier) { + if (shift) + *policy = kNavigationPolicyNewForegroundTab; + else + *policy = kNavigationPolicyNewBackgroundTab; + } else { + if (shift) + *policy = kNavigationPolicyNewWindow; + else + *policy = kNavigationPolicyDownload; + } + return true; +} + +STATIC_ASSERT_ENUM(kWebNavigationPolicyIgnore, kNavigationPolicyIgnore); +STATIC_ASSERT_ENUM(kWebNavigationPolicyDownload, kNavigationPolicyDownload); +STATIC_ASSERT_ENUM(kWebNavigationPolicyCurrentTab, kNavigationPolicyCurrentTab); +STATIC_ASSERT_ENUM(kWebNavigationPolicyNewBackgroundTab, + kNavigationPolicyNewBackgroundTab); +STATIC_ASSERT_ENUM(kWebNavigationPolicyNewForegroundTab, + kNavigationPolicyNewForegroundTab); +STATIC_ASSERT_ENUM(kWebNavigationPolicyNewWindow, kNavigationPolicyNewWindow); +STATIC_ASSERT_ENUM(kWebNavigationPolicyNewPopup, kNavigationPolicyNewPopup); + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/loader/navigation_policy.h b/chromium/third_party/blink/renderer/core/loader/navigation_policy.h new file mode 100644 index 00000000000..7aa9973dcdf --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/navigation_policy.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2009 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. + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_NAVIGATION_POLICY_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_NAVIGATION_POLICY_H_ + +#include "third_party/blink/renderer/core/core_export.h" + +namespace blink { + +enum NavigationPolicy { + kNavigationPolicyIgnore, + kNavigationPolicyDownload, + kNavigationPolicyCurrentTab, + kNavigationPolicyNewBackgroundTab, + kNavigationPolicyNewForegroundTab, + kNavigationPolicyNewWindow, + kNavigationPolicyNewPopup, + kNavigationPolicyHandledByClient, + kNavigationPolicyHandledByClientForInitialHistory, +}; + +CORE_EXPORT bool NavigationPolicyFromMouseEvent(unsigned short button, + bool ctrl, + bool shift, + bool alt, + bool meta, + NavigationPolicy*); + +} // namespace blink + +#endif diff --git a/chromium/third_party/blink/renderer/core/loader/navigation_scheduler.cc b/chromium/third_party/blink/renderer/core/loader/navigation_scheduler.cc new file mode 100644 index 00000000000..6f12e9c781c --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/navigation_scheduler.cc @@ -0,0 +1,582 @@ +/* + * Copyright (C) 2006, 2007, 2008, 2009, 2010 Apple Inc. All rights reserved. + * Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies) + * Copyright (C) 2008, 2009 Torch Mobile Inc. All rights reserved. + * (http://www.torchmobile.com/) + * Copyright (C) 2009 Adam Barth. 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") 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 APPLE AND ITS 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 APPLE OR ITS 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/navigation_scheduler.h" + +#include <memory> +#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/renderer/bindings/core/v8/script_controller.h" +#include "third_party/blink/renderer/core/dom/events/event.h" +#include "third_party/blink/renderer/core/dom/user_gesture_indicator.h" +#include "third_party/blink/renderer/core/fileapi/public_url_manager.h" +#include "third_party/blink/renderer/core/frame/csp/content_security_policy.h" +#include "third_party/blink/renderer/core/frame/deprecation.h" +#include "third_party/blink/renderer/core/frame/local_frame.h" +#include "third_party/blink/renderer/core/frame/local_frame_client.h" +#include "third_party/blink/renderer/core/html/forms/html_form_element.h" +#include "third_party/blink/renderer/core/loader/document_load_timing.h" +#include "third_party/blink/renderer/core/loader/document_loader.h" +#include "third_party/blink/renderer/core/loader/form_submission.h" +#include "third_party/blink/renderer/core/loader/frame_load_request.h" +#include "third_party/blink/renderer/core/loader/frame_loader.h" +#include "third_party/blink/renderer/core/loader/frame_loader_state_machine.h" +#include "third_party/blink/renderer/core/loader/scheduled_navigation.h" +#include "third_party/blink/renderer/core/page/page.h" +#include "third_party/blink/renderer/core/probe/core_probes.h" +#include "third_party/blink/renderer/platform/histogram.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource_loader_options.h" +#include "third_party/blink/renderer/platform/scheduler/child/web_scheduler.h" +#include "third_party/blink/renderer/platform/shared_buffer.h" +#include "third_party/blink/renderer/platform/wtf/time.h" + +namespace blink { + +namespace { + +// Add new scheduled navigation types before ScheduledLastEntry +enum ScheduledNavigationType { + kScheduledReload, + kScheduledFormSubmission, + kScheduledURLNavigation, + kScheduledRedirect, + kScheduledFrameNavigation, + kScheduledPageBlock, + + kScheduledLastEntry +}; + +// If the current frame has a provisional document loader, a scheduled +// navigation might abort that load. Log those occurrences until +// crbug.com/557430 is resolved. +void MaybeLogScheduledNavigationClobber(ScheduledNavigationType type, + LocalFrame* frame) { + if (!frame->Loader().GetProvisionalDocumentLoader()) + return; + // Include enumeration values userGesture variants. + DEFINE_STATIC_LOCAL(EnumerationHistogram, + scheduled_navigation_clobber_histogram, + ("Navigation.Scheduled.MaybeCausedAbort", + ScheduledNavigationType::kScheduledLastEntry * 2)); + + int value = Frame::HasTransientUserActivation(frame) + ? type + kScheduledLastEntry + : type; + scheduled_navigation_clobber_histogram.Count(value); + + DEFINE_STATIC_LOCAL( + CustomCountHistogram, scheduled_clobber_abort_time_histogram, + ("Navigation.Scheduled.MaybeCausedAbort.Time", 1, 10000, 50)); + TimeTicks navigation_start = frame->Loader() + .GetProvisionalDocumentLoader() + ->GetTiming() + .NavigationStart(); + if (!navigation_start.is_null()) { + scheduled_clobber_abort_time_histogram.Count( + (CurrentTimeTicks() - navigation_start).InSecondsF()); + } +} + +} // namespace + +unsigned NavigationDisablerForBeforeUnload::navigation_disable_count_ = 0; + +class ScheduledURLNavigation : public ScheduledNavigation { + protected: + ScheduledURLNavigation(Reason reason, + double delay, + Document* origin_document, + const KURL& url, + bool replaces_current_item, + bool is_location_change) + : ScheduledNavigation(reason, + delay, + origin_document, + replaces_current_item, + is_location_change), + url_(url), + should_check_main_world_content_security_policy_( + kCheckContentSecurityPolicy) { + if (ContentSecurityPolicy::ShouldBypassMainWorld(origin_document)) { + should_check_main_world_content_security_policy_ = + kDoNotCheckContentSecurityPolicy; + } + + if (origin_document && url.ProtocolIs("blob") && + RuntimeEnabledFeatures::MojoBlobURLsEnabled()) { + origin_document->GetPublicURLManager().Resolve( + url_, MakeRequest(&blob_url_token_)); + } + } + + void Fire(LocalFrame* frame) override { + std::unique_ptr<UserGestureIndicator> gesture_indicator = + CreateUserGestureIndicator(); + FrameLoadRequest request(OriginDocument(), ResourceRequest(url_), "_self", + should_check_main_world_content_security_policy_); + request.SetReplacesCurrentItem(ReplacesCurrentItem()); + request.SetClientRedirect(ClientRedirectPolicy::kClientRedirect); + + if (blob_url_token_) { + mojom::blink::BlobURLTokenPtr token_clone; + blob_url_token_->Clone(MakeRequest(&token_clone)); + request.SetBlobURLToken(std::move(token_clone)); + } + + ScheduledNavigationType type = + IsLocationChange() ? ScheduledNavigationType::kScheduledFrameNavigation + : ScheduledNavigationType::kScheduledURLNavigation; + MaybeLogScheduledNavigationClobber(type, frame); + frame->Loader().Load(request); + } + + KURL Url() const override { return url_; } + + private: + KURL url_; + mojom::blink::BlobURLTokenPtr blob_url_token_; + ContentSecurityPolicyDisposition + should_check_main_world_content_security_policy_; +}; + +class ScheduledRedirect final : public ScheduledURLNavigation { + public: + static ScheduledRedirect* Create(double delay, + Document* origin_document, + const KURL& url, + Document::HttpRefreshType http_refresh_type, + bool replaces_current_item) { + return new ScheduledRedirect(delay, origin_document, url, http_refresh_type, + replaces_current_item); + } + + bool ShouldStartTimer(LocalFrame* frame) override { + return frame->GetDocument()->LoadEventFinished(); + } + + void Fire(LocalFrame* frame) override { + std::unique_ptr<UserGestureIndicator> gesture_indicator = + CreateUserGestureIndicator(); + FrameLoadRequest request(OriginDocument(), ResourceRequest(Url()), "_self"); + request.SetReplacesCurrentItem(ReplacesCurrentItem()); + if (EqualIgnoringFragmentIdentifier(frame->GetDocument()->Url(), + request.GetResourceRequest().Url())) { + request.GetResourceRequest().SetCacheMode( + mojom::FetchCacheMode::kValidateCache); + } + request.SetClientRedirect(ClientRedirectPolicy::kClientRedirect); + MaybeLogScheduledNavigationClobber( + ScheduledNavigationType::kScheduledRedirect, frame); + frame->Loader().Load(request); + } + + private: + static Reason ToReason(Document::HttpRefreshType http_refresh_type) { + switch (http_refresh_type) { + case Document::HttpRefreshType::kHttpRefreshFromHeader: + return Reason::kHttpHeaderRefresh; + case Document::HttpRefreshType::kHttpRefreshFromMetaTag: + return Reason::kMetaTagRefresh; + default: + break; + } + NOTREACHED(); + return Reason::kMetaTagRefresh; + } + + ScheduledRedirect(double delay, + Document* origin_document, + const KURL& url, + Document::HttpRefreshType http_refresh_type, + bool replaces_current_item) + : ScheduledURLNavigation(ToReason(http_refresh_type), + delay, + origin_document, + url, + replaces_current_item, + false) { + ClearUserGesture(); + } +}; + +class ScheduledFrameNavigation final : public ScheduledURLNavigation { + public: + static ScheduledFrameNavigation* Create(Document* origin_document, + const KURL& url, + bool replaces_current_item) { + return new ScheduledFrameNavigation(origin_document, url, + replaces_current_item); + } + + private: + ScheduledFrameNavigation(Document* origin_document, + const KURL& url, + bool replaces_current_item) + : ScheduledURLNavigation(Reason::kFrameNavigation, + 0.0, + origin_document, + url, + replaces_current_item, + !url.ProtocolIsJavaScript()) {} +}; + +class ScheduledReload final : public ScheduledNavigation { + public: + static ScheduledReload* Create(LocalFrame* frame) { + return new ScheduledReload(frame); + } + + void Fire(LocalFrame* frame) override { + std::unique_ptr<UserGestureIndicator> gesture_indicator = + CreateUserGestureIndicator(); + ResourceRequest resource_request = frame->Loader().ResourceRequestForReload( + kFrameLoadTypeReload, KURL(), ClientRedirectPolicy::kClientRedirect); + if (resource_request.IsNull()) + return; + FrameLoadRequest request = FrameLoadRequest(nullptr, resource_request); + request.SetClientRedirect(ClientRedirectPolicy::kClientRedirect); + MaybeLogScheduledNavigationClobber( + ScheduledNavigationType::kScheduledReload, frame); + frame->Loader().Load(request, kFrameLoadTypeReload); + } + + KURL Url() const override { return frame_->GetDocument()->Url(); } + + void Trace(blink::Visitor* visitor) override { + visitor->Trace(frame_); + ScheduledNavigation::Trace(visitor); + } + + private: + explicit ScheduledReload(LocalFrame* frame) + : ScheduledNavigation(Reason::kReload, + 0.0, + nullptr /*origin_document */, + true, + true), + frame_(frame) { + DCHECK(frame->GetDocument()); + } + + Member<LocalFrame> frame_; +}; + +class ScheduledPageBlock final : public ScheduledNavigation { + public: + static ScheduledPageBlock* Create(Document* origin_document, int reason) { + return new ScheduledPageBlock(origin_document, reason); + } + + void Fire(LocalFrame* frame) override { + frame->Client()->LoadErrorPage(reason_); + } + + KURL Url() const override { return KURL(); } + + private: + ScheduledPageBlock(Document* origin_document, int reason) + : ScheduledNavigation(Reason::kPageBlock, + 0.0, + origin_document, + true, + true), + reason_(reason) {} + + int reason_; +}; + +class ScheduledFormSubmission final : public ScheduledNavigation { + public: + static ScheduledFormSubmission* Create(Document* document, + FormSubmission* submission, + bool replaces_current_item) { + return new ScheduledFormSubmission(document, submission, + replaces_current_item); + } + + void Fire(LocalFrame* frame) override { + std::unique_ptr<UserGestureIndicator> gesture_indicator = + CreateUserGestureIndicator(); + FrameLoadRequest frame_request = + submission_->CreateFrameLoadRequest(OriginDocument()); + frame_request.SetReplacesCurrentItem(ReplacesCurrentItem()); + MaybeLogScheduledNavigationClobber( + ScheduledNavigationType::kScheduledFormSubmission, frame); + frame->Loader().Load(frame_request); + } + + KURL Url() const override { return submission_->RequestURL(); } + + void Trace(blink::Visitor* visitor) override { + visitor->Trace(submission_); + ScheduledNavigation::Trace(visitor); + } + + private: + ScheduledFormSubmission(Document* document, + FormSubmission* submission, + bool replaces_current_item) + : ScheduledNavigation(submission->Method() == FormSubmission::kGetMethod + ? Reason::kFormSubmissionGet + : Reason::kFormSubmissionPost, + 0, + document, + replaces_current_item, + true), + submission_(submission) { + DCHECK_NE(submission->Method(), FormSubmission::kDialogMethod); + DCHECK(submission_->Form()); + } + + Member<FormSubmission> submission_; +}; + +NavigationScheduler::NavigationScheduler(LocalFrame* frame) + : frame_(frame), + frame_type_(frame_->IsMainFrame() + ? scheduler::WebMainThreadScheduler::NavigatingFrameType:: + kMainFrame + : scheduler::WebMainThreadScheduler::NavigatingFrameType:: + kChildFrame) {} + +NavigationScheduler::~NavigationScheduler() { + if (navigate_task_handle_.IsActive()) { + Platform::Current()->CurrentThread()->Scheduler()->RemovePendingNavigation( + frame_type_); + } +} + +bool NavigationScheduler::LocationChangePending() { + return redirect_ && redirect_->IsLocationChange(); +} + +bool NavigationScheduler::IsNavigationScheduledWithin(double interval) const { + return redirect_ && redirect_->Delay() <= interval; +} + +// TODO(dcheng): There are really two different load blocking concepts at work +// here and they have been incorrectly tangled together. +// +// 1. NavigationDisablerForBeforeUnload is for blocking navigation scheduling +// during a beforeunload events. Scheduled navigations during beforeunload +// would make it possible to get trapped in an endless loop of beforeunload +// dialogs. +// +// Checking Frame::isNavigationAllowed() doesn't make sense in this context: +// NavigationScheduler is always cleared when a new load commits, so it's +// impossible for a scheduled navigation to clobber a navigation that just +// committed. +// +// 2. FrameNavigationDisabler / LocalFrame::isNavigationAllowed() are intended +// to prevent Documents from being reattached during destruction, since it +// can cause bugs with security origin confusion. This is primarily intended +// to block /synchronous/ navigations during things lke +// Document::detachLayoutTree(). +inline bool NavigationScheduler::ShouldScheduleReload() const { + return frame_->GetPage() && frame_->IsNavigationAllowed() && + NavigationDisablerForBeforeUnload::IsNavigationAllowed(); +} + +inline bool NavigationScheduler::ShouldScheduleNavigation( + const KURL& url) const { + return frame_->GetPage() && frame_->IsNavigationAllowed() && + (url.ProtocolIsJavaScript() || + NavigationDisablerForBeforeUnload::IsNavigationAllowed()); +} + +void NavigationScheduler::ScheduleRedirect( + double delay, + const KURL& url, + Document::HttpRefreshType http_refresh_type) { + if (!ShouldScheduleNavigation(url)) + return; + if (delay < 0 || delay > INT_MAX / 1000) + return; + if (url.IsEmpty()) + return; + + // We want a new back/forward list item if the refresh timeout is > 1 second. + if (!redirect_ || delay <= redirect_->Delay()) { + Schedule(ScheduledRedirect::Create(delay, frame_->GetDocument(), url, + http_refresh_type, delay <= 1)); + } +} + +bool NavigationScheduler::MustReplaceCurrentItem(LocalFrame* target_frame) { + // Non-user navigation before the page has finished firing onload should not + // create a new back/forward item. See https://webkit.org/b/42861 for the + // original motivation for this. + if (!target_frame->GetDocument()->LoadEventFinished() && + !Frame::HasTransientUserActivation(target_frame)) + return true; + + // Navigation of a subframe during loading of an ancestor frame does not + // create a new back/forward item. The definition of "during load" is any time + // before all handlers for the load event have been run. See + // https://bugs.webkit.org/show_bug.cgi?id=14957 for the original motivation + // for this. + Frame* parent_frame = target_frame->Tree().Parent(); + return parent_frame && parent_frame->IsLocalFrame() && + !ToLocalFrame(parent_frame)->Loader().AllAncestorsAreComplete(); +} + +void NavigationScheduler::ScheduleFrameNavigation(Document* origin_document, + const KURL& url, + bool replaces_current_item) { + if (!ShouldScheduleNavigation(url)) + return; + + replaces_current_item = + replaces_current_item || MustReplaceCurrentItem(frame_); + + // If the URL we're going to navigate to is the same as the current one, + // except for the fragment part, we don't need to schedule the location + // change. We'll skip this optimization for cross-origin navigations to + // minimize the navigator's ability to execute timing attacks. + if (origin_document->GetSecurityOrigin()->CanAccess( + frame_->GetDocument()->GetSecurityOrigin())) { + if (url.HasFragmentIdentifier() && + EqualIgnoringFragmentIdentifier(frame_->GetDocument()->Url(), url)) { + FrameLoadRequest request(origin_document, ResourceRequest(url), "_self"); + request.SetReplacesCurrentItem(replaces_current_item); + if (replaces_current_item) + request.SetClientRedirect(ClientRedirectPolicy::kClientRedirect); + frame_->Loader().Load(request); + return; + } + } + + Schedule(ScheduledFrameNavigation::Create(origin_document, url, + replaces_current_item)); +} + +void NavigationScheduler::SchedulePageBlock(Document* origin_document, + int reason) { + DCHECK(frame_->GetPage()); + Schedule(ScheduledPageBlock::Create(origin_document, reason)); +} + +void NavigationScheduler::ScheduleFormSubmission(Document* document, + FormSubmission* submission) { + DCHECK(frame_->GetPage()); + Schedule(ScheduledFormSubmission::Create(document, submission, + MustReplaceCurrentItem(frame_))); +} + +void NavigationScheduler::ScheduleReload() { + if (!ShouldScheduleReload()) + return; + if (frame_->GetDocument()->Url().IsEmpty()) + return; + Schedule(ScheduledReload::Create(frame_)); +} + +void NavigationScheduler::NavigateTask() { + Platform::Current()->CurrentThread()->Scheduler()->RemovePendingNavigation( + frame_type_); + + if (!frame_->GetPage()) + return; + if (frame_->GetPage()->Paused()) { + probe::frameClearedScheduledNavigation(frame_); + return; + } + + ScheduledNavigation* redirect(redirect_.Release()); + redirect->Fire(frame_); + probe::frameClearedScheduledNavigation(frame_); +} + +void NavigationScheduler::Schedule(ScheduledNavigation* redirect) { + DCHECK(frame_->GetPage()); + + // In a back/forward navigation, we sometimes restore history state to + // iframes, even though the state was generated dynamically and JS will try to + // put something different in the iframe. In this case, we will load stale + // things and/or confuse the JS when it shortly thereafter tries to schedule a + // location change. Let the JS have its way. + // FIXME: This check seems out of place. + if (!frame_->Loader().StateMachine()->CommittedFirstRealDocumentLoad() && + frame_->Loader().GetProvisionalDocumentLoader() && + frame_->Loader().GetProvisionalDocumentLoader()->DidStart()) { + frame_->Loader().StopAllLoaders(); + if (!frame_->GetPage()) + return; + } + + Cancel(); + redirect_ = redirect; + if (redirect_->IsLocationChange()) + frame_->GetDocument()->SuppressLoadEvent(); + StartTimer(); +} + +void NavigationScheduler::StartTimer() { + if (!redirect_) + return; + + DCHECK(frame_->GetPage()); + if (navigate_task_handle_.IsActive()) + return; + if (!redirect_->ShouldStartTimer(frame_)) + return; + + WebScheduler* scheduler = Platform::Current()->CurrentThread()->Scheduler(); + scheduler->AddPendingNavigation(frame_type_); + + // wrapWeakPersistent(this) is safe because a posted task is canceled when the + // task handle is destroyed on the dtor of this NavigationScheduler. + navigate_task_handle_ = PostDelayedCancellableTask( + *frame_->GetFrameScheduler()->GetTaskRunner(TaskType::kInternalLoading), + FROM_HERE, + WTF::Bind(&NavigationScheduler::NavigateTask, WrapWeakPersistent(this)), + TimeDelta::FromSecondsD(redirect_->Delay())); + + probe::frameScheduledNavigation(frame_, redirect_.Get()); +} + +void NavigationScheduler::Cancel() { + if (navigate_task_handle_.IsActive()) { + Platform::Current()->CurrentThread()->Scheduler()->RemovePendingNavigation( + frame_type_); + probe::frameClearedScheduledNavigation(frame_); + } + navigate_task_handle_.Cancel(); + redirect_.Clear(); +} + +void NavigationScheduler::Trace(blink::Visitor* visitor) { + visitor->Trace(frame_); + visitor->Trace(redirect_); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/loader/navigation_scheduler.h b/chromium/third_party/blink/renderer/core/loader/navigation_scheduler.h new file mode 100644 index 00000000000..bb2c6e92c0a --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/navigation_scheduler.h @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2006, 2007, 2008, 2009 Apple Inc. All rights reserved. + * Copyright (C) 2008, 2009 Torch Mobile Inc. All rights reserved. + * (http://www.torchmobile.com/) + * Copyright (C) 2009 Adam Barth. 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") 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 APPLE AND ITS 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 APPLE OR ITS 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_NAVIGATION_SCHEDULER_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_NAVIGATION_SCHEDULER_H_ + +#include <memory> + +#include "base/macros.h" +#include "base/memory/scoped_refptr.h" +#include "third_party/blink/public/platform/scheduler/web_main_thread_scheduler.h" +#include "third_party/blink/renderer/core/core_export.h" +#include "third_party/blink/renderer/core/dom/document.h" +#include "third_party/blink/renderer/platform/heap/handle.h" +#include "third_party/blink/renderer/platform/web_task_runner.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/hash_map.h" +#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h" + +namespace blink { + +class FormSubmission; +class LocalFrame; +class ScheduledNavigation; + +class CORE_EXPORT NavigationScheduler final + : public GarbageCollectedFinalized<NavigationScheduler> { + public: + static NavigationScheduler* Create(LocalFrame* frame) { + return new NavigationScheduler(frame); + } + + ~NavigationScheduler(); + + bool LocationChangePending(); + bool IsNavigationScheduledWithin(double interval_in_seconds) const; + + void ScheduleRedirect(double delay, const KURL&, Document::HttpRefreshType); + void ScheduleFrameNavigation(Document*, + const KURL&, + bool replaces_current_item = true); + void SchedulePageBlock(Document*, int reason); + void ScheduleFormSubmission(Document*, FormSubmission*); + void ScheduleReload(); + + void StartTimer(); + void Cancel(); + + void Trace(blink::Visitor*); + + private: + explicit NavigationScheduler(LocalFrame*); + + bool ShouldScheduleReload() const; + bool ShouldScheduleNavigation(const KURL&) const; + + void NavigateTask(); + void Schedule(ScheduledNavigation*); + + static bool MustReplaceCurrentItem(LocalFrame* target_frame); + + Member<LocalFrame> frame_; + TaskHandle navigate_task_handle_; + Member<ScheduledNavigation> redirect_; + + // Exists because we can't deref m_frame in destructor. + scheduler::WebMainThreadScheduler::NavigatingFrameType frame_type_; + + DISALLOW_COPY_AND_ASSIGN(NavigationScheduler); +}; + +class NavigationDisablerForBeforeUnload { + DISALLOW_COPY_AND_ASSIGN(NavigationDisablerForBeforeUnload); + STACK_ALLOCATED(); + + public: + NavigationDisablerForBeforeUnload() { navigation_disable_count_++; } + ~NavigationDisablerForBeforeUnload() { + DCHECK(navigation_disable_count_); + navigation_disable_count_--; + } + static bool IsNavigationAllowed() { return !navigation_disable_count_; } + + private: + static unsigned navigation_disable_count_; +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_NAVIGATION_SCHEDULER_H_ diff --git a/chromium/third_party/blink/renderer/core/loader/network_hints_interface.h b/chromium/third_party/blink/renderer/core/loader/network_hints_interface.h new file mode 100644 index 00000000000..43391656f90 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/network_hints_interface.h @@ -0,0 +1,31 @@ +// 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_NETWORK_HINTS_INTERFACE_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_NETWORK_HINTS_INTERFACE_H_ + +#include "third_party/blink/renderer/platform/network/network_hints.h" + +namespace blink { + +class NetworkHintsInterface { + public: + virtual void DnsPrefetchHost(const String&) const = 0; + virtual void PreconnectHost(const KURL&, + const CrossOriginAttributeValue) const = 0; +}; + +class NetworkHintsInterfaceImpl : public NetworkHintsInterface { + void DnsPrefetchHost(const String& host) const override { PrefetchDNS(host); } + + void PreconnectHost( + const KURL& host, + const CrossOriginAttributeValue cross_origin) const override { + Preconnect(host, cross_origin); + } +}; + +} // namespace blink + +#endif diff --git a/chromium/third_party/blink/renderer/core/loader/ping_loader.cc b/chromium/third_party/blink/renderer/core/loader/ping_loader.cc new file mode 100644 index 00000000000..5d1aab29d38 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/ping_loader.cc @@ -0,0 +1,301 @@ +/* + * Copyright (C) 2010 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/ping_loader.h" + +#include "third_party/blink/public/platform/web_url_request.h" +#include "third_party/blink/renderer/core/dom/document.h" +#include "third_party/blink/renderer/core/fileapi/file.h" +#include "third_party/blink/renderer/core/frame/csp/content_security_policy.h" +#include "third_party/blink/renderer/core/frame/local_frame.h" +#include "third_party/blink/renderer/core/frame/local_frame_client.h" +#include "third_party/blink/renderer/core/html/forms/form_data.h" +#include "third_party/blink/renderer/core/typed_arrays/dom_array_buffer_view.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/fetch_utils.h" +#include "third_party/blink/renderer/platform/loader/fetch/raw_resource.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_options.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource_request.h" +#include "third_party/blink/renderer/platform/network/encoded_form_data.h" +#include "third_party/blink/renderer/platform/network/parsed_content_type.h" +#include "third_party/blink/renderer/platform/weborigin/security_origin.h" +#include "third_party/blink/renderer/platform/weborigin/security_policy.h" +#include "third_party/blink/renderer/platform/wtf/compiler.h" +#include "third_party/blink/renderer/platform/wtf/functional.h" + +namespace blink { + +namespace { + +class Beacon { + STACK_ALLOCATED(); + + public: + virtual void Serialize(ResourceRequest&) const = 0; + virtual unsigned long long size() const = 0; + virtual const AtomicString GetContentType() const = 0; +}; + +class BeaconString final : public Beacon { + public: + explicit BeaconString(const String& data) : data_(data) {} + + unsigned long long size() const override { + return data_.CharactersSizeInBytes(); + } + + void Serialize(ResourceRequest& request) const override { + scoped_refptr<EncodedFormData> entity_body = + EncodedFormData::Create(data_.Utf8()); + request.SetHTTPBody(entity_body); + request.SetHTTPContentType(GetContentType()); + } + + const AtomicString GetContentType() const override { + return AtomicString("text/plain;charset=UTF-8"); + } + + private: + const String data_; +}; + +class BeaconBlob final : public Beacon { + public: + explicit BeaconBlob(Blob* data) : data_(data) { + const String& blob_type = data_->type(); + if (!blob_type.IsEmpty() && ParsedContentType(blob_type).IsValid()) + content_type_ = AtomicString(blob_type); + } + + unsigned long long size() const override { return data_->size(); } + + void Serialize(ResourceRequest& request) const override { + DCHECK(data_); + + scoped_refptr<EncodedFormData> entity_body = EncodedFormData::Create(); + if (data_->HasBackingFile()) + entity_body->AppendFile(ToFile(data_)->GetPath()); + else + entity_body->AppendBlob(data_->Uuid(), data_->GetBlobDataHandle()); + + request.SetHTTPBody(std::move(entity_body)); + + if (!content_type_.IsEmpty()) + request.SetHTTPContentType(content_type_); + } + + const AtomicString GetContentType() const override { return content_type_; } + + private: + const Member<Blob> data_; + AtomicString content_type_; +}; + +class BeaconDOMArrayBufferView final : public Beacon { + public: + explicit BeaconDOMArrayBufferView(DOMArrayBufferView* data) : data_(data) {} + + unsigned long long size() const override { return data_->byteLength(); } + + void Serialize(ResourceRequest& request) const override { + DCHECK(data_); + + scoped_refptr<EncodedFormData> entity_body = + EncodedFormData::Create(data_->BaseAddress(), data_->byteLength()); + request.SetHTTPBody(std::move(entity_body)); + + // FIXME: a reasonable choice, but not in the spec; should it give a + // default? + request.SetHTTPContentType(AtomicString("application/octet-stream")); + } + + const AtomicString GetContentType() const override { return g_null_atom; } + + private: + const Member<DOMArrayBufferView> data_; +}; + +class BeaconFormData final : public Beacon { + public: + explicit BeaconFormData(FormData* data) + : data_(data), entity_body_(data_->EncodeMultiPartFormData()) { + content_type_ = AtomicString("multipart/form-data; boundary=") + + entity_body_->Boundary().data(); + } + + unsigned long long size() const override { + return entity_body_->SizeInBytes(); + } + + void Serialize(ResourceRequest& request) const override { + request.SetHTTPBody(entity_body_.get()); + request.SetHTTPContentType(content_type_); + } + + const AtomicString GetContentType() const override { return content_type_; } + + private: + const Member<FormData> data_; + scoped_refptr<EncodedFormData> entity_body_; + AtomicString content_type_; +}; + +bool SendBeaconCommon(LocalFrame* frame, + const KURL& url, + const Beacon& beacon) { + if (!frame->GetDocument()) + return false; + + if (!ContentSecurityPolicy::ShouldBypassMainWorld(frame->GetDocument()) && + !frame->GetDocument()->GetContentSecurityPolicy()->AllowConnectToSource( + url)) { + // We're simulating a network failure here, so we return 'true'. + return true; + } + + ResourceRequest request(url); + request.SetHTTPMethod(HTTPNames::POST); + request.SetKeepalive(true); + request.SetRequestContext(WebURLRequest::kRequestContextBeacon); + beacon.Serialize(request); + FetchParameters params(request); + // The spec says: + // - If mimeType is not null: + // - If mimeType value is a CORS-safelisted request-header value for the + // Content-Type header, set corsMode to "no-cors". + // As we don't support requests with non CORS-safelisted Content-Type, the + // mode should always be "no-cors". + params.MutableOptions().initiator_info.name = FetchInitiatorTypeNames::beacon; + + frame->Client()->DidDispatchPingLoader(request.Url()); + Resource* resource = + RawResource::Fetch(params, frame->GetDocument()->Fetcher(), nullptr); + return resource->GetStatus() != ResourceStatus::kLoadError; +} + +} // namespace + +// http://www.whatwg.org/specs/web-apps/current-work/multipage/links.html#hyperlink-auditing +void PingLoader::SendLinkAuditPing(LocalFrame* frame, + const KURL& ping_url, + const KURL& destination_url) { + if (!ping_url.ProtocolIsInHTTPFamily()) + return; + + ResourceRequest request(ping_url); + request.SetHTTPMethod(HTTPNames::POST); + request.SetHTTPContentType("text/ping"); + request.SetHTTPBody(EncodedFormData::Create("PING")); + request.SetHTTPHeaderField(HTTPNames::Cache_Control, "max-age=0"); + request.SetHTTPHeaderField(HTTPNames::Ping_To, + AtomicString(destination_url.GetString())); + scoped_refptr<const SecurityOrigin> ping_origin = + SecurityOrigin::Create(ping_url); + if (ProtocolIs(frame->GetDocument()->Url().GetString(), "http") || + frame->GetDocument()->GetSecurityOrigin()->CanAccess(ping_origin.get())) { + request.SetHTTPHeaderField( + HTTPNames::Ping_From, + AtomicString(frame->GetDocument()->Url().GetString())); + } + + request.SetKeepalive(true); + request.SetHTTPReferrer( + Referrer(Referrer::NoReferrer(), kReferrerPolicyNever)); + request.SetRequestContext(WebURLRequest::kRequestContextPing); + FetchParameters params(request); + params.MutableOptions().initiator_info.name = FetchInitiatorTypeNames::ping; + + frame->Client()->DidDispatchPingLoader(request.Url()); + RawResource::Fetch(params, frame->GetDocument()->Fetcher(), nullptr); +} + +void PingLoader::SendViolationReport(LocalFrame* frame, + const KURL& report_url, + scoped_refptr<EncodedFormData> report, + ViolationReportType type) { + ResourceRequest request(report_url); + request.SetHTTPMethod(HTTPNames::POST); + switch (type) { + case kContentSecurityPolicyViolationReport: + request.SetHTTPContentType("application/csp-report"); + break; + case kXSSAuditorViolationReport: + request.SetHTTPContentType("application/xss-auditor-report"); + break; + } + request.SetKeepalive(true); + request.SetHTTPBody(std::move(report)); + request.SetFetchCredentialsMode( + network::mojom::FetchCredentialsMode::kSameOrigin); + request.SetRequestContext(WebURLRequest::kRequestContextCSPReport); + request.SetFetchRedirectMode(network::mojom::FetchRedirectMode::kError); + FetchParameters params(request); + params.MutableOptions().initiator_info.name = + FetchInitiatorTypeNames::violationreport; + params.MutableOptions().security_origin = + frame->GetDocument()->GetSecurityOrigin(); + + frame->Client()->DidDispatchPingLoader(request.Url()); + RawResource::Fetch(params, frame->GetDocument()->Fetcher(), nullptr); +} + +bool PingLoader::SendBeacon(LocalFrame* frame, + const KURL& beacon_url, + const String& data) { + BeaconString beacon(data); + return SendBeaconCommon(frame, beacon_url, beacon); +} + +bool PingLoader::SendBeacon(LocalFrame* frame, + const KURL& beacon_url, + DOMArrayBufferView* data) { + BeaconDOMArrayBufferView beacon(data); + return SendBeaconCommon(frame, beacon_url, beacon); +} + +bool PingLoader::SendBeacon(LocalFrame* frame, + const KURL& beacon_url, + FormData* data) { + BeaconFormData beacon(data); + return SendBeaconCommon(frame, beacon_url, beacon); +} + +bool PingLoader::SendBeacon(LocalFrame* frame, + const KURL& beacon_url, + Blob* data) { + BeaconBlob beacon(data); + return SendBeaconCommon(frame, beacon_url, beacon); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/loader/ping_loader.h b/chromium/third_party/blink/renderer/core/loader/ping_loader.h new file mode 100644 index 00000000000..02f2fdddba3 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/ping_loader.h @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2010 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. + * + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_PING_LOADER_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_PING_LOADER_H_ + +#include <memory> + +#include "third_party/blink/public/platform/web_url_loader_client.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/self_keep_alive.h" +#include "third_party/blink/renderer/platform/timer.h" +#include "third_party/blink/renderer/platform/wtf/forward.h" + +namespace blink { + +class Blob; +class DOMArrayBufferView; +class EncodedFormData; +class FormData; +class LocalFrame; +class KURL; + +// Issue an asynchronous, one-directional request at some resources, ignoring +// any response. The request is made independent of any LocalFrame staying +// alive, and must only stay alive until the transmission has completed +// successfully (or not -- errors are not propagated back either.) Upon +// transmission, the the load is cancelled and the loader cancels itself. +// +// The ping loader is used by audit pings, beacon transmissions and image loads +// during page unloading. +class CORE_EXPORT PingLoader { + public: + enum ViolationReportType { + kContentSecurityPolicyViolationReport, + kXSSAuditorViolationReport + }; + + static void SendLinkAuditPing(LocalFrame*, + const KURL& ping_url, + const KURL& destination_url); + static void SendViolationReport(LocalFrame*, + const KURL& report_url, + scoped_refptr<EncodedFormData> report, + ViolationReportType); + + // The last argument is guaranteed to be set to the size of payload if + // these method return true. If these method returns false, the value + // shouldn't be used. + static bool SendBeacon(LocalFrame*, const KURL&, const String&); + static bool SendBeacon(LocalFrame*, const KURL&, DOMArrayBufferView*); + static bool SendBeacon(LocalFrame*, const KURL&, Blob*); + static bool SendBeacon(LocalFrame*, const KURL&, FormData*); +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_PING_LOADER_H_ diff --git a/chromium/third_party/blink/renderer/core/loader/ping_loader_test.cc b/chromium/third_party/blink/renderer/core/loader/ping_loader_test.cc new file mode 100644 index 00000000000..a7598e89d86 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/ping_loader_test.cc @@ -0,0 +1,151 @@ +// 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/ping_loader.h" + +#include "testing/gtest/include/gtest/gtest.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/frame/local_frame.h" +#include "third_party/blink/renderer/core/loader/empty_clients.h" +#include "third_party/blink/renderer/core/loader/frame_loader.h" +#include "third_party/blink/renderer/core/testing/page_test_base.h" +#include "third_party/blink/renderer/platform/loader/fetch/substitute_data.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" + +namespace blink { + +namespace { + +class PingLocalFrameClient : public EmptyLocalFrameClient { + public: + void DispatchWillSendRequest(ResourceRequest& request) override { + if (request.GetKeepalive()) + ping_request_ = request; + } + + const ResourceRequest& PingRequest() const { return ping_request_; } + + private: + ResourceRequest ping_request_; +}; + +class PingLoaderTest : public PageTestBase { + public: + void SetUp() override { + client_ = new PingLocalFrameClient; + PageTestBase::SetupPageWithClients(nullptr, client_); + } + + void TearDown() override { + Platform::Current() + ->GetURLLoaderMockFactory() + ->UnregisterAllURLsAndClearMemoryCache(); + } + + void SetDocumentURL(const KURL& url) { + FrameLoadRequest request(nullptr, ResourceRequest(url), + SubstituteData(SharedBuffer::Create())); + GetFrame().Loader().Load(request); + blink::test::RunPendingTasks(); + ASSERT_EQ(url.GetString(), GetDocument().Url().GetString()); + } + + const ResourceRequest& PingAndGetRequest(const KURL& ping_url) { + KURL destination_url("http://navigation.destination"); + URLTestHelpers::RegisterMockedURLLoad( + ping_url, test::CoreTestDataPath("bar.html"), "text/html"); + PingLoader::SendLinkAuditPing(&GetFrame(), ping_url, destination_url); + const ResourceRequest& ping_request = client_->PingRequest(); + if (!ping_request.IsNull()) { + EXPECT_EQ(destination_url.GetString(), + ping_request.HttpHeaderField("Ping-To")); + } + // Serve the ping request, since it will otherwise bleed in to the next + // test, and once begun there is no way to cancel it directly. + Platform::Current()->GetURLLoaderMockFactory()->ServeAsynchronousRequests(); + return ping_request; + } + + protected: + Persistent<PingLocalFrameClient> client_; +}; + +TEST_F(PingLoaderTest, HTTPSToHTTPS) { + KURL ping_url("https://localhost/bar.html"); + SetDocumentURL(KURL("https://127.0.0.1:8000/foo.html")); + const ResourceRequest& ping_request = PingAndGetRequest(ping_url); + ASSERT_FALSE(ping_request.IsNull()); + EXPECT_EQ(ping_url, ping_request.Url()); + EXPECT_EQ(String(), ping_request.HttpHeaderField("Ping-From")); +} + +TEST_F(PingLoaderTest, HTTPToHTTPS) { + KURL document_url("http://127.0.0.1:8000/foo.html"); + KURL ping_url("https://localhost/bar.html"); + SetDocumentURL(document_url); + const ResourceRequest& ping_request = PingAndGetRequest(ping_url); + ASSERT_FALSE(ping_request.IsNull()); + EXPECT_EQ(ping_url, ping_request.Url()); + EXPECT_EQ(document_url.GetString(), + ping_request.HttpHeaderField("Ping-From")); +} + +TEST_F(PingLoaderTest, NonHTTPPingTarget) { + SetDocumentURL(KURL("http://127.0.0.1:8000/foo.html")); + const ResourceRequest& ping_request = + PingAndGetRequest(KURL("ftp://localhost/bar.html")); + ASSERT_TRUE(ping_request.IsNull()); +} + +TEST_F(PingLoaderTest, LinkAuditPingPriority) { + KURL destination_url("http://navigation.destination"); + SetDocumentURL(KURL("http://localhost/foo.html")); + + KURL ping_url("https://localhost/bar.html"); + URLTestHelpers::RegisterMockedURLLoad( + ping_url, test::CoreTestDataPath("bar.html"), "text/html"); + PingLoader::SendLinkAuditPing(&GetFrame(), ping_url, destination_url); + Platform::Current()->GetURLLoaderMockFactory()->ServeAsynchronousRequests(); + const ResourceRequest& request = client_->PingRequest(); + ASSERT_FALSE(request.IsNull()); + ASSERT_EQ(request.Url(), ping_url); + EXPECT_EQ(ResourceLoadPriority::kVeryLow, request.Priority()); +} + +TEST_F(PingLoaderTest, ViolationPriority) { + SetDocumentURL(KURL("http://localhost/foo.html")); + + KURL ping_url("https://localhost/bar.html"); + URLTestHelpers::RegisterMockedURLLoad( + ping_url, test::CoreTestDataPath("bar.html"), "text/html"); + PingLoader::SendViolationReport(&GetFrame(), ping_url, + EncodedFormData::Create(), + PingLoader::kXSSAuditorViolationReport); + Platform::Current()->GetURLLoaderMockFactory()->ServeAsynchronousRequests(); + const ResourceRequest& request = client_->PingRequest(); + ASSERT_FALSE(request.IsNull()); + ASSERT_EQ(request.Url(), ping_url); + EXPECT_EQ(ResourceLoadPriority::kVeryLow, request.Priority()); +} + +TEST_F(PingLoaderTest, BeaconPriority) { + SetDocumentURL(KURL("https://localhost/foo.html")); + + KURL ping_url("https://localhost/bar.html"); + URLTestHelpers::RegisterMockedURLLoad( + ping_url, test::CoreTestDataPath("bar.html"), "text/html"); + PingLoader::SendBeacon(&GetFrame(), ping_url, "hello"); + Platform::Current()->GetURLLoaderMockFactory()->ServeAsynchronousRequests(); + const ResourceRequest& request = client_->PingRequest(); + ASSERT_FALSE(request.IsNull()); + ASSERT_EQ(request.Url(), ping_url); + EXPECT_EQ(ResourceLoadPriority::kVeryLow, request.Priority()); +} + +} // namespace + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/loader/prerenderer_client.cc b/chromium/third_party/blink/renderer/core/loader/prerenderer_client.cc new file mode 100644 index 00000000000..f8451b829d7 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/prerenderer_client.cc @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2012 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/prerenderer_client.h" + +#include "third_party/blink/public/platform/web_prerender.h" +#include "third_party/blink/public/web/web_prerenderer_client.h" +#include "third_party/blink/renderer/core/exported/web_view_impl.h" +#include "third_party/blink/renderer/core/page/page.h" +#include "third_party/blink/renderer/platform/prerender.h" + +namespace blink { + +// static +const char PrerendererClient::kSupplementName[] = "PrerendererClient"; + +// static +PrerendererClient* PrerendererClient::From(Page* page) { + return Supplement<Page>::From<PrerendererClient>(page); +} + +PrerendererClient::PrerendererClient(Page& page, WebPrerendererClient* client) + : Supplement<Page>(page), client_(client) {} + +void PrerendererClient::WillAddPrerender(Prerender* prerender) { + if (!client_) + return; + WebPrerender web_prerender(prerender); + client_->WillAddPrerender(&web_prerender); +} + +bool PrerendererClient::IsPrefetchOnly() { + return client_ && client_->IsPrefetchOnly(); +} + +void ProvidePrerendererClientTo(Page& page, PrerendererClient* client) { + PrerendererClient::ProvideTo(page, client); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/loader/prerenderer_client.h b/chromium/third_party/blink/renderer/core/loader/prerenderer_client.h new file mode 100644 index 00000000000..ea613dade39 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/prerenderer_client.h @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2012 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. + * + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_PRERENDERER_CLIENT_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_PRERENDERER_CLIENT_H_ + +#include "base/macros.h" +#include "third_party/blink/renderer/core/core_export.h" +#include "third_party/blink/renderer/core/page/page.h" +#include "third_party/blink/renderer/platform/supplementable.h" + +namespace blink { + +class Page; +class Prerender; +class WebPrerendererClient; + +class CORE_EXPORT PrerendererClient + : public GarbageCollected<PrerendererClient>, + public Supplement<Page> { + USING_GARBAGE_COLLECTED_MIXIN(PrerendererClient); + + public: + static const char kSupplementName[]; + + PrerendererClient(Page&, WebPrerendererClient*); + + virtual void WillAddPrerender(Prerender*); + virtual bool IsPrefetchOnly(); + + static PrerendererClient* From(Page*); + + private: + WebPrerendererClient* client_; + + DISALLOW_COPY_AND_ASSIGN(PrerendererClient); +}; + +CORE_EXPORT void ProvidePrerendererClientTo(Page&, PrerendererClient*); + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_PRERENDERER_CLIENT_H_ diff --git a/chromium/third_party/blink/renderer/core/loader/private/frame_client_hints_preferences_context.cc b/chromium/third_party/blink/renderer/core/loader/private/frame_client_hints_preferences_context.cc new file mode 100644 index 00000000000..8d5b072defe --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/private/frame_client_hints_preferences_context.cc @@ -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. + +#include "third_party/blink/renderer/core/loader/private/frame_client_hints_preferences_context.h" + +#include "third_party/blink/renderer/core/frame/use_counter.h" + +namespace blink { + +namespace { + +// Mapping from WebClientHintsType to WebFeature. The ordering should match the +// ordering of enums in WebClientHintsType. +static constexpr WebFeature kWebFeatureMapping[] = { + WebFeature::kClientHintsDeviceMemory, + WebFeature::kClientHintsDPR, + WebFeature::kClientHintsResourceWidth, + WebFeature::kClientHintsViewportWidth, + WebFeature::kClientHintsRtt, + WebFeature::kClientHintsDownlink, + WebFeature::kClientHintsEct, +}; + +static_assert(static_cast<int>(mojom::WebClientHintsType::kMaxValue) + 1 == + arraysize(kWebFeatureMapping), + "unhandled client hint type"); + +} // namespace + +FrameClientHintsPreferencesContext::FrameClientHintsPreferencesContext( + LocalFrame* frame) + : frame_(frame) {} + +void FrameClientHintsPreferencesContext::CountClientHints( + mojom::WebClientHintsType type) { + UseCounter::Count(frame_, kWebFeatureMapping[static_cast<int32_t>(type)]); +} + +void FrameClientHintsPreferencesContext::CountPersistentClientHintHeaders() { + UseCounter::Count(frame_, WebFeature::kPersistentClientHintHeader); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/loader/private/frame_client_hints_preferences_context.h b/chromium/third_party/blink/renderer/core/loader/private/frame_client_hints_preferences_context.h new file mode 100644 index 00000000000..cb24274256f --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/private/frame_client_hints_preferences_context.h @@ -0,0 +1,31 @@ +// 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_PRIVATE_FRAME_CLIENT_HINTS_PREFERENCES_CONTEXT_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_PRIVATE_FRAME_CLIENT_HINTS_PREFERENCES_CONTEXT_H_ + +#include "third_party/blink/renderer/core/frame/local_frame.h" +#include "third_party/blink/renderer/platform/heap/persistent.h" +#include "third_party/blink/renderer/platform/loader/fetch/client_hints_preferences.h" +#include "third_party/blink/renderer/platform/wtf/allocator.h" + +namespace blink { + +class FrameClientHintsPreferencesContext final + : public ClientHintsPreferences::Context { + STACK_ALLOCATED(); + + public: + explicit FrameClientHintsPreferencesContext(LocalFrame*); + + void CountClientHints(mojom::WebClientHintsType) override; + void CountPersistentClientHintHeaders() override; + + private: + Member<LocalFrame> frame_; +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_PRIVATE_FRAME_CLIENT_HINTS_PREFERENCES_CONTEXT_H_ diff --git a/chromium/third_party/blink/renderer/core/loader/private/prerender_handle.cc b/chromium/third_party/blink/renderer/core/loader/private/prerender_handle.cc new file mode 100644 index 00000000000..ce87f91a464 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/private/prerender_handle.cc @@ -0,0 +1,113 @@ +/* + * 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/private/prerender_handle.h" + +#include "third_party/blink/renderer/core/dom/document.h" +#include "third_party/blink/renderer/core/frame/local_frame.h" +#include "third_party/blink/renderer/core/loader/frame_loader.h" +#include "third_party/blink/renderer/core/loader/prerenderer_client.h" +#include "third_party/blink/renderer/platform/prerender.h" +#include "third_party/blink/renderer/platform/weborigin/referrer_policy.h" +#include "third_party/blink/renderer/platform/weborigin/security_policy.h" + +namespace blink { + +// static +PrerenderHandle* PrerenderHandle::Create(Document& document, + PrerenderClient* client, + const KURL& url, + const unsigned prerender_rel_types) { + // Prerenders are unlike requests in most ways (for instance, they pass down + // fragments, and they don't return data), but they do have referrers. + if (!document.GetFrame()) + return nullptr; + + Prerender* prerender = Prerender::Create( + client, url, prerender_rel_types, + SecurityPolicy::GenerateReferrer(document.GetReferrerPolicy(), url, + document.OutgoingReferrer())); + + PrerendererClient* prerenderer_client = + PrerendererClient::From(document.GetPage()); + if (prerenderer_client) + prerenderer_client->WillAddPrerender(prerender); + prerender->Add(); + + return new PrerenderHandle(document, prerender); +} + +PrerenderHandle::PrerenderHandle(Document& document, Prerender* prerender) + : ContextLifecycleObserver(&document), prerender_(prerender) {} + +PrerenderHandle::~PrerenderHandle() { + if (prerender_) { + prerender_->Abandon(); + Detach(); + } +} + +void PrerenderHandle::Cancel() { + // Avoid both abandoning and canceling the same prerender. In the abandon + // case, the LinkLoader cancels the PrerenderHandle as the Document is + // destroyed, even through the ContextLifecycleObserver has already abandoned + // it. + if (!prerender_) + return; + prerender_->Cancel(); + Detach(); +} + +const KURL& PrerenderHandle::Url() const { + return prerender_->Url(); +} + +void PrerenderHandle::ContextDestroyed(ExecutionContext*) { + // A PrerenderHandle is not removed from LifecycleNotifier::m_observers until + // the next GC runs. Thus contextDestroyed() can be called for a + // PrerenderHandle that is already cancelled (and thus detached). In that + // case, we should not detach the PrerenderHandle again. + if (!prerender_) + return; + prerender_->Abandon(); + Detach(); +} + +void PrerenderHandle::Detach() { + prerender_->Dispose(); + prerender_.Clear(); +} + +void PrerenderHandle::Trace(blink::Visitor* visitor) { + visitor->Trace(prerender_); + ContextLifecycleObserver::Trace(visitor); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/loader/private/prerender_handle.h b/chromium/third_party/blink/renderer/core/loader/private/prerender_handle.h new file mode 100644 index 00000000000..edee2a3cc49 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/private/prerender_handle.h @@ -0,0 +1,79 @@ +/* + * 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. + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_PRIVATE_PRERENDER_HANDLE_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_PRIVATE_PRERENDER_HANDLE_H_ + +#include "base/macros.h" +#include "base/memory/scoped_refptr.h" +#include "third_party/blink/renderer/core/dom/context_lifecycle_observer.h" +#include "third_party/blink/renderer/platform/heap/handle.h" +#include "third_party/blink/renderer/platform/weborigin/kurl.h" + +namespace blink { + +class Document; +class Prerender; +class PrerenderClient; + +class PrerenderHandle final : public GarbageCollectedFinalized<PrerenderHandle>, + public ContextLifecycleObserver { + USING_GARBAGE_COLLECTED_MIXIN(PrerenderHandle); + + public: + static PrerenderHandle* Create(Document&, + PrerenderClient*, + const KURL&, + unsigned prerender_rel_types); + + virtual ~PrerenderHandle(); + + void Cancel(); + const KURL& Url() const; + + // ContextLifecycleObserver: + void ContextDestroyed(ExecutionContext*) override; + + void Trace(blink::Visitor*) override; + EAGERLY_FINALIZE(); + + private: + PrerenderHandle(Document&, Prerender*); + + void Detach(); + + Member<Prerender> prerender_; + + DISALLOW_COPY_AND_ASSIGN(PrerenderHandle); +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_PRIVATE_PRERENDER_HANDLE_H_ diff --git a/chromium/third_party/blink/renderer/core/loader/programmatic_scroll_test.cc b/chromium/third_party/blink/renderer/core/loader/programmatic_scroll_test.cc new file mode 100644 index 00000000000..b201a969cb1 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/programmatic_scroll_test.cc @@ -0,0 +1,157 @@ +// Copyright 2017 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 "testing/gtest/include/gtest/gtest.h" +#include "third_party/blink/public/platform/platform.h" +#include "third_party/blink/public/platform/web_input_event.h" +#include "third_party/blink/public/platform/web_url_loader_mock_factory.h" +#include "third_party/blink/public/web/web_frame.h" +#include "third_party/blink/public/web/web_frame_client.h" +#include "third_party/blink/public/web/web_history_item.h" +#include "third_party/blink/public/web/web_script_source.h" +#include "third_party/blink/public/web/web_settings.h" +#include "third_party/blink/public/web/web_view.h" +#include "third_party/blink/renderer/core/exported/web_view_impl.h" +#include "third_party/blink/renderer/core/frame/frame_test_helpers.h" +#include "third_party/blink/renderer/core/frame/local_frame_view.h" +#include "third_party/blink/renderer/core/frame/web_local_frame_impl.h" +#include "third_party/blink/renderer/core/loader/document_loader.h" +#include "third_party/blink/renderer/core/loader/frame_loader.h" +#include "third_party/blink/renderer/core/testing/sim/sim_request.h" +#include "third_party/blink/renderer/core/testing/sim/sim_test.h" +#include "third_party/blink/renderer/platform/testing/unit_test_helpers.h" +#include "third_party/blink/renderer/platform/testing/url_test_helpers.h" + +namespace blink { + +class ProgrammaticScrollTest : public testing::Test { + public: + ProgrammaticScrollTest() : base_url_("http://www.test.com/") {} + + void TearDown() override { + Platform::Current() + ->GetURLLoaderMockFactory() + ->UnregisterAllURLsAndClearMemoryCache(); + } + + protected: + void RegisterMockedHttpURLLoad(const std::string& file_name) { + URLTestHelpers::RegisterMockedURLLoadFromBase( + WebString::FromUTF8(base_url_), test::CoreTestDataPath(), + WebString::FromUTF8(file_name)); + } + + std::string base_url_; +}; + +TEST_F(ProgrammaticScrollTest, RestoreScrollPositionAndViewStateWithScale) { + RegisterMockedHttpURLLoad("long_scroll.html"); + + FrameTestHelpers::WebViewHelper web_view_helper; + WebViewImpl* web_view = + web_view_helper.InitializeAndLoad(base_url_ + "long_scroll.html"); + web_view->Resize(WebSize(1000, 1000)); + web_view->UpdateAllLifecyclePhases(); + + FrameLoader& loader = web_view->MainFrameImpl()->GetFrame()->Loader(); + loader.GetDocumentLoader()->SetLoadType(kFrameLoadTypeBackForward); + + web_view->SetPageScaleFactor(3.0f); + web_view->MainFrameImpl()->SetScrollOffset(WebSize(0, 500)); + loader.GetDocumentLoader()->GetInitialScrollState().was_scrolled_by_user = + false; + loader.GetDocumentLoader()->GetHistoryItem()->SetPageScaleFactor(2); + loader.GetDocumentLoader()->GetHistoryItem()->SetScrollOffset( + ScrollOffset(0, 200)); + + // Flip back the wasScrolledByUser flag which was set to true by + // setPageScaleFactor because otherwise + // FrameLoader::restoreScrollPositionAndViewState does nothing. + loader.GetDocumentLoader()->GetInitialScrollState().was_scrolled_by_user = + false; + loader.RestoreScrollPositionAndViewState(); + + // Expect that both scroll and scale were restored. + EXPECT_EQ(2.0f, web_view->PageScaleFactor()); + EXPECT_EQ(200, web_view->MainFrameImpl()->GetScrollOffset().height); +} + +TEST_F(ProgrammaticScrollTest, RestoreScrollPositionAndViewStateWithoutScale) { + RegisterMockedHttpURLLoad("long_scroll.html"); + + FrameTestHelpers::WebViewHelper web_view_helper; + WebViewImpl* web_view = + web_view_helper.InitializeAndLoad(base_url_ + "long_scroll.html"); + web_view->Resize(WebSize(1000, 1000)); + web_view->UpdateAllLifecyclePhases(); + + FrameLoader& loader = web_view->MainFrameImpl()->GetFrame()->Loader(); + loader.GetDocumentLoader()->SetLoadType(kFrameLoadTypeBackForward); + + web_view->SetPageScaleFactor(3.0f); + web_view->MainFrameImpl()->SetScrollOffset(WebSize(0, 500)); + loader.GetDocumentLoader()->GetInitialScrollState().was_scrolled_by_user = + false; + loader.GetDocumentLoader()->GetHistoryItem()->SetPageScaleFactor(0); + loader.GetDocumentLoader()->GetHistoryItem()->SetScrollOffset( + ScrollOffset(0, 400)); + + // FrameLoader::restoreScrollPositionAndViewState flows differently if scale + // is zero. + loader.RestoreScrollPositionAndViewState(); + + // Expect that only the scroll position was restored. + EXPECT_EQ(3.0f, web_view->PageScaleFactor()); + EXPECT_EQ(400, web_view->MainFrameImpl()->GetScrollOffset().height); +} + +class ProgrammaticScrollSimTest : public testing::WithParamInterface<bool>, + private ScopedRootLayerScrollingForTest, + public SimTest { + public: + ProgrammaticScrollSimTest() : ScopedRootLayerScrollingForTest(GetParam()) {} +}; + +INSTANTIATE_TEST_CASE_P(All, ProgrammaticScrollSimTest, testing::Bool()); + +TEST_P(ProgrammaticScrollSimTest, NavigateToHash) { + WebView().Resize(WebSize(800, 600)); + SimRequest main_resource("https://example.com/test.html#target", "text/html"); + SimRequest css_resource("https://example.com/test.css", "text/css"); + + LoadURL("https://example.com/test.html#target"); + + // Finish loading the main document before the stylesheet is loaded so that + // rendering is blocked when parsing finishes. This will delay closing the + // document until the load event. + main_resource.Start(); + main_resource.Write( + "<!DOCTYPE html><link id=link rel=stylesheet href=test.css>"); + css_resource.Start(); + main_resource.Write(R"HTML( + <style> + body { + height: 4000px; + } + h2 { + position: absolute; + top: 3000px; + } + </style> + <h2 id="target">Target</h2> + )HTML"); + main_resource.Finish(); + css_resource.Complete(); + Compositor().BeginFrame(); + + // Run pending tasks to fire the load event and close the document. This + // should cause the document to scroll to the hash. + test::RunPendingTasks(); + + ScrollableArea* layout_viewport = + GetDocument().View()->LayoutViewportScrollableArea(); + EXPECT_EQ(3001, layout_viewport->GetScrollOffset().Height()); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/loader/progress_tracker.cc b/chromium/third_party/blink/renderer/core/loader/progress_tracker.cc new file mode 100644 index 00000000000..ddfd169dddb --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/progress_tracker.cc @@ -0,0 +1,245 @@ +/* + * Copyright (C) 2007 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. + */ + +#include "third_party/blink/renderer/core/loader/progress_tracker.h" + +#include "third_party/blink/public/web/web_settings.h" +#include "third_party/blink/renderer/core/frame/local_frame.h" +#include "third_party/blink/renderer/core/frame/local_frame_client.h" +#include "third_party/blink/renderer/core/frame/local_frame_view.h" +#include "third_party/blink/renderer/core/frame/settings.h" +#include "third_party/blink/renderer/core/loader/document_loader.h" +#include "third_party/blink/renderer/core/loader/frame_loader.h" +#include "third_party/blink/renderer/core/paint/paint_timing.h" +#include "third_party/blink/renderer/core/probe/core_probes.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource_response.h" +#include "third_party/blink/renderer/platform/wtf/assertions.h" +#include "third_party/blink/renderer/platform/wtf/text/cstring.h" +#include "third_party/blink/renderer/platform/wtf/time.h" + +namespace blink { + +// Always start progress at initialProgressValue. This helps provide feedback as +// soon as a load starts. +static const double kInitialProgressValue = 0.1; + +static const int kProgressItemDefaultEstimatedLength = 1024 * 1024; + +static const double kProgressNotificationInterval = 0.02; +static const double kProgressNotificationTimeInterval = 0.1; + +struct ProgressItem { + USING_FAST_MALLOC(ProgressItem); + + public: + explicit ProgressItem(long long length) + : bytes_received(0), estimated_length(length) {} + + long long bytes_received; + long long estimated_length; + + DISALLOW_COPY_AND_ASSIGN(ProgressItem); +}; + +ProgressTracker* ProgressTracker::Create(LocalFrame* frame) { + return new ProgressTracker(frame); +} + +ProgressTracker::ProgressTracker(LocalFrame* frame) + : frame_(frame), + last_notified_progress_value_(0), + last_notified_progress_time_(0), + finished_parsing_(false), + did_first_contentful_paint_(false), + progress_value_(0) {} + +ProgressTracker::~ProgressTracker() = default; + +void ProgressTracker::Trace(blink::Visitor* visitor) { + visitor->Trace(frame_); +} + +void ProgressTracker::Dispose() { + if (frame_->IsLoading()) + ProgressCompleted(); + DCHECK(!frame_->IsLoading()); +} + +double ProgressTracker::EstimatedProgress() const { + return progress_value_; +} + +void ProgressTracker::Reset() { + progress_items_.clear(); + progress_value_ = 0; + last_notified_progress_value_ = 0; + last_notified_progress_time_ = 0; + finished_parsing_ = false; + did_first_contentful_paint_ = false; +} + +LocalFrameClient* ProgressTracker::GetLocalFrameClient() const { + return frame_->Client(); +} + +void ProgressTracker::ProgressStarted(FrameLoadType type) { + Reset(); + progress_value_ = kInitialProgressValue; + if (!frame_->IsLoading()) { + GetLocalFrameClient()->DidStartLoading(kNavigationToDifferentDocument); + frame_->SetIsLoading(true); + probe::frameStartedLoading(frame_, type); + } +} + +void ProgressTracker::ProgressCompleted() { + DCHECK(frame_->IsLoading()); + frame_->SetIsLoading(false); + SendFinalProgress(); + Reset(); + GetLocalFrameClient()->DidStopLoading(); + probe::frameStoppedLoading(frame_); +} + +void ProgressTracker::FinishedParsing() { + finished_parsing_ = true; + MaybeSendProgress(); +} + +void ProgressTracker::DidFirstContentfulPaint() { + did_first_contentful_paint_ = true; + MaybeSendProgress(); +} + +void ProgressTracker::SendFinalProgress() { + if (progress_value_ == 1) + return; + progress_value_ = 1; + GetLocalFrameClient()->ProgressEstimateChanged(progress_value_); +} + +void ProgressTracker::WillStartLoading(unsigned long identifier, + ResourceLoadPriority priority) { + if (!frame_->IsLoading()) + return; + if (HaveParsedAndPainted() || priority < ResourceLoadPriority::kHigh) + return; + progress_items_.Set(identifier, std::make_unique<ProgressItem>( + kProgressItemDefaultEstimatedLength)); +} + +void ProgressTracker::IncrementProgress(unsigned long identifier, + const ResourceResponse& response) { + ProgressItem* item = progress_items_.at(identifier); + if (!item) + return; + + long long estimated_length = response.ExpectedContentLength(); + if (estimated_length < 0) + estimated_length = kProgressItemDefaultEstimatedLength; + item->bytes_received = 0; + item->estimated_length = estimated_length; +} + +void ProgressTracker::IncrementProgress(unsigned long identifier, int length) { + ProgressItem* item = progress_items_.at(identifier); + if (!item) + return; + + item->bytes_received += length; + if (item->bytes_received > item->estimated_length) + item->estimated_length = item->bytes_received * 2; + MaybeSendProgress(); +} + +bool ProgressTracker::HaveParsedAndPainted() { + return finished_parsing_ && did_first_contentful_paint_; +} + +void ProgressTracker::MaybeSendProgress() { + if (!frame_->IsLoading()) + return; + + progress_value_ = kInitialProgressValue + 0.1; // +0.1 for committing + if (finished_parsing_) + progress_value_ += 0.1; + if (did_first_contentful_paint_) + progress_value_ += 0.1; + + long long bytes_received = 0; + long long estimated_bytes_for_pending_requests = 0; + for (const auto& progress_item : progress_items_) { + bytes_received += progress_item.value->bytes_received; + estimated_bytes_for_pending_requests += + progress_item.value->estimated_length; + } + DCHECK_GE(estimated_bytes_for_pending_requests, 0); + DCHECK_GE(estimated_bytes_for_pending_requests, bytes_received); + + if (HaveParsedAndPainted() && + estimated_bytes_for_pending_requests == bytes_received) { + SendFinalProgress(); + return; + } + + double percent_of_bytes_received = + !estimated_bytes_for_pending_requests + ? 1.0 + : (double)bytes_received / + (double)estimated_bytes_for_pending_requests; + progress_value_ += percent_of_bytes_received / 2; + + DCHECK_GE(progress_value_, kInitialProgressValue); + // Always leave space at the end. This helps show the user that we're not + // done until we're done. + DCHECK_LE(progress_value_, 0.9); + if (progress_value_ < last_notified_progress_value_) + return; + + double now = CurrentTime(); + double notified_progress_time_delta = now - last_notified_progress_time_; + + double notification_progress_delta = + progress_value_ - last_notified_progress_value_; + if (notification_progress_delta >= kProgressNotificationInterval || + notified_progress_time_delta >= kProgressNotificationTimeInterval) { + GetLocalFrameClient()->ProgressEstimateChanged(progress_value_); + last_notified_progress_value_ = progress_value_; + last_notified_progress_time_ = now; + } +} + +void ProgressTracker::CompleteProgress(unsigned long identifier) { + ProgressItem* item = progress_items_.at(identifier); + if (!item) + return; + + item->estimated_length = item->bytes_received; + MaybeSendProgress(); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/loader/progress_tracker.h b/chromium/third_party/blink/renderer/core/loader/progress_tracker.h new file mode 100644 index 00000000000..0a05bb35b8d --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/progress_tracker.h @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2007 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_PROGRESS_TRACKER_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_PROGRESS_TRACKER_H_ + +#include <memory> + +#include "base/macros.h" +#include "third_party/blink/renderer/core/core_export.h" +#include "third_party/blink/renderer/core/loader/frame_loader_types.h" +#include "third_party/blink/renderer/platform/heap/handle.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource_load_priority.h" +#include "third_party/blink/renderer/platform/wtf/allocator.h" +#include "third_party/blink/renderer/platform/wtf/forward.h" +#include "third_party/blink/renderer/platform/wtf/hash_map.h" + +namespace blink { + +class LocalFrameClient; +class LocalFrame; +class ResourceResponse; +struct ProgressItem; + +// FIXME: This is only used on Android. Android is the only Chrome +// browser which shows a progress bar during loading. +// We should find a better way for Android to get this data and remove this! +class CORE_EXPORT ProgressTracker final + : public GarbageCollectedFinalized<ProgressTracker> { + public: + static ProgressTracker* Create(LocalFrame*); + + ~ProgressTracker(); + void Trace(blink::Visitor*); + void Dispose(); + + double EstimatedProgress() const; + + void ProgressStarted(FrameLoadType); + void ProgressCompleted(); + + void FinishedParsing(); + void DidFirstContentfulPaint(); + + void WillStartLoading(unsigned long identifier, ResourceLoadPriority); + void IncrementProgress(unsigned long identifier, const ResourceResponse&); + void IncrementProgress(unsigned long identifier, int); + void CompleteProgress(unsigned long identifier); + + private: + explicit ProgressTracker(LocalFrame*); + + LocalFrameClient* GetLocalFrameClient() const; + + void MaybeSendProgress(); + void SendFinalProgress(); + void Reset(); + + bool HaveParsedAndPainted(); + + Member<LocalFrame> frame_; + double last_notified_progress_value_; + double last_notified_progress_time_; + bool finished_parsing_; + bool did_first_contentful_paint_; + double progress_value_; + + HashMap<unsigned long, std::unique_ptr<ProgressItem>> progress_items_; + + DISALLOW_COPY_AND_ASSIGN(ProgressTracker); +}; + +} // namespace blink + +#endif diff --git a/chromium/third_party/blink/renderer/core/loader/progress_tracker_test.cc b/chromium/third_party/blink/renderer/core/loader/progress_tracker_test.cc new file mode 100644 index 00000000000..04eca2b73e7 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/progress_tracker_test.cc @@ -0,0 +1,149 @@ +// 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/progress_tracker.h" + +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/blink/renderer/core/frame/settings.h" +#include "third_party/blink/renderer/core/loader/empty_clients.h" +#include "third_party/blink/renderer/core/testing/page_test_base.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource_response.h" + +namespace blink { + +class ProgressClient : public EmptyLocalFrameClient { + public: + ProgressClient() : last_progress_(0.0) {} + + void ProgressEstimateChanged(double progress_estimate) override { + last_progress_ = progress_estimate; + } + + double LastProgress() const { return last_progress_; } + + private: + double last_progress_; +}; + +class ProgressTrackerTest : public PageTestBase { + public: + ProgressTrackerTest() + : response_(KURL("http://example.com"), "text/html", 1024) {} + + void SetUp() override { + client_ = new ProgressClient; + PageTestBase::SetupPageWithClients(nullptr, client_.Get()); + } + + ProgressTracker& Progress() const { return GetFrame().Loader().Progress(); } + double LastProgress() const { return client_->LastProgress(); } + const ResourceResponse& ResponseHeaders() const { return response_; } + + // Reports a 1024-byte "main resource" (VeryHigh priority) request/response + // to ProgressTracker with identifier 1, but tests are responsible for + // emulating payload and load completion. + void EmulateMainResourceRequestAndResponse() const { + Progress().ProgressStarted(kFrameLoadTypeStandard); + Progress().WillStartLoading(1ul, ResourceLoadPriority::kVeryHigh); + EXPECT_EQ(0.0, LastProgress()); + Progress().IncrementProgress(1ul, ResponseHeaders()); + EXPECT_EQ(0.0, LastProgress()); + } + + private: + Persistent<ProgressClient> client_; + ResourceResponse response_; +}; + +TEST_F(ProgressTrackerTest, Static) { + Progress().ProgressStarted(kFrameLoadTypeStandard); + EXPECT_EQ(0.0, LastProgress()); + Progress().ProgressCompleted(); + EXPECT_EQ(1.0, LastProgress()); +} + +TEST_F(ProgressTrackerTest, MainResourceOnly) { + EmulateMainResourceRequestAndResponse(); + + // .2 for committing, .25 out of .5 possible for bytes received. + Progress().IncrementProgress(1ul, 512); + EXPECT_EQ(0.45, LastProgress()); + + // .2 for committing, .5 for all bytes received. + Progress().CompleteProgress(1ul); + EXPECT_EQ(0.7, LastProgress()); + + Progress().FinishedParsing(); + Progress().DidFirstContentfulPaint(); + EXPECT_EQ(1.0, LastProgress()); +} + +TEST_F(ProgressTrackerTest, WithHighPriorirySubresource) { + EmulateMainResourceRequestAndResponse(); + + Progress().WillStartLoading(2ul, ResourceLoadPriority::kHigh); + Progress().IncrementProgress(2ul, ResponseHeaders()); + EXPECT_EQ(0.0, LastProgress()); + + // .2 for committing, .25 out of .5 possible for bytes received. + Progress().IncrementProgress(1ul, 1024); + Progress().CompleteProgress(1ul); + EXPECT_EQ(0.45, LastProgress()); + + // .4 for finishing parsing/painting, + // .25 out of .5 possible for bytes received. + Progress().FinishedParsing(); + Progress().DidFirstContentfulPaint(); + EXPECT_EQ(0.65, LastProgress()); + + Progress().CompleteProgress(2ul); + EXPECT_EQ(1.0, LastProgress()); +} + +TEST_F(ProgressTrackerTest, WithMediumPrioritySubresource) { + EmulateMainResourceRequestAndResponse(); + + Progress().WillStartLoading(2ul, ResourceLoadPriority::kMedium); + Progress().IncrementProgress(2ul, ResponseHeaders()); + EXPECT_EQ(0.0, LastProgress()); + + // .2 for committing, .5 for all bytes received. + // Medium priority resource is ignored. + Progress().CompleteProgress(1ul); + EXPECT_EQ(0.7, LastProgress()); + + Progress().FinishedParsing(); + Progress().DidFirstContentfulPaint(); + EXPECT_EQ(1.0, LastProgress()); +} + +TEST_F(ProgressTrackerTest, FinishParsingBeforeContentfulPaint) { + EmulateMainResourceRequestAndResponse(); + + // .2 for committing, .5 for all bytes received. + Progress().CompleteProgress(1ul); + EXPECT_EQ(0.7, LastProgress()); + + Progress().FinishedParsing(); + EXPECT_EQ(0.8, LastProgress()); + + Progress().DidFirstContentfulPaint(); + EXPECT_EQ(1.0, LastProgress()); +} + +TEST_F(ProgressTrackerTest, ContentfulPaintBeforeFinishParsing) { + EmulateMainResourceRequestAndResponse(); + + // .2 for committing, .5 for all bytes received. + Progress().CompleteProgress(1ul); + EXPECT_EQ(0.7, LastProgress()); + + Progress().DidFirstContentfulPaint(); + EXPECT_EQ(0.8, LastProgress()); + + Progress().FinishedParsing(); + EXPECT_EQ(1.0, LastProgress()); +} + +} // namespace blink 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 diff --git a/chromium/third_party/blink/renderer/core/loader/scheduled_navigation.cc b/chromium/third_party/blink/renderer/core/loader/scheduled_navigation.cc new file mode 100644 index 00000000000..7b6bc822b3f --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/scheduled_navigation.cc @@ -0,0 +1,36 @@ +// Copyright 2017 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/scheduled_navigation.h" + +#include <memory> + +#include "third_party/blink/renderer/core/frame/frame.h" +#include "third_party/blink/renderer/core/frame/local_frame.h" + +namespace blink { + +ScheduledNavigation::ScheduledNavigation(Reason reason, + double delay, + Document* origin_document, + bool replaces_current_item, + bool is_location_change) + : reason_(reason), + delay_(delay), + origin_document_(origin_document), + replaces_current_item_(replaces_current_item), + is_location_change_(is_location_change) { + if (Frame::HasTransientUserActivation( + origin_document ? origin_document->GetFrame() : nullptr)) + user_gesture_token_ = UserGestureIndicator::CurrentToken(); +} + +ScheduledNavigation::~ScheduledNavigation() = default; + +std::unique_ptr<UserGestureIndicator> +ScheduledNavigation::CreateUserGestureIndicator() { + return std::make_unique<UserGestureIndicator>(user_gesture_token_); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/loader/scheduled_navigation.h b/chromium/third_party/blink/renderer/core/loader/scheduled_navigation.h new file mode 100644 index 00000000000..82942e2d767 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/scheduled_navigation.h @@ -0,0 +1,71 @@ +// Copyright 2017 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_SCHEDULED_NAVIGATION_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_SCHEDULED_NAVIGATION_H_ + +#include "base/macros.h" +#include "third_party/blink/public/platform/platform.h" + +namespace blink { + +class Document; +class LocalFrame; +class UserGestureIndicator; +class UserGestureToken; + +class ScheduledNavigation + : public GarbageCollectedFinalized<ScheduledNavigation> { + public: + enum class Reason { + kFormSubmissionGet, + kFormSubmissionPost, + kHttpHeaderRefresh, + kFrameNavigation, + kMetaTagRefresh, + kPageBlock, + kReload, + }; + + ScheduledNavigation(Reason, + double delay, + Document* origin_document, + bool replaces_current_item, + bool is_location_change); + virtual ~ScheduledNavigation(); + + virtual void Fire(LocalFrame*) = 0; + + virtual KURL Url() const = 0; + + virtual bool ShouldStartTimer(LocalFrame*) { return true; } + + Reason GetReason() const { return reason_; } + double Delay() const { return delay_; } + Document* OriginDocument() const { return origin_document_.Get(); } + bool ReplacesCurrentItem() const { return replaces_current_item_; } + bool IsLocationChange() const { return is_location_change_; } + std::unique_ptr<UserGestureIndicator> CreateUserGestureIndicator(); + + virtual void Trace(blink::Visitor* visitor) { + visitor->Trace(origin_document_); + } + + protected: + void ClearUserGesture() { user_gesture_token_ = nullptr; } + + private: + Reason reason_; + double delay_; + Member<Document> origin_document_; + bool replaces_current_item_; + bool is_location_change_; + scoped_refptr<UserGestureToken> user_gesture_token_; + + DISALLOW_COPY_AND_ASSIGN(ScheduledNavigation); +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_SCHEDULED_NAVIGATION_H_ diff --git a/chromium/third_party/blink/renderer/core/loader/subresource_filter.cc b/chromium/third_party/blink/renderer/core/loader/subresource_filter.cc new file mode 100644 index 00000000000..a4e4dd44e13 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/subresource_filter.cc @@ -0,0 +1,154 @@ +// Copyright 2017 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/subresource_filter.h" + +#include <utility> + +#include "base/location.h" +#include "base/single_thread_task_runner.h" +#include "third_party/blink/public/platform/task_type.h" +#include "third_party/blink/renderer/core/dom/document.h" +#include "third_party/blink/renderer/core/frame/local_frame.h" +#include "third_party/blink/renderer/core/inspector/console_message.h" +#include "third_party/blink/renderer/core/loader/document_loader.h" +#include "third_party/blink/renderer/core/workers/worker_or_worklet_global_scope.h" +#include "third_party/blink/renderer/platform/runtime_enabled_features.h" +#include "third_party/blink/renderer/platform/weborigin/kurl.h" +#include "third_party/blink/renderer/platform/wtf/text/string_builder.h" + +namespace blink { + +namespace { + +String GetErrorStringForDisallowedLoad(const KURL& url) { + StringBuilder builder; + builder.Append("Chrome blocked resource "); + builder.Append(url.GetString()); + builder.Append( + " on this site because this site tends to show ads that interrupt, " + "distract, or prevent user control. Learn more at " + "https://www.chromestatus.com/feature/5738264052891648"); + return builder.ToString(); +} + +} // namespace + +// static +SubresourceFilter* SubresourceFilter::Create( + ExecutionContext& execution_context, + std::unique_ptr<WebDocumentSubresourceFilter> filter) { + return new SubresourceFilter(&execution_context, std::move(filter)); +} + +SubresourceFilter::SubresourceFilter( + ExecutionContext* execution_context, + std::unique_ptr<WebDocumentSubresourceFilter> subresource_filter) + : execution_context_(execution_context), + subresource_filter_(std::move(subresource_filter)) {} + +SubresourceFilter::~SubresourceFilter() = default; + +bool SubresourceFilter::AllowLoad( + const KURL& resource_url, + WebURLRequest::RequestContext request_context, + SecurityViolationReportingPolicy reporting_policy) { + // TODO(csharrison): Implement a caching layer here which is a HashMap of + // Pair<url string, context> -> LoadPolicy. + WebDocumentSubresourceFilter::LoadPolicy load_policy = + subresource_filter_->GetLoadPolicy(resource_url, request_context); + + if (reporting_policy == SecurityViolationReportingPolicy::kReport) + ReportLoad(resource_url, load_policy); + + last_resource_check_result_ = std::make_pair( + std::make_pair(resource_url, request_context), load_policy); + + return load_policy != WebDocumentSubresourceFilter::kDisallow; +} + +bool SubresourceFilter::AllowWebSocketConnection(const KURL& url) { + // WebSocket is handled via document on the main thread unless the + // experimental off-main-thread WebSocket flag is enabled. See + // https://crbug.com/825740 for the details of the off-main-thread WebSocket. + DCHECK(execution_context_->IsDocument() || + RuntimeEnabledFeatures::OffMainThreadWebSocketEnabled()); + + WebDocumentSubresourceFilter::LoadPolicy load_policy = + subresource_filter_->GetLoadPolicyForWebSocketConnect(url); + + // Post a task to notify this load to avoid unduly blocking the worker + // thread. Note that this unconditionally calls reportLoad unlike allowLoad, + // because there aren't developer-invisible connections (like speculative + // preloads) happening here. + scoped_refptr<base::SingleThreadTaskRunner> task_runner = + execution_context_->GetTaskRunner(TaskType::kNetworking); + DCHECK(task_runner->RunsTasksInCurrentSequence()); + task_runner->PostTask( + FROM_HERE, WTF::Bind(&SubresourceFilter::ReportLoad, WrapPersistent(this), + url, load_policy)); + return load_policy != WebDocumentSubresourceFilter::kDisallow; +} + +bool SubresourceFilter::GetIsAssociatedWithAdSubframe() { + return subresource_filter_->GetIsAssociatedWithAdSubframe(); +} + +bool SubresourceFilter::IsAdResource( + const KURL& resource_url, + WebURLRequest::RequestContext request_context) { + WebDocumentSubresourceFilter::LoadPolicy load_policy; + if (last_resource_check_result_.first == + std::make_pair(resource_url, request_context)) { + load_policy = last_resource_check_result_.second; + } else { + load_policy = + subresource_filter_->GetLoadPolicy(resource_url, request_context); + } + + // If the subresource cannot be identified as an ad via load_policy, check if + // its frame is identified as an ad. + return load_policy != WebDocumentSubresourceFilter::kAllow || + subresource_filter_->GetIsAssociatedWithAdSubframe(); +} + +void SubresourceFilter::ReportLoad( + const KURL& resource_url, + WebDocumentSubresourceFilter::LoadPolicy load_policy) { + switch (load_policy) { + case WebDocumentSubresourceFilter::kAllow: + break; + case WebDocumentSubresourceFilter::kDisallow: + subresource_filter_->ReportDisallowedLoad(); + + // Display console message for actually blocked resource. For a + // resource with |load_policy| as kWouldDisallow, we will be logging a + // document wide console message, so no need to log it here. + // TODO: Consider logging this as a kInterventionMessageSource for showing + // warning in Lighthouse. + if (subresource_filter_->ShouldLogToConsole()) { + execution_context_->AddConsoleMessage(ConsoleMessage::Create( + kOtherMessageSource, kErrorMessageLevel, + GetErrorStringForDisallowedLoad(resource_url))); + } + FALLTHROUGH; + case WebDocumentSubresourceFilter::kWouldDisallow: + // TODO(csharrison): Consider posting a task to the main thread from + // worker thread, or adding support for DidObserveLoadingBehavior to + // ExecutionContext. + if (execution_context_->IsDocument()) { + if (DocumentLoader* loader = ToDocument(execution_context_)->Loader()) { + loader->DidObserveLoadingBehavior( + kWebLoadingBehaviorSubresourceFilterMatch); + } + } + break; + } +} + +void SubresourceFilter::Trace(blink::Visitor* visitor) { + visitor->Trace(execution_context_); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/loader/subresource_filter.h b/chromium/third_party/blink/renderer/core/loader/subresource_filter.h new file mode 100644 index 00000000000..8fe4a263a52 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/subresource_filter.h @@ -0,0 +1,64 @@ +// Copyright 2017 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_SUBRESOURCE_FILTER_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_SUBRESOURCE_FILTER_H_ + +#include <memory> +#include <utility> + +#include "third_party/blink/public/platform/web_document_subresource_filter.h" +#include "third_party/blink/public/platform/web_url_request.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/weborigin/kurl.h" +#include "third_party/blink/renderer/platform/weborigin/security_violation_reporting_policy.h" + +namespace blink { + +class ExecutionContext; +class KURL; + +// Wrapper around a WebDocumentSubresourceFilter. This class will make it easier +// to extend the subresource filter with optimizations only possible using blink +// types (e.g. a caching layer using StringImpl). +class CORE_EXPORT SubresourceFilter final + : public GarbageCollectedFinalized<SubresourceFilter> { + public: + static SubresourceFilter* Create( + ExecutionContext&, + std::unique_ptr<WebDocumentSubresourceFilter>); + ~SubresourceFilter(); + + bool AllowLoad(const KURL& resource_url, + WebURLRequest::RequestContext, + SecurityViolationReportingPolicy); + bool AllowWebSocketConnection(const KURL&); + + bool GetIsAssociatedWithAdSubframe(); + + // Returns if |resource_url| is an ad resource. + bool IsAdResource(const KURL& resource_url, WebURLRequest::RequestContext); + + virtual void Trace(blink::Visitor*); + + private: + SubresourceFilter(ExecutionContext*, + std::unique_ptr<WebDocumentSubresourceFilter>); + + void ReportLoad(const KURL& resource_url, + WebDocumentSubresourceFilter::LoadPolicy); + + Member<ExecutionContext> execution_context_; + std::unique_ptr<WebDocumentSubresourceFilter> subresource_filter_; + + // Save the last resource check's result in the single element cache. + std::pair<std::pair<KURL, WebURLRequest::RequestContext>, + WebDocumentSubresourceFilter::LoadPolicy> + last_resource_check_result_; +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_SUBRESOURCE_FILTER_H_ diff --git a/chromium/third_party/blink/renderer/core/loader/subresource_integrity_helper.cc b/chromium/third_party/blink/renderer/core/loader/subresource_integrity_helper.cc new file mode 100644 index 00000000000..6748289bb60 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/subresource_integrity_helper.cc @@ -0,0 +1,73 @@ +// Copyright 2017 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/subresource_integrity_helper.h" + +#include "third_party/blink/renderer/core/execution_context/execution_context.h" +#include "third_party/blink/renderer/core/frame/use_counter.h" +#include "third_party/blink/renderer/core/inspector/console_types.h" +#include "third_party/blink/renderer/core/origin_trials/origin_trials.h" +#include "third_party/blink/renderer/platform/runtime_enabled_features.h" + +namespace blink { + +WebFeature GetWebFeature( + SubresourceIntegrity::ReportInfo::UseCounterFeature& feature) { + switch (feature) { + case SubresourceIntegrity::ReportInfo::UseCounterFeature:: + kSRIElementWithMatchingIntegrityAttribute: + return WebFeature::kSRIElementWithMatchingIntegrityAttribute; + case SubresourceIntegrity::ReportInfo::UseCounterFeature:: + kSRIElementWithNonMatchingIntegrityAttribute: + return WebFeature::kSRIElementWithNonMatchingIntegrityAttribute; + case SubresourceIntegrity::ReportInfo::UseCounterFeature:: + kSRIElementIntegrityAttributeButIneligible: + return WebFeature::kSRIElementIntegrityAttributeButIneligible; + case SubresourceIntegrity::ReportInfo::UseCounterFeature:: + kSRIElementWithUnparsableIntegrityAttribute: + return WebFeature::kSRIElementWithUnparsableIntegrityAttribute; + case SubresourceIntegrity::ReportInfo::UseCounterFeature:: + kSRISignatureCheck: + return WebFeature::kSRISignatureCheck; + case SubresourceIntegrity::ReportInfo::UseCounterFeature:: + kSRISignatureSuccess: + return WebFeature::kSRISignatureSuccess; + } + NOTREACHED(); + return WebFeature::kSRIElementWithUnparsableIntegrityAttribute; +} + +void SubresourceIntegrityHelper::DoReport( + ExecutionContext& execution_context, + const SubresourceIntegrity::ReportInfo& report_info) { + for (auto feature : report_info.UseCounts()) { + UseCounter::Count(&execution_context, GetWebFeature(feature)); + } + HeapVector<Member<ConsoleMessage>> messages; + GetConsoleMessages(report_info, &messages); + for (const auto& message : messages) { + execution_context.AddConsoleMessage(message); + } +} + +void SubresourceIntegrityHelper::GetConsoleMessages( + const SubresourceIntegrity::ReportInfo& report_info, + HeapVector<Member<ConsoleMessage>>* messages) { + DCHECK(messages); + for (const auto& message : report_info.ConsoleErrorMessages()) { + messages->push_back(ConsoleMessage::Create(kSecurityMessageSource, + kErrorMessageLevel, message)); + } +} + +SubresourceIntegrity::IntegrityFeatures SubresourceIntegrityHelper::GetFeatures( + ExecutionContext* execution_context) { + bool allow_signatures = + RuntimeEnabledFeatures::SignatureBasedIntegrityEnabledByRuntimeFlag() || + OriginTrials::signatureBasedIntegrityEnabled(execution_context); + return allow_signatures ? SubresourceIntegrity::IntegrityFeatures::kSignatures + : SubresourceIntegrity::IntegrityFeatures::kDefault; +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/loader/subresource_integrity_helper.h b/chromium/third_party/blink/renderer/core/loader/subresource_integrity_helper.h new file mode 100644 index 00000000000..ee19a328887 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/subresource_integrity_helper.h @@ -0,0 +1,32 @@ +// Copyright 2017 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_SUBRESOURCE_INTEGRITY_HELPER_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_SUBRESOURCE_INTEGRITY_HELPER_H_ + +#include "third_party/blink/renderer/core/core_export.h" +#include "third_party/blink/renderer/core/inspector/console_message.h" +#include "third_party/blink/renderer/platform/loader/subresource_integrity.h" +#include "third_party/blink/renderer/platform/wtf/allocator.h" + +namespace blink { + +class ExecutionContext; + +class CORE_EXPORT SubresourceIntegrityHelper final { + STATIC_ONLY(SubresourceIntegrityHelper); + + public: + static void DoReport(ExecutionContext&, + const SubresourceIntegrity::ReportInfo&); + + static void GetConsoleMessages(const SubresourceIntegrity::ReportInfo&, + HeapVector<Member<ConsoleMessage>>*); + + static SubresourceIntegrity::IntegrityFeatures GetFeatures(ExecutionContext*); +}; + +} // namespace blink + +#endif diff --git a/chromium/third_party/blink/renderer/core/loader/text_resource_decoder_builder.cc b/chromium/third_party/blink/renderer/core/loader/text_resource_decoder_builder.cc new file mode 100644 index 00000000000..4096c05bbf8 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/text_resource_decoder_builder.cc @@ -0,0 +1,167 @@ +/* + * 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/text_resource_decoder_builder.h" + +#include <memory> +#include "third_party/blink/renderer/core/dom/document.h" +#include "third_party/blink/renderer/core/dom/dom_implementation.h" +#include "third_party/blink/renderer/core/frame/local_frame.h" +#include "third_party/blink/renderer/core/frame/settings.h" +#include "third_party/blink/renderer/platform/weborigin/security_origin.h" + +namespace blink { + +static inline bool CanReferToParentFrameEncoding( + const LocalFrame* frame, + const LocalFrame* parent_frame) { + return parent_frame && + parent_frame->GetDocument()->GetSecurityOrigin()->CanAccess( + frame->GetDocument()->GetSecurityOrigin()); +} + +namespace { + +struct LegacyEncoding { + const char* domain; + const char* encoding; +}; + +static const LegacyEncoding kEncodings[] = { + {"au", "windows-1252"}, {"az", "ISO-8859-9"}, {"bd", "windows-1252"}, + {"bg", "windows-1251"}, {"br", "windows-1252"}, {"ca", "windows-1252"}, + {"ch", "windows-1252"}, {"cn", "GBK"}, {"cz", "windows-1250"}, + {"de", "windows-1252"}, {"dk", "windows-1252"}, {"ee", "windows-1256"}, + {"eg", "windows-1257"}, {"et", "windows-1252"}, {"fi", "windows-1252"}, + {"fr", "windows-1252"}, {"gb", "windows-1252"}, {"gr", "ISO-8859-7"}, + {"hk", "Big5"}, {"hr", "windows-1250"}, {"hu", "ISO-8859-2"}, + {"il", "windows-1255"}, {"ir", "windows-1257"}, {"is", "windows-1252"}, + {"it", "windows-1252"}, {"jp", "Shift_JIS"}, {"kr", "windows-949"}, + {"lt", "windows-1256"}, {"lv", "windows-1256"}, {"mk", "windows-1251"}, + {"nl", "windows-1252"}, {"no", "windows-1252"}, {"pl", "ISO-8859-2"}, + {"pt", "windows-1252"}, {"ro", "ISO-8859-2"}, {"rs", "windows-1251"}, + {"ru", "windows-1251"}, {"se", "windows-1252"}, {"si", "ISO-8859-2"}, + {"sk", "windows-1250"}, {"th", "windows-874"}, {"tr", "ISO-8859-9"}, + {"tw", "Big5"}, {"tz", "windows-1252"}, {"ua", "windows-1251"}, + {"us", "windows-1252"}, {"vn", "windows-1258"}, {"xa", "windows-1252"}, + {"xb", "windows-1257"}}; + +static const WTF::TextEncoding GetEncodingFromDomain(const KURL& url) { + Vector<String> tokens; + url.Host().Split(".", tokens); + if (!tokens.IsEmpty()) { + auto tld = tokens.back(); + for (size_t i = 0; i < WTF_ARRAY_LENGTH(kEncodings); i++) { + if (tld == kEncodings[i].domain) + return WTF::TextEncoding(kEncodings[i].encoding); + } + } + return WTF::TextEncoding(); +} + +TextResourceDecoderOptions::ContentType DetermineContentType( + const String& mime_type) { + if (DeprecatedEqualIgnoringCase(mime_type, "text/css")) + return TextResourceDecoderOptions::kCSSContent; + if (DeprecatedEqualIgnoringCase(mime_type, "text/html")) + return TextResourceDecoderOptions::kHTMLContent; + if (DOMImplementation::IsXMLMIMEType(mime_type)) + return TextResourceDecoderOptions::kXMLContent; + return TextResourceDecoderOptions::kPlainTextContent; +} + +} // namespace + +std::unique_ptr<TextResourceDecoder> BuildTextResourceDecoderFor( + Document* document, + const AtomicString& mime_type, + const AtomicString& encoding) { + const WTF::TextEncoding encoding_from_domain = + GetEncodingFromDomain(document->Url()); + + LocalFrame* frame = document->GetFrame(); + LocalFrame* parent_frame = nullptr; + if (frame && frame->Tree().Parent() && frame->Tree().Parent()->IsLocalFrame()) + parent_frame = ToLocalFrame(frame->Tree().Parent()); + + // Set the hint encoding to the parent frame encoding only if the parent and + // the current frames share the security origin. We impose this condition + // because somebody can make a child frameg63 containing a carefully crafted + // html/javascript in one encoding that can be mistaken for hintEncoding (or + // related encoding) by an auto detector. When interpreted in the latter, it + // could be an attack vector. + // FIXME: This might be too cautious for non-7bit-encodings and we may + // consider relaxing this later after testing. + const bool use_hint_encoding = + frame && CanReferToParentFrameEncoding(frame, parent_frame); + + std::unique_ptr<TextResourceDecoder> decoder; + if (frame && frame->GetSettings()) { + const WTF::TextEncoding default_encoding = + encoding_from_domain.IsValid() + ? encoding_from_domain + : WTF::TextEncoding( + frame->GetSettings()->GetDefaultTextEncodingName()); + // Disable autodetection for XML/JSON to honor the default encoding (UTF-8) + // for unlabelled documents. + if (DOMImplementation::IsXMLMIMEType(mime_type)) { + decoder = TextResourceDecoder::Create(TextResourceDecoderOptions( + TextResourceDecoderOptions::kXMLContent, default_encoding)); + } else if (DOMImplementation::IsJSONMIMEType(mime_type)) { + decoder = TextResourceDecoder::Create(TextResourceDecoderOptions( + TextResourceDecoderOptions::kJSONContent, default_encoding)); + } else { + WTF::TextEncoding hint_encoding; + if (use_hint_encoding && + parent_frame->GetDocument()->EncodingWasDetectedHeuristically()) + hint_encoding = parent_frame->GetDocument()->Encoding(); + decoder = TextResourceDecoder::Create( + TextResourceDecoderOptions::CreateWithAutoDetection( + DetermineContentType(mime_type), default_encoding, hint_encoding, + document->Url())); + } + } else { + decoder = TextResourceDecoder::Create(TextResourceDecoderOptions( + DetermineContentType(mime_type), encoding_from_domain)); + } + DCHECK(decoder); + + if (!encoding.IsEmpty()) { + decoder->SetEncoding(WTF::TextEncoding(encoding.GetString()), + TextResourceDecoder::kEncodingFromHTTPHeader); + } else if (use_hint_encoding) { + decoder->SetEncoding(parent_frame->GetDocument()->Encoding(), + TextResourceDecoder::kEncodingFromParentFrame); + } + + return decoder; +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/loader/text_resource_decoder_builder.h b/chromium/third_party/blink/renderer/core/loader/text_resource_decoder_builder.h new file mode 100644 index 00000000000..90725de3b4c --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/text_resource_decoder_builder.h @@ -0,0 +1,49 @@ +/* + * 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. + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_TEXT_RESOURCE_DECODER_BUILDER_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_TEXT_RESOURCE_DECODER_BUILDER_H_ + +#include <memory> +#include "third_party/blink/renderer/core/html/parser/text_resource_decoder.h" +#include "third_party/blink/renderer/platform/wtf/text/atomic_string.h" + +namespace blink { + +class Document; + +CORE_EXPORT std::unique_ptr<TextResourceDecoder> BuildTextResourceDecoderFor( + Document*, + const AtomicString& mime_type, + const AtomicString& encoding); + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_TEXT_RESOURCE_DECODER_BUILDER_H_ diff --git a/chromium/third_party/blink/renderer/core/loader/text_resource_decoder_builder_test.cc b/chromium/third_party/blink/renderer/core/loader/text_resource_decoder_builder_test.cc new file mode 100644 index 00000000000..ccb0bed9de5 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/text_resource_decoder_builder_test.cc @@ -0,0 +1,48 @@ +// 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/text_resource_decoder_builder.h" + +#include <memory> +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/blink/renderer/core/testing/dummy_page_holder.h" + +namespace blink { + +static const WTF::TextEncoding DefaultEncodingForUrlAndContentType( + const char* url, + const char* content_type) { + std::unique_ptr<DummyPageHolder> page_holder = + DummyPageHolder::Create(IntSize(0, 0)); + Document& document = page_holder->GetDocument(); + document.SetURL(KURL(NullURL(), url)); + return BuildTextResourceDecoderFor(&document, content_type, g_null_atom) + ->Encoding(); +} + +static const WTF::TextEncoding DefaultEncodingForURL(const char* url) { + return DefaultEncodingForUrlAndContentType(url, "text/html"); +} + +TEST(TextResourceDecoderBuilderTest, defaultEncodingForJsonIsUTF8) { + EXPECT_EQ(WTF::TextEncoding("UTF-8"), + DefaultEncodingForUrlAndContentType( + "https://udarenieru.ru/1.2/dealers/", "application/json")); +} + +TEST(TextResourceDecoderBuilderTest, defaultEncodingComesFromTopLevelDomain) { + EXPECT_EQ(WTF::TextEncoding("Shift_JIS"), + DefaultEncodingForURL("http://tsubotaa.la.coocan.jp")); + EXPECT_EQ(WTF::TextEncoding("windows-1251"), + DefaultEncodingForURL("http://udarenieru.ru/index.php")); +} + +TEST(TextResourceDecoderBuilderTest, + NoCountryDomainURLDefaultsToLatin1Encoding) { + // Latin1 encoding is set in |TextResourceDecoder::defaultEncoding()|. + EXPECT_EQ(WTF::Latin1Encoding(), + DefaultEncodingForURL("http://arstechnica.com/about-us")); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/loader/text_track_loader.cc b/chromium/third_party/blink/renderer/core/loader/text_track_loader.cc new file mode 100644 index 00000000000..0e8c721654d --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/text_track_loader.cc @@ -0,0 +1,196 @@ +/* + * Copyright (C) 2011 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: + * 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/text_track_loader.h" + +#include "third_party/blink/public/platform/task_type.h" +#include "third_party/blink/renderer/core/dom/document.h" +#include "third_party/blink/renderer/core/inspector/console_message.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/raw_resource.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/shared_buffer.h" +#include "third_party/blink/renderer/platform/weborigin/security_origin.h" + +namespace blink { + +TextTrackLoader::TextTrackLoader(TextTrackLoaderClient& client, + Document& document) + : client_(client), + document_(document), + cue_load_timer_(document.GetTaskRunner(TaskType::kNetworking), + this, + &TextTrackLoader::CueLoadTimerFired), + state_(kLoading), + new_cues_available_(false) {} + +TextTrackLoader::~TextTrackLoader() = default; + +void TextTrackLoader::CueLoadTimerFired(TimerBase* timer) { + DCHECK_EQ(timer, &cue_load_timer_); + + if (new_cues_available_) { + new_cues_available_ = false; + client_->NewCuesAvailable(this); + } + + if (state_ >= kFinished) + client_->CueLoadingCompleted(this, state_ == kFailed); +} + +void TextTrackLoader::CancelLoad() { + ClearResource(); +} + +void TextTrackLoader::ResponseReceived(Resource*, + const ResourceResponse& response, + std::unique_ptr<WebDataConsumerHandle>) { + if (response.IsOpaqueResponseFromServiceWorker()) { + CorsPolicyPreventedLoad(GetDocument().GetSecurityOrigin(), + response.OriginalURLViaServiceWorker()); + } +} + +bool TextTrackLoader::RedirectReceived(Resource* resource, + const ResourceRequest& request, + const ResourceResponse&) { + DCHECK_EQ(GetResource(), resource); + if (resource->GetResourceRequest().GetFetchRequestMode() == + network::mojom::FetchRequestMode::kCORS || + GetDocument().GetSecurityOrigin()->CanRequest(request.Url())) { + return true; + } + + CorsPolicyPreventedLoad(GetDocument().GetSecurityOrigin(), request.Url()); + if (!cue_load_timer_.IsActive()) + cue_load_timer_.StartOneShot(TimeDelta(), FROM_HERE); + ClearResource(); + return false; +} + +void TextTrackLoader::DataReceived(Resource* resource, + const char* data, + size_t length) { + DCHECK_EQ(GetResource(), resource); + + if (state_ == kFailed) + return; + + if (!cue_parser_) + cue_parser_ = VTTParser::Create(this, GetDocument()); + + cue_parser_->ParseBytes(data, length); +} + +void TextTrackLoader::CorsPolicyPreventedLoad( + const SecurityOrigin* security_origin, + const KURL& url) { + String console_message( + "Text track from origin '" + SecurityOrigin::Create(url)->ToString() + + "' has been blocked from loading: Not at same origin as the document, " + "and parent of track element does not have a 'crossorigin' attribute. " + "Origin '" + + security_origin->ToString() + "' is therefore not allowed access."); + GetDocument().AddConsoleMessage(ConsoleMessage::Create( + kSecurityMessageSource, kErrorMessageLevel, console_message)); + state_ = kFailed; +} + +void TextTrackLoader::NotifyFinished(Resource* resource) { + DCHECK_EQ(GetResource(), resource); + if (cue_parser_) + cue_parser_->Flush(); + + if (state_ != kFailed) { + if (resource->ErrorOccurred() || !cue_parser_) + state_ = kFailed; + else + state_ = kFinished; + } + + if (!cue_load_timer_.IsActive()) + cue_load_timer_.StartOneShot(TimeDelta(), FROM_HERE); + + CancelLoad(); +} + +bool TextTrackLoader::Load(const KURL& url, + CrossOriginAttributeValue cross_origin) { + CancelLoad(); + + ResourceLoaderOptions options; + options.initiator_info.name = FetchInitiatorTypeNames::texttrack; + + FetchParameters cue_fetch_params(ResourceRequest(url), options); + + if (cross_origin != kCrossOriginAttributeNotSet) { + cue_fetch_params.SetCrossOriginAccessControl( + GetDocument().GetSecurityOrigin(), cross_origin); + } else if (!GetDocument().GetSecurityOrigin()->CanRequest(url)) { + // Text track elements without 'crossorigin' set on the parent are "No + // CORS"; report error if not same-origin. + CorsPolicyPreventedLoad(GetDocument().GetSecurityOrigin(), url); + return false; + } + + ResourceFetcher* fetcher = GetDocument().Fetcher(); + return RawResource::FetchTextTrack(cue_fetch_params, fetcher, this); +} + +void TextTrackLoader::NewCuesParsed() { + if (cue_load_timer_.IsActive()) + return; + + new_cues_available_ = true; + cue_load_timer_.StartOneShot(TimeDelta(), FROM_HERE); +} + +void TextTrackLoader::FileFailedToParse() { + state_ = kFailed; + + if (!cue_load_timer_.IsActive()) + cue_load_timer_.StartOneShot(TimeDelta(), FROM_HERE); + + CancelLoad(); +} + +void TextTrackLoader::GetNewCues( + HeapVector<Member<TextTrackCue>>& output_cues) { + DCHECK(cue_parser_); + if (cue_parser_) + cue_parser_->GetNewCues(output_cues); +} + +void TextTrackLoader::Trace(blink::Visitor* visitor) { + visitor->Trace(client_); + visitor->Trace(cue_parser_); + visitor->Trace(document_); + RawResourceClient::Trace(visitor); + VTTParserClient::Trace(visitor); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/loader/text_track_loader.h b/chromium/third_party/blink/renderer/core/loader/text_track_loader.h new file mode 100644 index 00000000000..7547f531dba --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/text_track_loader.h @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2011 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS 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 APPLE INC. OR ITS 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_TEXT_TRACK_LOADER_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_TEXT_TRACK_LOADER_H_ + +#include "third_party/blink/renderer/core/html/track/vtt/vtt_parser.h" +#include "third_party/blink/renderer/platform/cross_origin_attribute_value.h" +#include "third_party/blink/renderer/platform/heap/handle.h" +#include "third_party/blink/renderer/platform/loader/fetch/raw_resource.h" + +namespace blink { + +class Document; +class TextTrackLoader; + +class TextTrackLoaderClient : public GarbageCollectedMixin { + public: + virtual ~TextTrackLoaderClient() = default; + + virtual void NewCuesAvailable(TextTrackLoader*) = 0; + virtual void CueLoadingCompleted(TextTrackLoader*, bool loading_failed) = 0; +}; + +class TextTrackLoader final : public GarbageCollectedFinalized<TextTrackLoader>, + public RawResourceClient, + private VTTParserClient { + USING_GARBAGE_COLLECTED_MIXIN(TextTrackLoader); + + public: + static TextTrackLoader* Create(TextTrackLoaderClient& client, + Document& document) { + return new TextTrackLoader(client, document); + } + ~TextTrackLoader() override; + + bool Load(const KURL&, CrossOriginAttributeValue); + void CancelLoad(); + + enum State { kLoading, kFinished, kFailed }; + State LoadState() { return state_; } + + void GetNewCues(HeapVector<Member<TextTrackCue>>& output_cues); + + void Trace(blink::Visitor*) override; + + private: + // RawResourceClient + void ResponseReceived(Resource*, + const ResourceResponse&, + std::unique_ptr<WebDataConsumerHandle>) override; + bool RedirectReceived(Resource*, + const ResourceRequest&, + const ResourceResponse&) override; + void DataReceived(Resource*, const char* data, size_t length) override; + void NotifyFinished(Resource*) override; + String DebugName() const override { return "TextTrackLoader"; } + + // VTTParserClient + void NewCuesParsed() override; + void FileFailedToParse() override; + + TextTrackLoader(TextTrackLoaderClient&, Document&); + + void CueLoadTimerFired(TimerBase*); + void CorsPolicyPreventedLoad(const SecurityOrigin*, const KURL&); + + Document& GetDocument() const { return *document_; } + + Member<TextTrackLoaderClient> client_; + Member<VTTParser> cue_parser_; + // FIXME: Remove this pointer and get the Document from m_client. + Member<Document> document_; + TaskRunnerTimer<TextTrackLoader> cue_load_timer_; + State state_; + bool new_cues_available_; +}; + +} // namespace blink + +#endif diff --git a/chromium/third_party/blink/renderer/core/loader/threadable_loader.cc b/chromium/third_party/blink/renderer/core/loader/threadable_loader.cc new file mode 100644 index 00000000000..44b688f05be --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/threadable_loader.cc @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2009 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/threadable_loader.h" + +#include "third_party/blink/renderer/core/execution_context/execution_context.h" +#include "third_party/blink/renderer/core/loader/document_threadable_loader.h" +#include "third_party/blink/renderer/core/loader/threadable_loading_context.h" +#include "third_party/blink/renderer/core/workers/worker_global_scope.h" + +namespace blink { + +ThreadableLoader* ThreadableLoader::Create( + ExecutionContext& context, + ThreadableLoaderClient* client, + const ThreadableLoaderOptions& options, + const ResourceLoaderOptions& resource_loader_options) { + DCHECK(client); + if (context.IsWorkerGlobalScope()) + ToWorkerGlobalScope(&context)->EnsureFetcher(); + return DocumentThreadableLoader::Create( + *ThreadableLoadingContext::Create(context), client, options, + resource_loader_options); +} + +void ThreadableLoader::LoadResourceSynchronously( + ExecutionContext& context, + const ResourceRequest& request, + ThreadableLoaderClient& client, + const ThreadableLoaderOptions& options, + const ResourceLoaderOptions& resource_loader_options) { + DocumentThreadableLoader::LoadResourceSynchronously( + *ThreadableLoadingContext::Create(context), request, client, options, + resource_loader_options); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/loader/threadable_loader.h b/chromium/third_party/blink/renderer/core/loader/threadable_loader.h new file mode 100644 index 00000000000..c0f81603c1f --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/threadable_loader.h @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2009 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. + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_THREADABLE_LOADER_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_THREADABLE_LOADER_H_ + +#include <memory> + +#include "base/macros.h" +#include "third_party/blink/renderer/core/core_export.h" +#include "third_party/blink/renderer/platform/cross_thread_copier.h" +#include "third_party/blink/renderer/platform/heap/handle.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource_loader_options.h" +#include "third_party/blink/renderer/platform/wtf/allocator.h" + +namespace blink { + +class ResourceRequest; +class ExecutionContext; +class ThreadableLoaderClient; + +struct ThreadableLoaderOptions { + DISALLOW_NEW(); + ThreadableLoaderOptions() : timeout_milliseconds(0) {} + + // When adding members, CrossThreadThreadableLoaderOptionsData should + // be updated. + + unsigned long timeout_milliseconds; +}; + +// Encode AtomicString as String to cross threads. +struct CrossThreadThreadableLoaderOptionsData { + STACK_ALLOCATED(); + explicit CrossThreadThreadableLoaderOptionsData( + const ThreadableLoaderOptions& options) + : timeout_milliseconds(options.timeout_milliseconds) {} + + operator ThreadableLoaderOptions() const { + ThreadableLoaderOptions options; + options.timeout_milliseconds = timeout_milliseconds; + return options; + } + + unsigned long timeout_milliseconds; +}; + +template <> +struct CrossThreadCopier<ThreadableLoaderOptions> { + typedef CrossThreadThreadableLoaderOptionsData Type; + static Type Copy(const ThreadableLoaderOptions& options) { + return CrossThreadThreadableLoaderOptionsData(options); + } +}; + +// Useful for doing loader operations from any thread (not threadsafe, just able +// to run on threads other than the main thread). +// +// Arguments common to both loadResourceSynchronously() and create(): +// +// - ThreadableLoaderOptions argument configures this ThreadableLoader's +// behavior. +// +// - ResourceLoaderOptions argument will be passed to the FetchParameters +// that this ThreadableLoader creates. It can be altered e.g. when +// redirect happens. +class CORE_EXPORT ThreadableLoader + : public GarbageCollectedFinalized<ThreadableLoader> { + public: + static void LoadResourceSynchronously(ExecutionContext&, + const ResourceRequest&, + ThreadableLoaderClient&, + const ThreadableLoaderOptions&, + const ResourceLoaderOptions&); + + // This method never returns nullptr. + // + // This method must always be followed by start() call. + // ThreadableLoaderClient methods are never called before start() call. + // + // The async loading feature is separated into the create() method and + // and the start() method in order to: + // - reduce work done in a constructor + // - not to ask the users to handle failures in the constructor and other + // async failures separately + // + // Loading completes when one of the following methods are called: + // - didFinishLoading() + // - didFail() + // - didFailAccessControlCheck() + // - didFailRedirectCheck() + // After any of these methods is called, the loader won't call any of the + // ThreadableLoaderClient methods. + // + // A user must guarantee that the loading completes before the attached + // client gets invalid. Also, a user must guarantee that the loading + // completes before the ThreadableLoader is destructed. + // + // When ThreadableLoader::cancel() is called, + // ThreadableLoaderClient::didFail() is called with a ResourceError + // with isCancellation() returning true, if any of didFinishLoading() + // or didFail.*() methods have not been called yet. (didFail() may be + // called with a ResourceError with isCancellation() returning true + // also for cancellation happened inside the loader.) + // + // ThreadableLoaderClient methods may call cancel(). + static ThreadableLoader* Create(ExecutionContext&, + ThreadableLoaderClient*, + const ThreadableLoaderOptions&, + const ResourceLoaderOptions&); + + // The methods on the ThreadableLoaderClient passed on create() call + // may be called synchronous to start() call. + virtual void Start(const ResourceRequest&) = 0; + + // A ThreadableLoader may have a timeout specified. It is possible, in some + // cases, for the timeout to be overridden after the request is sent (for + // example, XMLHttpRequests may override their timeout setting after sending). + // + // Set a new timeout relative to the time the request started, in + // milliseconds. + virtual void OverrideTimeout(unsigned long timeout_milliseconds) = 0; + + // Cancel the request. + virtual void Cancel() = 0; + + // Detach the loader from the request. This function is for "keepalive" + // requests. No notification will be sent to the client, but the request + // will be processed. + virtual void Detach() = 0; + + virtual ~ThreadableLoader() = default; + + virtual void Trace(blink::Visitor* visitor) {} + + protected: + ThreadableLoader() = default; + + DISALLOW_COPY_AND_ASSIGN(ThreadableLoader); +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_THREADABLE_LOADER_H_ diff --git a/chromium/third_party/blink/renderer/core/loader/threadable_loader_client.h b/chromium/third_party/blink/renderer/core/loader/threadable_loader_client.h new file mode 100644 index 00000000000..57378680681 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/threadable_loader_client.h @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2011 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. + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_THREADABLE_LOADER_CLIENT_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_THREADABLE_LOADER_CLIENT_H_ + +#include <memory> + +#include "base/macros.h" +#include "third_party/blink/public/platform/web_data_consumer_handle.h" +#include "third_party/blink/renderer/core/core_export.h" +#include "third_party/blink/renderer/platform/blob/blob_data.h" +#include "third_party/blink/renderer/platform/heap/handle.h" + +namespace blink { + +class KURL; +class ResourceError; +class ResourceResponse; +class ResourceTimingInfo; + +class CORE_EXPORT ThreadableLoaderClient { + public: + virtual void DidSendData(unsigned long long /*bytesSent*/, + unsigned long long /*totalBytesToBeSent*/) {} + virtual void DidReceiveRedirectTo(const KURL&) {} + virtual void DidReceiveResponse(unsigned long /*identifier*/, + const ResourceResponse&, + std::unique_ptr<WebDataConsumerHandle>) {} + virtual void DidReceiveData(const char*, unsigned /*dataLength*/) {} + virtual void DidReceiveCachedMetadata(const char*, int /*dataLength*/) {} + virtual void DidFinishLoading(unsigned long /*identifier*/, + double /*finishTime*/) {} + virtual void DidFail(const ResourceError&) {} + virtual void DidFailRedirectCheck() {} + virtual void DidReceiveResourceTiming(const ResourceTimingInfo&) {} + + virtual bool IsDocumentThreadableLoaderClient() { return false; } + + virtual void DidDownloadData(int /*dataLength*/) {} + // Called for requests that had DownloadToBlob set to true. Can be called with + // null if creating the blob failed for some reason (but the download itself + // otherwise succeeded). Could also not be called at all if the downloaded + // resource ended up being zero bytes. + virtual void DidDownloadToBlob(scoped_refptr<BlobDataHandle>) {} + + virtual ~ThreadableLoaderClient() = default; + + protected: + ThreadableLoaderClient() = default; + + DISALLOW_COPY_AND_ASSIGN(ThreadableLoaderClient); +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_THREADABLE_LOADER_CLIENT_H_ diff --git a/chromium/third_party/blink/renderer/core/loader/threadable_loader_test.cc b/chromium/third_party/blink/renderer/core/loader/threadable_loader_test.cc new file mode 100644 index 00000000000..f974752c039 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/threadable_loader_test.cc @@ -0,0 +1,897 @@ +// 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/threadable_loader.h" + +#include <memory> + +#include "base/memory/ptr_util.h" +#include "base/memory/scoped_refptr.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/blink/public/platform/platform.h" +#include "third_party/blink/public/platform/task_type.h" +#include "third_party/blink/public/platform/web_url_load_timing.h" +#include "third_party/blink/public/platform/web_url_loader_mock_factory.h" +#include "third_party/blink/public/platform/web_url_request.h" +#include "third_party/blink/public/platform/web_url_response.h" +#include "third_party/blink/public/platform/web_worker_fetch_context.h" +#include "third_party/blink/renderer/core/loader/document_threadable_loader.h" +#include "third_party/blink/renderer/core/loader/threadable_loader_client.h" +#include "third_party/blink/renderer/core/loader/threadable_loading_context.h" +#include "third_party/blink/renderer/core/loader/worker_fetch_context.h" +#include "third_party/blink/renderer/core/loader/worker_threadable_loader.h" +#include "third_party/blink/renderer/core/testing/dummy_page_holder.h" +#include "third_party/blink/renderer/core/workers/worker_reporting_proxy.h" +#include "third_party/blink/renderer/core/workers/worker_thread_test_helper.h" +#include "third_party/blink/renderer/platform/geometry/int_size.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_loader_options.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/fetch/resource_timing_info.h" +#include "third_party/blink/renderer/platform/loader/testing/web_url_loader_factory_with_mock.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/waitable_event.h" +#include "third_party/blink/renderer/platform/weborigin/kurl.h" +#include "third_party/blink/renderer/platform/weborigin/security_origin.h" +#include "third_party/blink/renderer/platform/wtf/assertions.h" +#include "third_party/blink/renderer/platform/wtf/functional.h" +#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h" + +namespace blink { + +namespace { + +using testing::_; +using testing::InSequence; +using testing::InvokeWithoutArgs; +using testing::StrEq; +using testing::Truly; +using Checkpoint = testing::StrictMock<testing::MockFunction<void(int)>>; + +constexpr char kFileName[] = "fox-null-terminated.html"; + +class MockThreadableLoaderClient : public ThreadableLoaderClient { + public: + static std::unique_ptr<MockThreadableLoaderClient> Create() { + return base::WrapUnique( + new testing::StrictMock<MockThreadableLoaderClient>); + } + MOCK_METHOD2(DidSendData, void(unsigned long long, unsigned long long)); + MOCK_METHOD3(DidReceiveResponseMock, + void(unsigned long, + const ResourceResponse&, + WebDataConsumerHandle*)); + void DidReceiveResponse(unsigned long identifier, + const ResourceResponse& response, + std::unique_ptr<WebDataConsumerHandle> handle) { + DidReceiveResponseMock(identifier, response, handle.get()); + } + MOCK_METHOD2(DidReceiveData, void(const char*, unsigned)); + MOCK_METHOD2(DidReceiveCachedMetadata, void(const char*, int)); + MOCK_METHOD2(DidFinishLoading, void(unsigned long, double)); + MOCK_METHOD1(DidFail, void(const ResourceError&)); + MOCK_METHOD0(DidFailRedirectCheck, void()); + MOCK_METHOD1(DidReceiveResourceTiming, void(const ResourceTimingInfo&)); + MOCK_METHOD1(DidDownloadData, void(int)); + + protected: + MockThreadableLoaderClient() = default; +}; + +bool IsCancellation(const ResourceError& error) { + return error.IsCancellation(); +} + +bool IsNotCancellation(const ResourceError& error) { + return !error.IsCancellation(); +} + +KURL SuccessURL() { + return KURL("http://example.com/success").Copy(); +} +KURL ErrorURL() { + return KURL("http://example.com/error").Copy(); +} +KURL RedirectURL() { + return KURL("http://example.com/redirect").Copy(); +} +KURL RedirectLoopURL() { + return KURL("http://example.com/loop").Copy(); +} + +void ServeAsynchronousRequests() { + Platform::Current()->GetURLLoaderMockFactory()->ServeAsynchronousRequests(); +} + +void UnregisterAllURLsAndClearMemoryCache() { + Platform::Current() + ->GetURLLoaderMockFactory() + ->UnregisterAllURLsAndClearMemoryCache(); +} + +void SetUpSuccessURL() { + URLTestHelpers::RegisterMockedURLLoad( + SuccessURL(), test::CoreTestDataPath(kFileName), "text/html"); +} + +void SetUpErrorURL() { + URLTestHelpers::RegisterMockedErrorURLLoad(ErrorURL()); +} + +void SetUpRedirectURL() { + KURL url = RedirectURL(); + + WebURLLoadTiming timing; + timing.Initialize(); + + WebURLResponse response; + response.SetURL(url); + response.SetHTTPStatusCode(301); + response.SetLoadTiming(timing); + response.AddHTTPHeaderField("Location", SuccessURL().GetString()); + response.AddHTTPHeaderField("Access-Control-Allow-Origin", "null"); + + URLTestHelpers::RegisterMockedURLLoadWithCustomResponse( + url, test::CoreTestDataPath(kFileName), response); +} + +void SetUpRedirectLoopURL() { + KURL url = RedirectLoopURL(); + + WebURLLoadTiming timing; + timing.Initialize(); + + WebURLResponse response; + response.SetURL(url); + response.SetHTTPStatusCode(301); + response.SetLoadTiming(timing); + response.AddHTTPHeaderField("Location", RedirectLoopURL().GetString()); + response.AddHTTPHeaderField("Access-Control-Allow-Origin", "null"); + + URLTestHelpers::RegisterMockedURLLoadWithCustomResponse( + url, test::CoreTestDataPath(kFileName), response); +} + +void SetUpMockURLs() { + SetUpSuccessURL(); + SetUpErrorURL(); + SetUpRedirectURL(); + SetUpRedirectLoopURL(); +} + +enum ThreadableLoaderToTest { + kDocumentThreadableLoaderTest, + kWorkerThreadableLoaderTest, +}; + +class ThreadableLoaderTestHelper { + public: + virtual ~ThreadableLoaderTestHelper() = default; + + virtual void CreateLoader(ThreadableLoaderClient*) = 0; + virtual void StartLoader(const ResourceRequest&) = 0; + virtual void CancelLoader() = 0; + virtual void CancelAndClearLoader() = 0; + virtual void ClearLoader() = 0; + virtual Checkpoint& GetCheckpoint() = 0; + virtual void CallCheckpoint(int) = 0; + virtual void OnSetUp() = 0; + virtual void OnServeRequests() = 0; + virtual void OnTearDown() = 0; +}; + +class DocumentThreadableLoaderTestHelper : public ThreadableLoaderTestHelper { + public: + DocumentThreadableLoaderTestHelper() + : dummy_page_holder_(DummyPageHolder::Create(IntSize(1, 1))) {} + + void CreateLoader(ThreadableLoaderClient* client) override { + ThreadableLoaderOptions options; + ResourceLoaderOptions resource_loader_options; + loader_ = DocumentThreadableLoader::Create( + *ThreadableLoadingContext::Create(GetDocument()), client, options, + resource_loader_options); + } + + void StartLoader(const ResourceRequest& request) override { + loader_->Start(request); + } + + void CancelLoader() override { loader_->Cancel(); } + void CancelAndClearLoader() override { + loader_->Cancel(); + loader_ = nullptr; + } + void ClearLoader() override { loader_ = nullptr; } + Checkpoint& GetCheckpoint() override { return checkpoint_; } + void CallCheckpoint(int n) override { checkpoint_.Call(n); } + + void OnSetUp() override { SetUpMockURLs(); } + + void OnServeRequests() override { ServeAsynchronousRequests(); } + + void OnTearDown() override { + if (loader_) { + loader_->Cancel(); + loader_ = nullptr; + } + UnregisterAllURLsAndClearMemoryCache(); + } + + private: + Document& GetDocument() { return dummy_page_holder_->GetDocument(); } + + std::unique_ptr<DummyPageHolder> dummy_page_holder_; + Checkpoint checkpoint_; + Persistent<DocumentThreadableLoader> loader_; +}; + +class WebWorkerFetchContextForTest : public WebWorkerFetchContext { + public: + WebWorkerFetchContextForTest(KURL site_for_cookies) + : site_for_cookies_(site_for_cookies.Copy()) {} + void SetTerminateSyncLoadEvent(base::WaitableEvent*) override {} + void InitializeOnWorkerThread() override {} + + std::unique_ptr<WebURLLoaderFactory> CreateURLLoaderFactory() override { + return std::make_unique<WebURLLoaderFactoryWithMock>( + Platform::Current()->GetURLLoaderMockFactory()); + } + std::unique_ptr<WebURLLoaderFactory> WrapURLLoaderFactory( + mojo::ScopedMessagePipeHandle) override { + return std::make_unique<WebURLLoaderFactoryWithMock>( + Platform::Current()->GetURLLoaderMockFactory()); + } + + void WillSendRequest(WebURLRequest&) override {} + bool IsControlledByServiceWorker() const override { return false; } + WebURL SiteForCookies() const override { return site_for_cookies_; } + + private: + WebURL site_for_cookies_; + + DISALLOW_COPY_AND_ASSIGN(WebWorkerFetchContextForTest); +}; + +class WorkerThreadableLoaderTestHelper : public ThreadableLoaderTestHelper { + public: + WorkerThreadableLoaderTestHelper() + : dummy_page_holder_(DummyPageHolder::Create(IntSize(1, 1))) {} + + void CreateLoader(ThreadableLoaderClient* client) override { + std::unique_ptr<WaitableEvent> completion_event = + std::make_unique<WaitableEvent>(); + PostCrossThreadTask( + *worker_loading_task_runner_, FROM_HERE, + CrossThreadBind(&WorkerThreadableLoaderTestHelper::WorkerCreateLoader, + CrossThreadUnretained(this), + CrossThreadUnretained(client), + CrossThreadUnretained(completion_event.get()))); + completion_event->Wait(); + } + + void StartLoader(const ResourceRequest& request) override { + std::unique_ptr<WaitableEvent> completion_event = + std::make_unique<WaitableEvent>(); + PostCrossThreadTask( + *worker_loading_task_runner_, FROM_HERE, + CrossThreadBind(&WorkerThreadableLoaderTestHelper::WorkerStartLoader, + CrossThreadUnretained(this), + CrossThreadUnretained(completion_event.get()), + request)); + completion_event->Wait(); + } + + // Must be called on the worker thread. + void CancelLoader() override { + DCHECK(worker_thread_); + DCHECK(worker_thread_->IsCurrentThread()); + loader_->Cancel(); + } + + void CancelAndClearLoader() override { + DCHECK(worker_thread_); + DCHECK(worker_thread_->IsCurrentThread()); + loader_->Cancel(); + loader_ = nullptr; + } + + // Must be called on the worker thread. + void ClearLoader() override { + DCHECK(worker_thread_); + DCHECK(worker_thread_->IsCurrentThread()); + loader_ = nullptr; + } + + Checkpoint& GetCheckpoint() override { return checkpoint_; } + + void CallCheckpoint(int n) override { + test::RunPendingTasks(); + + std::unique_ptr<WaitableEvent> completion_event = + std::make_unique<WaitableEvent>(); + PostCrossThreadTask( + *worker_loading_task_runner_, FROM_HERE, + CrossThreadBind(&WorkerThreadableLoaderTestHelper::WorkerCallCheckpoint, + CrossThreadUnretained(this), + CrossThreadUnretained(completion_event.get()), n)); + completion_event->Wait(); + } + + void OnSetUp() override { + reporting_proxy_ = std::make_unique<WorkerReportingProxy>(); + security_origin_ = GetDocument().GetSecurityOrigin(); + parent_execution_context_task_runners_ = + ParentExecutionContextTaskRunners::Create(&GetDocument()); + worker_thread_ = std::make_unique<WorkerThreadForTest>( + ThreadableLoadingContext::Create(GetDocument()), *reporting_proxy_); + WorkerClients* worker_clients = WorkerClients::Create(); + + ProvideWorkerFetchContextToWorker( + worker_clients, std::make_unique<WebWorkerFetchContextForTest>( + GetDocument().SiteForCookies())); + worker_thread_->StartWithSourceCode( + security_origin_.get(), "//fake source code", + parent_execution_context_task_runners_.Get(), GetDocument().Url(), + worker_clients); + worker_thread_->WaitForInit(); + worker_loading_task_runner_ = + worker_thread_->GetTaskRunner(TaskType::kInternalTest); + + PostCrossThreadTask(*worker_loading_task_runner_, FROM_HERE, + CrossThreadBind(&SetUpMockURLs)); + WaitForWorkerThreadSignal(); + } + + void OnServeRequests() override { + test::RunPendingTasks(); + PostCrossThreadTask(*worker_loading_task_runner_, FROM_HERE, + CrossThreadBind(&ServeAsynchronousRequests)); + WaitForWorkerThreadSignal(); + } + + void OnTearDown() override { + PostCrossThreadTask( + *worker_loading_task_runner_, FROM_HERE, + CrossThreadBind(&WorkerThreadableLoaderTestHelper::ClearLoader, + CrossThreadUnretained(this))); + WaitForWorkerThreadSignal(); + PostCrossThreadTask(*worker_loading_task_runner_, FROM_HERE, + CrossThreadBind(&UnregisterAllURLsAndClearMemoryCache)); + WaitForWorkerThreadSignal(); + + worker_thread_->Terminate(); + worker_thread_->WaitForShutdownForTesting(); + + // Needed to clean up the things on the main thread side and + // avoid Resource leaks. + test::RunPendingTasks(); + } + + private: + Document& GetDocument() { return dummy_page_holder_->GetDocument(); } + + void WorkerCreateLoader(ThreadableLoaderClient* client, + WaitableEvent* event) { + DCHECK(worker_thread_); + DCHECK(worker_thread_->IsCurrentThread()); + + ThreadableLoaderOptions options; + ResourceLoaderOptions resource_loader_options; + + // Ensure that WorkerThreadableLoader is created. + // ThreadableLoader::create() determines whether it should create + // a DocumentThreadableLoader or WorkerThreadableLoader based on + // isWorkerGlobalScope(). + DCHECK(worker_thread_->GlobalScope()->IsWorkerGlobalScope()); + + loader_ = ThreadableLoader::Create(*worker_thread_->GlobalScope(), client, + options, resource_loader_options); + DCHECK(loader_); + event->Signal(); + } + + void WorkerStartLoader( + WaitableEvent* event, + std::unique_ptr<CrossThreadResourceRequestData> request_data) { + DCHECK(worker_thread_); + DCHECK(worker_thread_->IsCurrentThread()); + + ResourceRequest request(request_data.get()); + request.SetFetchCredentialsMode( + network::mojom::FetchCredentialsMode::kOmit); + loader_->Start(request); + event->Signal(); + } + + void WorkerCallCheckpoint(WaitableEvent* event, int n) { + DCHECK(worker_thread_); + DCHECK(worker_thread_->IsCurrentThread()); + checkpoint_.Call(n); + event->Signal(); + } + + void WaitForWorkerThreadSignal() { + WaitableEvent event; + PostCrossThreadTask( + *worker_loading_task_runner_, FROM_HERE, + CrossThreadBind(&WaitableEvent::Signal, CrossThreadUnretained(&event))); + event.Wait(); + } + + scoped_refptr<const SecurityOrigin> security_origin_; + std::unique_ptr<WorkerReportingProxy> reporting_proxy_; + std::unique_ptr<WorkerThreadForTest> worker_thread_; + + std::unique_ptr<DummyPageHolder> dummy_page_holder_; + // Accessed cross-thread when worker thread posts tasks to the parent. + CrossThreadPersistent<ParentExecutionContextTaskRunners> + parent_execution_context_task_runners_; + scoped_refptr<base::SingleThreadTaskRunner> worker_loading_task_runner_; + Checkpoint checkpoint_; + // |m_loader| must be touched only from the worker thread only. + CrossThreadPersistent<ThreadableLoader> loader_; +}; + +class ThreadableLoaderTest + : public testing::TestWithParam<ThreadableLoaderToTest> { + public: + ThreadableLoaderTest() { + switch (GetParam()) { + case kDocumentThreadableLoaderTest: + helper_ = std::make_unique<DocumentThreadableLoaderTestHelper>(); + break; + case kWorkerThreadableLoaderTest: + helper_ = std::make_unique<WorkerThreadableLoaderTestHelper>(); + break; + } + } + + void StartLoader(const KURL& url, + network::mojom::FetchRequestMode fetch_request_mode = + network::mojom::FetchRequestMode::kNoCORS) { + ResourceRequest request(url); + request.SetRequestContext(WebURLRequest::kRequestContextObject); + request.SetFetchRequestMode(fetch_request_mode); + request.SetFetchCredentialsMode( + network::mojom::FetchCredentialsMode::kOmit); + helper_->StartLoader(request); + } + + void CancelLoader() { helper_->CancelLoader(); } + void CancelAndClearLoader() { helper_->CancelAndClearLoader(); } + void ClearLoader() { helper_->ClearLoader(); } + Checkpoint& GetCheckpoint() { return helper_->GetCheckpoint(); } + void CallCheckpoint(int n) { helper_->CallCheckpoint(n); } + + void ServeRequests() { + helper_->OnServeRequests(); + } + + void CreateLoader() { helper_->CreateLoader(Client()); } + + MockThreadableLoaderClient* Client() const { return client_.get(); } + + private: + void SetUp() override { + client_ = MockThreadableLoaderClient::Create(); + helper_->OnSetUp(); + } + + void TearDown() override { + helper_->OnTearDown(); + client_.reset(); + } + std::unique_ptr<MockThreadableLoaderClient> client_; + std::unique_ptr<ThreadableLoaderTestHelper> helper_; +}; + +INSTANTIATE_TEST_CASE_P(Document, + ThreadableLoaderTest, + testing::Values(kDocumentThreadableLoaderTest)); + +INSTANTIATE_TEST_CASE_P(Worker, + ThreadableLoaderTest, + testing::Values(kWorkerThreadableLoaderTest)); + +TEST_P(ThreadableLoaderTest, StartAndStop) {} + +TEST_P(ThreadableLoaderTest, CancelAfterStart) { + InSequence s; + EXPECT_CALL(GetCheckpoint(), Call(1)); + CreateLoader(); + CallCheckpoint(1); + + EXPECT_CALL(GetCheckpoint(), Call(2)) + .WillOnce(InvokeWithoutArgs(this, &ThreadableLoaderTest::CancelLoader)); + EXPECT_CALL(*Client(), DidFail(Truly(IsCancellation))); + EXPECT_CALL(GetCheckpoint(), Call(3)); + + StartLoader(SuccessURL()); + CallCheckpoint(2); + CallCheckpoint(3); + ServeRequests(); +} + +TEST_P(ThreadableLoaderTest, CancelAndClearAfterStart) { + InSequence s; + EXPECT_CALL(GetCheckpoint(), Call(1)); + CreateLoader(); + CallCheckpoint(1); + + EXPECT_CALL(GetCheckpoint(), Call(2)) + .WillOnce( + InvokeWithoutArgs(this, &ThreadableLoaderTest::CancelAndClearLoader)); + EXPECT_CALL(*Client(), DidFail(Truly(IsCancellation))); + EXPECT_CALL(GetCheckpoint(), Call(3)); + + StartLoader(SuccessURL()); + CallCheckpoint(2); + CallCheckpoint(3); + ServeRequests(); +} + +TEST_P(ThreadableLoaderTest, CancelInDidReceiveResponse) { + InSequence s; + EXPECT_CALL(GetCheckpoint(), Call(1)); + CreateLoader(); + CallCheckpoint(1); + + EXPECT_CALL(GetCheckpoint(), Call(2)); + EXPECT_CALL(*Client(), DidReceiveResponseMock(_, _, _)) + .WillOnce(InvokeWithoutArgs(this, &ThreadableLoaderTest::CancelLoader)); + EXPECT_CALL(*Client(), DidFail(Truly(IsCancellation))); + + StartLoader(SuccessURL()); + CallCheckpoint(2); + ServeRequests(); +} + +TEST_P(ThreadableLoaderTest, CancelAndClearInDidReceiveResponse) { + InSequence s; + EXPECT_CALL(GetCheckpoint(), Call(1)); + CreateLoader(); + CallCheckpoint(1); + + EXPECT_CALL(GetCheckpoint(), Call(2)); + EXPECT_CALL(*Client(), DidReceiveResponseMock(_, _, _)) + .WillOnce( + InvokeWithoutArgs(this, &ThreadableLoaderTest::CancelAndClearLoader)); + EXPECT_CALL(*Client(), DidFail(Truly(IsCancellation))); + + StartLoader(SuccessURL()); + CallCheckpoint(2); + ServeRequests(); +} + +TEST_P(ThreadableLoaderTest, CancelInDidReceiveData) { + InSequence s; + EXPECT_CALL(GetCheckpoint(), Call(1)); + CreateLoader(); + CallCheckpoint(1); + + EXPECT_CALL(GetCheckpoint(), Call(2)); + EXPECT_CALL(*Client(), DidReceiveResponseMock(_, _, _)); + EXPECT_CALL(*Client(), DidReceiveData(_, _)) + .WillOnce(InvokeWithoutArgs(this, &ThreadableLoaderTest::CancelLoader)); + EXPECT_CALL(*Client(), DidFail(Truly(IsCancellation))); + + StartLoader(SuccessURL()); + CallCheckpoint(2); + ServeRequests(); +} + +TEST_P(ThreadableLoaderTest, CancelAndClearInDidReceiveData) { + InSequence s; + EXPECT_CALL(GetCheckpoint(), Call(1)); + CreateLoader(); + CallCheckpoint(1); + + EXPECT_CALL(GetCheckpoint(), Call(2)); + EXPECT_CALL(*Client(), DidReceiveResponseMock(_, _, _)); + EXPECT_CALL(*Client(), DidReceiveData(_, _)) + .WillOnce( + InvokeWithoutArgs(this, &ThreadableLoaderTest::CancelAndClearLoader)); + EXPECT_CALL(*Client(), DidFail(Truly(IsCancellation))); + + StartLoader(SuccessURL()); + CallCheckpoint(2); + ServeRequests(); +} + +TEST_P(ThreadableLoaderTest, DidFinishLoading) { + InSequence s; + EXPECT_CALL(GetCheckpoint(), Call(1)); + CreateLoader(); + CallCheckpoint(1); + + EXPECT_CALL(GetCheckpoint(), Call(2)); + EXPECT_CALL(*Client(), DidReceiveResponseMock(_, _, _)); + EXPECT_CALL(*Client(), DidReceiveData(StrEq("fox"), 4)); + // We expect didReceiveResourceTiming() calls in DocumentThreadableLoader; + // it's used to connect DocumentThreadableLoader to WorkerThreadableLoader, + // not to ThreadableLoaderClient. + EXPECT_CALL(*Client(), DidReceiveResourceTiming(_)); + EXPECT_CALL(*Client(), DidFinishLoading(_, _)); + + StartLoader(SuccessURL()); + CallCheckpoint(2); + ServeRequests(); +} + +TEST_P(ThreadableLoaderTest, CancelInDidFinishLoading) { + InSequence s; + EXPECT_CALL(GetCheckpoint(), Call(1)); + CreateLoader(); + CallCheckpoint(1); + + EXPECT_CALL(GetCheckpoint(), Call(2)); + EXPECT_CALL(*Client(), DidReceiveResponseMock(_, _, _)); + EXPECT_CALL(*Client(), DidReceiveData(_, _)); + EXPECT_CALL(*Client(), DidReceiveResourceTiming(_)); + EXPECT_CALL(*Client(), DidFinishLoading(_, _)) + .WillOnce(InvokeWithoutArgs(this, &ThreadableLoaderTest::CancelLoader)); + + StartLoader(SuccessURL()); + CallCheckpoint(2); + ServeRequests(); +} + +TEST_P(ThreadableLoaderTest, ClearInDidFinishLoading) { + InSequence s; + EXPECT_CALL(GetCheckpoint(), Call(1)); + CreateLoader(); + CallCheckpoint(1); + + EXPECT_CALL(GetCheckpoint(), Call(2)); + EXPECT_CALL(*Client(), DidReceiveResponseMock(_, _, _)); + EXPECT_CALL(*Client(), DidReceiveData(_, _)); + EXPECT_CALL(*Client(), DidReceiveResourceTiming(_)); + EXPECT_CALL(*Client(), DidFinishLoading(_, _)) + .WillOnce(InvokeWithoutArgs(this, &ThreadableLoaderTest::ClearLoader)); + + StartLoader(SuccessURL()); + CallCheckpoint(2); + ServeRequests(); +} + +TEST_P(ThreadableLoaderTest, DidFail) { + InSequence s; + EXPECT_CALL(GetCheckpoint(), Call(1)); + CreateLoader(); + CallCheckpoint(1); + + EXPECT_CALL(GetCheckpoint(), Call(2)); + EXPECT_CALL(*Client(), DidReceiveResponseMock(_, _, _)); + EXPECT_CALL(*Client(), DidFail(Truly(IsNotCancellation))); + + StartLoader(ErrorURL()); + CallCheckpoint(2); + ServeRequests(); +} + +TEST_P(ThreadableLoaderTest, CancelInDidFail) { + InSequence s; + EXPECT_CALL(GetCheckpoint(), Call(1)); + CreateLoader(); + CallCheckpoint(1); + + EXPECT_CALL(GetCheckpoint(), Call(2)); + EXPECT_CALL(*Client(), DidReceiveResponseMock(_, _, _)); + EXPECT_CALL(*Client(), DidFail(_)) + .WillOnce(InvokeWithoutArgs(this, &ThreadableLoaderTest::CancelLoader)); + + StartLoader(ErrorURL()); + CallCheckpoint(2); + ServeRequests(); +} + +TEST_P(ThreadableLoaderTest, ClearInDidFail) { + InSequence s; + EXPECT_CALL(GetCheckpoint(), Call(1)); + CreateLoader(); + CallCheckpoint(1); + + EXPECT_CALL(GetCheckpoint(), Call(2)); + EXPECT_CALL(*Client(), DidReceiveResponseMock(_, _, _)); + EXPECT_CALL(*Client(), DidFail(_)) + .WillOnce(InvokeWithoutArgs(this, &ThreadableLoaderTest::ClearLoader)); + + StartLoader(ErrorURL()); + CallCheckpoint(2); + ServeRequests(); +} + +TEST_P(ThreadableLoaderTest, DidFailInStart) { + InSequence s; + EXPECT_CALL(GetCheckpoint(), Call(1)); + CreateLoader(); + CallCheckpoint(1); + + String error_message = String::Format( + "Failed to load '%s': Cross origin requests are not allowed by request " + "mode.", + ErrorURL().GetString().Utf8().data()); + EXPECT_CALL(*Client(), DidFail(ResourceError::CancelledDueToAccessCheckError( + ErrorURL(), ResourceRequestBlockedReason::kOther, + error_message))); + EXPECT_CALL(GetCheckpoint(), Call(2)); + + StartLoader(ErrorURL(), network::mojom::FetchRequestMode::kSameOrigin); + CallCheckpoint(2); + ServeRequests(); +} + +TEST_P(ThreadableLoaderTest, CancelInDidFailInStart) { + InSequence s; + EXPECT_CALL(GetCheckpoint(), Call(1)); + CreateLoader(); + CallCheckpoint(1); + + EXPECT_CALL(*Client(), DidFail(_)) + .WillOnce(InvokeWithoutArgs(this, &ThreadableLoaderTest::CancelLoader)); + EXPECT_CALL(GetCheckpoint(), Call(2)); + + StartLoader(ErrorURL(), network::mojom::FetchRequestMode::kSameOrigin); + CallCheckpoint(2); + ServeRequests(); +} + +TEST_P(ThreadableLoaderTest, ClearInDidFailInStart) { + InSequence s; + EXPECT_CALL(GetCheckpoint(), Call(1)); + CreateLoader(); + CallCheckpoint(1); + + EXPECT_CALL(*Client(), DidFail(_)) + .WillOnce(InvokeWithoutArgs(this, &ThreadableLoaderTest::ClearLoader)); + EXPECT_CALL(GetCheckpoint(), Call(2)); + + StartLoader(ErrorURL(), network::mojom::FetchRequestMode::kSameOrigin); + CallCheckpoint(2); + ServeRequests(); +} + +TEST_P(ThreadableLoaderTest, DidFailAccessControlCheck) { + InSequence s; + EXPECT_CALL(GetCheckpoint(), Call(1)); + CreateLoader(); + CallCheckpoint(1); + + EXPECT_CALL(GetCheckpoint(), Call(2)); + EXPECT_CALL( + *Client(), + DidFail(ResourceError::CancelledDueToAccessCheckError( + SuccessURL(), ResourceRequestBlockedReason::kOther, + "No 'Access-Control-Allow-Origin' header is present on the requested " + "resource. Origin 'null' is therefore not allowed access."))); + + StartLoader(SuccessURL(), network::mojom::FetchRequestMode::kCORS); + CallCheckpoint(2); + ServeRequests(); +} + +TEST_P(ThreadableLoaderTest, RedirectDidFinishLoading) { + InSequence s; + EXPECT_CALL(GetCheckpoint(), Call(1)); + CreateLoader(); + CallCheckpoint(1); + + EXPECT_CALL(GetCheckpoint(), Call(2)); + EXPECT_CALL(*Client(), DidReceiveResponseMock(_, _, _)); + EXPECT_CALL(*Client(), DidReceiveData(StrEq("fox"), 4)); + EXPECT_CALL(*Client(), DidReceiveResourceTiming(_)); + EXPECT_CALL(*Client(), DidFinishLoading(_, _)); + + StartLoader(RedirectURL()); + CallCheckpoint(2); + ServeRequests(); +} + +TEST_P(ThreadableLoaderTest, CancelInRedirectDidFinishLoading) { + InSequence s; + EXPECT_CALL(GetCheckpoint(), Call(1)); + CreateLoader(); + CallCheckpoint(1); + + EXPECT_CALL(GetCheckpoint(), Call(2)); + EXPECT_CALL(*Client(), DidReceiveResponseMock(_, _, _)); + EXPECT_CALL(*Client(), DidReceiveData(StrEq("fox"), 4)); + EXPECT_CALL(*Client(), DidReceiveResourceTiming(_)); + EXPECT_CALL(*Client(), DidFinishLoading(_, _)) + .WillOnce(InvokeWithoutArgs(this, &ThreadableLoaderTest::CancelLoader)); + + StartLoader(RedirectURL()); + CallCheckpoint(2); + ServeRequests(); +} + +TEST_P(ThreadableLoaderTest, ClearInRedirectDidFinishLoading) { + InSequence s; + EXPECT_CALL(GetCheckpoint(), Call(1)); + CreateLoader(); + CallCheckpoint(1); + + EXPECT_CALL(GetCheckpoint(), Call(2)); + EXPECT_CALL(*Client(), DidReceiveResponseMock(_, _, _)); + EXPECT_CALL(*Client(), DidReceiveData(StrEq("fox"), 4)); + EXPECT_CALL(*Client(), DidReceiveResourceTiming(_)); + EXPECT_CALL(*Client(), DidFinishLoading(_, _)) + .WillOnce(InvokeWithoutArgs(this, &ThreadableLoaderTest::ClearLoader)); + + StartLoader(RedirectURL()); + CallCheckpoint(2); + ServeRequests(); +} + +TEST_P(ThreadableLoaderTest, DidFailRedirectCheck) { + InSequence s; + EXPECT_CALL(GetCheckpoint(), Call(1)); + CreateLoader(); + CallCheckpoint(1); + + EXPECT_CALL(GetCheckpoint(), Call(2)); + EXPECT_CALL(*Client(), DidFailRedirectCheck()); + + StartLoader(RedirectLoopURL(), network::mojom::FetchRequestMode::kCORS); + CallCheckpoint(2); + ServeRequests(); +} + +TEST_P(ThreadableLoaderTest, CancelInDidFailRedirectCheck) { + InSequence s; + EXPECT_CALL(GetCheckpoint(), Call(1)); + CreateLoader(); + CallCheckpoint(1); + + EXPECT_CALL(GetCheckpoint(), Call(2)); + EXPECT_CALL(*Client(), DidFailRedirectCheck()) + .WillOnce(InvokeWithoutArgs(this, &ThreadableLoaderTest::CancelLoader)); + + StartLoader(RedirectLoopURL(), network::mojom::FetchRequestMode::kCORS); + CallCheckpoint(2); + ServeRequests(); +} + +TEST_P(ThreadableLoaderTest, ClearInDidFailRedirectCheck) { + InSequence s; + EXPECT_CALL(GetCheckpoint(), Call(1)); + CreateLoader(); + CallCheckpoint(1); + + EXPECT_CALL(GetCheckpoint(), Call(2)); + EXPECT_CALL(*Client(), DidFailRedirectCheck()) + .WillOnce(InvokeWithoutArgs(this, &ThreadableLoaderTest::ClearLoader)); + + StartLoader(RedirectLoopURL(), network::mojom::FetchRequestMode::kCORS); + CallCheckpoint(2); + ServeRequests(); +} + +// This test case checks blink doesn't crash even when the response arrives +// synchronously. +TEST_P(ThreadableLoaderTest, GetResponseSynchronously) { + InSequence s; + EXPECT_CALL(GetCheckpoint(), Call(1)); + CreateLoader(); + CallCheckpoint(1); + + EXPECT_CALL(*Client(), DidFail(_)); + EXPECT_CALL(GetCheckpoint(), Call(2)); + + // Currently didFailAccessControlCheck is dispatched synchronously. This + // test is not saying that didFailAccessControlCheck should be dispatched + // synchronously, but is saying that even when a response is served + // synchronously it should not lead to a crash. + StartLoader(KURL("about:blank"), network::mojom::FetchRequestMode::kCORS); + CallCheckpoint(2); +} + +} // namespace + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/loader/threadable_loading_context.cc b/chromium/third_party/blink/renderer/core/loader/threadable_loading_context.cc new file mode 100644 index 00000000000..f709dd0d18a --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/threadable_loading_context.cc @@ -0,0 +1,88 @@ +// Copyright 2017 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/threadable_loading_context.h" + +#include "third_party/blink/renderer/core/dom/document.h" +#include "third_party/blink/renderer/core/loader/worker_fetch_context.h" +#include "third_party/blink/renderer/core/workers/worker_global_scope.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h" + +namespace blink { + +class DocumentThreadableLoadingContext final : public ThreadableLoadingContext { + public: + explicit DocumentThreadableLoadingContext(Document& document) + : document_(&document) {} + + ~DocumentThreadableLoadingContext() override = default; + + ResourceFetcher* GetResourceFetcher() override { + DCHECK(IsContextThread()); + return document_->Fetcher(); + } + + ExecutionContext* GetExecutionContext() override { + DCHECK(IsContextThread()); + return document_.Get(); + } + + void Trace(blink::Visitor* visitor) override { + visitor->Trace(document_); + ThreadableLoadingContext::Trace(visitor); + } + + private: + bool IsContextThread() const { return document_->IsContextThread(); } + + Member<Document> document_; +}; + +class WorkerThreadableLoadingContext : public ThreadableLoadingContext { + public: + explicit WorkerThreadableLoadingContext( + WorkerGlobalScope& worker_global_scope) + : worker_global_scope_(&worker_global_scope) {} + + ~WorkerThreadableLoadingContext() override = default; + + ResourceFetcher* GetResourceFetcher() override { + DCHECK(IsContextThread()); + return worker_global_scope_->EnsureFetcher(); + } + + ExecutionContext* GetExecutionContext() override { + DCHECK(IsContextThread()); + return worker_global_scope_.Get(); + } + + void Trace(blink::Visitor* visitor) override { + visitor->Trace(worker_global_scope_); + ThreadableLoadingContext::Trace(visitor); + } + + private: + bool IsContextThread() const { + DCHECK(worker_global_scope_); + return worker_global_scope_->IsContextThread(); + } + + Member<WorkerGlobalScope> worker_global_scope_; +}; + +ThreadableLoadingContext* ThreadableLoadingContext::Create( + ExecutionContext& context) { + if (context.IsDocument()) + return new DocumentThreadableLoadingContext(ToDocument(context)); + if (context.IsWorkerGlobalScope()) + return new WorkerThreadableLoadingContext(ToWorkerGlobalScope(context)); + NOTREACHED(); + return nullptr; +} + +BaseFetchContext* ThreadableLoadingContext::GetFetchContext() { + return static_cast<BaseFetchContext*>(&GetResourceFetcher()->Context()); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/loader/threadable_loading_context.h b/chromium/third_party/blink/renderer/core/loader/threadable_loading_context.h new file mode 100644 index 00000000000..3d9b6339d58 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/threadable_loading_context.h @@ -0,0 +1,41 @@ +// Copyright 2017 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_THREADABLE_LOADING_CONTEXT_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_THREADABLE_LOADING_CONTEXT_H_ + +#include "base/macros.h" +#include "third_party/blink/renderer/core/core_export.h" +#include "third_party/blink/renderer/platform/heap/heap.h" +#include "third_party/blink/renderer/platform/wtf/forward.h" + +namespace blink { + +class BaseFetchContext; +class ExecutionContext; +class ResourceFetcher; + +// A convenient holder for various contexts associated with the loading +// activity. This should be accessed only from the thread where the loading +// context is bound to (e.g. on the main thread). +class CORE_EXPORT ThreadableLoadingContext + : public GarbageCollected<ThreadableLoadingContext> { + public: + static ThreadableLoadingContext* Create(ExecutionContext&); + + ThreadableLoadingContext() = default; + virtual ~ThreadableLoadingContext() = default; + + virtual ResourceFetcher* GetResourceFetcher() = 0; + virtual ExecutionContext* GetExecutionContext() = 0; + BaseFetchContext* GetFetchContext(); + + virtual void Trace(blink::Visitor* visitor) {} + + DISALLOW_COPY_AND_ASSIGN(ThreadableLoadingContext); +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_THREADABLE_LOADING_CONTEXT_H_ diff --git a/chromium/third_party/blink/renderer/core/loader/worker_fetch_context.cc b/chromium/third_party/blink/renderer/core/loader/worker_fetch_context.cc new file mode 100644 index 00000000000..784713e5df2 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/worker_fetch_context.cc @@ -0,0 +1,410 @@ +// Copyright 2017 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/worker_fetch_context.h" + +#include "base/single_thread_task_runner.h" +#include "third_party/blink/public/platform/platform.h" +#include "third_party/blink/public/platform/task_type.h" +#include "third_party/blink/public/platform/web_mixed_content.h" +#include "third_party/blink/public/platform/web_mixed_content_context_type.h" +#include "third_party/blink/public/platform/web_url_loader_factory.h" +#include "third_party/blink/public/platform/web_url_request.h" +#include "third_party/blink/public/platform/web_worker_fetch_context.h" +#include "third_party/blink/renderer/core/fileapi/public_url_manager.h" +#include "third_party/blink/renderer/core/frame/deprecation.h" +#include "third_party/blink/renderer/core/frame/use_counter.h" +#include "third_party/blink/renderer/core/loader/mixed_content_checker.h" +#include "third_party/blink/renderer/core/loader/subresource_filter.h" +#include "third_party/blink/renderer/core/probe/core_probes.h" +#include "third_party/blink/renderer/core/timing/worker_global_scope_performance.h" +#include "third_party/blink/renderer/core/workers/worker_clients.h" +#include "third_party/blink/renderer/core/workers/worker_global_scope.h" +#include "third_party/blink/renderer/platform/exported/wrapped_resource_request.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h" +#include "third_party/blink/renderer/platform/network/network_state_notifier.h" +#include "third_party/blink/renderer/platform/network/network_utils.h" +#include "third_party/blink/renderer/platform/runtime_enabled_features.h" +#include "third_party/blink/renderer/platform/supplementable.h" +#include "third_party/blink/renderer/platform/weborigin/security_policy.h" + +namespace blink { + +namespace { + +// WorkerFetchContextHolder is used to pass the WebWorkerFetchContext from the +// main thread to the worker thread by attaching to the WorkerClients as a +// Supplement. +class WorkerFetchContextHolder final + : public GarbageCollectedFinalized<WorkerFetchContextHolder>, + public Supplement<WorkerClients> { + USING_GARBAGE_COLLECTED_MIXIN(WorkerFetchContextHolder); + + public: + static WorkerFetchContextHolder* From(WorkerClients& clients) { + return Supplement<WorkerClients>::From<WorkerFetchContextHolder>(clients); + } + static const char kSupplementName[]; + + explicit WorkerFetchContextHolder( + std::unique_ptr<WebWorkerFetchContext> web_context) + : web_context_(std::move(web_context)) {} + virtual ~WorkerFetchContextHolder() = default; + + std::unique_ptr<WebWorkerFetchContext> TakeContext() { + return std::move(web_context_); + } + + void Trace(blink::Visitor* visitor) override { + Supplement<WorkerClients>::Trace(visitor); + } + + private: + std::unique_ptr<WebWorkerFetchContext> web_context_; +}; + +} // namespace + +// static +const char WorkerFetchContextHolder::kSupplementName[] = + "WorkerFetchContextHolder"; + +WorkerFetchContext::~WorkerFetchContext() = default; + +WorkerFetchContext* WorkerFetchContext::Create( + WorkerOrWorkletGlobalScope& global_scope) { + DCHECK(global_scope.IsContextThread()); + DCHECK(!global_scope.IsMainThreadWorkletGlobalScope()); + WorkerClients* worker_clients = global_scope.Clients(); + DCHECK(worker_clients); + WorkerFetchContextHolder* holder = + Supplement<WorkerClients>::From<WorkerFetchContextHolder>( + *worker_clients); + if (!holder) + return nullptr; + std::unique_ptr<WebWorkerFetchContext> web_context = holder->TakeContext(); + DCHECK(web_context); + return new WorkerFetchContext(global_scope, std::move(web_context)); +} + +WorkerFetchContext::WorkerFetchContext( + WorkerOrWorkletGlobalScope& global_scope, + std::unique_ptr<WebWorkerFetchContext> web_context) + : global_scope_(global_scope), + web_context_(std::move(web_context)), + loading_task_runner_( + global_scope_->GetTaskRunner(TaskType::kInternalLoading)), + save_data_enabled_(GetNetworkStateNotifier().SaveDataEnabled()) { + web_context_->InitializeOnWorkerThread(); + std::unique_ptr<blink::WebDocumentSubresourceFilter> web_filter = + web_context_->TakeSubresourceFilter(); + if (web_filter) { + subresource_filter_ = + SubresourceFilter::Create(global_scope, std::move(web_filter)); + } +} + +KURL WorkerFetchContext::GetSiteForCookies() const { + return web_context_->SiteForCookies(); +} + +SubresourceFilter* WorkerFetchContext::GetSubresourceFilter() const { + return subresource_filter_.Get(); +} + +bool WorkerFetchContext::AllowScriptFromSource(const KURL&) const { + // Currently we don't use WorkerFetchContext for loading scripts. So this + // method must not be called. + // TODO(horo): When we will use WorkerFetchContext for loading scripts, we + // need to have a copy the script rules of RendererContentSettingRules on the + // worker thread. + NOTREACHED(); + return false; +} + +bool WorkerFetchContext::ShouldBlockRequestByInspector(const KURL& url) const { + bool should_block_request = false; + probe::shouldBlockRequest(global_scope_, url, &should_block_request); + return should_block_request; +} + +void WorkerFetchContext::DispatchDidBlockRequest( + const ResourceRequest& resource_request, + const FetchInitiatorInfo& fetch_initiator_info, + ResourceRequestBlockedReason blocked_reason, + Resource::Type resource_type) const { + probe::didBlockRequest(global_scope_, resource_request, nullptr, + fetch_initiator_info, blocked_reason, resource_type); +} + +bool WorkerFetchContext::ShouldBypassMainWorldCSP() const { + // This method was introduced to bypass the page's CSP while running the + // script from an isolated world (ex: Chrome extensions). But worker threads + // doesn't have any isolated world. So we can just return false. + return false; +} + +bool WorkerFetchContext::IsSVGImageChromeClient() const { + return false; +} + +void WorkerFetchContext::CountUsage(WebFeature feature) const { + UseCounter::Count(global_scope_, feature); +} + +void WorkerFetchContext::CountDeprecation(WebFeature feature) const { + Deprecation::CountDeprecation(global_scope_, feature); +} + +bool WorkerFetchContext::ShouldBlockWebSocketByMixedContentCheck( + const KURL& url) const { + // Worklets don't support WebSocket. + DCHECK(global_scope_->IsWorkerGlobalScope()); + return !MixedContentChecker::IsWebSocketAllowed( + ToWorkerGlobalScope(global_scope_), web_context_.get(), url); +} + +bool WorkerFetchContext::ShouldBlockFetchByMixedContentCheck( + WebURLRequest::RequestContext request_context, + network::mojom::RequestContextFrameType frame_type, + ResourceRequest::RedirectStatus redirect_status, + const KURL& url, + SecurityViolationReportingPolicy reporting_policy) const { + return MixedContentChecker::ShouldBlockFetchOnWorker( + global_scope_, web_context_.get(), request_context, frame_type, + redirect_status, url, reporting_policy); +} + +bool WorkerFetchContext::ShouldBlockFetchAsCredentialedSubresource( + const ResourceRequest& resource_request, + const KURL& url) const { + if ((!url.User().IsEmpty() || !url.Pass().IsEmpty()) && + resource_request.GetRequestContext() != + WebURLRequest::kRequestContextXMLHttpRequest) { + if (Url().User() != url.User() || Url().Pass() != url.Pass()) { + CountDeprecation( + WebFeature::kRequestedSubresourceWithEmbeddedCredentials); + + // TODO(mkwst): Remove the runtime check one way or the other once we're + // sure it's going to stick (or that it's not). + if (RuntimeEnabledFeatures::BlockCredentialedSubresourcesEnabled()) + return true; + } + } + return false; +} + +ReferrerPolicy WorkerFetchContext::GetReferrerPolicy() const { + return global_scope_->GetReferrerPolicy(); +} + +String WorkerFetchContext::GetOutgoingReferrer() const { + return global_scope_->OutgoingReferrer(); +} + +const KURL& WorkerFetchContext::Url() const { + return global_scope_->Url(); +} + +const SecurityOrigin* WorkerFetchContext::GetParentSecurityOrigin() const { + // This method was introduced to check the parent frame's security context + // while loading iframe document resources. So this method is not suitable for + // workers. + NOTREACHED(); + return nullptr; +} + +Optional<mojom::IPAddressSpace> WorkerFetchContext::GetAddressSpace() const { + return WTF::make_optional(global_scope_->GetSecurityContext().AddressSpace()); +} + +const ContentSecurityPolicy* WorkerFetchContext::GetContentSecurityPolicy() + const { + return global_scope_->GetContentSecurityPolicy(); +} + +void WorkerFetchContext::AddConsoleMessage(ConsoleMessage* message) const { + return global_scope_->AddConsoleMessage(message); +} + +const SecurityOrigin* WorkerFetchContext::GetSecurityOrigin() const { + return global_scope_->GetSecurityOrigin(); +} + +std::unique_ptr<WebURLLoader> WorkerFetchContext::CreateURLLoader( + const ResourceRequest& request, + scoped_refptr<base::SingleThreadTaskRunner> task_runner, + const ResourceLoaderOptions& options) { + CountUsage(WebFeature::kOffMainThreadFetch); + WrappedResourceRequest wrapped(request); + + network::mojom::blink::URLLoaderFactoryPtr url_loader_factory; + if (options.url_loader_factory) { + options.url_loader_factory->data->Clone(MakeRequest(&url_loader_factory)); + } + // Resolve any blob: URLs that haven't been resolved yet. The XHR and fetch() + // API implementations resolve blob URLs earlier because there can be + // arbitrarily long delays between creating requests with those APIs and + // actually creating the URL loader here. Other subresource loading will + // immediately create the URL loader so resolving those blob URLs here is + // simplest. + if (request.Url().ProtocolIs("blob") && + RuntimeEnabledFeatures::MojoBlobURLsEnabled() && !url_loader_factory) { + global_scope_->GetPublicURLManager().Resolve( + request.Url(), MakeRequest(&url_loader_factory)); + } + if (url_loader_factory) { + return web_context_ + ->WrapURLLoaderFactory(url_loader_factory.PassInterface().PassHandle()) + ->CreateURLLoader(wrapped, task_runner); + } + + if (!url_loader_factory_) + url_loader_factory_ = web_context_->CreateURLLoaderFactory(); + return url_loader_factory_->CreateURLLoader(wrapped, task_runner); +} + +bool WorkerFetchContext::IsControlledByServiceWorker() const { + return web_context_->IsControlledByServiceWorker(); +} + +int WorkerFetchContext::ApplicationCacheHostID() const { + return web_context_->ApplicationCacheHostID(); +} + +void WorkerFetchContext::PrepareRequest(ResourceRequest& request, + RedirectType) { + String user_agent = global_scope_->UserAgent(); + probe::applyUserAgentOverride(global_scope_, &user_agent); + DCHECK(!user_agent.IsNull()); + request.SetHTTPUserAgent(AtomicString(user_agent)); + + WrappedResourceRequest webreq(request); + web_context_->WillSendRequest(webreq); +} + +void WorkerFetchContext::AddAdditionalRequestHeaders(ResourceRequest& request, + FetchResourceType type) { + BaseFetchContext::AddAdditionalRequestHeaders(request, type); + + // The remaining modifications are only necessary for HTTP and HTTPS. + if (!request.Url().IsEmpty() && !request.Url().ProtocolIsInHTTPFamily()) + return; + + if (save_data_enabled_) + request.SetHTTPHeaderField(HTTPNames::Save_Data, "on"); +} + +void WorkerFetchContext::DispatchWillSendRequest( + unsigned long identifier, + ResourceRequest& request, + const ResourceResponse& redirect_response, + Resource::Type resource_type, + const FetchInitiatorInfo& initiator_info) { + probe::willSendRequest(global_scope_, identifier, nullptr, request, + redirect_response, initiator_info, resource_type); +} + +void WorkerFetchContext::DispatchDidReceiveResponse( + unsigned long identifier, + const ResourceResponse& response, + network::mojom::RequestContextFrameType frame_type, + WebURLRequest::RequestContext request_context, + Resource* resource, + ResourceResponseType) { + if (response.HasMajorCertificateErrors()) { + WebMixedContentContextType context_type = + WebMixedContent::ContextTypeFromRequestContext( + request_context, false /* strictMixedContentCheckingForPlugin */); + if (context_type == WebMixedContentContextType::kBlockable) { + web_context_->DidRunContentWithCertificateErrors(); + } else { + web_context_->DidDisplayContentWithCertificateErrors(); + } + } + probe::didReceiveResourceResponse(global_scope_, identifier, nullptr, + response, resource); +} + +void WorkerFetchContext::DispatchDidReceiveData(unsigned long identifier, + const char* data, + int data_length) { + probe::didReceiveData(global_scope_, identifier, nullptr, data, data_length); +} + +void WorkerFetchContext::DispatchDidReceiveEncodedData( + unsigned long identifier, + int encoded_data_length) { + probe::didReceiveEncodedDataLength(global_scope_, nullptr, identifier, + encoded_data_length); +} + +void WorkerFetchContext::DispatchDidFinishLoading( + unsigned long identifier, + double finish_time, + int64_t encoded_data_length, + int64_t decoded_body_length, + bool blocked_cross_site_document) { + probe::didFinishLoading(global_scope_, identifier, nullptr, finish_time, + encoded_data_length, decoded_body_length, + blocked_cross_site_document); +} + +void WorkerFetchContext::DispatchDidFail(const KURL& url, + unsigned long identifier, + const ResourceError& error, + int64_t encoded_data_length, + bool is_internal_request) { + probe::didFailLoading(global_scope_, identifier, nullptr, error); + if (NetworkUtils::IsCertificateTransparencyRequiredError(error.ErrorCode())) { + CountUsage(WebFeature::kCertificateTransparencyRequiredErrorOnResourceLoad); + } +} + +void WorkerFetchContext::AddResourceTiming(const ResourceTimingInfo& info) { + // TODO(nhiroki): Add ResourceTiming API support once it's spec'ed for + // worklets. + if (global_scope_->IsWorkletGlobalScope()) + return; + WorkerGlobalScopePerformance::performance(*ToWorkerGlobalScope(global_scope_)) + ->GenerateAndAddResourceTiming(info); +} + +void WorkerFetchContext::PopulateResourceRequest( + Resource::Type type, + const ClientHintsPreferences& hints_preferences, + const FetchParameters::ResourceWidth& resource_width, + ResourceRequest& out_request) { + SetFirstPartyCookieAndRequestorOrigin(out_request); +} + +void WorkerFetchContext::SetFirstPartyCookieAndRequestorOrigin( + ResourceRequest& out_request) { + if (out_request.SiteForCookies().IsNull()) + out_request.SetSiteForCookies(GetSiteForCookies()); + if (!out_request.RequestorOrigin()) + out_request.SetRequestorOrigin(GetSecurityOrigin()); +} + +scoped_refptr<base::SingleThreadTaskRunner> +WorkerFetchContext::GetLoadingTaskRunner() { + return loading_task_runner_; +} + +void WorkerFetchContext::Trace(blink::Visitor* visitor) { + visitor->Trace(global_scope_); + visitor->Trace(subresource_filter_); + visitor->Trace(resource_fetcher_); + BaseFetchContext::Trace(visitor); +} + +void ProvideWorkerFetchContextToWorker( + WorkerClients* clients, + std::unique_ptr<WebWorkerFetchContext> web_context) { + DCHECK(clients); + WorkerFetchContextHolder::ProvideTo( + *clients, new WorkerFetchContextHolder(std::move(web_context))); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/loader/worker_fetch_context.h b/chromium/third_party/blink/renderer/core/loader/worker_fetch_context.h new file mode 100644 index 00000000000..1c1d20596a7 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/worker_fetch_context.h @@ -0,0 +1,134 @@ +// Copyright 2017 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_WORKER_FETCH_CONTEXT_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_WORKER_FETCH_CONTEXT_H_ + +#include <memory> +#include "base/single_thread_task_runner.h" +#include "services/network/public/mojom/request_context_frame_type.mojom-blink.h" +#include "third_party/blink/renderer/core/core_export.h" +#include "third_party/blink/renderer/core/loader/base_fetch_context.h" +#include "third_party/blink/renderer/platform/wtf/forward.h" + +namespace blink { + +class ResourceFetcher; +class SubresourceFilter; +class WebURLLoader; +class WebURLLoaderFactory; +class WebWorkerFetchContext; +class WorkerClients; +class WorkerOrWorkletGlobalScope; + +CORE_EXPORT void ProvideWorkerFetchContextToWorker( + WorkerClients*, + std::unique_ptr<WebWorkerFetchContext>); + +// The WorkerFetchContext is a FetchContext for workers (dedicated, shared and +// service workers) and threaded worklets (animation and audio worklets). +class WorkerFetchContext final : public BaseFetchContext { + public: + static WorkerFetchContext* Create(WorkerOrWorkletGlobalScope&); + ~WorkerFetchContext() override; + + // BaseFetchContext implementation: + KURL GetSiteForCookies() const override; + SubresourceFilter* GetSubresourceFilter() const override; + bool AllowScriptFromSource(const KURL&) const override; + bool ShouldBlockRequestByInspector(const KURL&) const override; + void DispatchDidBlockRequest(const ResourceRequest&, + const FetchInitiatorInfo&, + ResourceRequestBlockedReason, + Resource::Type) const override; + bool ShouldBypassMainWorldCSP() const override; + bool IsSVGImageChromeClient() const override; + void CountUsage(WebFeature) const override; + void CountDeprecation(WebFeature) const override; + bool ShouldBlockWebSocketByMixedContentCheck(const KURL&) const override; + bool ShouldBlockFetchByMixedContentCheck( + WebURLRequest::RequestContext, + network::mojom::RequestContextFrameType, + ResourceRequest::RedirectStatus, + const KURL&, + SecurityViolationReportingPolicy) const override; + bool ShouldBlockFetchAsCredentialedSubresource(const ResourceRequest&, + const KURL&) const override; + bool ShouldLoadNewResource(Resource::Type) const override { return true; } + ReferrerPolicy GetReferrerPolicy() const override; + String GetOutgoingReferrer() const override; + const KURL& Url() const override; + const SecurityOrigin* GetParentSecurityOrigin() const override; + Optional<mojom::IPAddressSpace> GetAddressSpace() const override; + const ContentSecurityPolicy* GetContentSecurityPolicy() const override; + void AddConsoleMessage(ConsoleMessage*) const override; + + // FetchContext implementation: + const SecurityOrigin* GetSecurityOrigin() const override; + std::unique_ptr<WebURLLoader> CreateURLLoader( + const ResourceRequest&, + scoped_refptr<base::SingleThreadTaskRunner>, + const ResourceLoaderOptions&) override; + void PrepareRequest(ResourceRequest&, RedirectType) override; + bool IsControlledByServiceWorker() const override; + int ApplicationCacheHostID() const override; + void AddAdditionalRequestHeaders(ResourceRequest&, + FetchResourceType) override; + void DispatchWillSendRequest(unsigned long, + ResourceRequest&, + const ResourceResponse&, + Resource::Type, + const FetchInitiatorInfo&) override; + void DispatchDidReceiveResponse(unsigned long identifier, + const ResourceResponse&, + network::mojom::RequestContextFrameType, + WebURLRequest::RequestContext, + Resource*, + ResourceResponseType) override; + void DispatchDidReceiveData(unsigned long identifier, + const char* data, + int dataLength) override; + void DispatchDidReceiveEncodedData(unsigned long identifier, + int encoded_data_length) override; + void DispatchDidFinishLoading(unsigned long identifier, + double finish_time, + int64_t encoded_data_length, + int64_t decoded_body_length, + bool blocked_cross_site_document) override; + void DispatchDidFail(const KURL&, + unsigned long identifier, + const ResourceError&, + int64_t encoded_data_length, + bool isInternalRequest) override; + void AddResourceTiming(const ResourceTimingInfo&) override; + void PopulateResourceRequest(Resource::Type, + const ClientHintsPreferences&, + const FetchParameters::ResourceWidth&, + ResourceRequest&) override; + scoped_refptr<base::SingleThreadTaskRunner> GetLoadingTaskRunner() override; + + void Trace(blink::Visitor*) override; + + private: + WorkerFetchContext(WorkerOrWorkletGlobalScope&, + std::unique_ptr<WebWorkerFetchContext>); + + void SetFirstPartyCookieAndRequestorOrigin(ResourceRequest&); + + Member<WorkerOrWorkletGlobalScope> global_scope_; + std::unique_ptr<WebWorkerFetchContext> web_context_; + std::unique_ptr<WebURLLoaderFactory> url_loader_factory_; + Member<SubresourceFilter> subresource_filter_; + Member<ResourceFetcher> resource_fetcher_; + scoped_refptr<base::SingleThreadTaskRunner> loading_task_runner_; + + // The value of |save_data_enabled_| is read once per frame from + // NetworkStateNotifier, which is guarded by a mutex lock, and cached locally + // here for performance. + const bool save_data_enabled_; +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_WORKER_FETCH_CONTEXT_H_ diff --git a/chromium/third_party/blink/renderer/core/loader/worker_threadable_loader.cc b/chromium/third_party/blink/renderer/core/loader/worker_threadable_loader.cc new file mode 100644 index 00000000000..0a626635c65 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/worker_threadable_loader.cc @@ -0,0 +1,682 @@ +/* + * Copyright (C) 2009, 2010 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/worker_threadable_loader.h" + +#include <memory> + +#include "base/debug/alias.h" +#include "third_party/blink/public/platform/task_type.h" +#include "third_party/blink/renderer/core/loader/document_threadable_loader.h" +#include "third_party/blink/renderer/core/loader/threadable_loading_context.h" +#include "third_party/blink/renderer/core/timing/worker_global_scope_performance.h" +#include "third_party/blink/renderer/core/workers/worker_global_scope.h" +#include "third_party/blink/renderer/core/workers/worker_thread.h" +#include "third_party/blink/renderer/core/workers/worker_thread_lifecycle_context.h" +#include "third_party/blink/renderer/platform/cross_thread_functional.h" +#include "third_party/blink/renderer/platform/heap/safe_point.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource_error.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/fetch/resource_timing_info.h" +#include "third_party/blink/renderer/platform/weborigin/kurl.h" +#include "third_party/blink/renderer/platform/weborigin/security_policy.h" +#include "third_party/blink/renderer/platform/wtf/functional.h" + +namespace blink { + +namespace { + +std::unique_ptr<Vector<char>> CreateVectorFromMemoryRegion( + const char* data, + unsigned data_length) { + std::unique_ptr<Vector<char>> buffer = + std::make_unique<Vector<char>>(data_length); + memcpy(buffer->data(), data, data_length); + return buffer; +} + +} // namespace + +class WorkerThreadableLoader::AsyncTaskForwarder final + : public WorkerThreadableLoader::TaskForwarder { + public: + explicit AsyncTaskForwarder( + scoped_refptr<base::SingleThreadTaskRunner> worker_loading_task_runner) + : worker_loading_task_runner_(std::move(worker_loading_task_runner)) { + DCHECK(IsMainThread()); + } + ~AsyncTaskForwarder() override { DCHECK(IsMainThread()); } + + void ForwardTask(const base::Location& location, + CrossThreadClosure task) override { + DCHECK(IsMainThread()); + PostCrossThreadTask(*worker_loading_task_runner_, location, + std::move(task)); + } + void ForwardTaskWithDoneSignal(const base::Location& location, + CrossThreadClosure task) override { + DCHECK(IsMainThread()); + PostCrossThreadTask(*worker_loading_task_runner_, location, + std::move(task)); + } + void Abort() override { DCHECK(IsMainThread()); } + + private: + scoped_refptr<base::SingleThreadTaskRunner> worker_loading_task_runner_; +}; + +struct WorkerThreadableLoader::TaskWithLocation final { + TaskWithLocation(const base::Location& location, CrossThreadClosure task) + : location_(location), task_(std::move(task)) {} + TaskWithLocation(TaskWithLocation&& task) + : TaskWithLocation(task.location_, std::move(task.task_)) {} + ~TaskWithLocation() = default; + + base::Location location_; + CrossThreadClosure task_; +}; + +// Observing functions and wait() need to be called on the worker thread. +// Setting functions and signal() need to be called on the main thread. +// All observing functions must be called after wait() returns, and all +// setting functions must be called before signal() is called. +class WorkerThreadableLoader::WaitableEventWithTasks final + : public ThreadSafeRefCounted<WaitableEventWithTasks> { + public: + static scoped_refptr<WaitableEventWithTasks> Create() { + return base::AdoptRef(new WaitableEventWithTasks); + } + + void Signal() { + DCHECK(IsMainThread()); + CHECK(!is_signal_called_); + is_signal_called_ = true; + event_.Signal(); + } + void Wait() { + DCHECK(!IsMainThread()); + CHECK(!is_wait_done_); + event_.Wait(); + is_wait_done_ = true; + } + + // Observing functions + bool IsAborted() const { + DCHECK(!IsMainThread()); + CHECK(is_wait_done_); + return is_aborted_; + } + Vector<TaskWithLocation> Take() { + DCHECK(!IsMainThread()); + CHECK(is_wait_done_); + return std::move(tasks_); + } + + // Setting functions + void Append(TaskWithLocation task) { + DCHECK(IsMainThread()); + CHECK(!is_signal_called_); + tasks_.push_back(std::move(task)); + } + void SetIsAborted() { + DCHECK(IsMainThread()); + CHECK(!is_signal_called_); + is_aborted_ = true; + } + + private: + WaitableEventWithTasks() = default; + + WaitableEvent event_; + Vector<TaskWithLocation> tasks_; + bool is_aborted_ = false; + bool is_signal_called_ = false; + bool is_wait_done_ = false; +}; + +class WorkerThreadableLoader::SyncTaskForwarder final + : public WorkerThreadableLoader::TaskForwarder { + public: + explicit SyncTaskForwarder( + scoped_refptr<WaitableEventWithTasks> event_with_tasks) + : event_with_tasks_(std::move(event_with_tasks)) { + DCHECK(IsMainThread()); + } + ~SyncTaskForwarder() override { DCHECK(IsMainThread()); } + + void ForwardTask(const base::Location& location, + CrossThreadClosure task) override { + DCHECK(IsMainThread()); + event_with_tasks_->Append(TaskWithLocation(location, std::move(task))); + } + void ForwardTaskWithDoneSignal(const base::Location& location, + CrossThreadClosure task) override { + DCHECK(IsMainThread()); + event_with_tasks_->Append(TaskWithLocation(location, std::move(task))); + event_with_tasks_->Signal(); + } + void Abort() override { + DCHECK(IsMainThread()); + event_with_tasks_->SetIsAborted(); + event_with_tasks_->Signal(); + } + + private: + scoped_refptr<WaitableEventWithTasks> event_with_tasks_; +}; + +WorkerThreadableLoader::WorkerThreadableLoader( + WorkerGlobalScope& worker_global_scope, + ThreadableLoaderClient* client, + const ThreadableLoaderOptions& options, + const ResourceLoaderOptions& resource_loader_options) + : worker_global_scope_(&worker_global_scope), + parent_execution_context_task_runners_( + worker_global_scope.GetThread() + ->GetParentExecutionContextTaskRunners()), + client_(client), + threadable_loader_options_(options), + resource_loader_options_(resource_loader_options) { + DCHECK(client); +} + +void WorkerThreadableLoader::LoadResourceSynchronously( + WorkerGlobalScope& worker_global_scope, + const ResourceRequest& request, + ThreadableLoaderClient& client, + const ThreadableLoaderOptions& options, + const ResourceLoaderOptions& resource_loader_options) { + (new WorkerThreadableLoader(worker_global_scope, &client, options, + resource_loader_options)) + ->Start(request); +} + +WorkerThreadableLoader::~WorkerThreadableLoader() { + DCHECK(!main_thread_loader_holder_); + DCHECK(!client_); +} + +void WorkerThreadableLoader::Start(const ResourceRequest& original_request) { + DCHECK(worker_global_scope_->IsContextThread()); + ResourceRequest request(original_request); + if (!request.DidSetHTTPReferrer()) { + request.SetHTTPReferrer(SecurityPolicy::GenerateReferrer( + worker_global_scope_->GetReferrerPolicy(), request.Url(), + worker_global_scope_->OutgoingReferrer())); + } + + scoped_refptr<WaitableEventWithTasks> event_with_tasks; + event_with_tasks = WaitableEventWithTasks::Create(); + + WorkerThread* worker_thread = worker_global_scope_->GetThread(); + scoped_refptr<base::SingleThreadTaskRunner> worker_loading_task_runner = + worker_global_scope_->GetTaskRunner(TaskType::kInternalLoading); + PostCrossThreadTask( + *parent_execution_context_task_runners_->Get(TaskType::kInternalLoading), + FROM_HERE, + CrossThreadBind( + &MainThreadLoaderHolder::CreateAndStart, + WrapCrossThreadPersistent(this), + WrapCrossThreadPersistent(worker_thread->GetLoadingContext()), + std::move(worker_loading_task_runner), + WrapCrossThreadPersistent( + worker_thread->GetWorkerThreadLifecycleContext()), + request, threadable_loader_options_, resource_loader_options_, + event_with_tasks)); + + event_with_tasks->Wait(); + + if (event_with_tasks->IsAborted()) { + // This thread is going to terminate. + Cancel(); + return; + } + + for (auto& task : event_with_tasks->Take()) { + // Store the program counter where the task is posted from, and alias + // it to ensure it is stored in the crash dump. + const void* program_counter = task.location_.program_counter(); + base::debug::Alias(&program_counter); + + std::move(task.task_).Run(); + } +} + +void WorkerThreadableLoader::OverrideTimeout( + unsigned long timeout_milliseconds) { + DCHECK(!IsMainThread()); + if (!main_thread_loader_holder_) + return; + PostCrossThreadTask( + *parent_execution_context_task_runners_->Get(TaskType::kInternalLoading), + FROM_HERE, + CrossThreadBind(&MainThreadLoaderHolder::OverrideTimeout, + main_thread_loader_holder_, timeout_milliseconds)); +} + +void WorkerThreadableLoader::Cancel() { + DCHECK(!IsMainThread()); + if (main_thread_loader_holder_) { + PostCrossThreadTask(*parent_execution_context_task_runners_->Get( + TaskType::kInternalLoading), + FROM_HERE, + CrossThreadBind(&MainThreadLoaderHolder::Cancel, + main_thread_loader_holder_)); + main_thread_loader_holder_ = nullptr; + } + + if (!client_) + return; + + // If the client hasn't reached a termination state, then transition it + // by sending a cancellation error. + // Note: no more client callbacks will be done after this method -- the + // clearClient() call ensures that. + DidFail(ResourceError::CancelledError(KURL())); + DCHECK(!client_); +} + +void WorkerThreadableLoader::Detach() { + // NOTREACHED + // Currently only "synchronous" requests are using this class and we will + // deprecate it in the future. As this method cannot be called for such + // requests, we don't implement it. + CHECK(false); +} + +void WorkerThreadableLoader::DidStart( + MainThreadLoaderHolder* main_thread_loader_holder) { + DCHECK(!IsMainThread()); + DCHECK(!main_thread_loader_holder_); + DCHECK(main_thread_loader_holder); + if (!client_) { + // The thread is terminating. + PostCrossThreadTask( + *parent_execution_context_task_runners_->Get( + TaskType::kInternalLoading), + FROM_HERE, + CrossThreadBind(&MainThreadLoaderHolder::Cancel, + WrapCrossThreadPersistent(main_thread_loader_holder))); + return; + } + + main_thread_loader_holder_ = main_thread_loader_holder; +} + +void WorkerThreadableLoader::DidSendData( + unsigned long long bytes_sent, + unsigned long long total_bytes_to_be_sent) { + DCHECK(!IsMainThread()); + if (!client_) + return; + client_->DidSendData(bytes_sent, total_bytes_to_be_sent); +} + +void WorkerThreadableLoader::DidReceiveRedirectTo(const KURL& url) { + DCHECK(!IsMainThread()); + if (!client_) + return; + client_->DidReceiveRedirectTo(url); +} + +void WorkerThreadableLoader::DidReceiveResponse( + unsigned long identifier, + std::unique_ptr<CrossThreadResourceResponseData> response_data, + std::unique_ptr<WebDataConsumerHandle> handle) { + DCHECK(!IsMainThread()); + if (!client_) + return; + ResourceResponse response(response_data.get()); + client_->DidReceiveResponse(identifier, response, std::move(handle)); +} + +void WorkerThreadableLoader::DidReceiveData( + std::unique_ptr<Vector<char>> data) { + DCHECK(!IsMainThread()); + CHECK_LE(data->size(), std::numeric_limits<unsigned>::max()); + if (!client_) + return; + client_->DidReceiveData(data->data(), data->size()); +} + +void WorkerThreadableLoader::DidReceiveCachedMetadata( + std::unique_ptr<Vector<char>> data) { + DCHECK(!IsMainThread()); + if (!client_) + return; + client_->DidReceiveCachedMetadata(data->data(), data->size()); +} + +void WorkerThreadableLoader::DidFinishLoading(unsigned long identifier, + double finish_time) { + DCHECK(!IsMainThread()); + if (!client_) + return; + auto* client = client_; + client_ = nullptr; + main_thread_loader_holder_ = nullptr; + client->DidFinishLoading(identifier, finish_time); +} + +void WorkerThreadableLoader::DidFail(const ResourceError& error) { + DCHECK(!IsMainThread()); + if (!client_) + return; + auto* client = client_; + client_ = nullptr; + main_thread_loader_holder_ = nullptr; + client->DidFail(error); +} + +void WorkerThreadableLoader::DidFailRedirectCheck() { + DCHECK(!IsMainThread()); + if (!client_) + return; + auto* client = client_; + client_ = nullptr; + main_thread_loader_holder_ = nullptr; + client->DidFailRedirectCheck(); +} + +void WorkerThreadableLoader::DidDownloadData(int data_length) { + DCHECK(!IsMainThread()); + if (!client_) + return; + client_->DidDownloadData(data_length); +} + +void WorkerThreadableLoader::DidDownloadToBlob( + scoped_refptr<BlobDataHandle> blob) { + DCHECK(!IsMainThread()); + if (!client_) + return; + client_->DidDownloadToBlob(std::move(blob)); +} + +void WorkerThreadableLoader::DidReceiveResourceTiming( + std::unique_ptr<CrossThreadResourceTimingInfoData> timing_data) { + DCHECK(!IsMainThread()); + if (!client_) + return; + scoped_refptr<ResourceTimingInfo> info( + ResourceTimingInfo::Adopt(std::move(timing_data))); + WorkerGlobalScopePerformance::performance(*worker_global_scope_) + ->GenerateAndAddResourceTiming(*info); + client_->DidReceiveResourceTiming(*info); +} + +void WorkerThreadableLoader::Trace(blink::Visitor* visitor) { + visitor->Trace(worker_global_scope_); + ThreadableLoader::Trace(visitor); +} + +void WorkerThreadableLoader::MainThreadLoaderHolder::CreateAndStart( + WorkerThreadableLoader* worker_loader, + ThreadableLoadingContext* loading_context, + scoped_refptr<base::SingleThreadTaskRunner> worker_loading_task_runner, + WorkerThreadLifecycleContext* worker_thread_lifecycle_context, + std::unique_ptr<CrossThreadResourceRequestData> request, + const ThreadableLoaderOptions& options, + const ResourceLoaderOptions& resource_loader_options, + scoped_refptr<WaitableEventWithTasks> event_with_tasks) { + DCHECK(IsMainThread()); + TaskForwarder* forwarder; + if (event_with_tasks) + forwarder = new SyncTaskForwarder(std::move(event_with_tasks)); + else + forwarder = new AsyncTaskForwarder(std::move(worker_loading_task_runner)); + + MainThreadLoaderHolder* main_thread_loader_holder = + new MainThreadLoaderHolder(forwarder, worker_thread_lifecycle_context); + if (main_thread_loader_holder->WasContextDestroyedBeforeObserverCreation()) { + // The thread is already terminating. + forwarder->Abort(); + main_thread_loader_holder->forwarder_ = nullptr; + return; + } + main_thread_loader_holder->worker_loader_ = worker_loader; + forwarder->ForwardTask( + FROM_HERE, + CrossThreadBind(&WorkerThreadableLoader::DidStart, + WrapCrossThreadPersistent(worker_loader), + WrapCrossThreadPersistent(main_thread_loader_holder))); + main_thread_loader_holder->Start(*loading_context, std::move(request), + options, resource_loader_options); +} + +WorkerThreadableLoader::MainThreadLoaderHolder::~MainThreadLoaderHolder() { + DCHECK(IsMainThread()); + DCHECK(!worker_loader_); +} + +void WorkerThreadableLoader::MainThreadLoaderHolder::OverrideTimeout( + unsigned long timeout_milliseconds) { + DCHECK(IsMainThread()); + if (!main_thread_loader_) + return; + main_thread_loader_->OverrideTimeout(timeout_milliseconds); +} + +void WorkerThreadableLoader::MainThreadLoaderHolder::Cancel() { + DCHECK(IsMainThread()); + worker_loader_ = nullptr; + if (!main_thread_loader_) + return; + main_thread_loader_->Cancel(); + main_thread_loader_ = nullptr; +} + +void WorkerThreadableLoader::MainThreadLoaderHolder::DidSendData( + unsigned long long bytes_sent, + unsigned long long total_bytes_to_be_sent) { + DCHECK(IsMainThread()); + CrossThreadPersistent<WorkerThreadableLoader> worker_loader = + worker_loader_.Get(); + if (!worker_loader || !forwarder_) + return; + forwarder_->ForwardTask( + FROM_HERE, + CrossThreadBind(&WorkerThreadableLoader::DidSendData, worker_loader, + bytes_sent, total_bytes_to_be_sent)); +} + +void WorkerThreadableLoader::MainThreadLoaderHolder::DidReceiveRedirectTo( + const KURL& url) { + DCHECK(IsMainThread()); + CrossThreadPersistent<WorkerThreadableLoader> worker_loader = + worker_loader_.Get(); + if (!worker_loader || !forwarder_) + return; + forwarder_->ForwardTask( + FROM_HERE, CrossThreadBind(&WorkerThreadableLoader::DidReceiveRedirectTo, + worker_loader, url)); +} + +void WorkerThreadableLoader::MainThreadLoaderHolder::DidReceiveResponse( + unsigned long identifier, + const ResourceResponse& response, + std::unique_ptr<WebDataConsumerHandle> handle) { + DCHECK(IsMainThread()); + CrossThreadPersistent<WorkerThreadableLoader> worker_loader = + worker_loader_.Get(); + if (!worker_loader || !forwarder_) + return; + forwarder_->ForwardTask( + FROM_HERE, CrossThreadBind(&WorkerThreadableLoader::DidReceiveResponse, + worker_loader, identifier, response, + WTF::Passed(std::move(handle)))); +} + +void WorkerThreadableLoader::MainThreadLoaderHolder::DidReceiveData( + const char* data, + unsigned data_length) { + DCHECK(IsMainThread()); + CrossThreadPersistent<WorkerThreadableLoader> worker_loader = + worker_loader_.Get(); + if (!worker_loader || !forwarder_) + return; + forwarder_->ForwardTask( + FROM_HERE, + CrossThreadBind( + &WorkerThreadableLoader::DidReceiveData, worker_loader, + WTF::Passed(CreateVectorFromMemoryRegion(data, data_length)))); +} + +void WorkerThreadableLoader::MainThreadLoaderHolder::DidDownloadData( + int data_length) { + DCHECK(IsMainThread()); + CrossThreadPersistent<WorkerThreadableLoader> worker_loader = + worker_loader_.Get(); + if (!worker_loader || !forwarder_) + return; + forwarder_->ForwardTask( + FROM_HERE, CrossThreadBind(&WorkerThreadableLoader::DidDownloadData, + worker_loader, data_length)); +} + +void WorkerThreadableLoader::MainThreadLoaderHolder::DidDownloadToBlob( + scoped_refptr<BlobDataHandle> blob) { + DCHECK(IsMainThread()); + CrossThreadPersistent<WorkerThreadableLoader> worker_loader = + worker_loader_.Get(); + if (!worker_loader || !forwarder_) + return; + forwarder_->ForwardTask( + FROM_HERE, CrossThreadBind(&WorkerThreadableLoader::DidDownloadToBlob, + worker_loader, std::move(blob))); +} + +void WorkerThreadableLoader::MainThreadLoaderHolder::DidReceiveCachedMetadata( + const char* data, + int data_length) { + DCHECK(IsMainThread()); + CrossThreadPersistent<WorkerThreadableLoader> worker_loader = + worker_loader_.Get(); + if (!worker_loader || !forwarder_) + return; + forwarder_->ForwardTask( + FROM_HERE, + CrossThreadBind( + &WorkerThreadableLoader::DidReceiveCachedMetadata, worker_loader, + WTF::Passed(CreateVectorFromMemoryRegion(data, data_length)))); +} + +void WorkerThreadableLoader::MainThreadLoaderHolder::DidFinishLoading( + unsigned long identifier, + double finish_time) { + DCHECK(IsMainThread()); + CrossThreadPersistent<WorkerThreadableLoader> worker_loader = + worker_loader_.Release(); + if (!worker_loader || !forwarder_) + return; + forwarder_->ForwardTaskWithDoneSignal( + FROM_HERE, CrossThreadBind(&WorkerThreadableLoader::DidFinishLoading, + worker_loader, identifier, finish_time)); + forwarder_ = nullptr; +} + +void WorkerThreadableLoader::MainThreadLoaderHolder::DidFail( + const ResourceError& error) { + DCHECK(IsMainThread()); + CrossThreadPersistent<WorkerThreadableLoader> worker_loader = + worker_loader_.Release(); + if (!worker_loader || !forwarder_) + return; + forwarder_->ForwardTaskWithDoneSignal( + FROM_HERE, + CrossThreadBind(&WorkerThreadableLoader::DidFail, worker_loader, error)); + forwarder_ = nullptr; +} + +void WorkerThreadableLoader::MainThreadLoaderHolder::DidFailRedirectCheck() { + DCHECK(IsMainThread()); + CrossThreadPersistent<WorkerThreadableLoader> worker_loader = + worker_loader_.Release(); + if (!worker_loader || !forwarder_) + return; + forwarder_->ForwardTaskWithDoneSignal( + FROM_HERE, CrossThreadBind(&WorkerThreadableLoader::DidFailRedirectCheck, + worker_loader)); + forwarder_ = nullptr; +} + +void WorkerThreadableLoader::MainThreadLoaderHolder::DidReceiveResourceTiming( + const ResourceTimingInfo& info) { + DCHECK(IsMainThread()); + CrossThreadPersistent<WorkerThreadableLoader> worker_loader = + worker_loader_.Get(); + if (!worker_loader || !forwarder_) + return; + forwarder_->ForwardTask( + FROM_HERE, + CrossThreadBind(&WorkerThreadableLoader::DidReceiveResourceTiming, + worker_loader, info)); +} + +void WorkerThreadableLoader::MainThreadLoaderHolder::ContextDestroyed( + WorkerThreadLifecycleContext*) { + DCHECK(IsMainThread()); + if (forwarder_) { + forwarder_->Abort(); + forwarder_ = nullptr; + } + Cancel(); +} + +void WorkerThreadableLoader::MainThreadLoaderHolder::Trace( + blink::Visitor* visitor) { + visitor->Trace(forwarder_); + visitor->Trace(main_thread_loader_); + WorkerThreadLifecycleObserver::Trace(visitor); +} + +WorkerThreadableLoader::MainThreadLoaderHolder::MainThreadLoaderHolder( + TaskForwarder* forwarder, + WorkerThreadLifecycleContext* context) + : WorkerThreadLifecycleObserver(context), forwarder_(forwarder) { + DCHECK(IsMainThread()); +} + +void WorkerThreadableLoader::MainThreadLoaderHolder::Start( + ThreadableLoadingContext& loading_context, + std::unique_ptr<CrossThreadResourceRequestData> request, + const ThreadableLoaderOptions& options, + const ResourceLoaderOptions& original_resource_loader_options) { + DCHECK(IsMainThread()); + ResourceLoaderOptions resource_loader_options = + original_resource_loader_options; + resource_loader_options.request_initiator_context = kWorkerContext; + main_thread_loader_ = DocumentThreadableLoader::Create( + loading_context, this, options, resource_loader_options); + main_thread_loader_->Start(ResourceRequest(request.get())); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/loader/worker_threadable_loader.h b/chromium/third_party/blink/renderer/core/loader/worker_threadable_loader.h new file mode 100644 index 00000000000..9689ef4cda2 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/loader/worker_threadable_loader.h @@ -0,0 +1,212 @@ +/* + * Copyright (C) 2009 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. + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_WORKER_THREADABLE_LOADER_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_WORKER_THREADABLE_LOADER_H_ + +#include <memory> +#include "base/location.h" +#include "base/memory/scoped_refptr.h" +#include "third_party/blink/renderer/core/loader/threadable_loader.h" +#include "third_party/blink/renderer/core/loader/threadable_loader_client.h" +#include "third_party/blink/renderer/core/workers/worker_thread.h" +#include "third_party/blink/renderer/core/workers/worker_thread_lifecycle_observer.h" +#include "third_party/blink/renderer/platform/heap/handle.h" +#include "third_party/blink/renderer/platform/waitable_event.h" +#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h" +#include "third_party/blink/renderer/platform/wtf/threading.h" +#include "third_party/blink/renderer/platform/wtf/vector.h" + +namespace blink { + +class ThreadableLoadingContext; +class ResourceError; +class ResourceRequest; +class ResourceResponse; +class WorkerGlobalScope; +class WorkerThreadLifecycleContext; +struct CrossThreadResourceRequestData; +struct CrossThreadResourceTimingInfoData; + +// A WorkerThreadableLoader is a ThreadableLoader implementation intended to +// be used in a WebWorker thread. Because Blink's ResourceFetcher and +// ResourceLoader work only in the main thread, a WorkerThreadableLoader holds +// a ThreadableLoader in the main thread and delegates tasks asynchronously +// to the loader. +// +// CTP: CrossThreadPersistent +// CTWP: CrossThreadWeakPersistent +// +// ---------------------------------------------------------------- +// +------------------------+ +// raw ptr | ThreadableLoaderClient | +// +--------> | worker thread | +// | +------------------------+ +// | +// +----+------------------+ CTP +------------------------+ +// + WorkerThreadableLoader|<--------+ MainThreadLoaderHolder | +// | worker thread +-------->| main thread | +// +-----------------------+ CTWP +----------------------+-+ +// | +// +------------------+ | Member +// | ThreadableLoader | <---+ +// | main thread | +// +------------------+ +// +class WorkerThreadableLoader final : public ThreadableLoader { + public: + static void LoadResourceSynchronously(WorkerGlobalScope&, + const ResourceRequest&, + ThreadableLoaderClient&, + const ThreadableLoaderOptions&, + const ResourceLoaderOptions&); + ~WorkerThreadableLoader() override; + + // ThreadableLoader functions + void Start(const ResourceRequest&) override; + void OverrideTimeout(unsigned long timeout) override; + void Cancel() override; + void Detach() override; + + void Trace(blink::Visitor*) override; + + private: + // A TaskForwarder forwards a task to the worker thread. + class TaskForwarder : public GarbageCollectedFinalized<TaskForwarder> { + public: + virtual ~TaskForwarder() = default; + virtual void ForwardTask(const base::Location&, CrossThreadClosure) = 0; + virtual void ForwardTaskWithDoneSignal(const base::Location&, + CrossThreadClosure) = 0; + virtual void Abort() = 0; + + virtual void Trace(blink::Visitor* visitor) {} + }; + class AsyncTaskForwarder; + struct TaskWithLocation; + class WaitableEventWithTasks; + class SyncTaskForwarder; + + // An instance of this class lives in the main thread. It is a + // ThreadableLoaderClient for a DocumentThreadableLoader and forward + // notifications to the associated WorkerThreadableLoader living in the + // worker thread. + class MainThreadLoaderHolder final + : public GarbageCollectedFinalized<MainThreadLoaderHolder>, + public ThreadableLoaderClient, + public WorkerThreadLifecycleObserver { + USING_GARBAGE_COLLECTED_MIXIN(MainThreadLoaderHolder); + USING_PRE_FINALIZER(MainThreadLoaderHolder, Cancel); + + public: + static void CreateAndStart(WorkerThreadableLoader*, + ThreadableLoadingContext*, + scoped_refptr<base::SingleThreadTaskRunner>, + WorkerThreadLifecycleContext*, + std::unique_ptr<CrossThreadResourceRequestData>, + const ThreadableLoaderOptions&, + const ResourceLoaderOptions&, + scoped_refptr<WaitableEventWithTasks>); + ~MainThreadLoaderHolder() override; + + void OverrideTimeout(unsigned long timeout_millisecond); + void Cancel(); + + void DidSendData(unsigned long long bytes_sent, + unsigned long long total_bytes_to_be_sent) override; + void DidReceiveRedirectTo(const KURL&) override; + void DidReceiveResponse(unsigned long identifier, + const ResourceResponse&, + std::unique_ptr<WebDataConsumerHandle>) override; + void DidReceiveData(const char*, unsigned data_length) override; + void DidDownloadData(int data_length) override; + void DidDownloadToBlob(scoped_refptr<BlobDataHandle>) override; + void DidReceiveCachedMetadata(const char*, int data_length) override; + void DidFinishLoading(unsigned long identifier, + double finish_time) override; + void DidFail(const ResourceError&) override; + void DidFailRedirectCheck() override; + void DidReceiveResourceTiming(const ResourceTimingInfo&) override; + + void ContextDestroyed(WorkerThreadLifecycleContext*) override; + + void Trace(blink::Visitor*) override; + + private: + MainThreadLoaderHolder(TaskForwarder*, WorkerThreadLifecycleContext*); + void Start(ThreadableLoadingContext&, + std::unique_ptr<CrossThreadResourceRequestData>, + const ThreadableLoaderOptions&, + const ResourceLoaderOptions&); + + Member<TaskForwarder> forwarder_; + Member<ThreadableLoader> main_thread_loader_; + + // |*m_workerLoader| lives in the worker thread. + CrossThreadWeakPersistent<WorkerThreadableLoader> worker_loader_; + }; + + WorkerThreadableLoader(WorkerGlobalScope&, + ThreadableLoaderClient*, + const ThreadableLoaderOptions&, + const ResourceLoaderOptions&); + void DidStart(MainThreadLoaderHolder*); + + void DidSendData(unsigned long long bytes_sent, + unsigned long long total_bytes_to_be_sent); + void DidReceiveRedirectTo(const KURL&); + void DidReceiveResponse(unsigned long identifier, + std::unique_ptr<CrossThreadResourceResponseData>, + std::unique_ptr<WebDataConsumerHandle>); + void DidReceiveData(std::unique_ptr<Vector<char>> data); + void DidReceiveCachedMetadata(std::unique_ptr<Vector<char>> data); + void DidFinishLoading(unsigned long identifier, double finish_time); + void DidFail(const ResourceError&); + void DidFailRedirectCheck(); + void DidDownloadData(int data_length); + void DidDownloadToBlob(scoped_refptr<BlobDataHandle>); + void DidReceiveResourceTiming( + std::unique_ptr<CrossThreadResourceTimingInfoData>); + + Member<WorkerGlobalScope> worker_global_scope_; + CrossThreadPersistent<ParentExecutionContextTaskRunners> + parent_execution_context_task_runners_; + ThreadableLoaderClient* client_; + + ThreadableLoaderOptions threadable_loader_options_; + ResourceLoaderOptions resource_loader_options_; + + // |*m_mainThreadLoaderHolder| lives in the main thread. + CrossThreadPersistent<MainThreadLoaderHolder> main_thread_loader_holder_; +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_WORKER_THREADABLE_LOADER_H_ |