summaryrefslogtreecommitdiff
path: root/chromium/third_party/blink/renderer/core/loader
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/third_party/blink/renderer/core/loader')
-rw-r--r--chromium/third_party/blink/renderer/core/loader/BUILD.gn132
-rw-r--r--chromium/third_party/blink/renderer/core/loader/OWNERS6
-rw-r--r--chromium/third_party/blink/renderer/core/loader/README.md6
-rw-r--r--chromium/third_party/blink/renderer/core/loader/allowed_by_nosniff.cc207
-rw-r--r--chromium/third_party/blink/renderer/core/loader/allowed_by_nosniff.h26
-rw-r--r--chromium/third_party/blink/renderer/core/loader/allowed_by_nosniff_test.cc191
-rw-r--r--chromium/third_party/blink/renderer/core/loader/appcache/application_cache.cc142
-rw-r--r--chromium/third_party/blink/renderer/core/loader/appcache/application_cache.h84
-rw-r--r--chromium/third_party/blink/renderer/core/loader/appcache/application_cache.idl56
-rw-r--r--chromium/third_party/blink/renderer/core/loader/appcache/application_cache_host.cc395
-rw-r--r--chromium/third_party/blink/renderer/core/loader/appcache/application_cache_host.h227
-rw-r--r--chromium/third_party/blink/renderer/core/loader/base_fetch_context.cc348
-rw-r--r--chromium/third_party/blink/renderer/core/loader/base_fetch_context.h122
-rw-r--r--chromium/third_party/blink/renderer/core/loader/base_fetch_context_test.cc447
-rw-r--r--chromium/third_party/blink/renderer/core/loader/cookie_jar.cc76
-rw-r--r--chromium/third_party/blink/renderer/core/loader/cookie_jar.h43
-rw-r--r--chromium/third_party/blink/renderer/core/loader/document_load_timing.cc227
-rw-r--r--chromium/third_party/blink/renderer/core/loader/document_load_timing.h122
-rw-r--r--chromium/third_party/blink/renderer/core/loader/document_load_timing_test.cc57
-rw-r--r--chromium/third_party/blink/renderer/core/loader/document_loader.cc1207
-rw-r--r--chromium/third_party/blink/renderer/core/loader/document_loader.h409
-rw-r--r--chromium/third_party/blink/renderer/core/loader/document_loader_test.cc196
-rw-r--r--chromium/third_party/blink/renderer/core/loader/document_threadable_loader.cc1306
-rw-r--r--chromium/third_party/blink/renderer/core/loader/document_threadable_loader.h256
-rw-r--r--chromium/third_party/blink/renderer/core/loader/document_threadable_loader_client.h61
-rw-r--r--chromium/third_party/blink/renderer/core/loader/document_threadable_loader_test.cc92
-rw-r--r--chromium/third_party/blink/renderer/core/loader/empty_clients.cc186
-rw-r--r--chromium/third_party/blink/renderer/core/loader/empty_clients.h453
-rw-r--r--chromium/third_party/blink/renderer/core/loader/form_submission.cc298
-rw-r--r--chromium/third_party/blink/renderer/core/loader/form_submission.h140
-rw-r--r--chromium/third_party/blink/renderer/core/loader/frame_fetch_context.cc1429
-rw-r--r--chromium/third_party/blink/renderer/core/loader/frame_fetch_context.h268
-rw-r--r--chromium/third_party/blink/renderer/core/loader/frame_fetch_context_test.cc1733
-rw-r--r--chromium/third_party/blink/renderer/core/loader/frame_load_request.cc108
-rw-r--r--chromium/third_party/blink/renderer/core/loader/frame_load_request.h169
-rw-r--r--chromium/third_party/blink/renderer/core/loader/frame_loader.cc1889
-rw-r--r--chromium/third_party/blink/renderer/core/loader/frame_loader.h323
-rw-r--r--chromium/third_party/blink/renderer/core/loader/frame_loader_state_machine.cc78
-rw-r--r--chromium/third_party/blink/renderer/core/loader/frame_loader_state_machine.h75
-rw-r--r--chromium/third_party/blink/renderer/core/loader/frame_loader_types.h109
-rw-r--r--chromium/third_party/blink/renderer/core/loader/history_item.cc181
-rw-r--r--chromium/third_party/blink/renderer/core/loader/history_item.h164
-rw-r--r--chromium/third_party/blink/renderer/core/loader/http_equiv.cc143
-rw-r--r--chromium/third_party/blink/renderer/core/loader/http_equiv.h52
-rw-r--r--chromium/third_party/blink/renderer/core/loader/idleness_detector.cc198
-rw-r--r--chromium/third_party/blink/renderer/core/loader/idleness_detector.h79
-rw-r--r--chromium/third_party/blink/renderer/core/loader/idleness_detector_test.cc91
-rw-r--r--chromium/third_party/blink/renderer/core/loader/image_loader.cc838
-rw-r--r--chromium/third_party/blink/renderer/core/loader/image_loader.h270
-rw-r--r--chromium/third_party/blink/renderer/core/loader/interactive_detector.cc478
-rw-r--r--chromium/third_party/blink/renderer/core/loader/interactive_detector.h173
-rw-r--r--chromium/third_party/blink/renderer/core/loader/interactive_detector_test.cc515
-rw-r--r--chromium/third_party/blink/renderer/core/loader/link_loader.cc703
-rw-r--r--chromium/third_party/blink/renderer/core/loader/link_loader.h155
-rw-r--r--chromium/third_party/blink/renderer/core/loader/link_loader_client.h63
-rw-r--r--chromium/third_party/blink/renderer/core/loader/link_loader_test.cc663
-rw-r--r--chromium/third_party/blink/renderer/core/loader/mixed_content_checker.cc748
-rw-r--r--chromium/third_party/blink/renderer/core/loader/mixed_content_checker.h149
-rw-r--r--chromium/third_party/blink/renderer/core/loader/mixed_content_checker_test.cc220
-rw-r--r--chromium/third_party/blink/renderer/core/loader/modulescript/document_module_script_fetcher.cc112
-rw-r--r--chromium/third_party/blink/renderer/core/loader/modulescript/document_module_script_fetcher.h52
-rw-r--r--chromium/third_party/blink/renderer/core/loader/modulescript/module_script_creation_params.h61
-rw-r--r--chromium/third_party/blink/renderer/core/loader/modulescript/module_script_fetch_request.h61
-rw-r--r--chromium/third_party/blink/renderer/core/loader/modulescript/module_script_fetcher.cc24
-rw-r--r--chromium/third_party/blink/renderer/core/loader/modulescript/module_script_fetcher.h52
-rw-r--r--chromium/third_party/blink/renderer/core/loader/modulescript/module_script_loader.cc220
-rw-r--r--chromium/third_party/blink/renderer/core/loader/modulescript/module_script_loader.h96
-rw-r--r--chromium/third_party/blink/renderer/core/loader/modulescript/module_script_loader_client.h32
-rw-r--r--chromium/third_party/blink/renderer/core/loader/modulescript/module_script_loader_registry.cc37
-rw-r--r--chromium/third_party/blink/renderer/core/loader/modulescript/module_script_loader_registry.h46
-rw-r--r--chromium/third_party/blink/renderer/core/loader/modulescript/module_script_loader_test.cc363
-rw-r--r--chromium/third_party/blink/renderer/core/loader/modulescript/module_tree_linker.cc492
-rw-r--r--chromium/third_party/blink/renderer/core/loader/modulescript/module_tree_linker.h129
-rw-r--r--chromium/third_party/blink/renderer/core/loader/modulescript/module_tree_linker_registry.cc54
-rw-r--r--chromium/third_party/blink/renderer/core/loader/modulescript/module_tree_linker_registry.h53
-rw-r--r--chromium/third_party/blink/renderer/core/loader/modulescript/module_tree_linker_test.cc411
-rw-r--r--chromium/third_party/blink/renderer/core/loader/modulescript/worker_or_worklet_module_script_fetcher.cc46
-rw-r--r--chromium/third_party/blink/renderer/core/loader/modulescript/worker_or_worklet_module_script_fetcher.h45
-rw-r--r--chromium/third_party/blink/renderer/core/loader/navigation_policy.cc78
-rw-r--r--chromium/third_party/blink/renderer/core/loader/navigation_policy.h59
-rw-r--r--chromium/third_party/blink/renderer/core/loader/navigation_scheduler.cc582
-rw-r--r--chromium/third_party/blink/renderer/core/loader/navigation_scheduler.h119
-rw-r--r--chromium/third_party/blink/renderer/core/loader/network_hints_interface.h31
-rw-r--r--chromium/third_party/blink/renderer/core/loader/ping_loader.cc301
-rw-r--r--chromium/third_party/blink/renderer/core/loader/ping_loader.h87
-rw-r--r--chromium/third_party/blink/renderer/core/loader/ping_loader_test.cc151
-rw-r--r--chromium/third_party/blink/renderer/core/loader/prerenderer_client.cc68
-rw-r--r--chromium/third_party/blink/renderer/core/loader/prerenderer_client.h71
-rw-r--r--chromium/third_party/blink/renderer/core/loader/private/frame_client_hints_preferences_context.cc44
-rw-r--r--chromium/third_party/blink/renderer/core/loader/private/frame_client_hints_preferences_context.h31
-rw-r--r--chromium/third_party/blink/renderer/core/loader/private/prerender_handle.cc113
-rw-r--r--chromium/third_party/blink/renderer/core/loader/private/prerender_handle.h79
-rw-r--r--chromium/third_party/blink/renderer/core/loader/programmatic_scroll_test.cc157
-rw-r--r--chromium/third_party/blink/renderer/core/loader/progress_tracker.cc245
-rw-r--r--chromium/third_party/blink/renderer/core/loader/progress_tracker.h97
-rw-r--r--chromium/third_party/blink/renderer/core/loader/progress_tracker_test.cc149
-rw-r--r--chromium/third_party/blink/renderer/core/loader/resource/css_style_sheet_resource.cc256
-rw-r--r--chromium/third_party/blink/renderer/core/loader/resource/css_style_sheet_resource.h101
-rw-r--r--chromium/third_party/blink/renderer/core/loader/resource/css_style_sheet_resource_test.cc178
-rw-r--r--chromium/third_party/blink/renderer/core/loader/resource/document_resource.cc92
-rw-r--r--chromium/third_party/blink/renderer/core/loader/resource/document_resource.h85
-rw-r--r--chromium/third_party/blink/renderer/core/loader/resource/font_resource.cc243
-rw-r--r--chromium/third_party/blink/renderer/core/loader/resource/font_resource.h138
-rw-r--r--chromium/third_party/blink/renderer/core/loader/resource/font_resource_test.cc167
-rw-r--r--chromium/third_party/blink/renderer/core/loader/resource/image_resource.cc730
-rw-r--r--chromium/third_party/blink/renderer/core/loader/resource/image_resource.h192
-rw-r--r--chromium/third_party/blink/renderer/core/loader/resource/image_resource_content.cc639
-rw-r--r--chromium/third_party/blink/renderer/core/loader/resource/image_resource_content.h236
-rw-r--r--chromium/third_party/blink/renderer/core/loader/resource/image_resource_info.h66
-rw-r--r--chromium/third_party/blink/renderer/core/loader/resource/image_resource_observer.h95
-rw-r--r--chromium/third_party/blink/renderer/core/loader/resource/image_resource_test.cc2016
-rw-r--r--chromium/third_party/blink/renderer/core/loader/resource/link_fetch_resource.cc29
-rw-r--r--chromium/third_party/blink/renderer/core/loader/resource/link_fetch_resource.h37
-rw-r--r--chromium/third_party/blink/renderer/core/loader/resource/mock_font_resource_client.cc29
-rw-r--r--chromium/third_party/blink/renderer/core/loader/resource/mock_font_resource_client.h44
-rw-r--r--chromium/third_party/blink/renderer/core/loader/resource/mock_image_resource_observer.cc58
-rw-r--r--chromium/third_party/blink/renderer/core/loader/resource/mock_image_resource_observer.h64
-rw-r--r--chromium/third_party/blink/renderer/core/loader/resource/multipart_image_resource_parser.cc190
-rw-r--r--chromium/third_party/blink/renderer/core/loader/resource/multipart_image_resource_parser.h103
-rw-r--r--chromium/third_party/blink/renderer/core/loader/resource/multipart_image_resource_parser_test.cc418
-rw-r--r--chromium/third_party/blink/renderer/core/loader/resource/script_resource.cc239
-rw-r--r--chromium/third_party/blink/renderer/core/loader/resource/script_resource.h111
-rw-r--r--chromium/third_party/blink/renderer/core/loader/resource/text_resource.cc46
-rw-r--r--chromium/third_party/blink/renderer/core/loader/resource/text_resource.h41
-rw-r--r--chromium/third_party/blink/renderer/core/loader/resource/xsl_style_sheet_resource.cc84
-rw-r--r--chromium/third_party/blink/renderer/core/loader/resource/xsl_style_sheet_resource.h74
-rw-r--r--chromium/third_party/blink/renderer/core/loader/scheduled_navigation.cc36
-rw-r--r--chromium/third_party/blink/renderer/core/loader/scheduled_navigation.h71
-rw-r--r--chromium/third_party/blink/renderer/core/loader/subresource_filter.cc154
-rw-r--r--chromium/third_party/blink/renderer/core/loader/subresource_filter.h64
-rw-r--r--chromium/third_party/blink/renderer/core/loader/subresource_integrity_helper.cc73
-rw-r--r--chromium/third_party/blink/renderer/core/loader/subresource_integrity_helper.h32
-rw-r--r--chromium/third_party/blink/renderer/core/loader/text_resource_decoder_builder.cc167
-rw-r--r--chromium/third_party/blink/renderer/core/loader/text_resource_decoder_builder.h49
-rw-r--r--chromium/third_party/blink/renderer/core/loader/text_resource_decoder_builder_test.cc48
-rw-r--r--chromium/third_party/blink/renderer/core/loader/text_track_loader.cc196
-rw-r--r--chromium/third_party/blink/renderer/core/loader/text_track_loader.h103
-rw-r--r--chromium/third_party/blink/renderer/core/loader/threadable_loader.cc64
-rw-r--r--chromium/third_party/blink/renderer/core/loader/threadable_loader.h171
-rw-r--r--chromium/third_party/blink/renderer/core/loader/threadable_loader_client.h84
-rw-r--r--chromium/third_party/blink/renderer/core/loader/threadable_loader_test.cc897
-rw-r--r--chromium/third_party/blink/renderer/core/loader/threadable_loading_context.cc88
-rw-r--r--chromium/third_party/blink/renderer/core/loader/threadable_loading_context.h41
-rw-r--r--chromium/third_party/blink/renderer/core/loader/worker_fetch_context.cc410
-rw-r--r--chromium/third_party/blink/renderer/core/loader/worker_fetch_context.h134
-rw-r--r--chromium/third_party/blink/renderer/core/loader/worker_threadable_loader.cc682
-rw-r--r--chromium/third_party/blink/renderer/core/loader/worker_threadable_loader.h212
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 &top;
+
+ 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_