diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2018-05-15 10:20:33 +0200 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2018-05-15 10:28:57 +0000 |
commit | d17ea114e5ef69ad5d5d7413280a13e6428098aa (patch) | |
tree | 2c01a75df69f30d27b1432467cfe7c1467a498da /chromium/third_party/blink/renderer/platform/loader | |
parent | 8c5c43c7b138c9b4b0bf56d946e61d3bbc111bec (diff) | |
download | qtwebengine-chromium-d17ea114e5ef69ad5d5d7413280a13e6428098aa.tar.gz |
BASELINE: Update Chromium to 67.0.3396.47
Change-Id: Idcb1341782e417561a2473eeecc82642dafda5b7
Reviewed-by: Michal Klocek <michal.klocek@qt.io>
Diffstat (limited to 'chromium/third_party/blink/renderer/platform/loader')
98 files changed, 21292 insertions, 0 deletions
diff --git a/chromium/third_party/blink/renderer/platform/loader/BUILD.gn b/chromium/third_party/blink/renderer/platform/loader/BUILD.gn new file mode 100644 index 00000000000..0ce462242b6 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/loader/BUILD.gn @@ -0,0 +1,164 @@ +# 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. + +import("//build/config/jumbo.gni") +import("//third_party/blink/renderer/build/scripts/scripts.gni") +import("//third_party/blink/renderer/platform/platform.gni") +import("//third_party/blink/renderer/platform/platform_generated.gni") + +make_names("make_platform_loader_generated_fetch_initiator_type_names") { + in_files = [ "fetch/fetch_initiator_type_names.json5" ] + output_dir = "$blink_platform_output_dir/loader/fetch" +} + +blink_platform_sources("loader") { + sources = [ + "cors/cors.cc", + "cors/cors.h", + "cors/cors_error_string.cc", + "cors/cors_error_string.h", + "fetch/access_control_status.h", + "fetch/buffering_data_pipe_writer.cc", + "fetch/buffering_data_pipe_writer.h", + "fetch/cached_metadata.cc", + "fetch/cached_metadata.h", + "fetch/cached_metadata_handler.h", + "fetch/client_hints_preferences.cc", + "fetch/client_hints_preferences.h", + "fetch/fetch_context.cc", + "fetch/fetch_context.h", + "fetch/fetch_initiator_info.h", + "fetch/fetch_parameters.cc", + "fetch/fetch_parameters.h", + "fetch/fetch_utils.cc", + "fetch/fetch_utils.h", + "fetch/integrity_metadata.cc", + "fetch/integrity_metadata.h", + "fetch/memory_cache.cc", + "fetch/memory_cache.h", + "fetch/preload_key.h", + "fetch/raw_resource.cc", + "fetch/raw_resource.h", + "fetch/resource.cc", + "fetch/resource.h", + "fetch/resource_client.h", + "fetch/resource_client_walker.h", + "fetch/resource_error.cc", + "fetch/resource_error.h", + "fetch/resource_fetcher.cc", + "fetch/resource_fetcher.h", + "fetch/resource_finish_observer.h", + "fetch/resource_load_info.h", + "fetch/resource_load_priority.h", + "fetch/resource_load_scheduler.cc", + "fetch/resource_load_scheduler.h", + "fetch/resource_load_timing.cc", + "fetch/resource_load_timing.h", + "fetch/resource_loader.cc", + "fetch/resource_loader.h", + "fetch/resource_loader_options.h", + "fetch/resource_loading_log.h", + "fetch/resource_priority.h", + "fetch/resource_request.cc", + "fetch/resource_request.h", + "fetch/resource_response.cc", + "fetch/resource_response.h", + "fetch/resource_status.h", + "fetch/resource_timing_info.cc", + "fetch/resource_timing_info.h", + "fetch/script_fetch_options.cc", + "fetch/script_fetch_options.h", + "fetch/source_keyed_cached_metadata_handler.cc", + "fetch/source_keyed_cached_metadata_handler.h", + "fetch/substitute_data.h", + "fetch/text_resource_decoder_options.cc", + "fetch/text_resource_decoder_options.h", + "fetch/unique_identifier.cc", + "fetch/unique_identifier.h", + "link_header.cc", + "link_header.h", + "subresource_integrity.cc", + "subresource_integrity.h", + ] + + sources += get_target_outputs( + ":make_platform_loader_generated_fetch_initiator_type_names") + + deps = [ + ":make_platform_loader_generated_fetch_initiator_type_names", + "//components/link_header_util", + "//services/network/public/cpp", + "//services/network/public/mojom:mojom_blink", + ] +} + +jumbo_source_set("unit_tests") { + # This target defines test files for blink_platform_unittests and only the + # blink_platform_unittests target should depend on it. + visibility = [ "//third_party/blink/renderer/platform:*" ] + testonly = true + + # Source files for blink_platform_unittests. + sources = [ + "fetch/buffering_data_pipe_writer_test.cc", + "fetch/client_hints_preferences_test.cc", + "fetch/fetch_utils_test.cc", + "fetch/memory_cache_correctness_test.cc", + "fetch/memory_cache_test.cc", + "fetch/raw_resource_test.cc", + "fetch/resource_fetcher_test.cc", + "fetch/resource_load_scheduler_test.cc", + "fetch/resource_loader_options_test.cc", + "fetch/resource_loader_test.cc", + "fetch/resource_request_test.cc", + "fetch/resource_response_test.cc", + "fetch/resource_test.cc", + "fetch/source_keyed_cached_metadata_handler_test.cc", + "link_header_test.cc", + "subresource_integrity_test.cc", + ] + + configs += [ "//third_party/blink/renderer/platform:blink_platform_config" ] + + deps = [ + "//testing/gmock", + "//testing/gtest", + "//third_party/blink/renderer/platform:platform", + ] +} + +jumbo_source_set("test_support") { + # This target defines test files for platform:test_support that + # blink_platform_unittests and webkit_unit_tests can use. + visibility = [ "//third_party/blink/renderer/platform:test_support" ] + testonly = true + + # Source files that can be called from blink_platform_unittests and + # webkit_unit_tests. + sources = [ + "testing/crypto_testing_platform_support.h", + "testing/fetch_testing_platform_support.cc", + "testing/fetch_testing_platform_support.h", + "testing/mock_fetch_context.h", + "testing/mock_resource.cc", + "testing/mock_resource.h", + "testing/mock_resource_client.h", + "testing/web_url_loader_factory_with_mock.cc", + "testing/web_url_loader_factory_with_mock.h", + ] + + configs += [ + "//third_party/blink/renderer:non_test_config", + "//third_party/blink/renderer/platform:blink_platform_config", + ] + + public_deps = [ + "//net", + "//skia", + "//third_party/blink/public:blink_headers", + "//third_party/blink/renderer/platform:platform", + "//third_party/blink/renderer/platform/blob:generator", + "//third_party/icu", + ] +} diff --git a/chromium/third_party/blink/renderer/platform/loader/DEPS b/chromium/third_party/blink/renderer/platform/loader/DEPS new file mode 100644 index 00000000000..3577746624c --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/loader/DEPS @@ -0,0 +1,13 @@ +include_rules = [ + "+base/metrics/field_trial_params.h", # for fetch/ResourceLoadScheduler.cpp + "+base/strings/string_number_conversions.h", # for fetch/ResourceLoadScheduler.cpp + "+components/link_header_util", # for LinkHeader.cpp + "+services/network/public", # for Fetch API and CORS + "+third_party/boringssl/src/include/openssl/curve25519.h" # for SubresourceIntegrity.cpp +] + +specific_include_rules = { + "resource_error\.cc": [ + "+net/base/net_errors.h" + ] +} diff --git a/chromium/third_party/blink/renderer/platform/loader/OWNERS b/chromium/third_party/blink/renderer/platform/loader/OWNERS new file mode 100644 index 00000000000..fa3423b99fb --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/loader/OWNERS @@ -0,0 +1,7 @@ +japhet@chromium.org +mkwst@chromium.org +yhirano@chromium.org +yoav@yoav.ws + +# TEAM: loading-dev@chromium.org +# COMPONENT: Blink>Loader diff --git a/chromium/third_party/blink/renderer/platform/loader/README.md b/chromium/third_party/blink/renderer/platform/loader/README.md new file mode 100644 index 00000000000..83fa16c4885 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/loader/README.md @@ -0,0 +1,22 @@ +# platform/loader/ + +This document describes how files under `platform/loader/` are organized. + +## cors + +Contains Cross-Origin Resource Sharing (CORS) related files. Some functions +in this directory will be removed once CORS support is moved to +//services/network. Please contact {kinuko,tyoshino,toyoshim}@chromium.org when +you need to depend on this directory from new code. + +## fetch + +Contains files for low-level loading APIs. The `PLATFORM_EXPORT` macro is +needed to use them from `core/`. Otherwise they can be used only in +`platform/`. + +## testing + +Contains helper files for testing that are available in both +`blink_platform_unittests` and `webkit_unit_tests`. +These files are built as a part of the `platform:test_support` static library. diff --git a/chromium/third_party/blink/renderer/platform/loader/cors/cors.cc b/chromium/third_party/blink/renderer/platform/loader/cors/cors.cc new file mode 100644 index 00000000000..370971d02b8 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/loader/cors/cors.cc @@ -0,0 +1,215 @@ +// Copyright 2018 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/platform/loader/cors/cors.h" + +#include <string> + +#include "services/network/public/cpp/cors/cors.h" +#include "services/network/public/cpp/cors/preflight_cache.h" +#include "third_party/blink/renderer/platform/loader/cors/cors_error_string.h" +#include "third_party/blink/renderer/platform/network/http_header_map.h" +#include "third_party/blink/renderer/platform/network/http_names.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/weborigin/scheme_registry.h" +#include "third_party/blink/renderer/platform/weborigin/security_origin.h" +#include "third_party/blink/renderer/platform/wtf/text/atomic_string.h" +#include "third_party/blink/renderer/platform/wtf/thread_specific.h" +#include "url/gurl.h" + +namespace blink { + +namespace { + +base::Optional<std::string> GetHeaderValue(const HTTPHeaderMap& header_map, + const AtomicString& header_name) { + if (header_map.Contains(header_name)) { + const AtomicString& atomic_value = header_map.Get(header_name); + CString string_value = atomic_value.GetString().Utf8(); + return std::string(string_value.data(), string_value.length()); + } + return base::nullopt; +} + +network::cors::PreflightCache& GetPerThreadPreflightCache() { + DEFINE_THREAD_SAFE_STATIC_LOCAL(ThreadSpecific<network::cors::PreflightCache>, + cache, ()); + return *cache; +} + +base::Optional<std::string> GetOptionalHeaderValue( + const HTTPHeaderMap& header_map, + const AtomicString& header_name) { + const AtomicString& result = header_map.Get(header_name); + if (result.IsNull()) + return base::nullopt; + + return std::string(result.Ascii().data()); +} + +std::unique_ptr<net::HttpRequestHeaders> CreateNetHttpRequestHeaders( + const HTTPHeaderMap& header_map) { + std::unique_ptr<net::HttpRequestHeaders> request_headers = + std::make_unique<net::HttpRequestHeaders>(); + for (HTTPHeaderMap::const_iterator i = header_map.begin(), + end = header_map.end(); + i != end; ++i) { + DCHECK(!i->key.IsNull()); + DCHECK(!i->value.IsNull()); + request_headers->SetHeader(std::string(i->key.Ascii().data()), + std::string(i->value.Ascii().data())); + } + return request_headers; +} + +} // namespace + +namespace CORS { + +WTF::Optional<network::mojom::CORSError> CheckAccess( + const KURL& response_url, + const int response_status_code, + const HTTPHeaderMap& response_header, + network::mojom::FetchCredentialsMode credentials_mode, + const SecurityOrigin& origin) { + std::unique_ptr<SecurityOrigin::PrivilegeData> privilege = + origin.CreatePrivilegeData(); + return network::cors::CheckAccess( + response_url, response_status_code, + GetHeaderValue(response_header, HTTPNames::Access_Control_Allow_Origin), + GetHeaderValue(response_header, + HTTPNames::Access_Control_Allow_Credentials), + credentials_mode, origin.ToUrlOrigin(), + !privilege->block_local_access_from_local_origin_); +} + +WTF::Optional<network::mojom::CORSError> CheckRedirectLocation( + const KURL& url) { + static const bool run_blink_side_scheme_check = + !RuntimeEnabledFeatures::OutOfBlinkCORSEnabled(); + // TODO(toyoshim): Deprecate Blink side scheme check when we enable + // out-of-renderer CORS support. This will need to deprecate Blink APIs that + // are currently used by an embedder. See https://crbug.com/800669. + if (run_blink_side_scheme_check && + !SchemeRegistry::ShouldTreatURLSchemeAsCORSEnabled(url.Protocol())) { + return network::mojom::CORSError::kRedirectDisallowedScheme; + } + return network::cors::CheckRedirectLocation(url, run_blink_side_scheme_check); +} + +WTF::Optional<network::mojom::CORSError> CheckPreflight( + const int preflight_response_status_code) { + return network::cors::CheckPreflight(preflight_response_status_code); +} + +WTF::Optional<network::mojom::CORSError> CheckExternalPreflight( + const HTTPHeaderMap& response_header) { + return network::cors::CheckExternalPreflight(GetHeaderValue( + response_header, HTTPNames::Access_Control_Allow_External)); +} + +bool IsCORSEnabledRequestMode(network::mojom::FetchRequestMode request_mode) { + return network::cors::IsCORSEnabledRequestMode(request_mode); +} + +bool EnsurePreflightResultAndCacheOnSuccess( + const HTTPHeaderMap& response_header_map, + const String& origin, + const KURL& request_url, + const String& request_method, + const HTTPHeaderMap& request_header_map, + network::mojom::FetchCredentialsMode request_credentials_mode, + String* error_description) { + DCHECK(!origin.IsNull()); + DCHECK(!request_method.IsNull()); + DCHECK(error_description); + + base::Optional<network::mojom::CORSError> error; + + std::unique_ptr<network::cors::PreflightResult> result = + network::cors::PreflightResult::Create( + request_credentials_mode, + GetOptionalHeaderValue(response_header_map, + HTTPNames::Access_Control_Allow_Methods), + GetOptionalHeaderValue(response_header_map, + HTTPNames::Access_Control_Allow_Headers), + GetOptionalHeaderValue(response_header_map, + HTTPNames::Access_Control_Max_Age), + &error); + if (error) { + *error_description = CORS::GetErrorString( + CORS::ErrorParameter::CreateForPreflightResponseCheck(*error, + String())); + return false; + } + + error = result->EnsureAllowedCrossOriginMethod( + std::string(request_method.Ascii().data())); + if (error) { + *error_description = CORS::GetErrorString( + CORS::ErrorParameter::CreateForPreflightResponseCheck(*error, + request_method)); + return false; + } + + std::string detected_error_header; + error = result->EnsureAllowedCrossOriginHeaders( + *CreateNetHttpRequestHeaders(request_header_map), &detected_error_header); + if (error) { + *error_description = CORS::GetErrorString( + CORS::ErrorParameter::CreateForPreflightResponseCheck( + *error, String(detected_error_header.data(), + detected_error_header.length()))); + return false; + } + + DCHECK(!error); + + GetPerThreadPreflightCache().AppendEntry(std::string(origin.Ascii().data()), + request_url, std::move(result)); + return true; +} + +bool CheckIfRequestCanSkipPreflight( + const String& origin, + const KURL& url, + network::mojom::FetchCredentialsMode credentials_mode, + const String& method, + const HTTPHeaderMap& request_header_map) { + DCHECK(!origin.IsNull()); + DCHECK(!method.IsNull()); + + return GetPerThreadPreflightCache().CheckIfRequestCanSkipPreflight( + std::string(origin.Ascii().data()), url, credentials_mode, + std::string(method.Ascii().data()), + *CreateNetHttpRequestHeaders(request_header_map)); +} + +bool IsCORSSafelistedMethod(const String& method) { + DCHECK(!method.IsNull()); + CString utf8_method = method.Utf8(); + return network::cors::IsCORSSafelistedMethod( + std::string(utf8_method.data(), utf8_method.length())); +} + +bool IsCORSSafelistedContentType(const String& media_type) { + CString utf8_media_type = media_type.Utf8(); + return network::cors::IsCORSSafelistedContentType( + std::string(utf8_media_type.data(), utf8_media_type.length())); +} + +bool IsCORSSafelistedHeader(const String& name, const String& value) { + DCHECK(!name.IsNull()); + DCHECK(!value.IsNull()); + CString utf8_name = name.Utf8(); + CString utf8_value = value.Utf8(); + return network::cors::IsCORSSafelistedHeader( + std::string(utf8_name.data(), utf8_name.length()), + std::string(utf8_value.data(), utf8_value.length())); +} + +} // namespace CORS + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/platform/loader/cors/cors.h b/chromium/third_party/blink/renderer/platform/loader/cors/cors.h new file mode 100644 index 00000000000..0ba71c770f7 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/loader/cors/cors.h @@ -0,0 +1,71 @@ +// Copyright 2018 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_PLATFORM_LOADER_CORS_CORS_H_ +#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_LOADER_CORS_CORS_H_ + +#include "services/network/public/mojom/cors.mojom-shared.h" +#include "services/network/public/mojom/fetch_api.mojom-shared.h" +#include "third_party/blink/renderer/platform/platform_export.h" +#include "third_party/blink/renderer/platform/wtf/optional.h" +#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h" + +namespace blink { + +class HTTPHeaderMap; +class KURL; +class SecurityOrigin; + +// CORS related utility functions. +namespace CORS { + +// Thin wrapper functions below are for calling ::network::cors functions from +// Blink core. Once Out-of-renderer CORS is enabled, following functions will +// be removed. +PLATFORM_EXPORT WTF::Optional<network::mojom::CORSError> CheckAccess( + const KURL&, + const int response_status_code, + const HTTPHeaderMap&, + network::mojom::FetchCredentialsMode, + const SecurityOrigin&); + +PLATFORM_EXPORT WTF::Optional<network::mojom::CORSError> CheckRedirectLocation( + const KURL&); + +PLATFORM_EXPORT WTF::Optional<network::mojom::CORSError> CheckPreflight( + const int preflight_response_status_code); + +PLATFORM_EXPORT WTF::Optional<network::mojom::CORSError> CheckExternalPreflight( + const HTTPHeaderMap&); + +PLATFORM_EXPORT bool IsCORSEnabledRequestMode(network::mojom::FetchRequestMode); + +PLATFORM_EXPORT bool EnsurePreflightResultAndCacheOnSuccess( + const HTTPHeaderMap& response_header_map, + const String& origin, + const KURL& request_url, + const String& request_method, + const HTTPHeaderMap& request_header_map, + network::mojom::FetchCredentialsMode request_credentials_mode, + String* error_description); + +PLATFORM_EXPORT bool CheckIfRequestCanSkipPreflight( + const String& origin, + const KURL&, + network::mojom::FetchCredentialsMode, + const String& method, + const HTTPHeaderMap& request_header_map); + +// Thin wrapper functions that will not be removed even after out-of-renderer +// CORS is enabled. +PLATFORM_EXPORT bool IsCORSSafelistedMethod(const String& method); +PLATFORM_EXPORT bool IsCORSSafelistedContentType(const String&); +PLATFORM_EXPORT bool IsCORSSafelistedHeader(const String& name, + const String& value); + +} // namespace CORS + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_PLATFORM_LOADER_CORS_CORS_H_ diff --git a/chromium/third_party/blink/renderer/platform/loader/cors/cors_error_string.cc b/chromium/third_party/blink/renderer/platform/loader/cors/cors_error_string.cc new file mode 100644 index 00000000000..e0d653ff462 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/loader/cors/cors_error_string.cc @@ -0,0 +1,344 @@ +// Copyright 2018 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/platform/loader/cors/cors_error_string.h" + +#include "third_party/blink/renderer/platform/network/http_header_map.h" +#include "third_party/blink/renderer/platform/network/http_names.h" +#include "third_party/blink/renderer/platform/weborigin/security_origin.h" +#include "third_party/blink/renderer/platform/wtf/std_lib_extras.h" + +namespace blink { + +namespace CORS { + +namespace { + +const KURL& GetInvalidURL() { + DEFINE_THREAD_SAFE_STATIC_LOCAL(KURL, invalid_url, ()); + return invalid_url; +} + +bool IsInterestingStatusCode(int status_code) { + // Predicate that gates what status codes should be included in console error + // messages for responses containing no access control headers. + return status_code >= 400; +} + +ErrorParameter CreateWrongParameter(network::mojom::CORSError error) { + return ErrorParameter( + error, GetInvalidURL(), GetInvalidURL(), 0 /* status_code */, + HTTPHeaderMap(), *SecurityOrigin::CreateUnique(), + WebURLRequest::kRequestContextUnspecified, String(), true); +} + +} // namespace + +// static +ErrorParameter ErrorParameter::Create( + const network::mojom::CORSError error, + const KURL& first_url, + const KURL& second_url, + const int status_code, + const HTTPHeaderMap& header_map, + const SecurityOrigin& origin, + const WebURLRequest::RequestContext context) { + return ErrorParameter(error, first_url, second_url, status_code, header_map, + origin, context, String(), false); +} + +// static +ErrorParameter ErrorParameter::CreateForDisallowedByMode( + const KURL& request_url) { + return ErrorParameter(network::mojom::CORSError::kDisallowedByMode, + request_url, GetInvalidURL(), 0 /* status_code */, + HTTPHeaderMap(), *SecurityOrigin::CreateUnique(), + WebURLRequest::kRequestContextUnspecified, String(), + false); +} + +// static +ErrorParameter ErrorParameter::CreateForInvalidResponse( + const KURL& request_url, + const SecurityOrigin& origin) { + return ErrorParameter( + network::mojom::CORSError::kInvalidResponse, request_url, GetInvalidURL(), + 0 /* status_code */, HTTPHeaderMap(), origin, + WebURLRequest::kRequestContextUnspecified, String(), false); +} + +// static +ErrorParameter ErrorParameter::CreateForAccessCheck( + const network::mojom::CORSError error, + const KURL& request_url, + int response_status_code, + const HTTPHeaderMap& response_header_map, + const SecurityOrigin& origin, + const WebURLRequest::RequestContext context, + const KURL& redirect_url) { + switch (error) { + case network::mojom::CORSError::kInvalidResponse: + case network::mojom::CORSError::kWildcardOriginNotAllowed: + case network::mojom::CORSError::kMissingAllowOriginHeader: + case network::mojom::CORSError::kMultipleAllowOriginValues: + case network::mojom::CORSError::kInvalidAllowOriginValue: + case network::mojom::CORSError::kAllowOriginMismatch: + case network::mojom::CORSError::kDisallowCredentialsNotSetToTrue: + return ErrorParameter(error, request_url, redirect_url, + response_status_code, response_header_map, origin, + context, String(), false); + default: + NOTREACHED(); + } + return CreateWrongParameter(error); +} + +// static +ErrorParameter ErrorParameter::CreateForPreflightStatusCheck( + int response_status_code) { + return ErrorParameter(network::mojom::CORSError::kPreflightInvalidStatus, + GetInvalidURL(), GetInvalidURL(), response_status_code, + HTTPHeaderMap(), *SecurityOrigin::CreateUnique(), + WebURLRequest::kRequestContextUnspecified, String(), + false); +} + +// static +ErrorParameter ErrorParameter::CreateForExternalPreflightCheck( + const network::mojom::CORSError error, + const HTTPHeaderMap& response_header_map) { + switch (error) { + case network::mojom::CORSError::kPreflightMissingAllowExternal: + case network::mojom::CORSError::kPreflightInvalidAllowExternal: + return ErrorParameter( + error, GetInvalidURL(), GetInvalidURL(), 0 /* status_code */, + response_header_map, *SecurityOrigin::CreateUnique(), + WebURLRequest::kRequestContextUnspecified, String(), false); + default: + NOTREACHED(); + } + return CreateWrongParameter(error); +} + +// static +ErrorParameter ErrorParameter::CreateForPreflightResponseCheck( + const network::mojom::CORSError error, + const String& hint) { + switch (error) { + case network::mojom::CORSError::kInvalidAllowMethodsPreflightResponse: + case network::mojom::CORSError::kInvalidAllowHeadersPreflightResponse: + case network::mojom::CORSError::kMethodDisallowedByPreflightResponse: + case network::mojom::CORSError::kHeaderDisallowedByPreflightResponse: + return ErrorParameter( + error, GetInvalidURL(), GetInvalidURL(), 0 /* status_code */, + HTTPHeaderMap(), *SecurityOrigin::CreateUnique(), + WebURLRequest::kRequestContextUnspecified, hint, false); + default: + NOTREACHED(); + } + return CreateWrongParameter(error); +} + +// static +ErrorParameter ErrorParameter::CreateForRedirectCheck( + network::mojom::CORSError error, + const KURL& request_url, + const KURL& redirect_url) { + switch (error) { + case network::mojom::CORSError::kRedirectDisallowedScheme: + case network::mojom::CORSError::kRedirectContainsCredentials: + return ErrorParameter( + error, request_url, redirect_url, 0 /* status_code */, + HTTPHeaderMap(), *SecurityOrigin::CreateUnique(), + WebURLRequest::kRequestContextUnspecified, String(), false); + default: + NOTREACHED(); + } + return CreateWrongParameter(error); +} + +ErrorParameter::ErrorParameter(const network::mojom::CORSError error, + const KURL& first_url, + const KURL& second_url, + const int status_code, + const HTTPHeaderMap& header_map, + const SecurityOrigin& origin, + const WebURLRequest::RequestContext context, + const String& hint, + bool unknown) + : error(error), + first_url(first_url), + second_url(second_url), + status_code(status_code), + header_map(header_map), + origin(origin), + context(context), + hint(hint), + unknown(unknown) {} + +String GetErrorString(const ErrorParameter& param) { + static const char kNoCorsInformation[] = + " Have the server send the header with a valid value, or, if an opaque " + "response serves your needs, set the request's mode to 'no-cors' to " + "fetch the resource with CORS disabled."; + + if (param.unknown) + return String::Format("CORS error, code %d", static_cast<int>(param.error)); + + String redirect_denied = + param.second_url.IsValid() + ? String::Format( + "Redirect from '%s' to '%s' has been blocked by CORS policy: ", + param.first_url.GetString().Utf8().data(), + param.second_url.GetString().Utf8().data()) + : String(); + + switch (param.error) { + case network::mojom::CORSError::kDisallowedByMode: + return String::Format( + "Failed to load '%s': Cross origin requests are not allowed by " + "request mode.", + param.first_url.GetString().Utf8().data()); + case network::mojom::CORSError::kInvalidResponse: + return String::Format( + "%sInvalid response. Origin '%s' is therefore not allowed access.", + redirect_denied.Utf8().data(), param.origin.ToString().Utf8().data()); + case network::mojom::CORSError::kWildcardOriginNotAllowed: + return String::Format( + "%sThe value of the 'Access-Control-Allow-Origin' header in the " + "response must not be the wildcard '*' when the request's " + "credentials mode is 'include'. Origin '%s' is therefore not allowed " + "access.%s", + redirect_denied.Utf8().data(), param.origin.ToString().Utf8().data(), + param.context == WebURLRequest::kRequestContextXMLHttpRequest + ? " The credentials mode of requests initiated by the " + "XMLHttpRequest is controlled by the withCredentials attribute." + : ""); + case network::mojom::CORSError::kMissingAllowOriginHeader: + return String::Format( + "%sNo 'Access-Control-Allow-Origin' header is present on the " + "requested resource. Origin '%s' is therefore not allowed access." + "%s%s", + redirect_denied.Utf8().data(), param.origin.ToString().Utf8().data(), + IsInterestingStatusCode(param.status_code) + ? String::Format(" The response had HTTP status code %d.", + param.status_code) + .Utf8() + .data() + : "", + param.context == WebURLRequest::kRequestContextFetch + ? " If an opaque response serves your needs, set the request's " + "mode to 'no-cors' to fetch the resource with CORS disabled." + : ""); + case network::mojom::CORSError::kMultipleAllowOriginValues: + return String::Format( + "%sThe 'Access-Control-Allow-Origin' header contains multiple values " + "'%s', but only one is allowed. Origin '%s' is therefore not allowed " + "access.%s", + redirect_denied.Utf8().data(), + param.header_map.Get(HTTPNames::Access_Control_Allow_Origin) + .Utf8() + .data(), + param.origin.ToString().Utf8().data(), + param.context == WebURLRequest::kRequestContextFetch + ? kNoCorsInformation + : ""); + case network::mojom::CORSError::kInvalidAllowOriginValue: + return String::Format( + "%sThe 'Access-Control-Allow-Origin' header contains the invalid " + "value '%s'. Origin '%s' is therefore not allowed access.%s", + redirect_denied.Utf8().data(), + param.header_map.Get(HTTPNames::Access_Control_Allow_Origin) + .Utf8() + .data(), + param.origin.ToString().Utf8().data(), + param.context == WebURLRequest::kRequestContextFetch + ? kNoCorsInformation + : ""); + case network::mojom::CORSError::kAllowOriginMismatch: + return String::Format( + "%sThe 'Access-Control-Allow-Origin' header has a value '%s' that is " + "not equal to the supplied origin. Origin '%s' is therefore not " + "allowed access.%s", + redirect_denied.Utf8().data(), + param.header_map.Get(HTTPNames::Access_Control_Allow_Origin) + .Utf8() + .data(), + param.origin.ToString().Utf8().data(), + param.context == WebURLRequest::kRequestContextFetch + ? kNoCorsInformation + : ""); + case network::mojom::CORSError::kDisallowCredentialsNotSetToTrue: + return String::Format( + "%sThe value of the 'Access-Control-Allow-Credentials' header in " + "the response is '%s' which must be 'true' when the request's " + "credentials mode is 'include'. Origin '%s' is therefore not allowed " + "access.%s", + redirect_denied.Utf8().data(), + param.header_map.Get(HTTPNames::Access_Control_Allow_Credentials) + .Utf8() + .data(), + param.origin.ToString().Utf8().data(), + (param.context == WebURLRequest::kRequestContextXMLHttpRequest + ? " The credentials mode of requests initiated by the " + "XMLHttpRequest is controlled by the withCredentials " + "attribute." + : "")); + case network::mojom::CORSError::kPreflightInvalidStatus: + return String::Format( + "Response for preflight has invalid HTTP status code %d.", + param.status_code); + case network::mojom::CORSError::kPreflightMissingAllowExternal: + return String( + "No 'Access-Control-Allow-External' header was present in the " + "preflight response for this external request (This is an " + "experimental header which is defined in " + "'https://wicg.github.io/cors-rfc1918/')."); + case network::mojom::CORSError::kPreflightInvalidAllowExternal: + return String::Format( + "The 'Access-Control-Allow-External' header in the preflight " + "response for this external request had a value of '%s', not 'true' " + "(This is an experimental header which is defined in " + "'https://wicg.github.io/cors-rfc1918/').", + param.header_map.Get(HTTPNames::Access_Control_Allow_External) + .Utf8() + .data()); + case network::mojom::CORSError::kInvalidAllowMethodsPreflightResponse: + return String( + "Cannot parse Access-Control-Allow-Methods response header field in " + "preflight response."); + case network::mojom::CORSError::kInvalidAllowHeadersPreflightResponse: + return String( + "Cannot parse Access-Control-Allow-Headers response header field in " + "preflight response."); + case network::mojom::CORSError::kMethodDisallowedByPreflightResponse: + return String::Format( + "Method %s is not allowed by Access-Control-Allow-Methods in " + "preflight response.", + param.hint.Utf8().data()); + case network::mojom::CORSError::kHeaderDisallowedByPreflightResponse: + return String::Format( + "Request header field %s is not allowed by " + "Access-Control-Allow-Headers in preflight response.", + param.hint.Utf8().data()); + case network::mojom::CORSError::kRedirectDisallowedScheme: + return String::Format( + "%sRedirect location '%s' has a disallowed scheme for cross-origin " + "requests.", + redirect_denied.Utf8().data(), + param.second_url.GetString().Utf8().data()); + case network::mojom::CORSError::kRedirectContainsCredentials: + return String::Format( + "%sRedirect location '%s' contains a username and password, which is " + "disallowed for cross-origin requests.", + redirect_denied.Utf8().data(), + param.second_url.GetString().Utf8().data()); + } + NOTREACHED(); + return String(); +} + +} // namespace CORS + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/platform/loader/cors/cors_error_string.h b/chromium/third_party/blink/renderer/platform/loader/cors/cors_error_string.h new file mode 100644 index 00000000000..73bd0f9e07d --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/loader/cors/cors_error_string.h @@ -0,0 +1,112 @@ +// Copyright 2018 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_PLATFORM_LOADER_CORS_CORS_ERROR_STRING_H_ +#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_LOADER_CORS_CORS_ERROR_STRING_H_ + +#include "base/macros.h" +#include "services/network/public/mojom/cors.mojom-shared.h" +#include "third_party/blink/public/platform/web_url_request.h" +#include "third_party/blink/renderer/platform/platform_export.h" +#include "third_party/blink/renderer/platform/weborigin/kurl.h" + +namespace blink { + +class HTTPHeaderMap; +class SecurityOrigin; + +// CORS error strings related utility functions. +namespace CORS { + +// A struct to pass error dependent arguments for |GetErrorString|. +struct PLATFORM_EXPORT ErrorParameter { + // Creates an ErrorParameter for generic cases. Use this function if |error| + // can contain any. + static ErrorParameter Create(const network::mojom::CORSError, + const KURL& first_url, + const KURL& second_url, + const int status_code, + const HTTPHeaderMap&, + const SecurityOrigin&, + const WebURLRequest::RequestContext); + + // Creates an ErrorParameter for kDisallowedByMode. + static ErrorParameter CreateForDisallowedByMode(const KURL& request_url); + + // Creates an ErrorParameter for kInvalidResponse. + static ErrorParameter CreateForInvalidResponse(const KURL& request_url, + const SecurityOrigin&); + + // Creates an ErrorParameter for an error that CORS::CheckAccess() returns. + // |error| for redirect check needs to specify a valid |redirect_url|. The + // |redirect_url| can be omitted not to include redirect related information. + static ErrorParameter CreateForAccessCheck( + const network::mojom::CORSError, + const KURL& request_url, + int response_status_code, + const HTTPHeaderMap& response_header_map, + const SecurityOrigin&, + const WebURLRequest::RequestContext, + const KURL& redirect_url = KURL()); + + // Creates an ErrorParameter for kPreflightInvalidStatus that + // CORS::CheckPreflight() returns. + static ErrorParameter CreateForPreflightStatusCheck(int response_status_code); + + // Creates an ErrorParameter for an error that CORS::CheckExternalPreflight() + // returns. + static ErrorParameter CreateForExternalPreflightCheck( + const network::mojom::CORSError, + const HTTPHeaderMap& response_header_map); + + // Creates an ErrorParameter for an error that is related to CORS-preflight + // response checks. + // |hint| should contain a banned request method for + // kMethodDisallowedByPreflightResponse, a banned request header name for + // kHeaderDisallowedByPreflightResponse, or can be omitted for others. + static ErrorParameter CreateForPreflightResponseCheck( + const network::mojom::CORSError, + const String& hint); + + // Creates an ErrorParameter for CORS::CheckRedirectLocation() returns. + static ErrorParameter CreateForRedirectCheck(network::mojom::CORSError, + const KURL& request_url, + const KURL& redirect_url); + + // Should not be used directly by external callers. Use Create functions + // above. + ErrorParameter(const network::mojom::CORSError, + const KURL& first_url, + const KURL& second_url, + const int status_code, + const HTTPHeaderMap&, + const SecurityOrigin&, + const WebURLRequest::RequestContext, + const String& hint, + bool unknown); + + // Members that this struct carries. + const network::mojom::CORSError error; + const KURL& first_url; + const KURL& second_url; + const int status_code; + const HTTPHeaderMap& header_map; + const SecurityOrigin& origin; + const WebURLRequest::RequestContext context; + const String& hint; + + // Set to true when an ErrorParameter was created in a wrong way. Used in + // GetErrorString() to be robust for coding errors. + const bool unknown; +}; + +// Stringify CORSError mainly for inspector messages. Generated string should +// not be exposed to JavaScript for security reasons. +PLATFORM_EXPORT String GetErrorString(const ErrorParameter&); + +} // namespace CORS + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_PLATFORM_LOADER_CORS_CORS_ERROR_STRING_H_ diff --git a/chromium/third_party/blink/renderer/platform/loader/cors/cors_status.h b/chromium/third_party/blink/renderer/platform/loader/cors/cors_status.h new file mode 100644 index 00000000000..d6ffdc89e3f --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/loader/cors/cors_status.h @@ -0,0 +1,27 @@ +// 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_PLATFORM_LOADER_CORS_CORS_STATUS_H_ +#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_LOADER_CORS_CORS_STATUS_H_ + +namespace blink { + +enum class CORSStatus { + kUnknown, // Status not determined - not supposed to be seen by users. + kNotApplicable, // E.g. for main resources or if not in fetch mode CORS. + + // Response not handled by service worker: + kSameOrigin, // Request was same origin. + kSuccessful, // Request was cross origin and CORS checks passed. + kFailed, // Request was cross origin and CORS checks failed. + + // Response handled by service worker: + kServiceWorkerSuccessful, // ResponseType other than opaque (including + // error). + kServiceWorkerOpaque, // ResponseType was opaque. +}; + +} // namespace blink + +#endif diff --git a/chromium/third_party/blink/renderer/platform/loader/fetch/README.md b/chromium/third_party/blink/renderer/platform/loader/fetch/README.md new file mode 100644 index 00000000000..5e6da7211f2 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/loader/fetch/README.md @@ -0,0 +1,6 @@ +Low-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/platform/loader/fetch/access_control_status.h b/chromium/third_party/blink/renderer/platform/loader/fetch/access_control_status.h new file mode 100644 index 00000000000..bd8d9454dc5 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/loader/fetch/access_control_status.h @@ -0,0 +1,18 @@ +// 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_PLATFORM_LOADER_FETCH_ACCESS_CONTROL_STATUS_H_ +#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_LOADER_FETCH_ACCESS_CONTROL_STATUS_H_ + +namespace blink { + +enum AccessControlStatus { + kNotSharableCrossOrigin, + kSharableCrossOrigin, + kOpaqueResource +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_PLATFORM_LOADER_FETCH_ACCESS_CONTROL_STATUS_H_ diff --git a/chromium/third_party/blink/renderer/platform/loader/fetch/buffering_data_pipe_writer.cc b/chromium/third_party/blink/renderer/platform/loader/fetch/buffering_data_pipe_writer.cc new file mode 100644 index 00000000000..e7f1ee3bfcb --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/loader/fetch/buffering_data_pipe_writer.cc @@ -0,0 +1,109 @@ +// 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/platform/loader/fetch/buffering_data_pipe_writer.h" + +#include "base/single_thread_task_runner.h" + +namespace blink { + +namespace { + +const auto kNone = MOJO_WRITE_DATA_FLAG_NONE; + +} // namespace + +BufferingDataPipeWriter::BufferingDataPipeWriter( + mojo::ScopedDataPipeProducerHandle handle, + base::SingleThreadTaskRunner* runner) + : handle_(std::move(handle)), + watcher_(FROM_HERE, mojo::SimpleWatcher::ArmingPolicy::MANUAL, runner) { + watcher_.Watch(handle_.get(), MOJO_HANDLE_SIGNAL_WRITABLE, + MOJO_WATCH_CONDITION_SATISFIED, + base::BindRepeating(&BufferingDataPipeWriter::OnWritable, + base::Unretained(this))); +} + +bool BufferingDataPipeWriter::Write(const char* buffer, uint32_t num_bytes) { + DCHECK(!finished_); + if (!handle_.is_valid()) + return false; + + if (buffer_.empty()) { + while (num_bytes > 0) { + uint32_t size = num_bytes; + MojoResult result = handle_->WriteData(buffer, &size, kNone); + if (result == MOJO_RESULT_SHOULD_WAIT) + break; + if (result != MOJO_RESULT_OK) { + Clear(); + return false; + } + num_bytes -= size; + buffer += size; + } + } + if (num_bytes == 0) + return true; + + buffer_.push_back(Vector<char>()); + buffer_.back().Append(buffer, num_bytes); + if (!waiting_) { + waiting_ = true; + watcher_.ArmOrNotify(); + } + return true; +} + +void BufferingDataPipeWriter::Finish() { + finished_ = true; + ClearIfNeeded(); +} + +void BufferingDataPipeWriter::OnWritable(MojoResult, + const mojo::HandleSignalsState&) { + if (!handle_.is_valid()) + return; + waiting_ = false; + while (!buffer_.empty()) { + WTF::Vector<char>& front = buffer_.front(); + + uint32_t size = front.size() - front_written_size_; + + MojoResult result = + handle_->WriteData(front.data() + front_written_size_, &size, kNone); + if (result == MOJO_RESULT_SHOULD_WAIT) { + waiting_ = true; + watcher_.ArmOrNotify(); + return; + } + if (result != MOJO_RESULT_OK) { + Clear(); + return; + } + front_written_size_ += size; + + if (front_written_size_ == front.size()) { + front_written_size_ = 0; + buffer_.TakeFirst(); + } + } + ClearIfNeeded(); +} + +void BufferingDataPipeWriter::Clear() { + handle_.reset(); + watcher_.Cancel(); + buffer_.clear(); +} + +void BufferingDataPipeWriter::ClearIfNeeded() { + if (!finished_) + return; + + if (buffer_.empty()) + Clear(); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/platform/loader/fetch/buffering_data_pipe_writer.h b/chromium/third_party/blink/renderer/platform/loader/fetch/buffering_data_pipe_writer.h new file mode 100644 index 00000000000..8b3175fd537 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/loader/fetch/buffering_data_pipe_writer.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_PLATFORM_LOADER_FETCH_BUFFERING_DATA_PIPE_WRITER_H_ +#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_LOADER_FETCH_BUFFERING_DATA_PIPE_WRITER_H_ + +#include "base/single_thread_task_runner.h" +#include "mojo/public/cpp/system/data_pipe.h" +#include "mojo/public/cpp/system/simple_watcher.h" +#include "third_party/blink/renderer/platform/platform_export.h" +#include "third_party/blink/renderer/platform/wtf/deque.h" +#include "third_party/blink/renderer/platform/wtf/vector.h" + +namespace blink { + +// A writer to a mojo data pipe which has a buffer to store contents. As a +// result, it is possible for a caller to miss write failures. +class PLATFORM_EXPORT BufferingDataPipeWriter { + public: + BufferingDataPipeWriter(mojo::ScopedDataPipeProducerHandle, + base::SingleThreadTaskRunner*); + + // Writes buffer[0:num_bytes] to the data pipe. Returns true if there is no + // error. + bool Write(const char* buffer, uint32_t num_bytes); + + // Finishes writing. After calling this function, Write must not be called. + void Finish(); + + private: + void OnWritable(MojoResult, const mojo::HandleSignalsState&); + void ClearIfNeeded(); + void Clear(); + + mojo::ScopedDataPipeProducerHandle handle_; + mojo::SimpleWatcher watcher_; + Deque<Vector<char>> buffer_; + size_t front_written_size_ = 0; + bool waiting_ = false; + bool finished_ = false; +}; + +} // namespace blink + +#endif diff --git a/chromium/third_party/blink/renderer/platform/loader/fetch/buffering_data_pipe_writer_test.cc b/chromium/third_party/blink/renderer/platform/loader/fetch/buffering_data_pipe_writer_test.cc new file mode 100644 index 00000000000..0788a286d23 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/loader/fetch/buffering_data_pipe_writer_test.cc @@ -0,0 +1,78 @@ +// 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/platform/loader/fetch/buffering_data_pipe_writer.h" + +#include <memory> +#include <random> + +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/blink/renderer/platform/testing/testing_platform_support_with_mock_scheduler.h" + +namespace blink { +namespace { + +TEST(BufferingDataPipeWriterTest, WriteMany) { + ScopedTestingPlatformSupport<TestingPlatformSupportWithMockScheduler> + platform; + constexpr int kCapacity = 4096; + + std::minstd_rand engine(99); + + mojo::ScopedDataPipeProducerHandle producer; + mojo::ScopedDataPipeConsumerHandle consumer; + MojoCreateDataPipeOptions options; + options.struct_size = sizeof(MojoCreateDataPipeOptions); + options.flags = MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE; + options.element_num_bytes = 1; + options.capacity_num_bytes = kCapacity; + + MojoResult result = mojo::CreateDataPipe(&options, &producer, &consumer); + ASSERT_EQ(MOJO_RESULT_OK, result); + + constexpr size_t total = kCapacity * 3; + constexpr size_t writing_chunk_size = 5; + constexpr size_t reading_chunk_size = 7; + Vector<char> input, output; + + for (size_t i = 0; i < total; ++i) + input.push_back(static_cast<char>(engine() % 26 + 'A')); + + auto writer = std::make_unique<BufferingDataPipeWriter>( + std::move(producer), platform->CurrentThread()->GetTaskRunner().get()); + + for (size_t i = 0; i < total;) { + // We use a temporary buffer to check that the buffer is copied immediately. + char temp[writing_chunk_size] = {}; + size_t size = std::min(total - i, writing_chunk_size); + + std::copy(input.data() + i, input.data() + i + size, temp); + ASSERT_TRUE(writer->Write(temp, size)); + + i += size; + } + + writer->Finish(); + + while (true) { + constexpr auto kNone = MOJO_READ_DATA_FLAG_NONE; + char buffer[reading_chunk_size] = {}; + uint32_t size = reading_chunk_size; + result = consumer->ReadData(buffer, &size, kNone); + + if (result == MOJO_RESULT_SHOULD_WAIT) { + platform->RunUntilIdle(); + continue; + } + if (result == MOJO_RESULT_FAILED_PRECONDITION) + break; + + ASSERT_EQ(MOJO_RESULT_OK, result); + output.Append(buffer, size); + } + EXPECT_EQ(output, input); +} + +} // namespace +} // namespace blink diff --git a/chromium/third_party/blink/renderer/platform/loader/fetch/cached_metadata.cc b/chromium/third_party/blink/renderer/platform/loader/fetch/cached_metadata.cc new file mode 100644 index 00000000000..563a6742750 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/loader/fetch/cached_metadata.cc @@ -0,0 +1,54 @@ +// 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/platform/loader/fetch/cached_metadata.h" + +#include "third_party/blink/renderer/platform/loader/fetch/cached_metadata_handler.h" + +namespace blink { + +scoped_refptr<CachedMetadata> CachedMetadata::CreateFromSerializedData( + const char* data, + size_t size) { + // Ensure the data is big enough, otherwise discard the data. + if (size < kCachedMetaDataStart) { + return nullptr; + } + // Ensure the marker matches, otherwise discard the data. + if (*reinterpret_cast<const uint32_t*>(data) != + CachedMetadataHandler::kSingleEntry) { + return nullptr; + } + return base::AdoptRef(new CachedMetadata(data, size)); +} + +CachedMetadata::CachedMetadata(const char* data, size_t size) { + // Serialized metadata should have non-empty data. + DCHECK_GT(size, kCachedMetaDataStart); + DCHECK(data); + // Make sure that the first int in the data is the single entry marker. + CHECK_EQ(*reinterpret_cast<const uint32_t*>(data), + CachedMetadataHandler::kSingleEntry); + + serialized_data_.ReserveInitialCapacity(size); + serialized_data_.Append(data, size); +} + +CachedMetadata::CachedMetadata(uint32_t data_type_id, + const char* data, + size_t size) { + // Don't allow an ID of 0, it is used internally to indicate errors. + DCHECK(data_type_id); + DCHECK(data); + + serialized_data_.ReserveInitialCapacity(kCachedMetaDataStart + size); + uint32_t marker = CachedMetadataHandler::kSingleEntry; + serialized_data_.Append(reinterpret_cast<const char*>(&marker), + sizeof(uint32_t)); + serialized_data_.Append(reinterpret_cast<const char*>(&data_type_id), + sizeof(uint32_t)); + serialized_data_.Append(data, size); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/platform/loader/fetch/cached_metadata.h b/chromium/third_party/blink/renderer/platform/loader/fetch/cached_metadata.h new file mode 100644 index 00000000000..1763b23fd42 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/loader/fetch/cached_metadata.h @@ -0,0 +1,95 @@ +/* + * 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_PLATFORM_LOADER_FETCH_CACHED_METADATA_H_ +#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_LOADER_FETCH_CACHED_METADATA_H_ + +#include <stdint.h> +#include "base/memory/scoped_refptr.h" +#include "third_party/blink/renderer/platform/platform_export.h" +#include "third_party/blink/renderer/platform/wtf/assertions.h" +#include "third_party/blink/renderer/platform/wtf/ref_counted.h" +#include "third_party/blink/renderer/platform/wtf/vector.h" + +namespace blink { + +// |m_serializedData| consists of a 32 bit marker, 32 bits type ID, and actual +// data. +constexpr size_t kCacheDataTypeStart = sizeof(uint32_t); +constexpr size_t kCachedMetaDataStart = kCacheDataTypeStart + sizeof(uint32_t); + +// Metadata retrieved from the embedding application's cache. +// +// Serialized data is NOT portable across architectures. However, reading the +// data type ID will reject data generated with a different byte-order. +class PLATFORM_EXPORT CachedMetadata : public RefCounted<CachedMetadata> { + public: + static scoped_refptr<CachedMetadata> Create(uint32_t data_type_id, + const char* data, + size_t size) { + return base::AdoptRef(new CachedMetadata(data_type_id, data, size)); + } + + static scoped_refptr<CachedMetadata> CreateFromSerializedData( + const char* data, + size_t); + + ~CachedMetadata() = default; + + const Vector<char>& SerializedData() const { return serialized_data_; } + + uint32_t DataTypeID() const { + DCHECK_GE(serialized_data_.size(), kCachedMetaDataStart); + return *reinterpret_cast_ptr<uint32_t*>( + const_cast<char*>(serialized_data_.data() + kCacheDataTypeStart)); + } + + const char* Data() const { + DCHECK_GE(serialized_data_.size(), kCachedMetaDataStart); + return serialized_data_.data() + kCachedMetaDataStart; + } + + size_t size() const { + DCHECK_GE(serialized_data_.size(), kCachedMetaDataStart); + return serialized_data_.size() - kCachedMetaDataStart; + } + + private: + CachedMetadata(const char* data, size_t); + CachedMetadata(uint32_t data_type_id, const char* data, size_t); + + // Since the serialization format supports random access, storing it in + // serialized form avoids need for a copy during serialization. + Vector<char> serialized_data_; +}; + +} // namespace blink + +#endif diff --git a/chromium/third_party/blink/renderer/platform/loader/fetch/cached_metadata_handler.h b/chromium/third_party/blink/renderer/platform/loader/fetch/cached_metadata_handler.h new file mode 100644 index 00000000000..626544fddef --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/loader/fetch/cached_metadata_handler.h @@ -0,0 +1,70 @@ +// 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_PLATFORM_LOADER_FETCH_CACHED_METADATA_HANDLER_H_ +#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_LOADER_FETCH_CACHED_METADATA_HANDLER_H_ + +#include <stdint.h> +#include "third_party/blink/renderer/platform/heap/handle.h" +#include "third_party/blink/renderer/platform/wtf/forward.h" + +namespace blink { + +class CachedMetadata; + +// Handler class for caching operations. +class CachedMetadataHandler + : public GarbageCollectedFinalized<CachedMetadataHandler> { + public: + enum CacheType { + kSendToPlatform, // send cache data to blink::Platform::cacheMetadata + kCacheLocally // cache only in Resource's member variables + }; + + // Enum for marking serialized cached metadatas so that the deserializers + // do not conflict. + enum CachedMetadataType : uint32_t { + kSingleEntry, // the metadata is a single CachedMetadata entry + kSourceKeyedMap // the metadata is multiple CachedMetadata entries keyed by + // a source string. + }; + + virtual ~CachedMetadataHandler() = default; + virtual void Trace(blink::Visitor* visitor) {} + + // Reset existing metadata, to allow setting new data. + virtual void ClearCachedMetadata(CacheType = kCacheLocally) = 0; + + // Returns the encoding to which the cache is specific. + virtual String Encoding() const = 0; + + virtual bool IsServedFromCacheStorage() const = 0; + + protected: + CachedMetadataHandler() = default; +}; + +// A CachedMetadataHandler which stores one piece of metadata. +class SingleCachedMetadataHandler : public CachedMetadataHandler { + public: + // Caches the given metadata in association with this resource and suggests + // that the platform persist it. The dataTypeID is a pseudo-randomly chosen + // identifier that is used to distinguish data generated by the caller. + virtual void SetCachedMetadata(uint32_t data_type_id, + const char*, + size_t, + CacheType = kSendToPlatform) = 0; + + // Returns cached metadata of the given type associated with this resource. + // This cached metadata can be pruned at any time. + virtual scoped_refptr<CachedMetadata> GetCachedMetadata( + uint32_t data_type_id) const = 0; + + protected: + SingleCachedMetadataHandler() = default; +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_PLATFORM_LOADER_FETCH_CACHED_METADATA_HANDLER_H_ diff --git a/chromium/third_party/blink/renderer/platform/loader/fetch/client_hints_preferences.cc b/chromium/third_party/blink/renderer/platform/loader/fetch/client_hints_preferences.cc new file mode 100644 index 00000000000..6a3d226f8d3 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/loader/fetch/client_hints_preferences.cc @@ -0,0 +1,150 @@ +// 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/platform/loader/fetch/client_hints_preferences.h" + +#include "base/macros.h" +#include "third_party/blink/public/common/client_hints/client_hints.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/http_parsers.h" +#include "third_party/blink/renderer/platform/runtime_enabled_features.h" +#include "third_party/blink/renderer/platform/weborigin/kurl.h" + +namespace blink { + +namespace { + +void ParseAcceptChHeader(const String& header_value, + WebEnabledClientHints& enabled_hints) { + CommaDelimitedHeaderSet accept_client_hints_header; + ParseCommaDelimitedHeader(header_value, accept_client_hints_header); + + for (size_t i = 0; + i < static_cast<int>(mojom::WebClientHintsType::kMaxValue) + 1; ++i) { + enabled_hints.SetIsEnabled( + static_cast<mojom::WebClientHintsType>(i), + accept_client_hints_header.Contains(kClientHintsHeaderMapping[i])); + } + + enabled_hints.SetIsEnabled( + mojom::WebClientHintsType::kDeviceMemory, + enabled_hints.IsEnabled(mojom::WebClientHintsType::kDeviceMemory) && + RuntimeEnabledFeatures::DeviceMemoryHeaderEnabled()); + + enabled_hints.SetIsEnabled( + mojom::WebClientHintsType::kRtt, + enabled_hints.IsEnabled(mojom::WebClientHintsType::kRtt) && + RuntimeEnabledFeatures::NetInfoRttHeaderEnabled()); + + enabled_hints.SetIsEnabled( + mojom::WebClientHintsType::kDownlink, + enabled_hints.IsEnabled(mojom::WebClientHintsType::kDownlink) && + RuntimeEnabledFeatures::NetInfoDownlinkHeaderEnabled()); + + enabled_hints.SetIsEnabled( + mojom::WebClientHintsType::kEct, + enabled_hints.IsEnabled(mojom::WebClientHintsType::kEct) && + RuntimeEnabledFeatures::NetInfoEffectiveTypeHeaderEnabled()); +} + +} // namespace + +ClientHintsPreferences::ClientHintsPreferences() { + DCHECK_EQ(static_cast<size_t>(mojom::WebClientHintsType::kMaxValue) + 1, + kClientHintsHeaderMappingCount); +} + +void ClientHintsPreferences::UpdateFrom( + const ClientHintsPreferences& preferences) { + for (size_t i = 0; + i < static_cast<int>(mojom::WebClientHintsType::kMaxValue) + 1; ++i) { + mojom::WebClientHintsType type = static_cast<mojom::WebClientHintsType>(i); + enabled_hints_.SetIsEnabled(type, preferences.ShouldSend(type)); + } +} + +void ClientHintsPreferences::UpdateFromAcceptClientHintsHeader( + const String& header_value, + const KURL& url, + Context* context) { + if (header_value.IsEmpty()) + return; + + // If the persistent client hint feature is enabled, then client hints + // should be allowed only on secure URLs. + if (blink::RuntimeEnabledFeatures::ClientHintsPersistentEnabled() && + !IsClientHintsAllowed(url)) { + return; + } + + WebEnabledClientHints new_enabled_types; + + ParseAcceptChHeader(header_value, new_enabled_types); + + for (size_t i = 0; + i < static_cast<int>(mojom::WebClientHintsType::kMaxValue) + 1; ++i) { + mojom::WebClientHintsType type = static_cast<mojom::WebClientHintsType>(i); + enabled_hints_.SetIsEnabled(type, enabled_hints_.IsEnabled(type) || + new_enabled_types.IsEnabled(type)); + } + + if (context) { + for (size_t i = 0; + i < static_cast<int>(mojom::WebClientHintsType::kMaxValue) + 1; ++i) { + mojom::WebClientHintsType type = + static_cast<mojom::WebClientHintsType>(i); + if (enabled_hints_.IsEnabled(type)) + context->CountClientHints(type); + } + } +} + +// static +void ClientHintsPreferences::UpdatePersistentHintsFromHeaders( + const ResourceResponse& response, + Context* context, + WebEnabledClientHints& enabled_hints, + TimeDelta* persist_duration) { + *persist_duration = base::TimeDelta(); + + if (response.WasCached()) + return; + + String accept_ch_header_value = + response.HttpHeaderField(HTTPNames::Accept_CH); + String accept_ch_lifetime_header_value = + response.HttpHeaderField(HTTPNames::Accept_CH_Lifetime); + + if (!RuntimeEnabledFeatures::ClientHintsPersistentEnabled() || + accept_ch_header_value.IsEmpty() || + accept_ch_lifetime_header_value.IsEmpty()) { + return; + } + + const KURL url = response.Url(); + if (!IsClientHintsAllowed(url)) + return; + + bool conversion_ok = false; + int64_t persist_duration_seconds = + accept_ch_lifetime_header_value.ToInt64Strict(&conversion_ok); + if (!conversion_ok || persist_duration_seconds <= 0) + return; + + *persist_duration = TimeDelta::FromSeconds(persist_duration_seconds); + if (context) + context->CountPersistentClientHintHeaders(); + + ParseAcceptChHeader(accept_ch_header_value, enabled_hints); +} + +// static +bool ClientHintsPreferences::IsClientHintsAllowed(const KURL& url) { + return (url.ProtocolIs("http") || url.ProtocolIs("https")) && + (SecurityOrigin::IsSecure(url) || + SecurityOrigin::Create(url)->IsLocalhost()); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/platform/loader/fetch/client_hints_preferences.h b/chromium/third_party/blink/renderer/platform/loader/fetch/client_hints_preferences.h new file mode 100644 index 00000000000..6900745d56e --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/loader/fetch/client_hints_preferences.h @@ -0,0 +1,77 @@ +// 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_PLATFORM_LOADER_FETCH_CLIENT_HINTS_PREFERENCES_H_ +#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_LOADER_FETCH_CLIENT_HINTS_PREFERENCES_H_ + +#include "third_party/blink/public/platform/web_client_hints_type.h" +#include "third_party/blink/renderer/platform/platform_export.h" +#include "third_party/blink/renderer/platform/wtf/allocator.h" +#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h" +#include "third_party/blink/renderer/platform/wtf/time.h" + +namespace blink { + +class KURL; +class ResourceResponse; + +// TODO (tbansal): Remove PLATFORM_EXPORT, and pass WebClientHintsType +// everywhere. +class PLATFORM_EXPORT ClientHintsPreferences { + DISALLOW_NEW(); + + public: + class Context { + public: + virtual void CountClientHints(mojom::WebClientHintsType) = 0; + virtual void CountPersistentClientHintHeaders() = 0; + + protected: + virtual ~Context() = default; + }; + + ClientHintsPreferences(); + + void UpdateFrom(const ClientHintsPreferences&); + + // Parses the client hints headers, and populates |this| with the client hint + // preferences. |url| is the URL of the resource whose response included the + // |header_value|. |context| may be null. If client hints are not allowed for + // |url|, then |this| would not be updated. + void UpdateFromAcceptClientHintsHeader(const String& header_value, + const KURL&, + Context*); + + bool ShouldSend(mojom::WebClientHintsType type) const { + return enabled_hints_.IsEnabled(type); + } + void SetShouldSendForTesting(mojom::WebClientHintsType type) { + enabled_hints_.SetIsEnabled(type, true); + } + + // Parses the client hints headers, and populates |enabled_hints| with the + // client hint preferences that should be persisted for |persist_duration|. + // |persist_duration| should be non-null. + // If there are no client hints that need to be persisted, + // |persist_duration| is not set, otherwise it is set to the duration for + // which the client hint preferences should be persisted. + // UpdatePersistentHintsFromHeaders may be called for all responses + // received (including subresources). |context| may be null. + static void UpdatePersistentHintsFromHeaders( + const ResourceResponse&, + Context*, + WebEnabledClientHints& enabled_hints, + TimeDelta* persist_duration); + + // Returns true if client hints are allowed for the provided KURL. Client + // hints are allowed only on HTTP URLs that belong to secure contexts. + static bool IsClientHintsAllowed(const KURL&); + + private: + WebEnabledClientHints enabled_hints_; +}; + +} // namespace blink + +#endif diff --git a/chromium/third_party/blink/renderer/platform/loader/fetch/client_hints_preferences_test.cc b/chromium/third_party/blink/renderer/platform/loader/fetch/client_hints_preferences_test.cc new file mode 100644 index 00000000000..dde2ec04ca4 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/loader/fetch/client_hints_preferences_test.cc @@ -0,0 +1,158 @@ +// 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/platform/loader/fetch/client_hints_preferences.h" + +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/blink/public/platform/web_runtime_features.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/weborigin/kurl.h" +#include "third_party/blink/renderer/platform/wtf/time.h" + +namespace blink { + +class ClientHintsPreferencesTest : public testing::Test {}; + +TEST_F(ClientHintsPreferencesTest, BasicSecure) { + struct TestCase { + const char* header_value; + bool expectation_resource_width; + bool expectation_dpr; + bool expectation_viewport_width; + bool expectation_rtt; + bool expectation_downlink; + bool expectation_ect; + } cases[] = { + {"width, dpr, viewportWidth", true, true, false, false, false, false}, + {"WiDtH, dPr, viewport-width, rtt, downlink, ect", true, true, true, true, + true, true}, + {"WiDtH, dPr, viewport-width, rtt, downlink, effective-connection-type", + true, true, true, true, true, false}, + {"WIDTH, DPR, VIWEPROT-Width", true, true, false, false, false, false}, + {"VIewporT-Width, wutwut, width", true, false, true, false, false, false}, + {"dprw", false, false, false, false, false, false}, + {"DPRW", false, false, false, false, false, false}, + }; + + for (const auto& test_case : cases) { + ClientHintsPreferences preferences; + const KURL kurl(String::FromUTF8("https://www.google.com/")); + preferences.UpdateFromAcceptClientHintsHeader(test_case.header_value, kurl, + nullptr); + EXPECT_EQ( + test_case.expectation_resource_width, + preferences.ShouldSend(mojom::WebClientHintsType::kResourceWidth)); + EXPECT_EQ(test_case.expectation_dpr, + preferences.ShouldSend(mojom::WebClientHintsType::kDpr)); + EXPECT_EQ( + test_case.expectation_viewport_width, + preferences.ShouldSend(mojom::WebClientHintsType::kViewportWidth)); + EXPECT_EQ(test_case.expectation_rtt, + preferences.ShouldSend(mojom::WebClientHintsType::kRtt)); + EXPECT_EQ(test_case.expectation_downlink, + preferences.ShouldSend(mojom::WebClientHintsType::kDownlink)); + EXPECT_EQ(test_case.expectation_ect, + preferences.ShouldSend(mojom::WebClientHintsType::kEct)); + + // Calling UpdateFromAcceptClientHintsHeader with empty header should have + // no impact on client hint preferences. + preferences.UpdateFromAcceptClientHintsHeader("", kurl, nullptr); + EXPECT_EQ( + test_case.expectation_resource_width, + preferences.ShouldSend(mojom::WebClientHintsType::kResourceWidth)); + EXPECT_EQ(test_case.expectation_dpr, + preferences.ShouldSend(mojom::WebClientHintsType::kDpr)); + EXPECT_EQ( + test_case.expectation_viewport_width, + preferences.ShouldSend(mojom::WebClientHintsType::kViewportWidth)); + + // Calling UpdateFromAcceptClientHintsHeader with an invalid header should + // have no impact on client hint preferences. + preferences.UpdateFromAcceptClientHintsHeader("foobar", kurl, nullptr); + EXPECT_EQ( + test_case.expectation_resource_width, + preferences.ShouldSend(mojom::WebClientHintsType::kResourceWidth)); + EXPECT_EQ(test_case.expectation_dpr, + preferences.ShouldSend(mojom::WebClientHintsType::kDpr)); + EXPECT_EQ( + test_case.expectation_viewport_width, + preferences.ShouldSend(mojom::WebClientHintsType::kViewportWidth)); + } +} + +TEST_F(ClientHintsPreferencesTest, Insecure) { + for (const auto& use_secure_url : {false, true}) { + ClientHintsPreferences preferences; + const KURL kurl = use_secure_url + ? KURL(String::FromUTF8("https://www.google.com/")) + : KURL(String::FromUTF8("http://www.google.com/")); + preferences.UpdateFromAcceptClientHintsHeader("dpr", kurl, nullptr); + EXPECT_EQ(use_secure_url, + preferences.ShouldSend(mojom::WebClientHintsType::kDpr)); + } +} + +TEST_F(ClientHintsPreferencesTest, PersistentHints) { + struct TestCase { + bool enable_persistent_runtime_feature; + const char* accept_ch_header_value; + const char* accept_lifetime_header_value; + int64_t expect_persist_duration_seconds; + } test_cases[] = { + {true, "width, dpr, viewportWidth", "", 0}, + {true, "width, dpr, viewportWidth", "-1000", 0}, + {true, "width, dpr, viewportWidth", "1000s", 0}, + {true, "width, dpr, viewportWidth", "1000.5", 0}, + {false, "width, dpr, viewportWidth", "1000", 0}, + {true, "width, dpr, rtt, downlink, ect", "1000", 1000}, + }; + + for (const auto& test : test_cases) { + WebRuntimeFeatures::EnableClientHintsPersistent( + test.enable_persistent_runtime_feature); + WebEnabledClientHints enabled_types; + TimeDelta persist_duration; + + const KURL kurl(String::FromUTF8("https://www.google.com/")); + + ResourceResponse response(kurl); + response.SetHTTPHeaderField(HTTPNames::Accept_CH, + test.accept_ch_header_value); + response.SetHTTPHeaderField(HTTPNames::Accept_CH_Lifetime, + test.accept_lifetime_header_value); + + ClientHintsPreferences::UpdatePersistentHintsFromHeaders( + response, nullptr, enabled_types, &persist_duration); + EXPECT_EQ(test.expect_persist_duration_seconds, + persist_duration.InSeconds()); + if (test.expect_persist_duration_seconds > 0) { + EXPECT_FALSE( + enabled_types.IsEnabled(mojom::WebClientHintsType::kDeviceMemory)); + EXPECT_TRUE(enabled_types.IsEnabled(mojom::WebClientHintsType::kDpr)); + EXPECT_TRUE( + enabled_types.IsEnabled(mojom::WebClientHintsType::kResourceWidth)); + EXPECT_FALSE( + enabled_types.IsEnabled(mojom::WebClientHintsType::kViewportWidth)); + EXPECT_TRUE(enabled_types.IsEnabled(mojom::WebClientHintsType::kRtt)); + EXPECT_TRUE( + enabled_types.IsEnabled(mojom::WebClientHintsType::kDownlink)); + EXPECT_TRUE(enabled_types.IsEnabled(mojom::WebClientHintsType::kEct)); + } else { + EXPECT_FALSE( + enabled_types.IsEnabled(mojom::WebClientHintsType::kDeviceMemory)); + EXPECT_FALSE(enabled_types.IsEnabled(mojom::WebClientHintsType::kDpr)); + EXPECT_FALSE( + enabled_types.IsEnabled(mojom::WebClientHintsType::kResourceWidth)); + EXPECT_FALSE( + enabled_types.IsEnabled(mojom::WebClientHintsType::kViewportWidth)); + EXPECT_FALSE(enabled_types.IsEnabled(mojom::WebClientHintsType::kRtt)); + EXPECT_FALSE( + enabled_types.IsEnabled(mojom::WebClientHintsType::kDownlink)); + EXPECT_FALSE(enabled_types.IsEnabled(mojom::WebClientHintsType::kEct)); + } + } +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/platform/loader/fetch/fetch_context.cc b/chromium/third_party/blink/renderer/platform/loader/fetch/fetch_context.cc new file mode 100644 index 00000000000..ab5e1ae5900 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/loader/fetch/fetch_context.cc @@ -0,0 +1,126 @@ +/* + * 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/platform/loader/fetch/fetch_context.h" + +#include "third_party/blink/renderer/platform/PlatformProbeSink.h" +#include "third_party/blink/renderer/platform/probe/PlatformTraceEventsAgent.h" + +namespace blink { + +FetchContext& FetchContext::NullInstance() { + return *(new FetchContext); +} + +FetchContext::FetchContext() : platform_probe_sink_(new PlatformProbeSink) { + platform_probe_sink_->addPlatformTraceEventsAgent( + new PlatformTraceEventsAgent); +} + +void FetchContext::Trace(blink::Visitor* visitor) { + visitor->Trace(platform_probe_sink_); +} + +void FetchContext::DispatchDidChangeResourcePriority(unsigned long, + ResourceLoadPriority, + int) {} + +void FetchContext::AddAdditionalRequestHeaders(ResourceRequest&, + FetchResourceType) {} + +mojom::FetchCacheMode FetchContext::ResourceRequestCachePolicy( + const ResourceRequest&, + Resource::Type, + FetchParameters::DeferOption defer) const { + return mojom::FetchCacheMode::kDefault; +} + +void FetchContext::PrepareRequest(ResourceRequest&, RedirectType) {} + +void FetchContext::DispatchWillSendRequest(unsigned long, + ResourceRequest&, + const ResourceResponse&, + Resource::Type, + const FetchInitiatorInfo&) {} + +void FetchContext::DispatchDidLoadResourceFromMemoryCache( + unsigned long, + const ResourceRequest&, + const ResourceResponse&) {} + +void FetchContext::DispatchDidReceiveResponse( + unsigned long, + const ResourceResponse&, + network::mojom::RequestContextFrameType FrameType, + WebURLRequest::RequestContext, + Resource*, + ResourceResponseType) {} + +void FetchContext::DispatchDidReceiveData(unsigned long, const char*, int) {} + +void FetchContext::DispatchDidReceiveEncodedData(unsigned long, int) {} + +void FetchContext::DispatchDidDownloadData(unsigned long, int, int) {} + +void FetchContext::DispatchDidDownloadToBlob(unsigned long identifier, + BlobDataHandle*) {} + +void FetchContext::DispatchDidFinishLoading(unsigned long, + double, + int64_t, + int64_t, + bool) {} + +void FetchContext::DispatchDidFail(const KURL&, + unsigned long, + const ResourceError&, + int64_t, + bool) {} + +void FetchContext::RecordLoadingActivity( + const ResourceRequest&, + Resource::Type, + const AtomicString& fetch_initiator_name) {} + +void FetchContext::DidLoadResource(Resource*) {} + +void FetchContext::AddResourceTiming(const ResourceTimingInfo&) {} + +void FetchContext::AddWarningConsoleMessage(const String&, LogSource) const {} + +void FetchContext::AddErrorConsoleMessage(const String&, LogSource) const {} + +void FetchContext::PopulateResourceRequest( + Resource::Type, + const ClientHintsPreferences&, + const FetchParameters::ResourceWidth&, + ResourceRequest&) {} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/platform/loader/fetch/fetch_context.h b/chromium/third_party/blink/renderer/platform/loader/fetch/fetch_context.h new file mode 100644 index 00000000000..600b89d2900 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/loader/fetch/fetch_context.h @@ -0,0 +1,292 @@ +/* + * 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_PLATFORM_LOADER_FETCH_FETCH_CONTEXT_H_ +#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_LOADER_FETCH_FETCH_CONTEXT_H_ + +#include "services/network/public/mojom/request_context_frame_type.mojom-shared.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_application_cache_host.h" +#include "third_party/blink/public/platform/web_url_loader.h" +#include "third_party/blink/public/platform/web_url_request.h" +#include "third_party/blink/renderer/platform/heap/handle.h" +#include "third_party/blink/renderer/platform/loader/fetch/fetch_initiator_info.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_load_priority.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource_load_scheduler.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/platform_export.h" +#include "third_party/blink/renderer/platform/scheduler/public/frame_scheduler.h" +#include "third_party/blink/renderer/platform/weborigin/security_violation_reporting_policy.h" +#include "third_party/blink/renderer/platform/wtf/forward.h" +#include "third_party/blink/renderer/platform/wtf/noncopyable.h" + +namespace blink { + +class ClientHintsPreferences; +class KURL; +class MHTMLArchive; +class PlatformProbeSink; +class ResourceError; +class ResourceResponse; +class ResourceTimingInfo; + +enum FetchResourceType { kFetchMainResource, kFetchSubresource }; + +// The FetchContext is an interface for performing context specific processing +// in response to events in the ResourceFetcher. The ResourceFetcher or its job +// class, ResourceLoader, may call the methods on a FetchContext. +// +// Any processing that depends on components outside platform/loader/fetch/ +// should be implemented on a subclass of this interface, and then exposed to +// the ResourceFetcher via this interface. +class PLATFORM_EXPORT FetchContext + : public GarbageCollectedFinalized<FetchContext> { + WTF_MAKE_NONCOPYABLE(FetchContext); + + public: + // This enum corresponds to blink::MessageSource. We have this not to + // introduce any dependency to core/. + // + // Currently only kJSMessageSource is used, but not to impress readers that + // AddConsoleMessage() call from FetchContext() should always use it, which is + // not true, we ask users of the Add.*ConsoleMessage() methods to explicitly + // specify the MessageSource to use. + // + // Extend this when needed. + enum LogSource { kJSSource }; + + static FetchContext& NullInstance(); + + virtual ~FetchContext() = default; + + virtual void Trace(blink::Visitor*); + + virtual bool IsFrameFetchContext() { return false; } + + virtual void AddAdditionalRequestHeaders(ResourceRequest&, FetchResourceType); + + // Called when the ResourceFetcher observes a data: URI load that contains an + // octothorpe ('#') character. This is a temporary method to support an Intent + // to Deprecate for spec incompliant handling of '#' characters in data URIs. + // + // TODO(crbug.com/123004): Remove once we have enough data for the I2D. + virtual void RecordDataUriWithOctothorpe() {} + + // Returns the cache policy for the resource. ResourceRequest is not passed as + // a const reference as a header needs to be added for doc.write blocking + // intervention. + virtual mojom::FetchCacheMode ResourceRequestCachePolicy( + const ResourceRequest&, + Resource::Type, + FetchParameters::DeferOption) const; + + virtual void DispatchDidChangeResourcePriority(unsigned long identifier, + ResourceLoadPriority, + int intra_priority_value); + + // This internally dispatches WebFrameClient::willSendRequest and hooks + // request interceptors like ServiceWorker and ApplicationCache. + // This may modify the request. + enum class RedirectType { kForRedirect, kNotForRedirect }; + virtual void PrepareRequest(ResourceRequest&, RedirectType); + + // The last callback before a request is actually sent to the browser process. + // TODO(https://crbug.com/632580): make this take const ResourceRequest&. + virtual void DispatchWillSendRequest( + unsigned long identifier, + ResourceRequest&, + const ResourceResponse& redirect_response, + Resource::Type, + const FetchInitiatorInfo& = FetchInitiatorInfo()); + virtual void DispatchDidLoadResourceFromMemoryCache(unsigned long identifier, + const ResourceRequest&, + const ResourceResponse&); + enum class ResourceResponseType { kNotFromMemoryCache, kFromMemoryCache }; + virtual void DispatchDidReceiveResponse( + unsigned long identifier, + const ResourceResponse&, + network::mojom::RequestContextFrameType, + WebURLRequest::RequestContext, + Resource*, + ResourceResponseType); + virtual void DispatchDidReceiveData(unsigned long identifier, + const char* data, + int data_length); + virtual void DispatchDidReceiveEncodedData(unsigned long identifier, + int encoded_data_length); + virtual void DispatchDidDownloadData(unsigned long identifier, + int data_length, + int encoded_data_length); + virtual void DispatchDidDownloadToBlob(unsigned long identifier, + BlobDataHandle*); + virtual void DispatchDidFinishLoading(unsigned long identifier, + double finish_time, + int64_t encoded_data_length, + int64_t decoded_body_length, + bool blocked_cross_site_document); + virtual void DispatchDidFail(const KURL&, + unsigned long identifier, + const ResourceError&, + int64_t encoded_data_length, + bool is_internal_request); + + virtual bool ShouldLoadNewResource(Resource::Type) const { return false; } + + // Called when a resource load is first requested, which may not be when the + // load actually begins. + virtual void RecordLoadingActivity(const ResourceRequest&, + Resource::Type, + const AtomicString& fetch_initiator_name); + + virtual void DidLoadResource(Resource*); + + virtual void AddResourceTiming(const ResourceTimingInfo&); + virtual bool AllowImage(bool, const KURL&) const { return false; } + virtual ResourceRequestBlockedReason CanRequest( + Resource::Type, + const ResourceRequest&, + const KURL&, + const ResourceLoaderOptions&, + SecurityViolationReportingPolicy, + FetchParameters::OriginRestriction, + ResourceRequest::RedirectStatus) const { + return ResourceRequestBlockedReason::kOther; + } + virtual ResourceRequestBlockedReason CheckCSPForRequest( + WebURLRequest::RequestContext, + const KURL&, + const ResourceLoaderOptions&, + SecurityViolationReportingPolicy, + ResourceRequest::RedirectStatus) const { + return ResourceRequestBlockedReason::kOther; + } + virtual ResourceRequestBlockedReason CheckResponseNosniff( + WebURLRequest::RequestContext, + const ResourceResponse&) const { + return ResourceRequestBlockedReason::kOther; + } + + virtual bool IsControlledByServiceWorker() const { return false; } + virtual int64_t ServiceWorkerID() const { return -1; } + virtual int ApplicationCacheHostID() const { + return WebApplicationCacheHost::kAppCacheNoHostId; + } + + virtual bool IsMainFrame() const { return true; } + virtual bool DefersLoading() const { return false; } + virtual bool IsLoadComplete() const { return false; } + virtual bool UpdateTimingInfoForIFrameNavigation(ResourceTimingInfo*) { + return false; + } + + virtual void AddWarningConsoleMessage(const String&, LogSource) const; + virtual void AddErrorConsoleMessage(const String&, LogSource) const; + + virtual const SecurityOrigin* GetSecurityOrigin() const { return nullptr; } + + // Populates the ResourceRequest using the given values and information + // stored in the FetchContext implementation. Used by ResourceFetcher to + // prepare a ResourceRequest instance at the start of resource loading. + virtual void PopulateResourceRequest(Resource::Type, + const ClientHintsPreferences&, + const FetchParameters::ResourceWidth&, + ResourceRequest&); + + virtual MHTMLArchive* Archive() const { return nullptr; } + + PlatformProbeSink* GetPlatformProbeSink() const { + return platform_probe_sink_; + } + + virtual std::unique_ptr<WebURLLoader> CreateURLLoader( + const ResourceRequest&, + scoped_refptr<base::SingleThreadTaskRunner>, + const ResourceLoaderOptions&) { + NOTREACHED(); + return nullptr; + } + + // Returns the initial throttling policy used by the associated + // ResourceLoadScheduler. + virtual ResourceLoadScheduler::ThrottlingPolicy InitialLoadThrottlingPolicy() + const { + return ResourceLoadScheduler::ThrottlingPolicy::kNormal; + } + + virtual bool IsDetached() const { return false; } + + // Obtains FrameScheduler instance that is used in the attached frame. + // May return nullptr if a frame is not attached or detached. + virtual FrameScheduler* GetFrameScheduler() const { return nullptr; } + + // Returns a task runner intended for loading tasks. Should work even in a + // worker context, where FrameScheduler doesn't exist, but the returned + // base::SingleThreadTaskRunner will not work after the context detaches + // (after Detach() is called, this will return a generic timer suitable for + // post-detach actions like keepalive requests. + virtual scoped_refptr<base::SingleThreadTaskRunner> GetLoadingTaskRunner() { + return Platform::Current()->CurrentThread()->GetTaskRunner(); + } + + // Called when the underlying context is detached. Note that some + // FetchContexts continue working after detached (e.g., for fetch() operations + // with "keepalive" specified). + // Returns a "detached" fetch context which can be null. + virtual FetchContext* Detach() { return nullptr; } + + // Returns the updated priority of the resource based on the experiments that + // may be currently enabled. + virtual ResourceLoadPriority ModifyPriorityForExperiments( + ResourceLoadPriority priority) const { + return priority; + } + + // Returns if the |resource_url| is identified as ad. + virtual bool IsAdResource( + const KURL& resource_url, + Resource::Type type, + WebURLRequest::RequestContext request_context) const { + return false; + } + + protected: + FetchContext(); + + private: + Member<PlatformProbeSink> platform_probe_sink_; +}; + +} // namespace blink + +#endif diff --git a/chromium/third_party/blink/renderer/platform/loader/fetch/fetch_initiator_info.h b/chromium/third_party/blink/renderer/platform/loader/fetch/fetch_initiator_info.h new file mode 100644 index 00000000000..8d471afcb18 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/loader/fetch/fetch_initiator_info.h @@ -0,0 +1,82 @@ +/* + * 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: + * 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 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_PLATFORM_LOADER_FETCH_FETCH_INITIATOR_INFO_H_ +#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_LOADER_FETCH_FETCH_INITIATOR_INFO_H_ + +#include "third_party/blink/renderer/platform/wtf/allocator.h" +#include "third_party/blink/renderer/platform/wtf/text/atomic_string.h" +#include "third_party/blink/renderer/platform/wtf/text/text_position.h" + +namespace blink { + +struct FetchInitiatorInfo { + DISALLOW_NEW(); + FetchInitiatorInfo() + : name(), + position(TextPosition::BelowRangePosition()), + start_time(0.0), + is_link_preload(false) {} + + // ATTENTION: When adding members, update CrossThreadFetchInitiatorInfoData, + // too. + AtomicString name; + TextPosition position; + double start_time; + bool is_link_preload; + String imported_module_referrer; +}; + +// Encode AtomicString as String to cross threads. +struct CrossThreadFetchInitiatorInfoData { + DISALLOW_NEW(); + explicit CrossThreadFetchInitiatorInfoData(const FetchInitiatorInfo& info) + : name(info.name.GetString().IsolatedCopy()), + position(info.position), + start_time(info.start_time), + is_link_preload(info.is_link_preload), + imported_module_referrer(info.imported_module_referrer.IsolatedCopy()) { + } + + operator FetchInitiatorInfo() const { + FetchInitiatorInfo info; + info.name = AtomicString(name); + info.position = position; + info.start_time = start_time; + info.is_link_preload = is_link_preload; + info.imported_module_referrer = imported_module_referrer; + return info; + } + + String name; + TextPosition position; + double start_time; + bool is_link_preload; + String imported_module_referrer; +}; + +} // namespace blink + +#endif diff --git a/chromium/third_party/blink/renderer/platform/loader/fetch/fetch_initiator_type_names.json5 b/chromium/third_party/blink/renderer/platform/loader/fetch/fetch_initiator_type_names.json5 new file mode 100644 index 00000000000..d2d2aac3a55 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/loader/fetch/fetch_initiator_type_names.json5 @@ -0,0 +1,22 @@ +{ + metadata: { + namespace: "FetchInitiatorType", + export: "PLATFORM_EXPORT", + }, + + data: [ + "beacon", + "css", + "document", + "icon", + "internal", + "link", + "ping", + "processinginstruction", + "texttrack", + "uacss", + "violationreport", + "xml", + "xmlhttprequest", + ], +} diff --git a/chromium/third_party/blink/renderer/platform/loader/fetch/fetch_parameters.cc b/chromium/third_party/blink/renderer/platform/loader/fetch/fetch_parameters.cc new file mode 100644 index 00000000000..88a956d43af --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/loader/fetch/fetch_parameters.cc @@ -0,0 +1,176 @@ +/* + * 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. + * + * THIS SOFTWARE IS PROVIDED BY GOOGLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * 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/platform/loader/fetch/fetch_parameters.h" + +#include <memory> + +#include "third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h" +#include "third_party/blink/renderer/platform/weborigin/kurl.h" +#include "third_party/blink/renderer/platform/weborigin/security_origin.h" + +namespace blink { + +FetchParameters::FetchParameters(const ResourceRequest& resource_request) + : resource_request_(resource_request), + decoder_options_(TextResourceDecoderOptions::kPlainTextContent), + speculative_preload_type_(SpeculativePreloadType::kNotSpeculative), + preload_discovery_time_(0.0), + defer_(kNoDefer), + origin_restriction_(kUseDefaultOriginRestrictionForType), + placeholder_image_request_type_(kDisallowPlaceholder) {} + +FetchParameters::FetchParameters( + std::unique_ptr<CrossThreadFetchParametersData> data) + : resource_request_(data->resource_request.get()), + decoder_options_(data->decoder_options), + options_(data->options), + speculative_preload_type_(data->speculative_preload_type), + preload_discovery_time_(data->preload_discovery_time), + defer_(data->defer), + origin_restriction_(data->origin_restriction), + resource_width_(data->resource_width), + client_hint_preferences_(data->client_hint_preferences), + placeholder_image_request_type_(data->placeholder_image_request_type) {} + +FetchParameters::FetchParameters(const ResourceRequest& resource_request, + const ResourceLoaderOptions& options) + : resource_request_(resource_request), + decoder_options_(TextResourceDecoderOptions::kPlainTextContent), + options_(options), + speculative_preload_type_(SpeculativePreloadType::kNotSpeculative), + preload_discovery_time_(0.0), + defer_(kNoDefer), + origin_restriction_(kUseDefaultOriginRestrictionForType), + placeholder_image_request_type_(kDisallowPlaceholder) {} + +FetchParameters::~FetchParameters() = default; + +void FetchParameters::SetCrossOriginAccessControl( + const SecurityOrigin* origin, + CrossOriginAttributeValue cross_origin) { + switch (cross_origin) { + case kCrossOriginAttributeNotSet: + NOTREACHED(); + break; + case kCrossOriginAttributeAnonymous: + SetCrossOriginAccessControl( + origin, network::mojom::FetchCredentialsMode::kSameOrigin); + break; + case kCrossOriginAttributeUseCredentials: + SetCrossOriginAccessControl( + origin, network::mojom::FetchCredentialsMode::kInclude); + break; + } +} + +void FetchParameters::SetCrossOriginAccessControl( + const SecurityOrigin* origin, + network::mojom::FetchCredentialsMode credentials_mode) { + // Currently FetchParametersMode is only used when the request goes to + // Service Worker. + resource_request_.SetFetchRequestMode( + network::mojom::FetchRequestMode::kCORS); + resource_request_.SetFetchCredentialsMode(credentials_mode); + + options_.security_origin = origin; + + // TODO: Credentials should be removed only when the request is cross origin. + resource_request_.RemoveUserAndPassFromURL(); + + if (origin) + resource_request_.SetHTTPOrigin(origin); +} + +void FetchParameters::SetResourceWidth(ResourceWidth resource_width) { + if (resource_width.is_set) { + resource_width_.width = resource_width.width; + resource_width_.is_set = true; + } +} + +void FetchParameters::SetSpeculativePreloadType( + SpeculativePreloadType speculative_preload_type, + double discovery_time) { + speculative_preload_type_ = speculative_preload_type; + preload_discovery_time_ = discovery_time; +} + +void FetchParameters::MakeSynchronous() { + // Synchronous requests should always be max priority, lest they hang the + // renderer. + resource_request_.SetPriority(ResourceLoadPriority::kHighest); + if (resource_request_.TimeoutInterval() == INT_MAX) { + resource_request_.SetTimeoutInterval(10); + } + // Skip ServiceWorker for synchronous loads from the main thread to avoid + // deadlocks. + if (IsMainThread()) + resource_request_.SetSkipServiceWorker(true); + options_.synchronous_policy = kRequestSynchronously; +} + +void FetchParameters::SetAllowImagePlaceholder() { + DCHECK_EQ(kDisallowPlaceholder, placeholder_image_request_type_); + if (!resource_request_.Url().ProtocolIsInHTTPFamily() || + resource_request_.HttpMethod() != "GET" || + !resource_request_.HttpHeaderField("range").IsNull()) { + // Make sure that the request isn't marked as using Client Lo-Fi, since + // without loading an image placeholder, Client Lo-Fi isn't really in use. + resource_request_.SetPreviewsState(resource_request_.GetPreviewsState() & + ~(WebURLRequest::kClientLoFiOn)); + return; + } + + placeholder_image_request_type_ = kAllowPlaceholder; + + // Fetch the first few bytes of the image. This number is tuned to both (a) + // likely capture the entire image for small images and (b) likely contain + // the dimensions for larger images. + // TODO(sclittle): Calculate the optimal value for this number. + resource_request_.SetHTTPHeaderField("range", "bytes=0-2047"); + + // TODO(sclittle): Indicate somehow (e.g. through a new request bit) to the + // embedder that it should return the full resource if the entire resource is + // fresh in the cache. +} + +std::unique_ptr<CrossThreadFetchParametersData> FetchParameters::CopyData() + const { + auto data = std::make_unique<CrossThreadFetchParametersData>(); + data->resource_request = resource_request_.CopyData(); + data->decoder_options = decoder_options_; + data->options = CrossThreadResourceLoaderOptionsData(options_); + data->speculative_preload_type = speculative_preload_type_; + data->preload_discovery_time = preload_discovery_time_; + data->defer = defer_; + data->origin_restriction = origin_restriction_; + data->resource_width = resource_width_; + data->client_hint_preferences = client_hint_preferences_; + data->placeholder_image_request_type = placeholder_image_request_type_; + return data; +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/platform/loader/fetch/fetch_parameters.h b/chromium/third_party/blink/renderer/platform/loader/fetch/fetch_parameters.h new file mode 100644 index 00000000000..4ed9aba1b11 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/loader/fetch/fetch_parameters.h @@ -0,0 +1,251 @@ +/* + * 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. + * + * THIS SOFTWARE IS PROVIDED BY GOOGLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * 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_PLATFORM_LOADER_FETCH_FETCH_PARAMETERS_H_ +#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_LOADER_FETCH_FETCH_PARAMETERS_H_ + +#include "third_party/blink/public/platform/web_url_request.h" +#include "third_party/blink/renderer/platform/cross_origin_attribute_value.h" +#include "third_party/blink/renderer/platform/loader/fetch/client_hints_preferences.h" +#include "third_party/blink/renderer/platform/loader/fetch/integrity_metadata.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/text_resource_decoder_options.h" +#include "third_party/blink/renderer/platform/platform_export.h" +#include "third_party/blink/renderer/platform/wtf/allocator.h" +#include "third_party/blink/renderer/platform/wtf/noncopyable.h" +#include "third_party/blink/renderer/platform/wtf/text/text_encoding.h" + +namespace blink { + +class SecurityOrigin; +struct CrossThreadFetchParametersData; + +// A FetchParameters is a "parameter object" for +// ResourceFetcher::requestResource to avoid the method having too many +// arguments. +// +// There are cases where we need to copy a FetchParameters across threads, and +// CrossThreadFetchParametersData is a struct for the purpose. When you add a +// member variable to this class, do not forget to add the corresponding +// one in CrossThreadFetchParametersData and write copying logic. +class PLATFORM_EXPORT FetchParameters { + DISALLOW_NEW(); + + public: + enum DeferOption { kNoDefer, kLazyLoad, kIdleLoad }; + enum class SpeculativePreloadType { + kNotSpeculative, + kInDocument, // The request was discovered in the main document + kInserted // The request was discovered in a document.write() + }; + enum OriginRestriction { + kUseDefaultOriginRestrictionForType, + kRestrictToSameOrigin, + kNoOriginRestriction + }; + enum PlaceholderImageRequestType { + kDisallowPlaceholder = 0, // The requested image must not be a placeholder. + kAllowPlaceholder, // The image is allowed to be a placeholder. + }; + struct ResourceWidth { + DISALLOW_NEW(); + float width; + bool is_set; + + ResourceWidth() : width(0), is_set(false) {} + }; + + explicit FetchParameters(const ResourceRequest&); + explicit FetchParameters(std::unique_ptr<CrossThreadFetchParametersData>); + FetchParameters(const ResourceRequest&, const ResourceLoaderOptions&); + ~FetchParameters(); + + ResourceRequest& MutableResourceRequest() { return resource_request_; } + const ResourceRequest& GetResourceRequest() const { + return resource_request_; + } + const KURL& Url() const { return resource_request_.Url(); } + + void SetRequestContext(WebURLRequest::RequestContext context) { + resource_request_.SetRequestContext(context); + } + + const TextResourceDecoderOptions& DecoderOptions() const { + return decoder_options_; + } + void SetDecoderOptions(const TextResourceDecoderOptions& decoder_options) { + decoder_options_ = decoder_options; + } + void OverrideContentType( + TextResourceDecoderOptions::ContentType content_type) { + decoder_options_.OverrideContentType(content_type); + } + void SetCharset(const WTF::TextEncoding& charset) { + SetDecoderOptions(TextResourceDecoderOptions( + TextResourceDecoderOptions::kPlainTextContent, charset)); + } + + ResourceLoaderOptions& MutableOptions() { return options_; } + const ResourceLoaderOptions& Options() const { return options_; } + + DeferOption Defer() const { return defer_; } + void SetDefer(DeferOption defer) { defer_ = defer; } + + ResourceWidth GetResourceWidth() const { return resource_width_; } + void SetResourceWidth(ResourceWidth); + + ClientHintsPreferences& GetClientHintsPreferences() { + return client_hint_preferences_; + } + + bool IsSpeculativePreload() const { + return speculative_preload_type_ != SpeculativePreloadType::kNotSpeculative; + } + SpeculativePreloadType GetSpeculativePreloadType() const { + return speculative_preload_type_; + } + void SetSpeculativePreloadType(SpeculativePreloadType, + double discovery_time = 0); + + double PreloadDiscoveryTime() const { return preload_discovery_time_; } + + bool IsLinkPreload() const { return options_.initiator_info.is_link_preload; } + void SetLinkPreload(bool is_link_preload) { + options_.initiator_info.is_link_preload = is_link_preload; + } + + void SetContentSecurityCheck( + ContentSecurityPolicyDisposition content_security_policy_option) { + options_.content_security_policy_option = content_security_policy_option; + } + // Configures the request to use the "cors" mode and the credentials mode + // specified by the crossOrigin attribute. + void SetCrossOriginAccessControl(const SecurityOrigin*, + CrossOriginAttributeValue); + // Configures the request to use the "cors" mode and the specified + // credentials mode. + void SetCrossOriginAccessControl(const SecurityOrigin*, + network::mojom::FetchCredentialsMode); + OriginRestriction GetOriginRestriction() const { return origin_restriction_; } + void SetOriginRestriction(OriginRestriction restriction) { + origin_restriction_ = restriction; + } + const IntegrityMetadataSet IntegrityMetadata() const { + return options_.integrity_metadata; + } + void SetIntegrityMetadata(const IntegrityMetadataSet& metadata) { + options_.integrity_metadata = metadata; + } + + String ContentSecurityPolicyNonce() const { + return options_.content_security_policy_nonce; + } + void SetContentSecurityPolicyNonce(const String& nonce) { + options_.content_security_policy_nonce = nonce; + } + + void SetParserDisposition(ParserDisposition parser_disposition) { + options_.parser_disposition = parser_disposition; + } + + void SetCacheAwareLoadingEnabled( + CacheAwareLoadingEnabled cache_aware_loading_enabled) { + options_.cache_aware_loading_enabled = cache_aware_loading_enabled; + } + + void MakeSynchronous(); + + PlaceholderImageRequestType GetPlaceholderImageRequestType() const { + return placeholder_image_request_type_; + } + + // Configures the request to load an image placeholder if the request is + // eligible (e.g. the url's protocol is HTTP, etc.). If this request is + // non-eligible, this method doesn't modify the ResourceRequest. Calling this + // method sets m_placeholderImageRequestType to the appropriate value. + void SetAllowImagePlaceholder(); + + // Gets a copy of the data suitable for passing to another thread. + std::unique_ptr<CrossThreadFetchParametersData> CopyData() const; + + private: + ResourceRequest resource_request_; + // |decoder_options_|'s ContentType is set to |kPlainTextContent| in + // FetchParameters but is later overridden by ResourceFactory::ContentType() + // in ResourceFetcher::PrepareRequest() before actual use. + TextResourceDecoderOptions decoder_options_; + ResourceLoaderOptions options_; + SpeculativePreloadType speculative_preload_type_; + double preload_discovery_time_; + DeferOption defer_; + OriginRestriction origin_restriction_; + ResourceWidth resource_width_; + ClientHintsPreferences client_hint_preferences_; + PlaceholderImageRequestType placeholder_image_request_type_; +}; + +// This class is needed to copy a FetchParameters across threads, because it +// has some members which cannot be transferred across threads (AtomicString +// for example). +// There are some rules / restrictions: +// - This struct cannot contain an object that cannot be transferred across +// threads (e.g., AtomicString) +// - Non-simple members need explicit copying (e.g., String::IsolatedCopy, +// KURL::Copy) rather than the copy constructor or the assignment operator. +struct CrossThreadFetchParametersData { + WTF_MAKE_NONCOPYABLE(CrossThreadFetchParametersData); + USING_FAST_MALLOC(CrossThreadFetchParametersData); + + public: + CrossThreadFetchParametersData() + : decoder_options(TextResourceDecoderOptions::kPlainTextContent), + options(ResourceLoaderOptions()) {} + + std::unique_ptr<CrossThreadResourceRequestData> resource_request; + TextResourceDecoderOptions decoder_options; + CrossThreadResourceLoaderOptionsData options; + FetchParameters::SpeculativePreloadType speculative_preload_type; + double preload_discovery_time; + FetchParameters::DeferOption defer; + FetchParameters::OriginRestriction origin_restriction; + FetchParameters::ResourceWidth resource_width; + ClientHintsPreferences client_hint_preferences; + FetchParameters::PlaceholderImageRequestType placeholder_image_request_type; +}; + +template <> +struct CrossThreadCopier<FetchParameters> { + STATIC_ONLY(CrossThreadCopier); + using Type = + WTF::PassedWrapper<std::unique_ptr<CrossThreadFetchParametersData>>; + static Type Copy(const FetchParameters& fetch_params) { + return WTF::Passed(fetch_params.CopyData()); + } +}; + +} // namespace blink + +#endif diff --git a/chromium/third_party/blink/renderer/platform/loader/fetch/fetch_utils.cc b/chromium/third_party/blink/renderer/platform/loader/fetch/fetch_utils.cc new file mode 100644 index 00000000000..643ed618ac3 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/loader/fetch/fetch_utils.cc @@ -0,0 +1,97 @@ +// 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/platform/loader/fetch/fetch_utils.h" + +#include "services/network/public/cpp/cors/cors.h" +#include "third_party/blink/renderer/platform/loader/cors/cors.h" +#include "third_party/blink/renderer/platform/network/http_header_map.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/wtf/hash_set.h" +#include "third_party/blink/renderer/platform/wtf/text/atomic_string.h" +#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h" +#include "third_party/blink/renderer/platform/wtf/threading.h" + +namespace blink { + +namespace { + +bool IsHTTPWhitespace(UChar chr) { + return chr == ' ' || chr == '\n' || chr == '\t' || chr == '\r'; +} + +} // namespace + +bool FetchUtils::IsForbiddenMethod(const String& method) { + // http://fetch.spec.whatwg.org/#forbidden-method + // "A forbidden method is a method that is a byte case-insensitive match" + // for one of `CONNECT`, `TRACE`, and `TRACK`." + return EqualIgnoringASCIICase(method, "TRACE") || + EqualIgnoringASCIICase(method, "TRACK") || + EqualIgnoringASCIICase(method, "CONNECT"); +} + +bool FetchUtils::IsForbiddenHeaderName(const String& name) { + const CString utf8_name = name.Utf8(); + return network::cors::IsForbiddenHeader( + std::string(utf8_name.data(), utf8_name.length())); +} + +bool FetchUtils::IsForbiddenResponseHeaderName(const String& name) { + // http://fetch.spec.whatwg.org/#forbidden-response-header-name + // "A forbidden response header name is a header name that is one of: + // `Set-Cookie`, `Set-Cookie2`" + + return EqualIgnoringASCIICase(name, "set-cookie") || + EqualIgnoringASCIICase(name, "set-cookie2"); +} + +AtomicString FetchUtils::NormalizeMethod(const AtomicString& method) { + // https://fetch.spec.whatwg.org/#concept-method-normalize + + // We place GET and POST first because they are more commonly used than + // others. + const char* const kMethods[] = { + "GET", "POST", "DELETE", "HEAD", "OPTIONS", "PUT", + }; + + for (auto* const known : kMethods) { + if (EqualIgnoringASCIICase(method, known)) { + // Don't bother allocating a new string if it's already all + // uppercase. + return method == known ? method : known; + } + } + return method; +} + +String FetchUtils::NormalizeHeaderValue(const String& value) { + // https://fetch.spec.whatwg.org/#concept-header-value-normalize + // Strip leading and trailing whitespace from header value. + // HTTP whitespace bytes are 0x09, 0x0A, 0x0D, and 0x20. + + return value.StripWhiteSpace(IsHTTPWhitespace); +} + +bool FetchUtils::ContainsOnlyCORSSafelistedHeaders( + const HTTPHeaderMap& header_map) { + for (const auto& header : header_map) { + if (!CORS::IsCORSSafelistedHeader(header.key, header.value)) + return false; + } + return true; +} + +bool FetchUtils::ContainsOnlyCORSSafelistedOrForbiddenHeaders( + const HTTPHeaderMap& header_map) { + for (const auto& header : header_map) { + if (!CORS::IsCORSSafelistedHeader(header.key, header.value) && + !IsForbiddenHeaderName(header.key)) + return false; + } + return true; +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/platform/loader/fetch/fetch_utils.h b/chromium/third_party/blink/renderer/platform/loader/fetch/fetch_utils.h new file mode 100644 index 00000000000..991ffcce318 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/loader/fetch/fetch_utils.h @@ -0,0 +1,38 @@ +// 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_PLATFORM_LOADER_FETCH_FETCH_UTILS_H_ +#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_LOADER_FETCH_FETCH_UTILS_H_ + +#include "third_party/blink/public/platform/web_url_request.h" +#include "third_party/blink/renderer/platform/platform_export.h" +#include "third_party/blink/renderer/platform/wtf/allocator.h" +#include "third_party/blink/renderer/platform/wtf/forward.h" + +namespace blink { + +class HTTPHeaderMap; + +class PLATFORM_EXPORT FetchUtils { + STATIC_ONLY(FetchUtils); + + public: + static bool IsForbiddenMethod(const String& method); + static bool IsForbiddenHeaderName(const String& name); + static bool IsForbiddenResponseHeaderName(const String& name); + static AtomicString NormalizeMethod(const AtomicString& method); + static String NormalizeHeaderValue(const String& value); + static bool ContainsOnlyCORSSafelistedHeaders(const HTTPHeaderMap&); + static bool ContainsOnlyCORSSafelistedOrForbiddenHeaders( + const HTTPHeaderMap&); + + // https://fetch.spec.whatwg.org/#ok-status aka a successful 2xx status + // code, https://tools.ietf.org/html/rfc7231#section-6.3 . We opt to use + // the Fetch term in naming the predicate. + static bool IsOkStatus(int status) { return status >= 200 && status < 300; } +}; + +} // namespace blink + +#endif diff --git a/chromium/third_party/blink/renderer/platform/loader/fetch/fetch_utils_test.cc b/chromium/third_party/blink/renderer/platform/loader/fetch/fetch_utils_test.cc new file mode 100644 index 00000000000..c7619245046 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/loader/fetch/fetch_utils_test.cc @@ -0,0 +1,40 @@ +// 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/platform/loader/fetch/fetch_utils.h" + +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h" + +namespace blink { + +namespace { + +TEST(FetchUtilsTest, NormalizeHeaderValue) { + EXPECT_EQ("t", FetchUtils::NormalizeHeaderValue(" t")); + EXPECT_EQ("t", FetchUtils::NormalizeHeaderValue("t ")); + EXPECT_EQ("t", FetchUtils::NormalizeHeaderValue(" t ")); + EXPECT_EQ("test", FetchUtils::NormalizeHeaderValue("test\r")); + EXPECT_EQ("test", FetchUtils::NormalizeHeaderValue("test\n")); + EXPECT_EQ("test", FetchUtils::NormalizeHeaderValue("test\r\n")); + EXPECT_EQ("test", FetchUtils::NormalizeHeaderValue("test\t")); + EXPECT_EQ("t t", FetchUtils::NormalizeHeaderValue("t t")); + EXPECT_EQ("t\tt", FetchUtils::NormalizeHeaderValue("t\tt")); + EXPECT_EQ("t\rt", FetchUtils::NormalizeHeaderValue("t\rt")); + EXPECT_EQ("t\nt", FetchUtils::NormalizeHeaderValue("t\nt")); + EXPECT_EQ("t\r\nt", FetchUtils::NormalizeHeaderValue("t\r\nt")); + EXPECT_EQ("test", FetchUtils::NormalizeHeaderValue("\rtest")); + EXPECT_EQ("test", FetchUtils::NormalizeHeaderValue("\ntest")); + EXPECT_EQ("test", FetchUtils::NormalizeHeaderValue("\r\ntest")); + EXPECT_EQ("test", FetchUtils::NormalizeHeaderValue("\ttest")); + EXPECT_EQ("", FetchUtils::NormalizeHeaderValue("")); + EXPECT_EQ("", FetchUtils::NormalizeHeaderValue(" ")); + EXPECT_EQ("", FetchUtils::NormalizeHeaderValue("\r\n\r\n\r\n")); + EXPECT_EQ("\xd0\xa1", FetchUtils::NormalizeHeaderValue("\xd0\xa1")); + EXPECT_EQ("test", FetchUtils::NormalizeHeaderValue("test")); +} + +} // namespace + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/platform/loader/fetch/integrity_metadata.cc b/chromium/third_party/blink/renderer/platform/loader/fetch/integrity_metadata.cc new file mode 100644 index 00000000000..864e5db6a10 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/loader/fetch/integrity_metadata.cc @@ -0,0 +1,33 @@ +// 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/platform/loader/fetch/integrity_metadata.h" + +namespace blink { + +IntegrityMetadata::IntegrityMetadata(WTF::String digest, + IntegrityAlgorithm algorithm) + : digest_(digest), algorithm_(algorithm) {} + +IntegrityMetadata::IntegrityMetadata(IntegrityMetadataPair pair) + : digest_(pair.first), algorithm_(pair.second) {} + +IntegrityMetadataPair IntegrityMetadata::ToPair() const { + return IntegrityMetadataPair(digest_, algorithm_); +} + +bool IntegrityMetadata::SetsEqual(const IntegrityMetadataSet& set1, + const IntegrityMetadataSet& set2) { + if (set1.size() != set2.size()) + return false; + + for (const IntegrityMetadataPair& metadata : set1) { + if (!set2.Contains(metadata)) + return false; + } + + return true; +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/platform/loader/fetch/integrity_metadata.h b/chromium/third_party/blink/renderer/platform/loader/fetch/integrity_metadata.h new file mode 100644 index 00000000000..5854670d344 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/loader/fetch/integrity_metadata.h @@ -0,0 +1,69 @@ +// 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_PLATFORM_LOADER_FETCH_INTEGRITY_METADATA_H_ +#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_LOADER_FETCH_INTEGRITY_METADATA_H_ + +#include "third_party/blink/renderer/platform/platform_export.h" +#include "third_party/blink/renderer/platform/wtf/hash_set.h" +#include "third_party/blink/renderer/platform/wtf/string_hasher.h" +#include "third_party/blink/renderer/platform/wtf/text/string_hash.h" +#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h" + +namespace blink { + +class IntegrityMetadata; +enum class IntegrityAlgorithm : uint8_t; + +using IntegrityMetadataPair = std::pair<String, IntegrityAlgorithm>; +using IntegrityMetadataSet = WTF::HashSet<IntegrityMetadataPair>; + +class PLATFORM_EXPORT IntegrityMetadata { + public: + IntegrityMetadata() = default; + IntegrityMetadata(String digest, IntegrityAlgorithm); + IntegrityMetadata(IntegrityMetadataPair); + + String Digest() const { return digest_; } + void SetDigest(const String& digest) { digest_ = digest; } + IntegrityAlgorithm Algorithm() const { return algorithm_; } + void SetAlgorithm(IntegrityAlgorithm algorithm) { algorithm_ = algorithm; } + + IntegrityMetadataPair ToPair() const; + + static bool SetsEqual(const IntegrityMetadataSet& set1, + const IntegrityMetadataSet& set2); + + private: + String digest_; + IntegrityAlgorithm algorithm_; +}; + +enum class ResourceIntegrityDisposition : uint8_t { + kNotChecked = 0, + kFailed, + kPassed +}; + +enum class IntegrityAlgorithm : uint8_t { kSha256, kSha384, kSha512, kEd25519 }; + +} // namespace blink + +namespace WTF { + +template <> +struct DefaultHash<blink::IntegrityAlgorithm> { + STATIC_ONLY(DefaultHash); + typedef IntHash<blink::IntegrityAlgorithm> Hash; +}; + +template <> +struct HashTraits<blink::IntegrityAlgorithm> + : UnsignedWithZeroKeyHashTraits<blink::IntegrityAlgorithm> { + STATIC_ONLY(HashTraits); +}; + +} // namespace WTF + +#endif diff --git a/chromium/third_party/blink/renderer/platform/loader/fetch/memory_cache.cc b/chromium/third_party/blink/renderer/platform/loader/fetch/memory_cache.cc new file mode 100644 index 00000000000..20be134ae12 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/loader/fetch/memory_cache.cc @@ -0,0 +1,476 @@ +/* + Copyright (C) 1998 Lars Knoll (knoll@mpi-hd.mpg.de) + Copyright (C) 2001 Dirk Mueller (mueller@kde.org) + Copyright (C) 2002 Waldo Bastian (bastian@kde.org) + Copyright (C) 2004, 2005, 2006, 2007, 2008 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/platform/loader/fetch/memory_cache.h" + +#include "third_party/blink/public/platform/platform.h" +#include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.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_origin_hash.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/math_extras.h" +#include "third_party/blink/renderer/platform/wtf/text/cstring.h" +#include "third_party/blink/renderer/platform/wtf/time.h" + +namespace blink { + +static Persistent<MemoryCache>* g_memory_cache; + +static const unsigned kCDefaultCacheCapacity = 8192 * 1024; +static const int kCMinDelayBeforeLiveDecodedPrune = 1; // Seconds. +static const double kCMaxPruneDeferralDelay = 0.5; // Seconds. + +// Percentage of capacity toward which we prune, to avoid immediately pruning +// again. +static const float kCTargetPrunePercentage = .95f; + +MemoryCache* GetMemoryCache() { + DCHECK(WTF::IsMainThread()); + if (!g_memory_cache) + g_memory_cache = new Persistent<MemoryCache>(MemoryCache::Create()); + return g_memory_cache->Get(); +} + +MemoryCache* ReplaceMemoryCacheForTesting(MemoryCache* cache) { + GetMemoryCache(); + MemoryCache* old_cache = g_memory_cache->Release(); + *g_memory_cache = cache; + MemoryCacheDumpProvider::Instance()->SetMemoryCache(cache); + return old_cache; +} + +void MemoryCacheEntry::Trace(blink::Visitor* visitor) { + visitor->template RegisterWeakMembers<MemoryCacheEntry, + &MemoryCacheEntry::ClearResourceWeak>( + this); +} + +void MemoryCacheEntry::ClearResourceWeak(Visitor* visitor) { + if (!resource_ || ThreadHeap::IsHeapObjectAlive(resource_)) + return; + GetMemoryCache()->Remove(resource_.Get()); + resource_.Clear(); +} + +inline MemoryCache::MemoryCache() + : in_prune_resources_(false), + prune_pending_(false), + max_prune_deferral_delay_(kCMaxPruneDeferralDelay), + prune_time_stamp_(0.0), + prune_frame_time_stamp_(0.0), + last_frame_paint_time_stamp_(0.0), + capacity_(kCDefaultCacheCapacity), + delay_before_live_decoded_prune_(kCMinDelayBeforeLiveDecodedPrune), + size_(0) { + MemoryCacheDumpProvider::Instance()->SetMemoryCache(this); + if (MemoryCoordinator::IsLowEndDevice()) + MemoryCoordinator::Instance().RegisterClient(this); +} + +MemoryCache* MemoryCache::Create() { + return new MemoryCache; +} + +MemoryCache::~MemoryCache() { + if (prune_pending_) + Platform::Current()->CurrentThread()->RemoveTaskObserver(this); +} + +void MemoryCache::Trace(blink::Visitor* visitor) { + visitor->Trace(resource_maps_); + MemoryCacheDumpClient::Trace(visitor); + MemoryCoordinatorClient::Trace(visitor); +} + +KURL MemoryCache::RemoveFragmentIdentifierIfNeeded(const KURL& original_url) { + if (!original_url.HasFragmentIdentifier()) + return original_url; + // Strip away fragment identifier from HTTP URLs. Data URLs must be + // unmodified. For file and custom URLs clients may expect resources to be + // unique even when they differ by the fragment identifier only. + if (!original_url.ProtocolIsInHTTPFamily()) + return original_url; + KURL url = original_url; + url.RemoveFragmentIdentifier(); + return url; +} + +String MemoryCache::DefaultCacheIdentifier() { + return g_empty_string; +} + +MemoryCache::ResourceMap* MemoryCache::EnsureResourceMap( + const String& cache_identifier) { + if (!resource_maps_.Contains(cache_identifier)) { + ResourceMapIndex::AddResult result = + resource_maps_.insert(cache_identifier, new ResourceMap); + CHECK(result.is_new_entry); + } + return resource_maps_.at(cache_identifier); +} + +void MemoryCache::Add(Resource* resource) { + DCHECK(resource); + ResourceMap* resources = EnsureResourceMap(resource->CacheIdentifier()); + AddInternal(resources, MemoryCacheEntry::Create(resource)); + RESOURCE_LOADING_DVLOG(1) + << "MemoryCache::add Added " << resource->Url().GetString() + << ", resource " << resource; +} + +void MemoryCache::AddInternal(ResourceMap* resource_map, + MemoryCacheEntry* entry) { + DCHECK(WTF::IsMainThread()); + DCHECK(resource_map); + + Resource* resource = entry->GetResource(); + if (!resource) + return; + DCHECK(resource->Url().IsValid()); + + KURL url = RemoveFragmentIdentifierIfNeeded(resource->Url()); + ResourceMap::iterator it = resource_map->find(url); + if (it != resource_map->end()) { + Resource* old_resource = it->value->GetResource(); + CHECK_NE(old_resource, resource); + Update(old_resource, old_resource->size(), 0); + } + resource_map->Set(url, entry); + Update(resource, 0, resource->size()); +} + +void MemoryCache::Remove(Resource* resource) { + DCHECK(WTF::IsMainThread()); + DCHECK(resource); + // Resources can be created with garbage urls in error cases. These Resources + // should never be added to the cache (AddInternal() DCHECKs that the url is + // valid). Null urls will crash if we attempt to hash them, so early exit. + if (resource->Url().IsNull()) + return; + + RESOURCE_LOADING_DVLOG(1) << "Evicting resource " << resource << " for " + << resource->Url().GetString() << " from cache"; + TRACE_EVENT1("blink", "MemoryCache::evict", "resource", + resource->Url().GetString().Utf8()); + + ResourceMap* resources = resource_maps_.at(resource->CacheIdentifier()); + if (!resources) + return; + + KURL url = RemoveFragmentIdentifierIfNeeded(resource->Url()); + ResourceMap::iterator it = resources->find(url); + if (it == resources->end() || it->value->GetResource() != resource) + return; + RemoveInternal(resources, it); +} + +void MemoryCache::RemoveInternal(ResourceMap* resource_map, + const ResourceMap::iterator& it) { + DCHECK(WTF::IsMainThread()); + DCHECK(resource_map); + + Resource* resource = it->value->GetResource(); + DCHECK(resource); + + Update(resource, resource->size(), 0); + resource_map->erase(it); +} + +bool MemoryCache::Contains(const Resource* resource) const { + if (!resource || resource->Url().IsEmpty()) + return false; + const ResourceMap* resources = resource_maps_.at(resource->CacheIdentifier()); + if (!resources) + return false; + KURL url = RemoveFragmentIdentifierIfNeeded(resource->Url()); + MemoryCacheEntry* entry = resources->at(url); + return entry && resource == entry->GetResource(); +} + +Resource* MemoryCache::ResourceForURL(const KURL& resource_url) const { + return ResourceForURL(resource_url, DefaultCacheIdentifier()); +} + +Resource* MemoryCache::ResourceForURL(const KURL& resource_url, + const String& cache_identifier) const { + DCHECK(WTF::IsMainThread()); + if (!resource_url.IsValid() || resource_url.IsNull()) + return nullptr; + DCHECK(!cache_identifier.IsNull()); + const ResourceMap* resources = resource_maps_.at(cache_identifier); + if (!resources) + return nullptr; + MemoryCacheEntry* entry = + resources->at(RemoveFragmentIdentifierIfNeeded(resource_url)); + if (!entry) + return nullptr; + return entry->GetResource(); +} + +HeapVector<Member<Resource>> MemoryCache::ResourcesForURL( + const KURL& resource_url) const { + DCHECK(WTF::IsMainThread()); + KURL url = RemoveFragmentIdentifierIfNeeded(resource_url); + HeapVector<Member<Resource>> results; + for (const auto& resource_map_iter : resource_maps_) { + if (MemoryCacheEntry* entry = resource_map_iter.value->at(url)) { + Resource* resource = entry->GetResource(); + DCHECK(resource); + results.push_back(resource); + } + } + return results; +} + +void MemoryCache::PruneResources(PruneStrategy strategy) { + DCHECK(!prune_pending_); + const size_t size_limit = (strategy == kMaximalPrune) ? 0 : Capacity(); + if (size_ <= size_limit) + return; + + // Cut by a percentage to avoid immediately pruning again. + size_t target_size = + static_cast<size_t>(size_limit * kCTargetPrunePercentage); + + for (const auto& resource_map_iter : resource_maps_) { + for (const auto& resource_iter : *resource_map_iter.value) { + Resource* resource = resource_iter.value->GetResource(); + DCHECK(resource); + if (resource->IsLoaded() && resource->DecodedSize()) { + // Check to see if the remaining resources are too new to prune. + double elapsed_time = prune_frame_time_stamp_ - + resource_iter.value->last_decoded_access_time_; + if (strategy == kAutomaticPrune && + elapsed_time < delay_before_live_decoded_prune_) + continue; + resource->Prune(); + if (size_ <= target_size) + return; + } + } + } +} + +void MemoryCache::SetCapacity(size_t total_bytes) { + capacity_ = total_bytes; + Prune(); +} + +void MemoryCache::Update(Resource* resource, size_t old_size, size_t new_size) { + if (!Contains(resource)) + return; + ptrdiff_t delta = new_size - old_size; + DCHECK(delta >= 0 || size_ >= static_cast<size_t>(-delta)); + size_ += delta; +} + +void MemoryCache::RemoveURLFromCache(const KURL& url) { + HeapVector<Member<Resource>> resources = ResourcesForURL(url); + for (Resource* resource : resources) + Remove(resource); +} + +void MemoryCache::TypeStatistic::AddResource(Resource* o) { + count++; + size += o->size(); + decoded_size += o->DecodedSize(); + encoded_size += o->EncodedSize(); + overhead_size += o->OverheadSize(); + encoded_size_duplicated_in_data_urls += + o->Url().ProtocolIsData() ? o->EncodedSize() : 0; +} + +MemoryCache::Statistics MemoryCache::GetStatistics() const { + Statistics stats; + for (const auto& resource_map_iter : resource_maps_) { + for (const auto& resource_iter : *resource_map_iter.value) { + Resource* resource = resource_iter.value->GetResource(); + DCHECK(resource); + switch (resource->GetType()) { + case Resource::kImage: + stats.images.AddResource(resource); + break; + case Resource::kCSSStyleSheet: + stats.css_style_sheets.AddResource(resource); + break; + case Resource::kScript: + stats.scripts.AddResource(resource); + break; + case Resource::kXSLStyleSheet: + stats.xsl_style_sheets.AddResource(resource); + break; + case Resource::kFont: + stats.fonts.AddResource(resource); + break; + default: + stats.other.AddResource(resource); + break; + } + } + } + return stats; +} + +void MemoryCache::EvictResources(EvictResourcePolicy policy) { + for (auto resource_map_iter = resource_maps_.begin(); + resource_map_iter != resource_maps_.end();) { + ResourceMap* resources = resource_map_iter->value.Get(); + HeapVector<Member<MemoryCacheEntry>> unused_preloads; + for (auto resource_iter = resources->begin(); + resource_iter != resources->end(); + resource_iter = resources->begin()) { + DCHECK(resource_iter.Get()); + DCHECK(resource_iter->value.Get()); + DCHECK(resource_iter->value->GetResource()); + Resource* resource = resource_iter->value->GetResource(); + DCHECK(resource); + if (policy != kEvictAllResources && resource->IsUnusedPreload()) { + // Store unused preloads aside, so they could be added back later. + // That is in order to avoid the performance impact of iterating over + // the same resource multiple times. + unused_preloads.push_back(resource_iter->value.Get()); + } + RemoveInternal(resources, resource_iter); + } + for (const auto& unused_preload : unused_preloads) { + AddInternal(resources, unused_preload); + } + // We may iterate multiple times over resourceMaps with unused preloads. + // That's extremely unlikely to have any real-life performance impact. + if (!resources->size()) { + resource_maps_.erase(resource_map_iter); + resource_map_iter = resource_maps_.begin(); + } else { + ++resource_map_iter; + } + } +} + +void MemoryCache::Prune() { + TRACE_EVENT0("renderer", "MemoryCache::prune()"); + + if (in_prune_resources_) + return; + if (size_ <= capacity_) // Fast path. + return; + + // To avoid burdening the current thread with repetitive pruning jobs, pruning + // is postponed until the end of the current task. If it has been more than + // m_maxPruneDeferralDelay since the last prune, then we prune immediately. If + // the current thread's run loop is not active, then pruning will happen + // immediately only if it has been over m_maxPruneDeferralDelay since the last + // prune. + double current_time = WTF::CurrentTime(); + if (prune_pending_) { + if (current_time - prune_time_stamp_ >= max_prune_deferral_delay_) { + PruneNow(current_time, kAutomaticPrune); + } + } else { + if (current_time - prune_time_stamp_ >= max_prune_deferral_delay_) { + PruneNow(current_time, kAutomaticPrune); // Delay exceeded, prune now. + } else { + // Defer. + Platform::Current()->CurrentThread()->AddTaskObserver(this); + prune_pending_ = true; + } + } +} + +void MemoryCache::WillProcessTask() {} + +void MemoryCache::DidProcessTask() { + // Perform deferred pruning + DCHECK(prune_pending_); + PruneNow(WTF::CurrentTime(), kAutomaticPrune); +} + +void MemoryCache::PruneAll() { + double current_time = WTF::CurrentTime(); + PruneNow(current_time, kMaximalPrune); +} + +void MemoryCache::PruneNow(double current_time, PruneStrategy strategy) { + if (prune_pending_) { + prune_pending_ = false; + Platform::Current()->CurrentThread()->RemoveTaskObserver(this); + } + + AutoReset<bool> reentrancy_protector(&in_prune_resources_, true); + + PruneResources(strategy); + prune_frame_time_stamp_ = last_frame_paint_time_stamp_; + prune_time_stamp_ = current_time; +} + +void MemoryCache::UpdateFramePaintTimestamp() { + last_frame_paint_time_stamp_ = CurrentTime(); +} + +bool MemoryCache::OnMemoryDump(WebMemoryDumpLevelOfDetail level_of_detail, + WebProcessMemoryDump* memory_dump) { + if (level_of_detail == WebMemoryDumpLevelOfDetail::kBackground) { + Statistics stats = GetStatistics(); + WebMemoryAllocatorDump* dump1 = + memory_dump->CreateMemoryAllocatorDump("web_cache/Image_resources"); + dump1->AddScalar("size", "bytes", + stats.images.encoded_size + stats.images.overhead_size); + WebMemoryAllocatorDump* dump2 = memory_dump->CreateMemoryAllocatorDump( + "web_cache/CSS stylesheet_resources"); + dump2->AddScalar("size", "bytes", + stats.css_style_sheets.encoded_size + + stats.css_style_sheets.overhead_size); + WebMemoryAllocatorDump* dump3 = + memory_dump->CreateMemoryAllocatorDump("web_cache/Script_resources"); + dump3->AddScalar("size", "bytes", + stats.scripts.encoded_size + stats.scripts.overhead_size); + WebMemoryAllocatorDump* dump4 = memory_dump->CreateMemoryAllocatorDump( + "web_cache/XSL stylesheet_resources"); + dump4->AddScalar("size", "bytes", + stats.xsl_style_sheets.encoded_size + + stats.xsl_style_sheets.overhead_size); + WebMemoryAllocatorDump* dump5 = + memory_dump->CreateMemoryAllocatorDump("web_cache/Font_resources"); + dump5->AddScalar("size", "bytes", + stats.fonts.encoded_size + stats.fonts.overhead_size); + WebMemoryAllocatorDump* dump6 = + memory_dump->CreateMemoryAllocatorDump("web_cache/Other_resources"); + dump6->AddScalar("size", "bytes", + stats.other.encoded_size + stats.other.overhead_size); + return true; + } + + for (const auto& resource_map_iter : resource_maps_) { + for (const auto& resource_iter : *resource_map_iter.value) { + Resource* resource = resource_iter.value->GetResource(); + resource->OnMemoryDump(level_of_detail, memory_dump); + } + } + return true; +} + +void MemoryCache::OnMemoryPressure(WebMemoryPressureLevel level) { + PruneAll(); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/platform/loader/fetch/memory_cache.h b/chromium/third_party/blink/renderer/platform/loader/fetch/memory_cache.h new file mode 100644 index 00000000000..bd63614edc1 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/loader/fetch/memory_cache.h @@ -0,0 +1,220 @@ +/* + Copyright (C) 1998 Lars Knoll (knoll@mpi-hd.mpg.de) + Copyright (C) 2001 Dirk Mueller <mueller@kde.org> + 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_PLATFORM_LOADER_FETCH_MEMORY_CACHE_H_ +#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_LOADER_FETCH_MEMORY_CACHE_H_ + +#include "third_party/blink/public/platform/web_thread.h" +#include "third_party/blink/renderer/platform/instrumentation/tracing/memory_cache_dump_provider.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource.h" +#include "third_party/blink/renderer/platform/memory_coordinator.h" +#include "third_party/blink/renderer/platform/platform_export.h" +#include "third_party/blink/renderer/platform/wtf/allocator.h" +#include "third_party/blink/renderer/platform/wtf/hash_map.h" +#include "third_party/blink/renderer/platform/wtf/noncopyable.h" +#include "third_party/blink/renderer/platform/wtf/text/string_hash.h" +#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h" +#include "third_party/blink/renderer/platform/wtf/vector.h" + +namespace blink { + +class KURL; + +// Member<MemoryCacheEntry> + MemoryCacheEntry::clearResourceWeak() monitors +// eviction from MemoryCache due to Resource garbage collection. +// WeakMember<Resource> + Resource's prefinalizer cannot determine whether the +// Resource was on MemoryCache or not, because WeakMember is already cleared +// when the prefinalizer is executed. +class MemoryCacheEntry final : public GarbageCollected<MemoryCacheEntry> { + public: + static MemoryCacheEntry* Create(Resource* resource) { + return new MemoryCacheEntry(resource); + } + void Trace(blink::Visitor*); + Resource* GetResource() const { return resource_; } + + double last_decoded_access_time_; // Used as a thrash guard + + private: + explicit MemoryCacheEntry(Resource* resource) + : last_decoded_access_time_(0.0), resource_(resource) {} + + void ClearResourceWeak(Visitor*); + + WeakMember<Resource> resource_; +}; + +WILL_NOT_BE_EAGERLY_TRACED_CLASS(MemoryCacheEntry); + +// This cache holds subresources used by Web pages: images, scripts, +// stylesheets, etc. +class PLATFORM_EXPORT MemoryCache final + : public GarbageCollectedFinalized<MemoryCache>, + public WebThread::TaskObserver, + public MemoryCacheDumpClient, + public MemoryCoordinatorClient { + USING_GARBAGE_COLLECTED_MIXIN(MemoryCache); + WTF_MAKE_NONCOPYABLE(MemoryCache); + + public: + static MemoryCache* Create(); + ~MemoryCache() override; + void Trace(blink::Visitor*) override; + + struct TypeStatistic { + STACK_ALLOCATED(); + size_t count; + size_t size; + size_t decoded_size; + size_t encoded_size; + size_t overhead_size; + size_t encoded_size_duplicated_in_data_urls; + + TypeStatistic() + : count(0), + size(0), + decoded_size(0), + encoded_size(0), + overhead_size(0), + encoded_size_duplicated_in_data_urls(0) {} + + void AddResource(Resource*); + }; + + struct Statistics { + STACK_ALLOCATED(); + TypeStatistic images; + TypeStatistic css_style_sheets; + TypeStatistic scripts; + TypeStatistic xsl_style_sheets; + TypeStatistic fonts; + TypeStatistic other; + }; + + Resource* ResourceForURL(const KURL&) const; + Resource* ResourceForURL(const KURL&, const String& cache_identifier) const; + HeapVector<Member<Resource>> ResourcesForURL(const KURL&) const; + + void Add(Resource*); + void Remove(Resource*); + bool Contains(const Resource*) const; + + static KURL RemoveFragmentIdentifierIfNeeded(const KURL& original_url); + + static String DefaultCacheIdentifier(); + + // Sets the cache's memory capacities, in bytes. These will hold only + // approximately, since the decoded cost of resources like scripts and + // stylesheets is not known. + // - totalBytes: The maximum number of bytes that the cache should consume + // overall. + void SetCapacity(size_t total_bytes); + void SetDelayBeforeLiveDecodedPrune(double seconds) { + delay_before_live_decoded_prune_ = seconds; + } + void SetMaxPruneDeferralDelay(double seconds) { + max_prune_deferral_delay_ = seconds; + } + + enum EvictResourcePolicy { kEvictAllResources, kDoNotEvictUnusedPreloads }; + void EvictResources(EvictResourcePolicy = kEvictAllResources); + + void Prune(); + + // Called to update MemoryCache::size(). + void Update(Resource*, size_t old_size, size_t new_size); + + void RemoveURLFromCache(const KURL&); + + Statistics GetStatistics() const; + + size_t Capacity() const { return capacity_; } + size_t size() const { return size_; } + + // TaskObserver implementation + void WillProcessTask() override; + void DidProcessTask() override; + + void PruneAll(); + + void UpdateFramePaintTimestamp(); + + // Take memory usage snapshot for tracing. + bool OnMemoryDump(WebMemoryDumpLevelOfDetail, WebProcessMemoryDump*) override; + + void OnMemoryPressure(WebMemoryPressureLevel) override; + + private: + enum PruneStrategy { + // Automatically decide how much to prune. + kAutomaticPrune, + // Maximally prune resources. + kMaximalPrune + }; + + // A URL-based map of all resources that are in the cache (including the + // freshest version of objects that are currently being referenced by a Web + // page). removeFragmentIdentifierIfNeeded() should be called for the url + // before using it as a key for the map. + using ResourceMap = HeapHashMap<String, Member<MemoryCacheEntry>>; + using ResourceMapIndex = HeapHashMap<String, Member<ResourceMap>>; + ResourceMap* EnsureResourceMap(const String& cache_identifier); + ResourceMapIndex resource_maps_; + + MemoryCache(); + + void AddInternal(ResourceMap*, MemoryCacheEntry*); + void RemoveInternal(ResourceMap*, const ResourceMap::iterator&); + + void PruneResources(PruneStrategy); + void PruneNow(double current_time, PruneStrategy); + + bool in_prune_resources_; + bool prune_pending_; + double max_prune_deferral_delay_; + double prune_time_stamp_; + double prune_frame_time_stamp_; + double last_frame_paint_time_stamp_; // used for detecting decoded resource + // thrash in the cache + + size_t capacity_; + double delay_before_live_decoded_prune_; + + // The number of bytes currently consumed by resources in the cache. + size_t size_; + + friend class MemoryCacheTest; +}; + +// Returns the global cache. +PLATFORM_EXPORT MemoryCache* GetMemoryCache(); + +// Sets the global cache, used to swap in a test instance. Returns the old +// MemoryCache object. +PLATFORM_EXPORT MemoryCache* ReplaceMemoryCacheForTesting(MemoryCache*); + +} // namespace blink + +#endif diff --git a/chromium/third_party/blink/renderer/platform/loader/fetch/memory_cache_correctness_test.cc b/chromium/third_party/blink/renderer/platform/loader/fetch/memory_cache_correctness_test.cc new file mode 100644 index 00000000000..75a0e6c10dd --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/loader/fetch/memory_cache_correctness_test.cc @@ -0,0 +1,564 @@ +/* + * Copyright (c) 2014, 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/platform/loader/fetch/memory_cache.h" + +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/blink/renderer/platform/loader/fetch/fetch_context.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.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/loader/testing/mock_fetch_context.h" +#include "third_party/blink/renderer/platform/loader/testing/mock_resource.h" +#include "third_party/blink/renderer/platform/testing/testing_platform_support_with_mock_scheduler.h" + +namespace blink { + +namespace { + +// An URL for the original request. +constexpr char kResourceURL[] = "http://resource.com/"; + +// The origin time of our first request. +constexpr char kOriginalRequestDateAsString[] = "Thu, 25 May 1977 18:30:00 GMT"; +constexpr char kOneDayBeforeOriginalRequest[] = "Wed, 24 May 1977 18:30:00 GMT"; +constexpr char kOneDayAfterOriginalRequest[] = "Fri, 26 May 1977 18:30:00 GMT"; + +} // namespace + +class MemoryCacheCorrectnessTest : public testing::Test { + protected: + MockResource* ResourceFromResourceResponse(ResourceResponse response) { + if (response.Url().IsNull()) + response.SetURL(KURL(kResourceURL)); + ResourceRequest request(response.Url()); + MockResource* resource = MockResource::Create(request); + resource->SetResponse(response); + resource->FinishForTest(); + AddResourceToMemoryCache(resource); + + return resource; + } + MockResource* ResourceFromResourceRequest(ResourceRequest request) { + if (request.Url().IsNull()) + request.SetURL(KURL(kResourceURL)); + request.SetFetchCredentialsMode( + network::mojom::FetchCredentialsMode::kOmit); + MockResource* resource = MockResource::Create(request); + resource->SetResponse(ResourceResponse(KURL(kResourceURL), "text/html")); + resource->FinishForTest(); + AddResourceToMemoryCache(resource); + + return resource; + } + void AddResourceToMemoryCache(Resource* resource) { + resource->SetSourceOrigin(security_origin_); + GetMemoryCache()->Add(resource); + } + // TODO(toyoshim): Consider to use MockResource for all tests instead of + // RawResource. + RawResource* FetchRawResource() { + ResourceRequest resource_request{KURL(kResourceURL)}; + resource_request.SetRequestContext(WebURLRequest::kRequestContextInternal); + FetchParameters fetch_params(resource_request); + return RawResource::Fetch(fetch_params, Fetcher(), nullptr); + } + MockResource* FetchMockResource() { + ResourceRequest resource_request{KURL(kResourceURL)}; + FetchParameters fetch_params(resource_request); + return MockResource::Fetch(fetch_params, Fetcher(), nullptr); + } + ResourceFetcher* Fetcher() const { return fetcher_.Get(); } + void AdvanceClock(double seconds) { platform_->AdvanceClockSeconds(seconds); } + + private: + // Overrides testing::Test. + void SetUp() override { + // Save the global memory cache to restore it upon teardown. + global_memory_cache_ = ReplaceMemoryCacheForTesting(MemoryCache::Create()); + + MockFetchContext* context = + MockFetchContext::Create(MockFetchContext::kShouldNotLoadNewResource); + security_origin_ = SecurityOrigin::CreateUnique(); + context->SetSecurityOrigin(security_origin_); + + fetcher_ = ResourceFetcher::Create(context); + } + void TearDown() override { + GetMemoryCache()->EvictResources(); + + // Yield the ownership of the global memory cache back. + ReplaceMemoryCacheForTesting(global_memory_cache_.Release()); + } + + Persistent<MemoryCache> global_memory_cache_; + scoped_refptr<const SecurityOrigin> security_origin_; + Persistent<ResourceFetcher> fetcher_; + ScopedTestingPlatformSupport<TestingPlatformSupportWithMockScheduler> + platform_; +}; + +TEST_F(MemoryCacheCorrectnessTest, FreshFromLastModified) { + ResourceResponse fresh200_response; + fresh200_response.SetHTTPStatusCode(200); + fresh200_response.SetHTTPHeaderField("Date", kOriginalRequestDateAsString); + fresh200_response.SetHTTPHeaderField("Last-Modified", + kOneDayBeforeOriginalRequest); + + MockResource* fresh200 = ResourceFromResourceResponse(fresh200_response); + + // Advance the clock within the implicit freshness period of this resource + // before we make a request. + AdvanceClock(600.); + + MockResource* fetched = FetchMockResource(); + EXPECT_EQ(fresh200, fetched); +} + +TEST_F(MemoryCacheCorrectnessTest, FreshFromExpires) { + ResourceResponse fresh200_response; + fresh200_response.SetHTTPStatusCode(200); + fresh200_response.SetHTTPHeaderField("Date", kOriginalRequestDateAsString); + fresh200_response.SetHTTPHeaderField("Expires", kOneDayAfterOriginalRequest); + + MockResource* fresh200 = ResourceFromResourceResponse(fresh200_response); + + // Advance the clock within the freshness period of this resource before we + // make a request. + AdvanceClock(24. * 60. * 60. - 15.); + + MockResource* fetched = FetchMockResource(); + EXPECT_EQ(fresh200, fetched); +} + +TEST_F(MemoryCacheCorrectnessTest, FreshFromMaxAge) { + ResourceResponse fresh200_response; + fresh200_response.SetHTTPStatusCode(200); + fresh200_response.SetHTTPHeaderField("Date", kOriginalRequestDateAsString); + fresh200_response.SetHTTPHeaderField("Cache-Control", "max-age=600"); + + MockResource* fresh200 = ResourceFromResourceResponse(fresh200_response); + + // Advance the clock within the freshness period of this resource before we + // make a request. + AdvanceClock(500.); + + MockResource* fetched = FetchMockResource(); + EXPECT_EQ(fresh200, fetched); +} + +// The strong validator causes a revalidation to be launched, and the proxy and +// original resources leak because of their reference loop. +TEST_F(MemoryCacheCorrectnessTest, DISABLED_ExpiredFromLastModified) { + ResourceResponse expired200_response; + expired200_response.SetHTTPStatusCode(200); + expired200_response.SetHTTPHeaderField("Date", kOriginalRequestDateAsString); + expired200_response.SetHTTPHeaderField("Last-Modified", + kOneDayBeforeOriginalRequest); + + MockResource* expired200 = ResourceFromResourceResponse(expired200_response); + + // Advance the clock beyond the implicit freshness period. + AdvanceClock(24. * 60. * 60. * 0.2); + + MockResource* fetched = FetchMockResource(); + EXPECT_NE(expired200, fetched); +} + +TEST_F(MemoryCacheCorrectnessTest, ExpiredFromExpires) { + ResourceResponse expired200_response; + expired200_response.SetHTTPStatusCode(200); + expired200_response.SetHTTPHeaderField("Date", kOriginalRequestDateAsString); + expired200_response.SetHTTPHeaderField("Expires", + kOneDayAfterOriginalRequest); + + MockResource* expired200 = ResourceFromResourceResponse(expired200_response); + + // Advance the clock within the expiredness period of this resource before we + // make a request. + AdvanceClock(24. * 60. * 60. + 15.); + + MockResource* fetched = FetchMockResource(); + EXPECT_NE(expired200, fetched); +} + +// If the resource hasn't been loaded in this "document" before, then it +// shouldn't have list of available resources logic. +TEST_F(MemoryCacheCorrectnessTest, NewMockResourceExpiredFromExpires) { + ResourceResponse expired200_response; + expired200_response.SetHTTPStatusCode(200); + expired200_response.SetHTTPHeaderField("Date", kOriginalRequestDateAsString); + expired200_response.SetHTTPHeaderField("Expires", + kOneDayAfterOriginalRequest); + + MockResource* expired200 = ResourceFromResourceResponse(expired200_response); + + // Advance the clock within the expiredness period of this resource before we + // make a request. + AdvanceClock(24. * 60. * 60. + 15.); + + MockResource* fetched = FetchMockResource(); + EXPECT_NE(expired200, fetched); +} + +// If the resource has been loaded in this "document" before, then it should +// have list of available resources logic, and so normal cache testing should be +// bypassed. +TEST_F(MemoryCacheCorrectnessTest, ReuseMockResourceExpiredFromExpires) { + ResourceResponse expired200_response; + expired200_response.SetHTTPStatusCode(200); + expired200_response.SetHTTPHeaderField("Date", kOriginalRequestDateAsString); + expired200_response.SetHTTPHeaderField("Expires", + kOneDayAfterOriginalRequest); + + MockResource* expired200 = ResourceFromResourceResponse(expired200_response); + + // Advance the clock within the freshness period, and make a request to add + // this resource to the document resources. + AdvanceClock(15.); + MockResource* first_fetched = FetchMockResource(); + EXPECT_EQ(expired200, first_fetched); + + // Advance the clock within the expiredness period of this resource before we + // make a request. + AdvanceClock(24. * 60. * 60. + 15.); + + MockResource* fetched = FetchMockResource(); + EXPECT_EQ(expired200, fetched); +} + +TEST_F(MemoryCacheCorrectnessTest, ExpiredFromMaxAge) { + ResourceResponse expired200_response; + expired200_response.SetHTTPStatusCode(200); + expired200_response.SetHTTPHeaderField("Date", kOriginalRequestDateAsString); + expired200_response.SetHTTPHeaderField("Cache-Control", "max-age=600"); + + MockResource* expired200 = ResourceFromResourceResponse(expired200_response); + + // Advance the clock within the expiredness period of this resource before we + // make a request. + AdvanceClock(700.); + + MockResource* fetched = FetchMockResource(); + EXPECT_NE(expired200, fetched); +} + +TEST_F(MemoryCacheCorrectnessTest, FreshButNoCache) { + ResourceResponse fresh200_nocache_response; + fresh200_nocache_response.SetHTTPStatusCode(200); + fresh200_nocache_response.SetHTTPHeaderField(HTTPNames::Date, + kOriginalRequestDateAsString); + fresh200_nocache_response.SetHTTPHeaderField(HTTPNames::Expires, + kOneDayAfterOriginalRequest); + fresh200_nocache_response.SetHTTPHeaderField(HTTPNames::Cache_Control, + "no-cache"); + + MockResource* fresh200_nocache = + ResourceFromResourceResponse(fresh200_nocache_response); + + // Advance the clock within the freshness period of this resource before we + // make a request. + AdvanceClock(24. * 60. * 60. - 15.); + + MockResource* fetched = FetchMockResource(); + EXPECT_NE(fresh200_nocache, fetched); +} + +TEST_F(MemoryCacheCorrectnessTest, RequestWithNoCache) { + ResourceRequest no_cache_request; + no_cache_request.SetHTTPHeaderField(HTTPNames::Cache_Control, "no-cache"); + MockResource* no_cache_resource = + ResourceFromResourceRequest(no_cache_request); + MockResource* fetched = FetchMockResource(); + EXPECT_NE(no_cache_resource, fetched); +} + +TEST_F(MemoryCacheCorrectnessTest, FreshButNoStore) { + ResourceResponse fresh200_nostore_response; + fresh200_nostore_response.SetHTTPStatusCode(200); + fresh200_nostore_response.SetHTTPHeaderField(HTTPNames::Date, + kOriginalRequestDateAsString); + fresh200_nostore_response.SetHTTPHeaderField(HTTPNames::Expires, + kOneDayAfterOriginalRequest); + fresh200_nostore_response.SetHTTPHeaderField(HTTPNames::Cache_Control, + "no-store"); + + MockResource* fresh200_nostore = + ResourceFromResourceResponse(fresh200_nostore_response); + + // Advance the clock within the freshness period of this resource before we + // make a request. + AdvanceClock(24. * 60. * 60. - 15.); + + MockResource* fetched = FetchMockResource(); + EXPECT_NE(fresh200_nostore, fetched); +} + +TEST_F(MemoryCacheCorrectnessTest, RequestWithNoStore) { + ResourceRequest no_store_request; + no_store_request.SetHTTPHeaderField(HTTPNames::Cache_Control, "no-store"); + MockResource* no_store_resource = + ResourceFromResourceRequest(no_store_request); + MockResource* fetched = FetchMockResource(); + EXPECT_NE(no_store_resource, fetched); +} + +// FIXME: Determine if ignoring must-revalidate for blink is correct behaviour. +// See crbug.com/340088 . +TEST_F(MemoryCacheCorrectnessTest, DISABLED_FreshButMustRevalidate) { + ResourceResponse fresh200_must_revalidate_response; + fresh200_must_revalidate_response.SetHTTPStatusCode(200); + fresh200_must_revalidate_response.SetHTTPHeaderField( + HTTPNames::Date, kOriginalRequestDateAsString); + fresh200_must_revalidate_response.SetHTTPHeaderField( + HTTPNames::Expires, kOneDayAfterOriginalRequest); + fresh200_must_revalidate_response.SetHTTPHeaderField(HTTPNames::Cache_Control, + "must-revalidate"); + + MockResource* fresh200_must_revalidate = + ResourceFromResourceResponse(fresh200_must_revalidate_response); + + // Advance the clock within the freshness period of this resource before we + // make a request. + AdvanceClock(24. * 60. * 60. - 15.); + + MockResource* fetched = FetchMockResource(); + EXPECT_NE(fresh200_must_revalidate, fetched); +} + +TEST_F(MemoryCacheCorrectnessTest, FreshWithFreshRedirect) { + KURL redirect_url(kResourceURL); + const char kRedirectTargetUrlString[] = "http://redirect-target.com"; + KURL redirect_target_url(kRedirectTargetUrlString); + + ResourceRequest request(redirect_url); + MockResource* first_resource = MockResource::Create(request); + + ResourceResponse fresh301_response(redirect_url); + fresh301_response.SetHTTPStatusCode(301); + fresh301_response.SetHTTPHeaderField(HTTPNames::Date, + kOriginalRequestDateAsString); + fresh301_response.SetHTTPHeaderField(HTTPNames::Location, + kRedirectTargetUrlString); + fresh301_response.SetHTTPHeaderField(HTTPNames::Cache_Control, "max-age=600"); + + // Add the redirect to our request. + ResourceRequest redirect_request = ResourceRequest(redirect_target_url); + first_resource->WillFollowRedirect(redirect_request, fresh301_response); + + // Add the final response to our request. + ResourceResponse fresh200_response(redirect_target_url); + fresh200_response.SetHTTPStatusCode(200); + fresh200_response.SetHTTPHeaderField(HTTPNames::Date, + kOriginalRequestDateAsString); + fresh200_response.SetHTTPHeaderField(HTTPNames::Expires, + kOneDayAfterOriginalRequest); + + first_resource->SetResponse(fresh200_response); + first_resource->FinishForTest(); + AddResourceToMemoryCache(first_resource); + + AdvanceClock(500.); + + MockResource* fetched = FetchMockResource(); + EXPECT_EQ(first_resource, fetched); +} + +TEST_F(MemoryCacheCorrectnessTest, FreshWithStaleRedirect) { + KURL redirect_url(kResourceURL); + const char kRedirectTargetUrlString[] = "http://redirect-target.com"; + KURL redirect_target_url(kRedirectTargetUrlString); + + ResourceRequest request(redirect_url); + request.SetFetchCredentialsMode(network::mojom::FetchCredentialsMode::kOmit); + MockResource* first_resource = MockResource::Create(request); + + ResourceResponse stale301_response(redirect_url); + stale301_response.SetHTTPStatusCode(301); + stale301_response.SetHTTPHeaderField(HTTPNames::Date, + kOriginalRequestDateAsString); + stale301_response.SetHTTPHeaderField(HTTPNames::Location, + kRedirectTargetUrlString); + + // Add the redirect to our request. + ResourceRequest redirect_request = ResourceRequest(redirect_target_url); + first_resource->WillFollowRedirect(redirect_request, stale301_response); + + // Add the final response to our request. + ResourceResponse fresh200_response(redirect_target_url); + fresh200_response.SetHTTPStatusCode(200); + fresh200_response.SetHTTPHeaderField(HTTPNames::Date, + kOriginalRequestDateAsString); + fresh200_response.SetHTTPHeaderField(HTTPNames::Expires, + kOneDayAfterOriginalRequest); + + first_resource->SetResponse(fresh200_response); + first_resource->FinishForTest(); + AddResourceToMemoryCache(first_resource); + + AdvanceClock(500.); + + MockResource* fetched = FetchMockResource(); + EXPECT_NE(first_resource, fetched); +} + +TEST_F(MemoryCacheCorrectnessTest, PostToSameURLTwice) { + ResourceRequest request1{KURL(kResourceURL)}; + request1.SetHTTPMethod(HTTPNames::POST); + RawResource* resource1 = RawResource::CreateForTest(request1, Resource::kRaw); + resource1->SetStatus(ResourceStatus::kPending); + AddResourceToMemoryCache(resource1); + + ResourceRequest request2{KURL(kResourceURL)}; + request2.SetHTTPMethod(HTTPNames::POST); + FetchParameters fetch2(request2); + RawResource* resource2 = RawResource::FetchSynchronously(fetch2, Fetcher()); + EXPECT_NE(resource1, resource2); +} + +TEST_F(MemoryCacheCorrectnessTest, 302RedirectNotImplicitlyFresh) { + KURL redirect_url(kResourceURL); + const char kRedirectTargetUrlString[] = "http://redirect-target.com"; + KURL redirect_target_url(kRedirectTargetUrlString); + + RawResource* first_resource = + RawResource::CreateForTest(redirect_url, Resource::kRaw); + + ResourceResponse fresh302_response(redirect_url); + fresh302_response.SetHTTPStatusCode(302); + fresh302_response.SetHTTPHeaderField(HTTPNames::Date, + kOriginalRequestDateAsString); + fresh302_response.SetHTTPHeaderField(HTTPNames::Last_Modified, + kOneDayBeforeOriginalRequest); + fresh302_response.SetHTTPHeaderField(HTTPNames::Location, + kRedirectTargetUrlString); + + // Add the redirect to our request. + ResourceRequest redirect_request = ResourceRequest(redirect_target_url); + first_resource->WillFollowRedirect(redirect_request, fresh302_response); + + // Add the final response to our request. + ResourceResponse fresh200_response(redirect_target_url); + fresh200_response.SetHTTPStatusCode(200); + fresh200_response.SetHTTPHeaderField(HTTPNames::Date, + kOriginalRequestDateAsString); + fresh200_response.SetHTTPHeaderField(HTTPNames::Expires, + kOneDayAfterOriginalRequest); + + first_resource->SetResponse(fresh200_response); + first_resource->FinishForTest(); + AddResourceToMemoryCache(first_resource); + + AdvanceClock(500.); + + RawResource* fetched = FetchRawResource(); + EXPECT_NE(first_resource, fetched); +} + +TEST_F(MemoryCacheCorrectnessTest, 302RedirectExplicitlyFreshMaxAge) { + KURL redirect_url(kResourceURL); + const char kRedirectTargetUrlString[] = "http://redirect-target.com"; + KURL redirect_target_url(kRedirectTargetUrlString); + + ResourceRequest request(redirect_url); + MockResource* first_resource = MockResource::Create(request); + + ResourceResponse fresh302_response(redirect_url); + fresh302_response.SetHTTPStatusCode(302); + fresh302_response.SetHTTPHeaderField(HTTPNames::Date, + kOriginalRequestDateAsString); + fresh302_response.SetHTTPHeaderField(HTTPNames::Cache_Control, "max-age=600"); + fresh302_response.SetHTTPHeaderField(HTTPNames::Location, + kRedirectTargetUrlString); + + // Add the redirect to our request. + ResourceRequest redirect_request = ResourceRequest(redirect_target_url); + first_resource->WillFollowRedirect(redirect_request, fresh302_response); + + // Add the final response to our request. + ResourceResponse fresh200_response(redirect_target_url); + fresh200_response.SetHTTPStatusCode(200); + fresh200_response.SetHTTPHeaderField(HTTPNames::Date, + kOriginalRequestDateAsString); + fresh200_response.SetHTTPHeaderField(HTTPNames::Expires, + kOneDayAfterOriginalRequest); + + first_resource->SetResponse(fresh200_response); + first_resource->FinishForTest(); + AddResourceToMemoryCache(first_resource); + + AdvanceClock(500.); + + MockResource* fetched = FetchMockResource(); + EXPECT_EQ(first_resource, fetched); +} + +TEST_F(MemoryCacheCorrectnessTest, 302RedirectExplicitlyFreshExpires) { + KURL redirect_url(kResourceURL); + const char kRedirectTargetUrlString[] = "http://redirect-target.com"; + KURL redirect_target_url(kRedirectTargetUrlString); + + ResourceRequest request(redirect_url); + MockResource* first_resource = MockResource::Create(request); + + ResourceResponse fresh302_response(redirect_url); + fresh302_response.SetHTTPStatusCode(302); + fresh302_response.SetHTTPHeaderField(HTTPNames::Date, + kOriginalRequestDateAsString); + fresh302_response.SetHTTPHeaderField(HTTPNames::Expires, + kOneDayAfterOriginalRequest); + fresh302_response.SetHTTPHeaderField(HTTPNames::Location, + kRedirectTargetUrlString); + + // Add the redirect to our request. + ResourceRequest redirect_request = ResourceRequest(redirect_target_url); + first_resource->WillFollowRedirect(redirect_request, fresh302_response); + + // Add the final response to our request. + ResourceResponse fresh200_response(redirect_target_url); + fresh200_response.SetHTTPStatusCode(200); + fresh200_response.SetHTTPHeaderField(HTTPNames::Date, + kOriginalRequestDateAsString); + fresh200_response.SetHTTPHeaderField(HTTPNames::Expires, + kOneDayAfterOriginalRequest); + + first_resource->SetResponse(fresh200_response); + first_resource->FinishForTest(); + AddResourceToMemoryCache(first_resource); + + AdvanceClock(500.); + + MockResource* fetched = FetchMockResource(); + EXPECT_EQ(first_resource, fetched); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/platform/loader/fetch/memory_cache_test.cc b/chromium/third_party/blink/renderer/platform/loader/fetch/memory_cache_test.cc new file mode 100644 index 00000000000..d107d869f93 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/loader/fetch/memory_cache_test.cc @@ -0,0 +1,419 @@ +/* + * 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/platform/loader/fetch/memory_cache.h" + +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/blink/public/platform/platform.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/loader/fetch/resource_request.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/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/weborigin/kurl.h" + +namespace blink { + +class FakeDecodedResource final : public Resource { + public: + static FakeDecodedResource* Fetch(FetchParameters& params, + ResourceFetcher* fetcher, + ResourceClient* client) { + return static_cast<FakeDecodedResource*>( + fetcher->RequestResource(params, Factory(), client)); + } + + void AppendData(const char* data, size_t len) override { + Resource::AppendData(data, len); + SetDecodedSize(this->size()); + } + + void FakeEncodedSize(size_t size) { SetEncodedSize(size); } + + private: + class Factory final : public NonTextResourceFactory { + public: + Factory() : NonTextResourceFactory(kMock) {} + + Resource* Create(const ResourceRequest& request, + const ResourceLoaderOptions& options) const override { + return new FakeDecodedResource(request, options); + } + }; + + FakeDecodedResource(const ResourceRequest& request, + const ResourceLoaderOptions& options) + : Resource(request, kMock, options) {} + + void DestroyDecodedDataIfPossible() override { SetDecodedSize(0); } +}; + +class MemoryCacheTest : public testing::Test { + public: + class FakeResource final : public Resource { + public: + static FakeResource* Create(const char* url, Type type) { + return Create(KURL(url), type); + } + static FakeResource* Create(const KURL& url, Type type) { + ResourceRequest request(url); + request.SetFetchCredentialsMode( + network::mojom::FetchCredentialsMode::kOmit); + + ResourceLoaderOptions options; + + return new FakeResource(request, type, options); + } + + private: + FakeResource(const ResourceRequest& request, + Type type, + const ResourceLoaderOptions& options) + : Resource(request, type, options) {} + }; + + protected: + void SetUp() override { + // Save the global memory cache to restore it upon teardown. + global_memory_cache_ = ReplaceMemoryCacheForTesting(MemoryCache::Create()); + fetcher_ = ResourceFetcher::Create( + MockFetchContext::Create(MockFetchContext::kShouldLoadNewResource)); + } + + void TearDown() override { + ReplaceMemoryCacheForTesting(global_memory_cache_.Release()); + } + + Persistent<MemoryCache> global_memory_cache_; + Persistent<ResourceFetcher> fetcher_; + ScopedTestingPlatformSupport<TestingPlatformSupportWithMockScheduler> + platform_; +}; + +// Verifies that setters and getters for cache capacities work correcty. +TEST_F(MemoryCacheTest, CapacityAccounting) { + const size_t kSizeMax = ~static_cast<size_t>(0); + const size_t kTotalCapacity = kSizeMax / 4; + GetMemoryCache()->SetCapacity(kTotalCapacity); + EXPECT_EQ(kTotalCapacity, GetMemoryCache()->Capacity()); +} + +TEST_F(MemoryCacheTest, VeryLargeResourceAccounting) { + const size_t kSizeMax = ~static_cast<size_t>(0); + const size_t kTotalCapacity = kSizeMax / 4; + const size_t kResourceSize1 = kSizeMax / 16; + const size_t kResourceSize2 = kSizeMax / 20; + GetMemoryCache()->SetCapacity(kTotalCapacity); + Persistent<MockResourceClient> client = new MockResourceClient; + FetchParameters params(ResourceRequest("data:text/html,")); + FakeDecodedResource* cached_resource = + FakeDecodedResource::Fetch(params, fetcher_, client); + cached_resource->FakeEncodedSize(kResourceSize1); + + EXPECT_TRUE(GetMemoryCache()->Contains(cached_resource)); + EXPECT_EQ(cached_resource->size(), GetMemoryCache()->size()); + + client->RemoveAsClient(); + EXPECT_EQ(cached_resource->size(), GetMemoryCache()->size()); + + cached_resource->FakeEncodedSize(kResourceSize2); + EXPECT_EQ(cached_resource->size(), GetMemoryCache()->size()); +} + +static void RunTask1(Resource* resource1, Resource* resource2) { + // The resource size has to be nonzero for this test to be meaningful, but + // we do not rely on it having any particular value. + EXPECT_GT(resource1->size(), 0u); + EXPECT_GT(resource2->size(), 0u); + + EXPECT_EQ(0u, GetMemoryCache()->size()); + + GetMemoryCache()->Add(resource1); + GetMemoryCache()->Add(resource2); + + size_t total_size = resource1->size() + resource2->size(); + EXPECT_EQ(total_size, GetMemoryCache()->size()); + EXPECT_GT(resource1->DecodedSize(), 0u); + EXPECT_GT(resource2->DecodedSize(), 0u); + + // We expect actual pruning doesn't occur here synchronously but deferred + // to the end of this task, due to the previous pruning invoked in + // testResourcePruningAtEndOfTask(). + GetMemoryCache()->Prune(); + EXPECT_EQ(total_size, GetMemoryCache()->size()); + EXPECT_GT(resource1->DecodedSize(), 0u); + EXPECT_GT(resource2->DecodedSize(), 0u); +} + +static void RunTask2(unsigned size_without_decode) { + // Next task: now, the resources was pruned. + EXPECT_EQ(size_without_decode, GetMemoryCache()->size()); +} + +static void TestResourcePruningAtEndOfTask(ResourceFetcher* fetcher, + const String& identifier1, + const String& identifier2) { + GetMemoryCache()->SetDelayBeforeLiveDecodedPrune(0); + + // Enforce pruning by adding |dummyResource| and then call prune(). + Resource* dummy_resource = + RawResource::CreateForTest("http://dummy", Resource::kRaw); + GetMemoryCache()->Add(dummy_resource); + EXPECT_GT(GetMemoryCache()->size(), 1u); + const unsigned kTotalCapacity = 1; + GetMemoryCache()->SetCapacity(kTotalCapacity); + GetMemoryCache()->Prune(); + GetMemoryCache()->Remove(dummy_resource); + EXPECT_EQ(0u, GetMemoryCache()->size()); + + const char kData[6] = "abcde"; + FetchParameters params1(ResourceRequest("data:text/html,resource1")); + Resource* resource1 = FakeDecodedResource::Fetch(params1, fetcher, nullptr); + GetMemoryCache()->Remove(resource1); + if (!identifier1.IsEmpty()) + resource1->SetCacheIdentifier(identifier1); + resource1->AppendData(kData, 3u); + resource1->FinishForTest(); + FetchParameters params2(ResourceRequest("data:text/html,resource2")); + Persistent<MockResourceClient> client = new MockResourceClient; + Resource* resource2 = FakeDecodedResource::Fetch(params2, fetcher, client); + GetMemoryCache()->Remove(resource2); + if (!identifier2.IsEmpty()) + resource2->SetCacheIdentifier(identifier2); + resource2->AppendData(kData, 4u); + resource2->FinishForTest(); + + Platform::Current()->CurrentThread()->GetTaskRunner()->PostTask( + FROM_HERE, WTF::Bind(&RunTask1, WrapPersistent(resource1), + WrapPersistent(resource2))); + Platform::Current()->CurrentThread()->GetTaskRunner()->PostTask( + FROM_HERE, + WTF::Bind(&RunTask2, + resource1->EncodedSize() + resource1->OverheadSize() + + resource2->EncodedSize() + resource2->OverheadSize())); + static_cast<TestingPlatformSupportWithMockScheduler*>(Platform::Current()) + ->RunUntilIdle(); +} + +// Verified that when ordering a prune in a runLoop task, the prune +// is deferred to the end of the task. +TEST_F(MemoryCacheTest, ResourcePruningAtEndOfTask_Basic) { + TestResourcePruningAtEndOfTask(fetcher_, "", ""); +} + +TEST_F(MemoryCacheTest, ResourcePruningAtEndOfTask_MultipleResourceMaps) { + { + TestResourcePruningAtEndOfTask(fetcher_, "foo", ""); + GetMemoryCache()->EvictResources(); + } + { + TestResourcePruningAtEndOfTask(fetcher_, "foo", "bar"); + GetMemoryCache()->EvictResources(); + } +} + +// Verifies that +// - Resources are not pruned synchronously when ResourceClient is removed. +// - size() is updated appropriately when Resources are added to MemoryCache +// and garbage collected. +static void TestClientRemoval(ResourceFetcher* fetcher, + const String& identifier1, + const String& identifier2) { + GetMemoryCache()->SetCapacity(0); + const char kData[6] = "abcde"; + Persistent<MockResourceClient> client1 = new MockResourceClient; + Persistent<MockResourceClient> client2 = new MockResourceClient; + FetchParameters params1(ResourceRequest("data:text/html,foo")); + Resource* resource1 = FakeDecodedResource::Fetch(params1, fetcher, client1); + FetchParameters params2(ResourceRequest("data:text/html,bar")); + Resource* resource2 = FakeDecodedResource::Fetch(params2, fetcher, client2); + resource1->AppendData(kData, 4u); + resource2->AppendData(kData, 4u); + + GetMemoryCache()->SetCapacity(0); + // Remove and re-Add the resources, with proper cache identifiers. + GetMemoryCache()->Remove(resource1); + GetMemoryCache()->Remove(resource2); + if (!identifier1.IsEmpty()) + resource1->SetCacheIdentifier(identifier1); + if (!identifier2.IsEmpty()) + resource2->SetCacheIdentifier(identifier2); + GetMemoryCache()->Add(resource1); + GetMemoryCache()->Add(resource2); + + size_t original_total_size = resource1->size() + resource2->size(); + + // Call prune. There is nothing to prune, but this will initialize + // the prune timestamp, allowing future prunes to be deferred. + GetMemoryCache()->Prune(); + EXPECT_GT(resource1->DecodedSize(), 0u); + EXPECT_GT(resource2->DecodedSize(), 0u); + EXPECT_EQ(original_total_size, GetMemoryCache()->size()); + + // Removing the client from resource1 should not trigger pruning. + client1->RemoveAsClient(); + EXPECT_GT(resource1->DecodedSize(), 0u); + EXPECT_GT(resource2->DecodedSize(), 0u); + EXPECT_EQ(original_total_size, GetMemoryCache()->size()); + EXPECT_TRUE(GetMemoryCache()->Contains(resource1)); + EXPECT_TRUE(GetMemoryCache()->Contains(resource2)); + + // Removing the client from resource2 should not trigger pruning. + client2->RemoveAsClient(); + EXPECT_GT(resource1->DecodedSize(), 0u); + EXPECT_GT(resource2->DecodedSize(), 0u); + EXPECT_EQ(original_total_size, GetMemoryCache()->size()); + EXPECT_TRUE(GetMemoryCache()->Contains(resource1)); + EXPECT_TRUE(GetMemoryCache()->Contains(resource2)); + + WeakPersistent<Resource> resource1_weak = resource1; + WeakPersistent<Resource> resource2_weak = resource2; + + ThreadState::Current()->CollectGarbage( + BlinkGC::kNoHeapPointersOnStack, BlinkGC::kAtomicMarking, + BlinkGC::kEagerSweeping, BlinkGC::kForcedGC); + // Resources are garbage-collected (WeakMemoryCache) and thus removed + // from MemoryCache. + EXPECT_FALSE(resource1_weak); + EXPECT_FALSE(resource2_weak); + EXPECT_EQ(0u, GetMemoryCache()->size()); +} + +TEST_F(MemoryCacheTest, ClientRemoval_Basic) { + TestClientRemoval(fetcher_, "", ""); +} + +TEST_F(MemoryCacheTest, ClientRemoval_MultipleResourceMaps) { + { + TestClientRemoval(fetcher_, "foo", ""); + GetMemoryCache()->EvictResources(); + } + { + TestClientRemoval(fetcher_, "", "foo"); + GetMemoryCache()->EvictResources(); + } + { + TestClientRemoval(fetcher_, "foo", "bar"); + GetMemoryCache()->EvictResources(); + } +} + +TEST_F(MemoryCacheTest, RemoveDuringRevalidation) { + FakeResource* resource1 = + FakeResource::Create("http://test/resource", Resource::kRaw); + GetMemoryCache()->Add(resource1); + + FakeResource* resource2 = + FakeResource::Create("http://test/resource", Resource::kRaw); + GetMemoryCache()->Remove(resource1); + GetMemoryCache()->Add(resource2); + EXPECT_TRUE(GetMemoryCache()->Contains(resource2)); + EXPECT_FALSE(GetMemoryCache()->Contains(resource1)); + + FakeResource* resource3 = + FakeResource::Create("http://test/resource", Resource::kRaw); + GetMemoryCache()->Remove(resource2); + GetMemoryCache()->Add(resource3); + EXPECT_TRUE(GetMemoryCache()->Contains(resource3)); + EXPECT_FALSE(GetMemoryCache()->Contains(resource2)); +} + +TEST_F(MemoryCacheTest, ResourceMapIsolation) { + FakeResource* resource1 = + FakeResource::Create("http://test/resource", Resource::kRaw); + GetMemoryCache()->Add(resource1); + + FakeResource* resource2 = + FakeResource::Create("http://test/resource", Resource::kRaw); + resource2->SetCacheIdentifier("foo"); + GetMemoryCache()->Add(resource2); + EXPECT_TRUE(GetMemoryCache()->Contains(resource1)); + EXPECT_TRUE(GetMemoryCache()->Contains(resource2)); + + const KURL url = KURL("http://test/resource"); + EXPECT_EQ(resource1, GetMemoryCache()->ResourceForURL(url)); + EXPECT_EQ(resource1, GetMemoryCache()->ResourceForURL( + url, GetMemoryCache()->DefaultCacheIdentifier())); + EXPECT_EQ(resource2, GetMemoryCache()->ResourceForURL(url, "foo")); + EXPECT_EQ(nullptr, GetMemoryCache()->ResourceForURL(NullURL())); + + FakeResource* resource3 = + FakeResource::Create("http://test/resource", Resource::kRaw); + resource3->SetCacheIdentifier("foo"); + GetMemoryCache()->Remove(resource2); + GetMemoryCache()->Add(resource3); + EXPECT_TRUE(GetMemoryCache()->Contains(resource1)); + EXPECT_FALSE(GetMemoryCache()->Contains(resource2)); + EXPECT_TRUE(GetMemoryCache()->Contains(resource3)); + + HeapVector<Member<Resource>> resources = + GetMemoryCache()->ResourcesForURL(url); + EXPECT_EQ(2u, resources.size()); + + GetMemoryCache()->EvictResources(); + EXPECT_FALSE(GetMemoryCache()->Contains(resource1)); + EXPECT_FALSE(GetMemoryCache()->Contains(resource3)); +} + +TEST_F(MemoryCacheTest, FragmentIdentifier) { + const KURL url1 = KURL("http://test/resource#foo"); + FakeResource* resource = FakeResource::Create(url1, Resource::kRaw); + GetMemoryCache()->Add(resource); + EXPECT_TRUE(GetMemoryCache()->Contains(resource)); + + EXPECT_EQ(resource, GetMemoryCache()->ResourceForURL(url1)); + + const KURL url2 = MemoryCache::RemoveFragmentIdentifierIfNeeded(url1); + EXPECT_EQ(resource, GetMemoryCache()->ResourceForURL(url2)); +} + +TEST_F(MemoryCacheTest, RemoveURLFromCache) { + const KURL url1 = KURL("http://test/resource1"); + Persistent<FakeResource> resource1 = + FakeResource::Create(url1, Resource::kRaw); + GetMemoryCache()->Add(resource1); + EXPECT_TRUE(GetMemoryCache()->Contains(resource1)); + + GetMemoryCache()->RemoveURLFromCache(url1); + EXPECT_FALSE(GetMemoryCache()->Contains(resource1)); + + const KURL url2 = KURL("http://test/resource2#foo"); + FakeResource* resource2 = FakeResource::Create(url2, Resource::kRaw); + GetMemoryCache()->Add(resource2); + EXPECT_TRUE(GetMemoryCache()->Contains(resource2)); + + GetMemoryCache()->RemoveURLFromCache(url2); + EXPECT_FALSE(GetMemoryCache()->Contains(resource2)); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/platform/loader/fetch/preload_key.h b/chromium/third_party/blink/renderer/platform/loader/fetch/preload_key.h new file mode 100644 index 00000000000..81273b6808a --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/loader/fetch/preload_key.h @@ -0,0 +1,75 @@ +// 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_PLATFORM_LOADER_FETCH_PRELOAD_KEY_H_ +#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_LOADER_FETCH_PRELOAD_KEY_H_ + +#include "third_party/blink/renderer/platform/loader/fetch/resource.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_traits.h" + +namespace blink { + +// PreloadKey is a key type of the preloads map in a fetch group (a.k.a. +// blink::ResourceFetcher). +struct PreloadKey final { + public: + struct Hash { + STATIC_ONLY(Hash); + + public: + static unsigned GetHash(const PreloadKey& key) { + return KURLHash::GetHash(key.url); + } + static bool Equal(const PreloadKey& x, const PreloadKey& y) { + return x == y; + } + static constexpr bool safe_to_compare_to_empty_or_deleted = false; + }; + + PreloadKey() = default; + PreloadKey(const KURL& url, Resource::Type type) + : url(RemoveFragmentFromUrl(url)), type(type) {} + + bool operator==(const PreloadKey& x) const { + return url == x.url && type == x.type; + } + + static KURL RemoveFragmentFromUrl(const KURL& src) { + if (!src.HasFragmentIdentifier()) + return src; + KURL url = src; + url.RemoveFragmentIdentifier(); + return url; + } + + KURL url; + Resource::Type type = Resource::kMainResource; +}; + +} // namespace blink + +namespace WTF { + +template <> +struct DefaultHash<blink::PreloadKey> { + using Hash = blink::PreloadKey::Hash; +}; + +template <> +struct HashTraits<blink::PreloadKey> + : public SimpleClassHashTraits<blink::PreloadKey> { + static bool IsDeletedValue(const blink::PreloadKey& value) { + return HashTraits<blink::KURL>::IsDeletedValue(value.url); + } + + static void ConstructDeletedValue(blink::PreloadKey& slot, bool zero_value) { + HashTraits<blink::KURL>::ConstructDeletedValue(slot.url, zero_value); + } +}; + +} // namespace WTF + +#endif // THIRD_PARTY_BLINK_RENDERER_PLATFORM_LOADER_FETCH_PRELOAD_KEY_H_ diff --git a/chromium/third_party/blink/renderer/platform/loader/fetch/raw_resource.cc b/chromium/third_party/blink/renderer/platform/loader/fetch/raw_resource.cc new file mode 100644 index 00000000000..083f95fa3f8 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/loader/fetch/raw_resource.cc @@ -0,0 +1,470 @@ +/* + * 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 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 + * 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/platform/loader/fetch/raw_resource.h" + +#include <memory> +#include "mojo/public/cpp/system/data_pipe.h" +#include "services/network/public/mojom/request_context_frame_type.mojom-shared.h" +#include "third_party/blink/public/platform/platform.h" +#include "third_party/blink/public/platform/web_thread.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_walker.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h" +#include "third_party/blink/renderer/platform/loader/fetch/source_keyed_cached_metadata_handler.h" +#include "third_party/blink/renderer/platform/network/http_names.h" +#include "third_party/blink/renderer/platform/scheduler/child/web_scheduler.h" + +namespace blink { + +RawResource* RawResource::FetchSynchronously(FetchParameters& params, + ResourceFetcher* fetcher) { + params.MakeSynchronous(); + return ToRawResource(fetcher->RequestResource( + params, RawResourceFactory(Resource::kRaw), nullptr)); +} + +RawResource* RawResource::FetchImport(FetchParameters& params, + ResourceFetcher* fetcher, + RawResourceClient* client) { + DCHECK_EQ(params.GetResourceRequest().GetFrameType(), + network::mojom::RequestContextFrameType::kNone); + params.SetRequestContext(WebURLRequest::kRequestContextImport); + return ToRawResource(fetcher->RequestResource( + params, RawResourceFactory(Resource::kImportResource), client)); +} + +RawResource* RawResource::Fetch(FetchParameters& params, + ResourceFetcher* fetcher, + RawResourceClient* client) { + DCHECK_EQ(params.GetResourceRequest().GetFrameType(), + network::mojom::RequestContextFrameType::kNone); + DCHECK_NE(params.GetResourceRequest().GetRequestContext(), + WebURLRequest::kRequestContextUnspecified); + return ToRawResource(fetcher->RequestResource( + params, RawResourceFactory(Resource::kRaw), client)); +} + +RawResource* RawResource::FetchMainResource( + FetchParameters& params, + ResourceFetcher* fetcher, + RawResourceClient* client, + const SubstituteData& substitute_data) { + DCHECK_NE(params.GetResourceRequest().GetFrameType(), + network::mojom::RequestContextFrameType::kNone); + DCHECK(params.GetResourceRequest().GetRequestContext() == + WebURLRequest::kRequestContextForm || + params.GetResourceRequest().GetRequestContext() == + WebURLRequest::kRequestContextFrame || + params.GetResourceRequest().GetRequestContext() == + WebURLRequest::kRequestContextHyperlink || + params.GetResourceRequest().GetRequestContext() == + WebURLRequest::kRequestContextIframe || + params.GetResourceRequest().GetRequestContext() == + WebURLRequest::kRequestContextInternal || + params.GetResourceRequest().GetRequestContext() == + WebURLRequest::kRequestContextLocation); + + return ToRawResource(fetcher->RequestResource( + params, RawResourceFactory(Resource::kMainResource), client, + substitute_data)); +} + +RawResource* RawResource::FetchMedia(FetchParameters& params, + ResourceFetcher* fetcher, + RawResourceClient* client) { + DCHECK_EQ(params.GetResourceRequest().GetFrameType(), + network::mojom::RequestContextFrameType::kNone); + auto context = params.GetResourceRequest().GetRequestContext(); + DCHECK(context == WebURLRequest::kRequestContextAudio || + context == WebURLRequest::kRequestContextVideo); + Resource::Type type = (context == WebURLRequest::kRequestContextAudio) + ? Resource::kAudio + : Resource::kVideo; + return ToRawResource( + fetcher->RequestResource(params, RawResourceFactory(type), client)); +} + +RawResource* RawResource::FetchTextTrack(FetchParameters& params, + ResourceFetcher* fetcher, + RawResourceClient* client) { + DCHECK_EQ(params.GetResourceRequest().GetFrameType(), + network::mojom::RequestContextFrameType::kNone); + params.SetRequestContext(WebURLRequest::kRequestContextTrack); + return ToRawResource(fetcher->RequestResource( + params, RawResourceFactory(Resource::kTextTrack), client)); +} + +RawResource* RawResource::FetchManifest(FetchParameters& params, + ResourceFetcher* fetcher, + RawResourceClient* client) { + DCHECK_EQ(params.GetResourceRequest().GetFrameType(), + network::mojom::RequestContextFrameType::kNone); + DCHECK_EQ(params.GetResourceRequest().GetRequestContext(), + WebURLRequest::kRequestContextManifest); + return ToRawResource(fetcher->RequestResource( + params, RawResourceFactory(Resource::kManifest), client)); +} + +RawResource::RawResource(const ResourceRequest& resource_request, + Type type, + const ResourceLoaderOptions& options) + : Resource(resource_request, type, options) {} + +void RawResource::AppendData(const char* data, size_t length) { + if (data_pipe_writer_) { + DCHECK_EQ(kDoNotBufferData, GetDataBufferingPolicy()); + data_pipe_writer_->Write(data, length); + } else { + Resource::AppendData(data, length); + } +} + +void RawResource::DidAddClient(ResourceClient* c) { + // CHECK()/RevalidationStartForbiddenScope are for + // https://crbug.com/640960#c24. + CHECK(!IsCacheValidator()); + if (!HasClient(c)) + return; + DCHECK(RawResourceClient::IsExpectedType(c)); + RevalidationStartForbiddenScope revalidation_start_forbidden_scope(this); + RawResourceClient* client = static_cast<RawResourceClient*>(c); + for (const auto& redirect : RedirectChain()) { + ResourceRequest request(redirect.request_); + client->RedirectReceived(this, request, redirect.redirect_response_); + if (!HasClient(c)) + return; + } + + if (!GetResponse().IsNull()) { + client->ResponseReceived(this, GetResponse(), + std::move(data_consumer_handle_)); + } + if (!HasClient(c)) + return; + Resource::DidAddClient(client); +} + +bool RawResource::WillFollowRedirect( + const ResourceRequest& new_request, + const ResourceResponse& redirect_response) { + bool follow = Resource::WillFollowRedirect(new_request, redirect_response); + // The base class method takes a const reference of a ResourceRequest and + // returns bool just for allowing RawResource to reject redirect. It must + // always return true. + DCHECK(follow); + + DCHECK(!redirect_response.IsNull()); + ResourceClientWalker<RawResourceClient> w(Clients()); + while (RawResourceClient* c = w.Next()) { + if (!c->RedirectReceived(this, new_request, redirect_response)) + follow = false; + } + + return follow; +} + +void RawResource::WillNotFollowRedirect() { + ResourceClientWalker<RawResourceClient> w(Clients()); + while (RawResourceClient* c = w.Next()) + c->RedirectBlocked(); +} + +SourceKeyedCachedMetadataHandler* RawResource::CacheHandler() { + return static_cast<SourceKeyedCachedMetadataHandler*>( + Resource::CacheHandler()); +} + +void RawResource::ResponseReceived( + const ResourceResponse& response, + std::unique_ptr<WebDataConsumerHandle> handle) { + if (response.WasFallbackRequiredByServiceWorker()) { + // The ServiceWorker asked us to re-fetch the request. This resource must + // not be reused. + // Note: This logic is needed here because DocumentThreadableLoader handles + // CORS independently from ResourceLoader. Fix it. + if (IsMainThread()) + GetMemoryCache()->Remove(this); + } + + Resource::ResponseReceived(response, nullptr); + + DCHECK(!handle || !data_consumer_handle_); + if (!handle && Clients().size() > 0) + handle = std::move(data_consumer_handle_); + ResourceClientWalker<RawResourceClient> w(Clients()); + DCHECK(Clients().size() <= 1 || !handle); + while (RawResourceClient* c = w.Next()) { + // |handle| is cleared when passed, but it's not a problem because |handle| + // is null when there are two or more clients, as asserted. + c->ResponseReceived(this, this->GetResponse(), std::move(handle)); + } +} + +CachedMetadataHandler* RawResource::CreateCachedMetadataHandler( + std::unique_ptr<CachedMetadataSender> send_callback) { + return new SourceKeyedCachedMetadataHandler(Encoding(), + std::move(send_callback)); +} + +void RawResource::SetSerializedCachedMetadata(const char* data, size_t size) { + Resource::SetSerializedCachedMetadata(data, size); + + SourceKeyedCachedMetadataHandler* cache_handler = CacheHandler(); + if (cache_handler) { + cache_handler->SetSerializedCachedMetadata(data, size); + } + + ResourceClientWalker<RawResourceClient> w(Clients()); + while (RawResourceClient* c = w.Next()) + c->SetSerializedCachedMetadata(this, data, size); +} + +void RawResource::DidSendData(unsigned long long bytes_sent, + unsigned long long total_bytes_to_be_sent) { + ResourceClientWalker<RawResourceClient> w(Clients()); + while (RawResourceClient* c = w.Next()) + c->DataSent(this, bytes_sent, total_bytes_to_be_sent); +} + +void RawResource::DidDownloadData(int data_length) { + downloaded_file_length_ = + (downloaded_file_length_ ? *downloaded_file_length_ : 0) + data_length; + ResourceClientWalker<RawResourceClient> w(Clients()); + while (RawResourceClient* c = w.Next()) + c->DataDownloaded(this, data_length); +} + +void RawResource::DidDownloadToBlob(scoped_refptr<BlobDataHandle> blob) { + downloaded_blob_ = blob; + ResourceClientWalker<RawResourceClient> w(Clients()); + while (RawResourceClient* c = w.Next()) + c->DidDownloadToBlob(this, blob); +} + +void RawResource::ReportResourceTimingToClients( + const ResourceTimingInfo& info) { + ResourceClientWalker<RawResourceClient> w(Clients()); + while (RawResourceClient* c = w.Next()) + c->DidReceiveResourceTiming(this, info); +} + +bool RawResource::MatchPreload(const FetchParameters& params, + base::SingleThreadTaskRunner* task_runner) { + if (!Resource::MatchPreload(params, task_runner)) + return false; + + // This is needed to call Platform::Current() below. Remove this branch + // when the calls are removed. + if (!IsMainThread()) + return false; + + if (!params.GetResourceRequest().UseStreamOnResponse()) + return true; + + if (ErrorOccurred()) + return true; + + // A preloaded resource is not for streaming. + DCHECK(!GetResourceRequest().UseStreamOnResponse()); + DCHECK_EQ(GetDataBufferingPolicy(), kBufferData); + + // Preloading for raw resources are not cached. + DCHECK(!IsMainThread() || !GetMemoryCache()->Contains(this)); + + constexpr auto kCapacity = 32 * 1024; + mojo::ScopedDataPipeProducerHandle producer; + mojo::ScopedDataPipeConsumerHandle consumer; + MojoCreateDataPipeOptions options; + options.struct_size = sizeof(MojoCreateDataPipeOptions); + options.flags = MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE; + options.element_num_bytes = 1; + options.capacity_num_bytes = kCapacity; + + MojoResult result = mojo::CreateDataPipe(&options, &producer, &consumer); + if (result != MOJO_RESULT_OK) + return false; + + data_consumer_handle_ = + Platform::Current()->CreateDataConsumerHandle(std::move(consumer)); + data_pipe_writer_ = std::make_unique<BufferingDataPipeWriter>( + std::move(producer), task_runner); + + if (Data()) { + Data()->ForEachSegment( + [this](const char* segment, size_t size, size_t offset) -> bool { + return data_pipe_writer_->Write(segment, size); + }); + } + SetDataBufferingPolicy(kDoNotBufferData); + + if (IsLoaded()) + data_pipe_writer_->Finish(); + return true; +} + +void RawResource::NotifyFinished() { + if (data_pipe_writer_) + data_pipe_writer_->Finish(); + Resource::NotifyFinished(); +} + +static bool ShouldIgnoreHeaderForCacheReuse(AtomicString header_name) { + // FIXME: This list of headers that don't affect cache policy almost certainly + // isn't complete. + DEFINE_STATIC_LOCAL( + HashSet<AtomicString>, headers, + ({"Cache-Control", "If-Modified-Since", "If-None-Match", "Origin", + "Pragma", "Purpose", "Referer", "User-Agent", + HTTPNames::X_DevTools_Emulate_Network_Conditions_Client_Id})); + return headers.Contains(header_name); +} + +static bool IsCacheableHTTPMethod(const AtomicString& method) { + // Per http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.10, + // these methods always invalidate the cache entry. + return method != HTTPNames::POST && method != HTTPNames::PUT && + method != "DELETE"; +} + +bool RawResource::CanReuse( + const FetchParameters& new_fetch_parameters, + scoped_refptr<const SecurityOrigin> new_source_origin) const { + const ResourceRequest& new_request = + new_fetch_parameters.GetResourceRequest(); + + if (GetDataBufferingPolicy() == kDoNotBufferData) + return false; + + if (!IsCacheableHTTPMethod(GetResourceRequest().HttpMethod())) + return false; + if (GetResourceRequest().HttpMethod() != new_request.HttpMethod()) + return false; + + if (GetResourceRequest().HttpBody() != new_request.HttpBody()) + return false; + + if (GetResourceRequest().AllowStoredCredentials() != + new_request.AllowStoredCredentials()) + return false; + + // Ensure most headers match the existing headers before continuing. Note that + // the list of ignored headers includes some headers explicitly related to + // caching. A more detailed check of caching policy will be performed later, + // this is simply a list of headers that we might permit to be different and + // still reuse the existing Resource. + const HTTPHeaderMap& new_headers = new_request.HttpHeaderFields(); + const HTTPHeaderMap& old_headers = GetResourceRequest().HttpHeaderFields(); + + for (const auto& header : new_headers) { + AtomicString header_name = header.key; + if (!ShouldIgnoreHeaderForCacheReuse(header_name) && + header.value != old_headers.Get(header_name)) + return false; + } + + for (const auto& header : old_headers) { + AtomicString header_name = header.key; + if (!ShouldIgnoreHeaderForCacheReuse(header_name) && + header.value != new_headers.Get(header_name)) + return false; + } + + return Resource::CanReuse(new_fetch_parameters, std::move(new_source_origin)); +} + +RawResourceClientStateChecker::RawResourceClientStateChecker() + : state_(kNotAddedAsClient) {} + +RawResourceClientStateChecker::~RawResourceClientStateChecker() = default; + +NEVER_INLINE void RawResourceClientStateChecker::WillAddClient() { + SECURITY_CHECK(state_ == kNotAddedAsClient); + state_ = kStarted; +} + +NEVER_INLINE void RawResourceClientStateChecker::WillRemoveClient() { + SECURITY_CHECK(state_ != kNotAddedAsClient); + state_ = kNotAddedAsClient; +} + +NEVER_INLINE void RawResourceClientStateChecker::RedirectReceived() { + SECURITY_CHECK(state_ == kStarted); +} + +NEVER_INLINE void RawResourceClientStateChecker::RedirectBlocked() { + SECURITY_CHECK(state_ == kStarted); + state_ = kRedirectBlocked; +} + +NEVER_INLINE void RawResourceClientStateChecker::DataSent() { + SECURITY_CHECK(state_ == kStarted); +} + +NEVER_INLINE void RawResourceClientStateChecker::ResponseReceived() { + SECURITY_CHECK(state_ == kStarted); + state_ = kResponseReceived; +} + +NEVER_INLINE void RawResourceClientStateChecker::SetSerializedCachedMetadata() { + SECURITY_CHECK(state_ == kResponseReceived); + state_ = kSetSerializedCachedMetadata; +} + +NEVER_INLINE void RawResourceClientStateChecker::DataReceived() { + SECURITY_CHECK(state_ == kResponseReceived || + state_ == kSetSerializedCachedMetadata || + state_ == kDataReceived); + state_ = kDataReceived; +} + +NEVER_INLINE void RawResourceClientStateChecker::DataDownloaded() { + SECURITY_CHECK(state_ == kResponseReceived || + state_ == kSetSerializedCachedMetadata || + state_ == kDataDownloaded); + state_ = kDataDownloaded; +} + +NEVER_INLINE void RawResourceClientStateChecker::DidDownloadToBlob() { + SECURITY_CHECK(state_ == kResponseReceived || + state_ == kSetSerializedCachedMetadata || + state_ == kDataDownloaded); + state_ = kDidDownloadToBlob; +} + +NEVER_INLINE void RawResourceClientStateChecker::NotifyFinished( + Resource* resource) { + SECURITY_CHECK(state_ != kNotAddedAsClient); + SECURITY_CHECK(state_ != kNotifyFinished); + SECURITY_CHECK(resource->ErrorOccurred() || + (state_ == kResponseReceived || + state_ == kSetSerializedCachedMetadata || + state_ == kDataReceived || state_ == kDataDownloaded || + state_ == kDidDownloadToBlob)); + state_ = kNotifyFinished; +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/platform/loader/fetch/raw_resource.h b/chromium/third_party/blink/renderer/platform/loader/fetch/raw_resource.h new file mode 100644 index 00000000000..88c232ccea0 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/loader/fetch/raw_resource.h @@ -0,0 +1,247 @@ +/* + 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_PLATFORM_LOADER_FETCH_RAW_RESOURCE_H_ +#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_LOADER_FETCH_RAW_RESOURCE_H_ + +#include <memory> + +#include "third_party/blink/public/platform/web_data_consumer_handle.h" +#include "third_party/blink/renderer/platform/loader/fetch/buffering_data_pipe_writer.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/resource_loader_options.h" +#include "third_party/blink/renderer/platform/platform_export.h" +#include "third_party/blink/renderer/platform/wtf/optional.h" + +namespace blink { +class WebDataConsumerHandle; +class FetchParameters; +class RawResourceClient; +class ResourceFetcher; +class SubstituteData; +class SourceKeyedCachedMetadataHandler; + +class PLATFORM_EXPORT RawResource final : public Resource { + public: + static RawResource* FetchSynchronously(FetchParameters&, ResourceFetcher*); + static RawResource* Fetch(FetchParameters&, + ResourceFetcher*, + RawResourceClient*); + static RawResource* FetchMainResource(FetchParameters&, + ResourceFetcher*, + RawResourceClient*, + const SubstituteData&); + static RawResource* FetchImport(FetchParameters&, + ResourceFetcher*, + RawResourceClient*); + static RawResource* FetchMedia(FetchParameters&, + ResourceFetcher*, + RawResourceClient*); + static RawResource* FetchTextTrack(FetchParameters&, + ResourceFetcher*, + RawResourceClient*); + static RawResource* FetchManifest(FetchParameters&, + ResourceFetcher*, + RawResourceClient*); + + // Exposed for testing + static RawResource* CreateForTest(ResourceRequest request, Type type) { + ResourceLoaderOptions options; + return new RawResource(request, type, options); + } + static RawResource* CreateForTest(const KURL& url, Type type) { + ResourceRequest request(url); + return CreateForTest(request, type); + } + static RawResource* CreateForTest(const char* url, Type type) { + return CreateForTest(KURL(url), type); + } + + // Resource implementation + bool CanReuse( + const FetchParameters&, + scoped_refptr<const SecurityOrigin> new_source_origin) const override; + bool WillFollowRedirect(const ResourceRequest&, + const ResourceResponse&) override; + + void SetSerializedCachedMetadata(const char*, size_t) override; + + // Used for code caching of scripts with source code inline in the HTML. + // Returns a cache handler which can store multiple cache metadata entries, + // keyed by the source code of the script. + SourceKeyedCachedMetadataHandler* CacheHandler(); + + WTF::Optional<int64_t> DownloadedFileLength() const { + return downloaded_file_length_; + } + scoped_refptr<BlobDataHandle> DownloadedBlob() const { + return downloaded_blob_; + } + + protected: + CachedMetadataHandler* CreateCachedMetadataHandler( + std::unique_ptr<CachedMetadataSender> send_callback) override; + + private: + class RawResourceFactory : public NonTextResourceFactory { + public: + explicit RawResourceFactory(Resource::Type type) + : NonTextResourceFactory(type) {} + + Resource* Create(const ResourceRequest& request, + const ResourceLoaderOptions& options) const override { + return new RawResource(request, type_, options); + } + }; + + RawResource(const ResourceRequest&, Type, const ResourceLoaderOptions&); + + // Resource implementation + void DidAddClient(ResourceClient*) override; + void AppendData(const char*, size_t) override; + bool ShouldIgnoreHTTPStatusCodeErrors() const override { + return !IsLinkPreload(); + } + void WillNotFollowRedirect() override; + void ResponseReceived(const ResourceResponse&, + std::unique_ptr<WebDataConsumerHandle>) override; + void DidSendData(unsigned long long bytes_sent, + unsigned long long total_bytes_to_be_sent) override; + void DidDownloadData(int) override; + void DidDownloadToBlob(scoped_refptr<BlobDataHandle>) override; + void ReportResourceTimingToClients(const ResourceTimingInfo&) override; + bool MatchPreload(const FetchParameters&, + base::SingleThreadTaskRunner*) override; + void NotifyFinished() override; + + WTF::Optional<int64_t> downloaded_file_length_; + scoped_refptr<BlobDataHandle> downloaded_blob_; + + // Used for preload matching. + std::unique_ptr<BufferingDataPipeWriter> data_pipe_writer_; + std::unique_ptr<WebDataConsumerHandle> data_consumer_handle_; +}; + +// TODO(yhirano): Recover #if ENABLE_SECURITY_ASSERT when we stop adding +// RawResources to MemoryCache. +inline bool IsRawResource(const Resource& resource) { + Resource::Type type = resource.GetType(); + return type == Resource::kMainResource || type == Resource::kRaw || + type == Resource::kTextTrack || type == Resource::kAudio || + type == Resource::kVideo || type == Resource::kManifest || + type == Resource::kImportResource; +} +inline RawResource* ToRawResource(Resource* resource) { + SECURITY_DCHECK(!resource || IsRawResource(*resource)); + return static_cast<RawResource*>(resource); +} + +class PLATFORM_EXPORT RawResourceClient : public ResourceClient { + public: + static bool IsExpectedType(ResourceClient* client) { + return client->GetResourceClientType() == kRawResourceType; + } + ResourceClientType GetResourceClientType() const final { + return kRawResourceType; + } + + // The order of the callbacks is as follows: + // [Case 1] A successful load: + // 0+ redirectReceived() and/or dataSent() + // 1 responseReceived() + // 0-1 setSerializedCachedMetadata() + // 0+ dataReceived() or dataDownloaded(), but never both + // 1 notifyFinished() with errorOccurred() = false + // [Case 2] When redirect is blocked: + // 0+ redirectReceived() and/or dataSent() + // 1 redirectBlocked() + // 1 notifyFinished() with errorOccurred() = true + // [Case 3] Other failures: + // notifyFinished() with errorOccurred() = true is called at any time + // (unless notifyFinished() is already called). + // In all cases: + // No callbacks are made after notifyFinished() or + // removeClient() is called. + virtual void DataSent(Resource*, + unsigned long long /* bytesSent */, + unsigned long long /* totalBytesToBeSent */) {} + virtual void ResponseReceived(Resource*, + const ResourceResponse&, + std::unique_ptr<WebDataConsumerHandle>) {} + virtual void SetSerializedCachedMetadata(Resource*, const char*, size_t) {} + virtual bool RedirectReceived(Resource*, + const ResourceRequest&, + const ResourceResponse&) { + return true; + } + virtual void RedirectBlocked() {} + virtual void DataDownloaded(Resource*, int) {} + virtual void DidReceiveResourceTiming(Resource*, const ResourceTimingInfo&) {} + // 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(Resource*, scoped_refptr<BlobDataHandle>) {} +}; + +// Checks the sequence of callbacks of RawResourceClient. This can be used only +// when a RawResourceClient is added as a client to at most one RawResource. +class PLATFORM_EXPORT RawResourceClientStateChecker final { + public: + RawResourceClientStateChecker(); + ~RawResourceClientStateChecker(); + + // Call before addClient()/removeClient() is called. + void WillAddClient(); + void WillRemoveClient(); + + // Call RawResourceClientStateChecker::f() at the beginning of + // RawResourceClient::f(). + void RedirectReceived(); + void RedirectBlocked(); + void DataSent(); + void ResponseReceived(); + void SetSerializedCachedMetadata(); + void DataReceived(); + void DataDownloaded(); + void DidDownloadToBlob(); + void NotifyFinished(Resource*); + + private: + enum State { + kNotAddedAsClient, + kStarted, + kRedirectBlocked, + kResponseReceived, + kSetSerializedCachedMetadata, + kDataReceived, + kDataDownloaded, + kDidDownloadToBlob, + kNotifyFinished + }; + State state_; +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_PLATFORM_LOADER_FETCH_RAW_RESOURCE_H_ diff --git a/chromium/third_party/blink/renderer/platform/loader/fetch/raw_resource_test.cc b/chromium/third_party/blink/renderer/platform/loader/fetch/raw_resource_test.cc new file mode 100644 index 00000000000..3a973b9476e --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/loader/fetch/raw_resource_test.cc @@ -0,0 +1,230 @@ +/* + * 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/platform/loader/fetch/raw_resource.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_thread.h" +#include "third_party/blink/public/platform/web_url.h" +#include "third_party/blink/public/platform/web_url_response.h" +#include "third_party/blink/renderer/platform/heap/handle.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_timing_info.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/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/weborigin/security_origin.h" + +namespace blink { + +class RawResourceTest : public testing::Test { + public: + RawResourceTest() = default; + ~RawResourceTest() override = default; + + protected: + ScopedTestingPlatformSupport<TestingPlatformSupportWithMockScheduler> + platform_; + + private: + DISALLOW_COPY_AND_ASSIGN(RawResourceTest); +}; + +TEST_F(RawResourceTest, DontIgnoreAcceptForCacheReuse) { + ResourceRequest jpeg_request; + jpeg_request.SetHTTPAccept("image/jpeg"); + + scoped_refptr<const SecurityOrigin> source_origin = + SecurityOrigin::CreateUnique(); + + RawResource* jpeg_resource( + RawResource::CreateForTest(jpeg_request, Resource::kRaw)); + jpeg_resource->SetSourceOrigin(source_origin); + + ResourceRequest png_request; + png_request.SetHTTPAccept("image/png"); + + EXPECT_FALSE( + jpeg_resource->CanReuse(FetchParameters(png_request), source_origin)); +} + +class DummyClient final : public GarbageCollectedFinalized<DummyClient>, + public RawResourceClient { + USING_GARBAGE_COLLECTED_MIXIN(DummyClient); + + public: + DummyClient() : called_(false), number_of_redirects_received_(0) {} + ~DummyClient() override = default; + + // ResourceClient implementation. + void NotifyFinished(Resource* resource) override { called_ = true; } + String DebugName() const override { return "DummyClient"; } + + void DataReceived(Resource*, const char* data, size_t length) override { + data_.Append(data, length); + } + + bool RedirectReceived(Resource*, + const ResourceRequest&, + const ResourceResponse&) override { + ++number_of_redirects_received_; + return true; + } + + bool Called() { return called_; } + int NumberOfRedirectsReceived() const { + return number_of_redirects_received_; + } + const Vector<char>& Data() { return data_; } + void Trace(blink::Visitor* visitor) override { + RawResourceClient::Trace(visitor); + } + + private: + bool called_; + int number_of_redirects_received_; + Vector<char> data_; +}; + +// This client adds another client when notified. +class AddingClient final : public GarbageCollectedFinalized<AddingClient>, + public RawResourceClient { + USING_GARBAGE_COLLECTED_MIXIN(AddingClient); + + public: + AddingClient(DummyClient* client, Resource* resource) + : dummy_client_(client), resource_(resource) {} + + ~AddingClient() override = default; + + // ResourceClient implementation. + void NotifyFinished(Resource* resource) override { + // First schedule an asynchronous task to remove the client. + // We do not expect a client to be called if the client is removed before + // a callback invocation task queued inside addClient() is scheduled. + Platform::Current()->CurrentThread()->GetTaskRunner()->PostTask( + FROM_HERE, + WTF::Bind(&AddingClient::RemoveClient, WrapPersistent(this))); + resource->AddClient( + dummy_client_, + Platform::Current()->CurrentThread()->GetTaskRunner().get()); + } + String DebugName() const override { return "AddingClient"; } + + void RemoveClient() { resource_->RemoveClient(dummy_client_); } + + void Trace(blink::Visitor* visitor) override { + visitor->Trace(dummy_client_); + visitor->Trace(resource_); + RawResourceClient::Trace(visitor); + } + + private: + Member<DummyClient> dummy_client_; + Member<Resource> resource_; +}; + +TEST_F(RawResourceTest, AddClientDuringCallback) { + Resource* raw = RawResource::CreateForTest("data:text/html,", Resource::kRaw); + raw->SetResponse(ResourceResponse(KURL("http://600.613/"))); + raw->FinishForTest(); + EXPECT_FALSE(raw->GetResponse().IsNull()); + + Persistent<DummyClient> dummy_client = new DummyClient(); + Persistent<AddingClient> adding_client = + new AddingClient(dummy_client.Get(), raw); + raw->AddClient(adding_client, + Platform::Current()->CurrentThread()->GetTaskRunner().get()); + platform_->RunUntilIdle(); + raw->RemoveClient(adding_client); + EXPECT_FALSE(dummy_client->Called()); + EXPECT_FALSE(raw->IsAlive()); +} + +// This client removes another client when notified. +class RemovingClient : public GarbageCollectedFinalized<RemovingClient>, + public RawResourceClient { + USING_GARBAGE_COLLECTED_MIXIN(RemovingClient); + + public: + explicit RemovingClient(DummyClient* client) : dummy_client_(client) {} + + ~RemovingClient() override = default; + + // ResourceClient implementation. + void NotifyFinished(Resource* resource) override { + resource->RemoveClient(dummy_client_); + resource->RemoveClient(this); + } + String DebugName() const override { return "RemovingClient"; } + void Trace(blink::Visitor* visitor) override { + visitor->Trace(dummy_client_); + RawResourceClient::Trace(visitor); + } + + private: + Member<DummyClient> dummy_client_; +}; + +TEST_F(RawResourceTest, RemoveClientDuringCallback) { + Resource* raw = RawResource::CreateForTest("data:text/html,", Resource::kRaw); + raw->SetResponse(ResourceResponse(KURL("http://600.613/"))); + raw->FinishForTest(); + EXPECT_FALSE(raw->GetResponse().IsNull()); + + Persistent<DummyClient> dummy_client = new DummyClient(); + Persistent<RemovingClient> removing_client = + new RemovingClient(dummy_client.Get()); + raw->AddClient(dummy_client, + Platform::Current()->CurrentThread()->GetTaskRunner().get()); + raw->AddClient(removing_client, + Platform::Current()->CurrentThread()->GetTaskRunner().get()); + platform_->RunUntilIdle(); + EXPECT_FALSE(raw->IsAlive()); +} + +TEST_F(RawResourceTest, + CanReuseDevToolsEmulateNetworkConditionsClientIdHeader) { + scoped_refptr<const SecurityOrigin> source_origin = + SecurityOrigin::CreateUnique(); + ResourceRequest request("data:text/html,"); + request.SetHTTPHeaderField( + HTTPNames::X_DevTools_Emulate_Network_Conditions_Client_Id, "Foo"); + Resource* raw = RawResource::CreateForTest(request, Resource::kRaw); + raw->SetSourceOrigin(source_origin); + EXPECT_TRUE(raw->CanReuse(FetchParameters(ResourceRequest("data:text/html,")), + source_origin)); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/platform/loader/fetch/resource.cc b/chromium/third_party/blink/renderer/platform/loader/fetch/resource.cc new file mode 100644 index 00000000000..13a3f06f3d0 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/loader/fetch/resource.cc @@ -0,0 +1,1207 @@ +/* + 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, 2009, 2010, 2011 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/platform/loader/fetch/resource.h" + +#include <stdint.h> + +#include <algorithm> +#include <cassert> +#include <memory> + +#include "base/single_thread_task_runner.h" +#include "build/build_config.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/web_security_origin.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/cors/cors.h" +#include "third_party/blink/renderer/platform/loader/fetch/cached_metadata.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/integrity_metadata.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_client_walker.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/network/http_parsers.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/weborigin/kurl.h" +#include "third_party/blink/renderer/platform/wtf/math_extras.h" +#include "third_party/blink/renderer/platform/wtf/std_lib_extras.h" +#include "third_party/blink/renderer/platform/wtf/text/cstring.h" +#include "third_party/blink/renderer/platform/wtf/text/string_builder.h" +#include "third_party/blink/renderer/platform/wtf/time.h" +#include "third_party/blink/renderer/platform/wtf/vector.h" + +namespace blink { + +namespace { + +void NotifyFinishObservers( + HeapHashSet<WeakMember<ResourceFinishObserver>>* observers) { + for (const auto& observer : *observers) + observer->NotifyFinished(); +} + +} // namespace + +// These response headers are not copied from a revalidated response to the +// cached response headers. For compatibility, this list is based on Chromium's +// net/http/http_response_headers.cc. +const char* const kHeadersToIgnoreAfterRevalidation[] = { + "allow", + "connection", + "etag", + "expires", + "keep-alive", + "last-modified", + "proxy-authenticate", + "proxy-connection", + "trailer", + "transfer-encoding", + "upgrade", + "www-authenticate", + "x-frame-options", + "x-xss-protection", +}; + +// Some header prefixes mean "Don't copy this header from a 304 response.". +// Rather than listing all the relevant headers, we can consolidate them into +// this list, also grabbed from Chromium's net/http/http_response_headers.cc. +const char* const kHeaderPrefixesToIgnoreAfterRevalidation[] = { + "content-", "x-content-", "x-webkit-"}; + +static inline bool ShouldUpdateHeaderAfterRevalidation( + const AtomicString& header) { + for (size_t i = 0; i < WTF_ARRAY_LENGTH(kHeadersToIgnoreAfterRevalidation); + i++) { + if (DeprecatedEqualIgnoringCase(header, + kHeadersToIgnoreAfterRevalidation[i])) + return false; + } + for (size_t i = 0; + i < WTF_ARRAY_LENGTH(kHeaderPrefixesToIgnoreAfterRevalidation); i++) { + if (header.StartsWithIgnoringASCIICase( + kHeaderPrefixesToIgnoreAfterRevalidation[i])) + return false; + } + return true; +} + +// This is a CachedMetadataSender implementation for normal responses. +class CachedMetadataSenderImpl : public CachedMetadataSender { + public: + CachedMetadataSenderImpl(const Resource*); + ~CachedMetadataSenderImpl() override = default; + + void Send(const char*, size_t) override; + bool IsServedFromCacheStorage() override { return false; } + + private: + const KURL response_url_; + const Time response_time_; +}; + +CachedMetadataSenderImpl::CachedMetadataSenderImpl(const Resource* resource) + : response_url_(resource->GetResponse().Url()), + response_time_(resource->GetResponse().ResponseTime()) { + DCHECK(resource->GetResponse().CacheStorageCacheName().IsNull()); +} + +void CachedMetadataSenderImpl::Send(const char* data, size_t size) { + Platform::Current()->CacheMetadata(response_url_, response_time_, data, size); +} + +// This is a CachedMetadataSender implementation that does nothing. +class NullCachedMetadataSender : public CachedMetadataSender { + public: + NullCachedMetadataSender() = default; + ~NullCachedMetadataSender() override = default; + + void Send(const char*, size_t) override {} + bool IsServedFromCacheStorage() override { return false; } +}; + +// This is a CachedMetadataSender implementation for responses that are served +// by a ServiceWorker from cache storage. +class ServiceWorkerCachedMetadataSender : public CachedMetadataSender { + public: + ServiceWorkerCachedMetadataSender(const Resource*, const SecurityOrigin*); + ~ServiceWorkerCachedMetadataSender() override = default; + + void Send(const char*, size_t) override; + bool IsServedFromCacheStorage() override { return true; } + + private: + const KURL response_url_; + const Time response_time_; + const String cache_storage_cache_name_; + scoped_refptr<const SecurityOrigin> security_origin_; +}; + +ServiceWorkerCachedMetadataSender::ServiceWorkerCachedMetadataSender( + const Resource* resource, + const SecurityOrigin* security_origin) + : response_url_(resource->GetResponse().Url()), + response_time_(resource->GetResponse().ResponseTime()), + cache_storage_cache_name_( + resource->GetResponse().CacheStorageCacheName()), + security_origin_(security_origin) { + DCHECK(!cache_storage_cache_name_.IsNull()); +} + +void ServiceWorkerCachedMetadataSender::Send(const char* data, size_t size) { + Platform::Current()->CacheMetadataInCacheStorage( + response_url_, response_time_, data, size, + WebSecurityOrigin(security_origin_), cache_storage_cache_name_); +} + +Resource::Resource(const ResourceRequest& request, + Type type, + const ResourceLoaderOptions& options) + : type_(type), + status_(ResourceStatus::kNotStarted), + load_finish_time_(0), + identifier_(0), + preload_discovery_time_(0.0), + encoded_size_(0), + encoded_size_memory_usage_(0), + decoded_size_(0), + overhead_size_(CalculateOverheadSize()), + cache_identifier_(MemoryCache::DefaultCacheIdentifier()), + link_preload_(false), + is_revalidating_(false), + is_alive_(false), + is_add_remove_client_prohibited_(false), + integrity_disposition_(ResourceIntegrityDisposition::kNotChecked), + options_(options), + response_timestamp_(CurrentTime()), + resource_request_(request) { + InstanceCounters::IncrementCounter(InstanceCounters::kResourceCounter); + + if (IsMainThread()) + MemoryCoordinator::Instance().RegisterClient(this); +} + +Resource::~Resource() { + InstanceCounters::DecrementCounter(InstanceCounters::kResourceCounter); +} + +void Resource::Trace(blink::Visitor* visitor) { + visitor->Trace(loader_); + visitor->Trace(cache_handler_); + visitor->Trace(clients_); + visitor->Trace(clients_awaiting_callback_); + visitor->Trace(finished_clients_); + visitor->Trace(finish_observers_); + MemoryCoordinatorClient::Trace(visitor); +} + +void Resource::SetLoader(ResourceLoader* loader) { + CHECK(!loader_); + DCHECK(StillNeedsLoad()); + loader_ = loader; + status_ = ResourceStatus::kPending; +} + +void Resource::CheckResourceIntegrity() { + // Skip the check and reuse the previous check result, especially on + // successful revalidation. + if (IntegrityDisposition() != ResourceIntegrityDisposition::kNotChecked) + return; + + // Loading error occurred? Then result is uncheckable. + integrity_report_info_.Clear(); + if (ErrorOccurred()) { + CHECK(!Data()); + integrity_disposition_ = ResourceIntegrityDisposition::kFailed; + return; + } + + // No integrity attributes to check? Then we're passing. + if (IntegrityMetadata().IsEmpty()) { + integrity_disposition_ = ResourceIntegrityDisposition::kPassed; + return; + } + + const char* data = nullptr; + size_t data_length = 0; + + // Edge case: If a resource actually has zero bytes then it will not + // typically have a resource buffer, but we still need to check integrity + // because people might want to assert a zero-length resource. + CHECK(DecodedSize() == 0 || Data()); + if (Data()) { + data = Data()->Data(); + data_length = Data()->size(); + } + + if (SubresourceIntegrity::CheckSubresourceIntegrity(IntegrityMetadata(), data, + data_length, Url(), *this, + integrity_report_info_)) + integrity_disposition_ = ResourceIntegrityDisposition::kPassed; + else + integrity_disposition_ = ResourceIntegrityDisposition::kFailed; + DCHECK_NE(IntegrityDisposition(), ResourceIntegrityDisposition::kNotChecked); +} + +void Resource::NotifyFinished() { + DCHECK(IsLoaded()); + + ResourceClientWalker<ResourceClient> w(clients_); + while (ResourceClient* c = w.Next()) { + MarkClientFinished(c); + c->NotifyFinished(this); + } +} + +void Resource::MarkClientFinished(ResourceClient* client) { + if (clients_.Contains(client)) { + finished_clients_.insert(client); + clients_.erase(client); + } +} + +void Resource::AppendData(const char* data, size_t length) { + TRACE_EVENT0("blink", "Resource::appendData"); + DCHECK(!is_revalidating_); + DCHECK(!ErrorOccurred()); + if (options_.data_buffering_policy == kBufferData) { + if (data_) + data_->Append(data, length); + else + data_ = SharedBuffer::Create(data, length); + SetEncodedSize(data_->size()); + } + ResourceClientWalker<ResourceClient> w(Clients()); + while (ResourceClient* c = w.Next()) + c->DataReceived(this, data, length); +} + +void Resource::SetResourceBuffer(scoped_refptr<SharedBuffer> resource_buffer) { + DCHECK(!is_revalidating_); + DCHECK(!ErrorOccurred()); + DCHECK_EQ(options_.data_buffering_policy, kBufferData); + data_ = std::move(resource_buffer); + SetEncodedSize(data_->size()); +} + +void Resource::ClearData() { + data_ = nullptr; + encoded_size_memory_usage_ = 0; +} + +void Resource::TriggerNotificationForFinishObservers( + base::SingleThreadTaskRunner* task_runner) { + if (finish_observers_.IsEmpty()) + return; + + auto* new_collections = new HeapHashSet<WeakMember<ResourceFinishObserver>>( + std::move(finish_observers_)); + finish_observers_.clear(); + + task_runner->PostTask(FROM_HERE, WTF::Bind(&NotifyFinishObservers, + WrapPersistent(new_collections))); + + DidRemoveClientOrObserver(); +} + +void Resource::SetDataBufferingPolicy( + DataBufferingPolicy data_buffering_policy) { + options_.data_buffering_policy = data_buffering_policy; + ClearData(); + SetEncodedSize(0); +} + +void Resource::FinishAsError(const ResourceError& error, + base::SingleThreadTaskRunner* task_runner) { + error_ = error; + is_revalidating_ = false; + + if (IsMainThread()) + GetMemoryCache()->Remove(this); + + if (!ErrorOccurred()) + SetStatus(ResourceStatus::kLoadError); + DCHECK(ErrorOccurred()); + ClearData(); + loader_ = nullptr; + CheckResourceIntegrity(); + TriggerNotificationForFinishObservers(task_runner); + NotifyFinished(); +} + +void Resource::Finish(double load_finish_time, + base::SingleThreadTaskRunner* task_runner) { + DCHECK(!is_revalidating_); + load_finish_time_ = load_finish_time; + if (!ErrorOccurred()) + status_ = ResourceStatus::kCached; + loader_ = nullptr; + CheckResourceIntegrity(); + TriggerNotificationForFinishObservers(task_runner); + NotifyFinished(); +} + +AtomicString Resource::HttpContentType() const { + return GetResponse().HttpContentType(); +} + +bool Resource::PassesAccessControlCheck( + const SecurityOrigin& security_origin) const { + WTF::Optional<network::mojom::CORSError> cors_error = CORS::CheckAccess( + GetResponse().Url(), GetResponse().HttpStatusCode(), + GetResponse().HttpHeaderFields(), + LastResourceRequest().GetFetchCredentialsMode(), security_origin); + + return !cors_error; +} + +bool Resource::MustRefetchDueToIntegrityMetadata( + const FetchParameters& params) const { + if (params.IntegrityMetadata().IsEmpty()) + return false; + + return !IntegrityMetadata::SetsEqual(IntegrityMetadata(), + params.IntegrityMetadata()); +} + +static double CurrentAge(const ResourceResponse& response, + double response_timestamp) { + // RFC2616 13.2.3 + // No compensation for latency as that is not terribly important in practice + double date_value = response.Date(); + double apparent_age = std::isfinite(date_value) + ? std::max(0., response_timestamp - date_value) + : 0; + double age_value = response.Age(); + double corrected_received_age = std::isfinite(age_value) + ? std::max(apparent_age, age_value) + : apparent_age; + double resident_time = CurrentTime() - response_timestamp; + return corrected_received_age + resident_time; +} + +static double FreshnessLifetime(const ResourceResponse& response, + double response_timestamp) { +#if !defined(OS_ANDROID) + // On desktop, local files should be reloaded in case they change. + if (response.Url().IsLocalFile()) + return 0; +#endif + + // Cache other non-http / non-filesystem resources liberally. + if (!response.Url().ProtocolIsInHTTPFamily() && + !response.Url().ProtocolIs("filesystem")) + return std::numeric_limits<double>::max(); + + // RFC2616 13.2.4 + double max_age_value = response.CacheControlMaxAge(); + if (std::isfinite(max_age_value)) + return max_age_value; + double expires_value = response.Expires(); + double date_value = response.Date(); + double creation_time = + std::isfinite(date_value) ? date_value : response_timestamp; + if (std::isfinite(expires_value)) + return expires_value - creation_time; + double last_modified_value = response.LastModified(); + if (std::isfinite(last_modified_value)) + return (creation_time - last_modified_value) * 0.1; + // If no cache headers are present, the specification leaves the decision to + // the UA. Other browsers seem to opt for 0. + return 0; +} + +static bool CanUseResponse(const ResourceResponse& response, + double response_timestamp) { + if (response.IsNull()) + return false; + + if (response.CacheControlContainsNoCache() || + response.CacheControlContainsNoStore()) + return false; + + if (response.HttpStatusCode() == 303) { + // Must not be cached. + return false; + } + + if (response.HttpStatusCode() == 302 || response.HttpStatusCode() == 307) { + // Default to not cacheable unless explicitly allowed. + bool has_max_age = std::isfinite(response.CacheControlMaxAge()); + bool has_expires = std::isfinite(response.Expires()); + // TODO: consider catching Cache-Control "private" and "public" here. + if (!has_max_age && !has_expires) + return false; + } + + return CurrentAge(response, response_timestamp) <= + FreshnessLifetime(response, response_timestamp); +} + +const ResourceRequest& Resource::LastResourceRequest() const { + if (!redirect_chain_.size()) + return GetResourceRequest(); + return redirect_chain_.back().request_; +} + +void Resource::SetRevalidatingRequest(const ResourceRequest& request) { + SECURITY_CHECK(redirect_chain_.IsEmpty()); + SECURITY_CHECK(!is_unused_preload_); + DCHECK(!request.IsNull()); + CHECK(!is_revalidation_start_forbidden_); + is_revalidating_ = true; + resource_request_ = request; + status_ = ResourceStatus::kNotStarted; +} + +bool Resource::WillFollowRedirect(const ResourceRequest& new_request, + const ResourceResponse& redirect_response) { + if (is_revalidating_) + RevalidationFailed(); + redirect_chain_.push_back(RedirectPair(new_request, redirect_response)); + return true; +} + +void Resource::SetResponse(const ResourceResponse& response) { + response_ = response; + + // Currently we support the metadata caching only for HTTP family. + if (!GetResourceRequest().Url().ProtocolIsInHTTPFamily() || + !GetResponse().Url().ProtocolIsInHTTPFamily()) { + cache_handler_.Clear(); + return; + } + + cache_handler_ = CreateCachedMetadataHandler(CreateCachedMetadataSender()); +} + +std::unique_ptr<CachedMetadataSender> Resource::CreateCachedMetadataSender() + const { + if (GetResponse().WasFetchedViaServiceWorker()) { + // TODO(leszeks): Check whether it's correct that the source_origin can be + // null. + if (!source_origin_ || GetResponse().CacheStorageCacheName().IsNull()) { + return std::make_unique<NullCachedMetadataSender>(); + } + return std::make_unique<ServiceWorkerCachedMetadataSender>( + this, source_origin_.get()); + } + return std::make_unique<CachedMetadataSenderImpl>(this); +} + +void Resource::ResponseReceived(const ResourceResponse& response, + std::unique_ptr<WebDataConsumerHandle>) { + response_timestamp_ = CurrentTime(); + if (preload_discovery_time_) { + int time_since_discovery = static_cast<int>( + 1000 * (CurrentTimeTicksInSeconds() - preload_discovery_time_)); + DEFINE_STATIC_LOCAL(CustomCountHistogram, + preload_discovery_to_first_byte_histogram, + ("PreloadScanner.TTFB", 0, 10000, 50)); + preload_discovery_to_first_byte_histogram.Count(time_since_discovery); + } + + if (is_revalidating_) { + if (response.HttpStatusCode() == 304) { + RevalidationSucceeded(response); + return; + } + RevalidationFailed(); + } + SetResponse(response); + String encoding = response.TextEncodingName(); + if (!encoding.IsNull()) + SetEncoding(encoding); +} + +void Resource::SetSerializedCachedMetadata(const char* data, size_t size) { + DCHECK(!is_revalidating_); + DCHECK(!GetResponse().IsNull()); +} + +String Resource::ReasonNotDeletable() const { + StringBuilder builder; + if (HasClientsOrObservers()) { + builder.Append("hasClients("); + builder.AppendNumber(clients_.size()); + if (!clients_awaiting_callback_.IsEmpty()) { + builder.Append(", AwaitingCallback="); + builder.AppendNumber(clients_awaiting_callback_.size()); + } + if (!finished_clients_.IsEmpty()) { + builder.Append(", Finished="); + builder.AppendNumber(finished_clients_.size()); + } + builder.Append(')'); + } + if (loader_) { + if (!builder.IsEmpty()) + builder.Append(' '); + builder.Append("loader_"); + } + if (IsMainThread() && GetMemoryCache()->Contains(this)) { + if (!builder.IsEmpty()) + builder.Append(' '); + builder.Append("in_memory_cache"); + } + return builder.ToString(); +} + +void Resource::DidAddClient(ResourceClient* c) { + if (scoped_refptr<SharedBuffer> data = Data()) { + data->ForEachSegment([this, &c](const char* segment, size_t segment_size, + size_t segment_offset) -> bool { + c->DataReceived(this, segment, segment_size); + + // Stop pushing data if the client removed itself. + return HasClient(c); + }); + } + if (!HasClient(c)) + return; + if (IsLoaded()) { + c->NotifyFinished(this); + if (clients_.Contains(c)) { + finished_clients_.insert(c); + clients_.erase(c); + } + } +} + +static bool TypeNeedsSynchronousCacheHit(Resource::Type type) { + // Some resources types default to return data synchronously. For most of + // these, it's because there are layout tests that expect data to return + // synchronously in case of cache hit. In the case of fonts, there was a + // performance regression. + // FIXME: Get to the point where we don't need to special-case sync/async + // behavior for different resource types. + if (type == Resource::kCSSStyleSheet) + return true; + if (type == Resource::kScript) + return true; + if (type == Resource::kFont) + return true; + return false; +} + +void Resource::WillAddClientOrObserver() { + if (!HasClientsOrObservers()) { + is_alive_ = true; + } +} + +void Resource::AddClient(ResourceClient* client, + base::SingleThreadTaskRunner* task_runner) { + CHECK(!is_add_remove_client_prohibited_); + + WillAddClientOrObserver(); + + if (is_revalidating_) { + clients_.insert(client); + return; + } + + // If an error has occurred or we have existing data to send to the new client + // and the resource type supports it, send it asynchronously. + if ((ErrorOccurred() || !GetResponse().IsNull()) && + !TypeNeedsSynchronousCacheHit(GetType())) { + clients_awaiting_callback_.insert(client); + if (!async_finish_pending_clients_task_.IsActive()) { + async_finish_pending_clients_task_ = PostCancellableTask( + *task_runner, FROM_HERE, + WTF::Bind(&Resource::FinishPendingClients, WrapWeakPersistent(this))); + } + return; + } + + clients_.insert(client); + DidAddClient(client); + return; +} + +void Resource::RemoveClient(ResourceClient* client) { + CHECK(!is_add_remove_client_prohibited_); + + // This code may be called in a pre-finalizer, where weak members in the + // HashCountedSet are already swept out. + + if (finished_clients_.Contains(client)) + finished_clients_.erase(client); + else if (clients_awaiting_callback_.Contains(client)) + clients_awaiting_callback_.erase(client); + else + clients_.erase(client); + + if (clients_awaiting_callback_.IsEmpty() && + async_finish_pending_clients_task_.IsActive()) { + async_finish_pending_clients_task_.Cancel(); + } + + DidRemoveClientOrObserver(); +} + +void Resource::AddFinishObserver(ResourceFinishObserver* client, + base::SingleThreadTaskRunner* task_runner) { + CHECK(!is_add_remove_client_prohibited_); + DCHECK(!finish_observers_.Contains(client)); + + WillAddClientOrObserver(); + finish_observers_.insert(client); + if (IsLoaded()) + TriggerNotificationForFinishObservers(task_runner); +} + +void Resource::RemoveFinishObserver(ResourceFinishObserver* client) { + CHECK(!is_add_remove_client_prohibited_); + + finish_observers_.erase(client); + DidRemoveClientOrObserver(); +} + +void Resource::DidRemoveClientOrObserver() { + if (!HasClientsOrObservers() && is_alive_) { + is_alive_ = false; + AllClientsAndObserversRemoved(); + + // RFC2616 14.9.2: + // "no-store: ... MUST make a best-effort attempt to remove the information + // from volatile storage as promptly as possible" + // "... History buffers MAY store such responses as part of their normal + // operation." + // We allow non-secure content to be reused in history, but we do not allow + // secure content to be reused. + if (HasCacheControlNoStoreHeader() && Url().ProtocolIs("https") && + IsMainThread()) + GetMemoryCache()->Remove(this); + } +} + +void Resource::AllClientsAndObserversRemoved() { + if (loader_ && !detachable_) + loader_->ScheduleCancel(); +} + +void Resource::SetDecodedSize(size_t decoded_size) { + if (decoded_size == decoded_size_) + return; + size_t old_size = size(); + decoded_size_ = decoded_size; + if (IsMainThread()) + GetMemoryCache()->Update(this, old_size, size()); +} + +void Resource::SetEncodedSize(size_t encoded_size) { + if (encoded_size == encoded_size_ && + encoded_size == encoded_size_memory_usage_) + return; + size_t old_size = size(); + encoded_size_ = encoded_size; + encoded_size_memory_usage_ = encoded_size; + if (IsMainThread()) + GetMemoryCache()->Update(this, old_size, size()); +} + +void Resource::FinishPendingClients() { + // We're going to notify clients one by one. It is simple if the client does + // nothing. However there are a couple other things that can happen. + // + // 1. Clients can be added during the loop. Make sure they are not processed. + // 2. Clients can be removed during the loop. Make sure they are always + // available to be removed. Also don't call removed clients or add them + // back. + // + // Handle case (1) by saving a list of clients to notify. A separate list also + // ensure a client is either in cliens_ or clients_awaiting_callback_. + HeapVector<Member<ResourceClient>> clients_to_notify; + CopyToVector(clients_awaiting_callback_, clients_to_notify); + + for (const auto& client : clients_to_notify) { + // Handle case (2) to skip removed clients. + if (!clients_awaiting_callback_.erase(client)) + continue; + clients_.insert(client); + + // When revalidation starts after waiting clients are scheduled and + // before they are added here. In such cases, we just add the clients + // to |clients_| without DidAddClient(), as in Resource::AddClient(). + if (!is_revalidating_) + DidAddClient(client); + } + + // It is still possible for the above loop to finish a new client + // synchronously. If there's no client waiting we should deschedule. + bool scheduled = async_finish_pending_clients_task_.IsActive(); + if (scheduled && clients_awaiting_callback_.IsEmpty()) + async_finish_pending_clients_task_.Cancel(); + + // Prevent the case when there are clients waiting but no callback scheduled. + DCHECK(clients_awaiting_callback_.IsEmpty() || scheduled); +} + +bool Resource::CanReuse( + const FetchParameters& params, + scoped_refptr<const SecurityOrigin> new_source_origin) const { + const ResourceRequest& new_request = params.GetResourceRequest(); + const ResourceLoaderOptions& new_options = params.Options(); + + // Never reuse opaque responses from a service worker for requests that are + // not no-cors. https://crbug.com/625575 + // TODO(yhirano): Remove this. + if (GetResponse().WasFetchedViaServiceWorker() && + GetResponse().ResponseTypeViaServiceWorker() == + network::mojom::FetchResponseType::kOpaque && + new_request.GetFetchRequestMode() != + network::mojom::FetchRequestMode::kNoCORS) { + return false; + } + + // If credentials were sent with the previous request and won't be with this + // one, or vice versa, re-fetch the resource. + // + // This helps with the case where the server sends back + // "Access-Control-Allow-Origin: *" all the time, but some of the client's + // requests are made without CORS and some with. + if (GetResourceRequest().AllowStoredCredentials() != + new_request.AllowStoredCredentials()) + return false; + + // Certain requests (e.g., XHRs) might have manually set headers that require + // revalidation. In theory, this should be a Revalidate case. In practice, the + // MemoryCache revalidation path assumes a whole bunch of things about how + // revalidation works that manual headers violate, so punt to Reload instead. + // + // Similarly, a request with manually added revalidation headers can lead to a + // 304 response for a request that wasn't flagged as a revalidation attempt. + // Normally, successful revalidation will maintain the original response's + // status code, but for a manual revalidation the response code remains 304. + // In this case, the Resource likely has insufficient context to provide a + // useful cache hit or revalidation. See http://crbug.com/643659 + if (new_request.IsConditional() || response_.HttpStatusCode() == 304) + return false; + + // Answers the question "can a separate request with different options be + // re-used" (e.g. preload request). The safe (but possibly slow) answer is + // always false. + // + // Data buffering policy differences are believed to be safe for re-use. + // + // TODO: Check content_security_policy_option. + // + // initiator_info is purely informational and should be benign for re-use. + // + // request_initiator_context is benign (indicates document vs. worker). + + // Reuse only if both the existing Resource and the new request are + // asynchronous. Particularly, + // 1. Sync and async Resource/requests shouldn't be mixed (crbug.com/652172), + // 2. Sync existing Resources shouldn't be revalidated, and + // 3. Sync new requests shouldn't revalidate existing Resources. + // + // 2. and 3. are because SyncResourceHandler handles redirects without + // calling WillFollowRedirect, and causes response URL mismatch + // (crbug.com/618967) and bypassing redirect restriction around revalidation + // (crbug.com/613971 for 2. and crbug.com/614989 for 3.). + if (new_options.synchronous_policy == kRequestSynchronously || + options_.synchronous_policy == kRequestSynchronously) + return false; + + if (resource_request_.GetKeepalive() || new_request.GetKeepalive()) { + return false; + } + + DCHECK(source_origin_); + DCHECK(new_source_origin); + + // Don't reuse an existing resource when the source origin is different. + if (!source_origin_->IsSameSchemeHostPort(new_source_origin.get())) + return false; + + // securityOrigin has more complicated checks which callers are responsible + // for. + + if (new_request.GetFetchCredentialsMode() != + resource_request_.GetFetchCredentialsMode()) + return false; + + const auto new_mode = new_request.GetFetchRequestMode(); + const auto existing_mode = resource_request_.GetFetchRequestMode(); + + if (new_mode != existing_mode) + return false; + + switch (new_mode) { + case network::mojom::FetchRequestMode::kNoCORS: + case network::mojom::FetchRequestMode::kNavigate: + break; + + case network::mojom::FetchRequestMode::kCORS: + case network::mojom::FetchRequestMode::kSameOrigin: + case network::mojom::FetchRequestMode::kCORSWithForcedPreflight: + // We have two separate CORS handling logics in DocumentThreadableLoader + // and ResourceLoader and sharing resources is difficult when they are + // handled differently. + if (options_.cors_handling_by_resource_fetcher != + new_options.cors_handling_by_resource_fetcher) { + // If the existing one is handled in DocumentThreadableLoader and the + // new one is handled in ResourceLoader, reusing the existing one will + // lead to CORS violations. + if (!options_.cors_handling_by_resource_fetcher) + return false; + + // Otherwise (i.e., if the existing one is handled in ResourceLoader + // and the new one is handled in DocumentThreadableLoader), reusing + // the existing one will lead to double check which is harmless. + } + break; + } + + return true; +} + +void Resource::Prune() { + DestroyDecodedDataIfPossible(); +} + +void Resource::OnPurgeMemory() { + Prune(); + if (!cache_handler_) + return; + cache_handler_->ClearCachedMetadata(CachedMetadataHandler::kCacheLocally); +} + +void Resource::OnMemoryDump(WebMemoryDumpLevelOfDetail level_of_detail, + WebProcessMemoryDump* memory_dump) const { + static const size_t kMaxURLReportLength = 128; + static const int kMaxResourceClientToShowInMemoryInfra = 10; + + const String dump_name = GetMemoryDumpName(); + WebMemoryAllocatorDump* dump = + memory_dump->CreateMemoryAllocatorDump(dump_name); + dump->AddScalar("encoded_size", "bytes", encoded_size_memory_usage_); + if (HasClientsOrObservers()) + dump->AddScalar("live_size", "bytes", encoded_size_memory_usage_); + else + dump->AddScalar("dead_size", "bytes", encoded_size_memory_usage_); + + if (data_) + data_->OnMemoryDump(dump_name, memory_dump); + + if (level_of_detail == WebMemoryDumpLevelOfDetail::kDetailed) { + String url_to_report = Url().GetString(); + if (url_to_report.length() > kMaxURLReportLength) { + url_to_report.Truncate(kMaxURLReportLength); + url_to_report = url_to_report + "..."; + } + dump->AddString("url", "", url_to_report); + + dump->AddString("reason_not_deletable", "", ReasonNotDeletable()); + + Vector<String> client_names; + ResourceClientWalker<ResourceClient> walker(clients_); + while (ResourceClient* client = walker.Next()) + client_names.push_back(client->DebugName()); + ResourceClientWalker<ResourceClient> walker2(clients_awaiting_callback_); + while (ResourceClient* client = walker2.Next()) + client_names.push_back("(awaiting) " + client->DebugName()); + ResourceClientWalker<ResourceClient> walker3(finished_clients_); + while (ResourceClient* client = walker3.Next()) + client_names.push_back("(finished) " + client->DebugName()); + std::sort(client_names.begin(), client_names.end(), + WTF::CodePointCompareLessThan); + + StringBuilder builder; + for (size_t i = 0; + i < client_names.size() && i < kMaxResourceClientToShowInMemoryInfra; + ++i) { + if (i > 0) + builder.Append(" / "); + builder.Append(client_names[i]); + } + if (client_names.size() > kMaxResourceClientToShowInMemoryInfra) { + builder.Append(" / and "); + builder.AppendNumber(client_names.size() - + kMaxResourceClientToShowInMemoryInfra); + builder.Append(" more"); + } + dump->AddString("ResourceClient", "", builder.ToString()); + } + + const String overhead_name = dump_name + "/metadata"; + WebMemoryAllocatorDump* overhead_dump = + memory_dump->CreateMemoryAllocatorDump(overhead_name); + overhead_dump->AddScalar("size", "bytes", OverheadSize()); + memory_dump->AddSuballocation( + overhead_dump->Guid(), String(WTF::Partitions::kAllocatedObjectPoolName)); +} + +String Resource::GetMemoryDumpName() const { + return String::Format( + "web_cache/%s_resources/%ld", + ResourceTypeToString(GetType(), Options().initiator_info.name), + identifier_); +} + +void Resource::SetCachePolicyBypassingCache() { + resource_request_.SetCacheMode(mojom::FetchCacheMode::kBypassCache); +} + +void Resource::SetPreviewsState(WebURLRequest::PreviewsState previews_state) { + resource_request_.SetPreviewsState(previews_state); +} + +void Resource::ClearRangeRequestHeader() { + resource_request_.ClearHTTPHeaderField("range"); +} + +void Resource::RevalidationSucceeded( + const ResourceResponse& validating_response) { + SECURITY_CHECK(redirect_chain_.IsEmpty()); + SECURITY_CHECK(EqualIgnoringFragmentIdentifier(validating_response.Url(), + GetResponse().Url())); + response_.SetResourceLoadTiming(validating_response.GetResourceLoadTiming()); + + // RFC2616 10.3.5 + // Update cached headers from the 304 response + const HTTPHeaderMap& new_headers = validating_response.HttpHeaderFields(); + for (const auto& header : new_headers) { + // Entity headers should not be sent by servers when generating a 304 + // response; misconfigured servers send them anyway. We shouldn't allow such + // headers to update the original request. We'll base this on the list + // defined by RFC2616 7.1, with a few additions for extension headers we + // care about. + if (!ShouldUpdateHeaderAfterRevalidation(header.key)) + continue; + response_.SetHTTPHeaderField(header.key, header.value); + } + + is_revalidating_ = false; +} + +void Resource::RevalidationFailed() { + SECURITY_CHECK(redirect_chain_.IsEmpty()); + ClearData(); + cache_handler_.Clear(); + integrity_disposition_ = ResourceIntegrityDisposition::kNotChecked; + integrity_report_info_.Clear(); + DestroyDecodedDataForFailedRevalidation(); + is_revalidating_ = false; +} + +void Resource::MarkAsPreload() { + DCHECK(!is_unused_preload_); + is_unused_preload_ = true; +} + +bool Resource::MatchPreload(const FetchParameters& params, + base::SingleThreadTaskRunner*) { + DCHECK(is_unused_preload_); + is_unused_preload_ = false; + + if (preload_discovery_time_) { + int time_since_discovery = static_cast<int>( + 1000 * (CurrentTimeTicksInSeconds() - preload_discovery_time_)); + DEFINE_STATIC_LOCAL(CustomCountHistogram, preload_discovery_histogram, + ("PreloadScanner.ReferenceTime", 0, 10000, 50)); + preload_discovery_histogram.Count(time_since_discovery); + } + return true; +} + +bool Resource::CanReuseRedirectChain() const { + for (auto& redirect : redirect_chain_) { + if (!CanUseResponse(redirect.redirect_response_, response_timestamp_)) + return false; + if (redirect.request_.CacheControlContainsNoCache() || + redirect.request_.CacheControlContainsNoStore()) + return false; + } + return true; +} + +bool Resource::HasCacheControlNoStoreHeader() const { + return GetResponse().CacheControlContainsNoStore() || + GetResourceRequest().CacheControlContainsNoStore(); +} + +bool Resource::MustReloadDueToVaryHeader( + const ResourceRequest& new_request) const { + const AtomicString& vary = GetResponse().HttpHeaderField(HTTPNames::Vary); + if (vary.IsNull()) + return false; + if (vary == "*") + return true; + + CommaDelimitedHeaderSet vary_headers; + ParseCommaDelimitedHeader(vary, vary_headers); + for (const String& header : vary_headers) { + AtomicString atomic_header(header); + if (GetResourceRequest().HttpHeaderField(atomic_header) != + new_request.HttpHeaderField(atomic_header)) { + return true; + } + } + return false; +} + +bool Resource::MustRevalidateDueToCacheHeaders() const { + return !CanUseResponse(GetResponse(), response_timestamp_) || + GetResourceRequest().CacheControlContainsNoCache() || + GetResourceRequest().CacheControlContainsNoStore(); +} + +bool Resource::CanUseCacheValidator() const { + if (IsLoading() || ErrorOccurred()) + return false; + + if (HasCacheControlNoStoreHeader()) + return false; + + // Do not revalidate Resource with redirects. https://crbug.com/613971 + if (!RedirectChain().IsEmpty()) + return false; + + return GetResponse().HasCacheValidatorFields() || + GetResourceRequest().HasCacheValidatorFields(); +} + +size_t Resource::CalculateOverheadSize() const { + static const int kAverageClientsHashMapSize = 384; + return sizeof(Resource) + GetResponse().MemoryUsage() + + kAverageClientsHashMapSize + + GetResourceRequest().Url().GetString().length() * 2; +} + +void Resource::DidChangePriority(ResourceLoadPriority load_priority, + int intra_priority_value) { + resource_request_.SetPriority(load_priority, intra_priority_value); + if (loader_) + loader_->DidChangePriority(load_priority, intra_priority_value); +} + +// TODO(toyoshim): Consider to generate automatically. https://crbug.com/675515. +static const char* InitiatorTypeNameToString( + const AtomicString& initiator_type_name) { + if (initiator_type_name == FetchInitiatorTypeNames::css) + return "CSS resource"; + if (initiator_type_name == FetchInitiatorTypeNames::document) + return "Document"; + if (initiator_type_name == FetchInitiatorTypeNames::icon) + return "Icon"; + if (initiator_type_name == FetchInitiatorTypeNames::internal) + return "Internal resource"; + if (initiator_type_name == FetchInitiatorTypeNames::link) + return "Link element resource"; + if (initiator_type_name == FetchInitiatorTypeNames::processinginstruction) + return "Processing instruction"; + if (initiator_type_name == FetchInitiatorTypeNames::texttrack) + return "Text track"; + if (initiator_type_name == FetchInitiatorTypeNames::uacss) + return "User Agent CSS resource"; + if (initiator_type_name == FetchInitiatorTypeNames::xml) + return "XML resource"; + if (initiator_type_name == FetchInitiatorTypeNames::xmlhttprequest) + return "XMLHttpRequest"; + + static_assert( + FetchInitiatorTypeNames::FetchInitiatorTypeNamesCount == 13, + "New FetchInitiatorTypeNames should be handled correctly here."); + + return "Resource"; +} + +const char* Resource::ResourceTypeToString( + Type type, + const AtomicString& fetch_initiator_name) { + switch (type) { + case Resource::kMainResource: + return "Main resource"; + case Resource::kImage: + return "Image"; + case Resource::kCSSStyleSheet: + return "CSS stylesheet"; + case Resource::kScript: + return "Script"; + case Resource::kFont: + return "Font"; + case Resource::kRaw: + return InitiatorTypeNameToString(fetch_initiator_name); + case Resource::kSVGDocument: + return "SVG document"; + case Resource::kXSLStyleSheet: + return "XSL stylesheet"; + case Resource::kLinkPrefetch: + return "Link prefetch resource"; + case Resource::kTextTrack: + return "Text track"; + case Resource::kImportResource: + return "Imported resource"; + case Resource::kAudio: + return "Audio"; + case Resource::kVideo: + return "Video"; + case Resource::kManifest: + return "Manifest"; + case Resource::kMock: + return "Mock"; + } + NOTREACHED(); + return InitiatorTypeNameToString(fetch_initiator_name); +} + +bool Resource::ShouldBlockLoadEvent() const { + return !link_preload_ && IsLoadEventBlockingResourceType(); +} + +bool Resource::IsLoadEventBlockingResourceType() const { + switch (type_) { + case Resource::kMainResource: + case Resource::kImage: + case Resource::kCSSStyleSheet: + case Resource::kScript: + case Resource::kFont: + case Resource::kSVGDocument: + case Resource::kXSLStyleSheet: + case Resource::kImportResource: + return true; + case Resource::kRaw: + case Resource::kLinkPrefetch: + case Resource::kTextTrack: + case Resource::kAudio: + case Resource::kVideo: + case Resource::kManifest: + case Resource::kMock: + return false; + } + NOTREACHED(); + return false; +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/platform/loader/fetch/resource.h b/chromium/third_party/blink/renderer/platform/loader/fetch/resource.h new file mode 100644 index 00000000000..b68b9c5abb0 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/loader/fetch/resource.h @@ -0,0 +1,596 @@ +/* + 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, 2009, 2010, 2011 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_PLATFORM_LOADER_FETCH_RESOURCE_H_ +#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_LOADER_FETCH_RESOURCE_H_ + +#include <memory> +#include "base/single_thread_task_runner.h" +#include "third_party/blink/public/platform/web_data_consumer_handle.h" +#include "third_party/blink/public/platform/web_scoped_virtual_time_pauser.h" +#include "third_party/blink/renderer/platform/instrumentation/tracing/web_process_memory_dump.h" +#include "third_party/blink/renderer/platform/loader/cors/cors_status.h" +#include "third_party/blink/renderer/platform/loader/fetch/cached_metadata_handler.h" +#include "third_party/blink/renderer/platform/loader/fetch/integrity_metadata.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_loader_options.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource_priority.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_status.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/memory_coordinator.h" +#include "third_party/blink/renderer/platform/platform_export.h" +#include "third_party/blink/renderer/platform/shared_buffer.h" +#include "third_party/blink/renderer/platform/timer.h" +#include "third_party/blink/renderer/platform/web_task_runner.h" +#include "third_party/blink/renderer/platform/wtf/allocator.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_set.h" +#include "third_party/blink/renderer/platform/wtf/optional.h" +#include "third_party/blink/renderer/platform/wtf/text/atomic_string.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 FetchParameters; +class ResourceClient; +class ResourceFetcher; +class ResourceFinishObserver; +class ResourceTimingInfo; +class ResourceLoader; +class SecurityOrigin; + +// A callback for sending the serialized data of cached metadata back to the +// platform. +class CachedMetadataSender { + public: + virtual ~CachedMetadataSender() = default; + virtual void Send(const char*, size_t) = 0; + + // IsServedFromCacheStorage is used to alter caching strategy to be more + // aggressive. See ScriptController.cpp CacheOptions() for an example. + virtual bool IsServedFromCacheStorage() = 0; +}; + +// A resource that is held in the cache. Classes who want to use this object +// should derive from ResourceClient, to get the function calls in case the +// requested data has arrived. This class also does the actual communication +// with the loader to obtain the resource from the network. +class PLATFORM_EXPORT Resource : public GarbageCollectedFinalized<Resource>, + public MemoryCoordinatorClient { + USING_GARBAGE_COLLECTED_MIXIN(Resource); + WTF_MAKE_NONCOPYABLE(Resource); + + public: + // |Type| enum values are used in UMAs, so do not change the values of + // existing |Type|. When adding a new |Type|, append it at the end and update + // |kLastResourceType|. + enum Type : uint8_t { + kMainResource, + kImage, + kCSSStyleSheet, + kScript, + kFont, + kRaw, + kSVGDocument, + kXSLStyleSheet, + kLinkPrefetch, + kTextTrack, + kImportResource, + kAudio, + kVideo, + kManifest, + kMock // Only for testing + }; + static const int kLastResourceType = kMock + 1; + + // Used by reloadIfLoFiOrPlaceholderImage(). + enum ReloadLoFiOrPlaceholderPolicy { + kReloadIfNeeded, + kReloadAlways, + }; + + ~Resource() override; + + void Trace(blink::Visitor*) override; + + virtual WTF::TextEncoding Encoding() const { return WTF::TextEncoding(); } + virtual void AppendData(const char*, size_t); + virtual void FinishAsError(const ResourceError&, + base::SingleThreadTaskRunner*); + + void SetLinkPreload(bool is_link_preload) { link_preload_ = is_link_preload; } + bool IsLinkPreload() const { return link_preload_; } + + void SetPreloadDiscoveryTime(double preload_discovery_time) { + preload_discovery_time_ = preload_discovery_time; + } + + const ResourceError& GetResourceError() const { + DCHECK(error_); + return *error_; + } + + void SetIdentifier(unsigned long identifier) { identifier_ = identifier; } + unsigned long Identifier() const { return identifier_; } + + virtual bool ShouldIgnoreHTTPStatusCodeErrors() const { return false; } + + const ResourceRequest& GetResourceRequest() const { + return resource_request_; + } + const ResourceRequest& LastResourceRequest() const; + + virtual void SetRevalidatingRequest(const ResourceRequest&); + + // This url can have a fragment, but it can match resources that differ by the + // fragment only. + const KURL& Url() const { return GetResourceRequest().Url(); } + Type GetType() const { return static_cast<Type>(type_); } + const ResourceLoaderOptions& Options() const { return options_; } + ResourceLoaderOptions& MutableOptions() { return options_; } + + void DidChangePriority(ResourceLoadPriority, int intra_priority_value); + virtual ResourcePriority PriorityFromObservers() { + return ResourcePriority(); + } + + // If this Resource is already finished when AddClient is called, the + // ResourceClient will be notified asynchronously by a task scheduled + // on the given base::SingleThreadTaskRunner. Otherwise, the given + // base::SingleThreadTaskRunner is unused. + void AddClient(ResourceClient*, base::SingleThreadTaskRunner*); + void RemoveClient(ResourceClient*); + // Once called, this resource will not be canceled until load finishes + // even if associated with no client. + void SetDetachable() { detachable_ = true; } + + // If this Resource is already finished when AddFinishObserver is called, the + // ResourceFinishObserver will be notified asynchronously by a task scheduled + // on the given base::SingleThreadTaskRunner. Otherwise, the given + // base::SingleThreadTaskRunner is unused. + void AddFinishObserver(ResourceFinishObserver*, + base::SingleThreadTaskRunner*); + void RemoveFinishObserver(ResourceFinishObserver*); + + bool IsUnusedPreload() const { return is_unused_preload_; } + + ResourceStatus GetStatus() const { return status_; } + void SetStatus(ResourceStatus status) { status_ = status; } + + size_t size() const { return EncodedSize() + DecodedSize() + OverheadSize(); } + + // Returns the size of content (response body) before decoding. Adding a new + // usage of this function is not recommended (See the TODO below). + // + // TODO(hiroshige): Now EncodedSize/DecodedSize states are inconsistent and + // need to be refactored (crbug/643135). + size_t EncodedSize() const { return encoded_size_; } + + // Returns the current memory usage for the encoded data. Adding a new usage + // of this function is not recommended as the same reason as |EncodedSize()|. + // + // |EncodedSize()| and |EncodedSizeMemoryUsageForTesting()| can return + // different values, e.g., when ImageResource purges encoded image data after + // finishing loading. + size_t EncodedSizeMemoryUsageForTesting() const { + return encoded_size_memory_usage_; + } + + size_t DecodedSize() const { return decoded_size_; } + size_t OverheadSize() const { return overhead_size_; } + + bool IsLoaded() const { return status_ > ResourceStatus::kPending; } + + bool IsLoading() const { return status_ == ResourceStatus::kPending; } + bool StillNeedsLoad() const { return status_ < ResourceStatus::kPending; } + + void SetLoader(ResourceLoader*); + ResourceLoader* Loader() const { return loader_.Get(); } + + bool ShouldBlockLoadEvent() const; + bool IsLoadEventBlockingResourceType() const; + + // Computes the status of an object after loading. Updates the expire date on + // the cache entry file + virtual void Finish(double finish_time, base::SingleThreadTaskRunner*); + void FinishForTest() { Finish(0.0, nullptr); } + + bool PassesAccessControlCheck(const SecurityOrigin&) const; + + virtual scoped_refptr<const SharedBuffer> ResourceBuffer() const { + return data_; + } + void SetResourceBuffer(scoped_refptr<SharedBuffer>); + + virtual bool WillFollowRedirect(const ResourceRequest&, + const ResourceResponse&); + + // Called when a redirect response was received but a decision has already + // been made to not follow it. + virtual void WillNotFollowRedirect() {} + + virtual void ResponseReceived(const ResourceResponse&, + std::unique_ptr<WebDataConsumerHandle>); + void SetResponse(const ResourceResponse&); + const ResourceResponse& GetResponse() const { return response_; } + + virtual void ReportResourceTimingToClients(const ResourceTimingInfo&) {} + + // Sets the serialized metadata retrieved from the platform's cache. + // Subclasses of Resource that support cached metadata should override this + // method with one that fills the current CachedMetadataHandler. + virtual void SetSerializedCachedMetadata(const char*, size_t); + + AtomicString HttpContentType() const; + + bool WasCanceled() const { return error_ && error_->IsCancellation(); } + bool ErrorOccurred() const { + return status_ == ResourceStatus::kLoadError || + status_ == ResourceStatus::kDecodeError; + } + bool LoadFailedOrCanceled() const { return !!error_; } + + DataBufferingPolicy GetDataBufferingPolicy() const { + return options_.data_buffering_policy; + } + void SetDataBufferingPolicy(DataBufferingPolicy); + + void MarkAsPreload(); + // Returns true if |this| resource is matched with the given parameters. + virtual bool MatchPreload(const FetchParameters&, + base::SingleThreadTaskRunner*); + + bool CanReuseRedirectChain() const; + bool MustRevalidateDueToCacheHeaders() const; + virtual bool CanUseCacheValidator() const; + bool IsCacheValidator() const { return is_revalidating_; } + bool HasCacheControlNoStoreHeader() const; + bool MustReloadDueToVaryHeader(const ResourceRequest& new_request) const; + + const IntegrityMetadataSet& IntegrityMetadata() const { + return options_.integrity_metadata; + } + ResourceIntegrityDisposition IntegrityDisposition() const { + return integrity_disposition_; + } + const SubresourceIntegrity::ReportInfo& IntegrityReportInfo() const { + return integrity_report_info_; + } + bool MustRefetchDueToIntegrityMetadata(const FetchParameters&) const; + + bool IsAlive() const { return is_alive_; } + + CORSStatus GetCORSStatus() const { return cors_status_; } + + bool IsSameOriginOrCORSSuccessful() const { + return cors_status_ == CORSStatus::kSameOrigin || + cors_status_ == CORSStatus::kSuccessful || + cors_status_ == CORSStatus::kServiceWorkerSuccessful; + } + + void SetCacheIdentifier(const String& cache_identifier) { + cache_identifier_ = cache_identifier; + } + String CacheIdentifier() const { return cache_identifier_; } + + void SetSourceOrigin(scoped_refptr<const SecurityOrigin> source_origin) { + source_origin_ = source_origin; + } + + virtual void DidSendData(unsigned long long /* bytesSent */, + unsigned long long /* totalBytesToBeSent */) {} + virtual void DidDownloadData(int) {} + virtual void DidDownloadToBlob(scoped_refptr<BlobDataHandle>) {} + + double LoadFinishTime() const { return load_finish_time_; } + + void SetEncodedDataLength(int64_t value) { + response_.SetEncodedDataLength(value); + } + void SetEncodedBodyLength(int value) { + response_.SetEncodedBodyLength(value); + } + void SetDecodedBodyLength(int value) { + response_.SetDecodedBodyLength(value); + } + + virtual bool CanReuse( + const FetchParameters&, + scoped_refptr<const SecurityOrigin> new_source_origin) const; + + // If cache-aware loading is activated, this callback is called when the first + // disk-cache-only request failed due to cache miss. After this callback, + // cache-aware loading is deactivated and a reload with original request will + // be triggered right away in ResourceLoader. + virtual void WillReloadAfterDiskCacheMiss() {} + + // TODO(shaochuan): This is for saving back the actual ResourceRequest sent + // in ResourceFetcher::StartLoad() for retry in cache-aware loading, remove + // once ResourceRequest is not modified in StartLoad(). crbug.com/632580 + void SetResourceRequest(const ResourceRequest& resource_request) { + resource_request_ = resource_request; + } + + // Used by the MemoryCache to reduce the memory consumption of the entry. + void Prune(); + + virtual void OnMemoryDump(WebMemoryDumpLevelOfDetail, + WebProcessMemoryDump*) const; + + // If this Resource is ImageResource and has the Lo-Fi response headers or is + // a placeholder, reload the full original image with the Lo-Fi state set to + // off and optionally bypassing the cache. + virtual void ReloadIfLoFiOrPlaceholderImage(ResourceFetcher*, + ReloadLoFiOrPlaceholderPolicy) {} + + // Used to notify ImageResourceContent of the start of actual loading. + // JavaScript calls or client/observer notifications are disallowed inside + // NotifyStartLoad(). + virtual void NotifyStartLoad() {} + + static const char* ResourceTypeToString( + Type, + const AtomicString& fetch_initiator_name); + + class ProhibitAddRemoveClientInScope : public AutoReset<bool> { + public: + ProhibitAddRemoveClientInScope(Resource* resource) + : AutoReset(&resource->is_add_remove_client_prohibited_, true) {} + }; + + class RevalidationStartForbiddenScope : public AutoReset<bool> { + public: + RevalidationStartForbiddenScope(Resource* resource) + : AutoReset(&resource->is_revalidation_start_forbidden_, true) {} + }; + + WebScopedVirtualTimePauser& VirtualTimePauser() { + return virtual_time_pauser_; + } + + protected: + Resource(const ResourceRequest&, Type, const ResourceLoaderOptions&); + + virtual void NotifyFinished(); + + void MarkClientFinished(ResourceClient*); + + virtual bool HasClientsOrObservers() const { + return !clients_.IsEmpty() || !clients_awaiting_callback_.IsEmpty() || + !finished_clients_.IsEmpty() || !finish_observers_.IsEmpty(); + } + virtual void DestroyDecodedDataForFailedRevalidation() {} + + void SetEncodedSize(size_t); + void SetDecodedSize(size_t); + + void FinishPendingClients(); + + virtual void DidAddClient(ResourceClient*); + void WillAddClientOrObserver(); + + void DidRemoveClientOrObserver(); + virtual void AllClientsAndObserversRemoved(); + + bool HasClient(ResourceClient* client) const { + return clients_.Contains(client) || + clients_awaiting_callback_.Contains(client) || + finished_clients_.Contains(client); + } + + struct RedirectPair { + DISALLOW_NEW_EXCEPT_PLACEMENT_NEW(); + + public: + explicit RedirectPair(const ResourceRequest& request, + const ResourceResponse& redirect_response) + : request_(request), redirect_response_(redirect_response) {} + + ResourceRequest request_; + ResourceResponse redirect_response_; + }; + const Vector<RedirectPair>& RedirectChain() const { return redirect_chain_; } + + virtual void DestroyDecodedDataIfPossible() {} + + // Returns the memory dump name used for tracing. See Resource::OnMemoryDump. + String GetMemoryDumpName() const; + + const HeapHashCountedSet<WeakMember<ResourceClient>>& Clients() const { + return clients_; + } + + void SetCachePolicyBypassingCache(); + void SetPreviewsState(WebURLRequest::PreviewsState); + void ClearRangeRequestHeader(); + + SharedBuffer* Data() const { return data_.get(); } + void ClearData(); + + virtual void SetEncoding(const String&) {} + + // Create a handler for the cached metadata of this resource. Subclasses of + // Resource that support cached metadata should override this method with one + // that creates an appropriate CachedMetadataHandler implementation, and + // override SetSerializedCachedMetadata with an implementation that fills the + // cache handler. + virtual CachedMetadataHandler* CreateCachedMetadataHandler( + std::unique_ptr<CachedMetadataSender> send_callback) { + return nullptr; + } + + CachedMetadataHandler* CacheHandler() { return cache_handler_.Get(); } + + private: + // To allow access to SetCORSStatus + friend class ResourceLoader; + friend class SubresourceIntegrityTest; + + void RevalidationSucceeded(const ResourceResponse&); + void RevalidationFailed(); + + size_t CalculateOverheadSize() const; + + String ReasonNotDeletable() const; + + void SetCORSStatus(const CORSStatus cors_status) { + cors_status_ = cors_status; + } + + // MemoryCoordinatorClient overrides: + void OnPurgeMemory() override; + + void CheckResourceIntegrity(); + void TriggerNotificationForFinishObservers(base::SingleThreadTaskRunner*); + + // Helper for creating the send callback function for the cached metadata + // handler. + std::unique_ptr<CachedMetadataSender> CreateCachedMetadataSender() const; + + Type type_; + ResourceStatus status_; + + // A SecurityOrigin representing the origin from which the loading of the + // Resource was initiated. This is calculated and set on Resource creation. + // + // Unlike |security_origin| on |options_|, which: + // - holds a SecurityOrigin to override the FetchContext's SecurityOrigin + // (in case of e.g. that the script initiated the loading is in an isolated + // world) + // + // Used for isolating resources for different origins in the MemoryCache. + // + // Note: A Resource returned from the memory cache has an origin for the first + // initiator that fetched the Resource. It may be different from the origin + // that you need for any runtime security check in Blink. + scoped_refptr<const SecurityOrigin> source_origin_; + + CORSStatus cors_status_; + + Member<CachedMetadataHandler> cache_handler_; + + Optional<ResourceError> error_; + + double load_finish_time_; + + unsigned long identifier_; + + double preload_discovery_time_; + + size_t encoded_size_; + size_t encoded_size_memory_usage_; + size_t decoded_size_; + + // Resource::CalculateOverheadSize() is affected by changes in + // |m_resourceRequest.url()|, but |m_overheadSize| is not updated after + // initial |m_resourceRequest| is given, to reduce MemoryCache manipulation + // and thus potential bugs. crbug.com/594644 + const size_t overhead_size_; + + String cache_identifier_; + + bool link_preload_; + bool is_revalidating_; + bool is_alive_; + bool is_add_remove_client_prohibited_; + bool is_revalidation_start_forbidden_ = false; + bool is_unused_preload_ = false; + bool detachable_ = false; + + ResourceIntegrityDisposition integrity_disposition_; + SubresourceIntegrity::ReportInfo integrity_report_info_; + + // Ordered list of all redirects followed while fetching this resource. + Vector<RedirectPair> redirect_chain_; + + HeapHashCountedSet<WeakMember<ResourceClient>> clients_; + HeapHashCountedSet<WeakMember<ResourceClient>> clients_awaiting_callback_; + HeapHashCountedSet<WeakMember<ResourceClient>> finished_clients_; + HeapHashSet<WeakMember<ResourceFinishObserver>> finish_observers_; + + ResourceLoaderOptions options_; + + double response_timestamp_; + + TaskHandle async_finish_pending_clients_task_; + + ResourceRequest resource_request_; + Member<ResourceLoader> loader_; + ResourceResponse response_; + + scoped_refptr<SharedBuffer> data_; + + WebScopedVirtualTimePauser virtual_time_pauser_; +}; + +class ResourceFactory { + STACK_ALLOCATED(); + + public: + virtual Resource* Create(const ResourceRequest&, + const ResourceLoaderOptions&, + const TextResourceDecoderOptions&) const = 0; + + Resource::Type GetType() const { return type_; } + TextResourceDecoderOptions::ContentType ContentType() const { + return content_type_; + } + + protected: + explicit ResourceFactory(Resource::Type type, + TextResourceDecoderOptions::ContentType content_type) + : type_(type), content_type_(content_type) {} + + Resource::Type type_; + TextResourceDecoderOptions::ContentType content_type_; +}; + +class NonTextResourceFactory : public ResourceFactory { + protected: + explicit NonTextResourceFactory(Resource::Type type) + : ResourceFactory(type, TextResourceDecoderOptions::kPlainTextContent) {} + + virtual Resource* Create(const ResourceRequest&, + const ResourceLoaderOptions&) const = 0; + + Resource* Create(const ResourceRequest& request, + const ResourceLoaderOptions& options, + const TextResourceDecoderOptions&) const final { + return Create(request, options); + } +}; + +#define DEFINE_RESOURCE_TYPE_CASTS(typeName) \ + DEFINE_TYPE_CASTS(typeName##Resource, Resource, resource, \ + resource->GetType() == Resource::k##typeName, \ + resource.GetType() == Resource::k##typeName); + +} // namespace blink + +#endif diff --git a/chromium/third_party/blink/renderer/platform/loader/fetch/resource_client.h b/chromium/third_party/blink/renderer/platform/loader/fetch/resource_client.h new file mode 100644 index 00000000000..f4476b61496 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/loader/fetch/resource_client.h @@ -0,0 +1,113 @@ +/* + Copyright (C) 1998 Lars Knoll (knoll@mpi-hd.mpg.de) + Copyright (C) 2001 Dirk Mueller <mueller@kde.org> + Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011 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_PLATFORM_LOADER_FETCH_RESOURCE_CLIENT_H_ +#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_LOADER_FETCH_RESOURCE_CLIENT_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/platform_export.h" +#include "third_party/blink/renderer/platform/wtf/forward.h" +#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h" + +namespace blink { + +class PLATFORM_EXPORT ResourceClient : public GarbageCollectedMixin { + USING_PRE_FINALIZER(ResourceClient, ClearResource); + + public: + enum ResourceClientType { + kBaseResourceType, + kFontType, + kRawResourceType + }; + + virtual ~ResourceClient() = default; + + // DataReceived() is called each time a chunk of data is received. + // For cache hits, the data is replayed before NotifyFinished() is called. + // For successful revalidation responses, the data is NOT replayed, because + // the Resource may not be in an entirely consistent state in the middle of + // completing the revalidation, when DataReceived() would have to be called. + // Some RawResourceClients depends on receiving all bytes via DataReceived(), + // but RawResources forbid revalidation attempts, so they still are guaranteed + // to get all data via DataReceived(). + virtual void DataReceived(Resource*, + const char* /* data */, + size_t /* length */) {} + virtual void NotifyFinished(Resource*) {} + + static bool IsExpectedType(ResourceClient*) { return true; } + virtual ResourceClientType GetResourceClientType() const { + return kBaseResourceType; + } + + Resource* GetResource() const { return resource_; } + + // Name for debugging, e.g. shown in memory-infra. + virtual String DebugName() const = 0; + + void Trace(blink::Visitor* visitor) override { visitor->Trace(resource_); } + + protected: + ResourceClient() = default; + + void ClearResource() { SetResource(nullptr, nullptr); } + + private: + // ResourceFetcher is primarily responsible for calling SetResource() with a + // non-null Resource*. ResourceClient subclasses are responsible for calling + // ClearResource(). + friend class ResourceFetcher; + // TODO(japhet): There isn't a clean way for SVGResourceClients to determine + // whether SVGElementProxy is holding a Resource that it should register with, + // so SVGElementProxy handles it for those clients. SVGResourceClients should + // have a better way to register themselves as clients. crbug.com/789198 + friend class SVGElementProxy; + // CSSFontFaceSrcValue only ever requests a Resource once, and acts as an + // intermediate caching layer of sorts. It needs to be able to register + // additional clients. + friend class CSSFontFaceSrcValue; + + void SetResource(Resource* new_resource, + base::SingleThreadTaskRunner* task_runner) { + if (new_resource == resource_) + return; + + // Some ResourceClient implementations reenter this so + // we need to prevent double removal. + if (Resource* old_resource = resource_.Release()) + old_resource->RemoveClient(this); + resource_ = new_resource; + if (resource_) + resource_->AddClient(this, task_runner); + } + + Member<Resource> resource_; +}; + +} // namespace blink + +#endif diff --git a/chromium/third_party/blink/renderer/platform/loader/fetch/resource_client_walker.h b/chromium/third_party/blink/renderer/platform/loader/fetch/resource_client_walker.h new file mode 100644 index 00000000000..33e7c8c8368 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/loader/fetch/resource_client_walker.h @@ -0,0 +1,69 @@ +/* + Copyright (C) 1998 Lars Knoll (knoll@mpi-hd.mpg.de) + Copyright (C) 2001 Dirk Mueller <mueller@kde.org> + 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. + + 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_PLATFORM_LOADER_FETCH_RESOURCE_CLIENT_WALKER_H_ +#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_LOADER_FETCH_RESOURCE_CLIENT_WALKER_H_ + +#include "third_party/blink/renderer/platform/heap/handle.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource_client.h" +#include "third_party/blink/renderer/platform/wtf/allocator.h" + +namespace blink { + +// Call this "walker" instead of iterator so people won't expect Qt or STL-style +// iterator interface. Just keep calling next() on this. It's safe from +// deletions of items. +template <typename T> +class ResourceClientWalker { + STACK_ALLOCATED(); + + public: + explicit ResourceClientWalker( + const HeapHashCountedSet<WeakMember<ResourceClient>>& set) + : client_set_(set) { + CopyToVector(client_set_, client_vector_); + } + + T* Next() { + size_t size = client_vector_.size(); + while (index_ < size) { + ResourceClient* next = client_vector_[index_++]; + DCHECK(next); + if (client_set_.Contains(next)) { + DCHECK(T::IsExpectedType(next)); + return static_cast<T*>(next); + } + } + return nullptr; + } + + private: + const HeapHashCountedSet<WeakMember<ResourceClient>>& client_set_; + HeapVector<Member<ResourceClient>> client_vector_; + size_t index_ = 0; +}; + +} // namespace blink + +#endif diff --git a/chromium/third_party/blink/renderer/platform/loader/fetch/resource_error.cc b/chromium/third_party/blink/renderer/platform/loader/fetch/resource_error.cc new file mode 100644 index 00000000000..4a4c0e1f55f --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/loader/fetch/resource_error.cc @@ -0,0 +1,190 @@ +/* + * Copyright (C) 2006 Apple Computer, Inc. All rights reserved. + * 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: + * 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/platform/loader/fetch/resource_error.h" + +#include "net/base/net_errors.h" +#include "third_party/blink/public/platform/platform.h" +#include "third_party/blink/public/platform/web_string.h" +#include "third_party/blink/public/platform/web_url.h" +#include "third_party/blink/public/platform/web_url_error.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource_request.h" + +namespace blink { + +namespace { +constexpr char kThrottledErrorDescription[] = + "Request throttled. Visit http://dev.chromium.org/throttling for more " + "information."; +} // namespace + +int ResourceError::BlockedByXSSAuditorErrorCode() { + return net::ERR_BLOCKED_BY_XSS_AUDITOR; +} + +ResourceError ResourceError::CancelledError(const KURL& url) { + return ResourceError(net::ERR_ABORTED, url, WTF::nullopt); +} + +ResourceError ResourceError::CancelledDueToAccessCheckError( + const KURL& url, + ResourceRequestBlockedReason blocked_reason) { + ResourceError error = CancelledError(url); + error.is_access_check_ = true; + error.should_collapse_initiator_ = + blocked_reason == ResourceRequestBlockedReason::kSubresourceFilter; + return error; +} + +ResourceError ResourceError::CancelledDueToAccessCheckError( + const KURL& url, + ResourceRequestBlockedReason blocked_reason, + const String& localized_description) { + ResourceError error = CancelledDueToAccessCheckError(url, blocked_reason); + error.localized_description_ = localized_description; + return error; +} + +ResourceError ResourceError::CacheMissError(const KURL& url) { + return ResourceError(net::ERR_CACHE_MISS, url, WTF::nullopt); +} + +ResourceError ResourceError::TimeoutError(const KURL& url) { + return ResourceError(net::ERR_TIMED_OUT, url, WTF::nullopt); +} + +ResourceError ResourceError::Failure(const KURL& url) { + return ResourceError(net::ERR_FAILED, url, WTF::nullopt); +} + +ResourceError::ResourceError( + int error_code, + const KURL& url, + WTF::Optional<network::CORSErrorStatus> cors_error_status) + : error_code_(error_code), + failing_url_(url), + cors_error_status_(cors_error_status) { + DCHECK_NE(error_code_, 0); + InitializeDescription(); +} + +ResourceError::ResourceError(const WebURLError& error) + : error_code_(error.reason()), + extended_error_code_(error.extended_reason()), + failing_url_(error.url()), + is_access_check_(error.is_web_security_violation()), + has_copy_in_cache_(error.has_copy_in_cache()), + cors_error_status_(error.cors_error_status()) { + DCHECK_NE(error_code_, 0); + InitializeDescription(); +} + +ResourceError ResourceError::Copy() const { + ResourceError error_copy(error_code_, failing_url_.Copy(), + cors_error_status_); + error_copy.extended_error_code_ = extended_error_code_; + error_copy.has_copy_in_cache_ = has_copy_in_cache_; + error_copy.localized_description_ = localized_description_.IsolatedCopy(); + error_copy.is_access_check_ = is_access_check_; + return error_copy; +} + +ResourceError::operator WebURLError() const { + WebURLError::HasCopyInCache has_copy_in_cache = + has_copy_in_cache_ ? WebURLError::HasCopyInCache::kTrue + : WebURLError::HasCopyInCache::kFalse; + + if (cors_error_status_) { + DCHECK_EQ(net::ERR_FAILED, error_code_); + return WebURLError(*cors_error_status_, has_copy_in_cache, failing_url_); + } + + return WebURLError(error_code_, extended_error_code_, has_copy_in_cache, + is_access_check_ + ? WebURLError::IsWebSecurityViolation::kTrue + : WebURLError::IsWebSecurityViolation::kFalse, + failing_url_); +} + +bool ResourceError::Compare(const ResourceError& a, const ResourceError& b) { + if (a.ErrorCode() != b.ErrorCode()) + return false; + + if (a.FailingURL() != b.FailingURL()) + return false; + + if (a.LocalizedDescription() != b.LocalizedDescription()) + return false; + + if (a.IsAccessCheck() != b.IsAccessCheck()) + return false; + + if (a.HasCopyInCache() != b.HasCopyInCache()) + return false; + + if (a.CORSErrorStatus() != b.CORSErrorStatus()) + return false; + + return true; +} + +bool ResourceError::IsTimeout() const { + return error_code_ == net::ERR_TIMED_OUT; +} + +bool ResourceError::IsCancellation() const { + return error_code_ == net::ERR_ABORTED; +} + +bool ResourceError::IsCacheMiss() const { + return error_code_ == net::ERR_CACHE_MISS; +} + +bool ResourceError::WasBlockedByResponse() const { + return error_code_ == net::ERR_BLOCKED_BY_RESPONSE; +} + +void ResourceError::InitializeDescription() { + if (error_code_ == net::ERR_TEMPORARILY_THROTTLED) { + localized_description_ = WebString::FromASCII(kThrottledErrorDescription); + } else { + localized_description_ = WebString::FromASCII( + net::ExtendedErrorToString(error_code_, extended_error_code_)); + } +} + +std::ostream& operator<<(std::ostream& os, const ResourceError& error) { + return os << ", ErrorCode = " << error.ErrorCode() + << ", FailingURL = " << error.FailingURL() + << ", LocalizedDescription = " << error.LocalizedDescription() + << ", IsCancellation = " << error.IsCancellation() + << ", IsAccessCheck = " << error.IsAccessCheck() + << ", IsTimeout = " << error.IsTimeout() + << ", HasCopyInCache = " << error.HasCopyInCache() + << ", IsCacheMiss = " << error.IsCacheMiss(); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/platform/loader/fetch/resource_error.h b/chromium/third_party/blink/renderer/platform/loader/fetch/resource_error.h new file mode 100644 index 00000000000..ea6e0f6acb2 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/loader/fetch/resource_error.h @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2006 Apple Computer, Inc. All rights reserved. + * 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: + * 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_PLATFORM_LOADER_FETCH_RESOURCE_ERROR_H_ +#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_LOADER_FETCH_RESOURCE_ERROR_H_ + +#include <iosfwd> +#include "services/network/public/cpp/cors/cors_error_status.h" +#include "third_party/blink/public/platform/web_url_error.h" +#include "third_party/blink/renderer/platform/platform_export.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/optional.h" +#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h" + +namespace blink { + +enum class ResourceRequestBlockedReason; + +// ResourceError represents an error for loading a resource. There is no +// "no-error" instance. Use Optional for nullable errors. +class PLATFORM_EXPORT ResourceError final { + DISALLOW_NEW_EXCEPT_PLACEMENT_NEW(); + + public: + static ResourceError CancelledError(const KURL&); + static ResourceError CancelledDueToAccessCheckError( + const KURL&, + ResourceRequestBlockedReason); + static ResourceError CancelledDueToAccessCheckError( + const KURL&, + ResourceRequestBlockedReason, + const String& localized_description); + + static ResourceError CacheMissError(const KURL&); + static ResourceError TimeoutError(const KURL&); + static ResourceError Failure(const KURL&); + + ResourceError() = delete; + // |error_code| must not be 0. + ResourceError(int error_code, + const KURL& failing_url, + WTF::Optional<network::CORSErrorStatus>); + ResourceError(const WebURLError&); + + // Makes a deep copy. Useful for when you need to use a ResourceError on + // another thread. + ResourceError Copy() const; + + int ErrorCode() const { return error_code_; } + const String& FailingURL() const { return failing_url_; } + const String& LocalizedDescription() const { return localized_description_; } + + bool IsCancellation() const; + bool IsAccessCheck() const { return is_access_check_; } + bool HasCopyInCache() const { return has_copy_in_cache_; } + bool IsTimeout() const; + bool IsCacheMiss() const; + bool WasBlockedByResponse() const; + bool ShouldCollapseInitiator() const { return should_collapse_initiator_; } + + WTF::Optional<network::CORSErrorStatus> CORSErrorStatus() const { + return cors_error_status_; + } + + operator WebURLError() const; + + static bool Compare(const ResourceError&, const ResourceError&); + + // Net error code getters are here to avoid unpreferred header inclusion. + static int BlockedByXSSAuditorErrorCode(); + + private: + void InitializeDescription(); + + int error_code_; + int extended_error_code_; + KURL failing_url_; + String localized_description_; + bool is_access_check_ = false; + bool has_copy_in_cache_ = false; + bool should_collapse_initiator_ = false; + WTF::Optional<network::CORSErrorStatus> cors_error_status_; +}; + +inline bool operator==(const ResourceError& a, const ResourceError& b) { + return ResourceError::Compare(a, b); +} +inline bool operator!=(const ResourceError& a, const ResourceError& b) { + return !(a == b); +} + +PLATFORM_EXPORT std::ostream& operator<<(std::ostream&, const ResourceError&); + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_PLATFORM_LOADER_FETCH_RESOURCE_ERROR_H_ diff --git a/chromium/third_party/blink/renderer/platform/loader/fetch/resource_fetcher.cc b/chromium/third_party/blink/renderer/platform/loader/fetch/resource_fetcher.cc new file mode 100644 index 00000000000..7a5b12a467f --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/loader/fetch/resource_fetcher.cc @@ -0,0 +1,1700 @@ +/* + Copyright (C) 1998 Lars Knoll (knoll@mpi-hd.mpg.de) + Copyright (C) 2001 Dirk Mueller (mueller@kde.org) + Copyright (C) 2002 Waldo Bastian (bastian@kde.org) + Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011 Apple Inc. All + rights reserved. + Copyright (C) 2009 Torch Mobile Inc. http://www.torchmobile.com/ + + 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/platform/loader/fetch/resource_fetcher.h" + +#include <algorithm> +#include <limits> +#include <utility> + +#include "base/time/time.h" +#include "third_party/blink/public/platform/modules/fetch/fetch_api_request.mojom-blink.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_request.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/instrumentation/tracing/traced_value.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/raw_resource.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource_loader.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/archive_resource.h" +#include "third_party/blink/renderer/platform/mhtml/mhtml_archive.h" +#include "third_party/blink/renderer/platform/network/network_instrumentation.h" +#include "third_party/blink/renderer/platform/network/network_utils.h" +#include "third_party/blink/renderer/platform/probe/platform_probes.h" +#include "third_party/blink/renderer/platform/runtime_enabled_features.h" +#include "third_party/blink/renderer/platform/scheduler/child/web_scheduler.h" +#include "third_party/blink/renderer/platform/weborigin/known_ports.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/weborigin/security_violation_reporting_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/text/wtf_string.h" + +using blink::WebURLRequest; + +namespace blink { + +constexpr uint32_t ResourceFetcher::kKeepaliveInflightBytesQuota; + +namespace { + +constexpr base::TimeDelta kKeepaliveLoadersTimeout = + base::TimeDelta::FromSeconds(30); + +#define DEFINE_SINGLE_RESOURCE_HISTOGRAM(prefix, name) \ + case Resource::k##name: { \ + DEFINE_THREAD_SAFE_STATIC_LOCAL( \ + EnumerationHistogram, resource_histogram, \ + ("Blink.MemoryCache.RevalidationPolicy." prefix #name, kLoad + 1)); \ + resource_histogram.Count(policy); \ + break; \ + } + +#define DEFINE_RESOURCE_HISTOGRAM(prefix) \ + switch (factory.GetType()) { \ + DEFINE_SINGLE_RESOURCE_HISTOGRAM(prefix, CSSStyleSheet) \ + DEFINE_SINGLE_RESOURCE_HISTOGRAM(prefix, Font) \ + DEFINE_SINGLE_RESOURCE_HISTOGRAM(prefix, Image) \ + DEFINE_SINGLE_RESOURCE_HISTOGRAM(prefix, ImportResource) \ + DEFINE_SINGLE_RESOURCE_HISTOGRAM(prefix, LinkPrefetch) \ + DEFINE_SINGLE_RESOURCE_HISTOGRAM(prefix, MainResource) \ + DEFINE_SINGLE_RESOURCE_HISTOGRAM(prefix, Manifest) \ + DEFINE_SINGLE_RESOURCE_HISTOGRAM(prefix, Audio) \ + DEFINE_SINGLE_RESOURCE_HISTOGRAM(prefix, Video) \ + DEFINE_SINGLE_RESOURCE_HISTOGRAM(prefix, Mock) \ + DEFINE_SINGLE_RESOURCE_HISTOGRAM(prefix, Raw) \ + DEFINE_SINGLE_RESOURCE_HISTOGRAM(prefix, Script) \ + DEFINE_SINGLE_RESOURCE_HISTOGRAM(prefix, SVGDocument) \ + DEFINE_SINGLE_RESOURCE_HISTOGRAM(prefix, TextTrack) \ + DEFINE_SINGLE_RESOURCE_HISTOGRAM(prefix, XSLStyleSheet) \ + } + +void AddRedirectsToTimingInfo(Resource* resource, ResourceTimingInfo* info) { + // Store redirect responses that were packed inside the final response. + const auto& responses = resource->GetResponse().RedirectResponses(); + for (size_t i = 0; i < responses.size(); ++i) { + const KURL& new_url = i + 1 < responses.size() + ? KURL(responses[i + 1].Url()) + : resource->GetResourceRequest().Url(); + bool cross_origin = + !SecurityOrigin::AreSameSchemeHostPort(responses[i].Url(), new_url); + info->AddRedirect(responses[i], cross_origin); + } +} + +ResourceLoadPriority TypeToPriority(Resource::Type type) { + switch (type) { + case Resource::kMainResource: + case Resource::kCSSStyleSheet: + case Resource::kFont: + // Also parser-blocking scripts (set explicitly in loadPriority) + return ResourceLoadPriority::kVeryHigh; + case Resource::kXSLStyleSheet: + DCHECK(RuntimeEnabledFeatures::XSLTEnabled()); + FALLTHROUGH; + case Resource::kRaw: + case Resource::kImportResource: + case Resource::kScript: + // Also visible resources/images (set explicitly in loadPriority) + return ResourceLoadPriority::kHigh; + case Resource::kManifest: + case Resource::kMock: + // Also late-body scripts discovered by the preload scanner (set + // explicitly in loadPriority) + return ResourceLoadPriority::kMedium; + case Resource::kImage: + case Resource::kTextTrack: + case Resource::kAudio: + case Resource::kVideo: + case Resource::kSVGDocument: + // Also async scripts (set explicitly in loadPriority) + return ResourceLoadPriority::kLow; + case Resource::kLinkPrefetch: + return ResourceLoadPriority::kVeryLow; + } + + NOTREACHED(); + return ResourceLoadPriority::kUnresolved; +} + +bool ShouldResourceBeAddedToMemoryCache(const FetchParameters& params, + Resource* resource) { + if (!IsMainThread()) + return false; + if (params.Options().data_buffering_policy == kDoNotBufferData) + return false; + if (IsRawResource(*resource)) + return false; + return true; +} + +} // namespace + +ResourceLoadPriority ResourceFetcher::ComputeLoadPriority( + Resource::Type type, + const ResourceRequest& resource_request, + ResourcePriority::VisibilityStatus visibility, + FetchParameters::DeferOption defer_option, + FetchParameters::SpeculativePreloadType speculative_preload_type, + bool is_link_preload) { + ResourceLoadPriority priority = TypeToPriority(type); + + // Visible resources (images in practice) get a boost to High priority. + if (visibility == ResourcePriority::kVisible) + priority = ResourceLoadPriority::kHigh; + + // Resources before the first image are considered "early" in the document and + // resources after the first image are "late" in the document. Important to + // note that this is based on when the preload scanner discovers a resource + // for the most part so the main parser may not have reached the image element + // yet. + if (type == Resource::kImage && !is_link_preload) + image_fetched_ = true; + + // A preloaded font should not take precedence over critical CSS or + // parser-blocking scripts. + if (type == Resource::kFont && is_link_preload) + priority = ResourceLoadPriority::kHigh; + + if (FetchParameters::kIdleLoad == defer_option) { + priority = ResourceLoadPriority::kVeryLow; + } else if (type == Resource::kScript) { + // Special handling for scripts. + // Default/Parser-Blocking/Preload early in document: High (set in + // typeToPriority) + // Async/Defer: Low Priority (applies to both preload and parser-inserted) + // Preload late in document: Medium + if (FetchParameters::kLazyLoad == defer_option) { + priority = ResourceLoadPriority::kLow; + } else if (speculative_preload_type == + FetchParameters::SpeculativePreloadType::kInDocument && + image_fetched_) { + // Speculative preload is used as a signal for scripts at the bottom of + // the document. + priority = ResourceLoadPriority::kMedium; + } + } else if (FetchParameters::kLazyLoad == defer_option) { + priority = ResourceLoadPriority::kVeryLow; + } else if (resource_request.GetRequestContext() == + WebURLRequest::kRequestContextBeacon || + resource_request.GetRequestContext() == + WebURLRequest::kRequestContextPing || + resource_request.GetRequestContext() == + WebURLRequest::kRequestContextCSPReport) { + priority = ResourceLoadPriority::kVeryLow; + } + + // A manually set priority acts as a floor. This is used to ensure that + // synchronous requests are always given the highest possible priority, as + // well as to ensure that there isn't priority churn if images move in and out + // of the viewport, or are displayed more than once, both in and out of the + // viewport. + return std::max(Context().ModifyPriorityForExperiments(priority), + resource_request.Priority()); +} + +static void PopulateTimingInfo(ResourceTimingInfo* info, Resource* resource) { + KURL initial_url = resource->GetResponse().RedirectResponses().IsEmpty() + ? resource->GetResourceRequest().Url() + : resource->GetResponse().RedirectResponses()[0].Url(); + info->SetInitialURL(initial_url); + info->SetFinalResponse(resource->GetResponse()); +} + +WebURLRequest::RequestContext ResourceFetcher::DetermineRequestContext( + Resource::Type type, + IsImageSet is_image_set, + bool is_main_frame) { + DCHECK((is_image_set == kImageNotImageSet) || + (type == Resource::kImage && is_image_set == kImageIsImageSet)); + switch (type) { + case Resource::kMainResource: + if (!is_main_frame) + return WebURLRequest::kRequestContextIframe; + // FIXME: Change this to a context frame type (once we introduce them): + // http://fetch.spec.whatwg.org/#concept-request-context-frame-type + return WebURLRequest::kRequestContextHyperlink; + case Resource::kXSLStyleSheet: + DCHECK(RuntimeEnabledFeatures::XSLTEnabled()); + FALLTHROUGH; + case Resource::kCSSStyleSheet: + return WebURLRequest::kRequestContextStyle; + case Resource::kScript: + return WebURLRequest::kRequestContextScript; + case Resource::kFont: + return WebURLRequest::kRequestContextFont; + case Resource::kImage: + if (is_image_set == kImageIsImageSet) + return WebURLRequest::kRequestContextImageSet; + return WebURLRequest::kRequestContextImage; + case Resource::kRaw: + return WebURLRequest::kRequestContextSubresource; + case Resource::kImportResource: + return WebURLRequest::kRequestContextImport; + case Resource::kLinkPrefetch: + return WebURLRequest::kRequestContextPrefetch; + case Resource::kTextTrack: + return WebURLRequest::kRequestContextTrack; + case Resource::kSVGDocument: + return WebURLRequest::kRequestContextImage; + case Resource::kAudio: + return WebURLRequest::kRequestContextAudio; + case Resource::kVideo: + return WebURLRequest::kRequestContextVideo; + case Resource::kManifest: + return WebURLRequest::kRequestContextManifest; + case Resource::kMock: + return WebURLRequest::kRequestContextSubresource; + } + NOTREACHED(); + return WebURLRequest::kRequestContextSubresource; +} + +ResourceFetcher::ResourceFetcher(FetchContext* new_context) + : context_(new_context), + scheduler_(ResourceLoadScheduler::Create(&Context())), + archive_(Context().IsMainFrame() ? nullptr : Context().Archive()), + resource_timing_report_timer_( + Context().GetLoadingTaskRunner(), + this, + &ResourceFetcher::ResourceTimingReportTimerFired), + auto_load_images_(true), + images_enabled_(true), + allow_stale_resources_(false), + image_fetched_(false) { + InstanceCounters::IncrementCounter(InstanceCounters::kResourceFetcherCounter); +} + +ResourceFetcher::~ResourceFetcher() { + InstanceCounters::DecrementCounter(InstanceCounters::kResourceFetcherCounter); +} + +Resource* ResourceFetcher::CachedResource(const KURL& resource_url) const { + KURL url = MemoryCache::RemoveFragmentIdentifierIfNeeded(resource_url); + const WeakMember<Resource>& resource = cached_resources_map_.at(url); + return resource.Get(); +} + +void ResourceFetcher::HoldResourcesFromPreviousFetcher( + ResourceFetcher* old_fetcher) { + DCHECK(resources_from_previous_fetcher_.IsEmpty()); + for (Resource* resource : old_fetcher->document_resources_) { + if (GetMemoryCache()->Contains(resource)) + resources_from_previous_fetcher_.insert(resource); + } +} + +void ResourceFetcher::ClearResourcesFromPreviousFetcher() { + resources_from_previous_fetcher_.clear(); +} + +bool ResourceFetcher::IsControlledByServiceWorker() const { + return Context().IsControlledByServiceWorker(); +} + +bool ResourceFetcher::ResourceNeedsLoad(Resource* resource, + const FetchParameters& params, + RevalidationPolicy policy) { + // Defer a font load until it is actually needed unless this is a link + // preload. + if (resource->GetType() == Resource::kFont && !params.IsLinkPreload()) + return false; + + // Defer loading images either when: + // - images are disabled + // - instructed to defer loading images from network + if (resource->GetType() == Resource::kImage && + ShouldDeferImageLoad(resource->Url())) + return false; + + return policy != kUse || resource->StillNeedsLoad(); +} + +void ResourceFetcher::RequestLoadStarted(unsigned long identifier, + Resource* resource, + const FetchParameters& params, + RevalidationPolicy policy, + bool is_static_data) { + KURL url = MemoryCache::RemoveFragmentIdentifierIfNeeded(params.Url()); + if (policy == kUse && resource->GetStatus() == ResourceStatus::kCached && + !cached_resources_map_.Contains(url)) { + // Loaded from MemoryCache. + DidLoadResourceFromMemoryCache(identifier, resource, + params.GetResourceRequest()); + } + + if (is_static_data) + return; + + if (policy == kUse && !resource->StillNeedsLoad() && + !cached_resources_map_.Contains(url)) { + // Resources loaded from memory cache should be reported the first time + // they're used. + scoped_refptr<ResourceTimingInfo> info = ResourceTimingInfo::Create( + params.Options().initiator_info.name, CurrentTimeTicksInSeconds(), + resource->GetType() == Resource::kMainResource); + PopulateTimingInfo(info.get(), resource); + info->ClearLoadTimings(); + info->SetLoadFinishTime(info->InitialTime()); + scheduled_resource_timing_reports_.push_back(std::move(info)); + if (!resource_timing_report_timer_.IsActive()) + resource_timing_report_timer_.StartOneShot(TimeDelta(), FROM_HERE); + } +} + +void ResourceFetcher::DidLoadResourceFromMemoryCache( + unsigned long identifier, + Resource* resource, + const ResourceRequest& original_resource_request) { + ResourceRequest resource_request(resource->Url()); + resource_request.SetFrameType(original_resource_request.GetFrameType()); + resource_request.SetRequestContext( + original_resource_request.GetRequestContext()); + if (original_resource_request.IsAdResource()) + resource_request.SetIsAdResource(); + + Context().DispatchDidLoadResourceFromMemoryCache(identifier, resource_request, + resource->GetResponse()); + Context().DispatchWillSendRequest( + identifier, resource_request, ResourceResponse() /* redirects */, + resource->GetType(), resource->Options().initiator_info); + Context().DispatchDidReceiveResponse( + identifier, resource->GetResponse(), resource_request.GetFrameType(), + resource_request.GetRequestContext(), resource, + FetchContext::ResourceResponseType::kFromMemoryCache); + + if (resource->EncodedSize() > 0) { + Context().DispatchDidReceiveData(identifier, nullptr, + resource->EncodedSize()); + } + + Context().DispatchDidFinishLoading( + identifier, 0, 0, resource->GetResponse().DecodedBodyLength(), false); +} + +static std::unique_ptr<TracedValue> UrlForTraceEvent(const KURL& url) { + std::unique_ptr<TracedValue> value = TracedValue::Create(); + value->SetString("url", url.GetString()); + return value; +} + +Resource* ResourceFetcher::ResourceForStaticData( + const FetchParameters& params, + const ResourceFactory& factory, + const SubstituteData& substitute_data) { + const KURL& url = params.GetResourceRequest().Url(); + DCHECK(url.ProtocolIsData() || substitute_data.IsValid() || archive_); + + // TODO(japhet): We only send main resource data: urls through WebURLLoader + // for the benefit of a service worker test + // (RenderViewImplTest.ServiceWorkerNetworkProviderSetup), which is at a layer + // where it isn't easy to mock out a network load. It uses data: urls to + // emulate the behavior it wants to test, which would otherwise be reserved + // for network loads. + if (!archive_ && !substitute_data.IsValid() && + (factory.GetType() == Resource::kMainResource || + factory.GetType() == Resource::kRaw)) + return nullptr; + + const String cache_identifier = GetCacheIdentifier(); + if (Resource* old_resource = + GetMemoryCache()->ResourceForURL(url, cache_identifier)) { + // There's no reason to re-parse if we saved the data from the previous + // parse. + if (params.Options().data_buffering_policy != kDoNotBufferData) + return old_resource; + GetMemoryCache()->Remove(old_resource); + } + + ResourceResponse response; + scoped_refptr<SharedBuffer> data; + if (substitute_data.IsValid()) { + data = substitute_data.Content(); + response.SetURL(url); + response.SetMimeType(substitute_data.MimeType()); + response.SetExpectedContentLength(data->size()); + response.SetTextEncodingName(substitute_data.TextEncoding()); + } else if (url.ProtocolIsData()) { + data = NetworkUtils::ParseDataURLAndPopulateResponse(url, response); + if (!data) + return nullptr; + // |response| is modified by parseDataURLAndPopulateResponse() and is + // ready to be used. + } else { + ArchiveResource* archive_resource = + archive_->SubresourceForURL(params.Url()); + // The archive doesn't contain the resource, the request must be aborted. + if (!archive_resource) + return nullptr; + data = archive_resource->Data(); + response.SetURL(url); + response.SetMimeType(archive_resource->MimeType()); + response.SetExpectedContentLength(data->size()); + response.SetTextEncodingName(archive_resource->TextEncoding()); + } + + Resource* resource = factory.Create( + params.GetResourceRequest(), params.Options(), params.DecoderOptions()); + // FIXME: We should provide a body stream here. + resource->SetStatus(ResourceStatus::kPending); + resource->NotifyStartLoad(); + resource->ResponseReceived(response, nullptr); + resource->SetDataBufferingPolicy(kBufferData); + if (data->size()) + resource->SetResourceBuffer(data); + resource->SetIdentifier(CreateUniqueIdentifier()); + resource->SetCacheIdentifier(cache_identifier); + resource->SetSourceOrigin(GetSourceOrigin(params.Options())); + resource->Finish(0.0, Context().GetLoadingTaskRunner().get()); + + if (!substitute_data.IsValid()) + AddToMemoryCacheIfNeeded(params, resource); + + return resource; +} + +Resource* ResourceFetcher::ResourceForBlockedRequest( + const FetchParameters& params, + const ResourceFactory& factory, + ResourceRequestBlockedReason blocked_reason) { + Resource* resource = factory.Create( + params.GetResourceRequest(), params.Options(), params.DecoderOptions()); + resource->SetStatus(ResourceStatus::kPending); + resource->NotifyStartLoad(); + resource->SetSourceOrigin(GetSourceOrigin(params.Options())); + resource->FinishAsError(ResourceError::CancelledDueToAccessCheckError( + params.Url(), blocked_reason), + Context().GetLoadingTaskRunner().get()); + return resource; +} + +void ResourceFetcher::MakePreloadedResourceBlockOnloadIfNeeded( + Resource* resource, + const FetchParameters& params) { + // TODO(yoav): Test that non-blocking resources (video/audio/track) continue + // to not-block even after being preloaded and discovered. + if (resource && resource->Loader() && + resource->IsLoadEventBlockingResourceType() && + resource->IsLinkPreload() && !params.IsLinkPreload() && + non_blocking_loaders_.Contains(resource->Loader())) { + non_blocking_loaders_.erase(resource->Loader()); + loaders_.insert(resource->Loader()); + } +} + +void ResourceFetcher::UpdateMemoryCacheStats(Resource* resource, + RevalidationPolicy policy, + const FetchParameters& params, + const ResourceFactory& factory, + bool is_static_data) const { + if (is_static_data) + return; + + if (params.IsSpeculativePreload() || params.IsLinkPreload()) { + DEFINE_RESOURCE_HISTOGRAM("Preload."); + } else { + DEFINE_RESOURCE_HISTOGRAM(""); + } + + // Aims to count Resource only referenced from MemoryCache (i.e. what would be + // dead if MemoryCache holds weak references to Resource). Currently we check + // references to Resource from ResourceClient and |m_preloads| only, because + // they are major sources of references. + if (resource && !resource->IsAlive() && !ContainsAsPreload(resource)) { + DEFINE_RESOURCE_HISTOGRAM("Dead."); + } +} + +bool ResourceFetcher::ContainsAsPreload(Resource* resource) const { + auto it = preloads_.find(PreloadKey(resource->Url(), resource->GetType())); + return it != preloads_.end() && it->value == resource; +} + +void ResourceFetcher::RemovePreload(Resource* resource) { + auto it = preloads_.find(PreloadKey(resource->Url(), resource->GetType())); + if (it == preloads_.end()) + return; + if (it->value == resource) + preloads_.erase(it); +} + +ResourceRequestBlockedReason ResourceFetcher::PrepareRequest( + FetchParameters& params, + const ResourceFactory& factory, + const SubstituteData& substitute_data, + unsigned long identifier) { + ResourceRequest& resource_request = params.MutableResourceRequest(); + Resource::Type resource_type = factory.GetType(); + const ResourceLoaderOptions& options = params.Options(); + + DCHECK(options.synchronous_policy == kRequestAsynchronously || + resource_type == Resource::kRaw || + resource_type == Resource::kXSLStyleSheet); + + params.OverrideContentType(factory.ContentType()); + + // Don't send security violation reports for speculative preloads. + SecurityViolationReportingPolicy reporting_policy = + params.IsSpeculativePreload() + ? SecurityViolationReportingPolicy::kSuppressReporting + : SecurityViolationReportingPolicy::kReport; + + // Note that resource_request.GetRedirectStatus() may return kFollowedRedirect + // here since e.g. DocumentThreadableLoader may create a new Resource from + // a ResourceRequest that originates from the ResourceRequest passed to + // the redirect handling callback. + + // Before modifying the request for CSP, evaluate report-only headers. This + // allows site owners to learn about requests that are being modified + // (e.g. mixed content that is being upgraded by upgrade-insecure-requests). + Context().CheckCSPForRequest( + resource_request.GetRequestContext(), + MemoryCache::RemoveFragmentIdentifierIfNeeded(params.Url()), options, + reporting_policy, resource_request.GetRedirectStatus()); + + // This may modify params.Url() (via the resource_request argument). + Context().PopulateResourceRequest( + resource_type, params.GetClientHintsPreferences(), + params.GetResourceWidth(), resource_request); + + if (!params.Url().IsValid()) + return ResourceRequestBlockedReason::kOther; + + resource_request.SetPriority(ComputeLoadPriority( + resource_type, params.GetResourceRequest(), ResourcePriority::kNotVisible, + params.Defer(), params.GetSpeculativePreloadType(), + params.IsLinkPreload())); + if (resource_request.GetCacheMode() == mojom::FetchCacheMode::kDefault) { + resource_request.SetCacheMode(Context().ResourceRequestCachePolicy( + resource_request, resource_type, params.Defer())); + } + if (resource_request.GetRequestContext() == + WebURLRequest::kRequestContextUnspecified) { + resource_request.SetRequestContext(DetermineRequestContext( + resource_type, kImageNotImageSet, Context().IsMainFrame())); + } + if (resource_type == Resource::kLinkPrefetch) + resource_request.SetHTTPHeaderField(HTTPNames::Purpose, "prefetch"); + + Context().AddAdditionalRequestHeaders( + resource_request, (resource_type == Resource::kMainResource) + ? kFetchMainResource + : kFetchSubresource); + + network_instrumentation::ResourcePrioritySet(identifier, + resource_request.Priority()); + + KURL url = MemoryCache::RemoveFragmentIdentifierIfNeeded(params.Url()); + ResourceRequestBlockedReason blocked_reason = Context().CanRequest( + resource_type, resource_request, url, options, reporting_policy, + params.GetOriginRestriction(), resource_request.GetRedirectStatus()); + + if (Context().IsAdResource(url, resource_type, + resource_request.GetRequestContext())) { + resource_request.SetIsAdResource(); + } + + if (blocked_reason != ResourceRequestBlockedReason::kNone) + return blocked_reason; + + const scoped_refptr<const SecurityOrigin>& origin = options.security_origin; + if (origin && !origin->IsUnique() && + !origin->IsSameSchemeHostPort(Context().GetSecurityOrigin())) { + // |options.security_origin| may differ from the document's origin if + // this is a fetch initiated by an isolated world execution context, with a + // different SecurityOrigin. In this case, plumb it through as the + // RequestorOrigin so that the browser process can make policy decisions for + // this request, based on any special permissions the isolated world may + // have been granted. + // TODO(nick, dcheng): Find a way to formalize the isolated world origin + // check in https://crbug.com/792154. + resource_request.SetRequestorOrigin(origin); + } + + // For initial requests, call prepareRequest() here before revalidation + // policy is determined. + Context().PrepareRequest(resource_request, + FetchContext::RedirectType::kNotForRedirect); + + if (!params.Url().IsValid()) + return ResourceRequestBlockedReason::kOther; + + params.MutableOptions().cors_flag = + !origin || !origin->CanRequest(params.Url()); + + if (options.cors_handling_by_resource_fetcher == + kEnableCORSHandlingByResourceFetcher) { + bool allow_stored_credentials = false; + switch (resource_request.GetFetchCredentialsMode()) { + case network::mojom::FetchCredentialsMode::kOmit: + break; + case network::mojom::FetchCredentialsMode::kSameOrigin: + allow_stored_credentials = !params.Options().cors_flag; + break; + case network::mojom::FetchCredentialsMode::kInclude: + allow_stored_credentials = true; + break; + } + resource_request.SetAllowStoredCredentials(allow_stored_credentials); + } + + return ResourceRequestBlockedReason::kNone; +} + +Resource* ResourceFetcher::RequestResource( + FetchParameters& params, + const ResourceFactory& factory, + ResourceClient* client, + const SubstituteData& substitute_data) { + // Only async requests get ResourceClient callbacks, so sync requests + // shouldn't provide a client. + DCHECK(!client || + params.Options().synchronous_policy == kRequestAsynchronously); + Resource* resource = + RequestResourceInternal(params, factory, substitute_data); + DCHECK(resource); + if (client) + client->SetResource(resource, Context().GetLoadingTaskRunner().get()); + return resource; +} + +Resource* ResourceFetcher::RequestResourceInternal( + FetchParameters& params, + const ResourceFactory& factory, + const SubstituteData& substitute_data) { + unsigned long identifier = CreateUniqueIdentifier(); + ResourceRequest& resource_request = params.MutableResourceRequest(); + network_instrumentation::ScopedResourceLoadTracker + scoped_resource_load_tracker(identifier, resource_request); + SCOPED_BLINK_UMA_HISTOGRAM_TIMER_THREAD_SAFE( + "Blink.Fetch.RequestResourceTime"); + // TODO(dproy): Remove this. http://crbug.com/659666 + TRACE_EVENT1("blink", "ResourceFetcher::requestResource", "url", + UrlForTraceEvent(params.Url())); + + // TODO(crbug.com/123004): Remove once we have enough stats on data URIs that + // contain fragments ('#' characters). + // + // TODO(crbug.com/796173): This call happens before commit for iframes that + // have data URI sources, which causes UKM to miss the metric recording. + if (context_) { + const KURL& url = params.Url(); + if (url.HasFragmentIdentifier() && url.ProtocolIsData()) { + context_->RecordDataUriWithOctothorpe(); + } + } + + ResourceRequestBlockedReason blocked_reason = + PrepareRequest(params, factory, substitute_data, identifier); + if (blocked_reason != ResourceRequestBlockedReason::kNone) + return ResourceForBlockedRequest(params, factory, blocked_reason); + + Resource::Type resource_type = factory.GetType(); + + if (!params.IsSpeculativePreload()) { + // Only log if it's not for speculative preload. + Context().RecordLoadingActivity(resource_request, resource_type, + params.Options().initiator_info.name); + } + + Resource* resource = nullptr; + RevalidationPolicy policy = kLoad; + + bool is_data_url = resource_request.Url().ProtocolIsData(); + bool is_static_data = is_data_url || substitute_data.IsValid() || archive_; + if (is_static_data) { + resource = ResourceForStaticData(params, factory, substitute_data); + if (resource) { + policy = + DetermineRevalidationPolicy(resource_type, params, *resource, true); + } else if (!is_data_url && archive_) { + // Abort the request if the archive doesn't contain the resource, except + // in the case of data URLs which might have resources such as fonts that + // need to be decoded only on demand. These data URLs are allowed to be + // processed using the normal ResourceFetcher machinery. + return ResourceForBlockedRequest(params, factory, + ResourceRequestBlockedReason::kOther); + } + } + + if (!resource) { + resource = MatchPreload(params, resource_type); + if (resource) { + policy = kUse; + // If |params| is for a blocking resource and a preloaded resource is + // found, we may need to make it block the onload event. + MakePreloadedResourceBlockOnloadIfNeeded(resource, params); + } else if (IsMainThread()) { + resource = + GetMemoryCache()->ResourceForURL(params.Url(), GetCacheIdentifier()); + if (resource) { + policy = DetermineRevalidationPolicy(resource_type, params, *resource, + is_static_data); + } + } + } + + UpdateMemoryCacheStats(resource, policy, params, factory, is_static_data); + + switch (policy) { + case kReload: + GetMemoryCache()->Remove(resource); + FALLTHROUGH; + case kLoad: + resource = CreateResourceForLoading(params, factory); + break; + case kRevalidate: + InitializeRevalidation(resource_request, resource); + break; + case kUse: + if (resource->IsLinkPreload() && !params.IsLinkPreload()) + resource->SetLinkPreload(false); + break; + } + DCHECK(resource); + // TODO(yoav): turn to a DCHECK. See https://crbug.com/690632 + CHECK_EQ(resource->GetType(), resource_type); + + if (policy != kUse) + resource->SetIdentifier(identifier); + + // TODO(yoav): It is not clear why preloads are exempt from this check. Can we + // remove the exemption? + if (!params.IsSpeculativePreload() || policy != kUse) { + // When issuing another request for a resource that is already in-flight + // make sure to not demote the priority of the in-flight request. If the new + // request isn't at the same priority as the in-flight request, only allow + // promotions. This can happen when a visible image's priority is increased + // and then another reference to the image is parsed (which would be at a + // lower priority). + if (resource_request.Priority() > resource->GetResourceRequest().Priority()) + resource->DidChangePriority(resource_request.Priority(), 0); + // TODO(yoav): I'd expect the stated scenario to not go here, as its policy + // would be Use. + } + + // If only the fragment identifiers differ, it is the same resource. + DCHECK(EqualIgnoringFragmentIdentifier(resource->Url(), params.Url())); + RequestLoadStarted(identifier, resource, params, policy, is_static_data); + cached_resources_map_.Set( + MemoryCache::RemoveFragmentIdentifierIfNeeded(params.Url()), resource); + document_resources_.insert(resource); + + // Returns with an existing resource if the resource does not need to start + // loading immediately. If revalidation policy was determined as |Revalidate|, + // the resource was already initialized for the revalidation here, but won't + // start loading. + if (ResourceNeedsLoad(resource, params, policy)) { + if (StartLoad(resource)) { + scoped_resource_load_tracker.ResourceLoadContinuesBeyondScope(); + } else { + resource->FinishAsError(ResourceError::CancelledError(params.Url()), + Context().GetLoadingTaskRunner().get()); + } + } + + if (policy != kUse) + InsertAsPreloadIfNecessary(resource, params, resource_type); + + return resource; +} + +void ResourceFetcher::ResourceTimingReportTimerFired(TimerBase* timer) { + DCHECK_EQ(timer, &resource_timing_report_timer_); + Vector<scoped_refptr<ResourceTimingInfo>> timing_reports; + timing_reports.swap(scheduled_resource_timing_reports_); + for (const auto& timing_info : timing_reports) + Context().AddResourceTiming(*timing_info); +} + +void ResourceFetcher::InitializeRevalidation( + ResourceRequest& revalidating_request, + Resource* resource) { + DCHECK(resource); + DCHECK(GetMemoryCache()->Contains(resource)); + DCHECK(resource->IsLoaded()); + DCHECK(resource->CanUseCacheValidator()); + DCHECK(!resource->IsCacheValidator()); + DCHECK(!Context().IsControlledByServiceWorker()); + // RawResource doesn't support revalidation. + CHECK(!IsRawResource(*resource)); + + const AtomicString& last_modified = + resource->GetResponse().HttpHeaderField(HTTPNames::Last_Modified); + const AtomicString& e_tag = + resource->GetResponse().HttpHeaderField(HTTPNames::ETag); + if (!last_modified.IsEmpty() || !e_tag.IsEmpty()) { + DCHECK_NE(mojom::FetchCacheMode::kBypassCache, + revalidating_request.GetCacheMode()); + if (revalidating_request.GetCacheMode() == + mojom::FetchCacheMode::kValidateCache) { + revalidating_request.SetHTTPHeaderField(HTTPNames::Cache_Control, + "max-age=0"); + } + } + if (!last_modified.IsEmpty()) { + revalidating_request.SetHTTPHeaderField(HTTPNames::If_Modified_Since, + last_modified); + } + if (!e_tag.IsEmpty()) + revalidating_request.SetHTTPHeaderField(HTTPNames::If_None_Match, e_tag); + + resource->SetRevalidatingRequest(revalidating_request); +} + +scoped_refptr<const SecurityOrigin> ResourceFetcher::GetSourceOrigin( + const ResourceLoaderOptions& options) const { + if (options.security_origin) + return options.security_origin; + + return Context().GetSecurityOrigin(); +} + +void ResourceFetcher::AddToMemoryCacheIfNeeded(const FetchParameters& params, + Resource* resource) { + if (!ShouldResourceBeAddedToMemoryCache(params, resource)) + return; + + GetMemoryCache()->Add(resource); +} + +Resource* ResourceFetcher::CreateResourceForLoading( + const FetchParameters& params, + const ResourceFactory& factory) { + const String cache_identifier = GetCacheIdentifier(); + DCHECK(!IsMainThread() || + !GetMemoryCache()->ResourceForURL(params.GetResourceRequest().Url(), + cache_identifier)); + + RESOURCE_LOADING_DVLOG(1) << "Loading Resource for " + << params.GetResourceRequest().Url().ElidedString(); + + Resource* resource = factory.Create( + params.GetResourceRequest(), params.Options(), params.DecoderOptions()); + resource->SetLinkPreload(params.IsLinkPreload()); + if (params.IsSpeculativePreload()) { + resource->SetPreloadDiscoveryTime(params.PreloadDiscoveryTime()); + } + resource->SetCacheIdentifier(cache_identifier); + resource->SetSourceOrigin(GetSourceOrigin(params.Options())); + + AddToMemoryCacheIfNeeded(params, resource); + return resource; +} + +void ResourceFetcher::StorePerformanceTimingInitiatorInformation( + Resource* resource) { + const AtomicString& fetch_initiator = resource->Options().initiator_info.name; + if (fetch_initiator == FetchInitiatorTypeNames::internal) + return; + + bool is_main_resource = resource->GetType() == Resource::kMainResource; + + // The request can already be fetched in a previous navigation. Thus + // startTime must be set accordingly. + double start_time = resource->GetResourceRequest().NavigationStartTime() + ? resource->GetResourceRequest().NavigationStartTime() + : CurrentTimeTicksInSeconds(); + + // This buffer is created and populated for providing transferSize + // and redirect timing opt-in information. + if (is_main_resource) { + DCHECK(!navigation_timing_info_); + navigation_timing_info_ = ResourceTimingInfo::Create( + fetch_initiator, start_time, is_main_resource); + } + + scoped_refptr<ResourceTimingInfo> info = + ResourceTimingInfo::Create(fetch_initiator, start_time, is_main_resource); + + if (resource->IsCacheValidator()) { + const AtomicString& timing_allow_origin = + resource->GetResponse().HttpHeaderField(HTTPNames::Timing_Allow_Origin); + if (!timing_allow_origin.IsEmpty()) + info->SetOriginalTimingAllowOrigin(timing_allow_origin); + } + + if (!is_main_resource || + Context().UpdateTimingInfoForIFrameNavigation(info.get())) { + resource_timing_info_map_.insert(resource, std::move(info)); + } +} + +void ResourceFetcher::RecordResourceTimingOnRedirect( + Resource* resource, + const ResourceResponse& redirect_response, + bool cross_origin) { + ResourceTimingInfoMap::iterator it = resource_timing_info_map_.find(resource); + if (it != resource_timing_info_map_.end()) { + it->value->AddRedirect(redirect_response, cross_origin); + } + + if (resource->GetType() == Resource::kMainResource) { + DCHECK(navigation_timing_info_); + navigation_timing_info_->AddRedirect(redirect_response, cross_origin); + } +} + +static bool IsDownloadOrStreamRequest(const ResourceRequest& request) { + // Never use cache entries for DownloadToFile / UseStreamOnResponse requests. + // The data will be delivered through other paths. + return request.DownloadToFile() || request.DownloadToBlob() || + request.UseStreamOnResponse(); +} + +Resource* ResourceFetcher::MatchPreload(const FetchParameters& params, + Resource::Type type) { + auto it = preloads_.find(PreloadKey(params.Url(), type)); + if (it == preloads_.end()) + return nullptr; + + Resource* resource = it->value; + + if (resource->MustRefetchDueToIntegrityMetadata(params)) + return nullptr; + + if (params.IsSpeculativePreload()) + return resource; + if (params.IsLinkPreload()) { + resource->SetLinkPreload(true); + return resource; + } + + const ResourceRequest& request = params.GetResourceRequest(); + if (request.DownloadToFile() || request.DownloadToBlob()) + return nullptr; + + if (IsImageResourceDisallowedToBeReused(*resource) || + !resource->CanReuse(params, GetSourceOrigin(params.Options()))) + return nullptr; + + if (!resource->MatchPreload(params, Context().GetLoadingTaskRunner().get())) + return nullptr; + preloads_.erase(it); + matched_preloads_.push_back(resource); + return resource; +} + +void ResourceFetcher::InsertAsPreloadIfNecessary(Resource* resource, + const FetchParameters& params, + Resource::Type type) { + if (!params.IsSpeculativePreload() && !params.IsLinkPreload()) + return; + // CSP layout tests verify that preloads are subject to access checks by + // seeing if they are in the `preload started` list. Therefore do not add + // them to the list if the load is immediately denied. + if (resource->LoadFailedOrCanceled() && + resource->GetResourceError().IsAccessCheck()) { + return; + } + PreloadKey key(params.Url(), type); + if (preloads_.find(key) != preloads_.end()) + return; + + preloads_.insert(key, resource); + resource->MarkAsPreload(); + if (preloaded_urls_for_test_) + preloaded_urls_for_test_->insert(resource->Url().GetString()); +} + +bool ResourceFetcher::IsImageResourceDisallowedToBeReused( + const Resource& existing_resource) const { + // When images are disabled, don't ever load images, even if the image is + // cached or it is a data: url. In this case: + // - remove the image from the memory cache, and + // - create a new resource but defer loading (this is done by + // ResourceNeedsLoad()). + // + // This condition must be placed before the condition on |is_static_data| to + // prevent loading a data: URL. + // + // TODO(japhet): Can we get rid of one of these settings? + + if (existing_resource.GetType() != Resource::kImage) + return false; + + return !Context().AllowImage(images_enabled_, existing_resource.Url()); +} + +ResourceFetcher::RevalidationPolicy +ResourceFetcher::DetermineRevalidationPolicy( + Resource::Type type, + const FetchParameters& fetch_params, + const Resource& existing_resource, + bool is_static_data) const { + RevalidationPolicy policy = DetermineRevalidationPolicyInternal( + type, fetch_params, existing_resource, is_static_data); + + TRACE_EVENT_INSTANT1("blink", "ResourceFetcher::DetermineRevalidationPolicy", + TRACE_EVENT_SCOPE_THREAD, "revalidationPolicy", policy); + + return policy; +} + +ResourceFetcher::RevalidationPolicy +ResourceFetcher::DetermineRevalidationPolicyInternal( + Resource::Type type, + const FetchParameters& fetch_params, + const Resource& existing_resource, + bool is_static_data) const { + const ResourceRequest& request = fetch_params.GetResourceRequest(); + + if (IsDownloadOrStreamRequest(request)) + return kReload; + + if (IsImageResourceDisallowedToBeReused(existing_resource)) + return kReload; + + // If the existing resource is loading and the associated fetcher is not equal + // to |this|, we must not use the resource. Otherwise, CSP violation may + // happen in redirect handling. + if (existing_resource.Loader() && + existing_resource.Loader()->Fetcher() != this) { + return kReload; + } + + // It's hard to share a not-yet-referenced preloads via MemoryCache correctly. + // A not-yet-matched preloads made by a foreign ResourceFetcher and stored in + // the memory cache could be used without this block. + if ((fetch_params.IsLinkPreload() || fetch_params.IsSpeculativePreload()) && + existing_resource.IsUnusedPreload()) { + return kReload; + } + + // Checks if the resource has an explicit policy about integrity metadata. + // + // This is necessary because ScriptResource and CSSStyleSheetResource objects + // do not keep the raw data around after the source is accessed once, so if + // the resource is accessed from the MemoryCache for a second time, there is + // no way to redo an integrity check. + // + // Thus, Blink implements a scheme where it caches the integrity information + // for those resources after the first time it is checked, and if there is + // another request for that resource, with the same integrity metadata, Blink + // skips the integrity calculation. However, if the integrity metadata is a + // mismatch, the MemoryCache must be skipped here, and a new request for the + // resource must be made to get the raw data. This is expected to be an + // uncommon case, however, as it implies two same-origin requests to the same + // resource, but with different integrity metadata. + if (existing_resource.MustRefetchDueToIntegrityMetadata(fetch_params)) { + return kReload; + } + + // If the same URL has been loaded as a different type, we need to reload. + if (existing_resource.GetType() != type) { + // FIXME: If existingResource is a Preload and the new type is LinkPrefetch + // We really should discard the new prefetch since the preload has more + // specific type information! crbug.com/379893 + // fast/dom/HTMLLinkElement/link-and-subresource-test hits this case. + RESOURCE_LOADING_DVLOG(1) << "ResourceFetcher::DetermineRevalidationPolicy " + "reloading due to type mismatch."; + return kReload; + } + + // If resource was populated from a SubstituteData load or data: url, use it. + // This doesn't necessarily mean that |resource| was just created by using + // ResourceForStaticData(). + if (is_static_data) + return kUse; + + if (!existing_resource.CanReuse(fetch_params, + GetSourceOrigin(fetch_params.Options()))) { + RESOURCE_LOADING_DVLOG(1) << "ResourceFetcher::DetermineRevalidationPolicy " + "reloading due to Resource::CanReuse() " + "returning false."; + return kReload; + } + + // Don't reload resources while pasting. + if (allow_stale_resources_) + return kUse; + + // FORCE_CACHE uses the cache no matter what. + if (request.GetCacheMode() == mojom::FetchCacheMode::kForceCache) + return kUse; + + // Don't reuse resources with Cache-control: no-store. + if (existing_resource.HasCacheControlNoStoreHeader()) { + RESOURCE_LOADING_DVLOG(1) << "ResourceFetcher::DetermineRevalidationPolicy " + "reloading due to Cache-control: no-store."; + return kReload; + } + + // During the initial load, avoid loading the same resource multiple times for + // a single document, even if the cache policies would tell us to. We also + // group loads of the same resource together. Raw resources are exempted, as + // XHRs fall into this category and may have user-set Cache-Control: headers + // or other factors that require separate requests. + if (type != Resource::kRaw) { + if (!Context().IsLoadComplete() && + cached_resources_map_.Contains( + MemoryCache::RemoveFragmentIdentifierIfNeeded( + existing_resource.Url()))) + return kUse; + if (existing_resource.IsLoading()) + return kUse; + } + + // RELOAD always reloads + if (request.GetCacheMode() == mojom::FetchCacheMode::kBypassCache) { + RESOURCE_LOADING_DVLOG(1) << "ResourceFetcher::DetermineRevalidationPolicy " + "reloading due to " + "FetchCacheMode::kBypassCache"; + return kReload; + } + + // We'll try to reload the resource if it failed last time. + if (existing_resource.ErrorOccurred()) { + RESOURCE_LOADING_DVLOG(1) << "ResourceFetcher::DetermineRevalidationPolicy " + "reloading due to resource being in the error " + "state"; + return kReload; + } + + // List of available images logic allows images to be re-used without cache + // validation. We restrict this only to images from memory cache which are the + // same as the version in the current document. + if (type == Resource::kImage && + &existing_resource == CachedResource(request.Url())) { + return kUse; + } + + if (existing_resource.MustReloadDueToVaryHeader(request)) + return kReload; + + // If any of the redirects in the chain to loading the resource were not + // cacheable, we cannot reuse our cached resource. + if (!existing_resource.CanReuseRedirectChain()) { + RESOURCE_LOADING_DVLOG(1) << "ResourceFetcher::DetermineRevalidationPolicy " + "reloading due to an uncacheable redirect"; + return kReload; + } + + // Check if the cache headers requires us to revalidate (cache expiration for + // example). + if (request.GetCacheMode() == mojom::FetchCacheMode::kValidateCache || + existing_resource.MustRevalidateDueToCacheHeaders() || + request.CacheControlContainsNoCache()) { + // Revalidation is harmful for non-matched preloads because it may lead to + // sharing one preloaded resource among multiple ResourceFetchers. + if (existing_resource.IsUnusedPreload()) + return kReload; + + // See if the resource has usable ETag or Last-modified headers. If the page + // is controlled by the ServiceWorker, we choose the Reload policy because + // the revalidation headers should not be exposed to the + // ServiceWorker.(crbug.com/429570) + if (existing_resource.CanUseCacheValidator() && + !Context().IsControlledByServiceWorker()) { + // If the resource is already a cache validator but not started yet, the + // |Use| policy should be applied to subsequent requests. + if (existing_resource.IsCacheValidator()) { + DCHECK(existing_resource.StillNeedsLoad()); + return kUse; + } + return kRevalidate; + } + + // No, must reload. + RESOURCE_LOADING_DVLOG(1) << "ResourceFetcher::DetermineRevalidationPolicy " + "reloading due to missing cache validators."; + return kReload; + } + + return kUse; +} + +void ResourceFetcher::SetAutoLoadImages(bool enable) { + if (enable == auto_load_images_) + return; + + auto_load_images_ = enable; + + if (!auto_load_images_) + return; + + ReloadImagesIfNotDeferred(); +} + +void ResourceFetcher::SetImagesEnabled(bool enable) { + if (enable == images_enabled_) + return; + + images_enabled_ = enable; + + if (!images_enabled_) + return; + + ReloadImagesIfNotDeferred(); +} + +bool ResourceFetcher::ShouldDeferImageLoad(const KURL& url) const { + return !Context().AllowImage(images_enabled_, url) || !auto_load_images_; +} + +void ResourceFetcher::ReloadImagesIfNotDeferred() { + for (Resource* resource : document_resources_) { + if (resource->GetType() == Resource::kImage && resource->StillNeedsLoad() && + !ShouldDeferImageLoad(resource->Url())) + StartLoad(resource); + } +} + +void ResourceFetcher::ClearContext() { + DCHECK(resources_from_previous_fetcher_.IsEmpty()); + scheduler_->Shutdown(); + ClearPreloads(ResourceFetcher::kClearAllPreloads); + context_ = Context().Detach(); + + // Make sure the only requests still going are keepalive requests. + // Callers of ClearContext() should be calling StopFetching() prior + // to this, but it's possible for additional requests to start during + // StopFetching() (e.g., fallback fonts that only trigger when the + // first choice font failed to load). + StopFetching(); + + if (!loaders_.IsEmpty() || !non_blocking_loaders_.IsEmpty()) { + // There are some keepalive requests. + // The use of WrapPersistent creates a reference cycle intentionally, + // to keep the ResourceFetcher and ResourceLoaders alive until the requests + // complete or the timer fires. + keepalive_loaders_task_handle_ = PostDelayedCancellableTask( + *Context().GetLoadingTaskRunner(), FROM_HERE, + WTF::Bind(&ResourceFetcher::StopFetchingIncludingKeepaliveLoaders, + WrapPersistent(this)), + kKeepaliveLoadersTimeout); + } +} + +int ResourceFetcher::BlockingRequestCount() const { + return loaders_.size(); +} + +int ResourceFetcher::NonblockingRequestCount() const { + return non_blocking_loaders_.size(); +} + +int ResourceFetcher::ActiveRequestCount() const { + return loaders_.size() + non_blocking_loaders_.size(); +} + +void ResourceFetcher::EnableIsPreloadedForTest() { + if (preloaded_urls_for_test_) + return; + preloaded_urls_for_test_ = std::make_unique<HashSet<String>>(); + + for (const auto& pair : preloads_) { + Resource* resource = pair.value; + preloaded_urls_for_test_->insert(resource->Url().GetString()); + } +} + +bool ResourceFetcher::IsPreloadedForTest(const KURL& url) const { + DCHECK(preloaded_urls_for_test_); + return preloaded_urls_for_test_->Contains(url.GetString()); +} + +void ResourceFetcher::ClearPreloads(ClearPreloadsPolicy policy) { + Vector<PreloadKey> keys_to_be_removed; + for (const auto& pair : preloads_) { + Resource* resource = pair.value; + if (policy == kClearAllPreloads || !resource->IsLinkPreload()) { + GetMemoryCache()->Remove(resource); + keys_to_be_removed.push_back(pair.key); + } + } + preloads_.RemoveAll(keys_to_be_removed); + + matched_preloads_.clear(); +} + +void ResourceFetcher::WarnUnusedPreloads() { + for (const auto& pair : preloads_) { + Resource* resource = pair.value; + if (resource && resource->IsLinkPreload() && resource->IsUnusedPreload()) { + Context().AddWarningConsoleMessage( + "The resource " + resource->Url().GetString() + + " was preloaded using link preload but not used within a few " + + "seconds from the window's load event. Please make sure it has " + + "an appropriate `as` value and it is preloaded intentionally.", + FetchContext::kJSSource); + } + } +} + +ArchiveResource* ResourceFetcher::CreateArchive(Resource* resource) { + // Only the top-frame can load MHTML. + if (!Context().IsMainFrame()) { + Context().AddErrorConsoleMessage( + "Attempted to load a multipart archive into an subframe: " + + resource->Url().GetString(), + FetchContext::kJSSource); + return nullptr; + } + + archive_ = MHTMLArchive::Create(resource->Url(), resource->ResourceBuffer()); + if (!archive_) { + // Log if attempting to load an invalid archive resource. + Context().AddErrorConsoleMessage( + "Malformed multipart archive: " + resource->Url().GetString(), + FetchContext::kJSSource); + return nullptr; + } + + return archive_->MainResource(); +} + +ResourceTimingInfo* ResourceFetcher::GetNavigationTimingInfo() { + return navigation_timing_info_.get(); +} + +void ResourceFetcher::HandleLoadCompletion(Resource* resource) { + Context().DidLoadResource(resource); + + resource->ReloadIfLoFiOrPlaceholderImage(this, Resource::kReloadIfNeeded); +} + +void ResourceFetcher::HandleLoaderFinish(Resource* resource, + double finish_time, + LoaderFinishType type, + uint32_t inflight_keepalive_bytes, + bool blocked_cross_site_document) { + DCHECK(resource); + + DCHECK_LE(inflight_keepalive_bytes, inflight_keepalive_bytes_); + inflight_keepalive_bytes_ -= inflight_keepalive_bytes; + + ResourceLoader* loader = resource->Loader(); + if (type == kDidFinishFirstPartInMultipart) { + // When loading a multipart resource, make the loader non-block when + // finishing loading the first part. + MoveResourceLoaderToNonBlocking(loader); + } else { + RemoveResourceLoader(loader); + DCHECK(!non_blocking_loaders_.Contains(loader)); + } + DCHECK(!loaders_.Contains(loader)); + + const int64_t encoded_data_length = + resource->GetResponse().EncodedDataLength(); + + if (resource->GetType() == Resource::kMainResource) { + DCHECK(navigation_timing_info_); + // Store redirect responses that were packed inside the final response. + AddRedirectsToTimingInfo(resource, navigation_timing_info_.get()); + if (resource->GetResponse().IsHTTP()) { + PopulateTimingInfo(navigation_timing_info_.get(), resource); + navigation_timing_info_->AddFinalTransferSize( + encoded_data_length == -1 ? 0 : encoded_data_length); + } + } + if (scoped_refptr<ResourceTimingInfo> info = + resource_timing_info_map_.Take(resource)) { + // Store redirect responses that were packed inside the final response. + AddRedirectsToTimingInfo(resource, info.get()); + + if (resource->GetResponse().IsHTTP() && + resource->GetResponse().HttpStatusCode() < 400) { + PopulateTimingInfo(info.get(), resource); + info->SetLoadFinishTime(finish_time); + // encodedDataLength == -1 means "not available". + // TODO(ricea): Find cases where it is not available but the + // PerformanceResourceTiming spec requires it to be available and fix + // them. + info->AddFinalTransferSize( + encoded_data_length == -1 ? 0 : encoded_data_length); + + if (resource->Options().request_initiator_context == kDocumentContext) + Context().AddResourceTiming(*info); + resource->ReportResourceTimingToClients(*info); + } + } + + resource->VirtualTimePauser().UnpauseVirtualTime(); + Context().DispatchDidFinishLoading( + resource->Identifier(), finish_time, encoded_data_length, + resource->GetResponse().DecodedBodyLength(), blocked_cross_site_document); + + if (type == kDidFinishLoading) + resource->Finish(finish_time, Context().GetLoadingTaskRunner().get()); + + HandleLoadCompletion(resource); +} + +void ResourceFetcher::HandleLoaderError(Resource* resource, + const ResourceError& error, + uint32_t inflight_keepalive_bytes) { + DCHECK(resource); + + DCHECK_LE(inflight_keepalive_bytes, inflight_keepalive_bytes_); + inflight_keepalive_bytes_ -= inflight_keepalive_bytes; + + RemoveResourceLoader(resource->Loader()); + + resource_timing_info_map_.Take(resource); + + bool is_internal_request = resource->Options().initiator_info.name == + FetchInitiatorTypeNames::internal; + + resource->VirtualTimePauser().UnpauseVirtualTime(); + Context().DispatchDidFail( + resource->LastResourceRequest().Url(), resource->Identifier(), error, + resource->GetResponse().EncodedDataLength(), is_internal_request); + + if (error.IsCancellation()) + RemovePreload(resource); + resource->FinishAsError(error, Context().GetLoadingTaskRunner().get()); + + HandleLoadCompletion(resource); +} + +void ResourceFetcher::MoveResourceLoaderToNonBlocking(ResourceLoader* loader) { + DCHECK(loader); + // TODO(yoav): Convert CHECK to DCHECK if no crash reports come in. + CHECK(loaders_.Contains(loader)); + non_blocking_loaders_.insert(loader); + loaders_.erase(loader); +} + +bool ResourceFetcher::StartLoad(Resource* resource) { + DCHECK(resource); + DCHECK(resource->StillNeedsLoad()); + + ResourceRequest request(resource->GetResourceRequest()); + ResourceLoader* loader = nullptr; + + { + // Forbids JavaScript/revalidation until start() + // to prevent unintended state transitions. + Resource::RevalidationStartForbiddenScope + revalidation_start_forbidden_scope(resource); + ScriptForbiddenScope script_forbidden_scope; + + if (!Context().ShouldLoadNewResource(resource->GetType()) && + IsMainThread()) { + GetMemoryCache()->Remove(resource); + return false; + } + + ResourceResponse response; + + blink::probe::PlatformSendRequest probe(&Context(), resource->Identifier(), + request, response, + resource->Options().initiator_info); + + if (Context().GetFrameScheduler()) { + WebScopedVirtualTimePauser virtual_time_pauser = + Context().GetFrameScheduler()->CreateWebScopedVirtualTimePauser( + resource->Url().GetString(), + WebScopedVirtualTimePauser::VirtualTaskDuration::kNonInstant); + virtual_time_pauser.PauseVirtualTime(); + resource->VirtualTimePauser() = std::move(virtual_time_pauser); + } + Context().DispatchWillSendRequest(resource->Identifier(), request, response, + resource->GetType(), + resource->Options().initiator_info); + + // TODO(shaochuan): Saving modified ResourceRequest back to |resource|, + // remove once dispatchWillSendRequest() takes const ResourceRequest. + // crbug.com/632580 + resource->SetResourceRequest(request); + + using QuotaType = decltype(inflight_keepalive_bytes_); + QuotaType size = 0; + if (request.GetKeepalive() && request.HttpBody()) { + auto original_size = request.HttpBody()->SizeInBytes(); + DCHECK_LE(inflight_keepalive_bytes_, kKeepaliveInflightBytesQuota); + if (original_size > std::numeric_limits<QuotaType>::max()) + return false; + size = static_cast<QuotaType>(original_size); + if (kKeepaliveInflightBytesQuota - inflight_keepalive_bytes_ < size) + return false; + + inflight_keepalive_bytes_ += size; + } + + loader = ResourceLoader::Create(this, scheduler_, resource, size); + if (resource->ShouldBlockLoadEvent()) + loaders_.insert(loader); + else + non_blocking_loaders_.insert(loader); + + StorePerformanceTimingInitiatorInformation(resource); + + // NotifyStartLoad() shouldn't cause AddClient/RemoveClient(). + Resource::ProhibitAddRemoveClientInScope + prohibit_add_remove_client_in_scope(resource); + + resource->NotifyStartLoad(); + } + + loader->Start(); + return true; +} + +void ResourceFetcher::RemoveResourceLoader(ResourceLoader* loader) { + DCHECK(loader); + if (loaders_.Contains(loader)) + loaders_.erase(loader); + else if (non_blocking_loaders_.Contains(loader)) + non_blocking_loaders_.erase(loader); + else + NOTREACHED(); + + if (loaders_.IsEmpty() && non_blocking_loaders_.IsEmpty()) + keepalive_loaders_task_handle_.Cancel(); +} + +void ResourceFetcher::StopFetching() { + StopFetchingInternal(StopFetchingTarget::kExcludingKeepaliveLoaders); +} + +void ResourceFetcher::SetDefersLoading(bool defers) { + for (const auto& loader : non_blocking_loaders_) + loader->SetDefersLoading(defers); + for (const auto& loader : loaders_) + loader->SetDefersLoading(defers); +} + +void ResourceFetcher::UpdateAllImageResourcePriorities() { + TRACE_EVENT0( + "blink", + "ResourceLoadPriorityOptimizer::updateAllImageResourcePriorities"); + for (Resource* resource : document_resources_) { + if (!resource || resource->GetType() != Resource::kImage || + !resource->IsLoading()) + continue; + + ResourcePriority resource_priority = resource->PriorityFromObservers(); + ResourceLoadPriority resource_load_priority = + ComputeLoadPriority(Resource::kImage, resource->GetResourceRequest(), + resource_priority.visibility); + if (resource_load_priority == resource->GetResourceRequest().Priority()) + continue; + + resource->DidChangePriority(resource_load_priority, + resource_priority.intra_priority_value); + network_instrumentation::ResourcePrioritySet(resource->Identifier(), + resource_load_priority); + Context().DispatchDidChangeResourcePriority( + resource->Identifier(), resource_load_priority, + resource_priority.intra_priority_value); + } +} + +void ResourceFetcher::ReloadLoFiImages() { + for (Resource* resource : document_resources_) { + if (resource) + resource->ReloadIfLoFiOrPlaceholderImage(this, Resource::kReloadAlways); + } +} + +String ResourceFetcher::GetCacheIdentifier() const { + if (Context().IsControlledByServiceWorker()) + return String::Number(Context().ServiceWorkerID()); + return MemoryCache::DefaultCacheIdentifier(); +} + +void ResourceFetcher::EmulateLoadStartedForInspector( + Resource* resource, + const KURL& url, + WebURLRequest::RequestContext request_context, + const AtomicString& initiator_name) { + if (CachedResource(url)) + return; + ResourceRequest resource_request(url); + resource_request.SetRequestContext(request_context); + ResourceLoaderOptions options = resource->Options(); + options.initiator_info.name = initiator_name; + FetchParameters params(resource_request, options); + Context().CanRequest(resource->GetType(), resource->LastResourceRequest(), + resource->LastResourceRequest().Url(), params.Options(), + SecurityViolationReportingPolicy::kReport, + params.GetOriginRestriction(), + resource->LastResourceRequest().GetRedirectStatus()); + RequestLoadStarted(resource->Identifier(), resource, params, kUse); +} + +void ResourceFetcher::PrepareForLeakDetection() { + // Stop loaders including keepalive ones that may persist after page + // navigation and thus affect instance counters of leak detection. + StopFetchingIncludingKeepaliveLoaders(); +} + +void ResourceFetcher::StopFetchingInternal(StopFetchingTarget target) { + // TODO(toyoshim): May want to suspend scheduler while canceling loaders so + // that the cancellations below do not awake unnecessary scheduling. + + HeapVector<Member<ResourceLoader>> loaders_to_cancel; + for (const auto& loader : non_blocking_loaders_) { + if (target == StopFetchingTarget::kIncludingKeepaliveLoaders || + !loader->ShouldBeKeptAliveWhenDetached()) { + loaders_to_cancel.push_back(loader); + } + } + for (const auto& loader : loaders_) { + if (target == StopFetchingTarget::kIncludingKeepaliveLoaders || + !loader->ShouldBeKeptAliveWhenDetached()) { + loaders_to_cancel.push_back(loader); + } + } + + for (const auto& loader : loaders_to_cancel) { + if (loaders_.Contains(loader) || non_blocking_loaders_.Contains(loader)) + loader->Cancel(); + } +} + +void ResourceFetcher::StopFetchingIncludingKeepaliveLoaders() { + StopFetchingInternal(StopFetchingTarget::kIncludingKeepaliveLoaders); +} + +void ResourceFetcher::Trace(blink::Visitor* visitor) { + visitor->Trace(context_); + visitor->Trace(scheduler_); + visitor->Trace(archive_); + visitor->Trace(loaders_); + visitor->Trace(non_blocking_loaders_); + visitor->Trace(cached_resources_map_); + visitor->Trace(document_resources_); + visitor->Trace(resources_from_previous_fetcher_); + visitor->Trace(preloads_); + visitor->Trace(matched_preloads_); + visitor->Trace(resource_timing_info_map_); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h b/chromium/third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h new file mode 100644 index 00000000000..134349299fb --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h @@ -0,0 +1,345 @@ +/* + Copyright (C) 1998 Lars Knoll (knoll@mpi-hd.mpg.de) + Copyright (C) 2001 Dirk Mueller <mueller@kde.org> + Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011 Apple Inc. All + rights reserved. + Copyright (C) 2009 Torch Mobile Inc. http://www.torchmobile.com/ + + This library is free software; you can redistribute it and/or + 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_PLATFORM_LOADER_FETCH_RESOURCE_FETCHER_H_ +#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_LOADER_FETCH_RESOURCE_FETCHER_H_ + +#include <memory> +#include "third_party/blink/renderer/platform/loader/fetch/fetch_context.h" +#include "third_party/blink/renderer/platform/loader/fetch/fetch_initiator_info.h" +#include "third_party/blink/renderer/platform/loader/fetch/fetch_parameters.h" +#include "third_party/blink/renderer/platform/loader/fetch/preload_key.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource.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_loader_options.h" +#include "third_party/blink/renderer/platform/loader/fetch/substitute_data.h" +#include "third_party/blink/renderer/platform/platform_export.h" +#include "third_party/blink/renderer/platform/timer.h" +#include "third_party/blink/renderer/platform/wtf/hash_map.h" +#include "third_party/blink/renderer/platform/wtf/hash_set.h" +#include "third_party/blink/renderer/platform/wtf/text/string_hash.h" + +namespace blink { + +class ArchiveResource; +class MHTMLArchive; +class KURL; +class ResourceTimingInfo; + +// The ResourceFetcher provides a per-context interface to the MemoryCache and +// enforces a bunch of security checks and rules for resource revalidation. Its +// lifetime is roughly per-DocumentLoader, in that it is generally created in +// the DocumentLoader constructor and loses its ability to generate network +// requests when the DocumentLoader is destroyed. Documents also hold a pointer +// to ResourceFetcher for their lifetime (and will create one if they are +// initialized without a LocalFrame), so a Document can keep a ResourceFetcher +// alive past detach if scripts still reference the Document. +class PLATFORM_EXPORT ResourceFetcher + : public GarbageCollectedFinalized<ResourceFetcher> { + WTF_MAKE_NONCOPYABLE(ResourceFetcher); + USING_PRE_FINALIZER(ResourceFetcher, ClearPreloads); + + public: + static ResourceFetcher* Create(FetchContext* context) { + return new ResourceFetcher(context); + } + virtual ~ResourceFetcher(); + virtual void Trace(blink::Visitor*); + + // Triggers a fetch based on the given FetchParameters (if there isn't a + // suitable Resource already cached) and registers the given ResourceClient + // with the Resource. Guaranteed to return a non-null Resource of the subtype + // specified by ResourceFactory::GetType(). + Resource* RequestResource(FetchParameters&, + const ResourceFactory&, + ResourceClient*, + const SubstituteData& = SubstituteData()); + + Resource* CachedResource(const KURL&) const; + + using DocumentResourceMap = HeapHashMap<String, WeakMember<Resource>>; + const DocumentResourceMap& AllResources() const { + return cached_resources_map_; + } + + void HoldResourcesFromPreviousFetcher(ResourceFetcher*); + void ClearResourcesFromPreviousFetcher(); + + // Binds the given Resource instance to this ResourceFetcher instance to + // start loading the Resource actually. + // Usually, RequestResource() calls this method internally, but needs to + // call this method explicitly on cases such as ResourceNeedsLoad() returning + // false. + bool StartLoad(Resource*); + + void SetAutoLoadImages(bool); + void SetImagesEnabled(bool); + + FetchContext& Context() const { + return context_ ? *context_.Get() : FetchContext::NullInstance(); + } + void ClearContext(); + + int BlockingRequestCount() const; + int NonblockingRequestCount() const; + int ActiveRequestCount() const; + + enum ClearPreloadsPolicy { + kClearAllPreloads, + kClearSpeculativeMarkupPreloads + }; + + void EnableIsPreloadedForTest(); + bool IsPreloadedForTest(const KURL&) const; + + int CountPreloads() const { return preloads_.size(); } + void ClearPreloads(ClearPreloadsPolicy = kClearAllPreloads); + void LogPreloadStats(ClearPreloadsPolicy); + void WarnUnusedPreloads(); + + MHTMLArchive* Archive() const { return archive_.Get(); } + ArchiveResource* CreateArchive(Resource*); + + void SetDefersLoading(bool); + void StopFetching(); + + bool ShouldDeferImageLoad(const KURL&) const; + + void RecordResourceTimingOnRedirect(Resource*, const ResourceResponse&, bool); + + enum LoaderFinishType { kDidFinishLoading, kDidFinishFirstPartInMultipart }; + void HandleLoaderFinish(Resource*, + double finish_time, + LoaderFinishType, + uint32_t inflight_keepalive_bytes, + bool blocked_cross_site_document); + void HandleLoaderError(Resource*, + const ResourceError&, + uint32_t inflight_keepalive_bytes); + bool IsControlledByServiceWorker() const; + + String GetCacheIdentifier() const; + + enum IsImageSet { kImageNotImageSet, kImageIsImageSet }; + + WARN_UNUSED_RESULT static WebURLRequest::RequestContext + DetermineRequestContext(Resource::Type, IsImageSet, bool is_main_frame); + + void UpdateAllImageResourcePriorities(); + + void ReloadLoFiImages(); + + // Calling this method before main document resource is fetched is invalid. + ResourceTimingInfo* GetNavigationTimingInfo(); + + // Returns whether the given resource is contained as a preloaded resource. + bool ContainsAsPreload(Resource*) const; + + void RemovePreload(Resource*); + + void LoosenLoadThrottlingPolicy() { scheduler_->LoosenThrottlingPolicy(); } + void OnNetworkQuiet() { scheduler_->OnNetworkQuiet(); } + + // Workaround for https://crbug.com/666214. + // TODO(hiroshige): Remove this hack. + void EmulateLoadStartedForInspector(Resource*, + const KURL&, + WebURLRequest::RequestContext, + const AtomicString& initiator_name); + + // This is called from leak detectors (Real-world leak detector & layout test + // leak detector) to clean up loaders after page navigation before instance + // counting. + void PrepareForLeakDetection(); + + private: + friend class ResourceCacheValidationSuppressor; + enum class StopFetchingTarget { + kExcludingKeepaliveLoaders, + kIncludingKeepaliveLoaders, + }; + + ResourceFetcher(FetchContext*); + + void InitializeRevalidation(ResourceRequest&, Resource*); + // When |security_origin| of the ResourceLoaderOptions is not a nullptr, it'll + // be used instead of the associated FetchContext's SecurityOrigin. + scoped_refptr<const SecurityOrigin> GetSourceOrigin( + const ResourceLoaderOptions&) const; + void AddToMemoryCacheIfNeeded(const FetchParameters&, Resource*); + Resource* CreateResourceForLoading(const FetchParameters&, + const ResourceFactory&); + void StorePerformanceTimingInitiatorInformation(Resource*); + ResourceLoadPriority ComputeLoadPriority( + Resource::Type, + const ResourceRequest&, + ResourcePriority::VisibilityStatus, + FetchParameters::DeferOption = FetchParameters::kNoDefer, + FetchParameters::SpeculativePreloadType = + FetchParameters::SpeculativePreloadType::kNotSpeculative, + bool is_link_preload = false); + + Resource* RequestResourceInternal(FetchParameters&, + const ResourceFactory&, + const SubstituteData&); + ResourceRequestBlockedReason PrepareRequest(FetchParameters&, + const ResourceFactory&, + const SubstituteData&, + unsigned long identifier); + + Resource* ResourceForStaticData(const FetchParameters&, + const ResourceFactory&, + const SubstituteData&); + Resource* ResourceForBlockedRequest(const FetchParameters&, + const ResourceFactory&, + ResourceRequestBlockedReason); + + Resource* MatchPreload(const FetchParameters& params, Resource::Type); + void InsertAsPreloadIfNecessary(Resource*, + const FetchParameters& params, + Resource::Type); + + bool IsImageResourceDisallowedToBeReused(const Resource&) const; + + void StopFetchingInternal(StopFetchingTarget); + void StopFetchingIncludingKeepaliveLoaders(); + + // RevalidationPolicy enum values are used in UMAs https://crbug.com/579496. + enum RevalidationPolicy { kUse, kRevalidate, kReload, kLoad }; + + // A wrapper just for placing a trace_event macro. + RevalidationPolicy DetermineRevalidationPolicy( + Resource::Type, + const FetchParameters&, + const Resource& existing_resource, + bool is_static_data) const; + // Determines a RevalidationPolicy given a FetchParameters and an existing + // resource retrieved from the memory cache (can be a newly constructed one + // for a static data). + RevalidationPolicy DetermineRevalidationPolicyInternal( + Resource::Type, + const FetchParameters&, + const Resource& existing_resource, + bool is_static_data) const; + + void MakePreloadedResourceBlockOnloadIfNeeded(Resource*, + const FetchParameters&); + void MoveResourceLoaderToNonBlocking(ResourceLoader*); + void RemoveResourceLoader(ResourceLoader*); + void HandleLoadCompletion(Resource*); + + void RequestLoadStarted(unsigned long identifier, + Resource*, + const FetchParameters&, + RevalidationPolicy, + bool is_static_data = false); + + void DidLoadResourceFromMemoryCache(unsigned long identifier, + Resource*, + const ResourceRequest&); + + bool ResourceNeedsLoad(Resource*, const FetchParameters&, RevalidationPolicy); + + void ResourceTimingReportTimerFired(TimerBase*); + + void ReloadImagesIfNotDeferred(); + + void UpdateMemoryCacheStats(Resource*, + RevalidationPolicy, + const FetchParameters&, + const ResourceFactory&, + bool is_static_data) const; + + Member<FetchContext> context_; + Member<ResourceLoadScheduler> scheduler_; + + DocumentResourceMap cached_resources_map_; + HeapHashSet<WeakMember<Resource>> document_resources_; + + // When populated, forces Resources to remain alive across a navigation, to + // increase the odds the next document will be able to reuse resources from + // the previous page. Unpopulated unless experiment is enabled. + HeapHashSet<Member<Resource>> resources_from_previous_fetcher_; + + HeapHashMap<PreloadKey, Member<Resource>> preloads_; + HeapVector<Member<Resource>> matched_preloads_; + Member<MHTMLArchive> archive_; + + TaskRunnerTimer<ResourceFetcher> resource_timing_report_timer_; + + using ResourceTimingInfoMap = + HeapHashMap<Member<Resource>, scoped_refptr<ResourceTimingInfo>>; + ResourceTimingInfoMap resource_timing_info_map_; + + scoped_refptr<ResourceTimingInfo> navigation_timing_info_; + + Vector<scoped_refptr<ResourceTimingInfo>> scheduled_resource_timing_reports_; + + HeapHashSet<Member<ResourceLoader>> loaders_; + HeapHashSet<Member<ResourceLoader>> non_blocking_loaders_; + + std::unique_ptr<HashSet<String>> preloaded_urls_for_test_; + + // Timeout timer for keepalive requests. + TaskHandle keepalive_loaders_task_handle_; + + uint32_t inflight_keepalive_bytes_ = 0; + + // 28 bits left + bool auto_load_images_ : 1; + bool images_enabled_ : 1; + bool allow_stale_resources_ : 1; + bool image_fetched_ : 1; + + static constexpr uint32_t kKeepaliveInflightBytesQuota = 64 * 1024; +}; + +class ResourceCacheValidationSuppressor { + WTF_MAKE_NONCOPYABLE(ResourceCacheValidationSuppressor); + STACK_ALLOCATED(); + + public: + explicit ResourceCacheValidationSuppressor(ResourceFetcher* loader) + : loader_(loader), previous_state_(false) { + if (loader_) { + previous_state_ = loader_->allow_stale_resources_; + loader_->allow_stale_resources_ = true; + } + } + ~ResourceCacheValidationSuppressor() { + if (loader_) + loader_->allow_stale_resources_ = previous_state_; + } + + private: + Member<ResourceFetcher> loader_; + bool previous_state_; +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_PLATFORM_LOADER_FETCH_RESOURCE_FETCHER_H_ diff --git a/chromium/third_party/blink/renderer/platform/loader/fetch/resource_fetcher_test.cc b/chromium/third_party/blink/renderer/platform/loader/fetch/resource_fetcher_test.cc new file mode 100644 index 00000000000..679e35ddf20 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/loader/fetch/resource_fetcher_test.cc @@ -0,0 +1,852 @@ +/* + * 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/platform/loader/fetch/resource_fetcher.h" + +#include <memory> +#include "services/network/public/mojom/request_context_frame_type.mojom-shared.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.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/platform/exported/wrapped_resource_response.h" +#include "third_party/blink/renderer/platform/heap/handle.h" +#include "third_party/blink/renderer/platform/heap/heap_allocator.h" +#include "third_party/blink/renderer/platform/heap/member.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/raw_resource.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource_error.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/loader/fetch/resource_timing_info.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/loader/testing/mock_resource.h" +#include "third_party/blink/renderer/platform/loader/testing/mock_resource_client.h" +#include "third_party/blink/renderer/platform/testing/testing_platform_support.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/testing/weburl_loader_mock.h" +#include "third_party/blink/renderer/platform/testing/weburl_loader_mock_factory_impl.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/optional.h" + +namespace blink { + +namespace { + +constexpr char kTestResourceFilename[] = "white-1x1.png"; +constexpr char kTestResourceMimeType[] = "image/png"; +constexpr int kTestResourceSize = 103; // size of white-1x1.png + +void RegisterMockedURLLoadWithCustomResponse(const KURL& url, + const ResourceResponse& response) { + URLTestHelpers::RegisterMockedURLLoadWithCustomResponse( + url, test::PlatformTestDataPath(kTestResourceFilename), + WrappedResourceResponse(response)); +} + +void RegisterMockedURLLoad(const KURL& url) { + URLTestHelpers::RegisterMockedURLLoad( + url, test::PlatformTestDataPath(kTestResourceFilename), + kTestResourceMimeType); +} + +} // namespace + +class ResourceFetcherTest : public testing::Test { + public: + ResourceFetcherTest() = default; + ~ResourceFetcherTest() override { GetMemoryCache()->EvictResources(); } + + protected: + MockFetchContext* Context() { return platform_->Context(); } + void AddResourceToMemoryCache( + Resource* resource, + scoped_refptr<const SecurityOrigin> source_origin) { + resource->SetSourceOrigin(source_origin); + GetMemoryCache()->Add(resource); + } + + ScopedTestingPlatformSupport<FetchTestingPlatformSupport> platform_; + + private: + DISALLOW_COPY_AND_ASSIGN(ResourceFetcherTest); +}; + +TEST_F(ResourceFetcherTest, StartLoadAfterFrameDetach) { + KURL secure_url("https://secureorigin.test/image.png"); + // Try to request a url. The request should fail, and a resource in an error + // state should be returned, and no resource should be present in the cache. + ResourceFetcher* fetcher = + ResourceFetcher::Create(&FetchContext::NullInstance()); + + ResourceRequest resource_request(secure_url); + resource_request.SetRequestContext(WebURLRequest::kRequestContextInternal); + FetchParameters fetch_params(resource_request); + Resource* resource = RawResource::Fetch(fetch_params, fetcher, nullptr); + ASSERT_TRUE(resource); + EXPECT_TRUE(resource->ErrorOccurred()); + EXPECT_TRUE(resource->GetResourceError().IsAccessCheck()); + EXPECT_FALSE(GetMemoryCache()->ResourceForURL(secure_url)); + + // Start by calling StartLoad() directly, rather than via RequestResource(). + // This shouldn't crash. + fetcher->StartLoad(RawResource::CreateForTest(secure_url, Resource::kRaw)); +} + +TEST_F(ResourceFetcherTest, UseExistingResource) { + ResourceFetcher* fetcher = ResourceFetcher::Create(Context()); + + KURL url("http://127.0.0.1:8000/foo.html"); + ResourceResponse response(url); + response.SetHTTPStatusCode(200); + response.SetHTTPHeaderField(HTTPNames::Cache_Control, "max-age=3600"); + RegisterMockedURLLoadWithCustomResponse(url, response); + + FetchParameters fetch_params{ResourceRequest(url)}; + Resource* resource = MockResource::Fetch(fetch_params, fetcher, nullptr); + ASSERT_TRUE(resource); + platform_->GetURLLoaderMockFactory()->ServeAsynchronousRequests(); + EXPECT_TRUE(resource->IsLoaded()); + EXPECT_TRUE(GetMemoryCache()->Contains(resource)); + + Resource* new_resource = MockResource::Fetch(fetch_params, fetcher, nullptr); + EXPECT_EQ(resource, new_resource); +} + +// Verify that the ad bit is copied to WillSendRequest's request when the +// response is served from the memory cache. +TEST_F(ResourceFetcherTest, WillSendRequestAdBit) { + // Add a resource to the memory cache. + scoped_refptr<const SecurityOrigin> source_origin = + SecurityOrigin::CreateUnique(); + Context()->SetSecurityOrigin(source_origin); + KURL url("http://127.0.0.1:8000/foo.html"); + Resource* resource = RawResource::CreateForTest(url, Resource::kRaw); + AddResourceToMemoryCache(resource, source_origin); + ResourceResponse response(url); + response.SetHTTPStatusCode(200); + response.SetHTTPHeaderField(HTTPNames::Cache_Control, "max-age=3600"); + resource->ResponseReceived(response, nullptr); + resource->FinishForTest(); + + // Fetch the cached resource. The request to DispatchWillSendRequest should + // preserve the ad bit. + ResourceFetcher* fetcher = ResourceFetcher::Create(Context()); + ResourceRequest resource_request(url); + resource_request.SetIsAdResource(); + resource_request.SetRequestContext(WebURLRequest::kRequestContextInternal); + FetchParameters fetch_params(resource_request); + platform_->GetURLLoaderMockFactory()->RegisterURL(url, WebURLResponse(), ""); + Resource* new_resource = RawResource::Fetch(fetch_params, fetcher, nullptr); + + EXPECT_EQ(resource, new_resource); + WTF::Optional<ResourceRequest> new_request = + Context()->RequestFromWillSendRequest(); + EXPECT_TRUE(new_request.has_value()); + EXPECT_TRUE(new_request.value().IsAdResource()); +} + +TEST_F(ResourceFetcherTest, Vary) { + scoped_refptr<const SecurityOrigin> source_origin = + SecurityOrigin::CreateUnique(); + Context()->SetSecurityOrigin(source_origin); + + KURL url("http://127.0.0.1:8000/foo.html"); + Resource* resource = RawResource::CreateForTest(url, Resource::kRaw); + AddResourceToMemoryCache(resource, source_origin); + + ResourceResponse response(url); + response.SetHTTPStatusCode(200); + response.SetHTTPHeaderField(HTTPNames::Cache_Control, "max-age=3600"); + response.SetHTTPHeaderField(HTTPNames::Vary, "*"); + resource->ResponseReceived(response, nullptr); + resource->FinishForTest(); + ASSERT_TRUE(resource->MustReloadDueToVaryHeader(ResourceRequest(url))); + + ResourceFetcher* fetcher = ResourceFetcher::Create(Context()); + ResourceRequest resource_request(url); + resource_request.SetRequestContext(WebURLRequest::kRequestContextInternal); + FetchParameters fetch_params(resource_request); + platform_->GetURLLoaderMockFactory()->RegisterURL(url, WebURLResponse(), ""); + Resource* new_resource = RawResource::Fetch(fetch_params, fetcher, nullptr); + EXPECT_NE(resource, new_resource); + new_resource->Loader()->Cancel(); +} + +TEST_F(ResourceFetcherTest, NavigationTimingInfo) { + KURL url("http://127.0.0.1:8000/foo.html"); + ResourceResponse response(url); + response.SetHTTPStatusCode(200); + + ResourceFetcher* fetcher = ResourceFetcher::Create(Context()); + ResourceRequest resource_request(url); + resource_request.SetFrameType( + network::mojom::RequestContextFrameType::kNested); + resource_request.SetRequestContext(WebURLRequest::kRequestContextForm); + FetchParameters fetch_params(resource_request); + platform_->GetURLLoaderMockFactory()->RegisterURL(url, WebURLResponse(), ""); + Resource* resource = RawResource::FetchMainResource( + fetch_params, fetcher, nullptr, SubstituteData()); + resource->ResponseReceived(response, nullptr); + EXPECT_EQ(resource->GetType(), Resource::kMainResource); + + ResourceTimingInfo* navigation_timing_info = + fetcher->GetNavigationTimingInfo(); + ASSERT_TRUE(navigation_timing_info); + long long encoded_data_length = 123; + resource->Loader()->DidFinishLoading(0.0, encoded_data_length, 0, 0, false); + EXPECT_EQ(navigation_timing_info->TransferSize(), encoded_data_length); + + // When there are redirects. + KURL redirect_url("http://127.0.0.1:8000/redirect.html"); + ResourceResponse redirect_response(redirect_url); + redirect_response.SetHTTPStatusCode(200); + long long redirect_encoded_data_length = 123; + redirect_response.SetEncodedDataLength(redirect_encoded_data_length); + ResourceRequest redirect_resource_request(url); + fetcher->RecordResourceTimingOnRedirect(resource, redirect_response, false); + EXPECT_EQ(navigation_timing_info->TransferSize(), + encoded_data_length + redirect_encoded_data_length); +} + +TEST_F(ResourceFetcherTest, VaryOnBack) { + scoped_refptr<const SecurityOrigin> source_origin = + SecurityOrigin::CreateUnique(); + Context()->SetSecurityOrigin(source_origin); + + ResourceFetcher* fetcher = ResourceFetcher::Create(Context()); + + KURL url("http://127.0.0.1:8000/foo.html"); + Resource* resource = RawResource::CreateForTest(url, Resource::kRaw); + AddResourceToMemoryCache(resource, source_origin); + + ResourceResponse response(url); + response.SetHTTPStatusCode(200); + response.SetHTTPHeaderField(HTTPNames::Cache_Control, "max-age=3600"); + response.SetHTTPHeaderField(HTTPNames::Vary, "*"); + resource->ResponseReceived(response, nullptr); + resource->FinishForTest(); + ASSERT_TRUE(resource->MustReloadDueToVaryHeader(ResourceRequest(url))); + + ResourceRequest resource_request(url); + resource_request.SetCacheMode(mojom::FetchCacheMode::kForceCache); + resource_request.SetRequestContext(WebURLRequest::kRequestContextInternal); + FetchParameters fetch_params(resource_request); + Resource* new_resource = RawResource::Fetch(fetch_params, fetcher, nullptr); + EXPECT_EQ(resource, new_resource); +} + +TEST_F(ResourceFetcherTest, VaryResource) { + ResourceFetcher* fetcher = ResourceFetcher::Create(Context()); + + KURL url("http://127.0.0.1:8000/foo.html"); + ResourceResponse response(url); + response.SetHTTPStatusCode(200); + response.SetHTTPHeaderField(HTTPNames::Cache_Control, "max-age=3600"); + response.SetHTTPHeaderField(HTTPNames::Vary, "*"); + RegisterMockedURLLoadWithCustomResponse(url, response); + + FetchParameters fetch_params_original{ResourceRequest(url)}; + Resource* resource = + MockResource::Fetch(fetch_params_original, fetcher, nullptr); + ASSERT_TRUE(resource); + platform_->GetURLLoaderMockFactory()->ServeAsynchronousRequests(); + ASSERT_TRUE(resource->MustReloadDueToVaryHeader(ResourceRequest(url))); + + FetchParameters fetch_params{ResourceRequest(url)}; + Resource* new_resource = MockResource::Fetch(fetch_params, fetcher, nullptr); + EXPECT_EQ(resource, new_resource); +} + +class RequestSameResourceOnComplete + : public GarbageCollectedFinalized<RequestSameResourceOnComplete>, + public RawResourceClient { + USING_GARBAGE_COLLECTED_MIXIN(RequestSameResourceOnComplete); + + public: + explicit RequestSameResourceOnComplete(FetchParameters& params, + ResourceFetcher* fetcher) + : notify_finished_called_(false), + source_origin_(fetcher->Context().GetSecurityOrigin()) { + MockResource::Fetch(params, fetcher, this); + } + + void NotifyFinished(Resource* resource) override { + EXPECT_EQ(GetResource(), resource); + MockFetchContext* context = + MockFetchContext::Create(MockFetchContext::kShouldLoadNewResource); + context->SetSecurityOrigin(source_origin_); + ResourceFetcher* fetcher2 = ResourceFetcher::Create(context); + ResourceRequest resource_request2(GetResource()->Url()); + resource_request2.SetCacheMode(mojom::FetchCacheMode::kValidateCache); + FetchParameters fetch_params2(resource_request2); + Resource* resource2 = MockResource::Fetch(fetch_params2, fetcher2, nullptr); + EXPECT_EQ(GetResource(), resource2); + notify_finished_called_ = true; + ClearResource(); + } + bool NotifyFinishedCalled() const { return notify_finished_called_; } + + void Trace(blink::Visitor* visitor) override { + RawResourceClient::Trace(visitor); + } + + String DebugName() const override { return "RequestSameResourceOnComplete"; } + + private: + bool notify_finished_called_; + scoped_refptr<const SecurityOrigin> source_origin_; +}; + +TEST_F(ResourceFetcherTest, RevalidateWhileFinishingLoading) { + scoped_refptr<const SecurityOrigin> source_origin = + SecurityOrigin::CreateUnique(); + Context()->SetSecurityOrigin(source_origin); + + KURL url("http://127.0.0.1:8000/foo.png"); + + ResourceResponse response(url); + response.SetHTTPStatusCode(200); + response.SetHTTPHeaderField(HTTPNames::Cache_Control, "max-age=3600"); + response.SetHTTPHeaderField(HTTPNames::ETag, "1234567890"); + RegisterMockedURLLoadWithCustomResponse(url, response); + + ResourceFetcher* fetcher1 = ResourceFetcher::Create(Context()); + ResourceRequest request1(url); + request1.SetHTTPHeaderField(HTTPNames::Cache_Control, "no-cache"); + FetchParameters fetch_params1(request1); + Persistent<RequestSameResourceOnComplete> client = + new RequestSameResourceOnComplete(fetch_params1, fetcher1); + platform_->GetURLLoaderMockFactory()->ServeAsynchronousRequests(); + EXPECT_TRUE(client->NotifyFinishedCalled()); +} + +TEST_F(ResourceFetcherTest, DontReuseMediaDataUrl) { + ResourceFetcher* fetcher = ResourceFetcher::Create(Context()); + ResourceRequest request(KURL("data:text/html,foo")); + request.SetRequestContext(WebURLRequest::kRequestContextVideo); + request.SetFetchCredentialsMode(network::mojom::FetchCredentialsMode::kOmit); + ResourceLoaderOptions options; + options.data_buffering_policy = kDoNotBufferData; + options.initiator_info.name = FetchInitiatorTypeNames::internal; + FetchParameters fetch_params(request, options); + Resource* resource1 = RawResource::FetchMedia(fetch_params, fetcher, nullptr); + Resource* resource2 = RawResource::FetchMedia(fetch_params, fetcher, nullptr); + EXPECT_NE(resource1, resource2); +} + +class ServeRequestsOnCompleteClient final + : public GarbageCollectedFinalized<ServeRequestsOnCompleteClient>, + public RawResourceClient { + USING_GARBAGE_COLLECTED_MIXIN(ServeRequestsOnCompleteClient); + + public: + void NotifyFinished(Resource*) override { + Platform::Current()->GetURLLoaderMockFactory()->ServeAsynchronousRequests(); + ClearResource(); + } + + // No callbacks should be received except for the NotifyFinished() triggered + // by ResourceLoader::Cancel(). + void DataSent(Resource*, unsigned long long, unsigned long long) override { + ASSERT_TRUE(false); + } + void ResponseReceived(Resource*, + const ResourceResponse&, + std::unique_ptr<WebDataConsumerHandle>) override { + ASSERT_TRUE(false); + } + void SetSerializedCachedMetadata(Resource*, const char*, size_t) override { + ASSERT_TRUE(false); + } + void DataReceived(Resource*, const char*, size_t) override { + ASSERT_TRUE(false); + } + bool RedirectReceived(Resource*, + const ResourceRequest&, + const ResourceResponse&) override { + ADD_FAILURE(); + return true; + } + void DataDownloaded(Resource*, int) override { ASSERT_TRUE(false); } + void DidReceiveResourceTiming(Resource*, const ResourceTimingInfo&) override { + ASSERT_TRUE(false); + } + + void Trace(blink::Visitor* visitor) override { + RawResourceClient::Trace(visitor); + } + + String DebugName() const override { return "ServeRequestsOnCompleteClient"; } +}; + +// Regression test for http://crbug.com/594072. +// This emulates a modal dialog triggering a nested run loop inside +// ResourceLoader::Cancel(). If the ResourceLoader doesn't promptly cancel its +// WebURLLoader before notifying its clients, a nested run loop may send a +// network response, leading to an invalid state transition in ResourceLoader. +TEST_F(ResourceFetcherTest, ResponseOnCancel) { + KURL url("http://127.0.0.1:8000/foo.png"); + RegisterMockedURLLoad(url); + + ResourceFetcher* fetcher = ResourceFetcher::Create(Context()); + ResourceRequest resource_request(url); + resource_request.SetRequestContext(WebURLRequest::kRequestContextInternal); + FetchParameters fetch_params(resource_request); + Persistent<ServeRequestsOnCompleteClient> client = + new ServeRequestsOnCompleteClient(); + Resource* resource = RawResource::Fetch(fetch_params, fetcher, client); + resource->Loader()->Cancel(); +} + +class ScopedMockRedirectRequester { + STACK_ALLOCATED(); + WTF_MAKE_NONCOPYABLE(ScopedMockRedirectRequester); + + public: + explicit ScopedMockRedirectRequester(MockFetchContext* context) + : context_(context) {} + + void RegisterRedirect(const WebString& from_url, const WebString& to_url) { + KURL redirect_url(from_url); + WebURLResponse redirect_response; + redirect_response.SetURL(redirect_url); + redirect_response.SetHTTPStatusCode(301); + redirect_response.SetHTTPHeaderField(HTTPNames::Location, to_url); + redirect_response.SetEncodedDataLength(kRedirectResponseOverheadBytes); + Platform::Current()->GetURLLoaderMockFactory()->RegisterURL( + redirect_url, redirect_response, ""); + } + + void RegisterFinalResource(const WebString& url) { + KURL final_url(url); + RegisterMockedURLLoad(final_url); + } + + void Request(const WebString& url) { + ResourceFetcher* fetcher = ResourceFetcher::Create(context_); + ResourceRequest resource_request(url); + resource_request.SetRequestContext(WebURLRequest::kRequestContextInternal); + FetchParameters fetch_params(resource_request); + RawResource::Fetch(fetch_params, fetcher, nullptr); + Platform::Current()->GetURLLoaderMockFactory()->ServeAsynchronousRequests(); + } + + private: + Member<MockFetchContext> context_; +}; + +TEST_F(ResourceFetcherTest, SameOriginRedirect) { + const char kRedirectURL[] = "http://127.0.0.1:8000/redirect.html"; + const char kFinalURL[] = "http://127.0.0.1:8000/final.html"; + ScopedMockRedirectRequester requester(Context()); + requester.RegisterRedirect(kRedirectURL, kFinalURL); + requester.RegisterFinalResource(kFinalURL); + requester.Request(kRedirectURL); + + EXPECT_EQ(kRedirectResponseOverheadBytes + kTestResourceSize, + Context()->GetTransferSize()); +} + +TEST_F(ResourceFetcherTest, CrossOriginRedirect) { + const char kRedirectURL[] = "http://otherorigin.test/redirect.html"; + const char kFinalURL[] = "http://127.0.0.1:8000/final.html"; + ScopedMockRedirectRequester requester(Context()); + requester.RegisterRedirect(kRedirectURL, kFinalURL); + requester.RegisterFinalResource(kFinalURL); + requester.Request(kRedirectURL); + + EXPECT_EQ(kTestResourceSize, Context()->GetTransferSize()); +} + +TEST_F(ResourceFetcherTest, ComplexCrossOriginRedirect) { + const char kRedirectURL1[] = "http://127.0.0.1:8000/redirect1.html"; + const char kRedirectURL2[] = "http://otherorigin.test/redirect2.html"; + const char kRedirectURL3[] = "http://127.0.0.1:8000/redirect3.html"; + const char kFinalURL[] = "http://127.0.0.1:8000/final.html"; + ScopedMockRedirectRequester requester(Context()); + requester.RegisterRedirect(kRedirectURL1, kRedirectURL2); + requester.RegisterRedirect(kRedirectURL2, kRedirectURL3); + requester.RegisterRedirect(kRedirectURL3, kFinalURL); + requester.RegisterFinalResource(kFinalURL); + requester.Request(kRedirectURL1); + + EXPECT_EQ(kTestResourceSize, Context()->GetTransferSize()); +} + +TEST_F(ResourceFetcherTest, SynchronousRequest) { + KURL url("http://127.0.0.1:8000/foo.png"); + RegisterMockedURLLoad(url); + + ResourceFetcher* fetcher = ResourceFetcher::Create(Context()); + ResourceRequest resource_request(url); + resource_request.SetRequestContext(WebURLRequest::kRequestContextInternal); + FetchParameters fetch_params(resource_request); + fetch_params.MakeSynchronous(); + Resource* resource = RawResource::Fetch(fetch_params, fetcher, nullptr); + EXPECT_TRUE(resource->IsLoaded()); + EXPECT_EQ(ResourceLoadPriority::kHighest, + resource->GetResourceRequest().Priority()); +} + +TEST_F(ResourceFetcherTest, PingPriority) { + KURL url("http://127.0.0.1:8000/foo.png"); + RegisterMockedURLLoad(url); + + ResourceFetcher* fetcher = ResourceFetcher::Create(Context()); + ResourceRequest resource_request(url); + resource_request.SetRequestContext(WebURLRequest::kRequestContextPing); + FetchParameters fetch_params(resource_request); + Resource* resource = RawResource::Fetch(fetch_params, fetcher, nullptr); + EXPECT_EQ(ResourceLoadPriority::kVeryLow, + resource->GetResourceRequest().Priority()); +} + +TEST_F(ResourceFetcherTest, PreloadResourceTwice) { + ResourceFetcher* fetcher = ResourceFetcher::Create(Context()); + + KURL url("http://127.0.0.1:8000/foo.png"); + RegisterMockedURLLoad(url); + + FetchParameters fetch_params_original{ResourceRequest(url)}; + fetch_params_original.SetLinkPreload(true); + Resource* resource = + MockResource::Fetch(fetch_params_original, fetcher, nullptr); + ASSERT_TRUE(resource); + EXPECT_TRUE(resource->IsLinkPreload()); + EXPECT_TRUE(fetcher->ContainsAsPreload(resource)); + platform_->GetURLLoaderMockFactory()->ServeAsynchronousRequests(); + + FetchParameters fetch_params{ResourceRequest(url)}; + fetch_params.SetLinkPreload(true); + Resource* new_resource = MockResource::Fetch(fetch_params, fetcher, nullptr); + EXPECT_EQ(resource, new_resource); + EXPECT_TRUE(fetcher->ContainsAsPreload(resource)); + + fetcher->ClearPreloads(ResourceFetcher::kClearAllPreloads); + EXPECT_FALSE(fetcher->ContainsAsPreload(resource)); + EXPECT_FALSE(GetMemoryCache()->Contains(resource)); + EXPECT_TRUE(resource->IsUnusedPreload()); +} + +TEST_F(ResourceFetcherTest, LinkPreloadResourceAndUse) { + ResourceFetcher* fetcher = ResourceFetcher::Create(Context()); + + KURL url("http://127.0.0.1:8000/foo.png"); + RegisterMockedURLLoad(url); + + // Link preload preload scanner + FetchParameters fetch_params_original{ResourceRequest(url)}; + fetch_params_original.SetLinkPreload(true); + Resource* resource = + MockResource::Fetch(fetch_params_original, fetcher, nullptr); + ASSERT_TRUE(resource); + EXPECT_TRUE(resource->IsLinkPreload()); + platform_->GetURLLoaderMockFactory()->ServeAsynchronousRequests(); + + // Resource created by preload scanner + FetchParameters fetch_params_preload_scanner{ResourceRequest(url)}; + Resource* preload_scanner_resource = + MockResource::Fetch(fetch_params_preload_scanner, fetcher, nullptr); + EXPECT_EQ(resource, preload_scanner_resource); + EXPECT_FALSE(resource->IsLinkPreload()); + + // Resource created by parser + FetchParameters fetch_params{ResourceRequest(url)}; + Persistent<MockResourceClient> client = new MockResourceClient; + Resource* new_resource = MockResource::Fetch(fetch_params, fetcher, client); + EXPECT_EQ(resource, new_resource); + EXPECT_FALSE(resource->IsLinkPreload()); + + // DCL reached + fetcher->ClearPreloads(ResourceFetcher::kClearSpeculativeMarkupPreloads); + EXPECT_TRUE(GetMemoryCache()->Contains(resource)); + EXPECT_FALSE(resource->IsUnusedPreload()); +} + +TEST_F(ResourceFetcherTest, PreloadMatchWithBypassingCache) { + ResourceFetcher* fetcher = ResourceFetcher::Create(Context()); + KURL url("http://127.0.0.1:8000/foo.png"); + RegisterMockedURLLoad(url); + + FetchParameters fetch_params_original{ResourceRequest(url)}; + fetch_params_original.SetLinkPreload(true); + Resource* resource = + MockResource::Fetch(fetch_params_original, fetcher, nullptr); + ASSERT_TRUE(resource); + EXPECT_TRUE(resource->IsLinkPreload()); + platform_->GetURLLoaderMockFactory()->ServeAsynchronousRequests(); + + FetchParameters fetch_params_second{ResourceRequest(url)}; + fetch_params_second.MutableResourceRequest().SetCacheMode( + mojom::FetchCacheMode::kBypassCache); + Resource* second_resource = + MockResource::Fetch(fetch_params_second, fetcher, nullptr); + EXPECT_EQ(resource, second_resource); + EXPECT_FALSE(resource->IsLinkPreload()); +} + +TEST_F(ResourceFetcherTest, CrossFramePreloadMatchIsNotAllowed) { + ResourceFetcher* fetcher = ResourceFetcher::Create(Context()); + ResourceFetcher* fetcher2 = ResourceFetcher::Create(Context()); + + KURL url("http://127.0.0.1:8000/foo.png"); + RegisterMockedURLLoad(url); + + FetchParameters fetch_params_original{ResourceRequest(url)}; + fetch_params_original.SetLinkPreload(true); + Resource* resource = + MockResource::Fetch(fetch_params_original, fetcher, nullptr); + ASSERT_TRUE(resource); + EXPECT_TRUE(resource->IsLinkPreload()); + platform_->GetURLLoaderMockFactory()->ServeAsynchronousRequests(); + + FetchParameters fetch_params_second{ResourceRequest(url)}; + fetch_params_second.MutableResourceRequest().SetCacheMode( + mojom::FetchCacheMode::kBypassCache); + Resource* second_resource = + MockResource::Fetch(fetch_params_second, fetcher2, nullptr); + + EXPECT_NE(resource, second_resource); + EXPECT_TRUE(resource->IsLinkPreload()); +} + +TEST_F(ResourceFetcherTest, RepetitiveLinkPreloadShouldBeMerged) { + ResourceFetcher* fetcher = ResourceFetcher::Create(Context()); + + KURL url("http://127.0.0.1:8000/foo.png"); + RegisterMockedURLLoad(url); + + FetchParameters fetch_params_for_request{ResourceRequest(url)}; + FetchParameters fetch_params_for_preload{ResourceRequest(url)}; + fetch_params_for_preload.SetLinkPreload(true); + + Resource* resource1 = + MockResource::Fetch(fetch_params_for_preload, fetcher, nullptr); + ASSERT_TRUE(resource1); + EXPECT_TRUE(resource1->IsUnusedPreload()); + EXPECT_TRUE(fetcher->ContainsAsPreload(resource1)); + platform_->GetURLLoaderMockFactory()->ServeAsynchronousRequests(); + + // The second preload fetch returns the first preload. + Resource* resource2 = + MockResource::Fetch(fetch_params_for_preload, fetcher, nullptr); + EXPECT_TRUE(fetcher->ContainsAsPreload(resource1)); + EXPECT_TRUE(resource1->IsUnusedPreload()); + EXPECT_EQ(resource1, resource2); + + // preload matching + Resource* resource3 = + MockResource::Fetch(fetch_params_for_request, fetcher, nullptr); + EXPECT_EQ(resource1, resource3); + EXPECT_FALSE(fetcher->ContainsAsPreload(resource1)); + EXPECT_FALSE(resource1->IsUnusedPreload()); +} + +TEST_F(ResourceFetcherTest, RepetitiveSpeculativePreloadShouldBeMerged) { + ResourceFetcher* fetcher = ResourceFetcher::Create(Context()); + + KURL url("http://127.0.0.1:8000/foo.png"); + RegisterMockedURLLoad(url); + + FetchParameters fetch_params_for_request{ResourceRequest(url)}; + FetchParameters fetch_params_for_preload{ResourceRequest(url)}; + fetch_params_for_preload.SetSpeculativePreloadType( + FetchParameters::SpeculativePreloadType::kInDocument); + + Resource* resource1 = + MockResource::Fetch(fetch_params_for_preload, fetcher, nullptr); + ASSERT_TRUE(resource1); + EXPECT_TRUE(resource1->IsUnusedPreload()); + EXPECT_TRUE(fetcher->ContainsAsPreload(resource1)); + platform_->GetURLLoaderMockFactory()->ServeAsynchronousRequests(); + + // The second preload fetch returns the first preload. + Resource* resource2 = + MockResource::Fetch(fetch_params_for_preload, fetcher, nullptr); + EXPECT_TRUE(fetcher->ContainsAsPreload(resource1)); + EXPECT_TRUE(resource1->IsUnusedPreload()); + EXPECT_EQ(resource1, resource2); + + // preload matching + Resource* resource3 = + MockResource::Fetch(fetch_params_for_request, fetcher, nullptr); + EXPECT_EQ(resource1, resource3); + EXPECT_FALSE(fetcher->ContainsAsPreload(resource1)); + EXPECT_FALSE(resource1->IsUnusedPreload()); +} + +TEST_F(ResourceFetcherTest, SpeculativePreloadShouldBePromotedToLinkePreload) { + ResourceFetcher* fetcher = ResourceFetcher::Create(Context()); + + KURL url("http://127.0.0.1:8000/foo.png"); + RegisterMockedURLLoad(url); + + FetchParameters fetch_params_for_request{ResourceRequest(url)}; + FetchParameters fetch_params_for_speculative_preload{ResourceRequest(url)}; + fetch_params_for_speculative_preload.SetSpeculativePreloadType( + FetchParameters::SpeculativePreloadType::kInDocument); + FetchParameters fetch_params_for_link_preload{ResourceRequest(url)}; + fetch_params_for_link_preload.SetLinkPreload(true); + + Resource* resource1 = MockResource::Fetch( + fetch_params_for_speculative_preload, fetcher, nullptr); + ASSERT_TRUE(resource1); + EXPECT_TRUE(resource1->IsUnusedPreload()); + EXPECT_FALSE(resource1->IsLinkPreload()); + EXPECT_TRUE(fetcher->ContainsAsPreload(resource1)); + platform_->GetURLLoaderMockFactory()->ServeAsynchronousRequests(); + + // The second preload fetch returns the first preload. + Resource* resource2 = + MockResource::Fetch(fetch_params_for_link_preload, fetcher, nullptr); + EXPECT_TRUE(fetcher->ContainsAsPreload(resource1)); + EXPECT_TRUE(resource1->IsUnusedPreload()); + EXPECT_TRUE(resource1->IsLinkPreload()); + EXPECT_EQ(resource1, resource2); + + // preload matching + Resource* resource3 = + MockResource::Fetch(fetch_params_for_request, fetcher, nullptr); + EXPECT_EQ(resource1, resource3); + EXPECT_FALSE(fetcher->ContainsAsPreload(resource1)); + EXPECT_FALSE(resource1->IsUnusedPreload()); + EXPECT_FALSE(resource1->IsLinkPreload()); +} + +TEST_F(ResourceFetcherTest, Revalidate304) { + scoped_refptr<const SecurityOrigin> source_origin = + SecurityOrigin::CreateUnique(); + Context()->SetSecurityOrigin(source_origin); + + KURL url("http://127.0.0.1:8000/foo.html"); + Resource* resource = RawResource::CreateForTest(url, Resource::kRaw); + AddResourceToMemoryCache(resource, source_origin); + + ResourceResponse response(url); + response.SetHTTPStatusCode(304); + response.SetHTTPHeaderField("etag", "1234567890"); + resource->ResponseReceived(response, nullptr); + resource->FinishForTest(); + + ResourceFetcher* fetcher = ResourceFetcher::Create(Context()); + ResourceRequest resource_request(url); + resource_request.SetRequestContext(WebURLRequest::kRequestContextInternal); + FetchParameters fetch_params(resource_request); + platform_->GetURLLoaderMockFactory()->RegisterURL(url, WebURLResponse(), ""); + Resource* new_resource = RawResource::Fetch(fetch_params, fetcher, nullptr); + fetcher->StopFetching(); + + EXPECT_NE(resource, new_resource); +} + +TEST_F(ResourceFetcherTest, LinkPreloadResourceMultipleFetchersAndMove) { + ResourceFetcher* fetcher = ResourceFetcher::Create(Context()); + ResourceFetcher* fetcher2 = ResourceFetcher::Create(Context()); + + KURL url("http://127.0.0.1:8000/foo.png"); + RegisterMockedURLLoad(url); + + FetchParameters fetch_params_original{ResourceRequest(url)}; + fetch_params_original.SetLinkPreload(true); + Resource* resource = + MockResource::Fetch(fetch_params_original, fetcher, nullptr); + ASSERT_TRUE(resource); + EXPECT_TRUE(resource->IsLinkPreload()); + EXPECT_EQ(0, fetcher->BlockingRequestCount()); + + // Resource created by parser on the second fetcher + FetchParameters fetch_params2{ResourceRequest(url)}; + Persistent<MockResourceClient> client2 = new MockResourceClient; + Resource* new_resource2 = + MockResource::Fetch(fetch_params2, fetcher2, client2); + EXPECT_NE(resource, new_resource2); + EXPECT_EQ(0, fetcher2->BlockingRequestCount()); + platform_->GetURLLoaderMockFactory()->ServeAsynchronousRequests(); +} + +TEST_F(ResourceFetcherTest, ContentTypeDataURL) { + ResourceFetcher* fetcher = ResourceFetcher::Create(Context()); + FetchParameters fetch_params{ResourceRequest("data:text/testmimetype,foo")}; + Resource* resource = MockResource::Fetch(fetch_params, fetcher, nullptr); + ASSERT_TRUE(resource); + EXPECT_EQ(ResourceStatus::kCached, resource->GetStatus()); + EXPECT_EQ("text/testmimetype", resource->GetResponse().MimeType()); + EXPECT_EQ("text/testmimetype", resource->GetResponse().HttpContentType()); +} + +// Request with the Content-ID scheme must not be canceled, even if there is no +// MHTMLArchive to serve them. +// Note: Not blocking it is important because there are some embedders of +// Android WebView that are intercepting Content-ID URLs and serve their own +// resources. Please see https://crbug.com/739658. +TEST_F(ResourceFetcherTest, ContentIdURL) { + KURL url("cid:0123456789@example.com"); + ResourceResponse response(url); + response.SetHTTPStatusCode(200); + RegisterMockedURLLoadWithCustomResponse(url, response); + + ResourceFetcher* fetcher = ResourceFetcher::Create(Context()); + + // Main resource case. + { + ResourceRequest resource_request(url); + resource_request.SetRequestContext(WebURLRequest::kRequestContextIframe); + resource_request.SetFrameType( + network::mojom::RequestContextFrameType::kNested); + FetchParameters fetch_params(resource_request); + RawResource* resource = RawResource::FetchMainResource( + fetch_params, fetcher, nullptr, SubstituteData()); + ASSERT_NE(nullptr, resource); + EXPECT_FALSE(resource->ErrorOccurred()); + } + + // Subresource case. + { + ResourceRequest resource_request(url); + resource_request.SetRequestContext(WebURLRequest::kRequestContextVideo); + FetchParameters fetch_params(resource_request); + RawResource* resource = + RawResource::FetchMedia(fetch_params, fetcher, nullptr); + ASSERT_NE(nullptr, resource); + EXPECT_FALSE(resource->ErrorOccurred()); + } +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/platform/loader/fetch/resource_finish_observer.h b/chromium/third_party/blink/renderer/platform/loader/fetch/resource_finish_observer.h new file mode 100644 index 00000000000..72cfbda8334 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/loader/fetch/resource_finish_observer.h @@ -0,0 +1,38 @@ +// 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_PLATFORM_LOADER_FETCH_RESOURCE_FINISH_OBSERVER_H_ +#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_LOADER_FETCH_RESOURCE_FINISH_OBSERVER_H_ + +#include "third_party/blink/renderer/platform/heap/handle.h" +#include "third_party/blink/renderer/platform/platform_export.h" +#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h" + +namespace blink { + +// ResourceFinishObserver is different from ResourceClient in several ways. +// - NotifyFinished is dispatched asynchronously. +// - ResourceFinishObservers will be removed from Resource when the load +// finishes. - This class is not intended to be "subclassed" per each Resource +// subclass. +// There is no ImageResourceFinishObserver, for example. +// ResourceFinishObserver should be quite simple. All notifications must be +// notified AFTER the loading finishes. +class PLATFORM_EXPORT ResourceFinishObserver : public GarbageCollectedMixin { + public: + virtual ~ResourceFinishObserver() = default; + + // Called asynchronously when loading finishes. + // Note that this can be dispatched after removing |this| client from a + // Resource, because of the asynchronicity. + virtual void NotifyFinished() = 0; + // Name for debugging + virtual String DebugName() const = 0; + + void Trace(blink::Visitor* visitor) override {} +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_PLATFORM_LOADER_FETCH_RESOURCE_FINISH_OBSERVER_H_ diff --git a/chromium/third_party/blink/renderer/platform/loader/fetch/resource_load_info.h b/chromium/third_party/blink/renderer/platform/loader/fetch/resource_load_info.h new file mode 100644 index 00000000000..db23bdd7a80 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/loader/fetch/resource_load_info.h @@ -0,0 +1,48 @@ +/* + * 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 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_PLATFORM_LOADER_FETCH_RESOURCE_LOAD_INFO_H_ +#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_LOADER_FETCH_RESOURCE_LOAD_INFO_H_ + +#include "third_party/blink/renderer/platform/network/http_header_map.h" +#include "third_party/blink/renderer/platform/wtf/ref_counted.h" + +namespace blink { + +struct ResourceLoadInfo : RefCounted<ResourceLoadInfo> { + ResourceLoadInfo() : http_status_code(0) {} + + int http_status_code; + String http_status_text; + HTTPHeaderMap request_headers; + HTTPHeaderMap response_headers; + String request_headers_text; + String response_headers_text; + String npn_negotiated_protocol; +}; + +} // namespace blink + +#endif diff --git a/chromium/third_party/blink/renderer/platform/loader/fetch/resource_load_priority.h b/chromium/third_party/blink/renderer/platform/loader/fetch/resource_load_priority.h new file mode 100644 index 00000000000..e5806041157 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/loader/fetch/resource_load_priority.h @@ -0,0 +1,37 @@ +/* + * Copyright (C) 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 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_PLATFORM_LOADER_FETCH_RESOURCE_LOAD_PRIORITY_H_ +#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_LOADER_FETCH_RESOURCE_LOAD_PRIORITY_H_ + +#include "third_party/blink/public/platform/web_url_request.h" + +namespace blink { + +using ResourceLoadPriority = WebURLRequest::Priority; + +} // namespace blink + +#endif diff --git a/chromium/third_party/blink/renderer/platform/loader/fetch/resource_load_scheduler.cc b/chromium/third_party/blink/renderer/platform/loader/fetch/resource_load_scheduler.cc new file mode 100644 index 00000000000..5e0f33f2aa3 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/loader/fetch/resource_load_scheduler.cc @@ -0,0 +1,722 @@ +// 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/platform/loader/fetch/resource_load_scheduler.h" + +#include "base/metrics/field_trial_params.h" +#include "base/metrics/histogram.h" +#include "base/strings/string_number_conversions.h" +#include "third_party/blink/public/platform/platform.h" +#include "third_party/blink/renderer/platform/histogram.h" +#include "third_party/blink/renderer/platform/loader/fetch/fetch_context.h" +#include "third_party/blink/renderer/platform/runtime_enabled_features.h" +#include "third_party/blink/renderer/platform/scheduler/renderer/frame_status.h" +#include "third_party/blink/renderer/platform/scheduler/util/aggregated_metric_reporter.h" + +namespace blink { + +namespace { + +// Field trial name. +const char kResourceLoadSchedulerTrial[] = "ResourceLoadScheduler"; + +// Field trial parameter names. +// Note: bg_limit is supported on m61+, but bg_sub_limit is only on m63+. +// If bg_sub_limit param is not found, we should use bg_limit to make the +// study result statistically correct. +const char kOutstandingLimitForBackgroundMainFrameName[] = "bg_limit"; +const char kOutstandingLimitForBackgroundSubFrameName[] = "bg_sub_limit"; + +// Field trial default parameters. +constexpr size_t kOutstandingLimitForBackgroundFrameDefault = 16u; + +// Maximum request count that request count metrics assume. +constexpr base::HistogramBase::Sample kMaximumReportSize10K = 10000; + +// Maximum traffic bytes that traffic metrics assume. +constexpr base::HistogramBase::Sample kMaximumReportSize1G = + 1 * 1000 * 1000 * 1000; + +// Bucket count for metrics. +constexpr int32_t kReportBucketCount = 25; + +constexpr char kRendererSideResourceScheduler[] = + "RendererSideResourceScheduler"; + +// These values are copied from resource_scheduler.cc, but the meaning is a bit +// different because ResourceScheduler counts the running delayable requests +// while ResourceLoadScheduler counts all the running requests. +constexpr size_t kTightLimitForRendererSideResourceScheduler = 1u; +constexpr size_t kLimitForRendererSideResourceScheduler = 10u; + +constexpr char kTightLimitForRendererSideResourceSchedulerName[] = + "tight_limit"; +constexpr char kLimitForRendererSideResourceSchedulerName[] = "limit"; + +// Represents a resource load circumstance, e.g. from main frame vs sub-frames, +// or on throttled state vs on not-throttled state. +// Used to report histograms. Do not reorder or insert new items. +enum class ReportCircumstance { + kMainframeThrottled, + kMainframeNotThrottled, + kSubframeThrottled, + kSubframeNotThrottled, + // Append new items here. + kNumOfCircumstances, +}; + +base::HistogramBase::Sample ToSample(ReportCircumstance circumstance) { + return static_cast<base::HistogramBase::Sample>(circumstance); +} + +uint32_t GetFieldTrialUint32Param(const char* trial_name, + const char* parameter_name, + uint32_t default_param) { + std::map<std::string, std::string> trial_params; + bool result = base::GetFieldTrialParams(trial_name, &trial_params); + if (!result) + return default_param; + + const auto& found = trial_params.find(parameter_name); + if (found == trial_params.end()) + return default_param; + + uint32_t param; + if (!base::StringToUint(found->second, ¶m)) + return default_param; + + return param; +} + +size_t GetOutstandingThrottledLimit(FetchContext* context) { + DCHECK(context); + + if (!RuntimeEnabledFeatures::ResourceLoadSchedulerEnabled()) + return ResourceLoadScheduler::kOutstandingUnlimited; + + uint32_t main_frame_limit = GetFieldTrialUint32Param( + kResourceLoadSchedulerTrial, kOutstandingLimitForBackgroundMainFrameName, + kOutstandingLimitForBackgroundFrameDefault); + if (context->IsMainFrame()) + return main_frame_limit; + + // We do not have a fixed default limit for sub-frames, but use the limit for + // the main frame so that it works as how previous versions that haven't + // consider sub-frames' specific limit work. + return GetFieldTrialUint32Param(kResourceLoadSchedulerTrial, + kOutstandingLimitForBackgroundSubFrameName, + main_frame_limit); +} + +int TakeWholeKilobytes(int64_t& bytes) { + int kilobytes = bytes / 1024; + bytes %= 1024; + return kilobytes; +} + +} // namespace + +// A class to gather throttling and traffic information to report histograms. +class ResourceLoadScheduler::TrafficMonitor { + public: + explicit TrafficMonitor(FetchContext*); + ~TrafficMonitor(); + + // Notified when the ThrottlingState is changed. + void OnThrottlingStateChanged(FrameScheduler::ThrottlingState); + + // Reports resource request completion. + void Report(const ResourceLoadScheduler::TrafficReportHints&); + + // Reports per-frame reports. + void ReportAll(); + + private: + const bool is_main_frame_; + + const WeakPersistent<FetchContext> context_; // NOT OWNED + + FrameScheduler::ThrottlingState current_state_ = + FrameScheduler::ThrottlingState::kStopped; + + size_t total_throttled_request_count_ = 0; + size_t total_throttled_traffic_bytes_ = 0; + size_t total_throttled_decoded_bytes_ = 0; + size_t total_not_throttled_request_count_ = 0; + size_t total_not_throttled_traffic_bytes_ = 0; + size_t total_not_throttled_decoded_bytes_ = 0; + size_t throttling_state_change_count_ = 0; + bool report_all_is_called_ = false; + + scheduler::AggregatedMetricReporter<scheduler::FrameStatus, int64_t> + traffic_kilobytes_per_frame_status_; + scheduler::AggregatedMetricReporter<scheduler::FrameStatus, int64_t> + decoded_kilobytes_per_frame_status_; +}; + +ResourceLoadScheduler::TrafficMonitor::TrafficMonitor(FetchContext* context) + : is_main_frame_(context->IsMainFrame()), + context_(context), + traffic_kilobytes_per_frame_status_( + "Blink.ResourceLoadScheduler.TrafficBytes.KBPerFrameStatus", + &TakeWholeKilobytes), + decoded_kilobytes_per_frame_status_( + "Blink.ResourceLoadScheduler.DecodedBytes.KBPerFrameStatus", + &TakeWholeKilobytes) { + DCHECK(context_); +} + +ResourceLoadScheduler::TrafficMonitor::~TrafficMonitor() { + ReportAll(); +} + +void ResourceLoadScheduler::TrafficMonitor::OnThrottlingStateChanged( + FrameScheduler::ThrottlingState state) { + current_state_ = state; + throttling_state_change_count_++; +} + +void ResourceLoadScheduler::TrafficMonitor::Report( + const ResourceLoadScheduler::TrafficReportHints& hints) { + // Currently we only care about stats from frames. + if (!IsMainThread()) + return; + if (!hints.IsValid()) + return; + + DEFINE_STATIC_LOCAL(EnumerationHistogram, request_count_by_circumstance, + ("Blink.ResourceLoadScheduler.RequestCount", + ToSample(ReportCircumstance::kNumOfCircumstances))); + + DEFINE_STATIC_LOCAL( + CustomCountHistogram, main_frame_throttled_traffic_bytes, + ("Blink.ResourceLoadScheduler.TrafficBytes.MainframeThrottled", 0, + kMaximumReportSize1G, kReportBucketCount)); + DEFINE_STATIC_LOCAL( + CustomCountHistogram, main_frame_not_throttled_traffic_bytes, + ("Blink.ResourceLoadScheduler.TrafficBytes.MainframeNotThrottled", 0, + kMaximumReportSize1G, kReportBucketCount)); + DEFINE_STATIC_LOCAL( + CustomCountHistogram, sub_frame_throttled_traffic_bytes, + ("Blink.ResourceLoadScheduler.TrafficBytes.SubframeThrottled", 0, + kMaximumReportSize1G, kReportBucketCount)); + DEFINE_STATIC_LOCAL( + CustomCountHistogram, sub_frame_not_throttled_traffic_bytes, + ("Blink.ResourceLoadScheduler.TrafficBytes.SubframeNotThrottled", 0, + kMaximumReportSize1G, kReportBucketCount)); + + DEFINE_STATIC_LOCAL( + CustomCountHistogram, main_frame_throttled_decoded_bytes, + ("Blink.ResourceLoadScheduler.DecodedBytes.MainframeThrottled", 0, + kMaximumReportSize1G, kReportBucketCount)); + DEFINE_STATIC_LOCAL( + CustomCountHistogram, main_frame_not_throttled_decoded_bytes, + ("Blink.ResourceLoadScheduler.DecodedBytes.MainframeNotThrottled", 0, + kMaximumReportSize1G, kReportBucketCount)); + DEFINE_STATIC_LOCAL( + CustomCountHistogram, sub_frame_throttled_decoded_bytes, + ("Blink.ResourceLoadScheduler.DecodedBytes.SubframeThrottled", 0, + kMaximumReportSize1G, kReportBucketCount)); + DEFINE_STATIC_LOCAL( + CustomCountHistogram, sub_frame_not_throttled_decoded_bytes, + ("Blink.ResourceLoadScheduler.DecodedBytes.SubframeNotThrottled", 0, + kMaximumReportSize1G, kReportBucketCount)); + + switch (current_state_) { + case FrameScheduler::ThrottlingState::kThrottled: + if (is_main_frame_) { + request_count_by_circumstance.Count( + ToSample(ReportCircumstance::kMainframeThrottled)); + main_frame_throttled_traffic_bytes.Count(hints.encoded_data_length()); + main_frame_throttled_decoded_bytes.Count(hints.decoded_body_length()); + } else { + request_count_by_circumstance.Count( + ToSample(ReportCircumstance::kSubframeThrottled)); + sub_frame_throttled_traffic_bytes.Count(hints.encoded_data_length()); + sub_frame_throttled_decoded_bytes.Count(hints.decoded_body_length()); + } + total_throttled_request_count_++; + total_throttled_traffic_bytes_ += hints.encoded_data_length(); + total_throttled_decoded_bytes_ += hints.decoded_body_length(); + break; + case FrameScheduler::ThrottlingState::kNotThrottled: + if (is_main_frame_) { + request_count_by_circumstance.Count( + ToSample(ReportCircumstance::kMainframeNotThrottled)); + main_frame_not_throttled_traffic_bytes.Count( + hints.encoded_data_length()); + main_frame_not_throttled_decoded_bytes.Count( + hints.decoded_body_length()); + } else { + request_count_by_circumstance.Count( + ToSample(ReportCircumstance::kSubframeNotThrottled)); + sub_frame_not_throttled_traffic_bytes.Count( + hints.encoded_data_length()); + sub_frame_not_throttled_decoded_bytes.Count( + hints.decoded_body_length()); + } + total_not_throttled_request_count_++; + total_not_throttled_traffic_bytes_ += hints.encoded_data_length(); + total_not_throttled_decoded_bytes_ += hints.decoded_body_length(); + break; + case FrameScheduler::ThrottlingState::kStopped: + break; + } + + // Report kilobytes instead of bytes to avoid overflows. + size_t encoded_kilobytes = hints.encoded_data_length() / 1024; + size_t decoded_kilobytes = hints.decoded_body_length() / 1024; + + if (encoded_kilobytes) { + traffic_kilobytes_per_frame_status_.RecordTask( + scheduler::GetFrameStatus(context_->GetFrameScheduler()), + encoded_kilobytes); + } + if (decoded_kilobytes) { + decoded_kilobytes_per_frame_status_.RecordTask( + scheduler::GetFrameStatus(context_->GetFrameScheduler()), + decoded_kilobytes); + } +} + +void ResourceLoadScheduler::TrafficMonitor::ReportAll() { + // Currently we only care about stats from frames. + if (!IsMainThread()) + return; + + // Blink has several cases to create DocumentLoader not for an actual page + // load use. I.e., per a XMLHttpRequest in "document" type response. + // We just ignore such uninteresting cases in following metrics. + if (!total_throttled_request_count_ && !total_not_throttled_request_count_) + return; + + if (report_all_is_called_) + return; + report_all_is_called_ = true; + + DEFINE_STATIC_LOCAL( + CustomCountHistogram, main_frame_total_throttled_request_count, + ("Blink.ResourceLoadScheduler.TotalRequestCount.MainframeThrottled", 0, + kMaximumReportSize10K, kReportBucketCount)); + DEFINE_STATIC_LOCAL( + CustomCountHistogram, main_frame_total_not_throttled_request_count, + ("Blink.ResourceLoadScheduler.TotalRequestCount.MainframeNotThrottled", 0, + kMaximumReportSize10K, kReportBucketCount)); + DEFINE_STATIC_LOCAL( + CustomCountHistogram, sub_frame_total_throttled_request_count, + ("Blink.ResourceLoadScheduler.TotalRequestCount.SubframeThrottled", 0, + kMaximumReportSize10K, kReportBucketCount)); + DEFINE_STATIC_LOCAL( + CustomCountHistogram, sub_frame_total_not_throttled_request_count, + ("Blink.ResourceLoadScheduler.TotalRequestCount.SubframeNotThrottled", 0, + kMaximumReportSize10K, kReportBucketCount)); + + DEFINE_STATIC_LOCAL( + CustomCountHistogram, main_frame_total_throttled_traffic_bytes, + ("Blink.ResourceLoadScheduler.TotalTrafficBytes.MainframeThrottled", 0, + kMaximumReportSize1G, kReportBucketCount)); + DEFINE_STATIC_LOCAL( + CustomCountHistogram, main_frame_total_not_throttled_traffic_bytes, + ("Blink.ResourceLoadScheduler.TotalTrafficBytes.MainframeNotThrottled", 0, + kMaximumReportSize1G, kReportBucketCount)); + DEFINE_STATIC_LOCAL( + CustomCountHistogram, sub_frame_total_throttled_traffic_bytes, + ("Blink.ResourceLoadScheduler.TotalTrafficBytes.SubframeThrottled", 0, + kMaximumReportSize1G, kReportBucketCount)); + DEFINE_STATIC_LOCAL( + CustomCountHistogram, sub_frame_total_not_throttled_traffic_bytes, + ("Blink.ResourceLoadScheduler.TotalTrafficBytes.SubframeNotThrottled", 0, + kMaximumReportSize1G, kReportBucketCount)); + + DEFINE_STATIC_LOCAL( + CustomCountHistogram, main_frame_total_throttled_decoded_bytes, + ("Blink.ResourceLoadScheduler.TotalDecodedBytes.MainframeThrottled", 0, + kMaximumReportSize1G, kReportBucketCount)); + DEFINE_STATIC_LOCAL( + CustomCountHistogram, main_frame_total_not_throttled_decoded_bytes, + ("Blink.ResourceLoadScheduler.TotalDecodedBytes.MainframeNotThrottled", 0, + kMaximumReportSize1G, kReportBucketCount)); + DEFINE_STATIC_LOCAL( + CustomCountHistogram, sub_frame_total_throttled_decoded_bytes, + ("Blink.ResourceLoadScheduler.TotalDecodedBytes.SubframeThrottled", 0, + kMaximumReportSize1G, kReportBucketCount)); + DEFINE_STATIC_LOCAL( + CustomCountHistogram, sub_frame_total_not_throttled_decoded_bytes, + ("Blink.ResourceLoadScheduler.TotalDecodedBytes.SubframeNotThrottled", 0, + kMaximumReportSize1G, kReportBucketCount)); + + DEFINE_STATIC_LOCAL(CustomCountHistogram, throttling_state_change_count, + ("Blink.ResourceLoadScheduler.ThrottlingStateChangeCount", + 0, 100, kReportBucketCount)); + + if (is_main_frame_) { + main_frame_total_throttled_request_count.Count( + total_throttled_request_count_); + main_frame_total_not_throttled_request_count.Count( + total_not_throttled_request_count_); + main_frame_total_throttled_traffic_bytes.Count( + total_throttled_traffic_bytes_); + main_frame_total_not_throttled_traffic_bytes.Count( + total_not_throttled_traffic_bytes_); + main_frame_total_throttled_decoded_bytes.Count( + total_throttled_decoded_bytes_); + main_frame_total_not_throttled_decoded_bytes.Count( + total_not_throttled_decoded_bytes_); + } else { + sub_frame_total_throttled_request_count.Count( + total_throttled_request_count_); + sub_frame_total_not_throttled_request_count.Count( + total_not_throttled_request_count_); + sub_frame_total_throttled_traffic_bytes.Count( + total_throttled_traffic_bytes_); + sub_frame_total_not_throttled_traffic_bytes.Count( + total_not_throttled_traffic_bytes_); + sub_frame_total_throttled_decoded_bytes.Count( + total_throttled_decoded_bytes_); + sub_frame_total_not_throttled_decoded_bytes.Count( + total_not_throttled_decoded_bytes_); + } + + throttling_state_change_count.Count(throttling_state_change_count_); +} + +constexpr ResourceLoadScheduler::ClientId + ResourceLoadScheduler::kInvalidClientId; + +ResourceLoadScheduler::ResourceLoadScheduler(FetchContext* context) + : outstanding_limit_for_throttled_frame_scheduler_( + GetOutstandingThrottledLimit(context)), + context_(context) { + traffic_monitor_ = + std::make_unique<ResourceLoadScheduler::TrafficMonitor>(context_); + + if (!RuntimeEnabledFeatures::ResourceLoadSchedulerEnabled() && + !Platform::Current()->IsRendererSideResourceSchedulerEnabled()) { + // Initialize TrafficMonitor's state to be |kNotThrottled| so that it + // reports metrics in a reasonable state group. + traffic_monitor_->OnThrottlingStateChanged( + FrameScheduler::ThrottlingState::kNotThrottled); + return; + } + + auto* scheduler = context->GetFrameScheduler(); + if (!scheduler) + return; + + if (Platform::Current()->IsRendererSideResourceSchedulerEnabled()) { + policy_ = context->InitialLoadThrottlingPolicy(); + normal_outstanding_limit_ = + GetFieldTrialUint32Param(kRendererSideResourceScheduler, + kLimitForRendererSideResourceSchedulerName, + kLimitForRendererSideResourceScheduler); + tight_outstanding_limit_ = GetFieldTrialUint32Param( + kRendererSideResourceScheduler, + kTightLimitForRendererSideResourceSchedulerName, + kTightLimitForRendererSideResourceScheduler); + } + + is_enabled_ = true; + scheduler_observer_handle_ = scheduler->AddThrottlingObserver( + FrameScheduler::ObserverType::kLoader, this); +} + +ResourceLoadScheduler* ResourceLoadScheduler::Create(FetchContext* context) { + return new ResourceLoadScheduler(context ? context + : &FetchContext::NullInstance()); +} + +ResourceLoadScheduler::~ResourceLoadScheduler() = default; + +void ResourceLoadScheduler::Trace(blink::Visitor* visitor) { + visitor->Trace(pending_request_map_); + visitor->Trace(context_); +} + +void ResourceLoadScheduler::LoosenThrottlingPolicy() { + switch (policy_) { + case ThrottlingPolicy::kTight: + break; + case ThrottlingPolicy::kNormal: + return; + } + policy_ = ThrottlingPolicy::kNormal; + MaybeRun(); +} + +void ResourceLoadScheduler::Shutdown() { + // Do nothing if the feature is not enabled, or Shutdown() was already called. + if (is_shutdown_) + return; + is_shutdown_ = true; + + if (traffic_monitor_) + traffic_monitor_.reset(); + + scheduler_observer_handle_.reset(); +} + +void ResourceLoadScheduler::Request(ResourceLoadSchedulerClient* client, + ThrottleOption option, + ResourceLoadPriority priority, + int intra_priority, + ResourceLoadScheduler::ClientId* id) { + *id = GenerateClientId(); + if (is_shutdown_) + return; + + if (!Platform::Current()->IsRendererSideResourceSchedulerEnabled()) { + // Prioritization is effectively disabled as we use the constant priority. + priority = ResourceLoadPriority::kMedium; + intra_priority = 0; + } + + if (!is_enabled_ || option == ThrottleOption::kCanNotBeThrottled || + !IsThrottablePriority(priority)) { + Run(*id, client, false); + return; + } + + pending_requests_.emplace(*id, priority, intra_priority); + pending_request_map_.insert( + *id, new ClientWithPriority(client, priority, intra_priority)); + MaybeRun(); +} + +void ResourceLoadScheduler::SetPriority(ClientId client_id, + ResourceLoadPriority priority, + int intra_priority) { + if (!Platform::Current()->IsRendererSideResourceSchedulerEnabled()) + return; + + auto client_it = pending_request_map_.find(client_id); + if (client_it == pending_request_map_.end()) + return; + + auto it = pending_requests_.find(ClientIdWithPriority( + client_id, client_it->value->priority, client_it->value->intra_priority)); + + DCHECK(it != pending_requests_.end()); + pending_requests_.erase(it); + + client_it->value->priority = priority; + client_it->value->intra_priority = intra_priority; + + pending_requests_.emplace(client_id, priority, intra_priority); + MaybeRun(); +} + +bool ResourceLoadScheduler::Release( + ResourceLoadScheduler::ClientId id, + ResourceLoadScheduler::ReleaseOption option, + const ResourceLoadScheduler::TrafficReportHints& hints) { + // Check kInvalidClientId that can not be passed to the HashSet. + if (id == kInvalidClientId) + return false; + + if (running_requests_.find(id) != running_requests_.end()) { + running_requests_.erase(id); + running_throttlable_requests_.erase(id); + + if (traffic_monitor_) + traffic_monitor_->Report(hints); + + if (option == ReleaseOption::kReleaseAndSchedule) + MaybeRun(); + return true; + } + auto found = pending_request_map_.find(id); + if (found != pending_request_map_.end()) { + pending_request_map_.erase(found); + // Intentionally does not remove it from |pending_requests_|. + + // Didn't release any running requests, but the outstanding limit might be + // changed to allow another request. + if (option == ReleaseOption::kReleaseAndSchedule) + MaybeRun(); + return true; + } + return false; +} + +void ResourceLoadScheduler::SetOutstandingLimitForTesting(size_t tight_limit, + size_t normal_limit) { + tight_outstanding_limit_ = tight_limit; + normal_outstanding_limit_ = normal_limit; + MaybeRun(); +} + +void ResourceLoadScheduler::OnNetworkQuiet() { + DCHECK(IsMainThread()); + + // Flush out all traffic reports here for safety. + traffic_monitor_->ReportAll(); + + if (maximum_running_requests_seen_ == 0) + return; + + DEFINE_STATIC_LOCAL( + CustomCountHistogram, main_frame_throttled, + ("Blink.ResourceLoadScheduler.PeakRequests.MainframeThrottled", 0, + kMaximumReportSize10K, kReportBucketCount)); + DEFINE_STATIC_LOCAL( + CustomCountHistogram, main_frame_not_throttled, + ("Blink.ResourceLoadScheduler.PeakRequests.MainframeNotThrottled", 0, + kMaximumReportSize10K, kReportBucketCount)); + DEFINE_STATIC_LOCAL( + CustomCountHistogram, sub_frame_throttled, + ("Blink.ResourceLoadScheduler.PeakRequests.SubframeThrottled", 0, + kMaximumReportSize10K, kReportBucketCount)); + DEFINE_STATIC_LOCAL( + CustomCountHistogram, sub_frame_not_throttled, + ("Blink.ResourceLoadScheduler.PeakRequests.SubframeNotThrottled", 0, + kMaximumReportSize10K, kReportBucketCount)); + + switch (throttling_history_) { + case ThrottlingHistory::kInitial: + case ThrottlingHistory::kNotThrottled: + if (context_->IsMainFrame()) + main_frame_not_throttled.Count(maximum_running_requests_seen_); + else + sub_frame_not_throttled.Count(maximum_running_requests_seen_); + break; + case ThrottlingHistory::kThrottled: + if (context_->IsMainFrame()) + main_frame_throttled.Count(maximum_running_requests_seen_); + else + sub_frame_throttled.Count(maximum_running_requests_seen_); + break; + case ThrottlingHistory::kPartiallyThrottled: + break; + case ThrottlingHistory::kStopped: + break; + } +} + +bool ResourceLoadScheduler::IsThrottablePriority( + ResourceLoadPriority priority) const { + if (!Platform::Current()->IsRendererSideResourceSchedulerEnabled()) + return true; + + if (RuntimeEnabledFeatures::ResourceLoadSchedulerEnabled()) { + // If this scheduler is throttled by the associated FrameScheduler, + // consider every prioritiy as throttlable. + const auto state = frame_scheduler_throttling_state_; + if (state == FrameScheduler::ThrottlingState::kThrottled || + state == FrameScheduler::ThrottlingState::kStopped) { + return true; + } + } + + return priority < ResourceLoadPriority::kHigh; +} + +void ResourceLoadScheduler::OnThrottlingStateChanged( + FrameScheduler::ThrottlingState state) { + if (traffic_monitor_) + traffic_monitor_->OnThrottlingStateChanged(state); + + frame_scheduler_throttling_state_ = state; + + switch (state) { + case FrameScheduler::ThrottlingState::kThrottled: + if (throttling_history_ == ThrottlingHistory::kInitial) + throttling_history_ = ThrottlingHistory::kThrottled; + else if (throttling_history_ == ThrottlingHistory::kNotThrottled) + throttling_history_ = ThrottlingHistory::kPartiallyThrottled; + break; + case FrameScheduler::ThrottlingState::kNotThrottled: + if (throttling_history_ == ThrottlingHistory::kInitial) + throttling_history_ = ThrottlingHistory::kNotThrottled; + else if (throttling_history_ == ThrottlingHistory::kThrottled) + throttling_history_ = ThrottlingHistory::kPartiallyThrottled; + break; + case FrameScheduler::ThrottlingState::kStopped: + throttling_history_ = ThrottlingHistory::kStopped; + break; + } + MaybeRun(); +} + +ResourceLoadScheduler::ClientId ResourceLoadScheduler::GenerateClientId() { + ClientId id = ++current_id_; + CHECK_NE(0u, id); + return id; +} + +void ResourceLoadScheduler::MaybeRun() { + // Requests for keep-alive loaders could be remained in the pending queue, + // but ignore them once Shutdown() is called. + if (is_shutdown_) + return; + + while (!pending_requests_.empty()) { + // TODO(yhirano): Consider using a unified value. + const auto num_requests = + frame_scheduler_throttling_state_ == + FrameScheduler::ThrottlingState::kNotThrottled + ? running_throttlable_requests_.size() + : running_requests_.size(); + + const bool has_enough_running_requets = + num_requests >= GetOutstandingLimit(); + + if (IsThrottablePriority(pending_requests_.begin()->priority) && + has_enough_running_requets) { + break; + } + if (IsThrottablePriority(pending_requests_.begin()->priority) && + has_enough_running_requets) { + break; + } + + ClientId id = pending_requests_.begin()->client_id; + pending_requests_.erase(pending_requests_.begin()); + auto found = pending_request_map_.find(id); + if (found == pending_request_map_.end()) + continue; // Already released. + ResourceLoadSchedulerClient* client = found->value->client; + pending_request_map_.erase(found); + Run(id, client, true); + } +} + +void ResourceLoadScheduler::Run(ResourceLoadScheduler::ClientId id, + ResourceLoadSchedulerClient* client, + bool throttlable) { + running_requests_.insert(id); + if (throttlable) + running_throttlable_requests_.insert(id); + if (running_requests_.size() > maximum_running_requests_seen_) { + maximum_running_requests_seen_ = running_requests_.size(); + } + client->Run(); +} + +size_t ResourceLoadScheduler::GetOutstandingLimit() const { + size_t limit = kOutstandingUnlimited; + + switch (frame_scheduler_throttling_state_) { + case FrameScheduler::ThrottlingState::kThrottled: + limit = std::min(limit, outstanding_limit_for_throttled_frame_scheduler_); + break; + case FrameScheduler::ThrottlingState::kNotThrottled: + break; + case FrameScheduler::ThrottlingState::kStopped: + if (RuntimeEnabledFeatures::ResourceLoadSchedulerEnabled()) + limit = 0; + break; + } + + switch (policy_) { + case ThrottlingPolicy::kTight: + limit = std::min(limit, tight_outstanding_limit_); + break; + case ThrottlingPolicy::kNormal: + limit = std::min(limit, normal_outstanding_limit_); + break; + } + return limit; +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/platform/loader/fetch/resource_load_scheduler.h b/chromium/third_party/blink/renderer/platform/loader/fetch/resource_load_scheduler.h new file mode 100644 index 00000000000..403276a26e4 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/loader/fetch/resource_load_scheduler.h @@ -0,0 +1,318 @@ +// 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_PLATFORM_LOADER_FETCH_RESOURCE_LOAD_SCHEDULER_H_ +#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_LOADER_FETCH_RESOURCE_LOAD_SCHEDULER_H_ + +#include <set> +#include "third_party/blink/renderer/platform/heap/garbage_collected.h" +#include "third_party/blink/renderer/platform/heap/heap_allocator.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/scheduler/public/frame_scheduler.h" +#include "third_party/blink/renderer/platform/wtf/hash_set.h" + +namespace blink { + +class FetchContext; + +// Client interface to use the throttling/scheduling functionality that +// ResourceLoadScheduler provides. +class PLATFORM_EXPORT ResourceLoadSchedulerClient + : public GarbageCollectedMixin { + public: + // Called when the request is granted to run. + virtual void Run() = 0; + + void Trace(blink::Visitor* visitor) override {} +}; + +// ResourceLoadScheduler provides a unified per-frame infrastructure to schedule +// loading requests. When Request() is called with a +// ResourceLoadSchedulerClient |client|, it calls |client|'s Run() method +// synchronously or asynchronously to notify that |client| can start loading. +// +// A ResourceLoadScheduler may initiate a new resource loading in the following +// cases: +// - When Request() is called +// - When LoosenThrottlingPolicy() is called +// - When SetPriority() is called +// - When Release() is called with kReleaseAndSchedule +// - When OnThrottlingStateChanged() is called +// +// A ResourceLoadScheduler determines if a request can be throttable or not, and +// keeps track of pending throttable requests with priority information (i.e., +// ResourceLoadPriority accompanied with an integer called "intra-priority"). +// Here are the general principles: +// - A ResourceLoadScheduler does not throttle requests that cannot be +// throttable. It will call client's Run() method as soon as possible. +// - A ResourceLoadScheduler determines whether a request can be throttable by +// seeing Request()'s ThrottleOption argument and requests' priority +// information. Requests' priority information can be modified via +// SetPriority(). +// - A ResourceLoadScheulder won't initiate a new resource loading which can +// be throttable when there are active resource loading activities more than +// its internal threshold (i.e., what GetOutstandingLimit() returns)". +// +// By default, ResourceLoadScheduler is disabled, which means it doesn't +// throttle any resource loading requests. +// +// Here are running experiments (as of M65): +// - "ResourceLoadScheduler" +// - Resource loading requests are not at throttled when the frame is in +// the foreground tab. +// - Resource loading requests are throttled when the frame is in a +// background tab. It has different thresholds for the main frame +// and sub frames. When the frame has been background for more than five +// minutes, all throttable resource loading requests are throttled +// indefinitely (i.e., threshold is zero in such a circumstance). +// - RendererSideResourceScheduler +// ResourceLoadScheduler has two modes each of which has its own threshold. +// - Tight mode (used until the frame sees a <body> element): +// ResourceLoadScheduler considers a request throttable if its priority +// is less than |kHigh|. +// - Normal mode: +// ResourceLoadScheduler considers a request throttable if its priority +// is less than |kMedium|. +class PLATFORM_EXPORT ResourceLoadScheduler final + : public GarbageCollectedFinalized<ResourceLoadScheduler>, + public FrameScheduler::Observer { + WTF_MAKE_NONCOPYABLE(ResourceLoadScheduler); + + public: + // An option to use in calling Request(). If kCanNotBeThrottled is specified, + // the request should be granted and Run() should be called synchronously. + // Otherwise, OnRequestGranted() could be called later when other outstanding + // requests are finished. + enum class ThrottleOption { kCanBeThrottled, kCanNotBeThrottled }; + + // An option to use in calling Release(). If kReleaseOnly is specified, + // the specified request should be released, but no other requests should + // be scheduled within the call. + enum class ReleaseOption { kReleaseOnly, kReleaseAndSchedule }; + + // A class to pass traffic report hints on calling Release(). + class TrafficReportHints { + public: + // |encoded_data_length| is payload size in bytes sent over the network. + // |decoded_body_length| is received resource data size in bytes. + TrafficReportHints(int64_t encoded_data_length, int64_t decoded_body_length) + : valid_(true), + encoded_data_length_(encoded_data_length), + decoded_body_length_(decoded_body_length) {} + + // Returns the instance that represents an invalid report, which can be + // used when a caller don't want to report traffic, i.e. on a failure. + static PLATFORM_EXPORT TrafficReportHints InvalidInstance() { + return TrafficReportHints(); + } + + bool IsValid() const { return valid_; } + + int64_t encoded_data_length() const { + DCHECK(valid_); + return encoded_data_length_; + } + int64_t decoded_body_length() const { + DCHECK(valid_); + return decoded_body_length_; + } + + private: + // Default constructor makes an invalid instance that won't be recorded. + TrafficReportHints() = default; + + bool valid_ = false; + int64_t encoded_data_length_ = 0; + int64_t decoded_body_length_ = 0; + }; + + // ResourceLoadScheduler has two policies: |kTight| and |kNormal|. Currently + // this is used to support aggressive throttling while the corresponding frame + // is in layout-blocking phase. There is only one state transition, + // |kTight| => |kNormal|, which is done by |LoosenThrottlingPolicy|. + enum class ThrottlingPolicy { kTight, kNormal }; + + // Returned on Request(). Caller should need to return it via Release(). + using ClientId = uint64_t; + + static constexpr ClientId kInvalidClientId = 0u; + + static constexpr size_t kOutstandingUnlimited = + std::numeric_limits<size_t>::max(); + + static ResourceLoadScheduler* Create(FetchContext* = nullptr); + ~ResourceLoadScheduler(); + + void Trace(blink::Visitor*); + + // Changes the policy from |kTight| to |kNormal|. This function can be called + // multiple times, and does nothing when the scheduler is already working with + // the normal policy. This function may initiate a new resource loading. + void LoosenThrottlingPolicy(); + + // Stops all operations including observing throttling signals. + // ResourceLoadSchedulerClient::Run() will not be called once this method is + // called. This method can be called multiple times safely. + void Shutdown(); + + // Makes a request. This may synchronously call + // ResourceLoadSchedulerClient::Run(), but it is guaranteed that ClientId is + // populated before ResourceLoadSchedulerClient::Run() is called, so that the + // caller can call Release() with the assigned ClientId correctly. + void Request(ResourceLoadSchedulerClient*, + ThrottleOption, + ResourceLoadPriority, + int intra_priority, + ClientId*); + + // Updates the priority information of the given client. This function may + // initiate a new resource loading. + void SetPriority(ClientId, ResourceLoadPriority, int intra_priority); + + // ResourceLoadSchedulerClient should call this method when the loading is + // finished, or canceled. This method can be called in a pre-finalization + // step, bug the ReleaseOption must be kReleaseOnly in such a case. + // TrafficReportHints is for reporting histograms. + // TrafficReportHints::InvalidInstance() can be used to omit reporting. + bool Release(ClientId, ReleaseOption, const TrafficReportHints&); + + // Checks if the specified client was already scheduled to call Run(), but + // haven't call Release() yet. + bool IsRunning(ClientId id) { return running_requests_.Contains(id); } + + // Sets outstanding limit for testing. + void SetOutstandingLimitForTesting(size_t limit) { + SetOutstandingLimitForTesting(limit, limit); + } + void SetOutstandingLimitForTesting(size_t tight_limit, size_t normal_limit); + + void OnNetworkQuiet(); + + // Returns whether we can throttle a request with the given priority. + // This function returns false when RendererSideResourceScheduler is disabled. + bool IsThrottablePriority(ResourceLoadPriority) const; + + // FrameScheduler::Observer overrides: + void OnThrottlingStateChanged(FrameScheduler::ThrottlingState) override; + + private: + class TrafficMonitor; + + class ClientIdWithPriority { + public: + struct Compare { + bool operator()(const ClientIdWithPriority& x, + const ClientIdWithPriority& y) const { + if (x.priority != y.priority) + return x.priority > y.priority; + if (x.intra_priority != y.intra_priority) + return x.intra_priority > y.intra_priority; + return x.client_id < y.client_id; + } + }; + + ClientIdWithPriority(ClientId client_id, + WebURLRequest::Priority priority, + int intra_priority) + : client_id(client_id), + priority(priority), + intra_priority(intra_priority) {} + + const ClientId client_id; + const WebURLRequest::Priority priority; + const int intra_priority; + }; + + struct ClientWithPriority : public GarbageCollected<ClientWithPriority> { + ClientWithPriority(ResourceLoadSchedulerClient* client, + ResourceLoadPriority priority, + int intra_priority) + : client(client), priority(priority), intra_priority(intra_priority) {} + + void Trace(blink::Visitor* visitor) { visitor->Trace(client); } + + Member<ResourceLoadSchedulerClient> client; + ResourceLoadPriority priority; + int intra_priority; + }; + + ResourceLoadScheduler(FetchContext*); + + // Generates the next ClientId. + ClientId GenerateClientId(); + + // Picks up one client if there is a budget and route it to run. + void MaybeRun(); + + // Grants a client to run, + void Run(ClientId, ResourceLoadSchedulerClient*, bool throttlable); + + size_t GetOutstandingLimit() const; + + // A flag to indicate an internal running state. + // TODO(toyoshim): We may want to use enum once we start to have more states. + bool is_shutdown_ = false; + + // A mutable flag to indicate if the throttling and scheduling are enabled. + // Can be modified by field trial flags or for testing. + bool is_enabled_ = false; + + ThrottlingPolicy policy_ = ThrottlingPolicy::kNormal; + + // ResourceLoadScheduler threshold values for various circumstances. Some + // conditions can overlap, and ResourceLoadScheduler chooses the smallest + // value in such cases. + + // Used when |policy_| is |kTight|. + size_t tight_outstanding_limit_ = kOutstandingUnlimited; + + // Used when |policy_| is |kNormal|. + size_t normal_outstanding_limit_ = kOutstandingUnlimited; + + // Used when |frame_scheduler_throttling_state_| is |kThrottled|. + const size_t outstanding_limit_for_throttled_frame_scheduler_; + + // The last used ClientId to calculate the next. + ClientId current_id_ = kInvalidClientId; + + // Holds clients that were granted and are running. + HashSet<ClientId> running_requests_; + + HashSet<ClientId> running_throttlable_requests_; + + // Largest number of running requests seen so far. + unsigned maximum_running_requests_seen_ = 0; + + enum class ThrottlingHistory { + kInitial, + kThrottled, + kNotThrottled, + kPartiallyThrottled, + kStopped, + }; + ThrottlingHistory throttling_history_ = ThrottlingHistory::kInitial; + FrameScheduler::ThrottlingState frame_scheduler_throttling_state_ = + FrameScheduler::ThrottlingState::kNotThrottled; + + // Holds clients that haven't been granted, and are waiting for a grant. + HeapHashMap<ClientId, Member<ClientWithPriority>> pending_request_map_; + // We use std::set here because WTF doesn't have its counterpart. + std::set<ClientIdWithPriority, ClientIdWithPriority::Compare> + pending_requests_; + + // Holds an internal class instance to monitor and report traffic. + std::unique_ptr<TrafficMonitor> traffic_monitor_; + + // Holds FetchContext reference to contact FrameScheduler. + Member<FetchContext> context_; + + // Handle to throttling observer. + std::unique_ptr<FrameScheduler::ThrottlingObserverHandle> + scheduler_observer_handle_; +}; + +} // namespace blink + +#endif diff --git a/chromium/third_party/blink/renderer/platform/loader/fetch/resource_load_scheduler_test.cc b/chromium/third_party/blink/renderer/platform/loader/fetch/resource_load_scheduler_test.cc new file mode 100644 index 00000000000..059d772e417 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/loader/fetch/resource_load_scheduler_test.cc @@ -0,0 +1,520 @@ +// 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/platform/loader/fetch/resource_load_scheduler.h" + +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/blink/renderer/platform/loader/testing/mock_fetch_context.h" +#include "third_party/blink/renderer/platform/runtime_enabled_features.h" +#include "third_party/blink/renderer/platform/testing/testing_platform_support.h" + +namespace blink { +namespace { + +class MockClient final : public GarbageCollectedFinalized<MockClient>, + public ResourceLoadSchedulerClient { + USING_GARBAGE_COLLECTED_MIXIN(MockClient); + + public: + ~MockClient() = default; + + void Run() override { + EXPECT_FALSE(was_run_); + was_run_ = true; + } + bool WasRun() { return was_run_; } + + void Trace(blink::Visitor* visitor) override { + ResourceLoadSchedulerClient::Trace(visitor); + } + + private: + bool was_run_ = false; +}; + +class ResourceLoadSchedulerTest : public testing::Test { + public: + using ThrottleOption = ResourceLoadScheduler::ThrottleOption; + void SetUp() override { + DCHECK(RuntimeEnabledFeatures::ResourceLoadSchedulerEnabled()); + scheduler_ = ResourceLoadScheduler::Create( + MockFetchContext::Create(MockFetchContext::kShouldNotLoadNewResource)); + Scheduler()->SetOutstandingLimitForTesting(1); + } + void TearDown() override { Scheduler()->Shutdown(); } + + ResourceLoadScheduler* Scheduler() { return scheduler_; } + + bool Release(ResourceLoadScheduler::ClientId client) { + return Scheduler()->Release( + client, ResourceLoadScheduler::ReleaseOption::kReleaseOnly, + ResourceLoadScheduler::TrafficReportHints::InvalidInstance()); + } + bool ReleaseAndSchedule(ResourceLoadScheduler::ClientId client) { + return Scheduler()->Release( + client, ResourceLoadScheduler::ReleaseOption::kReleaseAndSchedule, + ResourceLoadScheduler::TrafficReportHints::InvalidInstance()); + } + + private: + Persistent<ResourceLoadScheduler> scheduler_; +}; + +class RendererSideResourceSchedulerTest : public testing::Test { + public: + using ThrottleOption = ResourceLoadScheduler::ThrottleOption; + class TestingPlatformSupport : public ::blink::TestingPlatformSupport { + public: + bool IsRendererSideResourceSchedulerEnabled() const override { + return true; + } + }; + + void SetUp() override { + DCHECK(RuntimeEnabledFeatures::ResourceLoadSchedulerEnabled()); + scheduler_ = ResourceLoadScheduler::Create( + MockFetchContext::Create(MockFetchContext::kShouldNotLoadNewResource)); + Scheduler()->SetOutstandingLimitForTesting(1); + } + void TearDown() override { Scheduler()->Shutdown(); } + + ResourceLoadScheduler* Scheduler() { return scheduler_; } + + bool Release(ResourceLoadScheduler::ClientId client) { + return Scheduler()->Release( + client, ResourceLoadScheduler::ReleaseOption::kReleaseOnly, + ResourceLoadScheduler::TrafficReportHints::InvalidInstance()); + } + + private: + ScopedTestingPlatformSupport<TestingPlatformSupport> + testing_platform_support_; + Persistent<ResourceLoadScheduler> scheduler_; +}; + +TEST_F(ResourceLoadSchedulerTest, Bypass) { + // A request that disallows throttling should be ran synchronously. + MockClient* client1 = new MockClient; + ResourceLoadScheduler::ClientId id1 = ResourceLoadScheduler::kInvalidClientId; + Scheduler()->Request(client1, ThrottleOption::kCanNotBeThrottled, + ResourceLoadPriority::kMedium, 0 /* intra_priority */, + &id1); + EXPECT_NE(ResourceLoadScheduler::kInvalidClientId, id1); + EXPECT_TRUE(client1->WasRun()); + + // Another request that disallows throttling also should be ran even it makes + // the outstanding number reaches to the limit. + MockClient* client2 = new MockClient; + ResourceLoadScheduler::ClientId id2 = ResourceLoadScheduler::kInvalidClientId; + Scheduler()->Request(client2, ThrottleOption::kCanNotBeThrottled, + ResourceLoadPriority::kMedium, 0 /* intra_priority */, + &id2); + EXPECT_NE(ResourceLoadScheduler::kInvalidClientId, id2); + EXPECT_TRUE(client2->WasRun()); + + // Call Release() with different options just in case. + EXPECT_TRUE(Release(id1)); + EXPECT_TRUE(ReleaseAndSchedule(id2)); + + // Should not succeed to call with the same ID twice. + EXPECT_FALSE(Release(id1)); + + // Should not succeed to call with the invalid ID or unused ID. + EXPECT_FALSE(Release(ResourceLoadScheduler::kInvalidClientId)); + + EXPECT_FALSE(Release(static_cast<ResourceLoadScheduler::ClientId>(774))); +} + +TEST_F(ResourceLoadSchedulerTest, Throttled) { + // The first request should be ran synchronously. + MockClient* client1 = new MockClient; + ResourceLoadScheduler::ClientId id1 = ResourceLoadScheduler::kInvalidClientId; + Scheduler()->Request(client1, ThrottleOption::kCanBeThrottled, + ResourceLoadPriority::kMedium, 0 /* intra_priority */, + &id1); + EXPECT_NE(ResourceLoadScheduler::kInvalidClientId, id1); + EXPECT_TRUE(client1->WasRun()); + + // Another request should be throttled until the first request calls Release. + MockClient* client2 = new MockClient; + ResourceLoadScheduler::ClientId id2 = ResourceLoadScheduler::kInvalidClientId; + Scheduler()->Request(client2, ThrottleOption::kCanBeThrottled, + ResourceLoadPriority::kMedium, 0 /* intra_priority */, + &id2); + EXPECT_NE(ResourceLoadScheduler::kInvalidClientId, id2); + EXPECT_FALSE(client2->WasRun()); + + // Two more requests. + MockClient* client3 = new MockClient; + ResourceLoadScheduler::ClientId id3 = ResourceLoadScheduler::kInvalidClientId; + Scheduler()->Request(client3, ThrottleOption::kCanBeThrottled, + ResourceLoadPriority::kMedium, 0 /* intra_priority */, + &id3); + EXPECT_NE(ResourceLoadScheduler::kInvalidClientId, id3); + EXPECT_FALSE(client3->WasRun()); + + MockClient* client4 = new MockClient; + ResourceLoadScheduler::ClientId id4 = ResourceLoadScheduler::kInvalidClientId; + Scheduler()->Request(client4, ThrottleOption::kCanBeThrottled, + ResourceLoadPriority::kMedium, 0 /* intra_priority */, + &id4); + EXPECT_NE(ResourceLoadScheduler::kInvalidClientId, id4); + EXPECT_FALSE(client4->WasRun()); + + // Call Release() to run the second request. + EXPECT_TRUE(ReleaseAndSchedule(id1)); + EXPECT_TRUE(client2->WasRun()); + + // Call Release() with kReleaseOnly should not run the third and the fourth + // requests. + EXPECT_TRUE(Release(id2)); + EXPECT_FALSE(client3->WasRun()); + EXPECT_FALSE(client4->WasRun()); + + // Should be able to call Release() for a client that hasn't run yet. This + // should run another scheduling to run the fourth request. + EXPECT_TRUE(ReleaseAndSchedule(id3)); + EXPECT_TRUE(client4->WasRun()); +} + +TEST_F(ResourceLoadSchedulerTest, Unthrottle) { + // Push three requests. + MockClient* client1 = new MockClient; + ResourceLoadScheduler::ClientId id1 = ResourceLoadScheduler::kInvalidClientId; + Scheduler()->Request(client1, ThrottleOption::kCanBeThrottled, + ResourceLoadPriority::kMedium, 0 /* intra_priority */, + &id1); + EXPECT_NE(ResourceLoadScheduler::kInvalidClientId, id1); + EXPECT_TRUE(client1->WasRun()); + + MockClient* client2 = new MockClient; + ResourceLoadScheduler::ClientId id2 = ResourceLoadScheduler::kInvalidClientId; + Scheduler()->Request(client2, ThrottleOption::kCanBeThrottled, + ResourceLoadPriority::kMedium, 0 /* intra_priority */, + &id2); + EXPECT_NE(ResourceLoadScheduler::kInvalidClientId, id2); + EXPECT_FALSE(client2->WasRun()); + + MockClient* client3 = new MockClient; + ResourceLoadScheduler::ClientId id3 = ResourceLoadScheduler::kInvalidClientId; + Scheduler()->Request(client3, ThrottleOption::kCanBeThrottled, + ResourceLoadPriority::kMedium, 0 /* intra_priority */, + &id3); + EXPECT_NE(ResourceLoadScheduler::kInvalidClientId, id3); + EXPECT_FALSE(client3->WasRun()); + + // Allows to pass all requests. + Scheduler()->SetOutstandingLimitForTesting(3); + EXPECT_TRUE(client2->WasRun()); + EXPECT_TRUE(client3->WasRun()); + + // Release all. + EXPECT_TRUE(Release(id3)); + EXPECT_TRUE(Release(id2)); + EXPECT_TRUE(Release(id1)); +} + +TEST_F(ResourceLoadSchedulerTest, Stopped) { + // Push three requests. + MockClient* client1 = new MockClient; + ResourceLoadScheduler::ClientId id1 = ResourceLoadScheduler::kInvalidClientId; + Scheduler()->Request(client1, ThrottleOption::kCanBeThrottled, + ResourceLoadPriority::kMedium, 0 /* intra_priority */, + &id1); + EXPECT_NE(ResourceLoadScheduler::kInvalidClientId, id1); + EXPECT_TRUE(client1->WasRun()); + + MockClient* client2 = new MockClient; + ResourceLoadScheduler::ClientId id2 = ResourceLoadScheduler::kInvalidClientId; + Scheduler()->Request(client2, ThrottleOption::kCanBeThrottled, + ResourceLoadPriority::kMedium, 0 /* intra_priority */, + &id2); + EXPECT_NE(ResourceLoadScheduler::kInvalidClientId, id2); + EXPECT_FALSE(client2->WasRun()); + + MockClient* client3 = new MockClient; + ResourceLoadScheduler::ClientId id3 = ResourceLoadScheduler::kInvalidClientId; + Scheduler()->Request(client3, ThrottleOption::kCanBeThrottled, + ResourceLoadPriority::kMedium, 0 /* intra_priority */, + &id3); + EXPECT_NE(ResourceLoadScheduler::kInvalidClientId, id3); + EXPECT_FALSE(client3->WasRun()); + + // Setting outstanding_limit_ to 0 in ThrottlingState::kStopped, prevents + // further requests. + Scheduler()->SetOutstandingLimitForTesting(0); + EXPECT_FALSE(client2->WasRun()); + EXPECT_FALSE(client3->WasRun()); + + // Calling Release() still does not run the second request. + EXPECT_TRUE(ReleaseAndSchedule(id1)); + EXPECT_FALSE(client2->WasRun()); + EXPECT_FALSE(client3->WasRun()); + + // Release all. + EXPECT_TRUE(Release(id3)); + EXPECT_TRUE(Release(id2)); +} + +TEST_F(ResourceLoadSchedulerTest, PriotrityIsNotConsidered) { + // Push three requests. + MockClient* client1 = new MockClient; + + Scheduler()->SetOutstandingLimitForTesting(0); + + ResourceLoadScheduler::ClientId id1 = ResourceLoadScheduler::kInvalidClientId; + Scheduler()->Request(client1, ThrottleOption::kCanBeThrottled, + ResourceLoadPriority::kLowest, 10 /* intra_priority */, + &id1); + EXPECT_NE(ResourceLoadScheduler::kInvalidClientId, id1); + + MockClient* client2 = new MockClient; + ResourceLoadScheduler::ClientId id2 = ResourceLoadScheduler::kInvalidClientId; + Scheduler()->Request(client2, ThrottleOption::kCanBeThrottled, + ResourceLoadPriority::kLow, 1 /* intra_priority */, + &id2); + EXPECT_NE(ResourceLoadScheduler::kInvalidClientId, id2); + + MockClient* client3 = new MockClient; + ResourceLoadScheduler::ClientId id3 = ResourceLoadScheduler::kInvalidClientId; + Scheduler()->Request(client3, ThrottleOption::kCanBeThrottled, + ResourceLoadPriority::kLow, 3 /* intra_priority */, + &id3); + EXPECT_NE(ResourceLoadScheduler::kInvalidClientId, id3); + + EXPECT_FALSE(client1->WasRun()); + EXPECT_FALSE(client2->WasRun()); + EXPECT_FALSE(client3->WasRun()); + + Scheduler()->SetOutstandingLimitForTesting(1); + + EXPECT_TRUE(client1->WasRun()); + EXPECT_FALSE(client2->WasRun()); + EXPECT_FALSE(client3->WasRun()); + + Scheduler()->SetOutstandingLimitForTesting(2); + + EXPECT_TRUE(client1->WasRun()); + EXPECT_TRUE(client2->WasRun()); + EXPECT_FALSE(client3->WasRun()); + + // Release all. + EXPECT_TRUE(Release(id3)); + EXPECT_TRUE(Release(id2)); + EXPECT_TRUE(Release(id1)); +} + +TEST_F(RendererSideResourceSchedulerTest, PriorityIsConsidered) { + // Push three requests. + MockClient* client1 = new MockClient; + + Scheduler()->SetOutstandingLimitForTesting(0); + + ResourceLoadScheduler::ClientId id1 = ResourceLoadScheduler::kInvalidClientId; + Scheduler()->Request(client1, ThrottleOption::kCanBeThrottled, + ResourceLoadPriority::kLowest, 10 /* intra_priority */, + &id1); + EXPECT_NE(ResourceLoadScheduler::kInvalidClientId, id1); + + MockClient* client2 = new MockClient; + ResourceLoadScheduler::ClientId id2 = ResourceLoadScheduler::kInvalidClientId; + Scheduler()->Request(client2, ThrottleOption::kCanBeThrottled, + ResourceLoadPriority::kLow, 1 /* intra_priority */, + &id2); + EXPECT_NE(ResourceLoadScheduler::kInvalidClientId, id2); + + MockClient* client3 = new MockClient; + ResourceLoadScheduler::ClientId id3 = ResourceLoadScheduler::kInvalidClientId; + Scheduler()->Request(client3, ThrottleOption::kCanBeThrottled, + ResourceLoadPriority::kLow, 3 /* intra_priority */, + &id3); + EXPECT_NE(ResourceLoadScheduler::kInvalidClientId, id3); + + MockClient* client4 = new MockClient; + ResourceLoadScheduler::ClientId id4 = ResourceLoadScheduler::kInvalidClientId; + Scheduler()->Request(client4, ThrottleOption::kCanBeThrottled, + ResourceLoadPriority::kHigh, 0 /* intra_priority */, + &id4); + EXPECT_NE(ResourceLoadScheduler::kInvalidClientId, id4); + + EXPECT_FALSE(client1->WasRun()); + EXPECT_FALSE(client2->WasRun()); + EXPECT_FALSE(client3->WasRun()); + EXPECT_TRUE(client4->WasRun()); + + Scheduler()->SetOutstandingLimitForTesting(1); + + EXPECT_FALSE(client1->WasRun()); + EXPECT_FALSE(client2->WasRun()); + EXPECT_TRUE(client3->WasRun()); + EXPECT_TRUE(client4->WasRun()); + + Scheduler()->SetOutstandingLimitForTesting(2); + + EXPECT_FALSE(client1->WasRun()); + EXPECT_TRUE(client2->WasRun()); + EXPECT_TRUE(client3->WasRun()); + EXPECT_TRUE(client4->WasRun()); + + // Release all. + EXPECT_TRUE(Release(id4)); + EXPECT_TRUE(Release(id3)); + EXPECT_TRUE(Release(id2)); + EXPECT_TRUE(Release(id1)); +} + +TEST_F(RendererSideResourceSchedulerTest, IsThrottablePriority) { + EXPECT_TRUE( + Scheduler()->IsThrottablePriority(ResourceLoadPriority::kVeryLow)); + EXPECT_TRUE(Scheduler()->IsThrottablePriority(ResourceLoadPriority::kLow)); + EXPECT_TRUE(Scheduler()->IsThrottablePriority(ResourceLoadPriority::kMedium)); + EXPECT_FALSE(Scheduler()->IsThrottablePriority(ResourceLoadPriority::kHigh)); + EXPECT_FALSE( + Scheduler()->IsThrottablePriority(ResourceLoadPriority::kVeryHigh)); + + Scheduler()->LoosenThrottlingPolicy(); + + EXPECT_TRUE( + Scheduler()->IsThrottablePriority(ResourceLoadPriority::kVeryLow)); + EXPECT_TRUE(Scheduler()->IsThrottablePriority(ResourceLoadPriority::kLow)); + EXPECT_TRUE(Scheduler()->IsThrottablePriority(ResourceLoadPriority::kMedium)); + EXPECT_FALSE(Scheduler()->IsThrottablePriority(ResourceLoadPriority::kHigh)); + EXPECT_FALSE( + Scheduler()->IsThrottablePriority(ResourceLoadPriority::kVeryHigh)); +} + +TEST_F(RendererSideResourceSchedulerTest, SetPriority) { + // Start with the normal scheduling policy. + Scheduler()->LoosenThrottlingPolicy(); + // Push three requests. + MockClient* client1 = new MockClient; + + Scheduler()->SetOutstandingLimitForTesting(0); + + ResourceLoadScheduler::ClientId id1 = ResourceLoadScheduler::kInvalidClientId; + Scheduler()->Request(client1, ThrottleOption::kCanBeThrottled, + ResourceLoadPriority::kLowest, 0 /* intra_priority */, + &id1); + EXPECT_NE(ResourceLoadScheduler::kInvalidClientId, id1); + + MockClient* client2 = new MockClient; + ResourceLoadScheduler::ClientId id2 = ResourceLoadScheduler::kInvalidClientId; + Scheduler()->Request(client2, ThrottleOption::kCanBeThrottled, + ResourceLoadPriority::kLow, 5 /* intra_priority */, + &id2); + EXPECT_NE(ResourceLoadScheduler::kInvalidClientId, id2); + + MockClient* client3 = new MockClient; + ResourceLoadScheduler::ClientId id3 = ResourceLoadScheduler::kInvalidClientId; + Scheduler()->Request(client3, ThrottleOption::kCanBeThrottled, + ResourceLoadPriority::kLow, 10 /* intra_priority */, + &id3); + EXPECT_NE(ResourceLoadScheduler::kInvalidClientId, id3); + + EXPECT_FALSE(client1->WasRun()); + EXPECT_FALSE(client2->WasRun()); + EXPECT_FALSE(client3->WasRun()); + + Scheduler()->SetPriority(id1, ResourceLoadPriority::kHigh, 0); + + EXPECT_TRUE(client1->WasRun()); + EXPECT_FALSE(client2->WasRun()); + EXPECT_FALSE(client3->WasRun()); + + Scheduler()->SetPriority(id3, ResourceLoadPriority::kLow, 2); + + EXPECT_TRUE(client1->WasRun()); + EXPECT_FALSE(client2->WasRun()); + EXPECT_FALSE(client3->WasRun()); + + Scheduler()->SetOutstandingLimitForTesting(2); + + EXPECT_TRUE(client1->WasRun()); + EXPECT_TRUE(client2->WasRun()); + EXPECT_FALSE(client3->WasRun()); + + // Release all. + EXPECT_TRUE(Release(id3)); + EXPECT_TRUE(Release(id2)); + EXPECT_TRUE(Release(id1)); +} + +TEST_F(RendererSideResourceSchedulerTest, LoosenThrottlingPolicy) { + MockClient* client1 = new MockClient; + + Scheduler()->SetOutstandingLimitForTesting(0, 0); + + ResourceLoadScheduler::ClientId id1 = ResourceLoadScheduler::kInvalidClientId; + Scheduler()->Request(client1, ThrottleOption::kCanBeThrottled, + ResourceLoadPriority::kLowest, 0 /* intra_priority */, + &id1); + EXPECT_NE(ResourceLoadScheduler::kInvalidClientId, id1); + + MockClient* client2 = new MockClient; + ResourceLoadScheduler::ClientId id2 = ResourceLoadScheduler::kInvalidClientId; + Scheduler()->Request(client2, ThrottleOption::kCanBeThrottled, + ResourceLoadPriority::kLowest, 0 /* intra_priority */, + &id2); + EXPECT_NE(ResourceLoadScheduler::kInvalidClientId, id2); + + MockClient* client3 = new MockClient; + ResourceLoadScheduler::ClientId id3 = ResourceLoadScheduler::kInvalidClientId; + Scheduler()->Request(client3, ThrottleOption::kCanBeThrottled, + ResourceLoadPriority::kLowest, 0 /* intra_priority */, + &id3); + EXPECT_NE(ResourceLoadScheduler::kInvalidClientId, id3); + + MockClient* client4 = new MockClient; + ResourceLoadScheduler::ClientId id4 = ResourceLoadScheduler::kInvalidClientId; + Scheduler()->Request(client4, ThrottleOption::kCanBeThrottled, + ResourceLoadPriority::kLowest, 0 /* intra_priority */, + &id4); + EXPECT_NE(ResourceLoadScheduler::kInvalidClientId, id4); + + Scheduler()->SetPriority(id2, ResourceLoadPriority::kLow, 0); + Scheduler()->SetPriority(id3, ResourceLoadPriority::kLow, 0); + Scheduler()->SetPriority(id4, ResourceLoadPriority::kMedium, 0); + + // As the policy is |kTight|, |kMedium| is throttled. + EXPECT_FALSE(client1->WasRun()); + EXPECT_FALSE(client2->WasRun()); + EXPECT_FALSE(client3->WasRun()); + EXPECT_FALSE(client4->WasRun()); + + Scheduler()->SetOutstandingLimitForTesting(0, 2); + + // MockFetchContext's initial scheduling policy is |kTight|, setting the + // outstanding limit for the normal mode doesn't take effect. + EXPECT_FALSE(client1->WasRun()); + EXPECT_FALSE(client2->WasRun()); + EXPECT_FALSE(client3->WasRun()); + EXPECT_FALSE(client4->WasRun()); + + // Now let's tighten the limit again. + Scheduler()->SetOutstandingLimitForTesting(0, 0); + + // ...and change the scheduling policy to |kNormal|. + Scheduler()->LoosenThrottlingPolicy(); + + EXPECT_FALSE(client1->WasRun()); + EXPECT_FALSE(client2->WasRun()); + EXPECT_FALSE(client3->WasRun()); + EXPECT_FALSE(client4->WasRun()); + + Scheduler()->SetOutstandingLimitForTesting(0, 2); + + EXPECT_FALSE(client1->WasRun()); + EXPECT_TRUE(client2->WasRun()); + EXPECT_FALSE(client3->WasRun()); + EXPECT_TRUE(client4->WasRun()); + + // Release all. + EXPECT_TRUE(Release(id4)); + EXPECT_TRUE(Release(id3)); + EXPECT_TRUE(Release(id2)); + EXPECT_TRUE(Release(id1)); +} + +} // namespace +} // namespace blink diff --git a/chromium/third_party/blink/renderer/platform/loader/fetch/resource_load_timing.cc b/chromium/third_party/blink/renderer/platform/loader/fetch/resource_load_timing.cc new file mode 100644 index 00000000000..ac8c07f7469 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/loader/fetch/resource_load_timing.cc @@ -0,0 +1,126 @@ +// 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/platform/loader/fetch/resource_load_timing.h" + +#include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h" + +namespace blink { + +ResourceLoadTiming::ResourceLoadTiming() {} + +scoped_refptr<ResourceLoadTiming> ResourceLoadTiming::Create() { + return base::AdoptRef(new ResourceLoadTiming); +} + +scoped_refptr<ResourceLoadTiming> ResourceLoadTiming::DeepCopy() { + scoped_refptr<ResourceLoadTiming> timing = Create(); + timing->request_time_ = request_time_; + timing->proxy_start_ = proxy_start_; + timing->proxy_end_ = proxy_end_; + timing->dns_start_ = dns_start_; + timing->dns_end_ = dns_end_; + timing->connect_start_ = connect_start_; + timing->connect_end_ = connect_end_; + timing->worker_start_ = worker_start_; + timing->worker_ready_ = worker_ready_; + timing->send_start_ = send_start_; + timing->send_end_ = send_end_; + timing->receive_headers_end_ = receive_headers_end_; + timing->ssl_start_ = ssl_start_; + timing->ssl_end_ = ssl_end_; + timing->push_start_ = push_start_; + timing->push_end_ = push_end_; + return timing; +} + +bool ResourceLoadTiming::operator==(const ResourceLoadTiming& other) const { + return request_time_ == other.request_time_ && + proxy_start_ == other.proxy_start_ && proxy_end_ == other.proxy_end_ && + dns_start_ == other.dns_start_ && dns_end_ == other.dns_end_ && + connect_start_ == other.connect_start_ && + connect_end_ == other.connect_end_ && + worker_start_ == other.worker_start_ && + worker_ready_ == other.worker_ready_ && + send_start_ == other.send_start_ && send_end_ == other.send_end_ && + receive_headers_end_ == other.receive_headers_end_ && + ssl_start_ == other.ssl_start_ && ssl_end_ == other.ssl_end_ && + push_start_ == other.push_start_ && push_end_ == other.push_end_; +} + +bool ResourceLoadTiming::operator!=(const ResourceLoadTiming& other) const { + return !(*this == other); +} + +void ResourceLoadTiming::SetDnsStart(TimeTicks dns_start) { + dns_start_ = dns_start; +} + +void ResourceLoadTiming::SetRequestTime(TimeTicks request_time) { + request_time_ = request_time; +} + +void ResourceLoadTiming::SetProxyStart(TimeTicks proxy_start) { + proxy_start_ = proxy_start; +} + +void ResourceLoadTiming::SetProxyEnd(TimeTicks proxy_end) { + proxy_end_ = proxy_end; +} + +void ResourceLoadTiming::SetDnsEnd(TimeTicks dns_end) { + dns_end_ = dns_end; +} + +void ResourceLoadTiming::SetConnectStart(TimeTicks connect_start) { + connect_start_ = connect_start; +} + +void ResourceLoadTiming::SetConnectEnd(TimeTicks connect_end) { + connect_end_ = connect_end; +} + +void ResourceLoadTiming::SetWorkerStart(TimeTicks worker_start) { + worker_start_ = worker_start; +} + +void ResourceLoadTiming::SetWorkerReady(TimeTicks worker_ready) { + worker_ready_ = worker_ready; +} + +void ResourceLoadTiming::SetSendStart(TimeTicks send_start) { + TRACE_EVENT_MARK_WITH_TIMESTAMP0("blink.user_timing", "requestStart", + send_start); + send_start_ = send_start; +} + +void ResourceLoadTiming::SetSendEnd(TimeTicks send_end) { + send_end_ = send_end; +} + +void ResourceLoadTiming::SetReceiveHeadersEnd(TimeTicks receive_headers_end) { + receive_headers_end_ = receive_headers_end; +} + +void ResourceLoadTiming::SetSslStart(TimeTicks ssl_start) { + ssl_start_ = ssl_start; +} + +void ResourceLoadTiming::SetSslEnd(TimeTicks ssl_end) { + ssl_end_ = ssl_end; +} + +void ResourceLoadTiming::SetPushStart(TimeTicks push_start) { + push_start_ = push_start; +} + +void ResourceLoadTiming::SetPushEnd(TimeTicks push_end) { + push_end_ = push_end; +} + +double ResourceLoadTiming::CalculateMillisecondDelta(TimeTicks time) const { + return time.is_null() ? -1 : (time - request_time_).InMillisecondsF(); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/platform/loader/fetch/resource_load_timing.h b/chromium/third_party/blink/renderer/platform/loader/fetch/resource_load_timing.h new file mode 100644 index 00000000000..b5016d9beb8 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/loader/fetch/resource_load_timing.h @@ -0,0 +1,115 @@ +/* + * 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 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_PLATFORM_LOADER_FETCH_RESOURCE_LOAD_TIMING_H_ +#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_LOADER_FETCH_RESOURCE_LOAD_TIMING_H_ + +#include "base/memory/scoped_refptr.h" +#include "third_party/blink/renderer/platform/platform_export.h" +#include "third_party/blink/renderer/platform/wtf/ref_counted.h" +#include "third_party/blink/renderer/platform/wtf/time.h" + +namespace blink { + +class PLATFORM_EXPORT ResourceLoadTiming + : public RefCounted<ResourceLoadTiming> { + public: + static scoped_refptr<ResourceLoadTiming> Create(); + + scoped_refptr<ResourceLoadTiming> DeepCopy(); + bool operator==(const ResourceLoadTiming&) const; + bool operator!=(const ResourceLoadTiming&) const; + + void SetDnsStart(TimeTicks); + void SetRequestTime(TimeTicks); + void SetProxyStart(TimeTicks); + void SetProxyEnd(TimeTicks); + void SetDnsEnd(TimeTicks); + void SetConnectStart(TimeTicks); + void SetConnectEnd(TimeTicks); + void SetWorkerStart(TimeTicks); + void SetWorkerReady(TimeTicks); + void SetSendStart(TimeTicks); + void SetSendEnd(TimeTicks); + void SetReceiveHeadersEnd(TimeTicks); + void SetSslStart(TimeTicks); + void SetSslEnd(TimeTicks); + void SetPushStart(TimeTicks); + void SetPushEnd(TimeTicks); + + TimeTicks DnsStart() const { return dns_start_; } + TimeTicks RequestTime() const { return request_time_; } + TimeTicks ProxyStart() const { return proxy_start_; } + TimeTicks ProxyEnd() const { return proxy_end_; } + TimeTicks DnsEnd() const { return dns_end_; } + TimeTicks ConnectStart() const { return connect_start_; } + TimeTicks ConnectEnd() const { return connect_end_; } + TimeTicks WorkerStart() const { return worker_start_; } + TimeTicks WorkerReady() const { return worker_ready_; } + TimeTicks SendStart() const { return send_start_; } + TimeTicks SendEnd() const { return send_end_; } + TimeTicks ReceiveHeadersEnd() const { return receive_headers_end_; } + TimeTicks SslStart() const { return ssl_start_; } + TimeTicks SslEnd() const { return ssl_end_; } + TimeTicks PushStart() const { return push_start_; } + TimeTicks PushEnd() const { return push_end_; } + + double CalculateMillisecondDelta(TimeTicks) const; + + private: + ResourceLoadTiming(); + + // We want to present a unified timeline to Javascript. Using walltime is + // problematic, because the clock may skew while resources load. To prevent + // that skew, we record a single reference walltime when root document + // navigation begins. All other times are recorded using + // monotonicallyIncreasingTime(). When a time needs to be presented to + // Javascript, we build a pseudo-walltime using the following equation + // (m_requestTime as example): + // pseudo time = document wall reference + + // (m_requestTime - document monotonic reference). + + // All values from monotonicallyIncreasingTime(), in WTF::TimeTicks. + TimeTicks request_time_; + TimeTicks proxy_start_; + TimeTicks proxy_end_; + TimeTicks dns_start_; + TimeTicks dns_end_; + TimeTicks connect_start_; + TimeTicks connect_end_; + TimeTicks worker_start_; + TimeTicks worker_ready_; + TimeTicks send_start_; + TimeTicks send_end_; + TimeTicks receive_headers_end_; + TimeTicks ssl_start_; + TimeTicks ssl_end_; + TimeTicks push_start_; + TimeTicks push_end_; +}; + +} // namespace blink + +#endif diff --git a/chromium/third_party/blink/renderer/platform/loader/fetch/resource_loader.cc b/chromium/third_party/blink/renderer/platform/loader/fetch/resource_loader.cc new file mode 100644 index 00000000000..7800884770f --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/loader/fetch/resource_loader.cc @@ -0,0 +1,873 @@ +/* + * Copyright (C) 2006, 2007, 2010, 2011 Apple Inc. All rights reserved. + * (C) 2007 Graham Dennis (graham.dennis@gmail.com) + * + * 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/platform/loader/fetch/resource_loader.h" + +#include "services/network/public/mojom/fetch_api.mojom-blink.h" +#include "third_party/blink/public/mojom/blob/blob_registry.mojom-blink.h" +#include "third_party/blink/public/platform/platform.h" +#include "third_party/blink/public/platform/web_cors.h" +#include "third_party/blink/public/platform/web_data.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/renderer/platform/exported/wrapped_resource_request.h" +#include "third_party/blink/renderer/platform/exported/wrapped_resource_response.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_context.h" +#include "third_party/blink/renderer/platform/loader/fetch/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/network/network_instrumentation.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/weborigin/security_violation_reporting_policy.h" +#include "third_party/blink/renderer/platform/wtf/assertions.h" +#include "third_party/blink/renderer/platform/wtf/text/string_builder.h" +#include "third_party/blink/renderer/platform/wtf/time.h" + +namespace blink { + +ResourceLoader* ResourceLoader::Create(ResourceFetcher* fetcher, + ResourceLoadScheduler* scheduler, + Resource* resource, + uint32_t inflight_keepalive_bytes) { + return new ResourceLoader(fetcher, scheduler, resource, + inflight_keepalive_bytes); +} + +ResourceLoader::ResourceLoader(ResourceFetcher* fetcher, + ResourceLoadScheduler* scheduler, + Resource* resource, + uint32_t inflight_keepalive_bytes) + : scheduler_client_id_(ResourceLoadScheduler::kInvalidClientId), + fetcher_(fetcher), + scheduler_(scheduler), + resource_(resource), + inflight_keepalive_bytes_(inflight_keepalive_bytes), + is_cache_aware_loading_activated_(false), + progress_binding_(this), + cancel_timer_(Context().GetLoadingTaskRunner(), + this, + &ResourceLoader::CancelTimerFired) { + DCHECK(resource_); + DCHECK(fetcher_); + + resource_->SetLoader(this); +} + +ResourceLoader::~ResourceLoader() = default; + +void ResourceLoader::Trace(blink::Visitor* visitor) { + visitor->Trace(fetcher_); + visitor->Trace(scheduler_); + visitor->Trace(resource_); + ResourceLoadSchedulerClient::Trace(visitor); +} + +void ResourceLoader::Start() { + const ResourceRequest& request = resource_->GetResourceRequest(); + ActivateCacheAwareLoadingIfNeeded(request); + loader_ = Context().CreateURLLoader(request, Context().GetLoadingTaskRunner(), + resource_->Options()); + DCHECK_EQ(ResourceLoadScheduler::kInvalidClientId, scheduler_client_id_); + auto throttle_option = ResourceLoadScheduler::ThrottleOption::kCanBeThrottled; + + // Synchronous requests should not work with a throttling. Also, tentatively + // disables throttling for fetch requests that could keep on holding an active + // connection until data is read by JavaScript. + if (resource_->Options().synchronous_policy == kRequestSynchronously || + request.GetRequestContext() == WebURLRequest::kRequestContextFetch) { + throttle_option = ResourceLoadScheduler::ThrottleOption::kCanNotBeThrottled; + } + + scheduler_->Request(this, throttle_option, request.Priority(), + request.IntraPriorityValue(), &scheduler_client_id_); +} + +void ResourceLoader::Run() { + StartWith(resource_->GetResourceRequest()); +} + +void ResourceLoader::StartWith(const ResourceRequest& request) { + DCHECK_NE(ResourceLoadScheduler::kInvalidClientId, scheduler_client_id_); + DCHECK(loader_); + + if (resource_->Options().synchronous_policy == kRequestSynchronously && + Context().DefersLoading()) { + Cancel(); + return; + } + + is_downloading_to_blob_ = request.DownloadToBlob(); + + loader_->SetDefersLoading(Context().DefersLoading()); + + if (is_cache_aware_loading_activated_) { + // Override cache policy for cache-aware loading. If this request fails, a + // reload with original request will be triggered in DidFail(). + ResourceRequest cache_aware_request(request); + cache_aware_request.SetCacheMode( + mojom::FetchCacheMode::kUnspecifiedOnlyIfCachedStrict); + loader_->LoadAsynchronously(WrappedResourceRequest(cache_aware_request), + this); + return; + } + + if (resource_->Options().synchronous_policy == kRequestSynchronously) + RequestSynchronously(request); + else + loader_->LoadAsynchronously(WrappedResourceRequest(request), this); +} + +void ResourceLoader::Release( + ResourceLoadScheduler::ReleaseOption option, + const ResourceLoadScheduler::TrafficReportHints& hints) { + DCHECK_NE(ResourceLoadScheduler::kInvalidClientId, scheduler_client_id_); + bool released = scheduler_->Release(scheduler_client_id_, option, hints); + DCHECK(released); + scheduler_client_id_ = ResourceLoadScheduler::kInvalidClientId; +} + +void ResourceLoader::Restart(const ResourceRequest& request) { + CHECK_EQ(resource_->Options().synchronous_policy, kRequestAsynchronously); + + loader_ = Context().CreateURLLoader(request, Context().GetLoadingTaskRunner(), + resource_->Options()); + StartWith(request); +} + +void ResourceLoader::SetDefersLoading(bool defers) { + DCHECK(loader_); + loader_->SetDefersLoading(defers); + if (defers) { + resource_->VirtualTimePauser().UnpauseVirtualTime(); + } else { + resource_->VirtualTimePauser().PauseVirtualTime(); + } +} + +void ResourceLoader::DidChangePriority(ResourceLoadPriority load_priority, + int intra_priority_value) { + if (scheduler_->IsRunning(scheduler_client_id_)) { + DCHECK(loader_); + DCHECK_NE(ResourceLoadScheduler::kInvalidClientId, scheduler_client_id_); + loader_->DidChangePriority( + static_cast<WebURLRequest::Priority>(load_priority), + intra_priority_value); + } else { + scheduler_->SetPriority(scheduler_client_id_, load_priority, + intra_priority_value); + } +} + +void ResourceLoader::ScheduleCancel() { + if (!cancel_timer_.IsActive()) + cancel_timer_.StartOneShot(TimeDelta(), FROM_HERE); +} + +void ResourceLoader::CancelTimerFired(TimerBase*) { + if (loader_ && !resource_->HasClientsOrObservers()) + Cancel(); +} + +void ResourceLoader::Cancel() { + HandleError( + ResourceError::CancelledError(resource_->LastResourceRequest().Url())); +} + +void ResourceLoader::CancelForRedirectAccessCheckError( + const KURL& new_url, + ResourceRequestBlockedReason blocked_reason) { + resource_->WillNotFollowRedirect(); + + if (loader_) { + HandleError( + ResourceError::CancelledDueToAccessCheckError(new_url, blocked_reason)); + } +} + +static bool IsManualRedirectFetchRequest(const ResourceRequest& request) { + return request.GetFetchRedirectMode() == + network::mojom::FetchRedirectMode::kManual && + request.GetRequestContext() == WebURLRequest::kRequestContextFetch; +} + +bool ResourceLoader::WillFollowRedirect( + const WebURL& new_url, + const WebURL& new_site_for_cookies, + const WebString& new_referrer, + WebReferrerPolicy new_referrer_policy, + const WebString& new_method, + const WebURLResponse& passed_redirect_response, + bool& report_raw_headers) { + DCHECK(!passed_redirect_response.IsNull()); + + if (is_cache_aware_loading_activated_) { + // Fail as cache miss if cached response is a redirect. + HandleError( + ResourceError::CacheMissError(resource_->LastResourceRequest().Url())); + return false; + } + + std::unique_ptr<ResourceRequest> new_request = + resource_->LastResourceRequest().CreateRedirectRequest( + new_url, new_method, new_site_for_cookies, new_referrer, + static_cast<ReferrerPolicy>(new_referrer_policy), + !passed_redirect_response.WasFetchedViaServiceWorker()); + + Resource::Type resource_type = resource_->GetType(); + + const ResourceRequest& initial_request = resource_->GetResourceRequest(); + // The following parameters never change during the lifetime of a request. + WebURLRequest::RequestContext request_context = + initial_request.GetRequestContext(); + network::mojom::RequestContextFrameType frame_type = + initial_request.GetFrameType(); + network::mojom::FetchRequestMode fetch_request_mode = + initial_request.GetFetchRequestMode(); + network::mojom::FetchCredentialsMode fetch_credentials_mode = + initial_request.GetFetchCredentialsMode(); + + const ResourceLoaderOptions& options = resource_->Options(); + + const ResourceResponse& redirect_response( + passed_redirect_response.ToResourceResponse()); + + if (!IsManualRedirectFetchRequest(initial_request)) { + bool unused_preload = resource_->IsUnusedPreload(); + + // Don't send security violation reports for unused preloads. + SecurityViolationReportingPolicy reporting_policy = + unused_preload ? SecurityViolationReportingPolicy::kSuppressReporting + : SecurityViolationReportingPolicy::kReport; + + // CanRequest() checks only enforced CSP, so check report-only here to + // ensure that violations are sent. + Context().CheckCSPForRequest( + request_context, new_url, options, reporting_policy, + ResourceRequest::RedirectStatus::kFollowedRedirect); + + ResourceRequestBlockedReason blocked_reason = Context().CanRequest( + resource_type, *new_request, new_url, options, reporting_policy, + FetchParameters::kUseDefaultOriginRestrictionForType, + ResourceRequest::RedirectStatus::kFollowedRedirect); + + if (Context().IsAdResource(new_url, resource_type, + new_request->GetRequestContext())) { + new_request->SetIsAdResource(); + } + + if (blocked_reason != ResourceRequestBlockedReason::kNone) { + CancelForRedirectAccessCheckError(new_url, blocked_reason); + return false; + } + + if (options.cors_handling_by_resource_fetcher == + kEnableCORSHandlingByResourceFetcher && + fetch_request_mode == network::mojom::FetchRequestMode::kCORS) { + scoped_refptr<const SecurityOrigin> source_origin = GetSourceOrigin(); + WebSecurityOrigin source_web_origin(source_origin.get()); + WrappedResourceRequest new_request_wrapper(*new_request); + WTF::Optional<network::mojom::CORSError> cors_error = + WebCORS::HandleRedirect( + source_web_origin, new_request_wrapper, redirect_response.Url(), + redirect_response.HttpStatusCode(), + redirect_response.HttpHeaderFields(), fetch_credentials_mode, + resource_->MutableOptions()); + if (cors_error) { + resource_->SetCORSStatus(CORSStatus::kFailed); + + if (!unused_preload) { + Context().AddErrorConsoleMessage( + CORS::GetErrorString(CORS::ErrorParameter::Create( + *cors_error, redirect_response.Url(), new_url, + redirect_response.HttpStatusCode(), + redirect_response.HttpHeaderFields(), *source_origin.get(), + resource_->LastResourceRequest().GetRequestContext())), + FetchContext::kJSSource); + } + + CancelForRedirectAccessCheckError(new_url, + ResourceRequestBlockedReason::kOther); + return false; + } + + source_origin = source_web_origin; + } + if (resource_type == Resource::kImage && + fetcher_->ShouldDeferImageLoad(new_url)) { + CancelForRedirectAccessCheckError(new_url, + ResourceRequestBlockedReason::kOther); + return false; + } + } + + bool cross_origin = + !SecurityOrigin::AreSameSchemeHostPort(redirect_response.Url(), new_url); + fetcher_->RecordResourceTimingOnRedirect(resource_.Get(), redirect_response, + cross_origin); + + if (options.cors_handling_by_resource_fetcher == + kEnableCORSHandlingByResourceFetcher && + fetch_request_mode == network::mojom::FetchRequestMode::kCORS) { + bool allow_stored_credentials = false; + switch (fetch_credentials_mode) { + case network::mojom::FetchCredentialsMode::kOmit: + break; + case network::mojom::FetchCredentialsMode::kSameOrigin: + allow_stored_credentials = !options.cors_flag; + break; + case network::mojom::FetchCredentialsMode::kInclude: + allow_stored_credentials = true; + break; + } + new_request->SetAllowStoredCredentials(allow_stored_credentials); + } + + // The following two calls may rewrite the new_request.Url() to + // something else not for rejecting redirect but for other reasons. + // E.g. WebFrameTestClient::WillSendRequest() and + // RenderFrameImpl::WillSendRequest(). We should reflect the + // rewriting but currently we cannot. So, compare new_request.Url() and + // new_url after calling them, and return false to make the redirect fail on + // mismatch. + + Context().PrepareRequest(*new_request, + FetchContext::RedirectType::kForRedirect); + if (Context().GetFrameScheduler()) { + WebScopedVirtualTimePauser virtual_time_pauser = + Context().GetFrameScheduler()->CreateWebScopedVirtualTimePauser( + resource_->Url().GetString(), + WebScopedVirtualTimePauser::VirtualTaskDuration::kNonInstant); + virtual_time_pauser.PauseVirtualTime(); + resource_->VirtualTimePauser() = std::move(virtual_time_pauser); + } + Context().DispatchWillSendRequest(resource_->Identifier(), *new_request, + redirect_response, resource_->GetType(), + options.initiator_info); + + // First-party cookie logic moved from DocumentLoader in Blink to + // net::URLRequest in the browser. Assert that Blink didn't try to change it + // to something else. + DCHECK(KURL(new_site_for_cookies) == new_request->SiteForCookies()); + + // The following parameters never change during the lifetime of a request. + DCHECK_EQ(new_request->GetRequestContext(), request_context); + DCHECK_EQ(new_request->GetFrameType(), frame_type); + DCHECK_EQ(new_request->GetFetchRequestMode(), fetch_request_mode); + DCHECK_EQ(new_request->GetFetchCredentialsMode(), fetch_credentials_mode); + + if (new_request->Url() != KURL(new_url)) { + CancelForRedirectAccessCheckError(new_request->Url(), + ResourceRequestBlockedReason::kOther); + return false; + } + + if (!resource_->WillFollowRedirect(*new_request, redirect_response)) { + CancelForRedirectAccessCheckError(new_request->Url(), + ResourceRequestBlockedReason::kOther); + return false; + } + + report_raw_headers = new_request->ReportRawHeaders(); + + return true; +} + +void ResourceLoader::DidReceiveCachedMetadata(const char* data, int length) { + resource_->SetSerializedCachedMetadata(data, length); +} + +void ResourceLoader::DidSendData(unsigned long long bytes_sent, + unsigned long long total_bytes_to_be_sent) { + resource_->DidSendData(bytes_sent, total_bytes_to_be_sent); +} + +FetchContext& ResourceLoader::Context() const { + return fetcher_->Context(); +} + +scoped_refptr<const SecurityOrigin> ResourceLoader::GetSourceOrigin() const { + scoped_refptr<const SecurityOrigin> origin = + resource_->Options().security_origin; + if (origin) + return origin; + + return Context().GetSecurityOrigin(); +} + +CORSStatus ResourceLoader::DetermineCORSStatus(const ResourceResponse& response, + StringBuilder& error_msg) const { + // Service workers handle CORS separately. + if (response.WasFetchedViaServiceWorker()) { + switch (response.ResponseTypeViaServiceWorker()) { + case network::mojom::FetchResponseType::kBasic: + case network::mojom::FetchResponseType::kCORS: + case network::mojom::FetchResponseType::kDefault: + case network::mojom::FetchResponseType::kError: + return CORSStatus::kServiceWorkerSuccessful; + case network::mojom::FetchResponseType::kOpaque: + case network::mojom::FetchResponseType::kOpaqueRedirect: + return CORSStatus::kServiceWorkerOpaque; + } + NOTREACHED(); + } + + if (resource_->GetType() == Resource::Type::kMainResource) + return CORSStatus::kNotApplicable; + + scoped_refptr<const SecurityOrigin> source_origin = GetSourceOrigin(); + DCHECK(source_origin); + + if (source_origin->CanRequest(response.Url())) + return CORSStatus::kSameOrigin; + + // RequestContext, FetchRequestMode and FetchCredentialsMode never change + // during the lifetime of a request. + const ResourceRequest& initial_request = resource_->GetResourceRequest(); + + if (resource_->Options().cors_handling_by_resource_fetcher != + kEnableCORSHandlingByResourceFetcher || + initial_request.GetFetchRequestMode() != + network::mojom::FetchRequestMode::kCORS) { + return CORSStatus::kNotApplicable; + } + + // Use the original response instead of the 304 response for a successful + // revalidation. + const ResourceResponse& response_for_access_control = + (resource_->IsCacheValidator() && response.HttpStatusCode() == 304) + ? resource_->GetResponse() + : response; + + base::Optional<network::mojom::CORSError> cors_error = CORS::CheckAccess( + response_for_access_control.Url(), + response_for_access_control.HttpStatusCode(), + response_for_access_control.HttpHeaderFields(), + initial_request.GetFetchCredentialsMode(), *source_origin); + + if (!cors_error) + return CORSStatus::kSuccessful; + + String resource_type = Resource::ResourceTypeToString( + resource_->GetType(), resource_->Options().initiator_info.name); + error_msg.Append("Access to "); + error_msg.Append(resource_type); + error_msg.Append(" at '"); + error_msg.Append(response.Url().GetString()); + error_msg.Append("' from origin '"); + error_msg.Append(source_origin->ToString()); + error_msg.Append("' has been blocked by CORS policy: "); + error_msg.Append(CORS::GetErrorString(CORS::ErrorParameter::Create( + *cors_error, initial_request.Url(), KURL(), + response_for_access_control.HttpStatusCode(), + response_for_access_control.HttpHeaderFields(), *source_origin, + initial_request.GetRequestContext()))); + + return CORSStatus::kFailed; +} + +void ResourceLoader::DidReceiveResponse( + const WebURLResponse& web_url_response, + std::unique_ptr<WebDataConsumerHandle> handle) { + DCHECK(!web_url_response.IsNull()); + + if (Context().IsDetached()) { + // If the fetch context is already detached, we don't need further signals, + // so let's cancel the request. + HandleError(ResourceError::CancelledError(web_url_response.Url())); + return; + } + + Resource::Type resource_type = resource_->GetType(); + + const ResourceRequest& initial_request = resource_->GetResourceRequest(); + // The following parameters never change during the lifetime of a request. + WebURLRequest::RequestContext request_context = + initial_request.GetRequestContext(); + network::mojom::FetchRequestMode fetch_request_mode = + initial_request.GetFetchRequestMode(); + + const ResourceLoaderOptions& options = resource_->Options(); + + const ResourceResponse& response = web_url_response.ToResourceResponse(); + + // Later, CORS results should already be in the response we get from the + // browser at this point. + StringBuilder cors_error_msg; + resource_->SetCORSStatus(DetermineCORSStatus(response, cors_error_msg)); + + // Perform 'nosniff' checks against the original response instead of the 304 + // response for a successful revalidation. + const ResourceResponse& nosniffed_response = + (resource_->IsCacheValidator() && response.HttpStatusCode() == 304) + ? resource_->GetResponse() + : response; + ResourceRequestBlockedReason blocked_reason = + Context().CheckResponseNosniff(request_context, nosniffed_response); + if (blocked_reason != ResourceRequestBlockedReason::kNone) { + HandleError(ResourceError::CancelledDueToAccessCheckError(response.Url(), + blocked_reason)); + return; + } + + if (response.WasFetchedViaServiceWorker()) { + if (options.cors_handling_by_resource_fetcher == + kEnableCORSHandlingByResourceFetcher && + fetch_request_mode == network::mojom::FetchRequestMode::kCORS && + response.WasFallbackRequiredByServiceWorker()) { + ResourceRequest last_request = resource_->LastResourceRequest(); + DCHECK(!last_request.GetSkipServiceWorker()); + // This code handles the case when a controlling service worker doesn't + // handle a cross origin request. + if (!Context().ShouldLoadNewResource(resource_type)) { + // Cancel the request if we should not trigger a reload now. + HandleError(ResourceError::CancelledError(response.Url())); + return; + } + last_request.SetSkipServiceWorker(true); + Restart(last_request); + return; + } + + // If the response is fetched via ServiceWorker, the original URL of the + // response could be different from the URL of the request. We check the URL + // not to load the resources which are forbidden by the page CSP. + // https://w3c.github.io/webappsec-csp/#should-block-response + const KURL& original_url = response.OriginalURLViaServiceWorker(); + if (!original_url.IsEmpty()) { + // CanRequest() below only checks enforced policies: check report-only + // here to ensure violations are sent. + Context().CheckCSPForRequest( + request_context, original_url, options, + SecurityViolationReportingPolicy::kReport, + ResourceRequest::RedirectStatus::kFollowedRedirect); + + ResourceRequestBlockedReason blocked_reason = Context().CanRequest( + resource_type, initial_request, original_url, options, + SecurityViolationReportingPolicy::kReport, + FetchParameters::kUseDefaultOriginRestrictionForType, + ResourceRequest::RedirectStatus::kFollowedRedirect); + if (blocked_reason != ResourceRequestBlockedReason::kNone) { + HandleError(ResourceError::CancelledDueToAccessCheckError( + original_url, blocked_reason)); + return; + } + } + } else if (options.cors_handling_by_resource_fetcher == + kEnableCORSHandlingByResourceFetcher && + fetch_request_mode == network::mojom::FetchRequestMode::kCORS) { + if (!resource_->IsSameOriginOrCORSSuccessful()) { + if (!resource_->IsUnusedPreload()) + Context().AddErrorConsoleMessage(cors_error_msg.ToString(), + FetchContext::kJSSource); + + // Redirects can change the response URL different from one of request. + HandleError(ResourceError::CancelledDueToAccessCheckError( + response.Url(), ResourceRequestBlockedReason::kOther)); + return; + } + } + + // FrameType never changes during the lifetime of a request. + Context().DispatchDidReceiveResponse( + resource_->Identifier(), response, initial_request.GetFrameType(), + request_context, resource_, + FetchContext::ResourceResponseType::kNotFromMemoryCache); + + resource_->ResponseReceived(response, std::move(handle)); + if (!resource_->Loader()) + return; + + if (response.HttpStatusCode() >= 400 && + !resource_->ShouldIgnoreHTTPStatusCodeErrors()) + HandleError(ResourceError::CancelledError(response.Url())); +} + +void ResourceLoader::DidReceiveResponse(const WebURLResponse& response) { + DidReceiveResponse(response, nullptr); +} + +void ResourceLoader::DidStartLoadingResponseBody( + mojo::ScopedDataPipeConsumerHandle body) { + DCHECK(is_downloading_to_blob_); + DCHECK(!blob_response_started_); + blob_response_started_ = true; + + const ResourceResponse& response = resource_->GetResponse(); + AtomicString mime_type = response.MimeType(); + + mojom::blink::ProgressClientAssociatedPtrInfo progress_client_ptr; + progress_binding_.Bind(MakeRequest(&progress_client_ptr)); + + // Callback is bound to a WeakPersistent, as ResourceLoader is kept alive by + // ResourceFetcher as long as we still care about the result of the load. + mojom::blink::BlobRegistry* blob_registry = BlobDataHandle::GetBlobRegistry(); + blob_registry->RegisterFromStream( + mime_type.IsNull() ? g_empty_string : mime_type.LowerASCII(), "", + std::max(0ll, response.ExpectedContentLength()), std::move(body), + std::move(progress_client_ptr), + WTF::Bind(&ResourceLoader::FinishedCreatingBlob, + WrapWeakPersistent(this))); +} + +void ResourceLoader::DidDownloadData(int length, int encoded_data_length) { + Context().DispatchDidDownloadData(resource_->Identifier(), length, + encoded_data_length); + resource_->DidDownloadData(length); +} + +void ResourceLoader::DidReceiveData(const char* data, int length) { + CHECK_GE(length, 0); + + Context().DispatchDidReceiveData(resource_->Identifier(), data, length); + resource_->AppendData(data, length); +} + +void ResourceLoader::DidReceiveTransferSizeUpdate(int transfer_size_diff) { + DCHECK_GT(transfer_size_diff, 0); + Context().DispatchDidReceiveEncodedData(resource_->Identifier(), + transfer_size_diff); +} + +void ResourceLoader::DidFinishLoadingFirstPartInMultipart() { + network_instrumentation::EndResourceLoad( + resource_->Identifier(), + network_instrumentation::RequestOutcome::kSuccess); + + fetcher_->HandleLoaderFinish(resource_.Get(), 0, + ResourceFetcher::kDidFinishFirstPartInMultipart, + 0, false); +} + +void ResourceLoader::DidFinishLoading(double finish_time, + int64_t encoded_data_length, + int64_t encoded_body_length, + int64_t decoded_body_length, + bool blocked_cross_site_document) { + resource_->SetEncodedDataLength(encoded_data_length); + resource_->SetEncodedBodyLength(encoded_body_length); + resource_->SetDecodedBodyLength(decoded_body_length); + + if (is_downloading_to_blob_ && !blob_finished_ && blob_response_started_) { + load_did_finish_before_blob_ = + DeferedFinishLoadingInfo{finish_time, blocked_cross_site_document}; + return; + } + + Release(ResourceLoadScheduler::ReleaseOption::kReleaseAndSchedule, + ResourceLoadScheduler::TrafficReportHints(encoded_data_length, + decoded_body_length)); + loader_.reset(); + + network_instrumentation::EndResourceLoad( + resource_->Identifier(), + network_instrumentation::RequestOutcome::kSuccess); + + fetcher_->HandleLoaderFinish( + resource_.Get(), finish_time, ResourceFetcher::kDidFinishLoading, + inflight_keepalive_bytes_, blocked_cross_site_document); +} + +void ResourceLoader::DidFail(const WebURLError& error, + int64_t encoded_data_length, + int64_t encoded_body_length, + int64_t decoded_body_length) { + resource_->SetEncodedDataLength(encoded_data_length); + resource_->SetEncodedBodyLength(encoded_body_length); + resource_->SetDecodedBodyLength(decoded_body_length); + HandleError(error); +} + +void ResourceLoader::HandleError(const ResourceError& error) { + if (is_cache_aware_loading_activated_ && error.IsCacheMiss() && + Context().ShouldLoadNewResource(resource_->GetType())) { + resource_->WillReloadAfterDiskCacheMiss(); + is_cache_aware_loading_activated_ = false; + Restart(resource_->GetResourceRequest()); + return; + } + + Release(ResourceLoadScheduler::ReleaseOption::kReleaseAndSchedule, + ResourceLoadScheduler::TrafficReportHints::InvalidInstance()); + loader_.reset(); + + network_instrumentation::EndResourceLoad( + resource_->Identifier(), network_instrumentation::RequestOutcome::kFail); + + fetcher_->HandleLoaderError(resource_.Get(), error, + inflight_keepalive_bytes_); +} + +void ResourceLoader::RequestSynchronously(const ResourceRequest& request) { + DCHECK(loader_); + DCHECK_EQ(request.Priority(), ResourceLoadPriority::kHighest); + + WrappedResourceRequest request_in(request); + WebURLResponse response_out; + WTF::Optional<WebURLError> error_out; + WebData data_out; + int64_t encoded_data_length = WebURLLoaderClient::kUnknownEncodedDataLength; + int64_t encoded_body_length = 0; + base::Optional<int64_t> downloaded_file_length; + WebBlobInfo downloaded_blob; + loader_->LoadSynchronously(request_in, response_out, error_out, data_out, + encoded_data_length, encoded_body_length, + downloaded_file_length, downloaded_blob); + + // A message dispatched while synchronously fetching the resource + // can bring about the cancellation of this load. + if (!loader_) + return; + int64_t decoded_body_length = data_out.size(); + if (error_out) { + DidFail(*error_out, encoded_data_length, encoded_body_length, + decoded_body_length); + return; + } + DidReceiveResponse(response_out); + if (!loader_) + return; + DCHECK_GE(response_out.ToResourceResponse().EncodedBodyLength(), 0); + + // Follow the async case convention of not calling DidReceiveData or + // appending data to m_resource if the response body is empty. Copying the + // empty buffer is a noop in most cases, but is destructive in the case of + // a 304, where it will overwrite the cached data we should be reusing. + if (data_out.size()) { + data_out.ForEachSegment([this](const char* segment, size_t segment_size, + size_t segment_offset) { + Context().DispatchDidReceiveData(resource_->Identifier(), segment, + segment_size); + return true; + }); + resource_->SetResourceBuffer(data_out); + } + + if (downloaded_file_length) { + DCHECK(request.DownloadToFile()); + DidDownloadData(*downloaded_file_length, encoded_body_length); + } + if (request.DownloadToBlob()) { + auto blob = downloaded_blob.GetBlobHandle(); + if (blob) { + Context().DispatchDidReceiveData(resource_->Identifier(), nullptr, + blob->size()); + resource_->DidDownloadData(blob->size()); + } + Context().DispatchDidDownloadToBlob(resource_->Identifier(), blob.get()); + resource_->DidDownloadToBlob(blob); + } + DidFinishLoading(CurrentTimeTicksInSeconds(), encoded_data_length, + encoded_body_length, decoded_body_length, false); +} + +void ResourceLoader::Dispose() { + loader_ = nullptr; + + // Release() should be called to release |scheduler_client_id_| beforehand in + // DidFinishLoading() or DidFail(), but when a timer to call Cancel() is + // ignored due to GC, this case happens. We just release here because we can + // not schedule another request safely. See crbug.com/675947. + if (scheduler_client_id_ != ResourceLoadScheduler::kInvalidClientId) { + Release(ResourceLoadScheduler::ReleaseOption::kReleaseOnly, + ResourceLoadScheduler::TrafficReportHints::InvalidInstance()); + } +} + +void ResourceLoader::ActivateCacheAwareLoadingIfNeeded( + const ResourceRequest& request) { + DCHECK(!is_cache_aware_loading_activated_); + + if (resource_->Options().cache_aware_loading_enabled != + kIsCacheAwareLoadingEnabled) + return; + + // Synchronous requests are not supported. + if (resource_->Options().synchronous_policy == kRequestSynchronously) + return; + + // Don't activate on Resource revalidation. + if (resource_->IsCacheValidator()) + return; + + // Don't activate if cache policy is explicitly set. + if (request.GetCacheMode() != mojom::FetchCacheMode::kDefault) + return; + + // Don't activate if the page is controlled by service worker. + if (fetcher_->IsControlledByServiceWorker()) + return; + + is_cache_aware_loading_activated_ = true; +} + +bool ResourceLoader::ShouldBeKeptAliveWhenDetached() const { + return resource_->GetResourceRequest().GetKeepalive() && + resource_->GetResponse().IsNull(); +} + +scoped_refptr<base::SingleThreadTaskRunner> +ResourceLoader::GetLoadingTaskRunner() { + return Context().GetLoadingTaskRunner(); +} + +void ResourceLoader::OnProgress(uint64_t delta) { + DCHECK(!blob_finished_); + + if (scheduler_client_id_ == ResourceLoadScheduler::kInvalidClientId) + return; + + Context().DispatchDidReceiveData(resource_->Identifier(), nullptr, delta); + resource_->DidDownloadData(delta); +} + +void ResourceLoader::FinishedCreatingBlob( + const scoped_refptr<BlobDataHandle>& blob) { + DCHECK(!blob_finished_); + + if (scheduler_client_id_ == ResourceLoadScheduler::kInvalidClientId) + return; + + Context().DispatchDidDownloadToBlob(resource_->Identifier(), blob.get()); + resource_->DidDownloadToBlob(blob); + + blob_finished_ = true; + if (load_did_finish_before_blob_) { + const ResourceResponse& response = resource_->GetResponse(); + DidFinishLoading(load_did_finish_before_blob_->finish_time, + response.EncodedDataLength(), response.EncodedBodyLength(), + response.DecodedBodyLength(), + load_did_finish_before_blob_->blocked_cross_site_document); + } +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/platform/loader/fetch/resource_loader.h b/chromium/third_party/blink/renderer/platform/loader/fetch/resource_loader.h new file mode 100644 index 00000000000..cc8c44ea9a6 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/loader/fetch/resource_loader.h @@ -0,0 +1,205 @@ +/* + * Copyright (C) 2005, 2006, 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. + * 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_PLATFORM_LOADER_FETCH_RESOURCE_LOADER_H_ +#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_LOADER_FETCH_RESOURCE_LOADER_H_ + +#include <memory> +#include "base/gtest_prod_util.h" +#include "mojo/public/cpp/bindings/associated_binding.h" +#include "third_party/blink/public/mojom/blob/blob_registry.mojom-blink.h" +#include "third_party/blink/public/platform/web_url_loader.h" +#include "third_party/blink/public/platform/web_url_loader_client.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_load_scheduler.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/platform_export.h" +#include "third_party/blink/renderer/platform/wtf/forward.h" + +namespace blink { + +class FetchContext; +class ResourceError; +class ResourceFetcher; + +// A ResourceLoader is created for each Resource by the ResourceFetcher when it +// needs to load the specified resource. A ResourceLoader creates a +// WebURLLoader and loads the resource using it. Any per-load logic should be +// implemented in this class basically. +class PLATFORM_EXPORT ResourceLoader final + : public GarbageCollectedFinalized<ResourceLoader>, + public ResourceLoadSchedulerClient, + protected WebURLLoaderClient, + protected mojom::blink::ProgressClient { + USING_GARBAGE_COLLECTED_MIXIN(ResourceLoader); + USING_PRE_FINALIZER(ResourceLoader, Dispose); + + public: + static ResourceLoader* Create(ResourceFetcher*, + ResourceLoadScheduler*, + Resource*, + uint32_t inflight_keepalive_bytes = 0); + ~ResourceLoader() override; + void Trace(blink::Visitor*) override; + + void Start(); + + void ScheduleCancel(); + void Cancel(); + + void SetDefersLoading(bool); + + void DidChangePriority(ResourceLoadPriority, int intra_priority_value); + + // Called before start() to activate cache-aware loading if enabled in + // |m_resource->options()| and applicable. + void ActivateCacheAwareLoadingIfNeeded(const ResourceRequest&); + + bool IsCacheAwareLoadingActivated() const { + return is_cache_aware_loading_activated_; + } + + ResourceFetcher* Fetcher() { return fetcher_; } + bool ShouldBeKeptAliveWhenDetached() const; + + // WebURLLoaderClient + // + // A succesful load will consist of: + // 0+ WillFollowRedirect() + // 0+ DidSendData() + // 1 DidReceiveResponse() + // 0-1 DidReceiveCachedMetadata() + // 0+ DidReceiveData() or DidDownloadData(), but never both + // 1 DidFinishLoading() + // A failed load is indicated by 1 DidFail(), which can occur at any time + // before DidFinishLoading(), including synchronous inside one of the other + // callbacks via ResourceLoader::cancel() + bool WillFollowRedirect(const WebURL& new_url, + const WebURL& new_site_for_cookies, + const WebString& new_referrer, + WebReferrerPolicy new_referrer_policy, + const WebString& new_method, + const WebURLResponse& passed_redirect_response, + bool& report_raw_headers) override; + void DidSendData(unsigned long long bytes_sent, + unsigned long long total_bytes_to_be_sent) override; + void DidReceiveResponse(const WebURLResponse&) override; + void DidReceiveResponse(const WebURLResponse&, + std::unique_ptr<WebDataConsumerHandle>) override; + void DidReceiveCachedMetadata(const char* data, int length) override; + void DidReceiveData(const char*, int) override; + void DidReceiveTransferSizeUpdate(int transfer_size_diff) override; + void DidStartLoadingResponseBody( + mojo::ScopedDataPipeConsumerHandle body) override; + void DidDownloadData(int, int) override; + void DidFinishLoading(double finish_time, + int64_t encoded_data_length, + int64_t encoded_body_length, + int64_t decoded_body_length, + bool blocked_cross_site_document) override; + void DidFail(const WebURLError&, + int64_t encoded_data_length, + int64_t encoded_body_length, + int64_t decoded_body_length) override; + + void HandleError(const ResourceError&); + + void DidFinishLoadingFirstPartInMultipart(); + + // ResourceLoadSchedulerClient. + void Run() override; + + scoped_refptr<base::SingleThreadTaskRunner> GetLoadingTaskRunner(); + + private: + FRIEND_TEST_ALL_PREFIXES(ResourceLoaderTest, DetermineCORSStatus); + + friend class SubresourceIntegrityTest; + + // Assumes ResourceFetcher and Resource are non-null. + ResourceLoader(ResourceFetcher*, + ResourceLoadScheduler*, + Resource*, + uint32_t inflight_keepalive_bytes); + + void StartWith(const ResourceRequest&); + + void Release(ResourceLoadScheduler::ReleaseOption, + const ResourceLoadScheduler::TrafficReportHints&); + + // This method is currently only used for service worker fallback request and + // cache-aware loading, other users should be careful not to break + // ResourceLoader state. + void Restart(const ResourceRequest&); + + FetchContext& Context() const; + scoped_refptr<const SecurityOrigin> GetSourceOrigin() const; + + CORSStatus DetermineCORSStatus(const ResourceResponse&, StringBuilder&) const; + + void CancelForRedirectAccessCheckError(const KURL&, + ResourceRequestBlockedReason); + void RequestSynchronously(const ResourceRequest&); + void Dispose(); + + void CancelTimerFired(TimerBase*); + + void OnProgress(uint64_t delta) override; + void FinishedCreatingBlob(const scoped_refptr<BlobDataHandle>&); + + std::unique_ptr<WebURLLoader> loader_; + ResourceLoadScheduler::ClientId scheduler_client_id_; + Member<ResourceFetcher> fetcher_; + Member<ResourceLoadScheduler> scheduler_; + Member<Resource> resource_; + + uint32_t inflight_keepalive_bytes_; + bool is_cache_aware_loading_activated_; + + bool is_downloading_to_blob_ = false; + mojo::AssociatedBinding<mojom::blink::ProgressClient> progress_binding_; + bool blob_finished_ = false; + bool blob_response_started_ = false; + // If DidFinishLoading is called while downloading to a blob before the blob + // is finished, we might have to defer actually handling the event. This + // struct is used to store the information needed to refire DidFinishLoading + // when the blob is finished too. + struct DeferedFinishLoadingInfo { + double finish_time; + bool blocked_cross_site_document; + }; + Optional<DeferedFinishLoadingInfo> load_did_finish_before_blob_; + + TaskRunnerTimer<ResourceLoader> cancel_timer_; +}; + +} // namespace blink + +#endif diff --git a/chromium/third_party/blink/renderer/platform/loader/fetch/resource_loader_options.h b/chromium/third_party/blink/renderer/platform/loader/fetch/resource_loader_options.h new file mode 100644 index 00000000000..0907407eedf --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/loader/fetch/resource_loader_options.h @@ -0,0 +1,209 @@ +/* + * 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_PLATFORM_LOADER_FETCH_RESOURCE_LOADER_OPTIONS_H_ +#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_LOADER_FETCH_RESOURCE_LOADER_OPTIONS_H_ + +#include "base/memory/scoped_refptr.h" +#include "services/network/public/mojom/url_loader_factory.mojom-blink.h" +#include "third_party/blink/renderer/platform/cross_thread_copier.h" +#include "third_party/blink/renderer/platform/loader/fetch/fetch_initiator_info.h" +#include "third_party/blink/renderer/platform/loader/fetch/integrity_metadata.h" +#include "third_party/blink/renderer/platform/weborigin/security_origin.h" +#include "third_party/blink/renderer/platform/wtf/allocator.h" +#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h" + +namespace blink { + +enum DataBufferingPolicy : uint8_t { kBufferData, kDoNotBufferData }; + +enum ContentSecurityPolicyDisposition : uint8_t { + kCheckContentSecurityPolicy, + kDoNotCheckContentSecurityPolicy +}; + +enum RequestInitiatorContext : uint8_t { + kDocumentContext, + kWorkerContext, +}; + +enum SynchronousPolicy : uint8_t { + kRequestSynchronously, + kRequestAsynchronously +}; + +// Used by the DocumentThreadableLoader to turn off part of the CORS handling +// logic in the ResourceFetcher to use its own CORS handling logic. +enum CORSHandlingByResourceFetcher { + kDisableCORSHandlingByResourceFetcher, + kEnableCORSHandlingByResourceFetcher, +}; + +// Was the request generated from a "parser-inserted" element? +// https://html.spec.whatwg.org/multipage/scripting.html#parser-inserted +enum ParserDisposition : uint8_t { kParserInserted, kNotParserInserted }; + +enum CacheAwareLoadingEnabled : uint8_t { + kNotCacheAwareLoadingEnabled, + kIsCacheAwareLoadingEnabled +}; + +struct ResourceLoaderOptions { + USING_FAST_MALLOC(ResourceLoaderOptions); + + public: + ResourceLoaderOptions() + : data_buffering_policy(kBufferData), + content_security_policy_option(kCheckContentSecurityPolicy), + request_initiator_context(kDocumentContext), + synchronous_policy(kRequestAsynchronously), + cors_handling_by_resource_fetcher(kEnableCORSHandlingByResourceFetcher), + cors_flag(false), + parser_disposition(kParserInserted), + cache_aware_loading_enabled(kNotCacheAwareLoadingEnabled) {} + + FetchInitiatorInfo initiator_info; + + // ATTENTION: When adding members, update + // CrossThreadResourceLoaderOptionsData, too. + + DataBufferingPolicy data_buffering_policy; + + ContentSecurityPolicyDisposition content_security_policy_option; + RequestInitiatorContext request_initiator_context; + SynchronousPolicy synchronous_policy; + + // When set to kDisableCORSHandlingByResourceFetcher, the ResourceFetcher + // suppresses part of its CORS handling logic. + // Used by DocumentThreadableLoader which does CORS handling by itself. + CORSHandlingByResourceFetcher cors_handling_by_resource_fetcher; + + // Corresponds to the CORS flag in the Fetch spec. + bool cors_flag; + + scoped_refptr<const SecurityOrigin> security_origin; + String content_security_policy_nonce; + IntegrityMetadataSet integrity_metadata; + ParserDisposition parser_disposition; + CacheAwareLoadingEnabled cache_aware_loading_enabled; + + // If not null, this URLLoaderFactory should be used to load this resource + // rather than whatever factory the system might otherwise use. + // Used for example for loading blob: URLs and for prefetch loading. + scoped_refptr< + base::RefCountedData<network::mojom::blink::URLLoaderFactoryPtr>> + url_loader_factory; +}; + +// Encode AtomicString (in FetchInitiatorInfo) as String to cross threads. +struct CrossThreadResourceLoaderOptionsData { + DISALLOW_NEW(); + explicit CrossThreadResourceLoaderOptionsData( + const ResourceLoaderOptions& options) + : data_buffering_policy(options.data_buffering_policy), + content_security_policy_option(options.content_security_policy_option), + initiator_info(options.initiator_info), + request_initiator_context(options.request_initiator_context), + synchronous_policy(options.synchronous_policy), + cors_handling_by_resource_fetcher( + options.cors_handling_by_resource_fetcher), + cors_flag(options.cors_flag), + security_origin(options.security_origin + ? options.security_origin->IsolatedCopy() + : nullptr), + content_security_policy_nonce( + options.content_security_policy_nonce.IsolatedCopy()), + integrity_metadata(options.integrity_metadata), + parser_disposition(options.parser_disposition), + cache_aware_loading_enabled(options.cache_aware_loading_enabled) { + if (options.url_loader_factory) { + DCHECK(options.url_loader_factory->data.is_bound()); + url_loader_factory = base::MakeRefCounted<base::RefCountedData< + network::mojom::blink::URLLoaderFactoryPtrInfo>>(); + options.url_loader_factory->data->Clone( + MakeRequest(&url_loader_factory->data)); + } + } + + operator ResourceLoaderOptions() const { + ResourceLoaderOptions options; + options.data_buffering_policy = data_buffering_policy; + options.content_security_policy_option = content_security_policy_option; + options.initiator_info = initiator_info; + options.request_initiator_context = request_initiator_context; + options.synchronous_policy = synchronous_policy; + options.cors_handling_by_resource_fetcher = + cors_handling_by_resource_fetcher; + options.cors_flag = cors_flag; + options.security_origin = security_origin; + options.content_security_policy_nonce = content_security_policy_nonce; + options.integrity_metadata = integrity_metadata; + options.parser_disposition = parser_disposition; + options.cache_aware_loading_enabled = cache_aware_loading_enabled; + if (url_loader_factory) { + DCHECK(url_loader_factory->data.is_valid()); + options.url_loader_factory = base::MakeRefCounted< + base::RefCountedData<network::mojom::blink::URLLoaderFactoryPtr>>( + network::mojom::blink::URLLoaderFactoryPtr( + std::move(url_loader_factory->data))); + } + return options; + } + + DataBufferingPolicy data_buffering_policy; + ContentSecurityPolicyDisposition content_security_policy_option; + CrossThreadFetchInitiatorInfoData initiator_info; + RequestInitiatorContext request_initiator_context; + SynchronousPolicy synchronous_policy; + + CORSHandlingByResourceFetcher cors_handling_by_resource_fetcher; + bool cors_flag; + scoped_refptr<const SecurityOrigin> security_origin; + + String content_security_policy_nonce; + IntegrityMetadataSet integrity_metadata; + ParserDisposition parser_disposition; + CacheAwareLoadingEnabled cache_aware_loading_enabled; + scoped_refptr< + base::RefCountedData<network::mojom::blink::URLLoaderFactoryPtrInfo>> + url_loader_factory; +}; + +template <> +struct CrossThreadCopier<ResourceLoaderOptions> { + using Type = CrossThreadResourceLoaderOptionsData; + static Type Copy(const ResourceLoaderOptions& options) { + return CrossThreadResourceLoaderOptionsData(options); + } +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_PLATFORM_LOADER_FETCH_RESOURCE_LOADER_OPTIONS_H_ diff --git a/chromium/third_party/blink/renderer/platform/loader/fetch/resource_loader_options_test.cc b/chromium/third_party/blink/renderer/platform/loader/fetch/resource_loader_options_test.cc new file mode 100644 index 00000000000..9fd9edd4160 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/loader/fetch/resource_loader_options_test.cc @@ -0,0 +1,108 @@ +// 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/platform/loader/fetch/resource_loader_options.h" + +#include "testing/gtest/include/gtest/gtest.h" +#include <type_traits> + +namespace blink { + +namespace { + +TEST(ResourceLoaderOptionsTest, DeepCopy) { + // Check that the fields of ResourceLoaderOptions are enums, except for + // initiatorInfo and securityOrigin. + static_assert(std::is_enum<DataBufferingPolicy>::value, + "DataBufferingPolicy should be an enum"); + static_assert(std::is_enum<ContentSecurityPolicyDisposition>::value, + "ContentSecurityPolicyDisposition should be an enum"); + static_assert(std::is_enum<RequestInitiatorContext>::value, + "RequestInitiatorContext should be an enum"); + static_assert(std::is_enum<SynchronousPolicy>::value, + "SynchronousPolicy should be an enum"); + static_assert(std::is_enum<CORSHandlingByResourceFetcher>::value, + "CORSHandlingByResourceFetcher should be an enum"); + + ResourceLoaderOptions original; + scoped_refptr<const SecurityOrigin> security_origin = + SecurityOrigin::CreateFromString("http://www.google.com"); + original.security_origin = security_origin; + original.initiator_info.name = AtomicString("xmlhttprequest"); + + CrossThreadResourceLoaderOptionsData copy_data = + CrossThreadCopier<ResourceLoaderOptions>::Copy(original); + ResourceLoaderOptions copy = copy_data; + + // Check that contents are correctly copied to |copyData| + EXPECT_EQ(original.data_buffering_policy, copy_data.data_buffering_policy); + EXPECT_EQ(original.content_security_policy_option, + copy_data.content_security_policy_option); + EXPECT_EQ(original.initiator_info.name, copy_data.initiator_info.name); + EXPECT_EQ(original.initiator_info.position, + copy_data.initiator_info.position); + EXPECT_EQ(original.initiator_info.start_time, + copy_data.initiator_info.start_time); + EXPECT_EQ(original.request_initiator_context, + copy_data.request_initiator_context); + EXPECT_EQ(original.synchronous_policy, copy_data.synchronous_policy); + EXPECT_EQ(original.cors_handling_by_resource_fetcher, + copy_data.cors_handling_by_resource_fetcher); + EXPECT_EQ(original.security_origin->Protocol(), + copy_data.security_origin->Protocol()); + EXPECT_EQ(original.security_origin->Host(), + copy_data.security_origin->Host()); + EXPECT_EQ(original.security_origin->Domain(), + copy_data.security_origin->Domain()); + + // Check that pointers are different between |original| and |copyData| + EXPECT_NE(original.initiator_info.name.Impl(), + copy_data.initiator_info.name.Impl()); + EXPECT_NE(original.security_origin.get(), copy_data.security_origin.get()); + EXPECT_NE(original.security_origin->Protocol().Impl(), + copy_data.security_origin->Protocol().Impl()); + EXPECT_NE(original.security_origin->Host().Impl(), + copy_data.security_origin->Host().Impl()); + EXPECT_NE(original.security_origin->Domain().Impl(), + copy_data.security_origin->Domain().Impl()); + + // Check that contents are correctly copied to |copy| + EXPECT_EQ(original.data_buffering_policy, copy.data_buffering_policy); + EXPECT_EQ(original.content_security_policy_option, + copy.content_security_policy_option); + EXPECT_EQ(original.initiator_info.name, copy.initiator_info.name); + EXPECT_EQ(original.initiator_info.position, copy.initiator_info.position); + EXPECT_EQ(original.initiator_info.start_time, copy.initiator_info.start_time); + EXPECT_EQ(original.request_initiator_context, copy.request_initiator_context); + EXPECT_EQ(original.synchronous_policy, copy.synchronous_policy); + EXPECT_EQ(original.cors_handling_by_resource_fetcher, + copy.cors_handling_by_resource_fetcher); + EXPECT_EQ(original.security_origin->Protocol(), + copy.security_origin->Protocol()); + EXPECT_EQ(original.security_origin->Host(), copy.security_origin->Host()); + EXPECT_EQ(original.security_origin->Domain(), copy.security_origin->Domain()); + + // Check that pointers are different between |original| and |copy| + // FIXME: When |original| and |copy| are in different threads, then + // EXPECT_NE(original.initiatorInfo.name.impl(), + // copy.initiatorInfo.name.impl()); + // should pass. However, in the unit test here, these two pointers are the + // same, because initiatorInfo.name is AtomicString. + EXPECT_NE(original.security_origin.get(), copy.security_origin.get()); + EXPECT_NE(original.security_origin->Protocol().Impl(), + copy.security_origin->Protocol().Impl()); + EXPECT_NE(original.security_origin->Host().Impl(), + copy.security_origin->Host().Impl()); + EXPECT_NE(original.security_origin->Domain().Impl(), + copy.security_origin->Domain().Impl()); + + // FIXME: The checks for content equality/pointer inequality for + // securityOrigin here is not complete (i.e. m_filePath is not checked). A + // unit test for SecurityOrigin::isolatedCopy() that covers these checks + // should be added. +} + +} // namespace + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/platform/loader/fetch/resource_loader_test.cc b/chromium/third_party/blink/renderer/platform/loader/fetch/resource_loader_test.cc new file mode 100644 index 00000000000..0336dc93bc3 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/loader/fetch/resource_loader_test.cc @@ -0,0 +1,155 @@ +// 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/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_load_scheduler.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource_loader.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/testing/testing_platform_support_with_mock_scheduler.h" +#include "third_party/blink/renderer/platform/wtf/text/string_builder.h" + +#include "testing/gtest/include/gtest/gtest.h" + +namespace blink { + +class ResourceLoaderTest : public testing::Test { + DISALLOW_COPY_AND_ASSIGN(ResourceLoaderTest); + + public: + ResourceLoaderTest() + : foo_url_("https://foo.test"), bar_url_("https://bar.test"){}; + + void SetUp() override { + context_ = + MockFetchContext::Create(MockFetchContext::kShouldLoadNewResource); + } + + protected: + enum ServiceWorkerMode { kNoSW, kSWOpaque, kSWClear }; + + struct TestCase { + const KURL& origin; + const KURL& target; + const KURL* allow_origin_url; + const ServiceWorkerMode service_worker; + const Resource::Type resource_type; + const CORSStatus expectation; + }; + + Persistent<MockFetchContext> context_; + + ScopedTestingPlatformSupport<TestingPlatformSupportWithMockScheduler> + platform_; + + const KURL foo_url_; + const KURL bar_url_; +}; + +TEST_F(ResourceLoaderTest, DetermineCORSStatus) { + TestCase cases[] = { + // No CORS status for main resources: + {foo_url_, foo_url_, nullptr, kNoSW, Resource::Type::kMainResource, + CORSStatus::kNotApplicable}, + + // Same origin: + {foo_url_, foo_url_, nullptr, kNoSW, Resource::Type::kRaw, + CORSStatus::kSameOrigin}, + + // Cross origin CORS successful: + {foo_url_, bar_url_, &foo_url_, kNoSW, Resource::Type::kRaw, + CORSStatus::kSuccessful}, + + // Cross origin not in CORS mode: + {foo_url_, bar_url_, nullptr, kNoSW, Resource::Type::kRaw, + CORSStatus::kNotApplicable}, + + // Cross origin CORS failed: + {foo_url_, bar_url_, &bar_url_, kNoSW, Resource::Type::kRaw, + CORSStatus::kFailed}, + + // CORS handled by service worker + {foo_url_, foo_url_, nullptr, kSWClear, Resource::Type::kRaw, + CORSStatus::kServiceWorkerSuccessful}, + {foo_url_, foo_url_, &foo_url_, kSWClear, Resource::Type::kRaw, + CORSStatus::kServiceWorkerSuccessful}, + {foo_url_, bar_url_, nullptr, kSWClear, Resource::Type::kRaw, + CORSStatus::kServiceWorkerSuccessful}, + {foo_url_, bar_url_, &foo_url_, kSWClear, Resource::Type::kRaw, + CORSStatus::kServiceWorkerSuccessful}, + + // Opaque response by service worker + {foo_url_, foo_url_, nullptr, kSWOpaque, Resource::Type::kRaw, + CORSStatus::kServiceWorkerOpaque}, + {foo_url_, bar_url_, nullptr, kSWOpaque, Resource::Type::kRaw, + CORSStatus::kServiceWorkerOpaque}, + {foo_url_, bar_url_, &foo_url_, kSWOpaque, Resource::Type::kRaw, + CORSStatus::kServiceWorkerOpaque}, + }; + + ResourceLoadScheduler* scheduler = ResourceLoadScheduler::Create(); + + for (const auto& test : cases) { + SCOPED_TRACE(testing::Message() + << "Origin: " << test.origin.GetString() + << ", target: " << test.target.GetString() + << ", CORS access-control-allow-origin header: " + << (test.allow_origin_url ? test.allow_origin_url->GetString() + : "-") + << ", service worker: " + << (test.service_worker == kNoSW + ? "no" + : (test.service_worker == kSWClear + ? "clear response" + : "opaque response")) + << ", expected CORSStatus == " + << static_cast<unsigned>(test.expectation)); + + context_->SetSecurityOrigin(SecurityOrigin::Create(test.origin)); + ResourceFetcher* fetcher = ResourceFetcher::Create(context_); + + Resource* resource = + RawResource::CreateForTest(test.target, test.resource_type); + ResourceLoader* loader = + ResourceLoader::Create(fetcher, scheduler, resource); + + ResourceRequest request; + request.SetURL(test.target); + + ResourceResponse response(test.target); + response.SetHTTPStatusCode(200); + + if (test.allow_origin_url) { + request.SetFetchRequestMode(network::mojom::FetchRequestMode::kCORS); + resource->MutableOptions().cors_handling_by_resource_fetcher = + kEnableCORSHandlingByResourceFetcher; + response.SetHTTPHeaderField( + "access-control-allow-origin", + SecurityOrigin::Create(*test.allow_origin_url)->ToAtomicString()); + response.SetHTTPHeaderField("access-control-allow-credentials", "true"); + } + + resource->SetResourceRequest(request); + + if (test.service_worker != kNoSW) { + response.SetWasFetchedViaServiceWorker(true); + + if (test.service_worker == kSWOpaque) { + response.SetResponseTypeViaServiceWorker( + network::mojom::FetchResponseType::kOpaque); + } else { + response.SetResponseTypeViaServiceWorker( + network::mojom::FetchResponseType::kDefault); + } + } + + StringBuilder cors_error_msg; + CORSStatus cors_status = + loader->DetermineCORSStatus(response, cors_error_msg); + + EXPECT_EQ(cors_status, test.expectation); + } +} +} // namespace blink diff --git a/chromium/third_party/blink/renderer/platform/loader/fetch/resource_loading_log.h b/chromium/third_party/blink/renderer/platform/loader/fetch/resource_loading_log.h new file mode 100644 index 00000000000..62435966a4b --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/loader/fetch/resource_loading_log.h @@ -0,0 +1,21 @@ +// 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_PLATFORM_LOADER_FETCH_RESOURCE_LOADING_LOG_H_ +#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_LOADER_FETCH_RESOURCE_LOADING_LOG_H_ + +#include "third_party/blink/renderer/platform/wtf/assertions.h" + +#if DCHECK_IS_ON() +// We can see logs with |--v=N| or |--vmodule=ResourceLoadingLog=N| where N is a +// verbose level. +#define RESOURCE_LOADING_DVLOG(verbose_level) \ + LAZY_STREAM( \ + VLOG_STREAM(verbose_level), \ + ((verbose_level) <= ::logging::GetVlogLevel("ResourceLoadingLog.h"))) +#else +#define RESOURCE_LOADING_DVLOG(verbose_level) EAT_STREAM_PARAMETERS +#endif + +#endif // THIRD_PARTY_BLINK_RENDERER_PLATFORM_LOADER_FETCH_RESOURCE_LOADING_LOG_H_ diff --git a/chromium/third_party/blink/renderer/platform/loader/fetch/resource_priority.h b/chromium/third_party/blink/renderer/platform/loader/fetch/resource_priority.h new file mode 100644 index 00000000000..7b4ecebf224 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/loader/fetch/resource_priority.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) 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 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_PLATFORM_LOADER_FETCH_RESOURCE_PRIORITY_H_ +#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_LOADER_FETCH_RESOURCE_PRIORITY_H_ + +#include "third_party/blink/renderer/platform/wtf/allocator.h" + +namespace blink { + +struct ResourcePriority final { + STACK_ALLOCATED(); + + public: + enum VisibilityStatus { + kNotVisible, + kVisible, + }; + + ResourcePriority() : ResourcePriority(kNotVisible, 0) {} + ResourcePriority(VisibilityStatus status, int intra_value) + : visibility(status), intra_priority_value(intra_value) {} + + VisibilityStatus visibility; + int intra_priority_value; +}; + +} // namespace blink + +#endif diff --git a/chromium/third_party/blink/renderer/platform/loader/fetch/resource_request.cc b/chromium/third_party/blink/renderer/platform/loader/fetch/resource_request.cc new file mode 100644 index 00000000000..fab47ed61e5 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/loader/fetch/resource_request.cc @@ -0,0 +1,469 @@ +/* + * Copyright (C) 2003, 2006 Apple Computer, Inc. All rights reserved. + * Copyright (C) 2009, 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. + * + * 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/platform/loader/fetch/resource_request.h" + +#include <memory> + +#include "third_party/blink/public/platform/web_url_request.h" +#include "third_party/blink/renderer/platform/network/http_names.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/weborigin/security_origin.h" + +namespace blink { + +double ResourceRequest::default_timeout_interval_ = INT_MAX; + +ResourceRequest::ResourceRequest() : ResourceRequest(NullURL()) {} + +ResourceRequest::ResourceRequest(const String& url_string) + : ResourceRequest(KURL(url_string)) {} + +ResourceRequest::ResourceRequest(const KURL& url) + : url_(url), + timeout_interval_(default_timeout_interval_), + requestor_origin_(nullptr), + http_method_(HTTPNames::GET), + allow_stored_credentials_(true), + report_upload_progress_(false), + report_raw_headers_(false), + has_user_gesture_(false), + download_to_file_(false), + download_to_blob_(false), + use_stream_on_response_(false), + keepalive_(false), + should_reset_app_cache_(false), + cache_mode_(mojom::FetchCacheMode::kDefault), + skip_service_worker_(false), + priority_(ResourceLoadPriority::kLowest), + intra_priority_value_(0), + requestor_id_(0), + plugin_child_id_(-1), + app_cache_host_id_(0), + previews_state_(WebURLRequest::kPreviewsUnspecified), + request_context_(WebURLRequest::kRequestContextUnspecified), + frame_type_(network::mojom::RequestContextFrameType::kNone), + fetch_request_mode_(network::mojom::FetchRequestMode::kNoCORS), + fetch_credentials_mode_(network::mojom::FetchCredentialsMode::kInclude), + fetch_redirect_mode_(network::mojom::FetchRedirectMode::kFollow), + referrer_policy_(kReferrerPolicyDefault), + did_set_http_referrer_(false), + check_for_browser_side_navigation_(true), + was_discarded_(false), + ui_start_time_(0), + is_external_request_(false), + cors_preflight_policy_( + network::mojom::CORSPreflightPolicy::kConsiderPreflight), + is_same_document_navigation_(false), + input_perf_metric_report_policy_( + InputToLoadPerfMetricReportPolicy::kNoReport), + redirect_status_(RedirectStatus::kNoRedirect) {} + +ResourceRequest::ResourceRequest(CrossThreadResourceRequestData* data) + : ResourceRequest(data->url_) { + SetTimeoutInterval(data->timeout_interval_); + SetSiteForCookies(data->site_for_cookies_); + SetRequestorOrigin(data->requestor_origin_); + SetHTTPMethod(AtomicString(data->http_method_)); + SetPriority(data->priority_, data->intra_priority_value_); + + http_header_fields_.Adopt(std::move(data->http_headers_)); + + SetHTTPBody(data->http_body_); + SetAllowStoredCredentials(data->allow_stored_credentials_); + SetReportUploadProgress(data->report_upload_progress_); + SetHasUserGesture(data->has_user_gesture_); + SetDownloadToFile(data->download_to_file_); + SetDownloadToBlob(data->download_to_blob_); + SetUseStreamOnResponse(data->use_stream_on_response_); + SetKeepalive(data->keepalive_); + SetCacheMode(data->cache_mode_); + SetSkipServiceWorker(data->skip_service_worker_); + SetShouldResetAppCache(data->should_reset_app_cache_); + SetRequestorID(data->requestor_id_); + SetPluginChildID(data->plugin_child_id_); + SetAppCacheHostID(data->app_cache_host_id_); + SetPreviewsState(data->previews_state_); + SetRequestContext(data->request_context_); + SetFrameType(data->frame_type_); + SetFetchRequestMode(data->fetch_request_mode_); + SetFetchCredentialsMode(data->fetch_credentials_mode_); + SetFetchRedirectMode(data->fetch_redirect_mode_); + SetFetchIntegrity(data->fetch_integrity_.IsolatedCopy()); + referrer_policy_ = data->referrer_policy_; + did_set_http_referrer_ = data->did_set_http_referrer_; + check_for_browser_side_navigation_ = data->check_for_browser_side_navigation_; + ui_start_time_ = data->ui_start_time_; + is_external_request_ = data->is_external_request_; + cors_preflight_policy_ = data->cors_preflight_policy_; + input_perf_metric_report_policy_ = data->input_perf_metric_report_policy_; + redirect_status_ = data->redirect_status_; + suggested_filename_ = data->suggested_filename_; + is_ad_resource_ = data->is_ad_resource_; +} + +ResourceRequest::ResourceRequest(const ResourceRequest&) = default; + +ResourceRequest& ResourceRequest::operator=(const ResourceRequest&) = default; + +std::unique_ptr<ResourceRequest> ResourceRequest::CreateRedirectRequest( + const KURL& new_url, + const AtomicString& new_method, + const KURL& new_site_for_cookies, + const String& new_referrer, + ReferrerPolicy new_referrer_policy, + bool skip_service_worker) const { + std::unique_ptr<ResourceRequest> request = + std::make_unique<ResourceRequest>(new_url); + request->SetHTTPMethod(new_method); + request->SetSiteForCookies(new_site_for_cookies); + String referrer = + new_referrer.IsEmpty() ? Referrer::NoReferrer() : String(new_referrer); + request->SetHTTPReferrer( + Referrer(referrer, static_cast<ReferrerPolicy>(new_referrer_policy))); + request->SetSkipServiceWorker(skip_service_worker); + request->SetRedirectStatus(RedirectStatus::kFollowedRedirect); + + // Copy from parameters for |this|. + request->SetDownloadToFile(DownloadToFile()); + request->SetDownloadToBlob(DownloadToBlob()); + request->SetUseStreamOnResponse(UseStreamOnResponse()); + request->SetRequestContext(GetRequestContext()); + request->SetFrameType(GetFrameType()); + request->SetShouldResetAppCache(ShouldResetAppCache()); + request->SetFetchRequestMode(GetFetchRequestMode()); + request->SetFetchCredentialsMode(GetFetchCredentialsMode()); + request->SetKeepalive(GetKeepalive()); + request->SetPriority(Priority()); + + if (request->HttpMethod() == HttpMethod()) + request->SetHTTPBody(HttpBody()); + request->SetCheckForBrowserSideNavigation(CheckForBrowserSideNavigation()); + request->SetWasDiscarded(WasDiscarded()); + request->SetCORSPreflightPolicy(CORSPreflightPolicy()); + if (IsAdResource()) + request->SetIsAdResource(); + + return request; +} + +std::unique_ptr<CrossThreadResourceRequestData> ResourceRequest::CopyData() + const { + std::unique_ptr<CrossThreadResourceRequestData> data = + std::make_unique<CrossThreadResourceRequestData>(); + data->url_ = Url().Copy(); + data->timeout_interval_ = TimeoutInterval(); + data->site_for_cookies_ = SiteForCookies().Copy(); + data->requestor_origin_ = + RequestorOrigin() ? RequestorOrigin()->IsolatedCopy() : nullptr; + data->http_method_ = HttpMethod().GetString().IsolatedCopy(); + data->http_headers_ = HttpHeaderFields().CopyData(); + data->priority_ = Priority(); + data->intra_priority_value_ = intra_priority_value_; + + if (http_body_) + data->http_body_ = http_body_->DeepCopy(); + data->allow_stored_credentials_ = allow_stored_credentials_; + data->report_upload_progress_ = report_upload_progress_; + data->has_user_gesture_ = has_user_gesture_; + data->download_to_file_ = download_to_file_; + data->download_to_blob_ = download_to_blob_; + data->use_stream_on_response_ = use_stream_on_response_; + data->keepalive_ = keepalive_; + data->cache_mode_ = GetCacheMode(); + data->skip_service_worker_ = skip_service_worker_; + data->should_reset_app_cache_ = should_reset_app_cache_; + data->requestor_id_ = requestor_id_; + data->plugin_child_id_ = plugin_child_id_; + data->app_cache_host_id_ = app_cache_host_id_; + data->previews_state_ = previews_state_; + data->request_context_ = request_context_; + data->frame_type_ = frame_type_; + data->fetch_request_mode_ = fetch_request_mode_; + data->fetch_credentials_mode_ = fetch_credentials_mode_; + data->fetch_redirect_mode_ = fetch_redirect_mode_; + data->fetch_integrity_ = fetch_integrity_.IsolatedCopy(); + data->referrer_policy_ = referrer_policy_; + data->did_set_http_referrer_ = did_set_http_referrer_; + data->check_for_browser_side_navigation_ = check_for_browser_side_navigation_; + data->ui_start_time_ = ui_start_time_; + data->is_external_request_ = is_external_request_; + data->cors_preflight_policy_ = cors_preflight_policy_; + data->input_perf_metric_report_policy_ = input_perf_metric_report_policy_; + data->redirect_status_ = redirect_status_; + data->suggested_filename_ = suggested_filename_; + data->is_ad_resource_ = is_ad_resource_; + return data; +} + +bool ResourceRequest::IsNull() const { + return url_.IsNull(); +} + +const KURL& ResourceRequest::Url() const { + return url_; +} + +void ResourceRequest::SetURL(const KURL& url) { + url_ = url; +} + +void ResourceRequest::RemoveUserAndPassFromURL() { + if (url_.User().IsEmpty() && url_.Pass().IsEmpty()) + return; + + url_.SetUser(String()); + url_.SetPass(String()); +} + +mojom::FetchCacheMode ResourceRequest::GetCacheMode() const { + return cache_mode_; +} + +void ResourceRequest::SetCacheMode(mojom::FetchCacheMode cache_mode) { + cache_mode_ = cache_mode; +} + +double ResourceRequest::TimeoutInterval() const { + return timeout_interval_; +} + +void ResourceRequest::SetTimeoutInterval(double timout_interval_seconds) { + timeout_interval_ = timout_interval_seconds; +} + +const KURL& ResourceRequest::SiteForCookies() const { + return site_for_cookies_; +} + +void ResourceRequest::SetSiteForCookies(const KURL& site_for_cookies) { + site_for_cookies_ = site_for_cookies; +} + +scoped_refptr<const SecurityOrigin> ResourceRequest::RequestorOrigin() const { + return requestor_origin_; +} + +void ResourceRequest::SetRequestorOrigin( + scoped_refptr<const SecurityOrigin> requestor_origin) { + requestor_origin_ = std::move(requestor_origin); +} + +const AtomicString& ResourceRequest::HttpMethod() const { + return http_method_; +} + +void ResourceRequest::SetHTTPMethod(const AtomicString& http_method) { + http_method_ = http_method; +} + +const HTTPHeaderMap& ResourceRequest::HttpHeaderFields() const { + return http_header_fields_; +} + +const AtomicString& ResourceRequest::HttpHeaderField( + const AtomicString& name) const { + return http_header_fields_.Get(name); +} + +void ResourceRequest::SetHTTPHeaderField(const AtomicString& name, + const AtomicString& value) { + http_header_fields_.Set(name, value); +} + +void ResourceRequest::SetHTTPReferrer(const Referrer& referrer) { + if (referrer.referrer.IsEmpty()) + http_header_fields_.Remove(HTTPNames::Referer); + else + SetHTTPHeaderField(HTTPNames::Referer, referrer.referrer); + referrer_policy_ = referrer.referrer_policy; + did_set_http_referrer_ = true; +} + +void ResourceRequest::ClearHTTPReferrer() { + http_header_fields_.Remove(HTTPNames::Referer); + referrer_policy_ = kReferrerPolicyDefault; + did_set_http_referrer_ = false; +} + +void ResourceRequest::SetHTTPOrigin(const SecurityOrigin* origin) { + SetHTTPHeaderField(HTTPNames::Origin, origin->ToAtomicString()); +} + +void ResourceRequest::ClearHTTPOrigin() { + http_header_fields_.Remove(HTTPNames::Origin); +} + +void ResourceRequest::SetHTTPOriginIfNeeded(const SecurityOrigin* origin) { + if (NeedsHTTPOrigin()) + SetHTTPOrigin(origin); +} + +void ResourceRequest::SetHTTPOriginToMatchReferrerIfNeeded() { + if (NeedsHTTPOrigin()) { + SetHTTPOrigin( + SecurityOrigin::CreateFromString(HttpHeaderField(HTTPNames::Referer)) + .get()); + } +} + +void ResourceRequest::ClearHTTPUserAgent() { + http_header_fields_.Remove(HTTPNames::User_Agent); +} + +EncodedFormData* ResourceRequest::HttpBody() const { + return http_body_.get(); +} + +void ResourceRequest::SetHTTPBody(scoped_refptr<EncodedFormData> http_body) { + http_body_ = std::move(http_body); +} + +bool ResourceRequest::AllowStoredCredentials() const { + return allow_stored_credentials_; +} + +void ResourceRequest::SetAllowStoredCredentials(bool allow_credentials) { + allow_stored_credentials_ = allow_credentials; +} + +ResourceLoadPriority ResourceRequest::Priority() const { + return priority_; +} + +int ResourceRequest::IntraPriorityValue() const { + return intra_priority_value_; +} + +void ResourceRequest::SetPriority(ResourceLoadPriority priority, + int intra_priority_value) { + priority_ = priority; + intra_priority_value_ = intra_priority_value; +} + +void ResourceRequest::AddHTTPHeaderField(const AtomicString& name, + const AtomicString& value) { + HTTPHeaderMap::AddResult result = http_header_fields_.Add(name, value); + if (!result.is_new_entry) + result.stored_value->value = result.stored_value->value + ", " + value; +} + +void ResourceRequest::AddHTTPHeaderFields(const HTTPHeaderMap& header_fields) { + HTTPHeaderMap::const_iterator end = header_fields.end(); + for (HTTPHeaderMap::const_iterator it = header_fields.begin(); it != end; + ++it) + AddHTTPHeaderField(it->key, it->value); +} + +void ResourceRequest::ClearHTTPHeaderField(const AtomicString& name) { + http_header_fields_.Remove(name); +} + +void ResourceRequest::SetExternalRequestStateFromRequestorAddressSpace( + mojom::IPAddressSpace requestor_space) { + static_assert(mojom::IPAddressSpace::kLocal < mojom::IPAddressSpace::kPrivate, + "Local is inside Private"); + static_assert(mojom::IPAddressSpace::kLocal < mojom::IPAddressSpace::kPublic, + "Local is inside Public"); + static_assert( + mojom::IPAddressSpace::kPrivate < mojom::IPAddressSpace::kPublic, + "Private is inside Public"); + + // TODO(mkwst): This only checks explicit IP addresses. We'll have to move all + // this up to //net and //content in order to have any real impact on gateway + // attacks. That turns out to be a TON of work. https://crbug.com/378566 + if (!RuntimeEnabledFeatures::CorsRFC1918Enabled()) { + is_external_request_ = false; + return; + } + + mojom::IPAddressSpace target_space = mojom::IPAddressSpace::kPublic; + if (NetworkUtils::IsReservedIPAddress(url_.Host())) + target_space = mojom::IPAddressSpace::kPrivate; + if (SecurityOrigin::Create(url_)->IsLocalhost()) + target_space = mojom::IPAddressSpace::kLocal; + + is_external_request_ = requestor_space > target_space; +} + +void ResourceRequest::SetNavigationStartTime(double navigation_start) { + navigation_start_ = navigation_start; +} + +bool ResourceRequest::IsConditional() const { + return (http_header_fields_.Contains(HTTPNames::If_Match) || + http_header_fields_.Contains(HTTPNames::If_Modified_Since) || + http_header_fields_.Contains(HTTPNames::If_None_Match) || + http_header_fields_.Contains(HTTPNames::If_Range) || + http_header_fields_.Contains(HTTPNames::If_Unmodified_Since)); +} + +void ResourceRequest::SetHasUserGesture(bool has_user_gesture) { + has_user_gesture_ |= has_user_gesture; +} + +const CacheControlHeader& ResourceRequest::GetCacheControlHeader() const { + if (!cache_control_header_cache_.parsed) { + cache_control_header_cache_ = ParseCacheControlDirectives( + http_header_fields_.Get(HTTPNames::Cache_Control), + http_header_fields_.Get(HTTPNames::Pragma)); + } + return cache_control_header_cache_; +} + +bool ResourceRequest::CacheControlContainsNoCache() const { + return GetCacheControlHeader().contains_no_cache; +} + +bool ResourceRequest::CacheControlContainsNoStore() const { + return GetCacheControlHeader().contains_no_store; +} + +bool ResourceRequest::HasCacheValidatorFields() const { + return !http_header_fields_.Get(HTTPNames::Last_Modified).IsEmpty() || + !http_header_fields_.Get(HTTPNames::ETag).IsEmpty(); +} + +bool ResourceRequest::NeedsHTTPOrigin() const { + if (!HttpOrigin().IsEmpty()) + return false; // Request already has an Origin header. + + // Don't send an Origin header for GET or HEAD to avoid privacy issues. + // For example, if an intranet page has a hyperlink to an external web + // site, we don't want to include the Origin of the request because it + // will leak the internal host name. Similar privacy concerns have lead + // to the widespread suppression of the Referer header at the network + // layer. + if (HttpMethod() == HTTPNames::GET || HttpMethod() == HTTPNames::HEAD) + return false; + + // For non-GET and non-HEAD methods, always send an Origin header so the + // server knows we support this feature. + return true; +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/platform/loader/fetch/resource_request.h b/chromium/third_party/blink/renderer/platform/loader/fetch/resource_request.h new file mode 100644 index 00000000000..194138f38f3 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/loader/fetch/resource_request.h @@ -0,0 +1,498 @@ +/* + * Copyright (C) 2003, 2006 Apple Computer, Inc. All rights reserved. + * Copyright (C) 2006 Samuel Weinig <sam.weinig@gmail.com> + * Copyright (C) 2009, 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. + * + * 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_PLATFORM_LOADER_FETCH_RESOURCE_REQUEST_H_ +#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_LOADER_FETCH_RESOURCE_REQUEST_H_ + +#include <memory> +#include "services/network/public/mojom/cors.mojom-blink.h" +#include "services/network/public/mojom/fetch_api.mojom-blink.h" +#include "services/network/public/mojom/request_context_frame_type.mojom-shared.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_url_request.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource_load_priority.h" +#include "third_party/blink/renderer/platform/network/encoded_form_data.h" +#include "third_party/blink/renderer/platform/network/http_header_map.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/referrer.h" +#include "third_party/blink/renderer/platform/weborigin/security_origin.h" +#include "third_party/blink/renderer/platform/wtf/optional.h" +#include "third_party/blink/renderer/platform/wtf/ref_counted.h" + +namespace blink { + +enum class ResourceRequestBlockedReason { + kCSP, + kMixedContent, + kOrigin, + kInspector, + kSubresourceFilter, + kOther, + kContentType, + kNone +}; + +enum InputToLoadPerfMetricReportPolicy : uint8_t { + kNoReport, // Don't report metrics for this ResourceRequest. + kReportLink, // Report metrics for this request as initiated by a link click. + kReportIntent, // Report metrics for this request as initiated by an intent. +}; + +struct CrossThreadResourceRequestData; + +// A ResourceRequest is a "request" object for ResourceLoader. Conceptually +// it is https://fetch.spec.whatwg.org/#concept-request, but it contains +// a lot of blink specific fields. WebURLRequest is the "public version" +// of this class and WebURLLoader needs it. See WebURLRequest and +// WrappedResourceRequest. +// +// There are cases where we need to copy a request across threads, and +// CrossThreadResourceRequestData is a struct for the purpose. When you add a +// member variable to this class, do not forget to add the corresponding +// one in CrossThreadResourceRequestData and write copying logic. +class PLATFORM_EXPORT ResourceRequest final { + USING_FAST_MALLOC(ResourceRequest); + + public: + enum class RedirectStatus : uint8_t { kFollowedRedirect, kNoRedirect }; + + ResourceRequest(); + explicit ResourceRequest(const String& url_string); + explicit ResourceRequest(const KURL&); + explicit ResourceRequest(CrossThreadResourceRequestData*); + + // TODO(toyoshim): Use std::unique_ptr as much as possible, and hopefully + // make ResourceRequest WTF_MAKE_NONCOPYABLE. See crbug.com/787704. + ResourceRequest(const ResourceRequest&); + ResourceRequest& operator=(const ResourceRequest&); + + // Constructs a new ResourceRequest for a redirect from this instance. + std::unique_ptr<ResourceRequest> CreateRedirectRequest( + const KURL& new_url, + const AtomicString& new_method, + const KURL& new_site_for_cookies, + const String& new_referrer, + ReferrerPolicy new_referrer_policy, + bool skip_service_worker) const; + + // Gets a copy of the data suitable for passing to another thread. + std::unique_ptr<CrossThreadResourceRequestData> CopyData() const; + + bool IsNull() const; + + const KURL& Url() const; + void SetURL(const KURL&); + + void RemoveUserAndPassFromURL(); + + mojom::FetchCacheMode GetCacheMode() const; + void SetCacheMode(mojom::FetchCacheMode); + + double TimeoutInterval() const; // May return 0 when using platform default. + void SetTimeoutInterval(double); + + const KURL& SiteForCookies() const; + void SetSiteForCookies(const KURL&); + + scoped_refptr<const SecurityOrigin> RequestorOrigin() const; + void SetRequestorOrigin(scoped_refptr<const SecurityOrigin>); + + const AtomicString& HttpMethod() const; + void SetHTTPMethod(const AtomicString&); + + const HTTPHeaderMap& HttpHeaderFields() const; + const AtomicString& HttpHeaderField(const AtomicString& name) const; + void SetHTTPHeaderField(const AtomicString& name, const AtomicString& value); + void AddHTTPHeaderField(const AtomicString& name, const AtomicString& value); + void AddHTTPHeaderFields(const HTTPHeaderMap& header_fields); + void ClearHTTPHeaderField(const AtomicString& name); + + const AtomicString& HttpContentType() const { + return HttpHeaderField(HTTPNames::Content_Type); + } + void SetHTTPContentType(const AtomicString& http_content_type) { + SetHTTPHeaderField(HTTPNames::Content_Type, http_content_type); + } + + bool DidSetHTTPReferrer() const { return did_set_http_referrer_; } + const AtomicString& HttpReferrer() const { + return HttpHeaderField(HTTPNames::Referer); + } + ReferrerPolicy GetReferrerPolicy() const { return referrer_policy_; } + void SetHTTPReferrer(const Referrer&); + void ClearHTTPReferrer(); + + const AtomicString& HttpOrigin() const { + return HttpHeaderField(HTTPNames::Origin); + } + void SetHTTPOrigin(const SecurityOrigin*); + void ClearHTTPOrigin(); + void SetHTTPOriginIfNeeded(const SecurityOrigin*); + void SetHTTPOriginToMatchReferrerIfNeeded(); + + void SetHTTPUserAgent(const AtomicString& http_user_agent) { + SetHTTPHeaderField(HTTPNames::User_Agent, http_user_agent); + } + void ClearHTTPUserAgent(); + + void SetHTTPAccept(const AtomicString& http_accept) { + SetHTTPHeaderField(HTTPNames::Accept, http_accept); + } + + EncodedFormData* HttpBody() const; + void SetHTTPBody(scoped_refptr<EncodedFormData>); + + bool AllowStoredCredentials() const; + void SetAllowStoredCredentials(bool allow_credentials); + + // TODO(yhirano): Describe what Priority and IntraPriorityValue are. + ResourceLoadPriority Priority() const; + int IntraPriorityValue() const; + void SetPriority(ResourceLoadPriority, int intra_priority_value = 0); + + bool IsConditional() const; + + // Whether the associated ResourceHandleClient needs to be notified of + // upload progress made for that resource. + bool ReportUploadProgress() const { return report_upload_progress_; } + void SetReportUploadProgress(bool report_upload_progress) { + report_upload_progress_ = report_upload_progress; + } + + // Whether actual headers being sent/received should be collected and reported + // for the request. + bool ReportRawHeaders() const { return report_raw_headers_; } + void SetReportRawHeaders(bool report_raw_headers) { + report_raw_headers_ = report_raw_headers; + } + + // Allows the request to be matched up with its requestor. + int RequestorID() const { return requestor_id_; } + void SetRequestorID(int requestor_id) { requestor_id_ = requestor_id; } + + // The unique child id (not PID) of the process from which this request + // originated. In the case of out-of-process plugins, this allows to link back + // the request to the plugin process (as it is processed through a render view + // process). + int GetPluginChildID() const { return plugin_child_id_; } + void SetPluginChildID(int plugin_child_id) { + plugin_child_id_ = plugin_child_id; + } + + // Allows the request to be matched up with its app cache host. + int AppCacheHostID() const { return app_cache_host_id_; } + void SetAppCacheHostID(int id) { app_cache_host_id_ = id; } + + // True if request was user initiated. + bool HasUserGesture() const { return has_user_gesture_; } + void SetHasUserGesture(bool); + + // True if request should be downloaded to file. + bool DownloadToFile() const { return download_to_file_; } + void SetDownloadToFile(bool download_to_file) { + download_to_file_ = download_to_file; + } + + // True if request shuold be downloaded to blob. + bool DownloadToBlob() const { return download_to_blob_; } + void SetDownloadToBlob(bool download_to_blob) { + download_to_blob_ = download_to_blob; + } + + // True if the requestor wants to receive a response body as + // WebDataConsumerHandle. + bool UseStreamOnResponse() const { return use_stream_on_response_; } + void SetUseStreamOnResponse(bool use_stream_on_response) { + use_stream_on_response_ = use_stream_on_response; + } + + // True if the request can work after the fetch group is terminated. + bool GetKeepalive() const { return keepalive_; } + void SetKeepalive(bool keepalive) { keepalive_ = keepalive; } + + // True if service workers should not get events for the request. + bool GetSkipServiceWorker() const { return skip_service_worker_; } + void SetSkipServiceWorker(bool skip_service_worker) { + skip_service_worker_ = skip_service_worker; + } + + // True if corresponding AppCache group should be resetted. + bool ShouldResetAppCache() const { return should_reset_app_cache_; } + void SetShouldResetAppCache(bool should_reset_app_cache) { + should_reset_app_cache_ = should_reset_app_cache; + } + + // Extra data associated with this request. + WebURLRequest::ExtraData* GetExtraData() const { + return sharable_extra_data_ ? sharable_extra_data_->data.get() : nullptr; + } + void SetExtraData(std::unique_ptr<WebURLRequest::ExtraData> extra_data) { + if (extra_data) { + sharable_extra_data_ = + base::MakeRefCounted<SharableExtraData>(std::move(extra_data)); + } else { + sharable_extra_data_ = nullptr; + } + } + + WebURLRequest::RequestContext GetRequestContext() const { + return request_context_; + } + void SetRequestContext(WebURLRequest::RequestContext context) { + request_context_ = context; + } + + network::mojom::RequestContextFrameType GetFrameType() const { + return frame_type_; + } + void SetFrameType(network::mojom::RequestContextFrameType frame_type) { + frame_type_ = frame_type; + } + + network::mojom::FetchRequestMode GetFetchRequestMode() const { + return fetch_request_mode_; + } + void SetFetchRequestMode(network::mojom::FetchRequestMode mode) { + fetch_request_mode_ = mode; + } + + network::mojom::FetchCredentialsMode GetFetchCredentialsMode() const { + return fetch_credentials_mode_; + } + void SetFetchCredentialsMode(network::mojom::FetchCredentialsMode mode) { + fetch_credentials_mode_ = mode; + } + + network::mojom::FetchRedirectMode GetFetchRedirectMode() const { + return fetch_redirect_mode_; + } + void SetFetchRedirectMode(network::mojom::FetchRedirectMode redirect) { + fetch_redirect_mode_ = redirect; + } + + const String& GetFetchIntegrity() const { return fetch_integrity_; } + void SetFetchIntegrity(const String& integrity) { + fetch_integrity_ = integrity; + } + + WebURLRequest::PreviewsState GetPreviewsState() const { + return previews_state_; + } + void SetPreviewsState(WebURLRequest::PreviewsState previews_state) { + previews_state_ = previews_state; + } + + bool CacheControlContainsNoCache() const; + bool CacheControlContainsNoStore() const; + bool HasCacheValidatorFields() const; + + bool CheckForBrowserSideNavigation() const { + return check_for_browser_side_navigation_; + } + void SetCheckForBrowserSideNavigation(bool check) { + check_for_browser_side_navigation_ = check; + } + + bool WasDiscarded() const { return was_discarded_; } + void SetWasDiscarded(bool was_discarded) { was_discarded_ = was_discarded; } + + double UiStartTime() const { return ui_start_time_; } + void SetUIStartTime(double ui_start_time_seconds) { + ui_start_time_ = ui_start_time_seconds; + } + + // https://wicg.github.io/cors-rfc1918/#external-request + bool IsExternalRequest() const { return is_external_request_; } + void SetExternalRequestStateFromRequestorAddressSpace(mojom::IPAddressSpace); + + network::mojom::CORSPreflightPolicy CORSPreflightPolicy() const { + return cors_preflight_policy_; + } + void SetCORSPreflightPolicy(network::mojom::CORSPreflightPolicy policy) { + cors_preflight_policy_ = policy; + } + + InputToLoadPerfMetricReportPolicy InputPerfMetricReportPolicy() const { + return input_perf_metric_report_policy_; + } + void SetInputPerfMetricReportPolicy( + InputToLoadPerfMetricReportPolicy input_perf_metric_report_policy) { + input_perf_metric_report_policy_ = input_perf_metric_report_policy; + } + + void SetRedirectStatus(RedirectStatus status) { redirect_status_ = status; } + RedirectStatus GetRedirectStatus() const { return redirect_status_; } + + void SetSuggestedFilename(const WTF::Optional<String>& suggested_filename) { + suggested_filename_ = suggested_filename; + } + const WTF::Optional<String>& GetSuggestedFilename() const { + return suggested_filename_; + } + + void SetNavigationStartTime(double); + double NavigationStartTime() const { return navigation_start_; } + + void SetIsSameDocumentNavigation(bool is_same_document) { + is_same_document_navigation_ = is_same_document; + } + bool IsSameDocumentNavigation() const { return is_same_document_navigation_; } + + void SetIsAdResource() { is_ad_resource_ = true; } + bool IsAdResource() const { return is_ad_resource_; } + + private: + using SharableExtraData = + base::RefCountedData<std::unique_ptr<WebURLRequest::ExtraData>>; + + const CacheControlHeader& GetCacheControlHeader() const; + + bool NeedsHTTPOrigin() const; + + KURL url_; + double timeout_interval_; // 0 is a magic value for platform default on + // platforms that have one. + KURL site_for_cookies_; + + // The SecurityOrigin specified by the ResourceLoaderOptions in case e.g. + // when the fetching was initiated in an isolated world. Set by + // ResourceFetcher but only when needed. + // + // TODO(crbug.com/811669): Merge with some of the other origin variables. + scoped_refptr<const SecurityOrigin> requestor_origin_; + + AtomicString http_method_; + HTTPHeaderMap http_header_fields_; + scoped_refptr<EncodedFormData> http_body_; + bool allow_stored_credentials_ : 1; + bool report_upload_progress_ : 1; + bool report_raw_headers_ : 1; + bool has_user_gesture_ : 1; + bool download_to_file_ : 1; + bool download_to_blob_ : 1; + bool use_stream_on_response_ : 1; + bool keepalive_ : 1; + bool should_reset_app_cache_ : 1; + mojom::FetchCacheMode cache_mode_; + bool skip_service_worker_ : 1; + ResourceLoadPriority priority_; + int intra_priority_value_; + int requestor_id_; + int plugin_child_id_; + int app_cache_host_id_; + WebURLRequest::PreviewsState previews_state_; + scoped_refptr<SharableExtraData> sharable_extra_data_; + WebURLRequest::RequestContext request_context_; + network::mojom::RequestContextFrameType frame_type_; + network::mojom::FetchRequestMode fetch_request_mode_; + network::mojom::FetchCredentialsMode fetch_credentials_mode_; + network::mojom::FetchRedirectMode fetch_redirect_mode_; + String fetch_integrity_; + ReferrerPolicy referrer_policy_; + bool did_set_http_referrer_; + bool check_for_browser_side_navigation_; + bool was_discarded_; + double ui_start_time_; + bool is_external_request_; + network::mojom::CORSPreflightPolicy cors_preflight_policy_; + bool is_same_document_navigation_; + InputToLoadPerfMetricReportPolicy input_perf_metric_report_policy_; + RedirectStatus redirect_status_; + WTF::Optional<String> suggested_filename_; + + mutable CacheControlHeader cache_control_header_cache_; + + static double default_timeout_interval_; + + double navigation_start_ = 0; + + bool is_ad_resource_ = false; +}; + +// This class is needed to copy a ResourceRequest across threads, because it +// has some members which cannot be transferred across threads (AtomicString +// for example). +// There are some rules / restrictions: +// - This struct cannot contain an object that cannot be transferred across +// threads (e.g., AtomicString) +// - Non-simple members need explicit copying (e.g., String::IsolatedCopy, +// KURL::Copy) rather than the copy constructor or the assignment operator. +struct CrossThreadResourceRequestData { + WTF_MAKE_NONCOPYABLE(CrossThreadResourceRequestData); + USING_FAST_MALLOC(CrossThreadResourceRequestData); + + public: + CrossThreadResourceRequestData() = default; + KURL url_; + + mojom::FetchCacheMode cache_mode_; + double timeout_interval_; + KURL site_for_cookies_; + scoped_refptr<const SecurityOrigin> requestor_origin_; + + String http_method_; + std::unique_ptr<CrossThreadHTTPHeaderMapData> http_headers_; + scoped_refptr<EncodedFormData> http_body_; + bool allow_stored_credentials_; + bool report_upload_progress_; + bool has_user_gesture_; + bool download_to_file_; + bool download_to_blob_; + bool skip_service_worker_; + bool use_stream_on_response_; + bool keepalive_; + bool should_reset_app_cache_; + ResourceLoadPriority priority_; + int intra_priority_value_; + int requestor_id_; + int plugin_child_id_; + int app_cache_host_id_; + WebURLRequest::RequestContext request_context_; + network::mojom::RequestContextFrameType frame_type_; + network::mojom::FetchRequestMode fetch_request_mode_; + network::mojom::FetchCredentialsMode fetch_credentials_mode_; + network::mojom::FetchRedirectMode fetch_redirect_mode_; + String fetch_integrity_; + WebURLRequest::PreviewsState previews_state_; + ReferrerPolicy referrer_policy_; + bool did_set_http_referrer_; + bool check_for_browser_side_navigation_; + double ui_start_time_; + bool is_external_request_; + network::mojom::CORSPreflightPolicy cors_preflight_policy_; + InputToLoadPerfMetricReportPolicy input_perf_metric_report_policy_; + ResourceRequest::RedirectStatus redirect_status_; + base::Optional<String> suggested_filename_; + bool is_ad_resource_; +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_PLATFORM_LOADER_FETCH_RESOURCE_REQUEST_H_ diff --git a/chromium/third_party/blink/renderer/platform/loader/fetch/resource_request_test.cc b/chromium/third_party/blink/renderer/platform/loader/fetch/resource_request_test.cc new file mode 100644 index 00000000000..0c00adaf85c --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/loader/fetch/resource_request_test.cc @@ -0,0 +1,161 @@ +// 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/platform/loader/fetch/resource_request.h" + +#include <memory> +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/blink/public/platform/web_url_request.h" +#include "third_party/blink/renderer/platform/network/encoded_form_data.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/atomic_string.h" + +namespace blink { + +TEST(ResourceRequestTest, CrossThreadResourceRequestData) { + ResourceRequest original; + original.SetURL(KURL("http://www.example.com/test.htm")); + original.SetCacheMode(mojom::FetchCacheMode::kDefault); + original.SetTimeoutInterval(10); + original.SetSiteForCookies(KURL("http://www.example.com/first_party.htm")); + original.SetRequestorOrigin( + SecurityOrigin::Create(KURL("http://www.example.com/first_party.htm"))); + original.SetHTTPMethod(HTTPNames::GET); + original.SetHTTPHeaderField(AtomicString("Foo"), AtomicString("Bar")); + original.SetHTTPHeaderField(AtomicString("Piyo"), AtomicString("Fuga")); + original.SetPriority(ResourceLoadPriority::kLow, 20); + + scoped_refptr<EncodedFormData> original_body( + EncodedFormData::Create("Test Body")); + original.SetHTTPBody(original_body); + original.SetAllowStoredCredentials(false); + original.SetReportUploadProgress(false); + original.SetHasUserGesture(false); + original.SetDownloadToFile(false); + original.SetSkipServiceWorker(false); + original.SetFetchRequestMode(network::mojom::FetchRequestMode::kCORS); + original.SetFetchCredentialsMode( + network::mojom::FetchCredentialsMode::kSameOrigin); + original.SetRequestorID(30); + original.SetPluginChildID(40); + original.SetAppCacheHostID(50); + original.SetRequestContext(WebURLRequest::kRequestContextAudio); + original.SetFrameType(network::mojom::RequestContextFrameType::kNested); + original.SetHTTPReferrer( + Referrer("http://www.example.com/referrer.htm", kReferrerPolicyDefault)); + + EXPECT_STREQ("http://www.example.com/test.htm", + original.Url().GetString().Utf8().data()); + EXPECT_EQ(mojom::FetchCacheMode::kDefault, original.GetCacheMode()); + EXPECT_EQ(10, original.TimeoutInterval()); + EXPECT_STREQ("http://www.example.com/first_party.htm", + original.SiteForCookies().GetString().Utf8().data()); + EXPECT_STREQ("www.example.com", + original.RequestorOrigin()->Host().Utf8().data()); + EXPECT_STREQ("GET", original.HttpMethod().Utf8().data()); + EXPECT_STREQ("Bar", original.HttpHeaderFields().Get("Foo").Utf8().data()); + EXPECT_STREQ("Fuga", original.HttpHeaderFields().Get("Piyo").Utf8().data()); + EXPECT_EQ(ResourceLoadPriority::kLow, original.Priority()); + EXPECT_STREQ("Test Body", + original.HttpBody()->FlattenToString().Utf8().data()); + EXPECT_FALSE(original.AllowStoredCredentials()); + EXPECT_FALSE(original.ReportUploadProgress()); + EXPECT_FALSE(original.HasUserGesture()); + EXPECT_FALSE(original.DownloadToFile()); + EXPECT_FALSE(original.GetSkipServiceWorker()); + EXPECT_EQ(network::mojom::FetchRequestMode::kCORS, + original.GetFetchRequestMode()); + EXPECT_EQ(network::mojom::FetchCredentialsMode::kSameOrigin, + original.GetFetchCredentialsMode()); + EXPECT_EQ(30, original.RequestorID()); + EXPECT_EQ(40, original.GetPluginChildID()); + EXPECT_EQ(50, original.AppCacheHostID()); + EXPECT_EQ(WebURLRequest::kRequestContextAudio, original.GetRequestContext()); + EXPECT_EQ(network::mojom::RequestContextFrameType::kNested, + original.GetFrameType()); + EXPECT_STREQ("http://www.example.com/referrer.htm", + original.HttpReferrer().Utf8().data()); + EXPECT_EQ(kReferrerPolicyDefault, original.GetReferrerPolicy()); + + std::unique_ptr<CrossThreadResourceRequestData> data1(original.CopyData()); + ResourceRequest copy1(data1.get()); + + EXPECT_STREQ("http://www.example.com/test.htm", + copy1.Url().GetString().Utf8().data()); + EXPECT_EQ(mojom::FetchCacheMode::kDefault, copy1.GetCacheMode()); + EXPECT_EQ(10, copy1.TimeoutInterval()); + EXPECT_STREQ("http://www.example.com/first_party.htm", + copy1.SiteForCookies().GetString().Utf8().data()); + EXPECT_STREQ("www.example.com", + copy1.RequestorOrigin()->Host().Utf8().data()); + EXPECT_STREQ("GET", copy1.HttpMethod().Utf8().data()); + EXPECT_STREQ("Bar", copy1.HttpHeaderFields().Get("Foo").Utf8().data()); + EXPECT_EQ(ResourceLoadPriority::kLow, copy1.Priority()); + EXPECT_STREQ("Test Body", copy1.HttpBody()->FlattenToString().Utf8().data()); + EXPECT_FALSE(copy1.AllowStoredCredentials()); + EXPECT_FALSE(copy1.ReportUploadProgress()); + EXPECT_FALSE(copy1.HasUserGesture()); + EXPECT_FALSE(copy1.DownloadToFile()); + EXPECT_FALSE(copy1.GetSkipServiceWorker()); + EXPECT_EQ(network::mojom::FetchRequestMode::kCORS, + copy1.GetFetchRequestMode()); + EXPECT_EQ(network::mojom::FetchCredentialsMode::kSameOrigin, + copy1.GetFetchCredentialsMode()); + EXPECT_EQ(30, copy1.RequestorID()); + EXPECT_EQ(40, copy1.GetPluginChildID()); + EXPECT_EQ(50, copy1.AppCacheHostID()); + EXPECT_EQ(WebURLRequest::kRequestContextAudio, copy1.GetRequestContext()); + EXPECT_EQ(network::mojom::RequestContextFrameType::kNested, + copy1.GetFrameType()); + EXPECT_STREQ("http://www.example.com/referrer.htm", + copy1.HttpReferrer().Utf8().data()); + EXPECT_EQ(kReferrerPolicyDefault, copy1.GetReferrerPolicy()); + + copy1.SetAllowStoredCredentials(true); + copy1.SetReportUploadProgress(true); + copy1.SetHasUserGesture(true); + copy1.SetDownloadToFile(true); + copy1.SetSkipServiceWorker(true); + copy1.SetFetchRequestMode(network::mojom::FetchRequestMode::kNoCORS); + copy1.SetFetchCredentialsMode(network::mojom::FetchCredentialsMode::kInclude); + + std::unique_ptr<CrossThreadResourceRequestData> data2(copy1.CopyData()); + ResourceRequest copy2(data2.get()); + EXPECT_TRUE(copy2.AllowStoredCredentials()); + EXPECT_TRUE(copy2.ReportUploadProgress()); + EXPECT_TRUE(copy2.HasUserGesture()); + EXPECT_TRUE(copy2.DownloadToFile()); + EXPECT_TRUE(copy2.GetSkipServiceWorker()); + EXPECT_EQ(network::mojom::FetchRequestMode::kNoCORS, + copy1.GetFetchRequestMode()); + EXPECT_EQ(network::mojom::FetchCredentialsMode::kInclude, + copy1.GetFetchCredentialsMode()); +} + +TEST(ResourceRequestTest, SetHasUserGesture) { + ResourceRequest original; + EXPECT_FALSE(original.HasUserGesture()); + original.SetHasUserGesture(true); + EXPECT_TRUE(original.HasUserGesture()); + original.SetHasUserGesture(false); + EXPECT_TRUE(original.HasUserGesture()); +} + +TEST(ResourceRequestTest, SetIsAdResource) { + ResourceRequest original; + EXPECT_FALSE(original.IsAdResource()); + original.SetIsAdResource(); + EXPECT_TRUE(original.IsAdResource()); + + // Should persist across redirects. + std::unique_ptr<ResourceRequest> redirect_request = + original.CreateRedirectRequest( + KURL("https://example.test/redirect"), original.HttpMethod(), + original.SiteForCookies(), original.HttpReferrer(), + original.GetReferrerPolicy(), original.GetSkipServiceWorker()); + EXPECT_TRUE(redirect_request->IsAdResource()); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/platform/loader/fetch/resource_response.cc b/chromium/third_party/blink/renderer/platform/loader/fetch/resource_response.cc new file mode 100644 index 00000000000..6e6281a18b5 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/loader/fetch/resource_response.cc @@ -0,0 +1,625 @@ +/* + * Copyright (C) 2006, 2008 Apple Inc. All rights reserved. + * 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: + * 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/platform/loader/fetch/resource_response.h" + +#include <algorithm> +#include <limits> +#include <memory> +#include <string> + +#include "third_party/blink/public/platform/web_url_response.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/wtf/assertions.h" +#include "third_party/blink/renderer/platform/wtf/std_lib_extras.h" +#include "third_party/blink/renderer/platform/wtf/time.h" + +namespace blink { + +namespace { + +template <typename Interface> +Vector<Interface> IsolatedCopy(const Vector<Interface>& src) { + Vector<Interface> result; + result.ReserveCapacity(src.size()); + for (const auto& timestamp : src) { + result.push_back(timestamp.IsolatedCopy()); + } + return result; +} + +static const char kCacheControlHeader[] = "cache-control"; +static const char kPragmaHeader[] = "pragma"; + +} // namespace + +ResourceResponse::SignedCertificateTimestamp::SignedCertificateTimestamp( + const blink::WebURLResponse::SignedCertificateTimestamp& sct) + : status_(sct.status), + origin_(sct.origin), + log_description_(sct.log_description), + log_id_(sct.log_id), + timestamp_(sct.timestamp), + hash_algorithm_(sct.hash_algorithm), + signature_algorithm_(sct.signature_algorithm), + signature_data_(sct.signature_data) {} + +ResourceResponse::SignedCertificateTimestamp +ResourceResponse::SignedCertificateTimestamp::IsolatedCopy() const { + return SignedCertificateTimestamp( + status_.IsolatedCopy(), origin_.IsolatedCopy(), + log_description_.IsolatedCopy(), log_id_.IsolatedCopy(), timestamp_, + hash_algorithm_.IsolatedCopy(), signature_algorithm_.IsolatedCopy(), + signature_data_.IsolatedCopy()); +} + +ResourceResponse::ResourceResponse() + : expected_content_length_(0), is_null_(true) {} + +ResourceResponse::ResourceResponse(const KURL& url, + const AtomicString& mime_type, + long long expected_length, + const AtomicString& text_encoding_name) + : url_(url), + mime_type_(mime_type), + expected_content_length_(expected_length), + text_encoding_name_(text_encoding_name), + is_null_(false) {} + +ResourceResponse::ResourceResponse(CrossThreadResourceResponseData* data) + : ResourceResponse() { + SetURL(data->url_); + SetMimeType(AtomicString(data->mime_type_)); + SetExpectedContentLength(data->expected_content_length_); + SetTextEncodingName(AtomicString(data->text_encoding_name_)); + + SetHTTPStatusCode(data->http_status_code_); + SetHTTPStatusText(AtomicString(data->http_status_text_)); + + http_header_fields_.Adopt(std::move(data->http_headers_)); + SetResourceLoadTiming(std::move(data->resource_load_timing_)); + remote_ip_address_ = AtomicString(data->remote_ip_address_); + remote_port_ = data->remote_port_; + has_major_certificate_errors_ = data->has_major_certificate_errors_; + ct_policy_compliance_ = data->ct_policy_compliance_; + is_legacy_symantec_cert_ = data->is_legacy_symantec_cert_; + cert_validity_start_ = data->cert_validity_start_; + was_fetched_via_spdy_ = data->was_fetched_via_spdy_; + was_fetched_via_proxy_ = data->was_fetched_via_proxy_; + was_fetched_via_service_worker_ = data->was_fetched_via_service_worker_; + was_fallback_required_by_service_worker_ = + data->was_fallback_required_by_service_worker_; + did_service_worker_navigation_preload_ = + data->did_service_worker_navigation_preload_; + response_type_via_service_worker_ = data->response_type_via_service_worker_; + security_style_ = data->security_style_; + security_details_.protocol = data->security_details_.protocol; + security_details_.cipher = data->security_details_.cipher; + security_details_.key_exchange = data->security_details_.key_exchange; + security_details_.key_exchange_group = + data->security_details_.key_exchange_group; + security_details_.mac = data->security_details_.mac; + security_details_.subject_name = data->security_details_.subject_name; + security_details_.san_list = data->security_details_.san_list; + security_details_.issuer = data->security_details_.issuer; + security_details_.valid_from = data->security_details_.valid_from; + security_details_.valid_to = data->security_details_.valid_to; + for (auto& cert : data->certificate_) + security_details_.certificate.push_back(AtomicString(cert)); + security_details_.sct_list = data->security_details_.sct_list; + http_version_ = data->http_version_; + app_cache_id_ = data->app_cache_id_; + app_cache_manifest_url_ = data->app_cache_manifest_url_.Copy(); + multipart_boundary_ = data->multipart_boundary_; + url_list_via_service_worker_ = data->url_list_via_service_worker_; + cache_storage_cache_name_ = data->cache_storage_cache_name_; + response_time_ = data->response_time_; + encoded_data_length_ = data->encoded_data_length_; + encoded_body_length_ = data->encoded_body_length_; + decoded_body_length_ = data->decoded_body_length_; + downloaded_file_path_ = data->downloaded_file_path_; + downloaded_file_handle_ = data->downloaded_file_handle_; + + // Bug https://bugs.webkit.org/show_bug.cgi?id=60397 this doesn't support + // whatever values may be present in the opaque m_extraData structure. +} + +ResourceResponse::ResourceResponse(const ResourceResponse&) = default; +ResourceResponse& ResourceResponse::operator=(const ResourceResponse&) = + default; + +std::unique_ptr<CrossThreadResourceResponseData> ResourceResponse::CopyData() + const { + std::unique_ptr<CrossThreadResourceResponseData> data = + std::make_unique<CrossThreadResourceResponseData>(); + data->url_ = Url().Copy(); + data->mime_type_ = MimeType().GetString().IsolatedCopy(); + data->expected_content_length_ = ExpectedContentLength(); + data->text_encoding_name_ = TextEncodingName().GetString().IsolatedCopy(); + data->http_status_code_ = HttpStatusCode(); + data->http_status_text_ = HttpStatusText().GetString().IsolatedCopy(); + data->http_headers_ = HttpHeaderFields().CopyData(); + if (resource_load_timing_) + data->resource_load_timing_ = resource_load_timing_->DeepCopy(); + data->remote_ip_address_ = remote_ip_address_.GetString().IsolatedCopy(); + data->remote_port_ = remote_port_; + data->has_major_certificate_errors_ = has_major_certificate_errors_; + data->ct_policy_compliance_ = ct_policy_compliance_; + data->is_legacy_symantec_cert_ = is_legacy_symantec_cert_; + data->cert_validity_start_ = cert_validity_start_; + data->was_fetched_via_spdy_ = was_fetched_via_spdy_; + data->was_fetched_via_proxy_ = was_fetched_via_proxy_; + data->was_fetched_via_service_worker_ = was_fetched_via_service_worker_; + data->was_fallback_required_by_service_worker_ = + was_fallback_required_by_service_worker_; + data->did_service_worker_navigation_preload_ = + did_service_worker_navigation_preload_; + data->response_type_via_service_worker_ = response_type_via_service_worker_; + data->security_style_ = security_style_; + data->security_details_.protocol = security_details_.protocol.IsolatedCopy(); + data->security_details_.cipher = security_details_.cipher.IsolatedCopy(); + data->security_details_.key_exchange = + security_details_.key_exchange.IsolatedCopy(); + data->security_details_.key_exchange_group = + security_details_.key_exchange_group.IsolatedCopy(); + data->security_details_.mac = security_details_.mac.IsolatedCopy(); + data->security_details_.subject_name = + security_details_.subject_name.IsolatedCopy(); + data->security_details_.san_list = IsolatedCopy(security_details_.san_list); + data->security_details_.issuer = security_details_.issuer.IsolatedCopy(); + data->security_details_.valid_from = security_details_.valid_from; + data->security_details_.valid_to = security_details_.valid_to; + for (auto& cert : security_details_.certificate) + data->certificate_.push_back(cert.GetString().IsolatedCopy()); + data->security_details_.sct_list = IsolatedCopy(security_details_.sct_list); + data->http_version_ = http_version_; + data->app_cache_id_ = app_cache_id_; + data->app_cache_manifest_url_ = app_cache_manifest_url_.Copy(); + data->multipart_boundary_ = multipart_boundary_; + data->url_list_via_service_worker_.resize( + url_list_via_service_worker_.size()); + std::transform(url_list_via_service_worker_.begin(), + url_list_via_service_worker_.end(), + data->url_list_via_service_worker_.begin(), + [](const KURL& url) { return url.Copy(); }); + data->cache_storage_cache_name_ = CacheStorageCacheName().IsolatedCopy(); + data->response_time_ = response_time_; + data->encoded_data_length_ = encoded_data_length_; + data->encoded_body_length_ = encoded_body_length_; + data->decoded_body_length_ = decoded_body_length_; + data->downloaded_file_path_ = downloaded_file_path_.IsolatedCopy(); + data->downloaded_file_handle_ = downloaded_file_handle_; + + // Bug https://bugs.webkit.org/show_bug.cgi?id=60397 this doesn't support + // whatever values may be present in the opaque m_extraData structure. + + return data; +} + +bool ResourceResponse::IsHTTP() const { + return url_.ProtocolIsInHTTPFamily(); +} + +const KURL& ResourceResponse::Url() const { + return url_; +} + +void ResourceResponse::SetURL(const KURL& url) { + is_null_ = false; + + url_ = url; +} + +const AtomicString& ResourceResponse::MimeType() const { + return mime_type_; +} + +void ResourceResponse::SetMimeType(const AtomicString& mime_type) { + is_null_ = false; + + // FIXME: MIME type is determined by HTTP Content-Type header. We should + // update the header, so that it doesn't disagree with m_mimeType. + mime_type_ = mime_type; +} + +long long ResourceResponse::ExpectedContentLength() const { + return expected_content_length_; +} + +void ResourceResponse::SetExpectedContentLength( + long long expected_content_length) { + is_null_ = false; + + // FIXME: Content length is determined by HTTP Content-Length header. We + // should update the header, so that it doesn't disagree with + // m_expectedContentLength. + expected_content_length_ = expected_content_length; +} + +const AtomicString& ResourceResponse::TextEncodingName() const { + return text_encoding_name_; +} + +void ResourceResponse::SetTextEncodingName(const AtomicString& encoding_name) { + is_null_ = false; + + // FIXME: Text encoding is determined by HTTP Content-Type header. We should + // update the header, so that it doesn't disagree with m_textEncodingName. + text_encoding_name_ = encoding_name; +} + +int ResourceResponse::HttpStatusCode() const { + return http_status_code_; +} + +void ResourceResponse::SetHTTPStatusCode(int status_code) { + http_status_code_ = status_code; +} + +const AtomicString& ResourceResponse::HttpStatusText() const { + return http_status_text_; +} + +void ResourceResponse::SetHTTPStatusText(const AtomicString& status_text) { + http_status_text_ = status_text; +} + +const AtomicString& ResourceResponse::HttpHeaderField( + const AtomicString& name) const { + return http_header_fields_.Get(name); +} + +void ResourceResponse::UpdateHeaderParsedState(const AtomicString& name) { + static const char kAgeHeader[] = "age"; + static const char kDateHeader[] = "date"; + static const char kExpiresHeader[] = "expires"; + static const char kLastModifiedHeader[] = "last-modified"; + + if (DeprecatedEqualIgnoringCase(name, kAgeHeader)) + have_parsed_age_header_ = false; + else if (DeprecatedEqualIgnoringCase(name, kCacheControlHeader) || + DeprecatedEqualIgnoringCase(name, kPragmaHeader)) + cache_control_header_ = CacheControlHeader(); + else if (DeprecatedEqualIgnoringCase(name, kDateHeader)) + have_parsed_date_header_ = false; + else if (DeprecatedEqualIgnoringCase(name, kExpiresHeader)) + have_parsed_expires_header_ = false; + else if (DeprecatedEqualIgnoringCase(name, kLastModifiedHeader)) + have_parsed_last_modified_header_ = false; +} + +void ResourceResponse::SetSecurityDetails( + const String& protocol, + const String& key_exchange, + const String& key_exchange_group, + const String& cipher, + const String& mac, + const String& subject_name, + const Vector<String>& san_list, + const String& issuer, + time_t valid_from, + time_t valid_to, + const Vector<AtomicString>& certificate, + const SignedCertificateTimestampList& sct_list) { + security_details_.protocol = protocol; + security_details_.key_exchange = key_exchange; + security_details_.key_exchange_group = key_exchange_group; + security_details_.cipher = cipher; + security_details_.mac = mac; + security_details_.subject_name = subject_name; + security_details_.san_list = san_list; + security_details_.issuer = issuer; + security_details_.valid_from = valid_from; + security_details_.valid_to = valid_to; + security_details_.certificate = certificate; + security_details_.sct_list = sct_list; +} + +void ResourceResponse::SetHTTPHeaderField(const AtomicString& name, + const AtomicString& value) { + UpdateHeaderParsedState(name); + + http_header_fields_.Set(name, value); +} + +void ResourceResponse::AddHTTPHeaderField(const AtomicString& name, + const AtomicString& value) { + UpdateHeaderParsedState(name); + + HTTPHeaderMap::AddResult result = http_header_fields_.Add(name, value); + if (!result.is_new_entry) + result.stored_value->value = result.stored_value->value + ", " + value; +} + +void ResourceResponse::ClearHTTPHeaderField(const AtomicString& name) { + http_header_fields_.Remove(name); +} + +const HTTPHeaderMap& ResourceResponse::HttpHeaderFields() const { + return http_header_fields_; +} + +bool ResourceResponse::CacheControlContainsNoCache() const { + if (!cache_control_header_.parsed) { + cache_control_header_ = ParseCacheControlDirectives( + http_header_fields_.Get(kCacheControlHeader), + http_header_fields_.Get(kPragmaHeader)); + } + return cache_control_header_.contains_no_cache; +} + +bool ResourceResponse::CacheControlContainsNoStore() const { + if (!cache_control_header_.parsed) { + cache_control_header_ = ParseCacheControlDirectives( + http_header_fields_.Get(kCacheControlHeader), + http_header_fields_.Get(kPragmaHeader)); + } + return cache_control_header_.contains_no_store; +} + +bool ResourceResponse::CacheControlContainsMustRevalidate() const { + if (!cache_control_header_.parsed) { + cache_control_header_ = ParseCacheControlDirectives( + http_header_fields_.Get(kCacheControlHeader), + http_header_fields_.Get(kPragmaHeader)); + } + return cache_control_header_.contains_must_revalidate; +} + +bool ResourceResponse::HasCacheValidatorFields() const { + static const char kLastModifiedHeader[] = "last-modified"; + static const char kETagHeader[] = "etag"; + return !http_header_fields_.Get(kLastModifiedHeader).IsEmpty() || + !http_header_fields_.Get(kETagHeader).IsEmpty(); +} + +double ResourceResponse::CacheControlMaxAge() const { + if (!cache_control_header_.parsed) { + cache_control_header_ = ParseCacheControlDirectives( + http_header_fields_.Get(kCacheControlHeader), + http_header_fields_.Get(kPragmaHeader)); + } + return cache_control_header_.max_age; +} + +static double ParseDateValueInHeader(const HTTPHeaderMap& headers, + const AtomicString& header_name) { + const AtomicString& header_value = headers.Get(header_name); + if (header_value.IsEmpty()) + return std::numeric_limits<double>::quiet_NaN(); + // This handles all date formats required by RFC2616: + // Sun, 06 Nov 1994 08:49:37 GMT ; RFC 822, updated by RFC 1123 + // Sunday, 06-Nov-94 08:49:37 GMT ; RFC 850, obsoleted by RFC 1036 + // Sun Nov 6 08:49:37 1994 ; ANSI C's asctime() format + double date_in_milliseconds = ParseDate(header_value); + if (!std::isfinite(date_in_milliseconds)) + return std::numeric_limits<double>::quiet_NaN(); + return date_in_milliseconds / 1000; +} + +double ResourceResponse::Date() const { + if (!have_parsed_date_header_) { + static const char kHeaderName[] = "date"; + date_ = ParseDateValueInHeader(http_header_fields_, kHeaderName); + have_parsed_date_header_ = true; + } + return date_; +} + +double ResourceResponse::Age() const { + if (!have_parsed_age_header_) { + static const char kHeaderName[] = "age"; + const AtomicString& header_value = http_header_fields_.Get(kHeaderName); + bool ok; + age_ = header_value.ToDouble(&ok); + if (!ok) + age_ = std::numeric_limits<double>::quiet_NaN(); + have_parsed_age_header_ = true; + } + return age_; +} + +double ResourceResponse::Expires() const { + if (!have_parsed_expires_header_) { + static const char kHeaderName[] = "expires"; + expires_ = ParseDateValueInHeader(http_header_fields_, kHeaderName); + have_parsed_expires_header_ = true; + } + return expires_; +} + +double ResourceResponse::LastModified() const { + if (!have_parsed_last_modified_header_) { + static const char kHeaderName[] = "last-modified"; + last_modified_ = ParseDateValueInHeader(http_header_fields_, kHeaderName); + have_parsed_last_modified_header_ = true; + } + return last_modified_; +} + +bool ResourceResponse::IsAttachment() const { + static const char kAttachmentString[] = "attachment"; + String value = http_header_fields_.Get(HTTPNames::Content_Disposition); + size_t loc = value.find(';'); + if (loc != kNotFound) + value = value.Left(loc); + value = value.StripWhiteSpace(); + return DeprecatedEqualIgnoringCase(value, kAttachmentString); +} + +AtomicString ResourceResponse::HttpContentType() const { + return ExtractMIMETypeFromMediaType( + HttpHeaderField(HTTPNames::Content_Type).DeprecatedLower()); +} + +bool ResourceResponse::WasCached() const { + return was_cached_; +} + +void ResourceResponse::SetWasCached(bool value) { + was_cached_ = value; +} + +bool ResourceResponse::ConnectionReused() const { + return connection_reused_; +} + +void ResourceResponse::SetConnectionReused(bool connection_reused) { + connection_reused_ = connection_reused; +} + +unsigned ResourceResponse::ConnectionID() const { + return connection_id_; +} + +void ResourceResponse::SetConnectionID(unsigned connection_id) { + connection_id_ = connection_id; +} + +ResourceLoadTiming* ResourceResponse::GetResourceLoadTiming() const { + return resource_load_timing_.get(); +} + +void ResourceResponse::SetResourceLoadTiming( + scoped_refptr<ResourceLoadTiming> resource_load_timing) { + resource_load_timing_ = std::move(resource_load_timing); +} + +scoped_refptr<ResourceLoadInfo> ResourceResponse::GetResourceLoadInfo() const { + return resource_load_info_.get(); +} + +void ResourceResponse::SetResourceLoadInfo( + scoped_refptr<ResourceLoadInfo> load_info) { + resource_load_info_ = std::move(load_info); +} + +void ResourceResponse::SetCTPolicyCompliance(CTPolicyCompliance compliance) { + ct_policy_compliance_ = compliance; +} + +bool ResourceResponse::IsOpaqueResponseFromServiceWorker() const { + switch (response_type_via_service_worker_) { + case network::mojom::FetchResponseType::kBasic: + case network::mojom::FetchResponseType::kCORS: + case network::mojom::FetchResponseType::kDefault: + case network::mojom::FetchResponseType::kError: + return false; + case network::mojom::FetchResponseType::kOpaque: + case network::mojom::FetchResponseType::kOpaqueRedirect: + return true; + } + NOTREACHED(); + return false; +} + +KURL ResourceResponse::OriginalURLViaServiceWorker() const { + if (url_list_via_service_worker_.IsEmpty()) + return KURL(); + return url_list_via_service_worker_.back(); +} + +AtomicString ResourceResponse::ConnectionInfoString() const { + std::string connection_info_string = + net::HttpResponseInfo::ConnectionInfoToString(connection_info_); + return AtomicString( + reinterpret_cast<const LChar*>(connection_info_string.data()), + connection_info_string.length()); +} + +void ResourceResponse::SetEncodedDataLength(long long value) { + encoded_data_length_ = value; +} + +void ResourceResponse::SetEncodedBodyLength(long long value) { + encoded_body_length_ = value; +} + +void ResourceResponse::SetDecodedBodyLength(long long value) { + decoded_body_length_ = value; +} + +void ResourceResponse::SetDownloadedFilePath( + const String& downloaded_file_path) { + downloaded_file_path_ = downloaded_file_path; + if (downloaded_file_path_.IsEmpty()) { + downloaded_file_handle_ = nullptr; + return; + } + // TODO(dmurph): Investigate whether we need the mimeType on this blob. + std::unique_ptr<BlobData> blob_data = + BlobData::CreateForFileWithUnknownSize(downloaded_file_path_); + blob_data->DetachFromCurrentThread(); + downloaded_file_handle_ = BlobDataHandle::Create(std::move(blob_data), -1); +} + +void ResourceResponse::AppendRedirectResponse( + const ResourceResponse& response) { + redirect_responses_.push_back(response); +} + +bool ResourceResponse::Compare(const ResourceResponse& a, + const ResourceResponse& b) { + if (a.IsNull() != b.IsNull()) + return false; + if (a.Url() != b.Url()) + return false; + if (a.MimeType() != b.MimeType()) + return false; + if (a.ExpectedContentLength() != b.ExpectedContentLength()) + return false; + if (a.TextEncodingName() != b.TextEncodingName()) + return false; + if (a.HttpStatusCode() != b.HttpStatusCode()) + return false; + if (a.HttpStatusText() != b.HttpStatusText()) + return false; + if (a.HttpHeaderFields() != b.HttpHeaderFields()) + return false; + if (a.GetResourceLoadTiming() && b.GetResourceLoadTiming() && + *a.GetResourceLoadTiming() == *b.GetResourceLoadTiming()) + return true; + if (a.GetResourceLoadTiming() != b.GetResourceLoadTiming()) + return false; + if (a.EncodedBodyLength() != b.EncodedBodyLength()) + return false; + if (a.DecodedBodyLength() != b.DecodedBodyLength()) + return false; + return true; +} + +STATIC_ASSERT_ENUM(WebURLResponse::kHTTPVersionUnknown, + ResourceResponse::kHTTPVersionUnknown); +STATIC_ASSERT_ENUM(WebURLResponse::kHTTPVersion_0_9, + ResourceResponse::kHTTPVersion_0_9); +STATIC_ASSERT_ENUM(WebURLResponse::kHTTPVersion_1_0, + ResourceResponse::kHTTPVersion_1_0); +STATIC_ASSERT_ENUM(WebURLResponse::kHTTPVersion_1_1, + ResourceResponse::kHTTPVersion_1_1); +STATIC_ASSERT_ENUM(WebURLResponse::kHTTPVersion_2_0, + ResourceResponse::kHTTPVersion_2_0); +} // namespace blink diff --git a/chromium/third_party/blink/renderer/platform/loader/fetch/resource_response.h b/chromium/third_party/blink/renderer/platform/loader/fetch/resource_response.h new file mode 100644 index 00000000000..d25f83dfaba --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/loader/fetch/resource_response.h @@ -0,0 +1,625 @@ +/* + * Copyright (C) 2006, 2008 Apple Inc. All rights reserved. + * 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: + * 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_PLATFORM_LOADER_FETCH_RESOURCE_RESPONSE_H_ +#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_LOADER_FETCH_RESOURCE_RESPONSE_H_ + +#include <memory> +#include <utility> + +#include "base/memory/scoped_refptr.h" +#include "base/time/time.h" +#include "services/network/public/mojom/fetch_api.mojom-blink.h" +#include "third_party/blink/public/platform/web_url_response.h" +#include "third_party/blink/renderer/platform/blob/blob_data.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource_load_info.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource_load_timing.h" +#include "third_party/blink/renderer/platform/network/http_header_map.h" +#include "third_party/blink/renderer/platform/network/http_parsers.h" +#include "third_party/blink/renderer/platform/platform_export.h" +#include "third_party/blink/renderer/platform/weborigin/kurl.h" +#include "third_party/blink/renderer/platform/wtf/noncopyable.h" +#include "third_party/blink/renderer/platform/wtf/ref_counted.h" +#include "third_party/blink/renderer/platform/wtf/text/cstring.h" +#include "third_party/blink/renderer/platform/wtf/time.h" +#include "third_party/blink/renderer/platform/wtf/vector.h" + +namespace blink { + +struct CrossThreadResourceResponseData; + +// A ResourceResponse is a "response" object used in blink. Conceptually +// it is https://fetch.spec.whatwg.org/#concept-response, but it contains +// a lot of blink specific fields. WebURLResponse is the "public version" +// of this class and public classes (i.e., classes in public/platform) use it. +// +// There are cases where we need to copy a response across threads, and +// CrossThreadResourceResponseData is a struct for the purpose. When you add a +// member variable to this class, do not forget to add the corresponding +// one in CrossThreadResourceResponseData and write copying logic. +class PLATFORM_EXPORT ResourceResponse final { + DISALLOW_NEW_EXCEPT_PLACEMENT_NEW(); + + public: + enum HTTPVersion : uint8_t { + kHTTPVersionUnknown, + kHTTPVersion_0_9, + kHTTPVersion_1_0, + kHTTPVersion_1_1, + kHTTPVersion_2_0 + }; + enum SecurityStyle : uint8_t { + kSecurityStyleUnknown, + kSecurityStyleUnauthenticated, + kSecurityStyleAuthenticationBroken, + kSecurityStyleAuthenticated + }; + + enum CTPolicyCompliance { + kCTPolicyComplianceDetailsNotAvailable, + kCTPolicyComplies, + kCTPolicyDoesNotComply + }; + + class PLATFORM_EXPORT SignedCertificateTimestamp final { + public: + SignedCertificateTimestamp(String status, + String origin, + String log_description, + String log_id, + int64_t timestamp, + String hash_algorithm, + String signature_algorithm, + String signature_data) + : status_(status), + origin_(origin), + log_description_(log_description), + log_id_(log_id), + timestamp_(timestamp), + hash_algorithm_(hash_algorithm), + signature_algorithm_(signature_algorithm), + signature_data_(signature_data) {} + explicit SignedCertificateTimestamp( + const struct blink::WebURLResponse::SignedCertificateTimestamp&); + SignedCertificateTimestamp IsolatedCopy() const; + + String status_; + String origin_; + String log_description_; + String log_id_; + int64_t timestamp_; + String hash_algorithm_; + String signature_algorithm_; + String signature_data_; + }; + + using SignedCertificateTimestampList = + WTF::Vector<SignedCertificateTimestamp>; + + struct SecurityDetails { + DISALLOW_NEW(); + SecurityDetails() : valid_from(0), valid_to(0) {} + // All strings are human-readable values. + String protocol; + // keyExchange is the empty string if not applicable for the connection's + // protocol. + String key_exchange; + // keyExchangeGroup is the empty string if not applicable for the + // connection's key exchange. + String key_exchange_group; + String cipher; + // mac is the empty string when the connection cipher suite does not + // have a separate MAC value (i.e. if the cipher suite is AEAD). + String mac; + String subject_name; + Vector<String> san_list; + String issuer; + time_t valid_from; + time_t valid_to; + // DER-encoded X509Certificate certificate chain. + Vector<AtomicString> certificate; + SignedCertificateTimestampList sct_list; + }; + + class ExtraData : public RefCounted<ExtraData> { + public: + virtual ~ExtraData() = default; + }; + + explicit ResourceResponse(CrossThreadResourceResponseData*); + + // Gets a copy of the data suitable for passing to another thread. + std::unique_ptr<CrossThreadResourceResponseData> CopyData() const; + + ResourceResponse(); + explicit ResourceResponse( + const KURL&, + const AtomicString& mime_type = g_null_atom, + long long expected_length = 0, + const AtomicString& text_encoding_name = g_null_atom); + ResourceResponse(const ResourceResponse&); + ResourceResponse& operator=(const ResourceResponse&); + + bool IsNull() const { return is_null_; } + bool IsHTTP() const; + + // The URL of the resource. Note that if a service worker responded to the + // request for this resource, it may have fetched an entirely different URL + // and responded with that resource. wasFetchedViaServiceWorker() and + // originalURLViaServiceWorker() can be used to determine whether and how a + // service worker responded to the request. Example service worker code: + // + // onfetch = (event => { + // if (event.request.url == 'https://abc.com') + // event.respondWith(fetch('https://def.com')); + // }); + // + // If this service worker responds to an "https://abc.com" request, then for + // the resulting ResourceResponse, url() is "https://abc.com", + // wasFetchedViaServiceWorker() is true, and originalURLViaServiceWorker() is + // "https://def.com". + const KURL& Url() const; + void SetURL(const KURL&); + + const AtomicString& MimeType() const; + void SetMimeType(const AtomicString&); + + long long ExpectedContentLength() const; + void SetExpectedContentLength(long long); + + const AtomicString& TextEncodingName() const; + void SetTextEncodingName(const AtomicString&); + + int HttpStatusCode() const; + void SetHTTPStatusCode(int); + + const AtomicString& HttpStatusText() const; + void SetHTTPStatusText(const AtomicString&); + + const AtomicString& HttpHeaderField(const AtomicString& name) const; + void SetHTTPHeaderField(const AtomicString& name, const AtomicString& value); + void AddHTTPHeaderField(const AtomicString& name, const AtomicString& value); + void ClearHTTPHeaderField(const AtomicString& name); + const HTTPHeaderMap& HttpHeaderFields() const; + + bool IsMultipart() const { return MimeType() == "multipart/x-mixed-replace"; } + + bool IsAttachment() const; + + AtomicString HttpContentType() const; + + // These functions return parsed values of the corresponding response headers. + // NaN means that the header was not present or had invalid value. + bool CacheControlContainsNoCache() const; + bool CacheControlContainsNoStore() const; + bool CacheControlContainsMustRevalidate() const; + bool HasCacheValidatorFields() const; + double CacheControlMaxAge() const; + double Date() const; + double Age() const; + double Expires() const; + double LastModified() const; + + unsigned ConnectionID() const; + void SetConnectionID(unsigned); + + bool ConnectionReused() const; + void SetConnectionReused(bool); + + bool WasCached() const; + void SetWasCached(bool); + + ResourceLoadTiming* GetResourceLoadTiming() const; + void SetResourceLoadTiming(scoped_refptr<ResourceLoadTiming>); + + scoped_refptr<ResourceLoadInfo> GetResourceLoadInfo() const; + void SetResourceLoadInfo(scoped_refptr<ResourceLoadInfo>); + + HTTPVersion HttpVersion() const { return http_version_; } + void SetHTTPVersion(HTTPVersion version) { http_version_ = version; } + + bool HasMajorCertificateErrors() const { + return has_major_certificate_errors_; + } + void SetHasMajorCertificateErrors(bool has_major_certificate_errors) { + has_major_certificate_errors_ = has_major_certificate_errors; + } + + CTPolicyCompliance GetCTPolicyCompliance() const { + return ct_policy_compliance_; + } + void SetCTPolicyCompliance(CTPolicyCompliance); + + bool IsLegacySymantecCert() const { return is_legacy_symantec_cert_; } + void SetIsLegacySymantecCert(bool is_legacy_symantec_cert) { + is_legacy_symantec_cert_ = is_legacy_symantec_cert; + } + + SecurityStyle GetSecurityStyle() const { return security_style_; } + void SetSecurityStyle(SecurityStyle security_style) { + security_style_ = security_style; + } + + const SecurityDetails* GetSecurityDetails() const { + return &security_details_; + } + void SetSecurityDetails(const String& protocol, + const String& key_exchange, + const String& key_exchange_group, + const String& cipher, + const String& mac, + const String& subject_name, + const Vector<String>& san_list, + const String& issuer, + time_t valid_from, + time_t valid_to, + const Vector<AtomicString>& certificate, + const SignedCertificateTimestampList& sct_list); + + long long AppCacheID() const { return app_cache_id_; } + void SetAppCacheID(long long id) { app_cache_id_ = id; } + + const KURL& AppCacheManifestURL() const { return app_cache_manifest_url_; } + void SetAppCacheManifestURL(const KURL& url) { + app_cache_manifest_url_ = url; + } + + bool WasFetchedViaSPDY() const { return was_fetched_via_spdy_; } + void SetWasFetchedViaSPDY(bool value) { was_fetched_via_spdy_ = value; } + + // See ServiceWorkerResponseInfo::was_fetched_via_service_worker. + bool WasFetchedViaServiceWorker() const { + return was_fetched_via_service_worker_; + } + void SetWasFetchedViaServiceWorker(bool value) { + was_fetched_via_service_worker_ = value; + } + + // See ServiceWorkerResponseInfo::was_fallback_required. + bool WasFallbackRequiredByServiceWorker() const { + return was_fallback_required_by_service_worker_; + } + void SetWasFallbackRequiredByServiceWorker(bool value) { + was_fallback_required_by_service_worker_ = value; + } + + network::mojom::FetchResponseType ResponseTypeViaServiceWorker() const { + return response_type_via_service_worker_; + } + void SetResponseTypeViaServiceWorker( + network::mojom::FetchResponseType value) { + response_type_via_service_worker_ = value; + } + bool IsOpaqueResponseFromServiceWorker() const; + + // See ServiceWorkerResponseInfo::url_list_via_service_worker. + const Vector<KURL>& UrlListViaServiceWorker() const { + return url_list_via_service_worker_; + } + void SetURLListViaServiceWorker(const Vector<KURL>& url_list) { + url_list_via_service_worker_ = url_list; + } + + // Returns the last URL of urlListViaServiceWorker if exists. Otherwise + // returns an empty URL. + KURL OriginalURLViaServiceWorker() const; + + const Vector<char>& MultipartBoundary() const { return multipart_boundary_; } + void SetMultipartBoundary(const char* bytes, size_t size) { + multipart_boundary_.clear(); + multipart_boundary_.Append(bytes, size); + } + + const String& CacheStorageCacheName() const { + return cache_storage_cache_name_; + } + void SetCacheStorageCacheName(const String& cache_storage_cache_name) { + cache_storage_cache_name_ = cache_storage_cache_name; + } + + const Vector<String>& CorsExposedHeaderNames() const { + return cors_exposed_header_names_; + } + void SetCorsExposedHeaderNames(const Vector<String>& header_names) { + cors_exposed_header_names_ = header_names; + } + + bool DidServiceWorkerNavigationPreload() const { + return did_service_worker_navigation_preload_; + } + void SetDidServiceWorkerNavigationPreload(bool value) { + did_service_worker_navigation_preload_ = value; + } + + Time ResponseTime() const { return response_time_; } + void SetResponseTime(Time response_time) { response_time_ = response_time; } + + const AtomicString& RemoteIPAddress() const { return remote_ip_address_; } + void SetRemoteIPAddress(const AtomicString& value) { + remote_ip_address_ = value; + } + + unsigned short RemotePort() const { return remote_port_; } + void SetRemotePort(unsigned short value) { remote_port_ = value; } + + const AtomicString& AlpnNegotiatedProtocol() const { + return alpn_negotiated_protocol_; + } + void SetAlpnNegotiatedProtocol(const AtomicString& value) { + alpn_negotiated_protocol_ = value; + } + + net::HttpResponseInfo::ConnectionInfo ConnectionInfo() const { + return connection_info_; + } + void SetConnectionInfo(net::HttpResponseInfo::ConnectionInfo value) { + connection_info_ = value; + } + + AtomicString ConnectionInfoString() const; + + long long EncodedDataLength() const { return encoded_data_length_; } + void SetEncodedDataLength(long long value); + + long long EncodedBodyLength() const { return encoded_body_length_; } + void SetEncodedBodyLength(long long value); + + long long DecodedBodyLength() const { return decoded_body_length_; } + void SetDecodedBodyLength(long long value); + + const String& DownloadedFilePath() const { return downloaded_file_path_; } + void SetDownloadedFilePath(const String&); + + // Extra data associated with this response. + ExtraData* GetExtraData() const { return extra_data_.get(); } + void SetExtraData(scoped_refptr<ExtraData> extra_data) { + extra_data_ = std::move(extra_data); + } + + unsigned MemoryUsage() const { + // average size, mostly due to URL and Header Map strings + return 1280; + } + + // PlzNavigate: Even if there is redirections, only one + // ResourceResponse is built: the final response. + // The redirect response chain can be accessed by this function. + const Vector<ResourceResponse>& RedirectResponses() const { + return redirect_responses_; + } + void AppendRedirectResponse(const ResourceResponse&); + + // This method doesn't compare the all members. + static bool Compare(const ResourceResponse&, const ResourceResponse&); + + private: + void UpdateHeaderParsedState(const AtomicString& name); + + KURL url_; + AtomicString mime_type_; + long long expected_content_length_; + AtomicString text_encoding_name_; + + unsigned connection_id_ = 0; + int http_status_code_ = 0; + AtomicString http_status_text_; + HTTPHeaderMap http_header_fields_; + + // Remote IP address of the socket which fetched this resource. + AtomicString remote_ip_address_; + + // Remote port number of the socket which fetched this resource. + unsigned short remote_port_ = 0; + + bool was_cached_ = false; + bool connection_reused_ = false; + bool is_null_; + mutable bool have_parsed_age_header_ = false; + mutable bool have_parsed_date_header_ = false; + mutable bool have_parsed_expires_header_ = false; + mutable bool have_parsed_last_modified_header_ = false; + + // True if the resource was retrieved by the embedder in spite of + // certificate errors. + bool has_major_certificate_errors_ = false; + + // The Certificate Transparency policy compliance status of the resource. + CTPolicyCompliance ct_policy_compliance_ = + kCTPolicyComplianceDetailsNotAvailable; + + // True if the resource was retrieved with a legacy Symantec certificate which + // is slated for distrust in future. + bool is_legacy_symantec_cert_ = false; + + // The time at which the resource's certificate expires. Null if there was no + // certificate. + base::Time cert_validity_start_; + + // Was the resource fetched over SPDY. See http://dev.chromium.org/spdy + bool was_fetched_via_spdy_ = false; + + // Was the resource fetched over an explicit proxy (HTTP, SOCKS, etc). + bool was_fetched_via_proxy_ = false; + + // Was the resource fetched over a ServiceWorker. + bool was_fetched_via_service_worker_ = false; + + // Was the fallback request with skip service worker flag required. + bool was_fallback_required_by_service_worker_ = false; + + // True if service worker navigation preload was performed due to + // the request for this resource. + bool did_service_worker_navigation_preload_ = false; + + // The type of the response which was returned by the ServiceWorker. + network::mojom::FetchResponseType response_type_via_service_worker_ = + network::mojom::FetchResponseType::kDefault; + + // HTTP version used in the response, if known. + HTTPVersion http_version_ = kHTTPVersionUnknown; + + // The security style of the resource. + // This only contains a valid value when the DevTools Network domain is + // enabled. (Otherwise, it contains a default value of Unknown.) + SecurityStyle security_style_ = kSecurityStyleUnknown; + + // Security details of this request's connection. + // If m_securityStyle is Unknown or Unauthenticated, this does not contain + // valid data. + SecurityDetails security_details_; + + scoped_refptr<ResourceLoadTiming> resource_load_timing_; + scoped_refptr<ResourceLoadInfo> resource_load_info_; + + mutable CacheControlHeader cache_control_header_; + + mutable double age_ = 0.0; + mutable double date_ = 0.0; + mutable double expires_ = 0.0; + mutable double last_modified_ = 0.0; + + // The id of the appcache this response was retrieved from, or zero if + // the response was not retrieved from an appcache. + long long app_cache_id_ = 0; + + // The manifest url of the appcache this response was retrieved from, if any. + // Note: only valid for main resource responses. + KURL app_cache_manifest_url_; + + // The multipart boundary of this response. + Vector<char> multipart_boundary_; + + // The URL list of the response which was fetched by the ServiceWorker. + // This is empty if the response was created inside the ServiceWorker. + Vector<KURL> url_list_via_service_worker_; + + // The cache name of the CacheStorage from where the response is served via + // the ServiceWorker. Null if the response isn't from the CacheStorage. + String cache_storage_cache_name_; + + // The headers that should be exposed according to CORS. Only guaranteed + // to be set if the response was fetched by a ServiceWorker. + Vector<String> cors_exposed_header_names_; + + // The time at which the response headers were received. For cached + // responses, this time could be "far" in the past. + Time response_time_; + + // ALPN negotiated protocol of the socket which fetched this resource. + AtomicString alpn_negotiated_protocol_; + + // Information about the type of connection used to fetch this resource. + net::HttpResponseInfo::ConnectionInfo connection_info_ = + net::HttpResponseInfo::ConnectionInfo::CONNECTION_INFO_UNKNOWN; + + // Size of the response in bytes prior to decompression. + long long encoded_data_length_ = 0; + + // Size of the response body in bytes prior to decompression. + long long encoded_body_length_ = 0; + + // Sizes of the response body in bytes after any content-encoding is + // removed. + long long decoded_body_length_ = 0; + + // The downloaded file path if the load streamed to a file. + String downloaded_file_path_; + + // The handle to the downloaded file to ensure the underlying file will not + // be deleted. + scoped_refptr<BlobDataHandle> downloaded_file_handle_; + + // ExtraData associated with the response. + scoped_refptr<ExtraData> extra_data_; + + // PlzNavigate: the redirect responses are transmitted + // inside the final response. + Vector<ResourceResponse> redirect_responses_; +}; + +inline bool operator==(const ResourceResponse& a, const ResourceResponse& b) { + return ResourceResponse::Compare(a, b); +} +inline bool operator!=(const ResourceResponse& a, const ResourceResponse& b) { + return !(a == b); +} + +// This class is needed to copy a ResourceResponse across threads, because it +// has some members which cannot be transferred across threads (AtomicString +// for example). +// There are some rules / restrictions: +// - This struct cannot contain an object that cannot be transferred across +// threads (e.g., AtomicString) +// - Non-simple members need explicit copying (e.g., String::IsolatedCopy, +// KURL::Copy) rather than the copy constructor or the assignment operator. +struct CrossThreadResourceResponseData { + WTF_MAKE_NONCOPYABLE(CrossThreadResourceResponseData); + USING_FAST_MALLOC(CrossThreadResourceResponseData); + + public: + CrossThreadResourceResponseData() = default; + KURL url_; + String mime_type_; + long long expected_content_length_; + String text_encoding_name_; + int http_status_code_; + String http_status_text_; + std::unique_ptr<CrossThreadHTTPHeaderMapData> http_headers_; + scoped_refptr<ResourceLoadTiming> resource_load_timing_; + bool has_major_certificate_errors_; + ResourceResponse::CTPolicyCompliance ct_policy_compliance_; + bool is_legacy_symantec_cert_; + base::Time cert_validity_start_; + ResourceResponse::SecurityStyle security_style_; + ResourceResponse::SecurityDetails security_details_; + // This is |certificate| from SecurityDetails since that structure should + // use an AtomicString but this temporary structure is sent across threads. + Vector<String> certificate_; + ResourceResponse::HTTPVersion http_version_; + long long app_cache_id_; + KURL app_cache_manifest_url_; + Vector<char> multipart_boundary_; + bool was_fetched_via_spdy_; + bool was_fetched_via_proxy_; + bool was_fetched_via_service_worker_; + bool was_fallback_required_by_service_worker_; + network::mojom::FetchResponseType response_type_via_service_worker_; + Vector<KURL> url_list_via_service_worker_; + String cache_storage_cache_name_; + bool did_service_worker_navigation_preload_; + Time response_time_; + String remote_ip_address_; + unsigned short remote_port_; + long long encoded_data_length_; + long long encoded_body_length_; + long long decoded_body_length_; + String downloaded_file_path_; + scoped_refptr<BlobDataHandle> downloaded_file_handle_; +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_PLATFORM_LOADER_FETCH_RESOURCE_RESPONSE_H_ diff --git a/chromium/third_party/blink/renderer/platform/loader/fetch/resource_response_test.cc b/chromium/third_party/blink/renderer/platform/loader/fetch/resource_response_test.cc new file mode 100644 index 00000000000..c22bfd36a9e --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/loader/fetch/resource_response_test.cc @@ -0,0 +1,87 @@ +// 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/platform/loader/fetch/resource_response.h" + +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/blink/public/platform/platform.h" +#include "third_party/blink/public/platform/web_thread.h" +#include "third_party/blink/renderer/platform/cross_thread_functional.h" +#include "third_party/blink/renderer/platform/testing/testing_platform_support_with_mock_scheduler.h" +#include "third_party/blink/renderer/platform/web_task_runner.h" + +namespace blink { + +namespace { + +ResourceResponse CreateTestResponse() { + ResourceResponse response; + response.AddHTTPHeaderField("age", "0"); + response.AddHTTPHeaderField("cache-control", "no-cache"); + response.AddHTTPHeaderField("date", "Tue, 17 Jan 2017 04:01:00 GMT"); + response.AddHTTPHeaderField("expires", "Tue, 17 Jan 2017 04:11:00 GMT"); + response.AddHTTPHeaderField("last-modified", "Tue, 17 Jan 2017 04:00:00 GMT"); + response.AddHTTPHeaderField("pragma", "public"); + response.AddHTTPHeaderField("etag", "abc"); + response.AddHTTPHeaderField("content-disposition", + "attachment; filename=a.txt"); + return response; +} + +void RunHeaderRelatedTest(const ResourceResponse& response) { + EXPECT_EQ(0, response.Age()); + EXPECT_NE(0, response.Date()); + EXPECT_NE(0, response.Expires()); + EXPECT_NE(0, response.LastModified()); + EXPECT_EQ(true, response.CacheControlContainsNoCache()); +} + +void RunInThread() { + ResourceResponse response(CreateTestResponse()); + RunHeaderRelatedTest(response); +} + +} // namespace + +TEST(ResourceResponseTest, SignedCertificateTimestampIsolatedCopy) { + ResourceResponse::SignedCertificateTimestamp src( + "status", "origin", "logDescription", "logId", 7, "hashAlgorithm", + "signatureAlgorithm", "signatureData"); + + ResourceResponse::SignedCertificateTimestamp dest = src.IsolatedCopy(); + + EXPECT_EQ(src.status_, dest.status_); + EXPECT_NE(src.status_.Impl(), dest.status_.Impl()); + EXPECT_EQ(src.origin_, dest.origin_); + EXPECT_NE(src.origin_.Impl(), dest.origin_.Impl()); + EXPECT_EQ(src.log_description_, dest.log_description_); + EXPECT_NE(src.log_description_.Impl(), dest.log_description_.Impl()); + EXPECT_EQ(src.log_id_, dest.log_id_); + EXPECT_NE(src.log_id_.Impl(), dest.log_id_.Impl()); + EXPECT_EQ(src.timestamp_, dest.timestamp_); + EXPECT_EQ(src.hash_algorithm_, dest.hash_algorithm_); + EXPECT_NE(src.hash_algorithm_.Impl(), dest.hash_algorithm_.Impl()); + EXPECT_EQ(src.signature_algorithm_, dest.signature_algorithm_); + EXPECT_NE(src.signature_algorithm_.Impl(), dest.signature_algorithm_.Impl()); + EXPECT_EQ(src.signature_data_, dest.signature_data_); + EXPECT_NE(src.signature_data_.Impl(), dest.signature_data_.Impl()); +} + +// This test checks that AtomicStrings in ResourceResponse doesn't cause the +// failure of ThreadRestrictionVerifier check. +TEST(ResourceResponseTest, CrossThreadAtomicStrings) { + ScopedTestingPlatformSupport<TestingPlatformSupportWithMockScheduler> + platform; + + ResourceResponse response(CreateTestResponse()); + RunHeaderRelatedTest(response); + std::unique_ptr<WebThread> thread = Platform::Current()->CreateThread( + WebThreadCreationParams(WebThreadType::kTestThread) + .SetThreadNameForTest("WorkerThread")); + PostCrossThreadTask(*thread->GetTaskRunner(), FROM_HERE, + CrossThreadBind(&RunInThread)); + thread.reset(); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/platform/loader/fetch/resource_status.h b/chromium/third_party/blink/renderer/platform/loader/fetch/resource_status.h new file mode 100644 index 00000000000..28773d48bf2 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/loader/fetch/resource_status.h @@ -0,0 +1,20 @@ +// 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_PLATFORM_LOADER_FETCH_RESOURCE_STATUS_H_ +#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_LOADER_FETCH_RESOURCE_STATUS_H_ + +namespace blink { + +enum class ResourceStatus : uint8_t { + kNotStarted, + kPending, // load in progress + kCached, // load completed successfully + kLoadError, + kDecodeError +}; + +} // namespace blink + +#endif diff --git a/chromium/third_party/blink/renderer/platform/loader/fetch/resource_test.cc b/chromium/third_party/blink/renderer/platform/loader/fetch/resource_test.cc new file mode 100644 index 00000000000..453c1c32137 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/loader/fetch/resource_test.cc @@ -0,0 +1,424 @@ +// 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/platform/loader/fetch/resource.h" + +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/blink/public/platform/platform.h" +#include "third_party/blink/renderer/platform/loader/fetch/memory_cache.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_resource.h" +#include "third_party/blink/renderer/platform/loader/testing/mock_resource_client.h" +#include "third_party/blink/renderer/platform/shared_buffer.h" +#include "third_party/blink/renderer/platform/testing/testing_platform_support_with_mock_scheduler.h" +#include "third_party/blink/renderer/platform/testing/url_test_helpers.h" +#include "third_party/blink/renderer/platform/wtf/time.h" +#include "third_party/blink/renderer/platform/wtf/vector.h" + +namespace blink { + +namespace { + +class MockPlatform final : public TestingPlatformSupportWithMockScheduler { + public: + MockPlatform() = default; + ~MockPlatform() override = default; + + // From blink::Platform: + void CacheMetadata(const WebURL& url, Time, const char*, size_t) override { + cached_urls_.push_back(url); + } + + const Vector<WebURL>& CachedURLs() const { return cached_urls_; } + + private: + Vector<WebURL> cached_urls_; +}; + +ResourceResponse CreateTestResourceResponse() { + ResourceResponse response(URLTestHelpers::ToKURL("https://example.com/")); + response.SetHTTPStatusCode(200); + return response; +} + +void CreateTestResourceAndSetCachedMetadata(const ResourceResponse& response) { + const char kTestData[] = "test data"; + MockResource* resource = MockResource::Create(response.Url()); + resource->SetResponse(response); + resource->SendCachedMetadata(kTestData, sizeof(kTestData)); + return; +} + +} // anonymous namespace + +TEST(ResourceTest, SetCachedMetadata_SendsMetadataToPlatform) { + ScopedTestingPlatformSupport<MockPlatform> mock; + ResourceResponse response(CreateTestResourceResponse()); + CreateTestResourceAndSetCachedMetadata(response); + EXPECT_EQ(1u, mock->CachedURLs().size()); +} + +TEST( + ResourceTest, + SetCachedMetadata_DoesNotSendMetadataToPlatformWhenFetchedViaServiceWorker) { + ScopedTestingPlatformSupport<MockPlatform> mock; + ResourceResponse response(CreateTestResourceResponse()); + response.SetWasFetchedViaServiceWorker(true); + CreateTestResourceAndSetCachedMetadata(response); + EXPECT_EQ(0u, mock->CachedURLs().size()); +} + +TEST(ResourceTest, RevalidateWithFragment) { + ScopedTestingPlatformSupport<MockPlatform> mock; + KURL url("http://127.0.0.1:8000/foo.html"); + ResourceResponse response(url); + response.SetHTTPStatusCode(200); + MockResource* resource = MockResource::Create(url); + resource->ResponseReceived(response, nullptr); + resource->FinishForTest(); + + // Revalidating with a url that differs by only the fragment + // shouldn't trigger a securiy check. + url.SetFragmentIdentifier("bar"); + resource->SetRevalidatingRequest(ResourceRequest(url)); + ResourceResponse revalidating_response(url); + revalidating_response.SetHTTPStatusCode(304); + resource->ResponseReceived(revalidating_response, nullptr); +} + +TEST(ResourceTest, Vary) { + ScopedTestingPlatformSupport<MockPlatform> mock; + const KURL url("http://127.0.0.1:8000/foo.html"); + ResourceResponse response(url); + response.SetHTTPStatusCode(200); + + MockResource* resource = MockResource::Create(url); + resource->ResponseReceived(response, nullptr); + resource->FinishForTest(); + + ResourceRequest new_request(url); + EXPECT_FALSE(resource->MustReloadDueToVaryHeader(new_request)); + + response.SetHTTPHeaderField(HTTPNames::Vary, "*"); + resource->SetResponse(response); + EXPECT_TRUE(resource->MustReloadDueToVaryHeader(new_request)); + + // Irrelevant header + response.SetHTTPHeaderField(HTTPNames::Vary, "definitelynotarealheader"); + resource->SetResponse(response); + EXPECT_FALSE(resource->MustReloadDueToVaryHeader(new_request)); + + // Header present on new but not old + new_request.SetHTTPHeaderField(HTTPNames::User_Agent, "something"); + response.SetHTTPHeaderField(HTTPNames::Vary, HTTPNames::User_Agent); + resource->SetResponse(response); + EXPECT_TRUE(resource->MustReloadDueToVaryHeader(new_request)); + new_request.ClearHTTPHeaderField(HTTPNames::User_Agent); + + ResourceRequest old_request(url); + old_request.SetHTTPHeaderField(HTTPNames::User_Agent, "something"); + old_request.SetHTTPHeaderField(HTTPNames::Referer, "http://foo.com"); + resource = MockResource::Create(old_request); + resource->ResponseReceived(response, nullptr); + resource->FinishForTest(); + + // Header present on old but not new + new_request.ClearHTTPHeaderField(HTTPNames::User_Agent); + response.SetHTTPHeaderField(HTTPNames::Vary, HTTPNames::User_Agent); + resource->SetResponse(response); + EXPECT_TRUE(resource->MustReloadDueToVaryHeader(new_request)); + + // Header present on both + new_request.SetHTTPHeaderField(HTTPNames::User_Agent, "something"); + EXPECT_FALSE(resource->MustReloadDueToVaryHeader(new_request)); + + // One matching, one mismatching + response.SetHTTPHeaderField(HTTPNames::Vary, "User-Agent, Referer"); + resource->SetResponse(response); + EXPECT_TRUE(resource->MustReloadDueToVaryHeader(new_request)); + + // Two matching + new_request.SetHTTPHeaderField(HTTPNames::Referer, "http://foo.com"); + EXPECT_FALSE(resource->MustReloadDueToVaryHeader(new_request)); +} + +TEST(ResourceTest, RevalidationFailed) { + ScopedTestingPlatformSupport<TestingPlatformSupportWithMockScheduler> + platform_; + const KURL url("http://test.example.com/"); + MockResource* resource = MockResource::Create(ResourceRequest(url)); + ResourceResponse response(url); + response.SetHTTPStatusCode(200); + resource->ResponseReceived(response, nullptr); + const char kData[5] = "abcd"; + resource->AppendData(kData, 4); + resource->FinishForTest(); + GetMemoryCache()->Add(resource); + + MockCacheHandler* original_cache_handler = resource->CacheHandler(); + EXPECT_TRUE(original_cache_handler); + + // Simulate revalidation start. + resource->SetRevalidatingRequest(ResourceRequest(url)); + + EXPECT_EQ(original_cache_handler, resource->CacheHandler()); + + Persistent<MockResourceClient> client = new MockResourceClient; + resource->AddClient(client, nullptr); + + ResourceResponse revalidating_response(url); + revalidating_response.SetHTTPStatusCode(200); + resource->ResponseReceived(revalidating_response, nullptr); + + EXPECT_FALSE(resource->IsCacheValidator()); + EXPECT_EQ(200, resource->GetResponse().HttpStatusCode()); + EXPECT_FALSE(resource->ResourceBuffer()); + EXPECT_TRUE(resource->CacheHandler()); + EXPECT_NE(original_cache_handler, resource->CacheHandler()); + EXPECT_EQ(resource, GetMemoryCache()->ResourceForURL(url)); + + resource->AppendData(kData, 4); + + EXPECT_FALSE(client->NotifyFinishedCalled()); + + resource->FinishForTest(); + + EXPECT_TRUE(client->NotifyFinishedCalled()); + + resource->RemoveClient(client); + EXPECT_FALSE(resource->IsAlive()); +} + +TEST(ResourceTest, RevalidationSucceeded) { + ScopedTestingPlatformSupport<TestingPlatformSupportWithMockScheduler> + platform_; + const KURL url("http://test.example.com/"); + MockResource* resource = MockResource::Create(ResourceRequest(url)); + ResourceResponse response(url); + response.SetHTTPStatusCode(200); + resource->ResponseReceived(response, nullptr); + const char kData[5] = "abcd"; + resource->AppendData(kData, 4); + resource->FinishForTest(); + GetMemoryCache()->Add(resource); + + MockCacheHandler* original_cache_handler = resource->CacheHandler(); + EXPECT_TRUE(original_cache_handler); + + // Simulate a successful revalidation. + resource->SetRevalidatingRequest(ResourceRequest(url)); + + EXPECT_EQ(original_cache_handler, resource->CacheHandler()); + + Persistent<MockResourceClient> client = new MockResourceClient; + resource->AddClient(client, nullptr); + + ResourceResponse revalidating_response(url); + revalidating_response.SetHTTPStatusCode(304); + resource->ResponseReceived(revalidating_response, nullptr); + + EXPECT_FALSE(resource->IsCacheValidator()); + EXPECT_EQ(200, resource->GetResponse().HttpStatusCode()); + EXPECT_EQ(4u, resource->ResourceBuffer()->size()); + EXPECT_EQ(original_cache_handler, resource->CacheHandler()); + EXPECT_EQ(resource, GetMemoryCache()->ResourceForURL(url)); + + GetMemoryCache()->Remove(resource); + + resource->RemoveClient(client); + EXPECT_FALSE(resource->IsAlive()); + EXPECT_FALSE(client->NotifyFinishedCalled()); +} + +TEST(ResourceTest, RevalidationSucceededForResourceWithoutBody) { + ScopedTestingPlatformSupport<TestingPlatformSupportWithMockScheduler> + platform_; + const KURL url("http://test.example.com/"); + Resource* resource = MockResource::Create(ResourceRequest(url)); + ResourceResponse response(url); + response.SetHTTPStatusCode(200); + resource->ResponseReceived(response, nullptr); + resource->FinishForTest(); + GetMemoryCache()->Add(resource); + + // Simulate a successful revalidation. + resource->SetRevalidatingRequest(ResourceRequest(url)); + + Persistent<MockResourceClient> client = new MockResourceClient; + resource->AddClient(client, nullptr); + + ResourceResponse revalidating_response(url); + revalidating_response.SetHTTPStatusCode(304); + resource->ResponseReceived(revalidating_response, nullptr); + EXPECT_FALSE(resource->IsCacheValidator()); + EXPECT_EQ(200, resource->GetResponse().HttpStatusCode()); + EXPECT_FALSE(resource->ResourceBuffer()); + EXPECT_EQ(resource, GetMemoryCache()->ResourceForURL(url)); + GetMemoryCache()->Remove(resource); + + resource->RemoveClient(client); + EXPECT_FALSE(resource->IsAlive()); + EXPECT_FALSE(client->NotifyFinishedCalled()); +} + +TEST(ResourceTest, RevalidationSucceededUpdateHeaders) { + ScopedTestingPlatformSupport<TestingPlatformSupportWithMockScheduler> + platform_; + const KURL url("http://test.example.com/"); + Resource* resource = MockResource::Create(ResourceRequest(url)); + ResourceResponse response(url); + response.SetHTTPStatusCode(200); + response.AddHTTPHeaderField("keep-alive", "keep-alive value"); + response.AddHTTPHeaderField("expires", "expires value"); + response.AddHTTPHeaderField("last-modified", "last-modified value"); + response.AddHTTPHeaderField("proxy-authenticate", "proxy-authenticate value"); + response.AddHTTPHeaderField("proxy-connection", "proxy-connection value"); + response.AddHTTPHeaderField("x-custom", "custom value"); + resource->ResponseReceived(response, nullptr); + resource->FinishForTest(); + GetMemoryCache()->Add(resource); + + // Simulate a successful revalidation. + resource->SetRevalidatingRequest(ResourceRequest(url)); + + // Validate that these headers pre-update. + EXPECT_EQ("keep-alive value", + resource->GetResponse().HttpHeaderField("keep-alive")); + EXPECT_EQ("expires value", + resource->GetResponse().HttpHeaderField("expires")); + EXPECT_EQ("last-modified value", + resource->GetResponse().HttpHeaderField("last-modified")); + EXPECT_EQ("proxy-authenticate value", + resource->GetResponse().HttpHeaderField("proxy-authenticate")); + EXPECT_EQ("proxy-authenticate value", + resource->GetResponse().HttpHeaderField("proxy-authenticate")); + EXPECT_EQ("proxy-connection value", + resource->GetResponse().HttpHeaderField("proxy-connection")); + EXPECT_EQ("custom value", + resource->GetResponse().HttpHeaderField("x-custom")); + + Persistent<MockResourceClient> client = new MockResourceClient; + resource->AddClient(client, nullptr); + + // Perform a revalidation step. + ResourceResponse revalidating_response(url); + revalidating_response.SetHTTPStatusCode(304); + // Headers that aren't copied with an 304 code. + revalidating_response.AddHTTPHeaderField("keep-alive", "garbage"); + revalidating_response.AddHTTPHeaderField("expires", "garbage"); + revalidating_response.AddHTTPHeaderField("last-modified", "garbage"); + revalidating_response.AddHTTPHeaderField("proxy-authenticate", "garbage"); + revalidating_response.AddHTTPHeaderField("proxy-connection", "garbage"); + // Header that is updated with 304 code. + revalidating_response.AddHTTPHeaderField("x-custom", "updated"); + resource->ResponseReceived(revalidating_response, nullptr); + + // Validate the original response. + EXPECT_EQ(200, resource->GetResponse().HttpStatusCode()); + + // Validate that these headers are not updated. + EXPECT_EQ("keep-alive value", + resource->GetResponse().HttpHeaderField("keep-alive")); + EXPECT_EQ("expires value", + resource->GetResponse().HttpHeaderField("expires")); + EXPECT_EQ("last-modified value", + resource->GetResponse().HttpHeaderField("last-modified")); + EXPECT_EQ("proxy-authenticate value", + resource->GetResponse().HttpHeaderField("proxy-authenticate")); + EXPECT_EQ("proxy-authenticate value", + resource->GetResponse().HttpHeaderField("proxy-authenticate")); + EXPECT_EQ("proxy-connection value", + resource->GetResponse().HttpHeaderField("proxy-connection")); + EXPECT_EQ("updated", resource->GetResponse().HttpHeaderField("x-custom")); + + resource->RemoveClient(client); + EXPECT_FALSE(resource->IsAlive()); + EXPECT_FALSE(client->NotifyFinishedCalled()); +} + +TEST(ResourceTest, RedirectDuringRevalidation) { + ScopedTestingPlatformSupport<TestingPlatformSupportWithMockScheduler> + platform_; + const KURL url("http://test.example.com/1"); + const KURL redirect_target_url("http://test.example.com/2"); + + MockResource* resource = MockResource::Create(ResourceRequest(url)); + ResourceResponse response(url); + response.SetHTTPStatusCode(200); + resource->ResponseReceived(response, nullptr); + const char kData[5] = "abcd"; + resource->AppendData(kData, 4); + resource->FinishForTest(); + GetMemoryCache()->Add(resource); + + EXPECT_FALSE(resource->IsCacheValidator()); + EXPECT_EQ(url, resource->GetResourceRequest().Url()); + EXPECT_EQ(url, resource->LastResourceRequest().Url()); + + MockCacheHandler* original_cache_handler = resource->CacheHandler(); + EXPECT_TRUE(original_cache_handler); + + // Simulate a revalidation. + resource->SetRevalidatingRequest(ResourceRequest(url)); + EXPECT_TRUE(resource->IsCacheValidator()); + EXPECT_EQ(url, resource->GetResourceRequest().Url()); + EXPECT_EQ(url, resource->LastResourceRequest().Url()); + EXPECT_EQ(original_cache_handler, resource->CacheHandler()); + + Persistent<MockResourceClient> client = new MockResourceClient; + resource->AddClient(client, nullptr); + + // The revalidating request is redirected. + ResourceResponse redirect_response(url); + redirect_response.SetHTTPHeaderField( + "location", AtomicString(redirect_target_url.GetString())); + redirect_response.SetHTTPStatusCode(308); + ResourceRequest redirected_revalidating_request(redirect_target_url); + resource->WillFollowRedirect(redirected_revalidating_request, + redirect_response); + EXPECT_FALSE(resource->IsCacheValidator()); + EXPECT_EQ(url, resource->GetResourceRequest().Url()); + EXPECT_EQ(redirect_target_url, resource->LastResourceRequest().Url()); + EXPECT_FALSE(resource->CacheHandler()); + + // The final response is received. + ResourceResponse revalidating_response(redirect_target_url); + revalidating_response.SetHTTPStatusCode(200); + resource->ResponseReceived(revalidating_response, nullptr); + + EXPECT_TRUE(resource->CacheHandler()); + + const char kData2[4] = "xyz"; + resource->AppendData(kData2, 3); + resource->FinishForTest(); + EXPECT_FALSE(resource->IsCacheValidator()); + EXPECT_EQ(url, resource->GetResourceRequest().Url()); + EXPECT_EQ(redirect_target_url, resource->LastResourceRequest().Url()); + EXPECT_FALSE(resource->IsCacheValidator()); + EXPECT_EQ(200, resource->GetResponse().HttpStatusCode()); + EXPECT_EQ(3u, resource->ResourceBuffer()->size()); + EXPECT_EQ(resource, GetMemoryCache()->ResourceForURL(url)); + + EXPECT_TRUE(client->NotifyFinishedCalled()); + + // Test the case where a client is added after revalidation is completed. + Persistent<MockResourceClient> client2 = new MockResourceClient; + resource->AddClient( + client2, Platform::Current()->CurrentThread()->GetTaskRunner().get()); + + // Because the client is added asynchronously, + // |runUntilIdle()| is called to make |client2| to be notified. + platform_->RunUntilIdle(); + + EXPECT_TRUE(client2->NotifyFinishedCalled()); + + GetMemoryCache()->Remove(resource); + + resource->RemoveClient(client); + resource->RemoveClient(client2); + EXPECT_FALSE(resource->IsAlive()); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/platform/loader/fetch/resource_timing_info.cc b/chromium/third_party/blink/renderer/platform/loader/fetch/resource_timing_info.cc new file mode 100644 index 00000000000..31574cfc049 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/loader/fetch/resource_timing_info.cc @@ -0,0 +1,61 @@ +// 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/platform/loader/fetch/resource_timing_info.h" + +#include <memory> +#include "third_party/blink/renderer/platform/cross_thread_copier.h" + +namespace blink { + +scoped_refptr<ResourceTimingInfo> ResourceTimingInfo::Adopt( + std::unique_ptr<CrossThreadResourceTimingInfoData> data) { + scoped_refptr<ResourceTimingInfo> info = ResourceTimingInfo::Create( + AtomicString(data->type_), data->initial_time_, data->is_main_resource_); + info->original_timing_allow_origin_ = + AtomicString(data->original_timing_allow_origin_); + info->load_finish_time_ = data->load_finish_time_; + info->initial_url_ = data->initial_url_.Copy(); + info->final_response_ = ResourceResponse(data->final_response_.get()); + for (auto& response_data : data->redirect_chain_) + info->redirect_chain_.push_back(ResourceResponse(response_data.get())); + info->transfer_size_ = data->transfer_size_; + info->negative_allowed_ = data->negative_allowed_; + return info; +} + +std::unique_ptr<CrossThreadResourceTimingInfoData> +ResourceTimingInfo::CopyData() const { + std::unique_ptr<CrossThreadResourceTimingInfoData> data = + std::make_unique<CrossThreadResourceTimingInfoData>(); + data->type_ = type_.GetString().IsolatedCopy(); + data->original_timing_allow_origin_ = + original_timing_allow_origin_.GetString().IsolatedCopy(); + data->initial_time_ = initial_time_; + data->load_finish_time_ = load_finish_time_; + data->initial_url_ = initial_url_.Copy(); + data->final_response_ = final_response_.CopyData(); + for (const auto& response : redirect_chain_) + data->redirect_chain_.push_back(response.CopyData()); + data->transfer_size_ = transfer_size_; + data->is_main_resource_ = is_main_resource_; + data->negative_allowed_ = negative_allowed_; + return data; +} + +void ResourceTimingInfo::AddRedirect(const ResourceResponse& redirect_response, + bool cross_origin) { + redirect_chain_.push_back(redirect_response); + if (has_cross_origin_redirect_) + return; + if (cross_origin) { + has_cross_origin_redirect_ = true; + transfer_size_ = 0; + } else { + DCHECK_GE(redirect_response.EncodedDataLength(), 0); + transfer_size_ += redirect_response.EncodedDataLength(); + } +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/platform/loader/fetch/resource_timing_info.h b/chromium/third_party/blink/renderer/platform/loader/fetch/resource_timing_info.h new file mode 100644 index 00000000000..dd964d8be55 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/loader/fetch/resource_timing_info.h @@ -0,0 +1,165 @@ +/* + * Copyright (C) 2013 Intel 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_PLATFORM_LOADER_FETCH_RESOURCE_TIMING_INFO_H_ +#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_LOADER_FETCH_RESOURCE_TIMING_INFO_H_ + +#include <memory> +#include "third_party/blink/renderer/platform/cross_thread_copier.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/wtf/allocator.h" +#include "third_party/blink/renderer/platform/wtf/functional.h" +#include "third_party/blink/renderer/platform/wtf/noncopyable.h" +#include "third_party/blink/renderer/platform/wtf/text/atomic_string.h" + +namespace blink { + +struct CrossThreadResourceTimingInfoData; + +class PLATFORM_EXPORT ResourceTimingInfo + : public RefCounted<ResourceTimingInfo> { + USING_FAST_MALLOC(ResourceTimingInfo); + WTF_MAKE_NONCOPYABLE(ResourceTimingInfo); + + public: + static scoped_refptr<ResourceTimingInfo> Create(const AtomicString& type, + const double time, + bool is_main_resource) { + return base::AdoptRef(new ResourceTimingInfo(type, time, is_main_resource)); + } + static scoped_refptr<ResourceTimingInfo> Adopt( + std::unique_ptr<CrossThreadResourceTimingInfoData>); + + // Gets a copy of the data suitable for passing to another thread. + std::unique_ptr<CrossThreadResourceTimingInfoData> CopyData() const; + + double InitialTime() const { return initial_time_; } + bool IsMainResource() const { return is_main_resource_; } + + const AtomicString& InitiatorType() const { return type_; } + + void SetOriginalTimingAllowOrigin( + const AtomicString& original_timing_allow_origin) { + original_timing_allow_origin_ = original_timing_allow_origin; + } + const AtomicString& OriginalTimingAllowOrigin() const { + return original_timing_allow_origin_; + } + + void SetLoadFinishTime(double time) { load_finish_time_ = time; } + double LoadFinishTime() const { return load_finish_time_; } + + void SetInitialURL(const KURL& url) { initial_url_ = url; } + const KURL& InitialURL() const { return initial_url_; } + + void SetFinalResponse(const ResourceResponse& response) { + final_response_ = response; + } + const ResourceResponse& FinalResponse() const { return final_response_; } + + void AddRedirect(const ResourceResponse& redirect_response, + bool cross_origin); + const Vector<ResourceResponse>& RedirectChain() const { + return redirect_chain_; + } + + void AddFinalTransferSize(long long encoded_data_length) { + transfer_size_ += encoded_data_length; + } + long long TransferSize() const { return transfer_size_; } + + void ClearLoadTimings() { + final_response_.SetResourceLoadTiming(nullptr); + for (ResourceResponse& redirect : redirect_chain_) + redirect.SetResourceLoadTiming(nullptr); + } + + // The timestamps in PerformanceResourceTiming are measured relative from the + // time origin. In most cases these timestamps must be positive value, so we + // use 0 for invalid negative values. But the timestamps for Service Worker + // navigation preload requests may be negative, because these requests may + // be started before the service worker started. We set this flag true, to + // support such case. + void SetNegativeAllowed(bool negative_allowed) { + negative_allowed_ = negative_allowed; + } + bool NegativeAllowed() const { return negative_allowed_; } + + private: + ResourceTimingInfo(const AtomicString& type, + const double time, + bool is_main_resource) + : type_(type), initial_time_(time), is_main_resource_(is_main_resource) {} + + AtomicString type_; + AtomicString original_timing_allow_origin_; + double initial_time_; + double load_finish_time_; + KURL initial_url_; + ResourceResponse final_response_; + Vector<ResourceResponse> redirect_chain_; + long long transfer_size_ = 0; + bool is_main_resource_; + bool has_cross_origin_redirect_ = false; + bool negative_allowed_ = false; +}; + +struct CrossThreadResourceTimingInfoData { + WTF_MAKE_NONCOPYABLE(CrossThreadResourceTimingInfoData); + USING_FAST_MALLOC(CrossThreadResourceTimingInfoData); + + public: + CrossThreadResourceTimingInfoData() = default; + + String type_; + String original_timing_allow_origin_; + double initial_time_; + double load_finish_time_; + KURL initial_url_; + std::unique_ptr<CrossThreadResourceResponseData> final_response_; + Vector<std::unique_ptr<CrossThreadResourceResponseData>> redirect_chain_; + long long transfer_size_; + bool is_main_resource_; + bool negative_allowed_; +}; + +template <> +struct CrossThreadCopier<ResourceTimingInfo> { + typedef WTF::PassedWrapper<std::unique_ptr<CrossThreadResourceTimingInfoData>> + Type; + static Type Copy(const ResourceTimingInfo& info) { + return WTF::Passed(info.CopyData()); + } +}; + +} // namespace blink + +#endif diff --git a/chromium/third_party/blink/renderer/platform/loader/fetch/script_fetch_options.cc b/chromium/third_party/blink/renderer/platform/loader/fetch/script_fetch_options.cc new file mode 100644 index 00000000000..7fca9edf686 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/loader/fetch/script_fetch_options.cc @@ -0,0 +1,69 @@ +// 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/platform/loader/fetch/script_fetch_options.h" + +#include "third_party/blink/renderer/platform/weborigin/kurl.h" +#include "third_party/blink/renderer/platform/weborigin/security_origin.h" + +namespace blink { + +// https://html.spec.whatwg.org/multipage/webappapis.html#fetch-a-classic-script +FetchParameters ScriptFetchOptions::CreateFetchParameters( + const KURL& url, + const SecurityOrigin* security_origin, + const WTF::TextEncoding& encoding, + FetchParameters::DeferOption defer) const { + // Step 1. Let request be the result of creating a potential-CORS request + // given url, ... [spec text] + ResourceRequest resource_request(url); + + // Step 1. ... "script", ... [spec text] + ResourceLoaderOptions resource_loader_options; + resource_loader_options.initiator_info.name = "script"; + FetchParameters params(resource_request, resource_loader_options); + + // Step 1. ... and CORS setting. [spec text] + // + // Instead of using CrossOriginAttributeValue that corresponds to |CORS + // setting|, we use ScriptFetchOptions::CredentialsMode(). + // We shouldn't call SetCrossOriginAccessControl() if CredentialsMode() is + // kFetchCredentialsModeOmit, because in that case the request should be + // no-cors, while SetCrossOriginAccessControl(kFetchCredentialsModeOmit) + // would result in a cors request. + if (CredentialsMode() != network::mojom::FetchCredentialsMode::kOmit) { + params.SetCrossOriginAccessControl(security_origin, CredentialsMode()); + } + + // Step 2. Set request's client to settings object. [spec text] + // Note: Implemented at ClassicPendingScript::Fetch(). + + // Step 3. Set up the classic script request given request and options. [spec + // text] + // + // https://html.spec.whatwg.org/multipage/webappapis.html#set-up-the-classic-script-request + // Set request's cryptographic nonce metadata to options's cryptographic + // nonce, [spec text] + params.SetContentSecurityPolicyNonce(Nonce()); + + // its integrity metadata to options's integrity metadata, [spec text] + params.SetIntegrityMetadata(GetIntegrityMetadata()); + params.MutableResourceRequest().SetFetchIntegrity( + GetIntegrityAttributeValue()); + + // and its parser metadata to options's parser metadata. [spec text] + params.SetParserDisposition(ParserState()); + + params.SetCharset(encoding); + + // This DeferOption logic is only for classic scripts, as we always set + // |kLazyLoad| for module scripts in ModuleScriptLoader. + params.SetDefer(defer); + + // Steps 4- are Implemented at ClassicPendingScript::Fetch(). + + return params; +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/platform/loader/fetch/script_fetch_options.h b/chromium/third_party/blink/renderer/platform/loader/fetch/script_fetch_options.h new file mode 100644 index 00000000000..57883285a4a --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/loader/fetch/script_fetch_options.h @@ -0,0 +1,82 @@ +// 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_PLATFORM_LOADER_FETCH_SCRIPT_FETCH_OPTIONS_H_ +#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_LOADER_FETCH_SCRIPT_FETCH_OPTIONS_H_ + +#include "third_party/blink/public/platform/web_url_request.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_loader_options.h" +#include "third_party/blink/renderer/platform/platform_export.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 KURL; +class SecurityOrigin; + +// ScriptFetchOptions corresponds to the spec concept "script fetch options". +// https://html.spec.whatwg.org/multipage/webappapis.html#script-fetch-options +class PLATFORM_EXPORT ScriptFetchOptions final { + public: + // https://html.spec.whatwg.org/multipage/webappapis.html#default-classic-script-fetch-options + // "The default classic script fetch options are a script fetch options whose + // cryptographic nonce is the empty string, integrity metadata is the empty + // string, parser metadata is "not-parser-inserted", and credentials mode + // is "omit"." [spec text] + ScriptFetchOptions() + : parser_state_(ParserDisposition::kNotParserInserted), + credentials_mode_(network::mojom::FetchCredentialsMode::kOmit) {} + + ScriptFetchOptions(const String& nonce, + const IntegrityMetadataSet& integrity_metadata, + const String& integrity_attribute, + ParserDisposition parser_state, + network::mojom::FetchCredentialsMode credentials_mode) + : nonce_(nonce), + integrity_metadata_(integrity_metadata), + integrity_attribute_(integrity_attribute), + parser_state_(parser_state), + credentials_mode_(credentials_mode) {} + ~ScriptFetchOptions() = default; + + const String& Nonce() const { return nonce_; } + const IntegrityMetadataSet& GetIntegrityMetadata() const { + return integrity_metadata_; + } + const String& GetIntegrityAttributeValue() const { + return integrity_attribute_; + } + const ParserDisposition& ParserState() const { return parser_state_; } + network::mojom::FetchCredentialsMode CredentialsMode() const { + return credentials_mode_; + } + + // https://html.spec.whatwg.org/multipage/webappapis.html#fetch-a-classic-script + // Steps 1 and 3. + FetchParameters CreateFetchParameters(const KURL&, + const SecurityOrigin*, + const WTF::TextEncoding&, + FetchParameters::DeferOption) const; + + private: + // https://html.spec.whatwg.org/multipage/webappapis.html#concept-script-fetch-options-nonce + const String nonce_; + + // https://html.spec.whatwg.org/multipage/webappapis.html#concept-script-fetch-options-integrity + const IntegrityMetadataSet integrity_metadata_; + const String integrity_attribute_; + + // https://html.spec.whatwg.org/multipage/webappapis.html#concept-script-fetch-options-parser + const ParserDisposition parser_state_; + + // https://html.spec.whatwg.org/multipage/webappapis.html#concept-script-fetch-options-credentials + const network::mojom::FetchCredentialsMode credentials_mode_; +}; + +} // namespace blink + +#endif diff --git a/chromium/third_party/blink/renderer/platform/loader/fetch/source_keyed_cached_metadata_handler.cc b/chromium/third_party/blink/renderer/platform/loader/fetch/source_keyed_cached_metadata_handler.cc new file mode 100644 index 00000000000..4e4e33a3147 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/loader/fetch/source_keyed_cached_metadata_handler.cc @@ -0,0 +1,218 @@ +// Copyright 2018 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/platform/loader/fetch/source_keyed_cached_metadata_handler.h" + +#include "base/bit_cast.h" +#include "third_party/blink/renderer/platform/crypto.h" +#include "third_party/blink/renderer/platform/loader/fetch/cached_metadata.h" +#include "third_party/blink/renderer/platform/wtf/string_hasher.h" +#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h" + +namespace blink { + +// Defined here for storage/ODR reasons, but initialized in the header. +const size_t SourceKeyedCachedMetadataHandler::kKeySize; + +class SourceKeyedCachedMetadataHandler::SingleKeyHandler final + : public SingleCachedMetadataHandler { + public: + void Trace(Visitor* visitor) override { + visitor->Trace(parent_); + SingleCachedMetadataHandler::Trace(visitor); + } + + SingleKeyHandler(SourceKeyedCachedMetadataHandler* parent, Key key) + : parent_(parent), key_(key) {} + + void SetCachedMetadata(uint32_t data_type_id, + const char* data, + size_t size, + CacheType cache_type) override { + DCHECK(!parent_->cached_metadata_map_.Contains(key_)); + parent_->cached_metadata_map_.insert( + key_, CachedMetadata::Create(data_type_id, data, size)); + if (cache_type == CachedMetadataHandler::kSendToPlatform) + parent_->SendToPlatform(); + } + + void ClearCachedMetadata(CacheType cache_type) override { + parent_->cached_metadata_map_.erase(key_); + if (cache_type == CachedMetadataHandler::kSendToPlatform) + parent_->SendToPlatform(); + } + + scoped_refptr<CachedMetadata> GetCachedMetadata( + uint32_t data_type_id) const override { + scoped_refptr<CachedMetadata> cached_metadata = + parent_->cached_metadata_map_.at(key_); + if (!cached_metadata || cached_metadata->DataTypeID() != data_type_id) + return nullptr; + return cached_metadata; + } + + String Encoding() const override { return parent_->Encoding(); } + + bool IsServedFromCacheStorage() const override { + return parent_->IsServedFromCacheStorage(); + } + + private: + Member<SourceKeyedCachedMetadataHandler> parent_; + Key key_; +}; + +class SourceKeyedCachedMetadataHandler::KeyHash { + public: + static unsigned GetHash(const Key& key) { + return StringHasher::ComputeHash(key.data(), key.size()); + } + + static bool Equal(const Key& a, const Key& b) { return a == b; } + + static const bool safe_to_compare_to_empty_or_deleted = true; +}; + +SingleCachedMetadataHandler* SourceKeyedCachedMetadataHandler::HandlerForSource( + const String& source) { + DigestValue digest_value; + + if (!ComputeDigest(kHashAlgorithmSha256, + static_cast<const char*>(source.Bytes()), + source.CharactersSizeInBytes(), digest_value)) + return nullptr; + + Key key; + DCHECK_EQ(digest_value.size(), kKeySize); + memcpy(key.data(), digest_value.data(), kKeySize); + + return new SingleKeyHandler(this, key); +} + +void SourceKeyedCachedMetadataHandler::ClearCachedMetadata( + CachedMetadataHandler::CacheType cache_type) { + cached_metadata_map_.clear(); + if (cache_type == CachedMetadataHandler::kSendToPlatform) + SendToPlatform(); +}; + +String SourceKeyedCachedMetadataHandler::Encoding() const { + return String(encoding_.GetName()); +} + +// Encoding of keyed map: +// - marker: CachedMetadataHandler::kSourceKeyedMap (uint32_t) +// - num_entries (int) +// - key 1 (Key type) +// - len data 1 (size_t) +// - type data 1 +// - data for key 1 +// ... +// - key N (Key type) +// - len data N (size_t) +// - type data N +// - data for key N + +namespace { +// Reading a value from a char buffer without using reinterpret cast. This +// should inline and optimize to the same code as *reinterpret_cast<T>(data), +// but without the risk of undefined behaviour. +template <typename T> +T ReadVal(const char* data) { + static_assert(base::is_trivially_copyable<T>::value, + "ReadVal requires the value type to be copyable"); + T ret; + memcpy(&ret, data, sizeof(T)); + return ret; +} +} // namespace + +void SourceKeyedCachedMetadataHandler::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_map_.IsEmpty()); + + // Ensure we have a marker. + if (size < sizeof(uint32_t)) + return; + uint32_t marker = ReadVal<uint32_t>(data); + // Check for our marker to avoid conflicts with other kinds of cached + // metadata. + if (marker != CachedMetadataHandler::kSourceKeyedMap) { + return; + } + data += sizeof(uint32_t); + size -= sizeof(uint32_t); + + // Ensure we have a length. + if (size < sizeof(int)) + return; + int num_entries = ReadVal<int>(data); + data += sizeof(int); + size -= sizeof(int); + + for (int i = 0; i < num_entries; ++i) { + // Ensure we have an entry key and size. + if (size < kKeySize + sizeof(size_t)) { + cached_metadata_map_.clear(); + return; + } + + Key key; + std::copy(data, data + kKeySize, std::begin(key)); + data += kKeySize; + size_t entry_size = ReadVal<size_t>(data); + data += sizeof(size_t); + + size -= kKeySize + sizeof(size_t); + + // Ensure we have enough data for this entry. + if (size < entry_size) { + cached_metadata_map_.clear(); + return; + } + + if (scoped_refptr<CachedMetadata> deserialized_entry = + CachedMetadata::CreateFromSerializedData(data, entry_size)) { + // Only insert the deserialized entry if it deserialized correctly. + cached_metadata_map_.insert(key, std::move(deserialized_entry)); + } + data += entry_size; + size -= entry_size; + } + + // Ensure we have no more data. + if (size > 0) { + cached_metadata_map_.clear(); + } +}; + +void SourceKeyedCachedMetadataHandler::SendToPlatform() { + if (!sender_) + return; + + if (cached_metadata_map_.IsEmpty()) { + sender_->Send(nullptr, 0); + } else { + Vector<char> serialized_data; + uint32_t marker = CachedMetadataHandler::kSourceKeyedMap; + serialized_data.Append(reinterpret_cast<char*>(&marker), sizeof(marker)); + int num_entries = cached_metadata_map_.size(); + serialized_data.Append(reinterpret_cast<char*>(&num_entries), + sizeof(num_entries)); + for (const auto& metadata : cached_metadata_map_) { + serialized_data.Append(metadata.key.data(), kKeySize); + size_t entry_size = metadata.value->SerializedData().size(); + serialized_data.Append(reinterpret_cast<const char*>(&entry_size), + sizeof(entry_size)); + serialized_data.AppendVector(metadata.value->SerializedData()); + } + sender_->Send(serialized_data.data(), serialized_data.size()); + } +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/platform/loader/fetch/source_keyed_cached_metadata_handler.h b/chromium/third_party/blink/renderer/platform/loader/fetch/source_keyed_cached_metadata_handler.h new file mode 100644 index 00000000000..b9b7b83a773 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/loader/fetch/source_keyed_cached_metadata_handler.h @@ -0,0 +1,81 @@ +// Copyright 2018 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_PLATFORM_LOADER_FETCH_SOURCE_KEYED_CACHED_METADATA_HANDLER_H_ +#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_LOADER_FETCH_SOURCE_KEYED_CACHED_METADATA_HANDLER_H_ + +#include <stdint.h> +#include <array> +#include "third_party/blink/renderer/platform/loader/fetch/cached_metadata.h" +#include "third_party/blink/renderer/platform/loader/fetch/cached_metadata_handler.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource.h" +#include "third_party/blink/renderer/platform/wtf/hash_map.h" + +namespace blink { + +// An implementation of CachedMetadataHandler which can hold multiple +// CachedMetadata entries. These entries are keyed by a cryptograph hash of the +// source code which produced them. +// +// This is used to store cached metadata for multiple inline scripts on a single +// HTML document's resource. +class PLATFORM_EXPORT SourceKeyedCachedMetadataHandler final + : public CachedMetadataHandler { + public: + SourceKeyedCachedMetadataHandler( + WTF::TextEncoding encoding, + std::unique_ptr<CachedMetadataSender> send_callback) + : sender_(std::move(send_callback)), encoding_(encoding) {} + + // Produce a metadata handler for a single cached metadata associated with + // the given source code. + SingleCachedMetadataHandler* HandlerForSource(const String& source); + + void ClearCachedMetadata(CachedMetadataHandler::CacheType) override; + String Encoding() const override; + bool IsServedFromCacheStorage() const override { + return sender_->IsServedFromCacheStorage(); + } + + void SetSerializedCachedMetadata(const char*, size_t); + + private: + // Keys are SHA-256, which are 256/8 = 32 bytes. + static constexpr size_t kKeySize = 32; + typedef std::array<uint8_t, kKeySize> Key; + + class SingleKeyHandler; + class KeyHash; + class KeyHashTraits : public WTF::GenericHashTraits<Key> { + public: + // Note: This class relies on hashes never being zero or 1 followed by all + // zeros. Practically, our hash space is large enough that the risk of such + // a collision is infinitesimal. + + typedef Key EmptyValueType; + static const bool kEmptyValueIsZero = true; + static EmptyValueType EmptyValue() { + // Rely on integer value initialization to zero out the key array. + return Key{}; + } + + static void ConstructDeletedValue(Key& slot, bool) { + slot = {1}; // Remaining entries are value initialized to 0. + } + static bool IsDeletedValue(const Key& value) { return value == Key{1}; } + }; + + void SendToPlatform(); + + // TODO(leszeks): Maybe just store the SingleKeyHandlers directly in here? + WTF::HashMap<Key, scoped_refptr<CachedMetadata>, KeyHash, KeyHashTraits> + cached_metadata_map_; + std::unique_ptr<CachedMetadataSender> sender_; + + const WTF::TextEncoding encoding_; +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_PLATFORM_LOADER_FETCH_SOURCE_KEYED_CACHED_METADATA_HANDLER_H_ diff --git a/chromium/third_party/blink/renderer/platform/loader/fetch/source_keyed_cached_metadata_handler_test.cc b/chromium/third_party/blink/renderer/platform/loader/fetch/source_keyed_cached_metadata_handler_test.cc new file mode 100644 index 00000000000..b30c1769416 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/loader/fetch/source_keyed_cached_metadata_handler_test.cc @@ -0,0 +1,459 @@ +// Copyright 2018 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/platform/loader/fetch/source_keyed_cached_metadata_handler.h" + +#include <array> + +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/blink/renderer/platform/crypto.h" +#include "third_party/blink/renderer/platform/testing/testing_platform_support_with_mock_scheduler.h" + +namespace blink { + +namespace { + +class MockSha256WebCryptoDigestor : public WebCryptoDigestor { + public: + virtual bool Consume(const unsigned char* data, unsigned data_size) { + String key(data, data_size); + + auto it = kMapOfHashes.find(key); + + if (it != kMapOfHashes.end()) { + hash_exists_ = true; + hash_ = it->value; + } + + return hash_exists_; + } + + virtual bool Finish(unsigned char*& result_data, unsigned& result_data_size) { + if (hash_exists_) { + result_data = hash_.data(); + result_data_size = hash_.size(); + } + return hash_exists_; + } + + private: + Vector<unsigned char> hash_; + bool hash_exists_; + + HashMap<String, Vector<unsigned char>> kMapOfHashes = { + {"source1", + Vector<unsigned char>{0xc4, 0xd5, 0xe4, 0x35, 0x74, 0x89, 0x3c, 0x3c, + 0xc3, 0xd4, 0xba, 0xba, 0x65, 0x58, 0x92, 0x48, + 0x47, 0x9a, 0x9f, 0xbf, 0xaf, 0x1f, 0x60, 0x8e, + 0xb1, 0x54, 0x1e, 0xc0, 0xc6, 0xfe, 0x63, 0x6f}}, + {"source2", + Vector<unsigned char>{0x99, 0x2f, 0x4e, 0xb2, 0x41, 0xee, 0x6e, 0xef, + 0xe4, 0x92, 0x80, 0x25, 0xa2, 0x74, 0x7d, 0xb0, + 0x8b, 0x91, 0x98, 0x34, 0xc9, 0x3c, 0x5f, 0x57, + 0x41, 0x72, 0x5f, 0xa2, 0x6b, 0x63, 0x38, 0x41}}}; +}; + +// Mock WebCrypto implementation for digest calculation. +class MockDigestWebCrypto : public WebCrypto { + std::unique_ptr<WebCryptoDigestor> CreateDigestor( + WebCryptoAlgorithmId algorithm_id) override { + EXPECT_EQ(algorithm_id, WebCryptoAlgorithmId::kWebCryptoAlgorithmIdSha256); + return std::make_unique<MockSha256WebCryptoDigestor>(); + } +}; + +// Structure holding cache metadata sent to the platform. +struct CacheMetadataEntry { + CacheMetadataEntry(const WebURL& url, + base::Time response_time, + const char* data, + size_t data_size) + : url(url), response_time(response_time) { + this->data.Append(data, data_size); + } + + WebURL url; + base::Time response_time; + Vector<char> data; +}; + +// Mock Platform implementation that provides basic crypto and caching. +class SourceKeyedCachedMetadataHandlerMockPlatform final + : public TestingPlatformSupportWithMockScheduler { + public: + SourceKeyedCachedMetadataHandlerMockPlatform() {} + ~SourceKeyedCachedMetadataHandlerMockPlatform() override = default; + + WebCrypto* Crypto() override { return &mock_web_crypto_; } + + void CacheMetadata(const WebURL& url, + base::Time response_time, + const char* data, + size_t data_size) override { + cache_entries_.emplace_back(url, response_time, data, data_size); + } + + bool HasCacheMetadataFor(const WebURL& url) { + for (const CacheMetadataEntry& entry : cache_entries_) { + if (entry.url == url) { + return true; + } + } + return false; + } + + Vector<CacheMetadataEntry> GetCacheMetadatasFor(const WebURL& url) { + Vector<CacheMetadataEntry> url_entries; + for (const CacheMetadataEntry& entry : cache_entries_) { + if (entry.url == url) { + url_entries.push_back(entry); + } + } + return url_entries; + } + + private: + MockDigestWebCrypto mock_web_crypto_; + Vector<CacheMetadataEntry> cache_entries_; +}; + +// Mock CachedMetadataSender implementation that forwards data to the platform. +class MockCachedMetadataSender final : public CachedMetadataSender { + public: + MockCachedMetadataSender(KURL response_url) : response_url_(response_url) {} + + void Send(const char* data, size_t size) { + Platform::Current()->CacheMetadata(response_url_, response_time_, data, + size); + } + + bool IsServedFromCacheStorage() override { return false; } + + private: + const KURL response_url_; + const Time response_time_; +}; + +template <size_t N> +::testing::AssertionResult CachedMetadataFailure( + const char* failure_msg, + const char* actual_expression, + const std::array<char, N>& expected, + const scoped_refptr<CachedMetadata>& actual) { + ::testing::Message msg; + msg << failure_msg << " for " << actual_expression; + msg << "\n Expected: [" << N << "] { "; + for (size_t i = 0; i < N; ++i) { + if (i > 0) + msg << ", "; + msg << std::hex << static_cast<int>(expected[i]); + } + msg << " }"; + if (actual) { + msg << "\n Actual: [" << actual->size() << "] { "; + for (size_t i = 0; i < actual->size(); ++i) { + if (i > 0) + msg << ", "; + msg << std::hex << static_cast<int>(actual->Data()[i]); + } + msg << " }"; + } else { + msg << "\n Actual: (null)"; + } + + return testing::AssertionFailure() << msg; +} + +template <size_t N> +::testing::AssertionResult CachedMetadataEqual( + const char* expected_expression, + const char* actual_expression, + const std::array<char, N>& expected, + const scoped_refptr<CachedMetadata>& actual) { + if (!actual) { + return CachedMetadataFailure("Expected non-null data", actual_expression, + expected, actual); + } + if (actual->size() != N) { + return CachedMetadataFailure("Wrong size", actual_expression, expected, + actual); + } + const char* actual_data = actual->Data(); + for (size_t i = 0; i < N; ++i) { + if (actual_data[i] != expected[i]) { + return CachedMetadataFailure("Wrong data", actual_expression, expected, + actual); + } + } + + return testing::AssertionSuccess(); +} + +#define EXPECT_METADATA(data_array, cached_metadata) \ + EXPECT_PRED_FORMAT2(CachedMetadataEqual, data_array, cached_metadata) + +} // namespace + +TEST(SourceKeyedCachedMetadataHandlerTest, + HandlerForSource_InitiallyNonNullHandlersWithNullData) { + ScopedTestingPlatformSupport<SourceKeyedCachedMetadataHandlerMockPlatform> + platform; + + KURL url("http://SourceKeyedCachedMetadataHandlerTest.com"); + SourceKeyedCachedMetadataHandler* handler = + new SourceKeyedCachedMetadataHandler( + WTF::TextEncoding(), std::make_unique<MockCachedMetadataSender>(url)); + + WTF::String source1("source1"); + SingleCachedMetadataHandler* source1_handler = + handler->HandlerForSource(source1); + + WTF::String source2("source2"); + SingleCachedMetadataHandler* source2_handler = + handler->HandlerForSource(source2); + + EXPECT_NE(nullptr, source1_handler); + EXPECT_EQ(nullptr, source1_handler->GetCachedMetadata(0xbeef)); + EXPECT_NE(nullptr, source2_handler); + EXPECT_EQ(nullptr, source2_handler->GetCachedMetadata(0x5eed)); +} + +TEST(SourceKeyedCachedMetadataHandlerTest, + HandlerForSource_OneHandlerSetOtherNull) { + ScopedTestingPlatformSupport<SourceKeyedCachedMetadataHandlerMockPlatform> + platform; + + KURL url("http://SourceKeyedCachedMetadataHandlerTest.com"); + SourceKeyedCachedMetadataHandler* handler = + new SourceKeyedCachedMetadataHandler( + WTF::TextEncoding(), std::make_unique<MockCachedMetadataSender>(url)); + + WTF::String source1("source1"); + SingleCachedMetadataHandler* source1_handler = + handler->HandlerForSource(source1); + + WTF::String source2("source2"); + SingleCachedMetadataHandler* source2_handler = + handler->HandlerForSource(source2); + + std::array<char, 3> data1 = {1, 2, 3}; + source1_handler->SetCachedMetadata(0xbeef, data1.data(), data1.size()); + + EXPECT_NE(nullptr, source1_handler); + EXPECT_METADATA(data1, source1_handler->GetCachedMetadata(0xbeef)); + + EXPECT_NE(nullptr, source2_handler); + EXPECT_EQ(nullptr, source2_handler->GetCachedMetadata(0x5eed)); +} + +TEST(SourceKeyedCachedMetadataHandlerTest, HandlerForSource_BothHandlersSet) { + ScopedTestingPlatformSupport<SourceKeyedCachedMetadataHandlerMockPlatform> + platform; + + KURL url("http://SourceKeyedCachedMetadataHandlerTest.com"); + SourceKeyedCachedMetadataHandler* handler = + new SourceKeyedCachedMetadataHandler( + WTF::TextEncoding(), std::make_unique<MockCachedMetadataSender>(url)); + + WTF::String source1("source1"); + SingleCachedMetadataHandler* source1_handler = + handler->HandlerForSource(source1); + + WTF::String source2("source2"); + SingleCachedMetadataHandler* source2_handler = + handler->HandlerForSource(source2); + + std::array<char, 3> data1 = {1, 2, 3}; + source1_handler->SetCachedMetadata(0xbeef, data1.data(), data1.size()); + + std::array<char, 4> data2 = {3, 4, 5, 6}; + source2_handler->SetCachedMetadata(0x5eed, data2.data(), data2.size()); + + EXPECT_NE(nullptr, source1_handler); + EXPECT_METADATA(data1, source1_handler->GetCachedMetadata(0xbeef)); + + EXPECT_NE(nullptr, source2_handler); + EXPECT_METADATA(data2, source2_handler->GetCachedMetadata(0x5eed)); +} + +TEST(SourceKeyedCachedMetadataHandlerTest, Serialize_EmptyClearDoesSend) { + ScopedTestingPlatformSupport<SourceKeyedCachedMetadataHandlerMockPlatform> + platform; + + KURL url("http://SourceKeyedCachedMetadataHandlerTest.com"); + SourceKeyedCachedMetadataHandler* handler = + new SourceKeyedCachedMetadataHandler( + WTF::TextEncoding(), std::make_unique<MockCachedMetadataSender>(url)); + + // Clear and send to the platform + handler->ClearCachedMetadata(CachedMetadataHandler::kSendToPlatform); + + // Load from platform + Vector<CacheMetadataEntry> cache_metadatas = + platform->GetCacheMetadatasFor(url); + + EXPECT_EQ(1u, cache_metadatas.size()); +} + +TEST(SourceKeyedCachedMetadataHandlerTest, Serialize_EachSetDoesSend) { + ScopedTestingPlatformSupport<SourceKeyedCachedMetadataHandlerMockPlatform> + platform; + + KURL url("http://SourceKeyedCachedMetadataHandlerTest.com"); + SourceKeyedCachedMetadataHandler* handler = + new SourceKeyedCachedMetadataHandler( + WTF::TextEncoding(), std::make_unique<MockCachedMetadataSender>(url)); + + WTF::String source1("source1"); + SingleCachedMetadataHandler* source1_handler = + handler->HandlerForSource(source1); + + WTF::String source2("source2"); + SingleCachedMetadataHandler* source2_handler = + handler->HandlerForSource(source2); + + std::array<char, 3> data1 = {1, 2, 3}; + source1_handler->SetCachedMetadata(0xbeef, data1.data(), data1.size()); + + std::array<char, 4> data2 = {3, 4, 5, 6}; + source2_handler->SetCachedMetadata(0x5eed, data2.data(), data2.size()); + + // Load from platform + Vector<CacheMetadataEntry> cache_metadatas = + platform->GetCacheMetadatasFor(url); + + EXPECT_EQ(2u, cache_metadatas.size()); +} + +TEST(SourceKeyedCachedMetadataHandlerTest, Serialize_SetWithNoSendDoesNotSend) { + ScopedTestingPlatformSupport<SourceKeyedCachedMetadataHandlerMockPlatform> + platform; + + KURL url("http://SourceKeyedCachedMetadataHandlerTest.com"); + SourceKeyedCachedMetadataHandler* handler = + new SourceKeyedCachedMetadataHandler( + WTF::TextEncoding(), std::make_unique<MockCachedMetadataSender>(url)); + + WTF::String source1("source1"); + SingleCachedMetadataHandler* source1_handler = + handler->HandlerForSource(source1); + + WTF::String source2("source2"); + SingleCachedMetadataHandler* source2_handler = + handler->HandlerForSource(source2); + + std::array<char, 3> data1 = {1, 2, 3}; + source1_handler->SetCachedMetadata(0xbeef, data1.data(), data1.size(), + CachedMetadataHandler::kCacheLocally); + + std::array<char, 4> data2 = {3, 4, 5, 6}; + source2_handler->SetCachedMetadata(0x5eed, data2.data(), data2.size()); + + // Load from platform + Vector<CacheMetadataEntry> cache_metadatas = + platform->GetCacheMetadatasFor(url); + + EXPECT_EQ(1u, cache_metadatas.size()); +} + +TEST(SourceKeyedCachedMetadataHandlerTest, + SerializeAndDeserialize_NoHandlersSet) { + ScopedTestingPlatformSupport<SourceKeyedCachedMetadataHandlerMockPlatform> + platform; + + KURL url("http://SourceKeyedCachedMetadataHandlerTest.com"); + WTF::String source1("source1"); + WTF::String source2("source2"); + { + SourceKeyedCachedMetadataHandler* handler = + new SourceKeyedCachedMetadataHandler( + WTF::TextEncoding(), + std::make_unique<MockCachedMetadataSender>(url)); + + // Clear and send to the platform + handler->ClearCachedMetadata(CachedMetadataHandler::kSendToPlatform); + } + + // Reload from platform + { + Vector<CacheMetadataEntry> cache_metadatas = + platform->GetCacheMetadatasFor(url); + // Use the last data received by the platform + EXPECT_EQ(1u, cache_metadatas.size()); + CacheMetadataEntry& last_cache_metadata = cache_metadatas[0]; + + SourceKeyedCachedMetadataHandler* handler = + new SourceKeyedCachedMetadataHandler( + WTF::TextEncoding(), + std::make_unique<MockCachedMetadataSender>(url)); + handler->SetSerializedCachedMetadata(last_cache_metadata.data.data(), + last_cache_metadata.data.size()); + + SingleCachedMetadataHandler* source1_handler = + handler->HandlerForSource(source1); + SingleCachedMetadataHandler* source2_handler = + handler->HandlerForSource(source2); + + EXPECT_NE(nullptr, source1_handler); + EXPECT_EQ(nullptr, source1_handler->GetCachedMetadata(0xbeef)); + + EXPECT_NE(nullptr, source2_handler); + EXPECT_EQ(nullptr, source2_handler->GetCachedMetadata(0x5eed)); + } +} + +TEST(SourceKeyedCachedMetadataHandlerTest, + SerializeAndDeserialize_BothHandlersSet) { + ScopedTestingPlatformSupport<SourceKeyedCachedMetadataHandlerMockPlatform> + platform; + + KURL url("http://SourceKeyedCachedMetadataHandlerTest.com"); + WTF::String source1("source1"); + WTF::String source2("source2"); + std::array<char, 3> data1 = {1, 2, 3}; + std::array<char, 4> data2 = {3, 4, 5, 6}; + { + SourceKeyedCachedMetadataHandler* handler = + new SourceKeyedCachedMetadataHandler( + WTF::TextEncoding(), + std::make_unique<MockCachedMetadataSender>(url)); + + SingleCachedMetadataHandler* source1_handler = + handler->HandlerForSource(source1); + SingleCachedMetadataHandler* source2_handler = + handler->HandlerForSource(source2); + + source1_handler->SetCachedMetadata(0xbeef, data1.data(), data1.size()); + source2_handler->SetCachedMetadata(0x5eed, data2.data(), data2.size()); + } + + // Reload from platform + { + Vector<CacheMetadataEntry> cache_metadatas = + platform->GetCacheMetadatasFor(url); + // Use the last data received by the platform + EXPECT_EQ(2u, cache_metadatas.size()); + CacheMetadataEntry& last_cache_metadata = cache_metadatas[1]; + + SourceKeyedCachedMetadataHandler* handler = + new SourceKeyedCachedMetadataHandler( + WTF::TextEncoding(), + std::make_unique<MockCachedMetadataSender>(url)); + handler->SetSerializedCachedMetadata(last_cache_metadata.data.data(), + last_cache_metadata.data.size()); + + SingleCachedMetadataHandler* source1_handler = + handler->HandlerForSource(source1); + SingleCachedMetadataHandler* source2_handler = + handler->HandlerForSource(source2); + + EXPECT_NE(nullptr, source1_handler); + EXPECT_METADATA(data1, source1_handler->GetCachedMetadata(0xbeef)); + + EXPECT_NE(nullptr, source2_handler); + EXPECT_METADATA(data2, source2_handler->GetCachedMetadata(0x5eed)); + } +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/platform/loader/fetch/substitute_data.h b/chromium/third_party/blink/renderer/platform/loader/fetch/substitute_data.h new file mode 100644 index 00000000000..2dd436b2711 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/loader/fetch/substitute_data.h @@ -0,0 +1,70 @@ +/* + * 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_PLATFORM_LOADER_FETCH_SUBSTITUTE_DATA_H_ +#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_LOADER_FETCH_SUBSTITUTE_DATA_H_ + +#include "base/memory/scoped_refptr.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/wtf/allocator.h" + +namespace blink { + +class SubstituteData { + DISALLOW_NEW(); + + public: + SubstituteData() = default; + + SubstituteData(scoped_refptr<SharedBuffer> content) + : SubstituteData(content, "text/html", "UTF-8", KURL()) {} + + SubstituteData(scoped_refptr<SharedBuffer> content, + const AtomicString& mime_type, + const AtomicString& text_encoding, + const KURL& failing_url) + : content_(std::move(content)), + mime_type_(mime_type), + text_encoding_(text_encoding), + failing_url_(failing_url) {} + + bool IsValid() const { return content_.get(); } + + SharedBuffer* Content() const { return content_.get(); } + const AtomicString& MimeType() const { return mime_type_; } + const AtomicString& TextEncoding() const { return text_encoding_; } + const KURL& FailingURL() const { return failing_url_; } + + private: + scoped_refptr<SharedBuffer> content_; + AtomicString mime_type_; + AtomicString text_encoding_; + KURL failing_url_; +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_PLATFORM_LOADER_FETCH_SUBSTITUTE_DATA_H_ diff --git a/chromium/third_party/blink/renderer/platform/loader/fetch/text_resource_decoder_options.cc b/chromium/third_party/blink/renderer/platform/loader/fetch/text_resource_decoder_options.cc new file mode 100644 index 00000000000..b0b42a8bc22 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/loader/fetch/text_resource_decoder_options.cc @@ -0,0 +1,65 @@ +// 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/platform/language.h" +#include "third_party/blink/renderer/platform/loader/fetch/text_resource_decoder_options.h" + +namespace blink { + +TextResourceDecoderOptions::TextResourceDecoderOptions( + ContentType content_type, + const WTF::TextEncoding& default_encoding) + : TextResourceDecoderOptions(kUseContentAndBOMBasedDetection, + content_type, + default_encoding, + nullptr, + KURL()) {} + +TextResourceDecoderOptions +TextResourceDecoderOptions::CreateAlwaysUseUTF8ForText() { + return TextResourceDecoderOptions(kAlwaysUseUTF8ForText, kPlainTextContent, + UTF8Encoding(), nullptr, NullURL()); +} + +TextResourceDecoderOptions TextResourceDecoderOptions::CreateWithAutoDetection( + ContentType content_type, + const WTF::TextEncoding& default_encoding, + const WTF::TextEncoding& hint_encoding, + const KURL& hint_url) { + return TextResourceDecoderOptions(kUseAllAutoDetection, content_type, + default_encoding, hint_encoding.GetName(), + hint_url); +} + +TextResourceDecoderOptions::TextResourceDecoderOptions( + EncodingDetectionOption encoding_detection_option, + ContentType content_type, + const WTF::TextEncoding& default_encoding, + const char* hint_encoding, + const KURL& hint_url) + : encoding_detection_option_(encoding_detection_option), + content_type_(content_type), + default_encoding_(default_encoding), + use_lenient_xml_decoding_(false), + hint_encoding_(hint_encoding), + hint_url_(hint_url) { + hint_language_[0] = 0; + if (encoding_detection_option_ == kUseAllAutoDetection) { + // Checking empty URL helps unit testing. Providing DefaultLanguage() is + // sometimes difficult in tests. + if (!hint_url_.IsEmpty()) { + // This object is created in the main thread, but used in another thread. + // We should not share an AtomicString. + AtomicString locale = DefaultLanguage(); + if (locale.length() >= 2) { + // DefaultLanguage() is always an ASCII string. + hint_language_[0] = static_cast<char>(locale[0]); + hint_language_[1] = static_cast<char>(locale[1]); + hint_language_[2] = 0; + } + } + } +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/platform/loader/fetch/text_resource_decoder_options.h b/chromium/third_party/blink/renderer/platform/loader/fetch/text_resource_decoder_options.h new file mode 100644 index 00000000000..b38a63a6cf9 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/loader/fetch/text_resource_decoder_options.h @@ -0,0 +1,99 @@ +// 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_PLATFORM_LOADER_FETCH_TEXT_RESOURCE_DECODER_OPTIONS_H_ +#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_LOADER_FETCH_TEXT_RESOURCE_DECODER_OPTIONS_H_ + +#include "third_party/blink/renderer/platform/platform_export.h" +#include "third_party/blink/renderer/platform/weborigin/kurl.h" +#include "third_party/blink/renderer/platform/wtf/text/text_encoding.h" + +namespace blink { + +class PLATFORM_EXPORT TextResourceDecoderOptions final { + public: + enum ContentType { + kPlainTextContent, + kHTMLContent, + kJSONContent, + kXMLContent, + kCSSContent, + kMaxContentType = kCSSContent + }; // PlainText only checks for BOM. + + explicit TextResourceDecoderOptions( + ContentType, + const WTF::TextEncoding& default_encoding = WTF::TextEncoding()); + + // Corresponds to utf-8 decode in Encoding spec: + // https://encoding.spec.whatwg.org/#utf-8-decode. + static TextResourceDecoderOptions CreateAlwaysUseUTF8ForText(); + + static TextResourceDecoderOptions CreateWithAutoDetection( + ContentType, + const WTF::TextEncoding& default_encoding, + const WTF::TextEncoding& hint_encoding, + const KURL& hint_url); + + void SetUseLenientXMLDecoding() { use_lenient_xml_decoding_ = true; } + void OverrideContentType(ContentType content_type) { + if (encoding_detection_option_ != kAlwaysUseUTF8ForText) + content_type_ = content_type; + } + + static ContentType DetermineContentType(const String& mime_type); + + // TextResourceDecoder does three kind of encoding detection: + // 1. By BOM, + // 2. By Content if |content_type_| is not |kPlainTextContext| + // (e.g. <meta> tag for HTML), and + // 3. By DetectTextEncoding(). + enum EncodingDetectionOption { + // Use 1. + 2. + 3. + kUseAllAutoDetection, + + // Use 1. + 2. + kUseContentAndBOMBasedDetection, + + // Use None of them. + // |content_type_| must be |kPlainTextContent| and + // |default_encoding_| must be UTF8Encoding. + // This doesn't change encoding based on BOMs, but still processes + // utf-8 BOMs so that utf-8 BOMs don't appear in the decoded result. + kAlwaysUseUTF8ForText + }; + + EncodingDetectionOption GetEncodingDetectionOption() const { + return encoding_detection_option_; + } + ContentType GetContentType() const { return content_type_; } + const WTF::TextEncoding& DefaultEncoding() const { return default_encoding_; } + bool GetUseLenientXMLDecoding() const { return use_lenient_xml_decoding_; } + + const char* HintEncoding() const { return hint_encoding_; } + const KURL& HintURL() const { return hint_url_; } + const char* HintLanguage() const { return hint_language_; } + + private: + TextResourceDecoderOptions(EncodingDetectionOption, + ContentType, + const WTF::TextEncoding& default_encoding, + const char* hint_encoding, + const KURL& hint_url); + + EncodingDetectionOption encoding_detection_option_; + ContentType content_type_; + WTF::TextEncoding default_encoding_; + bool use_lenient_xml_decoding_; // Don't stop on XML decoding errors. + + // Hints for DetectTextEncoding(). + // Only used when |encoding_detection_option_| == |kUseAllAutoDetection|. + const char* hint_encoding_; + KURL hint_url_; + char hint_language_[3]; +}; + +} // namespace blink + +#endif diff --git a/chromium/third_party/blink/renderer/platform/loader/fetch/unique_identifier.cc b/chromium/third_party/blink/renderer/platform/loader/fetch/unique_identifier.cc new file mode 100644 index 00000000000..67cb66ab375 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/loader/fetch/unique_identifier.cc @@ -0,0 +1,43 @@ +/* + * 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 <atomic> + +#include "third_party/blink/renderer/platform/loader/fetch/unique_identifier.h" + +namespace blink { + +static std::atomic_ulong g_unique_identifier(1); + +unsigned long CreateUniqueIdentifier() { + return g_unique_identifier.fetch_add(1, std::memory_order_relaxed); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/platform/loader/fetch/unique_identifier.h b/chromium/third_party/blink/renderer/platform/loader/fetch/unique_identifier.h new file mode 100644 index 00000000000..b408454b093 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/loader/fetch/unique_identifier.h @@ -0,0 +1,41 @@ +/* + * 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_PLATFORM_LOADER_FETCH_UNIQUE_IDENTIFIER_H_ +#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_LOADER_FETCH_UNIQUE_IDENTIFIER_H_ + +#include "third_party/blink/renderer/platform/platform_export.h" + +namespace blink { + +PLATFORM_EXPORT unsigned long CreateUniqueIdentifier(); +} + +#endif // THIRD_PARTY_BLINK_RENDERER_PLATFORM_LOADER_FETCH_UNIQUE_IDENTIFIER_H_ diff --git a/chromium/third_party/blink/renderer/platform/loader/link_header.cc b/chromium/third_party/blink/renderer/platform/loader/link_header.cc new file mode 100644 index 00000000000..d314eebcbca --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/loader/link_header.cc @@ -0,0 +1,102 @@ +// 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/platform/loader/link_header.h" + +#include "base/strings/string_util.h" +#include "components/link_header_util/link_header_util.h" +#include "third_party/blink/renderer/platform/wtf/text/parsing_utilities.h" + +namespace blink { + +// Verify that the parameter is a link-extension which according to spec doesn't +// have to have a value. +static bool IsExtensionParameter(LinkHeader::LinkParameterName name) { + return name >= LinkHeader::kLinkParameterUnknown; +} + +static LinkHeader::LinkParameterName ParameterNameFromString( + base::StringPiece name) { + if (base::EqualsCaseInsensitiveASCII(name, "rel")) + return LinkHeader::kLinkParameterRel; + if (base::EqualsCaseInsensitiveASCII(name, "anchor")) + return LinkHeader::kLinkParameterAnchor; + if (base::EqualsCaseInsensitiveASCII(name, "crossorigin")) + return LinkHeader::kLinkParameterCrossOrigin; + if (base::EqualsCaseInsensitiveASCII(name, "title")) + return LinkHeader::kLinkParameterTitle; + if (base::EqualsCaseInsensitiveASCII(name, "media")) + return LinkHeader::kLinkParameterMedia; + if (base::EqualsCaseInsensitiveASCII(name, "type")) + return LinkHeader::kLinkParameterType; + if (base::EqualsCaseInsensitiveASCII(name, "rev")) + return LinkHeader::kLinkParameterRev; + if (base::EqualsCaseInsensitiveASCII(name, "hreflang")) + return LinkHeader::kLinkParameterHreflang; + if (base::EqualsCaseInsensitiveASCII(name, "as")) + return LinkHeader::kLinkParameterAs; + if (base::EqualsCaseInsensitiveASCII(name, "nonce")) + return LinkHeader::kLinkParameterNonce; + if (base::EqualsCaseInsensitiveASCII(name, "integrity")) + return LinkHeader::kLinkParameterIntegrity; + if (base::EqualsCaseInsensitiveASCII(name, "srcset")) + return LinkHeader::kLinkParameterSrcset; + if (base::EqualsCaseInsensitiveASCII(name, "imgsizes")) + return LinkHeader::kLinkParameterImgsizes; + return LinkHeader::kLinkParameterUnknown; +} + +void LinkHeader::SetValue(LinkParameterName name, const String& value) { + if (name == kLinkParameterRel && !rel_) + rel_ = value.DeprecatedLower(); + else if (name == kLinkParameterAnchor) + is_valid_ = false; + else if (name == kLinkParameterCrossOrigin) + cross_origin_ = value; + else if (name == kLinkParameterAs) + as_ = value.DeprecatedLower(); + else if (name == kLinkParameterType) + mime_type_ = value.DeprecatedLower(); + else if (name == kLinkParameterMedia) + media_ = value.DeprecatedLower(); + else if (name == kLinkParameterNonce) + nonce_ = value; + else if (name == kLinkParameterIntegrity) + integrity_ = value; + else if (name == kLinkParameterSrcset) + srcset_ = value; + else if (name == kLinkParameterImgsizes) + imgsizes_ = value; +} + +template <typename Iterator> +LinkHeader::LinkHeader(Iterator begin, Iterator end) : is_valid_(true) { + std::string url; + std::unordered_map<std::string, base::Optional<std::string>> params; + is_valid_ = link_header_util::ParseLinkHeaderValue(begin, end, &url, ¶ms); + if (!is_valid_) + return; + + url_ = String(&url[0], url.length()); + for (const auto& param : params) { + LinkParameterName name = ParameterNameFromString(param.first); + if (!IsExtensionParameter(name) && !param.second) + is_valid_ = false; + std::string value = param.second.value_or(""); + SetValue(name, String(&value[0], value.length())); + } +} + +LinkHeaderSet::LinkHeaderSet(const String& header) { + if (header.IsNull()) + return; + + DCHECK(header.Is8Bit()) << "Headers should always be 8 bit"; + std::string header_string(reinterpret_cast<const char*>(header.Characters8()), + header.length()); + for (const auto& value : link_header_util::SplitLinkHeader(header_string)) + header_set_.push_back(LinkHeader(value.first, value.second)); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/platform/loader/link_header.h b/chromium/third_party/blink/renderer/platform/loader/link_header.h new file mode 100644 index 00000000000..61d5e41310f --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/loader/link_header.h @@ -0,0 +1,87 @@ +// 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_PLATFORM_LOADER_LINK_HEADER_H_ +#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_LOADER_LINK_HEADER_H_ + +#include "third_party/blink/renderer/platform/platform_export.h" +#include "third_party/blink/renderer/platform/wtf/allocator.h" +#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h" + +namespace blink { + +class LinkHeader { + DISALLOW_NEW_EXCEPT_PLACEMENT_NEW(); + + public: + const String& Url() const { return url_; } + const String& Rel() const { return rel_; } + const String& As() const { return as_; } + const String& MimeType() const { return mime_type_; } + const String& Media() const { return media_; } + const String& CrossOrigin() const { return cross_origin_; } + const String& Nonce() const { return nonce_; } + const String& Integrity() const { return integrity_; } + const String& Srcset() const { return srcset_; } + const String& Imgsizes() const { return imgsizes_; } + bool Valid() const { return is_valid_; } + + enum LinkParameterName { + kLinkParameterRel, + kLinkParameterAnchor, + kLinkParameterTitle, + kLinkParameterMedia, + kLinkParameterType, + kLinkParameterRev, + kLinkParameterHreflang, + // Beyond this point, only link-extension parameters + kLinkParameterUnknown, + kLinkParameterCrossOrigin, + kLinkParameterAs, + kLinkParameterNonce, + kLinkParameterIntegrity, + kLinkParameterSrcset, + kLinkParameterImgsizes, + }; + + private: + friend class LinkHeaderSet; + + template <typename Iterator> + LinkHeader(Iterator begin, Iterator end); + void SetValue(LinkParameterName, const String& value); + + String url_; + String rel_; + String as_; + String mime_type_; + String media_; + String cross_origin_; + String nonce_; + String integrity_; + String srcset_; + String imgsizes_; + bool is_valid_; +}; + +class PLATFORM_EXPORT LinkHeaderSet { + STACK_ALLOCATED(); + + public: + LinkHeaderSet(const String& header); + + Vector<LinkHeader>::const_iterator begin() const { + return header_set_.begin(); + } + Vector<LinkHeader>::const_iterator end() const { return header_set_.end(); } + LinkHeader& operator[](size_t i) { return header_set_[i]; } + size_t size() { return header_set_.size(); } + + private: + Vector<LinkHeader> header_set_; +}; + +} // namespace blink + +#endif diff --git a/chromium/third_party/blink/renderer/platform/loader/link_header_test.cc b/chromium/third_party/blink/renderer/platform/loader/link_header_test.cc new file mode 100644 index 00000000000..a82f7b897fa --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/loader/link_header_test.cc @@ -0,0 +1,287 @@ +// 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/platform/loader/link_header.h" + +#include <base/macros.h> +#include "testing/gtest/include/gtest/gtest.h" + +namespace blink { +namespace { + +TEST(LinkHeaderTest, Empty) { + String null_string; + LinkHeaderSet null_header_set(null_string); + ASSERT_EQ(null_header_set.size(), unsigned(0)); + String empty_string(""); + LinkHeaderSet empty_header_set(empty_string); + ASSERT_EQ(empty_header_set.size(), unsigned(0)); +} + +struct SingleTestCase { + const char* header_value; + bool valid; + const char* url; + const char* rel; + const char* as; + const char* media; +} g_single_test_cases[] = { + {"</images/cat.jpg>; rel=prefetch", true, "/images/cat.jpg", "prefetch", "", + ""}, + {"</images/cat.jpg>;rel=prefetch", true, "/images/cat.jpg", "prefetch", "", + ""}, + {"</images/cat.jpg> ;rel=prefetch", true, "/images/cat.jpg", "prefetch", + "", ""}, + {"</images/cat.jpg> ; rel=prefetch", true, "/images/cat.jpg", + "prefetch", "", ""}, + {"< /images/cat.jpg> ; rel=prefetch", true, "/images/cat.jpg", + "prefetch", "", ""}, + {"</images/cat.jpg > ; rel=prefetch", true, "/images/cat.jpg", + "prefetch", "", ""}, + {"</images/cat.jpg wutwut> ; rel=prefetch", true, + "/images/cat.jpg wutwut", "prefetch", "", ""}, + {"</images/cat.jpg wutwut \t > ; rel=prefetch", true, + "/images/cat.jpg wutwut", "prefetch", "", ""}, + {"</images/cat.jpg>; rel=prefetch ", true, "/images/cat.jpg", "prefetch", + "", ""}, + {"</images/cat.jpg>; Rel=prefetch ", true, "/images/cat.jpg", "prefetch", + "", ""}, + {"</images/cat.jpg>; Rel=PReFetCh ", true, "/images/cat.jpg", "prefetch", + "", ""}, + {"</images/cat.jpg>; rel=prefetch; rel=somethingelse", true, + "/images/cat.jpg", "prefetch", "", ""}, + {" </images/cat.jpg>; rel=prefetch ", true, "/images/cat.jpg", + "prefetch", "", ""}, + {"\t </images/cat.jpg>; rel=prefetch ", true, "/images/cat.jpg", + "prefetch", "", ""}, + {"</images/cat.jpg>\t\t ; \trel=prefetch \t ", true, "/images/cat.jpg", + "prefetch", "", ""}, + {"\f</images/cat.jpg>\t\t ; \trel=prefetch \t ", false}, + {"</images/cat.jpg>; rel= prefetch", true, "/images/cat.jpg", "prefetch", + "", ""}, + {"<../images/cat.jpg?dog>; rel= prefetch", true, "../images/cat.jpg?dog", + "prefetch", "", ""}, + {"</images/cat.jpg>; rel =prefetch", true, "/images/cat.jpg", "prefetch", + "", ""}, + {"</images/cat.jpg>; rel pel=prefetch", false}, + {"< /images/cat.jpg>", true, "/images/cat.jpg", "", "", ""}, + {"</images/cat.jpg>; rel =", false}, + {"</images/cat.jpg>; wut=sup; rel =prefetch", true, "/images/cat.jpg", + "prefetch", "", ""}, + {"</images/cat.jpg>; wut=sup ; rel =prefetch", true, "/images/cat.jpg", + "prefetch", "", ""}, + {"</images/cat.jpg>; wut=sup ; rel =prefetch \t ;", true, + "/images/cat.jpg", "prefetch", "", ""}, + {"</images/cat.jpg> wut=sup ; rel =prefetch \t ;", false}, + {"< /images/cat.jpg", false}, + {"< http://wut.com/ sdfsdf ?sd>; rel=dns-prefetch", true, + "http://wut.com/ sdfsdf ?sd", "dns-prefetch", "", ""}, + {"< http://wut.com/%20%20%3dsdfsdf?sd>; rel=dns-prefetch", true, + "http://wut.com/%20%20%3dsdfsdf?sd", "dns-prefetch", "", ""}, + {"< http://wut.com/dfsdf?sdf=ghj&wer=rty>; rel=prefetch", true, + "http://wut.com/dfsdf?sdf=ghj&wer=rty", "prefetch", "", ""}, + {"< http://wut.com/dfsdf?sdf=ghj&wer=rty>;;;;; rel=prefetch", true, + "http://wut.com/dfsdf?sdf=ghj&wer=rty", "prefetch", "", ""}, + {"< http://wut.com/%20%20%3dsdfsdf?sd>; rel=preload;as=image", true, + "http://wut.com/%20%20%3dsdfsdf?sd", "preload", "image", ""}, + {"< http://wut.com/%20%20%3dsdfsdf?sd>; rel=preload;as=whatever", true, + "http://wut.com/%20%20%3dsdfsdf?sd", "preload", "whatever", ""}, + {"</images/cat.jpg>; anchor=foo; rel=prefetch;", false}, + {"</images/cat.jpg>; rel=prefetch;anchor=foo ", false}, + {"</images/cat.jpg>; anchor='foo'; rel=prefetch;", false}, + {"</images/cat.jpg>; rel=prefetch;anchor='foo' ", false}, + {"</images/cat.jpg>; rel=prefetch;anchor='' ", false}, + {"</images/cat.jpg>; rel=prefetch;", true, "/images/cat.jpg", "prefetch", + "", ""}, + {"</images/cat.jpg>; rel=prefetch ;", true, "/images/cat.jpg", + "prefetch", "", ""}, + {"</images/ca,t.jpg>; rel=prefetch ;", true, "/images/ca,t.jpg", + "prefetch", "", ""}, + {"<simple.css>; rel=stylesheet; title=\"title with a DQUOTE and " + "backslash\"", + true, "simple.css", "stylesheet", "", ""}, + {"<simple.css>; rel=stylesheet; title=\"title with a DQUOTE \\\" and " + "backslash: \\\"", + false}, + {"<simple.css>; title=\"title with a DQUOTE \\\" and backslash: \"; " + "rel=stylesheet; ", + true, "simple.css", "stylesheet", "", ""}, + {"<simple.css>; title=\'title with a DQUOTE \\\' and backslash: \'; " + "rel=stylesheet; ", + true, "simple.css", "stylesheet", "", ""}, + {"<simple.css>; title=\"title with a DQUOTE \\\" and ;backslash,: \"; " + "rel=stylesheet; ", + true, "simple.css", "stylesheet", "", ""}, + {"<simple.css>; title=\"title with a DQUOTE \' and ;backslash,: \"; " + "rel=stylesheet; ", + true, "simple.css", "stylesheet", "", ""}, + {"<simple.css>; title=\"\"; rel=stylesheet; ", true, "simple.css", + "stylesheet", "", ""}, + {"<simple.css>; title=\"\"; rel=\"stylesheet\"; ", true, "simple.css", + "stylesheet", "", ""}, + {"<simple.css>; rel=stylesheet; title=\"", false}, + {"<simple.css>; rel=stylesheet; title=\"\"", true, "simple.css", + "stylesheet", "", ""}, + {"<simple.css>; rel=\"stylesheet\"; title=\"", false}, + {"<simple.css>; rel=\";style,sheet\"; title=\"", false}, + {"<simple.css>; rel=\"bla'sdf\"; title=\"", false}, + {"<simple.css>; rel=\"\"; title=\"\"", true, "simple.css", "", "", ""}, + {"<simple.css>; rel=''; title=\"\"", true, "simple.css", "''", "", ""}, + {"<simple.css>; rel=''; title=", false}, + {"<simple.css>; rel=''; title", false}, + {"<simple.css>; rel=''; media", false}, + {"<simple.css>; rel=''; hreflang", false}, + {"<simple.css>; rel=''; type", false}, + {"<simple.css>; rel=''; rev", false}, + {"<simple.css>; rel=''; bla", true, "simple.css", "''", "", ""}, + {"<simple.css>; rel='prefetch", true, "simple.css", "'prefetch", "", ""}, + {"<simple.css>; rel=\"prefetch", false}, + {"<simple.css>; rel=\"", false}, + {"<http://whatever.com>; rel=preconnect; valid!", true, + "http://whatever.com", "preconnect", "", ""}, + {"<http://whatever.com>; rel=preconnect; valid$", true, + "http://whatever.com", "preconnect", "", ""}, + {"<http://whatever.com>; rel=preconnect; invalid@", false}, + {"<http://whatever.com>; rel=preconnect; invalid*", false}, + {"</images/cat.jpg>; rel=prefetch;media='(max-width: 5000px)'", true, + "/images/cat.jpg", "prefetch", "", "'(max-width: 5000px)'"}, + {"</images/cat.jpg>; rel=prefetch;media=\"(max-width: 5000px)\"", true, + "/images/cat.jpg", "prefetch", "", "(max-width: 5000px)"}, + {"</images/cat.jpg>; rel=prefetch;media=(max-width:5000px)", true, + "/images/cat.jpg", "prefetch", "", "(max-width:5000px)"}, +}; + +void PrintTo(const SingleTestCase& test, std::ostream* os) { + *os << testing::PrintToString(test.header_value); +} + +class SingleLinkHeaderTest : public testing::TestWithParam<SingleTestCase> {}; + +// Test the cases with a single header +TEST_P(SingleLinkHeaderTest, Single) { + const SingleTestCase test_case = GetParam(); + LinkHeaderSet header_set(test_case.header_value); + ASSERT_EQ(1u, header_set.size()); + LinkHeader& header = header_set[0]; + EXPECT_EQ(test_case.valid, header.Valid()); + if (test_case.valid) { + EXPECT_STREQ(test_case.url, header.Url().Ascii().data()); + EXPECT_STREQ(test_case.rel, header.Rel().Ascii().data()); + EXPECT_STREQ(test_case.as, header.As().Ascii().data()); + EXPECT_STREQ(test_case.media, header.Media().Ascii().data()); + } +} + +INSTANTIATE_TEST_CASE_P(LinkHeaderTest, + SingleLinkHeaderTest, + testing::ValuesIn(g_single_test_cases)); + +struct DoubleTestCase { + const char* header_value; + const char* url; + const char* rel; + bool valid; + const char* url2; + const char* rel2; + bool valid2; +} g_double_test_cases[] = { + {"<ybg.css>; rel=stylesheet, <simple.css>; rel=stylesheet", "ybg.css", + "stylesheet", true, "simple.css", "stylesheet", true}, + {"<ybg.css>; rel=stylesheet,<simple.css>; rel=stylesheet", "ybg.css", + "stylesheet", true, "simple.css", "stylesheet", true}, + {"<ybg.css>; rel=stylesheet;crossorigin,<simple.css>; rel=stylesheet", + "ybg.css", "stylesheet", true, "simple.css", "stylesheet", true}, + {"<hel,lo.css>; rel=stylesheet; title=\"foo,bar\", <simple.css>; " + "rel=stylesheet; title=\"foo;bar\"", + "hel,lo.css", "stylesheet", true, "simple.css", "stylesheet", true}, +}; + +void PrintTo(const DoubleTestCase& test, std::ostream* os) { + *os << testing::PrintToString(test.header_value); +} + +class DoubleLinkHeaderTest : public testing::TestWithParam<DoubleTestCase> {}; + +TEST_P(DoubleLinkHeaderTest, Double) { + const DoubleTestCase test_case = GetParam(); + LinkHeaderSet header_set(test_case.header_value); + ASSERT_EQ(2u, header_set.size()); + LinkHeader& header1 = header_set[0]; + LinkHeader& header2 = header_set[1]; + EXPECT_STREQ(test_case.url, header1.Url().Ascii().data()); + EXPECT_STREQ(test_case.rel, header1.Rel().Ascii().data()); + EXPECT_EQ(test_case.valid, header1.Valid()); + EXPECT_STREQ(test_case.url2, header2.Url().Ascii().data()); + EXPECT_STREQ(test_case.rel2, header2.Rel().Ascii().data()); + EXPECT_EQ(test_case.valid2, header2.Valid()); +} + +INSTANTIATE_TEST_CASE_P(LinkHeaderTest, + DoubleLinkHeaderTest, + testing::ValuesIn(g_double_test_cases)); + +struct CrossOriginTestCase { + const char* header_value; + const char* url; + const char* rel; + const char* crossorigin; + bool valid; +} g_cross_origin_test_cases[] = { + {"<http://whatever.com>; rel=preconnect", "http://whatever.com", + "preconnect", nullptr, true}, + {"<http://whatever.com>; rel=preconnect; crossorigin=", "", "", "", false}, + {"<http://whatever.com>; rel=preconnect; crossorigin", + "http://whatever.com", "preconnect", "", true}, + {"<http://whatever.com>; rel=preconnect; crossorigin ", + "http://whatever.com", "preconnect", "", true}, + {"<http://whatever.com>; rel=preconnect; crossorigin;", + "http://whatever.com", "preconnect", "", true}, + {"<http://whatever.com>; rel=preconnect; crossorigin, " + "<http://whatever2.com>; rel=preconnect", + "http://whatever.com", "preconnect", "", true}, + {"<http://whatever.com>; rel=preconnect; crossorigin , " + "<http://whatever2.com>; rel=preconnect", + "http://whatever.com", "preconnect", "", true}, + {"<http://whatever.com>; rel=preconnect; " + "crossorigin,<http://whatever2.com>; rel=preconnect", + "http://whatever.com", "preconnect", "", true}, + {"<http://whatever.com>; rel=preconnect; crossorigin=anonymous", + "http://whatever.com", "preconnect", "anonymous", true}, + {"<http://whatever.com>; rel=preconnect; crossorigin=use-credentials", + "http://whatever.com", "preconnect", "use-credentials", true}, + {"<http://whatever.com>; rel=preconnect; crossorigin=whatever", + "http://whatever.com", "preconnect", "whatever", true}, + {"<http://whatever.com>; rel=preconnect; crossorig|in=whatever", + "http://whatever.com", "preconnect", nullptr, true}, + {"<http://whatever.com>; rel=preconnect; crossorigin|=whatever", + "http://whatever.com", "preconnect", nullptr, true}, +}; + +void PrintTo(const CrossOriginTestCase& test, std::ostream* os) { + *os << testing::PrintToString(test.header_value); +} + +class CrossOriginLinkHeaderTest + : public testing::TestWithParam<CrossOriginTestCase> {}; + +TEST_P(CrossOriginLinkHeaderTest, CrossOrigin) { + const CrossOriginTestCase test_case = GetParam(); + LinkHeaderSet header_set(test_case.header_value); + ASSERT_GE(header_set.size(), 1u); + LinkHeader& header = header_set[0]; + EXPECT_STREQ(test_case.url, header.Url().Ascii().data()); + EXPECT_STREQ(test_case.rel, header.Rel().Ascii().data()); + EXPECT_EQ(test_case.valid, header.Valid()); + if (!test_case.crossorigin) + EXPECT_TRUE(header.CrossOrigin().IsNull()); + else + EXPECT_STREQ(test_case.crossorigin, header.CrossOrigin().Ascii().data()); +} + +INSTANTIATE_TEST_CASE_P(LinkHeaderTest, + CrossOriginLinkHeaderTest, + testing::ValuesIn(g_cross_origin_test_cases)); + +} // namespace +} // namespace blink diff --git a/chromium/third_party/blink/renderer/platform/loader/subresource_integrity.cc b/chromium/third_party/blink/renderer/platform/loader/subresource_integrity.cc new file mode 100644 index 00000000000..7d6141afa47 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/loader/subresource_integrity.cc @@ -0,0 +1,503 @@ +// 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/platform/loader/subresource_integrity.h" + +#include "third_party/blink/public/platform/web_crypto.h" +#include "third_party/blink/public/platform/web_crypto_algorithm.h" +#include "third_party/blink/renderer/platform/crypto.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource.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/ascii_ctype.h" +#include "third_party/blink/renderer/platform/wtf/dtoa/utils.h" +#include "third_party/blink/renderer/platform/wtf/text/base64.h" +#include "third_party/blink/renderer/platform/wtf/text/parsing_utilities.h" +#include "third_party/blink/renderer/platform/wtf/text/string_utf8_adaptor.h" +#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h" +#include "third_party/blink/renderer/platform/wtf/vector.h" +#include "third_party/boringssl/src/include/openssl/curve25519.h" + +namespace blink { + +// FIXME: This should probably use common functions with ContentSecurityPolicy. +static bool IsIntegrityCharacter(UChar c) { + // Check if it's a base64 encoded value. We're pretty loose here, as there's + // not much risk in it, and it'll make it simpler for developers. + return IsASCIIAlphanumeric(c) || c == '_' || c == '-' || c == '+' || + c == '/' || c == '='; +} + +static bool IsValueCharacter(UChar c) { + // VCHAR per https://tools.ietf.org/html/rfc5234#appendix-B.1 + return c >= 0x21 && c <= 0x7e; +} + +static bool DigestsEqual(const DigestValue& digest1, + const DigestValue& digest2) { + if (digest1.size() != digest2.size()) + return false; + + for (size_t i = 0; i < digest1.size(); i++) { + if (digest1[i] != digest2[i]) + return false; + } + + return true; +} + +inline bool IsSpaceOrComma(UChar c) { + return IsASCIISpace(c) || c == ','; +} + +static String DigestToString(const DigestValue& digest) { + return Base64Encode(reinterpret_cast<const char*>(digest.data()), + digest.size(), kBase64DoNotInsertLFs); +} + +void SubresourceIntegrity::ReportInfo::AddUseCount(UseCounterFeature feature) { + use_counts_.push_back(feature); +} + +void SubresourceIntegrity::ReportInfo::AddConsoleErrorMessage( + const String& message) { + console_error_messages_.push_back(message); +} + +void SubresourceIntegrity::ReportInfo::Clear() { + use_counts_.clear(); + console_error_messages_.clear(); +} + +bool SubresourceIntegrity::CheckSubresourceIntegrity( + const IntegrityMetadataSet& metadata_set, + const char* content, + size_t size, + const KURL& resource_url, + const Resource& resource, + ReportInfo& report_info) { + if (!resource.IsSameOriginOrCORSSuccessful()) { + report_info.AddConsoleErrorMessage( + "Subresource Integrity: The resource '" + resource_url.ElidedString() + + "' has an integrity attribute, but the resource " + "requires the request to be CORS enabled to check " + "the integrity, and it is not. The resource has been " + "blocked because the integrity cannot be enforced."); + report_info.AddUseCount(ReportInfo::UseCounterFeature:: + kSRIElementIntegrityAttributeButIneligible); + return false; + } + + return CheckSubresourceIntegrityImpl( + metadata_set, content, size, resource_url, + resource.GetResponse().HttpHeaderField("Integrity"), report_info); +} + +bool SubresourceIntegrity::CheckSubresourceIntegrity( + const String& integrity_metadata, + IntegrityFeatures features, + const char* content, + size_t size, + const KURL& resource_url, + ReportInfo& report_info) { + if (integrity_metadata.IsEmpty()) + return true; + + IntegrityMetadataSet metadata_set; + IntegrityParseResult integrity_parse_result = ParseIntegrityAttribute( + integrity_metadata, features, metadata_set, &report_info); + if (integrity_parse_result != kIntegrityParseValidResult) + return true; + // TODO(vogelheim): crbug.com/753349, figure out how deal with Ed25519 + // checking here. + String integrity_header; + return CheckSubresourceIntegrityImpl( + metadata_set, content, size, resource_url, integrity_header, report_info); +} + +bool SubresourceIntegrity::CheckSubresourceIntegrityImpl( + const IntegrityMetadataSet& metadata_set, + const char* content, + size_t size, + const KURL& resource_url, + const String integrity_header, + ReportInfo& report_info) { + if (!metadata_set.size()) + return true; + + // Check any of the "strongest" integrity constraints. + IntegrityAlgorithm max_algorithm = FindBestAlgorithm(metadata_set); + CheckFunction checker = GetCheckFunctionForAlgorithm(max_algorithm); + bool report_ed25519 = max_algorithm == IntegrityAlgorithm::kEd25519; + if (report_ed25519) { + report_info.AddUseCount(ReportInfo::UseCounterFeature::kSRISignatureCheck); + } + for (const IntegrityMetadata& metadata : metadata_set) { + if (metadata.Algorithm() == max_algorithm && + (*checker)(metadata, content, size, integrity_header)) { + report_info.AddUseCount(ReportInfo::UseCounterFeature:: + kSRIElementWithMatchingIntegrityAttribute); + if (report_ed25519) { + report_info.AddUseCount( + ReportInfo::UseCounterFeature::kSRISignatureSuccess); + } + return true; + } + } + + // If we arrive here, none of the "strongest" constaints have validated + // the data we received. Report this fact. + DigestValue digest; + if (ComputeDigest(kHashAlgorithmSha256, content, size, digest)) { + // This message exposes the digest of the resource to the console. + // Because this is only to the console, that's okay for now, but we + // need to be very careful not to expose this in exceptions or + // JavaScript, otherwise it risks exposing information about the + // resource cross-origin. + report_info.AddConsoleErrorMessage( + "Failed to find a valid digest in the 'integrity' attribute for " + "resource '" + + resource_url.ElidedString() + "' with computed SHA-256 integrity '" + + DigestToString(digest) + "'. The resource has been blocked."); + } else { + report_info.AddConsoleErrorMessage( + "There was an error computing an integrity value for resource '" + + resource_url.ElidedString() + "'. The resource has been blocked."); + } + report_info.AddUseCount(ReportInfo::UseCounterFeature:: + kSRIElementWithNonMatchingIntegrityAttribute); + return false; +} + +IntegrityAlgorithm SubresourceIntegrity::FindBestAlgorithm( + const IntegrityMetadataSet& metadata_set) { + // Find the "strongest" algorithm in the set. (This relies on + // IntegrityAlgorithm declaration order matching the "strongest" order, so + // make the compiler check this assumption first.) + static_assert(IntegrityAlgorithm::kSha256 < IntegrityAlgorithm::kSha384 && + IntegrityAlgorithm::kSha384 < IntegrityAlgorithm::kSha512 && + IntegrityAlgorithm::kSha512 < IntegrityAlgorithm::kEd25519, + "IntegrityAlgorithm enum order should match the priority " + "of the integrity algorithms."); + + // metadata_set is non-empty, so we are guaranteed to always have a result. + // This is effectively an implemenation of std::max_element (C++17). + DCHECK(!metadata_set.IsEmpty()); + auto iter = metadata_set.begin(); + IntegrityAlgorithm max_algorithm = iter->second; + ++iter; + for (; iter != metadata_set.end(); ++iter) { + max_algorithm = std::max(iter->second, max_algorithm); + } + return max_algorithm; +} + +SubresourceIntegrity::CheckFunction +SubresourceIntegrity::GetCheckFunctionForAlgorithm( + IntegrityAlgorithm algorithm) { + switch (algorithm) { + case IntegrityAlgorithm::kSha256: + case IntegrityAlgorithm::kSha384: + case IntegrityAlgorithm::kSha512: + return SubresourceIntegrity::CheckSubresourceIntegrityDigest; + case IntegrityAlgorithm::kEd25519: + return SubresourceIntegrity::CheckSubresourceIntegritySignature; + } + NOTREACHED(); + return nullptr; +} + +bool SubresourceIntegrity::CheckSubresourceIntegrityDigest( + const IntegrityMetadata& metadata, + const char* content, + size_t size, + const String& integrity_header) { + blink::HashAlgorithm hash_algo = kHashAlgorithmSha256; + switch (metadata.Algorithm()) { + case IntegrityAlgorithm::kSha256: + hash_algo = kHashAlgorithmSha256; + break; + case IntegrityAlgorithm::kSha384: + hash_algo = kHashAlgorithmSha384; + break; + case IntegrityAlgorithm::kSha512: + hash_algo = kHashAlgorithmSha512; + break; + case IntegrityAlgorithm::kEd25519: + NOTREACHED(); + break; + } + + DigestValue digest; + if (!ComputeDigest(hash_algo, content, size, digest)) + return false; + + Vector<char> hash_vector; + Base64Decode(metadata.Digest(), hash_vector); + DigestValue converted_hash_vector; + converted_hash_vector.Append(reinterpret_cast<uint8_t*>(hash_vector.data()), + hash_vector.size()); + return DigestsEqual(digest, converted_hash_vector); +} + +bool SubresourceIntegrity::CheckSubresourceIntegritySignature( + const IntegrityMetadata& metadata, + const char* content, + size_t size, + const String& integrity_header) { + DCHECK_EQ(IntegrityAlgorithm::kEd25519, metadata.Algorithm()); + + Vector<char> pubkey; + if (!Base64Decode(metadata.Digest(), pubkey) || + pubkey.size() != ED25519_PUBLIC_KEY_LEN) + return false; + + // Parse the Integrity:-header containing the signature(s). + Vector<UChar> integrity_header_chars; + integrity_header.AppendTo(integrity_header_chars); + const UChar* position = integrity_header_chars.begin(); + + const UChar* const end_position = integrity_header_chars.end(); + while (position < end_position) { + // We expect substrings of the form "ed25519-<BASE64>* ,". + // We'll move all of our UChar* pointers up front (before any early exits + // from the loop), since we should cleanly skip the next token in the + // header in all cases, even if the current token doesn't validate. + SkipWhile<UChar, IsSpaceOrComma>(position, end_position); + IntegrityAlgorithm algorithm; + bool found_ed25519 = + kAlgorithmValid == + ParseIntegrityHeaderAlgorithm(position, end_position, algorithm) && + IntegrityAlgorithm::kEd25519 == algorithm; + const UChar* digest_begin = position; + SkipUntil<UChar, IsSpaceOrComma>(position, end_position); + const UChar* const digest_end = position; + + // Now, algorithm contains the parsed algorithm specifier, the digest is + // found at digest_begin..digest_end, and position sits before the next + // token. + + if (!found_ed25519) + continue; + + String signature_raw; + if (!ParseDigest(digest_begin, digest_end, signature_raw)) + continue; + + Vector<char> signature; + Base64Decode(signature_raw, signature); + if (signature.size() != ED25519_SIGNATURE_LEN) + continue; + + // BoringSSL/OpenSSL functions return 1 for success. + if (1 == + ED25519_verify(reinterpret_cast<const uint8_t*>(content), size, + reinterpret_cast<const uint8_t*>(&*signature.begin()), + reinterpret_cast<const uint8_t*>(&*pubkey.begin()))) { + return true; + } + } + return false; +} + +SubresourceIntegrity::AlgorithmParseResult +SubresourceIntegrity::ParseAttributeAlgorithm(const UChar*& begin, + const UChar* end, + IntegrityFeatures features, + IntegrityAlgorithm& algorithm) { + static const AlgorithmPrefixPair kPrefixes[] = { + {"sha256", IntegrityAlgorithm::kSha256}, + {"sha-256", IntegrityAlgorithm::kSha256}, + {"sha384", IntegrityAlgorithm::kSha384}, + {"sha-384", IntegrityAlgorithm::kSha384}, + {"sha512", IntegrityAlgorithm::kSha512}, + {"sha-512", IntegrityAlgorithm::kSha512}, + {"ed25519", IntegrityAlgorithm::kEd25519}}; + + // The last algorithm prefix is the ed25519 signature algorithm, which should + // only be enabled if kSignatures is requested. We'll implement this by + // adjusting the last_prefix index into the array. + size_t last_prefix = WTF_ARRAY_LENGTH(kPrefixes); + if (features != IntegrityFeatures::kSignatures) + last_prefix--; + + return ParseAlgorithmPrefix(begin, end, kPrefixes, last_prefix, algorithm); +} + +SubresourceIntegrity::AlgorithmParseResult +SubresourceIntegrity::ParseIntegrityHeaderAlgorithm( + const UChar*& begin, + const UChar* end, + IntegrityAlgorithm& algorithm) { + static const AlgorithmPrefixPair kPrefixes[] = { + {"ed25519", IntegrityAlgorithm::kEd25519}}; + return ParseAlgorithmPrefix(begin, end, kPrefixes, + WTF_ARRAY_LENGTH(kPrefixes), algorithm); +} + +SubresourceIntegrity::AlgorithmParseResult +SubresourceIntegrity::ParseAlgorithmPrefix( + const UChar*& string_position, + const UChar* string_end, + const AlgorithmPrefixPair* prefix_table, + size_t prefix_table_size, + IntegrityAlgorithm& algorithm) { + for (size_t i = 0; i < prefix_table_size; i++) { + const UChar* pos = string_position; + if (SkipToken<UChar>(pos, string_end, prefix_table[i].first) && + SkipExactly<UChar>(pos, string_end, '-')) { + string_position = pos; + algorithm = prefix_table[i].second; + return kAlgorithmValid; + } + } + + const UChar* dash_position = string_position; + SkipUntil<UChar>(dash_position, string_end, '-'); + return dash_position < string_end ? kAlgorithmUnknown : kAlgorithmUnparsable; +} + +// Before: +// +// [algorithm]-[hash] OR [algorithm]-[hash]?[options] +// ^ ^ ^ ^ +// position end position end +// +// After (if successful: if the method returns false, we make no promises and +// the caller should exit early): +// +// [algorithm]-[hash] OR [algorithm]-[hash]?[options] +// ^ ^ ^ +// position/end position end +bool SubresourceIntegrity::ParseDigest(const UChar*& position, + const UChar* end, + String& digest) { + const UChar* begin = position; + SkipWhile<UChar, IsIntegrityCharacter>(position, end); + if (position == begin || (position != end && *position != '?')) { + digest = g_empty_string; + return false; + } + + // We accept base64url encoding, but normalize to "normal" base64 internally: + digest = NormalizeToBase64(String(begin, position - begin)); + return true; +} + +SubresourceIntegrity::IntegrityParseResult +SubresourceIntegrity::ParseIntegrityAttribute( + const WTF::String& attribute, + IntegrityFeatures features, + IntegrityMetadataSet& metadata_set) { + return ParseIntegrityAttribute(attribute, features, metadata_set, nullptr); +} + +SubresourceIntegrity::IntegrityParseResult +SubresourceIntegrity::ParseIntegrityAttribute( + const WTF::String& attribute, + IntegrityFeatures features, + IntegrityMetadataSet& metadata_set, + ReportInfo* report_info) { + // We expect a "clean" metadata_set, since metadata_set should only be filled + // once. + DCHECK(metadata_set.IsEmpty()); + + Vector<UChar> characters; + attribute.StripWhiteSpace().AppendTo(characters); + const UChar* position = characters.data(); + const UChar* end = characters.end(); + const UChar* current_integrity_end; + + bool error = false; + + // The integrity attribute takes the form: + // *WSP hash-with-options *( 1*WSP hash-with-options ) *WSP / *WSP + // To parse this, break on whitespace, parsing each algorithm/digest/option + // in order. + while (position < end) { + WTF::String digest; + IntegrityAlgorithm algorithm; + + SkipWhile<UChar, IsASCIISpace>(position, end); + current_integrity_end = position; + SkipUntil<UChar, IsASCIISpace>(current_integrity_end, end); + + // Algorithm parsing errors are non-fatal (the subresource should + // still be loaded) because strong hash algorithms should be used + // without fear of breaking older user agents that don't support + // them. + AlgorithmParseResult parse_result = ParseAttributeAlgorithm( + position, current_integrity_end, features, algorithm); + if (parse_result == kAlgorithmUnknown) { + // Unknown hash algorithms are treated as if they're not present, + // and thus are not marked as an error, they're just skipped. + SkipUntil<UChar, IsASCIISpace>(position, end); + if (report_info) { + report_info->AddConsoleErrorMessage( + "Error parsing 'integrity' attribute ('" + attribute + + "'). The specified hash algorithm must be one of " + "'sha256', 'sha384', or 'sha512'."); + report_info->AddUseCount( + ReportInfo::UseCounterFeature:: + kSRIElementWithUnparsableIntegrityAttribute); + } + continue; + } + + if (parse_result == kAlgorithmUnparsable) { + error = true; + SkipUntil<UChar, IsASCIISpace>(position, end); + if (report_info) { + report_info->AddConsoleErrorMessage( + "Error parsing 'integrity' attribute ('" + attribute + + "'). The hash algorithm must be one of 'sha256', " + "'sha384', or 'sha512', followed by a '-' " + "character."); + report_info->AddUseCount( + ReportInfo::UseCounterFeature:: + kSRIElementWithUnparsableIntegrityAttribute); + } + continue; + } + + DCHECK_EQ(parse_result, kAlgorithmValid); + + if (!ParseDigest(position, current_integrity_end, digest)) { + error = true; + SkipUntil<UChar, IsASCIISpace>(position, end); + if (report_info) { + report_info->AddConsoleErrorMessage( + "Error parsing 'integrity' attribute ('" + attribute + + "'). The digest must be a valid, base64-encoded value."); + report_info->AddUseCount( + ReportInfo::UseCounterFeature:: + kSRIElementWithUnparsableIntegrityAttribute); + } + continue; + } + + // The spec defines a space in the syntax for options, separated by a + // '?' character followed by unbounded VCHARs, but no actual options + // have been defined yet. Thus, for forward compatibility, ignore any + // options specified. + if (SkipExactly<UChar>(position, end, '?')) { + const UChar* begin = position; + SkipWhile<UChar, IsValueCharacter>(position, end); + if (begin != position && report_info) { + report_info->AddConsoleErrorMessage( + "Ignoring unrecogized 'integrity' attribute option '" + + String(begin, position - begin) + "'."); + } + } + + IntegrityMetadata integrity_metadata(digest, algorithm); + metadata_set.insert(integrity_metadata.ToPair()); + } + if (metadata_set.size() == 0 && error) + return kIntegrityParseNoValidResult; + + return kIntegrityParseValidResult; +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/platform/loader/subresource_integrity.h b/chromium/third_party/blink/renderer/platform/loader/subresource_integrity.h new file mode 100644 index 00000000000..a5fb3ca2c87 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/loader/subresource_integrity.h @@ -0,0 +1,152 @@ +// 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_PLATFORM_LOADER_SUBRESOURCE_INTEGRITY_H_ +#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_LOADER_SUBRESOURCE_INTEGRITY_H_ + +#include "base/gtest_prod_util.h" +#include "third_party/blink/renderer/platform/loader/fetch/integrity_metadata.h" +#include "third_party/blink/renderer/platform/platform_export.h" +#include "third_party/blink/renderer/platform/wtf/allocator.h" +#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h" +#include "third_party/blink/renderer/platform/wtf/vector.h" + +namespace blink { + +class KURL; +class Resource; + +class PLATFORM_EXPORT SubresourceIntegrity final { + STATIC_ONLY(SubresourceIntegrity); + + public: + class PLATFORM_EXPORT ReportInfo final { + public: + enum class UseCounterFeature { + kSRIElementWithMatchingIntegrityAttribute, + kSRIElementWithNonMatchingIntegrityAttribute, + kSRIElementIntegrityAttributeButIneligible, + kSRIElementWithUnparsableIntegrityAttribute, + kSRISignatureCheck, + kSRISignatureSuccess, + }; + + void AddUseCount(UseCounterFeature); + void AddConsoleErrorMessage(const String&); + void Clear(); + + const Vector<UseCounterFeature>& UseCounts() const { return use_counts_; } + const Vector<String>& ConsoleErrorMessages() const { + return console_error_messages_; + } + + private: + Vector<UseCounterFeature> use_counts_; + Vector<String> console_error_messages_; + }; + + enum IntegrityParseResult { + kIntegrityParseValidResult, + kIntegrityParseNoValidResult + }; + + // Determine which SRI features to support when parsing integrity attributes. + enum class IntegrityFeatures { + kDefault, // Default: All sha* hash codes. + kSignatures // Also support the ed25519 signature scheme. + }; + + // The version with the IntegrityMetadataSet passed as the first argument + // assumes that the integrity attribute has already been parsed, and the + // IntegrityMetadataSet represents the result of that parsing. + static bool CheckSubresourceIntegrity(const IntegrityMetadataSet&, + const char* content, + size_t content_size, + const KURL& resource_url, + const Resource&, + ReportInfo&); + static bool CheckSubresourceIntegrity(const String&, + IntegrityFeatures, + const char* content, + size_t content_size, + const KURL& resource_url, + ReportInfo&); + + // The IntegrityMetadataSet arguments are out parameters which contain the + // set of all valid, parsed metadata from |attribute|. + static IntegrityParseResult ParseIntegrityAttribute( + const WTF::String& attribute, + IntegrityFeatures, + IntegrityMetadataSet&); + static IntegrityParseResult ParseIntegrityAttribute( + const WTF::String& attribute, + IntegrityFeatures, + IntegrityMetadataSet&, + ReportInfo*); + + private: + friend class SubresourceIntegrityTest; + FRIEND_TEST_ALL_PREFIXES(SubresourceIntegrityTest, Parsing); + FRIEND_TEST_ALL_PREFIXES(SubresourceIntegrityTest, ParseAlgorithm); + FRIEND_TEST_ALL_PREFIXES(SubresourceIntegrityTest, ParseHeader); + FRIEND_TEST_ALL_PREFIXES(SubresourceIntegrityTest, Prioritization); + FRIEND_TEST_ALL_PREFIXES(SubresourceIntegrityTest, FindBestAlgorithm); + FRIEND_TEST_ALL_PREFIXES(SubresourceIntegrityTest, + GetCheckFunctionForAlgorithm); + + // The core implementation for all CheckSubresoureIntegrity functions. + static bool CheckSubresourceIntegrityImpl(const IntegrityMetadataSet&, + const char*, + size_t, + const KURL& resource_url, + const String integrity_header, + ReportInfo&); + + enum AlgorithmParseResult { + kAlgorithmValid, + kAlgorithmUnparsable, + kAlgorithmUnknown + }; + + static IntegrityAlgorithm FindBestAlgorithm(const IntegrityMetadataSet&); + + typedef bool (*CheckFunction)(const IntegrityMetadata&, + const char*, + size_t, + const String&); + static CheckFunction GetCheckFunctionForAlgorithm(IntegrityAlgorithm); + + static bool CheckSubresourceIntegrityDigest(const IntegrityMetadata&, + const char*, + size_t, + const String& integrity_header); + static bool CheckSubresourceIntegritySignature( + const IntegrityMetadata&, + const char*, + size_t, + const String& integrity_header); + + static AlgorithmParseResult ParseAttributeAlgorithm(const UChar*& begin, + const UChar* end, + IntegrityFeatures, + IntegrityAlgorithm&); + static AlgorithmParseResult ParseIntegrityHeaderAlgorithm( + const UChar*& begin, + const UChar* end, + IntegrityAlgorithm&); + typedef std::pair<const char*, IntegrityAlgorithm> AlgorithmPrefixPair; + static AlgorithmParseResult ParseAlgorithmPrefix( + const UChar*& string_position, + const UChar* string_end, + const AlgorithmPrefixPair* prefix_table, + size_t prefix_table_size, + IntegrityAlgorithm&); + static bool ParseDigest(const UChar*& begin, + const UChar* end, + String& digest); +}; + +} // namespace blink + +#endif diff --git a/chromium/third_party/blink/renderer/platform/loader/subresource_integrity_test.cc b/chromium/third_party/blink/renderer/platform/loader/subresource_integrity_test.cc new file mode 100644 index 00000000000..e0c92673a0a --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/loader/subresource_integrity_test.cc @@ -0,0 +1,713 @@ +// 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/platform/loader/subresource_integrity.h" + +#include "base/memory/scoped_refptr.h" +#include "services/network/public/mojom/fetch_api.mojom-blink.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/blink/renderer/platform/crypto.h" +#include "third_party/blink/renderer/platform/loader/fetch/integrity_metadata.h" +#include "third_party/blink/renderer/platform/loader/fetch/raw_resource.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_load_scheduler.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource_loader.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource_response.h" +#include "third_party/blink/renderer/platform/loader/testing/crypto_testing_platform_support.h" +#include "third_party/blink/renderer/platform/loader/testing/mock_fetch_context.h" +#include "third_party/blink/renderer/platform/runtime_enabled_features.h" +#include "third_party/blink/renderer/platform/testing/runtime_enabled_features_test_helpers.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/dtoa/utils.h" +#include "third_party/blink/renderer/platform/wtf/text/string_builder.h" +#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h" +#include "third_party/blink/renderer/platform/wtf/vector.h" + +#include <algorithm> + +namespace blink { + +static const char kBasicScript[] = "alert('test');"; +static unsigned char kSha256Hash[] = { + 0x18, 0x01, 0x78, 0xf1, 0x03, 0xa8, 0xc5, 0x1b, 0xee, 0xd2, 0x06, + 0x40, 0x99, 0x08, 0xaf, 0x51, 0xd2, 0x4f, 0xc8, 0x16, 0x9c, 0xab, + 0x39, 0xc1, 0x01, 0x7c, 0x27, 0x91, 0xfa, 0x66, 0x41, 0x7e}; +static unsigned char kSha384Hash[] = { + 0x9d, 0xea, 0x77, 0x5e, 0x9b, 0xe1, 0x53, 0x1a, 0x42, 0x30, 0xe5, 0x57, + 0x20, 0x53, 0xde, 0x71, 0x38, 0x40, 0xa9, 0xd6, 0x3f, 0xb9, 0x57, 0xa2, + 0x0f, 0x89, 0x17, 0x4a, 0xa5, 0xe9, 0xc7, 0x46, 0x09, 0x51, 0x65, 0x38, + 0x7d, 0x34, 0xda, 0x16, 0x07, 0x22, 0x4e, 0xe6, 0x64, 0xed, 0xf9, 0x84}; +static unsigned char kSha512Hash[] = { + 0x4d, 0x79, 0x09, 0xc3, 0x5f, 0x0f, 0xaa, 0x55, 0x65, 0x11, 0x45, + 0xd7, 0x8d, 0xe5, 0xdb, 0x19, 0xeb, 0x68, 0xa7, 0x54, 0xca, 0x07, + 0x7c, 0x18, 0x40, 0x8a, 0x75, 0xfe, 0x28, 0x71, 0x08, 0xe1, 0x46, + 0x51, 0xf1, 0xbd, 0x4d, 0x83, 0x9a, 0x03, 0x53, 0x25, 0x92, 0x94, + 0xc0, 0xa9, 0x25, 0x7a, 0xc9, 0xa7, 0xaf, 0x2c, 0xef, 0x13, 0x8f, + 0x9a, 0x60, 0x1f, 0x52, 0x66, 0x67, 0xef, 0x88, 0xb4}; +static const char kSha256Integrity[] = + "sha256-GAF48QOoxRvu0gZAmQivUdJPyBacqznBAXwnkfpmQX4="; +static const char kSha256IntegrityLenientSyntax[] = + "sha256-GAF48QOoxRvu0gZAmQivUdJPyBacqznBAXwnkfpmQX4="; +static const char kSha256IntegrityWithEmptyOption[] = + "sha256-GAF48QOoxRvu0gZAmQivUdJPyBacqznBAXwnkfpmQX4=?"; +static const char kSha256IntegrityWithOption[] = + "sha256-GAF48QOoxRvu0gZAmQivUdJPyBacqznBAXwnkfpmQX4=?foo=bar"; +static const char kSha256IntegrityWithOptions[] = + "sha256-GAF48QOoxRvu0gZAmQivUdJPyBacqznBAXwnkfpmQX4=?foo=bar?baz=foz"; +static const char kSha256IntegrityWithMimeOption[] = + "sha256-GAF48QOoxRvu0gZAmQivUdJPyBacqznBAXwnkfpmQX4=?ct=application/" + "javascript"; +static const char kSha384Integrity[] = + "sha384-nep3XpvhUxpCMOVXIFPecThAqdY_uVeiD4kXSqXpx0YJUWU4fTTaFgciTuZk7fmE"; +static const char kSha512Integrity[] = + "sha512-TXkJw18PqlVlEUXXjeXbGetop1TKB3wYQIp1_" + "ihxCOFGUfG9TYOaA1MlkpTAqSV6yaevLO8Tj5pgH1JmZ--ItA=="; +static const char kSha384IntegrityLabeledAs256[] = + "sha256-nep3XpvhUxpCMOVXIFPecThAqdY_uVeiD4kXSqXpx0YJUWU4fTTaFgciTuZk7fmE"; +static const char kSha256AndSha384Integrities[] = + "sha256-GAF48QOoxRvu0gZAmQivUdJPyBacqznBAXwnkfpmQX4= " + "sha384-nep3XpvhUxpCMOVXIFPecThAqdY_uVeiD4kXSqXpx0YJUWU4fTTaFgciTuZk7fmE"; +static const char kBadSha256AndGoodSha384Integrities[] = + "sha256-deadbeef " + "sha384-nep3XpvhUxpCMOVXIFPecThAqdY_uVeiD4kXSqXpx0YJUWU4fTTaFgciTuZk7fmE"; +static const char kGoodSha256AndBadSha384Integrities[] = + "sha256-GAF48QOoxRvu0gZAmQivUdJPyBacqznBAXwnkfpmQX4= sha384-deadbeef"; +static const char kBadSha256AndBadSha384Integrities[] = + "sha256-deadbeef sha384-deadbeef"; +static const char kUnsupportedHashFunctionIntegrity[] = + "sha1-JfLW308qMPKfb4DaHpUBEESwuPc="; + +class SubresourceIntegrityTest : public testing::Test { + public: + SubresourceIntegrityTest() + : sec_url("https://example.test:443"), + insec_url("http://example.test:80") {} + + protected: + void SetUp() override { + context = + MockFetchContext::Create(MockFetchContext::kShouldLoadNewResource); + } + + SubresourceIntegrity::IntegrityFeatures Features() const { + return RuntimeEnabledFeatures::SignatureBasedIntegrityEnabledByRuntimeFlag() + ? SubresourceIntegrity::IntegrityFeatures::kSignatures + : SubresourceIntegrity::IntegrityFeatures::kDefault; + } + + void ExpectAlgorithm(const String& text, + IntegrityAlgorithm expected_algorithm) { + Vector<UChar> characters; + text.AppendTo(characters); + const UChar* position = characters.data(); + const UChar* end = characters.end(); + IntegrityAlgorithm algorithm; + + EXPECT_EQ(SubresourceIntegrity::kAlgorithmValid, + SubresourceIntegrity::ParseAttributeAlgorithm( + position, end, Features(), algorithm)); + EXPECT_EQ(expected_algorithm, algorithm); + EXPECT_EQ(end, position); + } + + void ExpectAlgorithmFailure( + const String& text, + SubresourceIntegrity::AlgorithmParseResult expected_result) { + Vector<UChar> characters; + text.AppendTo(characters); + const UChar* position = characters.data(); + const UChar* begin = characters.data(); + const UChar* end = characters.end(); + IntegrityAlgorithm algorithm; + + EXPECT_EQ(expected_result, SubresourceIntegrity::ParseAttributeAlgorithm( + position, end, Features(), algorithm)); + EXPECT_EQ(begin, position); + } + + void ExpectDigest(const String& text, const char* expected_digest) { + Vector<UChar> characters; + text.AppendTo(characters); + const UChar* position = characters.data(); + const UChar* end = characters.end(); + String digest; + + EXPECT_TRUE(SubresourceIntegrity::ParseDigest(position, end, digest)); + EXPECT_EQ(expected_digest, digest); + } + + void ExpectDigestFailure(const String& text) { + Vector<UChar> characters; + text.AppendTo(characters); + const UChar* position = characters.data(); + const UChar* end = characters.end(); + String digest; + + EXPECT_FALSE(SubresourceIntegrity::ParseDigest(position, end, digest)); + EXPECT_TRUE(digest.IsEmpty()); + } + + void ExpectParse(const char* integrity_attribute, + const char* expected_digest, + IntegrityAlgorithm expected_algorithm) { + IntegrityMetadataSet metadata_set; + + EXPECT_EQ(SubresourceIntegrity::kIntegrityParseValidResult, + SubresourceIntegrity::ParseIntegrityAttribute( + integrity_attribute, Features(), metadata_set)); + EXPECT_EQ(1u, metadata_set.size()); + if (metadata_set.size() > 0) { + IntegrityMetadata metadata = *metadata_set.begin(); + EXPECT_EQ(expected_digest, metadata.Digest()); + EXPECT_EQ(expected_algorithm, metadata.Algorithm()); + } + } + + void ExpectParseMultipleHashes( + const char* integrity_attribute, + const IntegrityMetadata expected_metadata_array[], + size_t expected_metadata_array_size) { + IntegrityMetadataSet expected_metadata_set; + for (size_t i = 0; i < expected_metadata_array_size; i++) { + expected_metadata_set.insert(expected_metadata_array[i].ToPair()); + } + IntegrityMetadataSet metadata_set; + EXPECT_EQ(SubresourceIntegrity::kIntegrityParseValidResult, + SubresourceIntegrity::ParseIntegrityAttribute( + integrity_attribute, Features(), metadata_set)); + EXPECT_TRUE( + IntegrityMetadata::SetsEqual(expected_metadata_set, metadata_set)); + } + + void ExpectParseFailure(const char* integrity_attribute) { + IntegrityMetadataSet metadata_set; + + EXPECT_EQ(SubresourceIntegrity::kIntegrityParseNoValidResult, + SubresourceIntegrity::ParseIntegrityAttribute( + integrity_attribute, Features(), metadata_set)); + } + + void ExpectEmptyParseResult(const char* integrity_attribute) { + IntegrityMetadataSet metadata_set; + + EXPECT_EQ(SubresourceIntegrity::kIntegrityParseValidResult, + SubresourceIntegrity::ParseIntegrityAttribute( + integrity_attribute, Features(), metadata_set)); + EXPECT_EQ(0u, metadata_set.size()); + } + + enum ServiceWorkerMode { + kNoServiceWorker, + kSWOpaqueResponse, + kSWClearResponse + }; + + enum Expectation { kIntegritySuccess, kIntegrityFailure }; + + struct TestCase { + const KURL& origin; + const KURL& target; + const KURL* allow_origin_url; + const ServiceWorkerMode service_worker; + const Expectation expectation; + }; + + void CheckExpectedIntegrity(const char* integrity, const TestCase test) { + CheckExpectedIntegrity(integrity, test, test.expectation); + } + + // Allows to overwrite the test expectation for cases that are always expected + // to fail: + void CheckExpectedIntegrity(const char* integrity, + const TestCase test, + Expectation expectation) { + context->SetSecurityOrigin(SecurityOrigin::Create(test.origin)); + + IntegrityMetadataSet metadata_set; + EXPECT_EQ(SubresourceIntegrity::kIntegrityParseValidResult, + SubresourceIntegrity::ParseIntegrityAttribute( + String(integrity), Features(), metadata_set)); + + SubresourceIntegrity::ReportInfo report_info; + EXPECT_EQ(expectation == kIntegritySuccess, + SubresourceIntegrity::CheckSubresourceIntegrity( + metadata_set, kBasicScript, strlen(kBasicScript), test.target, + *CreateTestResource(test.target, test.allow_origin_url, + test.service_worker), + report_info)); + } + + Resource* CreateTestResource(const KURL& url, + const KURL* allow_origin_url, + ServiceWorkerMode service_worker_mode) { + ResourceFetcher* fetcher = ResourceFetcher::Create(context); + ResourceLoadScheduler* scheduler = ResourceLoadScheduler::Create(); + Resource* resource = RawResource::CreateForTest(url, Resource::kRaw); + ResourceLoader* loader = + ResourceLoader::Create(fetcher, scheduler, resource); + + ResourceRequest request; + request.SetURL(url); + + ResourceResponse response(url); + response.SetHTTPStatusCode(200); + + if (allow_origin_url) { + request.SetFetchRequestMode(network::mojom::FetchRequestMode::kCORS); + resource->MutableOptions().cors_handling_by_resource_fetcher = + kEnableCORSHandlingByResourceFetcher; + response.SetHTTPHeaderField( + "access-control-allow-origin", + SecurityOrigin::Create(*allow_origin_url)->ToAtomicString()); + response.SetHTTPHeaderField("access-control-allow-credentials", "true"); + } + + resource->SetResourceRequest(request); + + if (service_worker_mode != kNoServiceWorker) { + response.SetWasFetchedViaServiceWorker(true); + + if (service_worker_mode == kSWOpaqueResponse) { + response.SetResponseTypeViaServiceWorker( + network::mojom::FetchResponseType::kOpaque); + } else { + response.SetResponseTypeViaServiceWorker( + network::mojom::FetchResponseType::kDefault); + } + } + + StringBuilder cors_error_msg; + CORSStatus cors_status = + loader->DetermineCORSStatus(response, cors_error_msg); + resource->SetCORSStatus(cors_status); + + return resource; + } + + KURL sec_url; + KURL insec_url; + + ScopedTestingPlatformSupport<CryptoTestingPlatformSupport> platform_; + Persistent<MockFetchContext> context; +}; + +// Test the prioritization (i.e. selecting the "strongest" algorithm. +// This effectively tests the definition of IntegrityAlgorithm in +// IntegrityMetadata. The test is here, because SubresourceIntegrity is the +// class that relies on this working as expected.) +TEST_F(SubresourceIntegrityTest, Prioritization) { + // Check that each algorithm is it's own "strongest". + EXPECT_EQ( + IntegrityAlgorithm::kSha256, + std::max({IntegrityAlgorithm::kSha256, IntegrityAlgorithm::kSha256})); + EXPECT_EQ( + IntegrityAlgorithm::kSha384, + std::max({IntegrityAlgorithm::kSha384, IntegrityAlgorithm::kSha384})); + + EXPECT_EQ( + IntegrityAlgorithm::kSha512, + std::max({IntegrityAlgorithm::kSha512, IntegrityAlgorithm::kSha512})); + EXPECT_EQ( + IntegrityAlgorithm::kEd25519, + std::max({IntegrityAlgorithm::kEd25519, IntegrityAlgorithm::kEd25519})); + + // Check a mix of algorithms. + EXPECT_EQ(IntegrityAlgorithm::kSha384, + std::max({IntegrityAlgorithm::kSha256, IntegrityAlgorithm::kSha384, + IntegrityAlgorithm::kSha256})); + EXPECT_EQ(IntegrityAlgorithm::kSha512, + std::max({IntegrityAlgorithm::kSha384, IntegrityAlgorithm::kSha512, + IntegrityAlgorithm::kSha256})); + EXPECT_EQ( + IntegrityAlgorithm::kEd25519, + std::max({IntegrityAlgorithm::kSha384, IntegrityAlgorithm::kSha512, + IntegrityAlgorithm::kEd25519, IntegrityAlgorithm::kSha512, + IntegrityAlgorithm::kSha256, IntegrityAlgorithm::kSha512})); +} + +TEST_F(SubresourceIntegrityTest, ParseAlgorithm) { + ExpectAlgorithm("sha256-", IntegrityAlgorithm::kSha256); + ExpectAlgorithm("sha384-", IntegrityAlgorithm::kSha384); + ExpectAlgorithm("sha512-", IntegrityAlgorithm::kSha512); + ExpectAlgorithm("sha-256-", IntegrityAlgorithm::kSha256); + ExpectAlgorithm("sha-384-", IntegrityAlgorithm::kSha384); + ExpectAlgorithm("sha-512-", IntegrityAlgorithm::kSha512); + + { + ScopedSignatureBasedIntegrityForTest signature_based_integrity(true); + ExpectAlgorithm("ed25519-", IntegrityAlgorithm::kEd25519); + } + ScopedSignatureBasedIntegrityForTest signature_based_integrity(false); + ExpectAlgorithmFailure("ed25519-", SubresourceIntegrity::kAlgorithmUnknown); + + ExpectAlgorithmFailure("sha1-", SubresourceIntegrity::kAlgorithmUnknown); + ExpectAlgorithmFailure("sha-1-", SubresourceIntegrity::kAlgorithmUnknown); + ExpectAlgorithmFailure("foobarsha256-", + SubresourceIntegrity::kAlgorithmUnknown); + ExpectAlgorithmFailure("foobar-", SubresourceIntegrity::kAlgorithmUnknown); + ExpectAlgorithmFailure("-", SubresourceIntegrity::kAlgorithmUnknown); + ExpectAlgorithmFailure("ed-25519-", SubresourceIntegrity::kAlgorithmUnknown); + ExpectAlgorithmFailure("ed25518-", SubresourceIntegrity::kAlgorithmUnknown); + + ExpectAlgorithmFailure("sha256", SubresourceIntegrity::kAlgorithmUnparsable); + ExpectAlgorithmFailure("", SubresourceIntegrity::kAlgorithmUnparsable); +} + +TEST_F(SubresourceIntegrityTest, ParseDigest) { + ExpectDigest("abcdefg", "abcdefg"); + ExpectDigest("abcdefg?", "abcdefg"); + ExpectDigest("ab+de/g", "ab+de/g"); + ExpectDigest("ab-de_g", "ab+de/g"); + + ExpectDigestFailure("?"); + ExpectDigestFailure("&&&foobar&&&"); + ExpectDigestFailure("\x01\x02\x03\x04"); +} + +// +// End-to-end parsing tests. +// + +TEST_F(SubresourceIntegrityTest, Parsing) { + ExpectParseFailure("not_really_a_valid_anything"); + ExpectParseFailure("sha256-&&&foobar&&&"); + ExpectParseFailure("sha256-\x01\x02\x03\x04"); + ExpectParseFailure("sha256-!!! sha256-!!!"); + + ExpectEmptyParseResult("foobar:///sha256-abcdefg"); + ExpectEmptyParseResult("ni://sha256-abcdefg"); + ExpectEmptyParseResult("ni:///sha256-abcdefg"); + ExpectEmptyParseResult("notsha256atall-abcdefg"); + + ExpectParse("sha256-BpfBw7ivV8q2jLiT13fxDYAe2tJllusRSZ273h2nFSE=", + "BpfBw7ivV8q2jLiT13fxDYAe2tJllusRSZ273h2nFSE=", + IntegrityAlgorithm::kSha256); + + ExpectParse("sha-256-BpfBw7ivV8q2jLiT13fxDYAe2tJllusRSZ273h2nFSE=", + "BpfBw7ivV8q2jLiT13fxDYAe2tJllusRSZ273h2nFSE=", + IntegrityAlgorithm::kSha256); + + ExpectParse(" sha256-BpfBw7ivV8q2jLiT13fxDYAe2tJllusRSZ273h2nFSE= ", + "BpfBw7ivV8q2jLiT13fxDYAe2tJllusRSZ273h2nFSE=", + IntegrityAlgorithm::kSha256); + + ExpectParse( + "sha384-XVVXBGoYw6AJOh9J-Z8pBDMVVPfkBpngexkA7JqZu8d5GENND6TEIup_tA1v5GPr", + "XVVXBGoYw6AJOh9J+Z8pBDMVVPfkBpngexkA7JqZu8d5GENND6TEIup/tA1v5GPr", + IntegrityAlgorithm::kSha384); + + ExpectParse( + "sha-384-XVVXBGoYw6AJOh9J_Z8pBDMVVPfkBpngexkA7JqZu8d5GENND6TEIup_" + "tA1v5GPr", + "XVVXBGoYw6AJOh9J/Z8pBDMVVPfkBpngexkA7JqZu8d5GENND6TEIup/tA1v5GPr", + IntegrityAlgorithm::kSha384); + + ExpectParse( + "sha512-tbUPioKbVBplr0b1ucnWB57SJWt4x9dOE0Vy2mzCXvH3FepqDZ-" + "07yMK81ytlg0MPaIrPAjcHqba5csorDWtKg==", + "tbUPioKbVBplr0b1ucnWB57SJWt4x9dOE0Vy2mzCXvH3FepqDZ+" + "07yMK81ytlg0MPaIrPAjcHqba5csorDWtKg==", + IntegrityAlgorithm::kSha512); + + ExpectParse( + "sha-512-tbUPioKbVBplr0b1ucnWB57SJWt4x9dOE0Vy2mzCXvH3FepqDZ-" + "07yMK81ytlg0MPaIrPAjcHqba5csorDWtKg==", + "tbUPioKbVBplr0b1ucnWB57SJWt4x9dOE0Vy2mzCXvH3FepqDZ+" + "07yMK81ytlg0MPaIrPAjcHqba5csorDWtKg==", + IntegrityAlgorithm::kSha512); + + ExpectParse( + "sha-512-tbUPioKbVBplr0b1ucnWB57SJWt4x9dOE0Vy2mzCXvH3FepqDZ-" + "07yMK81ytlg0MPaIrPAjcHqba5csorDWtKg==?ct=application/javascript", + "tbUPioKbVBplr0b1ucnWB57SJWt4x9dOE0Vy2mzCXvH3FepqDZ+" + "07yMK81ytlg0MPaIrPAjcHqba5csorDWtKg==", + IntegrityAlgorithm::kSha512); + + ExpectParse( + "sha-512-tbUPioKbVBplr0b1ucnWB57SJWt4x9dOE0Vy2mzCXvH3FepqDZ-" + "07yMK81ytlg0MPaIrPAjcHqba5csorDWtKg==?ct=application/xhtml+xml", + "tbUPioKbVBplr0b1ucnWB57SJWt4x9dOE0Vy2mzCXvH3FepqDZ+" + "07yMK81ytlg0MPaIrPAjcHqba5csorDWtKg==", + IntegrityAlgorithm::kSha512); + + ExpectParse( + "sha-512-tbUPioKbVBplr0b1ucnWB57SJWt4x9dOE0Vy2mzCXvH3FepqDZ-" + "07yMK81ytlg0MPaIrPAjcHqba5csorDWtKg==?foo=bar?ct=application/xhtml+xml", + "tbUPioKbVBplr0b1ucnWB57SJWt4x9dOE0Vy2mzCXvH3FepqDZ+" + "07yMK81ytlg0MPaIrPAjcHqba5csorDWtKg==", + IntegrityAlgorithm::kSha512); + + ExpectParse( + "sha-512-tbUPioKbVBplr0b1ucnWB57SJWt4x9dOE0Vy2mzCXvH3FepqDZ-" + "07yMK81ytlg0MPaIrPAjcHqba5csorDWtKg==?ct=application/xhtml+xml?foo=bar", + "tbUPioKbVBplr0b1ucnWB57SJWt4x9dOE0Vy2mzCXvH3FepqDZ+" + "07yMK81ytlg0MPaIrPAjcHqba5csorDWtKg==", + IntegrityAlgorithm::kSha512); + + ExpectParse( + "sha-512-tbUPioKbVBplr0b1ucnWB57SJWt4x9dOE0Vy2mzCXvH3FepqDZ-" + "07yMK81ytlg0MPaIrPAjcHqba5csorDWtKg==?baz=foz?ct=application/" + "xhtml+xml?foo=bar", + "tbUPioKbVBplr0b1ucnWB57SJWt4x9dOE0Vy2mzCXvH3FepqDZ+" + "07yMK81ytlg0MPaIrPAjcHqba5csorDWtKg==", + IntegrityAlgorithm::kSha512); + + ExpectParseMultipleHashes("", nullptr, 0); + ExpectParseMultipleHashes(" ", nullptr, 0); + + const IntegrityMetadata valid_sha384_and_sha512[] = { + IntegrityMetadata( + "XVVXBGoYw6AJOh9J+Z8pBDMVVPfkBpngexkA7JqZu8d5GENND6TEIup/tA1v5GPr", + IntegrityAlgorithm::kSha384), + IntegrityMetadata("tbUPioKbVBplr0b1ucnWB57SJWt4x9dOE0Vy2mzCXvH3FepqDZ+" + "07yMK81ytlg0MPaIrPAjcHqba5csorDWtKg==", + IntegrityAlgorithm::kSha512), + }; + ExpectParseMultipleHashes( + "sha384-XVVXBGoYw6AJOh9J+Z8pBDMVVPfkBpngexkA7JqZu8d5GENND6TEIup/tA1v5GPr " + "sha512-tbUPioKbVBplr0b1ucnWB57SJWt4x9dOE0Vy2mzCXvH3FepqDZ+" + "07yMK81ytlg0MPaIrPAjcHqba5csorDWtKg==", + valid_sha384_and_sha512, WTF_ARRAY_LENGTH(valid_sha384_and_sha512)); + + const IntegrityMetadata valid_sha256_and_sha256[] = { + IntegrityMetadata("BpfBw7ivV8q2jLiT13fxDYAe2tJllusRSZ273h2nFSE=", + IntegrityAlgorithm::kSha256), + IntegrityMetadata("deadbeef", IntegrityAlgorithm::kSha256), + }; + ExpectParseMultipleHashes( + "sha256-BpfBw7ivV8q2jLiT13fxDYAe2tJllusRSZ273h2nFSE= sha256-deadbeef", + valid_sha256_and_sha256, WTF_ARRAY_LENGTH(valid_sha256_and_sha256)); + + const IntegrityMetadata valid_sha256_and_invalid_sha256[] = { + IntegrityMetadata("BpfBw7ivV8q2jLiT13fxDYAe2tJllusRSZ273h2nFSE=", + IntegrityAlgorithm::kSha256), + }; + ExpectParseMultipleHashes( + "sha256-BpfBw7ivV8q2jLiT13fxDYAe2tJllusRSZ273h2nFSE= sha256-!!!!", + valid_sha256_and_invalid_sha256, + WTF_ARRAY_LENGTH(valid_sha256_and_invalid_sha256)); + + const IntegrityMetadata invalid_sha256_and_valid_sha256[] = { + IntegrityMetadata("BpfBw7ivV8q2jLiT13fxDYAe2tJllusRSZ273h2nFSE=", + IntegrityAlgorithm::kSha256), + }; + ExpectParseMultipleHashes( + "sha256-!!! sha256-BpfBw7ivV8q2jLiT13fxDYAe2tJllusRSZ273h2nFSE=", + invalid_sha256_and_valid_sha256, + WTF_ARRAY_LENGTH(invalid_sha256_and_valid_sha256)); + + ExpectParse("sha256-BpfBw7ivV8q2jLiT13fxDYAe2tJllusRSZ273h2nFSE=?foo=bar", + "BpfBw7ivV8q2jLiT13fxDYAe2tJllusRSZ273h2nFSE=", + IntegrityAlgorithm::kSha256); + + ExpectParse( + "sha256-BpfBw7ivV8q2jLiT13fxDYAe2tJllusRSZ273h2nFSE=?foo=bar?baz=foz", + "BpfBw7ivV8q2jLiT13fxDYAe2tJllusRSZ273h2nFSE=", + IntegrityAlgorithm::kSha256); + + ExpectParse("sha256-BpfBw7ivV8q2jLiT13fxDYAe2tJllusRSZ273h2nFSE=?", + "BpfBw7ivV8q2jLiT13fxDYAe2tJllusRSZ273h2nFSE=", + IntegrityAlgorithm::kSha256); + ExpectParse("sha256-BpfBw7ivV8q2jLiT13fxDYAe2tJllusRSZ273h2nFSE=?foo=bar", + "BpfBw7ivV8q2jLiT13fxDYAe2tJllusRSZ273h2nFSE=", + IntegrityAlgorithm::kSha256); + ExpectParse( + "sha256-BpfBw7ivV8q2jLiT13fxDYAe2tJllusRSZ273h2nFSE=?foo=bar?baz=foz", + "BpfBw7ivV8q2jLiT13fxDYAe2tJllusRSZ273h2nFSE=", + IntegrityAlgorithm::kSha256); + ExpectParse("sha256-BpfBw7ivV8q2jLiT13fxDYAe2tJllusRSZ273h2nFSE=?foo", + "BpfBw7ivV8q2jLiT13fxDYAe2tJllusRSZ273h2nFSE=", + IntegrityAlgorithm::kSha256); + ExpectParse("sha256-BpfBw7ivV8q2jLiT13fxDYAe2tJllusRSZ273h2nFSE=?foo=bar?", + "BpfBw7ivV8q2jLiT13fxDYAe2tJllusRSZ273h2nFSE=", + IntegrityAlgorithm::kSha256); + ExpectParse("sha256-BpfBw7ivV8q2jLiT13fxDYAe2tJllusRSZ273h2nFSE=?foo:bar", + "BpfBw7ivV8q2jLiT13fxDYAe2tJllusRSZ273h2nFSE=", + IntegrityAlgorithm::kSha256); + + { + ScopedSignatureBasedIntegrityForTest signature_based_integrity(false); + ExpectEmptyParseResult("ed25519-xxxx"); + ExpectEmptyParseResult( + "ed25519-qGFmwTxlocg707D1cX4w60iTwtfwbMLf8ITDyfko7s0="); + } + + ScopedSignatureBasedIntegrityForTest signature_based_integrity(true); + ExpectParse("ed25519-xxxx", "xxxx", IntegrityAlgorithm::kEd25519); + ExpectParse("ed25519-qGFmwTxlocg707D1cX4w60iTwtfwbMLf8ITDyfko7s0=", + "qGFmwTxlocg707D1cX4w60iTwtfwbMLf8ITDyfko7s0=", + IntegrityAlgorithm::kEd25519); + ExpectParse("ed25519-qGFmwTxlocg707D1cX4w60iTwtfwbMLf8ITDyfko7s0=?foo=bar", + "qGFmwTxlocg707D1cX4w60iTwtfwbMLf8ITDyfko7s0=", + IntegrityAlgorithm::kEd25519); + ExpectEmptyParseResult("ed-25519-xxx"); + ExpectEmptyParseResult( + "ed-25519-qGFmwTxlocg707D1cX4w60iTwtfwbMLf8ITDyfko7s0="); +} + +TEST_F(SubresourceIntegrityTest, ParsingBase64) { + ExpectParse( + "sha384-XVVXBGoYw6AJOh9J+Z8pBDMVVPfkBpngexkA7JqZu8d5GENND6TEIup/tA1v5GPr", + "XVVXBGoYw6AJOh9J+Z8pBDMVVPfkBpngexkA7JqZu8d5GENND6TEIup/tA1v5GPr", + IntegrityAlgorithm::kSha384); +} + +// Tests that SubresourceIntegrity::CheckSubresourceIntegrity behaves correctly +// when faced with secure or insecure origins, same origin and cross origin +// requests, successful and failing CORS checks as well as when the response was +// handled by a service worker. +TEST_F(SubresourceIntegrityTest, OriginIntegrity) { + TestCase cases[] = { + // Secure origin, same origin -> integrity expected: + {sec_url, sec_url, nullptr, kNoServiceWorker, kIntegritySuccess}, + {sec_url, sec_url, nullptr, kSWClearResponse, kIntegritySuccess}, + + // Insecure origin, secure target, CORS ok -> integrity expected: + {insec_url, sec_url, &insec_url, kNoServiceWorker, kIntegritySuccess}, + {insec_url, sec_url, &insec_url, kSWClearResponse, kIntegritySuccess}, + {insec_url, sec_url, nullptr, kSWClearResponse, kIntegritySuccess}, + + // Secure origin, insecure target, CORS ok -> no failure expected: + {sec_url, insec_url, &sec_url, kNoServiceWorker, kIntegritySuccess}, + {sec_url, insec_url, &sec_url, kSWClearResponse, kIntegritySuccess}, + {sec_url, insec_url, nullptr, kSWClearResponse, kIntegritySuccess}, + + // Insecure origin, secure target, no CORS headers -> failure expected: + {insec_url, sec_url, nullptr, kNoServiceWorker, kIntegrityFailure}, + + // Insecure origin, secure target, CORS failure -> failure expected: + {insec_url, sec_url, &sec_url, kNoServiceWorker, kIntegrityFailure}, + {insec_url, sec_url, &sec_url, kSWOpaqueResponse, kIntegrityFailure}, + {insec_url, sec_url, nullptr, kSWOpaqueResponse, kIntegrityFailure}, + + // Secure origin, same origin, opaque response from service worker -> + // failure expected: + {sec_url, sec_url, &sec_url, kSWOpaqueResponse, kIntegrityFailure}, + + // Insecure origin, insecure target, same origin-> failure expected: + {sec_url, insec_url, nullptr, kNoServiceWorker, kIntegrityFailure}, + }; + + MockWebCryptoDigestorFactory factory_sha256( + kBasicScript, strlen(kBasicScript), kSha256Hash, sizeof(kSha256Hash)); + MockWebCryptoDigestorFactory factory_sha384( + kBasicScript, strlen(kBasicScript), kSha384Hash, sizeof(kSha384Hash)); + MockWebCryptoDigestorFactory factory_sha512( + kBasicScript, strlen(kBasicScript), kSha512Hash, sizeof(kSha512Hash)); + + CryptoTestingPlatformSupport::SetMockCryptoScope mock_crypto_scope( + *platform_.GetTestingPlatformSupport()); + + EXPECT_CALL(mock_crypto_scope.MockCrypto(), + CreateDigestorProxy(kWebCryptoAlgorithmIdSha256)) + .WillRepeatedly(testing::InvokeWithoutArgs( + &factory_sha256, &MockWebCryptoDigestorFactory::Create)); + EXPECT_CALL(mock_crypto_scope.MockCrypto(), + CreateDigestorProxy(kWebCryptoAlgorithmIdSha384)) + .WillRepeatedly(testing::InvokeWithoutArgs( + &factory_sha384, &MockWebCryptoDigestorFactory::Create)); + EXPECT_CALL(mock_crypto_scope.MockCrypto(), + CreateDigestorProxy(kWebCryptoAlgorithmIdSha512)) + .WillRepeatedly(testing::InvokeWithoutArgs( + &factory_sha512, &MockWebCryptoDigestorFactory::Create)); + + for (const auto& test : cases) { + SCOPED_TRACE( + testing::Message() + << "Origin: " << test.origin.BaseAsString() + << ", target: " << test.target.BaseAsString() + << ", CORS access-control-allow-origin header: " + << (test.allow_origin_url ? test.allow_origin_url->BaseAsString() : "-") + << ", service worker: " + << (test.service_worker == kNoServiceWorker + ? "no" + : (test.service_worker == kSWClearResponse ? "clear response" + : "opaque response")) + << ", expected result: " + << (test.expectation == kIntegritySuccess ? "integrity" : "failure")); + + // Verify basic sha256, sha384, and sha512 integrity checks. + CheckExpectedIntegrity(kSha256Integrity, test); + CheckExpectedIntegrity(kSha256IntegrityLenientSyntax, test); + CheckExpectedIntegrity(kSha384Integrity, test); + CheckExpectedIntegrity(kSha512Integrity, test); + + // Verify multiple hashes in an attribute. + CheckExpectedIntegrity(kSha256AndSha384Integrities, test); + CheckExpectedIntegrity(kBadSha256AndGoodSha384Integrities, test); + + // Unsupported hash functions should succeed. + CheckExpectedIntegrity(kUnsupportedHashFunctionIntegrity, test); + + // Options should be ignored + CheckExpectedIntegrity(kSha256IntegrityWithEmptyOption, test); + CheckExpectedIntegrity(kSha256IntegrityWithOption, test); + CheckExpectedIntegrity(kSha256IntegrityWithOptions, test); + CheckExpectedIntegrity(kSha256IntegrityWithMimeOption, test); + + // The following tests are expected to fail in every scenario: + + // The hash label must match the hash value. + CheckExpectedIntegrity(kSha384IntegrityLabeledAs256, test, + Expectation::kIntegrityFailure); + + // With multiple values, at least one must match, and it must be the + // strongest hash algorithm. + CheckExpectedIntegrity(kGoodSha256AndBadSha384Integrities, test, + Expectation::kIntegrityFailure); + CheckExpectedIntegrity(kBadSha256AndBadSha384Integrities, test, + Expectation::kIntegrityFailure); + } +} + +TEST_F(SubresourceIntegrityTest, FindBestAlgorithm) { + // Each algorithm is its own best. + EXPECT_EQ(IntegrityAlgorithm::kSha256, + SubresourceIntegrity::FindBestAlgorithm( + IntegrityMetadataSet({{"", IntegrityAlgorithm::kSha256}}))); + EXPECT_EQ(IntegrityAlgorithm::kSha384, + SubresourceIntegrity::FindBestAlgorithm( + IntegrityMetadataSet({{"", IntegrityAlgorithm::kSha384}}))); + EXPECT_EQ(IntegrityAlgorithm::kSha512, + SubresourceIntegrity::FindBestAlgorithm( + IntegrityMetadataSet({{"", IntegrityAlgorithm::kSha512}}))); + EXPECT_EQ(IntegrityAlgorithm::kEd25519, + SubresourceIntegrity::FindBestAlgorithm( + IntegrityMetadataSet({{"", IntegrityAlgorithm::kEd25519}}))); + + // Test combinations of multiple algorithms. + EXPECT_EQ(IntegrityAlgorithm::kSha384, + SubresourceIntegrity::FindBestAlgorithm( + IntegrityMetadataSet({{"", IntegrityAlgorithm::kSha256}, + {"", IntegrityAlgorithm::kSha384}}))); + EXPECT_EQ(IntegrityAlgorithm::kSha512, + SubresourceIntegrity::FindBestAlgorithm( + IntegrityMetadataSet({{"", IntegrityAlgorithm::kSha256}, + {"", IntegrityAlgorithm::kSha512}, + {"", IntegrityAlgorithm::kSha384}}))); + EXPECT_EQ(IntegrityAlgorithm::kEd25519, + SubresourceIntegrity::FindBestAlgorithm( + IntegrityMetadataSet({{"", IntegrityAlgorithm::kSha256}, + {"", IntegrityAlgorithm::kSha512}, + {"", IntegrityAlgorithm::kEd25519}}))); +} + +TEST_F(SubresourceIntegrityTest, GetCheckFunctionForAlgorithm) { + EXPECT_TRUE(SubresourceIntegrity::CheckSubresourceIntegrityDigest == + SubresourceIntegrity::GetCheckFunctionForAlgorithm( + IntegrityAlgorithm::kSha256)); + EXPECT_TRUE(SubresourceIntegrity::CheckSubresourceIntegrityDigest == + SubresourceIntegrity::GetCheckFunctionForAlgorithm( + IntegrityAlgorithm::kSha384)); + EXPECT_TRUE(SubresourceIntegrity::CheckSubresourceIntegrityDigest == + SubresourceIntegrity::GetCheckFunctionForAlgorithm( + IntegrityAlgorithm::kSha512)); + EXPECT_TRUE(SubresourceIntegrity::CheckSubresourceIntegritySignature == + SubresourceIntegrity::GetCheckFunctionForAlgorithm( + IntegrityAlgorithm::kEd25519)); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/platform/loader/testing/crypto_testing_platform_support.h b/chromium/third_party/blink/renderer/platform/loader/testing/crypto_testing_platform_support.h new file mode 100644 index 00000000000..19ba9b6dfe1 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/loader/testing/crypto_testing_platform_support.h @@ -0,0 +1,51 @@ +// 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_PLATFORM_LOADER_TESTING_CRYPTO_TESTING_PLATFORM_SUPPORT_H_ +#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_LOADER_TESTING_CRYPTO_TESTING_PLATFORM_SUPPORT_H_ + +#include <memory> +#include "third_party/blink/renderer/platform/loader/testing/fetch_testing_platform_support.h" +#include "third_party/blink/renderer/platform/testing/mock_web_crypto.h" +#include "third_party/blink/renderer/platform/wtf/allocator.h" + +namespace blink { + +class CryptoTestingPlatformSupport : public FetchTestingPlatformSupport { + public: + CryptoTestingPlatformSupport() = default; + ~CryptoTestingPlatformSupport() override = default; + + // Platform: + WebCrypto* Crypto() override { return mock_web_crypto_.get(); } + + class SetMockCryptoScope final { + STACK_ALLOCATED(); + + public: + explicit SetMockCryptoScope(CryptoTestingPlatformSupport& platform) + : platform_(platform) { + DCHECK(!platform_.Crypto()); + platform_.SetMockCrypto(MockWebCrypto::Create()); + } + ~SetMockCryptoScope() { platform_.SetMockCrypto(nullptr); } + MockWebCrypto& MockCrypto() { return *platform_.mock_web_crypto_; } + + private: + CryptoTestingPlatformSupport& platform_; + }; + + private: + void SetMockCrypto(std::unique_ptr<MockWebCrypto> crypto) { + mock_web_crypto_ = std::move(crypto); + } + + std::unique_ptr<MockWebCrypto> mock_web_crypto_; + + DISALLOW_COPY_AND_ASSIGN(CryptoTestingPlatformSupport); +}; + +} // namespace blink + +#endif diff --git a/chromium/third_party/blink/renderer/platform/loader/testing/fetch_testing_platform_support.cc b/chromium/third_party/blink/renderer/platform/loader/testing/fetch_testing_platform_support.cc new file mode 100644 index 00000000000..ac86a8cca00 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/loader/testing/fetch_testing_platform_support.cc @@ -0,0 +1,48 @@ +// 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/platform/loader/testing/fetch_testing_platform_support.h" + +#include <memory> +#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.h" +#include "third_party/blink/public/platform/web_url_loader_mock_factory.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource_error.h" +#include "third_party/blink/renderer/platform/loader/testing/mock_fetch_context.h" +#include "third_party/blink/renderer/platform/loader/testing/web_url_loader_factory_with_mock.h" +#include "third_party/blink/renderer/platform/testing/weburl_loader_mock_factory_impl.h" + +namespace blink { + +FetchTestingPlatformSupport::FetchTestingPlatformSupport() + : url_loader_mock_factory_(new WebURLLoaderMockFactoryImpl(this)) {} + +FetchTestingPlatformSupport::~FetchTestingPlatformSupport() { + // Shutdowns WebURLLoaderMockFactory gracefully, serving all pending requests + // first, then flushing all registered URLs. + url_loader_mock_factory_->ServeAsynchronousRequests(); + url_loader_mock_factory_->UnregisterAllURLsAndClearMemoryCache(); +} + +MockFetchContext* FetchTestingPlatformSupport::Context() { + if (!context_) { + context_ = + MockFetchContext::Create(MockFetchContext::kShouldLoadNewResource); + } + return context_; +} + +WebURLLoaderMockFactory* +FetchTestingPlatformSupport::GetURLLoaderMockFactory() { + return url_loader_mock_factory_.get(); +} + +std::unique_ptr<WebURLLoaderFactory> +FetchTestingPlatformSupport::CreateDefaultURLLoaderFactory() { + return std::make_unique<WebURLLoaderFactoryWithMock>( + url_loader_mock_factory_.get()); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/platform/loader/testing/fetch_testing_platform_support.h b/chromium/third_party/blink/renderer/platform/loader/testing/fetch_testing_platform_support.h new file mode 100644 index 00000000000..10e4c61c532 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/loader/testing/fetch_testing_platform_support.h @@ -0,0 +1,39 @@ +// 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_PLATFORM_LOADER_TESTING_FETCH_TESTING_PLATFORM_SUPPORT_H_ +#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_LOADER_TESTING_FETCH_TESTING_PLATFORM_SUPPORT_H_ + +#include <memory> +#include "third_party/blink/renderer/platform/heap/persistent.h" +#include "third_party/blink/renderer/platform/testing/testing_platform_support_with_mock_scheduler.h" + +namespace blink { + +class MockFetchContext; + +class FetchTestingPlatformSupport + : public TestingPlatformSupportWithMockScheduler { + public: + FetchTestingPlatformSupport(); + ~FetchTestingPlatformSupport() override; + + MockFetchContext* Context(); + + // Platform: + WebURLLoaderMockFactory* GetURLLoaderMockFactory() override; + std::unique_ptr<WebURLLoaderFactory> CreateDefaultURLLoaderFactory() override; + + private: + class FetchTestingWebURLLoaderMockFactory; + + Persistent<MockFetchContext> context_; + std::unique_ptr<WebURLLoaderMockFactory> url_loader_mock_factory_; + + DISALLOW_COPY_AND_ASSIGN(FetchTestingPlatformSupport); +}; + +} // namespace blink + +#endif diff --git a/chromium/third_party/blink/renderer/platform/loader/testing/mock_fetch_context.h b/chromium/third_party/blink/renderer/platform/loader/testing/mock_fetch_context.h new file mode 100644 index 00000000000..e5f3adb730f --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/loader/testing/mock_fetch_context.h @@ -0,0 +1,166 @@ +// 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_PLATFORM_LOADER_TESTING_MOCK_FETCH_CONTEXT_H_ +#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_LOADER_TESTING_MOCK_FETCH_CONTEXT_H_ + +#include "third_party/blink/public/platform/platform.h" +#include "third_party/blink/public/platform/web_url_loader_factory.h" +#include "third_party/blink/renderer/platform/exported/wrapped_resource_request.h" +#include "third_party/blink/renderer/platform/loader/fetch/fetch_context.h" +#include "third_party/blink/renderer/platform/loader/fetch/fetch_parameters.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource_timing_info.h" +#include "third_party/blink/renderer/platform/scheduler/public/frame_scheduler.h" +#include "third_party/blink/renderer/platform/scheduler/test/fake_frame_scheduler.h" +#include "third_party/blink/renderer/platform/scheduler/test/fake_task_runner.h" +#include "third_party/blink/renderer/platform/wtf/optional.h" + +#include <memory> + +namespace blink { + +class KURL; +class ResourceRequest; +struct ResourceLoaderOptions; + +// Mocked FetchContext for testing. +class MockFetchContext : public FetchContext { + public: + enum LoadPolicy { + kShouldLoadNewResource, + kShouldNotLoadNewResource, + }; + static MockFetchContext* Create(LoadPolicy load_policy, + scoped_refptr<base::SingleThreadTaskRunner> + loading_task_runner = nullptr) { + return new MockFetchContext(load_policy, std::move(loading_task_runner)); + } + + ~MockFetchContext() override = default; + + void SetLoadComplete(bool complete) { complete_ = complete; } + long long GetTransferSize() const { return transfer_size_; } + + const SecurityOrigin* GetSecurityOrigin() const override { + return security_origin_.get(); + } + + void SetSecurityOrigin(scoped_refptr<const SecurityOrigin> security_origin) { + security_origin_ = security_origin; + } + + // The last ResourceRequest passed to DispatchWillSendRequest. + WTF::Optional<ResourceRequest> RequestFromWillSendRequest() const { + return will_send_request_; + } + + // FetchContext: + void DispatchWillSendRequest( + unsigned long identifier, + ResourceRequest& request, + const ResourceResponse& redirect_response, + Resource::Type, + const FetchInitiatorInfo& = FetchInitiatorInfo()) override { + will_send_request_ = request; + } + bool AllowImage(bool images_enabled, const KURL&) const override { + return true; + } + ResourceRequestBlockedReason CanRequest( + Resource::Type, + const ResourceRequest&, + const KURL&, + const ResourceLoaderOptions&, + SecurityViolationReportingPolicy, + FetchParameters::OriginRestriction, + ResourceRequest::RedirectStatus redirect_status) const override { + return ResourceRequestBlockedReason::kNone; + } + ResourceRequestBlockedReason CheckCSPForRequest( + WebURLRequest::RequestContext, + const KURL& url, + const ResourceLoaderOptions& options, + SecurityViolationReportingPolicy reporting_policy, + ResourceRequest::RedirectStatus redirect_status) const override { + return ResourceRequestBlockedReason::kNone; + } + virtual ResourceRequestBlockedReason CheckResponseNosniff( + WebURLRequest::RequestContext, + const ResourceResponse&) const { + return ResourceRequestBlockedReason::kNone; + } + bool ShouldLoadNewResource(Resource::Type) const override { + return load_policy_ == kShouldLoadNewResource; + } + bool IsLoadComplete() const override { return complete_; } + void AddResourceTiming( + const ResourceTimingInfo& resource_timing_info) override { + transfer_size_ = resource_timing_info.TransferSize(); + } + + std::unique_ptr<WebURLLoader> CreateURLLoader( + const ResourceRequest& request, + scoped_refptr<base::SingleThreadTaskRunner> task_runner, + const ResourceLoaderOptions&) override { + if (!url_loader_factory_) { + url_loader_factory_ = + Platform::Current()->CreateDefaultURLLoaderFactory(); + } + WrappedResourceRequest wrapped(request); + return url_loader_factory_->CreateURLLoader(wrapped, task_runner); + } + + ResourceLoadScheduler::ThrottlingPolicy InitialLoadThrottlingPolicy() + const override { + return ResourceLoadScheduler::ThrottlingPolicy::kTight; + } + + FrameScheduler* GetFrameScheduler() const override { + return frame_scheduler_.get(); + } + + scoped_refptr<base::SingleThreadTaskRunner> GetLoadingTaskRunner() override { + return frame_scheduler_->GetTaskRunner(TaskType::kInternalTest); + } + + private: + class MockFrameScheduler final : public scheduler::FakeFrameScheduler { + public: + explicit MockFrameScheduler( + scoped_refptr<base::SingleThreadTaskRunner> runner) + : runner_(std::move(runner)) {} + scoped_refptr<base::SingleThreadTaskRunner> GetTaskRunner( + TaskType) override { + return runner_; + } + + private: + scoped_refptr<base::SingleThreadTaskRunner> runner_; + }; + + MockFetchContext( + LoadPolicy load_policy, + scoped_refptr<base::SingleThreadTaskRunner> loading_task_runner) + : load_policy_(load_policy), + runner_(loading_task_runner + ? std::move(loading_task_runner) + : base::MakeRefCounted<scheduler::FakeTaskRunner>()), + security_origin_(SecurityOrigin::CreateUnique()), + frame_scheduler_(new MockFrameScheduler(runner_)), + complete_(false), + transfer_size_(-1) {} + + enum LoadPolicy load_policy_; + scoped_refptr<base::SingleThreadTaskRunner> runner_; + scoped_refptr<const SecurityOrigin> security_origin_; + std::unique_ptr<FrameScheduler> frame_scheduler_; + std::unique_ptr<WebURLLoaderFactory> url_loader_factory_; + bool complete_; + long long transfer_size_; + WTF::Optional<ResourceRequest> will_send_request_; +}; + +} // namespace blink + +#endif diff --git a/chromium/third_party/blink/renderer/platform/loader/testing/mock_resource.cc b/chromium/third_party/blink/renderer/platform/loader/testing/mock_resource.cc new file mode 100644 index 00000000000..367473a7aa0 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/loader/testing/mock_resource.cc @@ -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. + +#include "third_party/blink/renderer/platform/loader/testing/mock_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/resource_loader_options.h" + +namespace blink { + +namespace { + +class MockResourceFactory final : public NonTextResourceFactory { + public: + MockResourceFactory() : NonTextResourceFactory(Resource::kMock) {} + + Resource* Create(const ResourceRequest& request, + const ResourceLoaderOptions& options) const override { + return new MockResource(request, options); + } +}; + +} // namespace + +// static +MockResource* MockResource::Fetch(FetchParameters& params, + ResourceFetcher* fetcher, + ResourceClient* client) { + params.SetRequestContext(WebURLRequest::kRequestContextSubresource); + return static_cast<MockResource*>( + fetcher->RequestResource(params, MockResourceFactory(), client)); +} + +// static +MockResource* MockResource::Create(const ResourceRequest& request) { + ResourceLoaderOptions options; + return new MockResource(request, options); +} + +MockResource* MockResource::Create(const KURL& url) { + ResourceRequest request(url); + return Create(request); +} + +MockResource::MockResource(const ResourceRequest& request, + const ResourceLoaderOptions& options) + : Resource(request, Resource::kMock, options) {} + +CachedMetadataHandler* MockResource::CreateCachedMetadataHandler( + std::unique_ptr<CachedMetadataSender> send_callback) { + return new MockCacheHandler(std::move(send_callback)); +} + +void MockResource::SetSerializedCachedMetadata(const char* data, size_t size) { + Resource::SetSerializedCachedMetadata(data, size); + MockCacheHandler* cache_handler = + static_cast<MockCacheHandler*>(Resource::CacheHandler()); + if (cache_handler) { + cache_handler->Set(data, size); + } +} + +void MockResource::SendCachedMetadata(const char* data, size_t size) { + MockCacheHandler* cache_handler = + static_cast<MockCacheHandler*>(Resource::CacheHandler()); + if (cache_handler) { + cache_handler->Set(data, size); + cache_handler->Send(); + } +} + +MockCacheHandler* MockResource::CacheHandler() { + return static_cast<MockCacheHandler*>(Resource::CacheHandler()); +} + +MockCacheHandler::MockCacheHandler( + std::unique_ptr<CachedMetadataSender> send_callback) + : send_callback_(std::move(send_callback)) {} + +void MockCacheHandler::Set(const char* data, size_t size) { + data_.emplace(); + data_->Append(data, size); +} + +void MockCacheHandler::ClearCachedMetadata( + CachedMetadataHandler::CacheType cache_type) { + if (cache_type == CachedMetadataHandler::kSendToPlatform) { + Send(); + } + data_.reset(); +} + +void MockCacheHandler::Send() { + if (data_) { + send_callback_->Send(data_->data(), data_->size()); + } else { + send_callback_->Send(nullptr, 0); + } +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/platform/loader/testing/mock_resource.h b/chromium/third_party/blink/renderer/platform/loader/testing/mock_resource.h new file mode 100644 index 00000000000..69e307efbe4 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/loader/testing/mock_resource.h @@ -0,0 +1,60 @@ +// 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_PLATFORM_LOADER_TESTING_MOCK_RESOURCE_H_ +#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_LOADER_TESTING_MOCK_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/wtf/ref_counted.h" + +namespace blink { + +class FetchParameters; +class ResourceFetcher; +struct ResourceLoaderOptions; + +// Mocked cache handler class used by MockResource to test the caching behaviour +// of Resource. +class MockCacheHandler : public CachedMetadataHandler { + public: + MockCacheHandler(std::unique_ptr<CachedMetadataSender> send_callback); + + void Set(const char* data, size_t); + void ClearCachedMetadata(CachedMetadataHandler::CacheType) override; + void Send(); + + String Encoding() const override { return "mock encoding"; } + bool IsServedFromCacheStorage() const override { return false; } + + private: + std::unique_ptr<CachedMetadataSender> send_callback_; + base::Optional<Vector<char>> data_; +}; + +// Mocked Resource sub-class for testing. MockResource class can pretend a type +// of Resource sub-class in a simple way. You should not expect anything +// complicated to emulate actual sub-resources, but you may be able to use this +// class to verify classes that consume Resource sub-classes in a simple way. +class MockResource final : public Resource { + public: + static MockResource* Fetch(FetchParameters&, + ResourceFetcher*, + ResourceClient*); + static MockResource* Create(const ResourceRequest&); + static MockResource* Create(const KURL&); + MockResource(const ResourceRequest&, const ResourceLoaderOptions&); + + CachedMetadataHandler* CreateCachedMetadataHandler( + std::unique_ptr<CachedMetadataSender> send_callback) override; + void SetSerializedCachedMetadata(const char*, size_t) override; + + MockCacheHandler* CacheHandler(); + + void SendCachedMetadata(const char*, size_t); +}; + +} // namespace blink + +#endif diff --git a/chromium/third_party/blink/renderer/platform/loader/testing/mock_resource_client.h b/chromium/third_party/blink/renderer/platform/loader/testing/mock_resource_client.h new file mode 100644 index 00000000000..a9fe34287e3 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/loader/testing/mock_resource_client.h @@ -0,0 +1,61 @@ +/* + * 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_PLATFORM_LOADER_TESTING_MOCK_RESOURCE_CLIENT_H_ +#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_LOADER_TESTING_MOCK_RESOURCE_CLIENT_H_ + +#include "third_party/blink/renderer/platform/heap/handle.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource_client.h" + +namespace blink { + +class MockResourceClient : public GarbageCollectedFinalized<MockResourceClient>, + public ResourceClient { + USING_GARBAGE_COLLECTED_MIXIN(MockResourceClient); + + public: + MockResourceClient() = default; + ~MockResourceClient() override = default; + + void NotifyFinished(Resource*) override { + CHECK(!notify_finished_called_); + notify_finished_called_ = true; + } + String DebugName() const override { return "MockResourceClient"; } + bool NotifyFinishedCalled() const { return notify_finished_called_; } + void RemoveAsClient() { ClearResource(); } + + protected: + bool notify_finished_called_ = false; +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_PLATFORM_LOADER_TESTING_MOCK_RESOURCE_CLIENT_H_ diff --git a/chromium/third_party/blink/renderer/platform/loader/testing/web_url_loader_factory_with_mock.cc b/chromium/third_party/blink/renderer/platform/loader/testing/web_url_loader_factory_with_mock.cc new file mode 100644 index 00000000000..3297570e76a --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/loader/testing/web_url_loader_factory_with_mock.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/platform/loader/testing/web_url_loader_factory_with_mock.h" + +#include "third_party/blink/public/platform/web_url_loader.h" +#include "third_party/blink/public/platform/web_url_loader_mock_factory.h" + +namespace blink { + +WebURLLoaderFactoryWithMock::WebURLLoaderFactoryWithMock( + WebURLLoaderMockFactory* mock_factory) + : mock_factory_(mock_factory) {} + +WebURLLoaderFactoryWithMock::~WebURLLoaderFactoryWithMock() = default; + +std::unique_ptr<WebURLLoader> WebURLLoaderFactoryWithMock::CreateURLLoader( + const WebURLRequest& request, + scoped_refptr<base::SingleThreadTaskRunner> task_runner) { + return mock_factory_->CreateURLLoader(nullptr); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/platform/loader/testing/web_url_loader_factory_with_mock.h b/chromium/third_party/blink/renderer/platform/loader/testing/web_url_loader_factory_with_mock.h new file mode 100644 index 00000000000..c66ba9ff8fd --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/loader/testing/web_url_loader_factory_with_mock.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_PLATFORM_LOADER_TESTING_WEB_URL_LOADER_FACTORY_WITH_MOCK_H_ +#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_LOADER_TESTING_WEB_URL_LOADER_FACTORY_WITH_MOCK_H_ + +#include <memory> + +#include "third_party/blink/public/platform/web_url_loader_factory.h" + +namespace blink { + +class WebURLLoaderMockFactory; + +class WebURLLoaderFactoryWithMock : public WebURLLoaderFactory { + public: + explicit WebURLLoaderFactoryWithMock(WebURLLoaderMockFactory*); + ~WebURLLoaderFactoryWithMock() override; + + std::unique_ptr<WebURLLoader> CreateURLLoader( + const WebURLRequest&, + scoped_refptr<base::SingleThreadTaskRunner>) override; + + private: + // Not owned. The mock factory should outlive |this|. + WebURLLoaderMockFactory* mock_factory_; +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_PLATFORM_LOADER_TESTING_WEB_URL_LOADER_FACTORY_WITH_MOCK_H_ |