blob: ffa8ab2350b5bae012e06598760aa35b2c6385c8 [file] [log] [blame]
/*
* Copyright (C) 2006, 2007, 2008 Apple Inc. All rights reserved.
* Copyright (C) 2011 Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
* its contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "third_party/blink/renderer/core/loader/document_loader.h"
#include <memory>
#include <optional>
#include <utility>
#include "base/auto_reset.h"
#include "base/containers/flat_map.h"
#include "base/debug/dump_without_crashing.h"
#include "base/feature_list.h"
#include "base/metrics/field_trial_params.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/numerics/safe_conversions.h"
#include "base/time/default_tick_clock.h"
#include "base/types/optional_util.h"
#include "base/uuid.h"
#include "build/chromeos_buildflags.h"
#include "services/metrics/public/cpp/ukm_builders.h"
#include "services/network/public/cpp/client_hints.h"
#include "services/network/public/cpp/header_util.h"
#include "services/network/public/cpp/shared_dictionary_encoding_names.h"
#include "services/network/public/cpp/web_sandbox_flags.h"
#include "services/network/public/mojom/url_response_head.mojom-shared.h"
#include "services/network/public/mojom/web_sandbox_flags.mojom-blink.h"
#include "third_party/blink/public/common/client_hints/client_hints.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/common/loader/javascript_framework_detection.h"
#include "third_party/blink/public/common/loader/loading_behavior_flag.h"
#include "third_party/blink/public/common/metrics/accept_language_and_content_language_usage.h"
#include "third_party/blink/public/common/page/browsing_context_group_info.h"
#include "third_party/blink/public/common/permissions_policy/permissions_policy.h"
#include "third_party/blink/public/common/scheme_registry.h"
#include "third_party/blink/public/mojom/commit_result/commit_result.mojom-blink.h"
#include "third_party/blink/public/mojom/fetch/fetch_api_request.mojom-blink.h"
#include "third_party/blink/public/mojom/frame/frame.mojom-blink.h"
#include "third_party/blink/public/mojom/origin_trial_feature/origin_trial_feature.mojom-shared.h"
#include "third_party/blink/public/mojom/page/page.mojom-blink.h"
#include "third_party/blink/public/mojom/service_worker/service_worker_fetch_handler_bypass_option.mojom-blink.h"
#include "third_party/blink/public/mojom/service_worker/service_worker_fetch_handler_type.mojom-blink.h"
#include "third_party/blink/public/mojom/timing/resource_timing.mojom-blink-forward.h"
#include "third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom-shared.h"
#include "third_party/blink/public/platform/modules/service_worker/web_service_worker_network_provider.h"
#include "third_party/blink/public/platform/platform.h"
#include "third_party/blink/public/platform/web_content_security_policy_struct.h"
#include "third_party/blink/public/platform/web_url_request.h"
#include "third_party/blink/public/web/blink.h"
#include "third_party/blink/public/web/web_navigation_type.h"
#include "third_party/blink/renderer/bindings/core/v8/script_controller.h"
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/dom/document_init.h"
#include "third_party/blink/renderer/core/dom/document_parser.h"
#include "third_party/blink/renderer/core/dom/events/event.h"
#include "third_party/blink/renderer/core/dom/scriptable_document_parser.h"
#include "third_party/blink/renderer/core/dom/weak_identifier_map.h"
#include "third_party/blink/renderer/core/execution_context/agent.h"
#include "third_party/blink/renderer/core/execution_context/security_context_init.h"
#include "third_party/blink/renderer/core/execution_context/window_agent.h"
#include "third_party/blink/renderer/core/execution_context/window_agent_factory.h"
#include "third_party/blink/renderer/core/fragment_directive/text_fragment_anchor.h"
#include "third_party/blink/renderer/core/frame/csp/content_security_policy.h"
#include "third_party/blink/renderer/core/frame/deprecation/deprecation.h"
#include "third_party/blink/renderer/core/frame/frame_console.h"
#include "third_party/blink/renderer/core/frame/intervention.h"
#include "third_party/blink/renderer/core/frame/local_dom_window.h"
#include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/core/frame/local_frame_client.h"
#include "third_party/blink/renderer/core/frame/navigator.h"
#include "third_party/blink/renderer/core/frame/settings.h"
#include "third_party/blink/renderer/core/frame/web_feature.h"
#include "third_party/blink/renderer/core/html/html_document.h"
#include "third_party/blink/renderer/core/html/html_frame_owner_element.h"
#include "third_party/blink/renderer/core/html/html_object_element.h"
#include "third_party/blink/renderer/core/html/parser/html_parser_idioms.h"
#include "third_party/blink/renderer/core/html/parser/text_resource_decoder_builder.h"
#include "third_party/blink/renderer/core/input/event_handler.h"
#include "third_party/blink/renderer/core/inspector/console_message.h"
#include "third_party/blink/renderer/core/inspector/inspector_trace_events.h"
#include "third_party/blink/renderer/core/inspector/main_thread_debugger.h"
#include "third_party/blink/renderer/core/lcp_critical_path_predictor/lcp_critical_path_predictor.h"
#include "third_party/blink/renderer/core/loader/alternate_signed_exchange_resource_info.h"
#include "third_party/blink/renderer/core/loader/frame_client_hints_preferences_context.h"
#include "third_party/blink/renderer/core/loader/frame_fetch_context.h"
#include "third_party/blink/renderer/core/loader/frame_loader.h"
#include "third_party/blink/renderer/core/loader/idleness_detector.h"
#include "third_party/blink/renderer/core/loader/interactive_detector.h"
#include "third_party/blink/renderer/core/loader/old_document_info_for_commit.h"
#include "third_party/blink/renderer/core/loader/prefetched_signed_exchange_manager.h"
#include "third_party/blink/renderer/core/loader/preload_helper.h"
#include "third_party/blink/renderer/core/loader/progress_tracker.h"
#include "third_party/blink/renderer/core/loader/subresource_filter.h"
#include "third_party/blink/renderer/core/mobile_metrics/mobile_friendliness_checker.h"
#include "third_party/blink/renderer/core/navigation_api/navigation_api.h"
#include "third_party/blink/renderer/core/origin_trials/origin_trial_context.h"
#include "third_party/blink/renderer/core/page/frame_tree.h"
#include "third_party/blink/renderer/core/page/page.h"
#include "third_party/blink/renderer/core/performance_entry_names.h"
#include "third_party/blink/renderer/core/permissions_policy/document_policy_parser.h"
#include "third_party/blink/renderer/core/probe/core_probes.h"
#include "third_party/blink/renderer/core/speculation_rules/auto_speculation_rules_config.h"
#include "third_party/blink/renderer/core/speculation_rules/document_speculation_rules.h"
#include "third_party/blink/renderer/core/speculation_rules/speculation_rule_set.h"
#include "third_party/blink/renderer/core/speculation_rules/speculation_rules_header.h"
#include "third_party/blink/renderer/core/timing/dom_window_performance.h"
#include "third_party/blink/renderer/core/timing/profiler_group.h"
#include "third_party/blink/renderer/core/timing/soft_navigation_heuristics.h"
#include "third_party/blink/renderer/core/timing/window_performance.h"
#include "third_party/blink/renderer/core/view_transition/view_transition_supplement.h"
#include "third_party/blink/renderer/core/xml/document_xslt.h"
#include "third_party/blink/renderer/platform/bindings/script_forbidden_scope.h"
#include "third_party/blink/renderer/platform/bindings/v8_per_isolate_data.h"
#include "third_party/blink/renderer/platform/fonts/font_performance.h"
#include "third_party/blink/renderer/platform/heap/garbage_collected.h"
#include "third_party/blink/renderer/platform/loader/cors/cors.h"
#include "third_party/blink/renderer/platform/loader/fetch/background_code_cache_host.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/loader_freeze_mode.h"
#include "third_party/blink/renderer/platform/loader/fetch/memory_cache.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource_load_timing.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource_loader_options.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource_timing_utils.h"
#include "third_party/blink/renderer/platform/loader/fetch/unique_identifier.h"
#include "third_party/blink/renderer/platform/loader/fetch/url_loader/navigation_body_loader.h"
#include "third_party/blink/renderer/platform/loader/static_data_navigation_body_loader.h"
#include "third_party/blink/renderer/platform/mhtml/archive_resource.h"
#include "third_party/blink/renderer/platform/mhtml/mhtml_archive.h"
#include "third_party/blink/renderer/platform/network/content_security_policy_response_headers.h"
#include "third_party/blink/renderer/platform/network/encoded_form_data.h"
#include "third_party/blink/renderer/platform/network/http_names.h"
#include "third_party/blink/renderer/platform/network/http_parsers.h"
#include "third_party/blink/renderer/platform/network/network_utils.h"
#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
#include "third_party/blink/renderer/platform/runtime_feature_state/runtime_feature_state_override_context.h"
#include "third_party/blink/renderer/platform/scheduler/public/event_loop.h"
#include "third_party/blink/renderer/platform/scheduler/public/frame_scheduler.h"
#include "third_party/blink/renderer/platform/scheduler/public/task_attribution_tracker.h"
#include "third_party/blink/renderer/platform/storage/blink_storage_key.h"
#include "third_party/blink/renderer/platform/web_test_support.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_policy.h"
#include "third_party/blink/renderer/platform/wtf/cross_thread_functional.h"
#include "third_party/blink/renderer/platform/wtf/hash_map.h"
#include "third_party/blink/renderer/platform/wtf/text/string_builder.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"
namespace blink {
namespace {
Vector<mojom::blink::OriginTrialFeature> CopyInitiatorOriginTrials(
const WebVector<int>& initiator_origin_trial_features) {
Vector<mojom::blink::OriginTrialFeature> result;
for (auto feature : initiator_origin_trial_features) {
// Convert from int to OriginTrialFeature. These values are passed between
// blink navigations. OriginTrialFeature isn't visible outside of blink (and
// doesn't need to be) so the values are transferred outside of blink as
// ints and casted to OriginTrialFeature once being processed in blink.
result.push_back(static_cast<mojom::blink::OriginTrialFeature>(feature));
}
return result;
}
WebVector<int> CopyInitiatorOriginTrials(
const Vector<mojom::blink::OriginTrialFeature>&
initiator_origin_trial_features) {
WebVector<int> result;
for (auto feature : initiator_origin_trial_features) {
// Convert from OriginTrialFeature to int. These values are passed between
// blink navigations. OriginTrialFeature isn't visible outside of blink (and
// doesn't need to be) so the values are transferred outside of blink as
// ints and casted to OriginTrialFeature once being processed in blink.
result.emplace_back(static_cast<int>(feature));
}
return result;
}
Vector<String> CopyForceEnabledOriginTrials(
const WebVector<WebString>& force_enabled_origin_trials) {
Vector<String> result;
result.ReserveInitialCapacity(
base::checked_cast<wtf_size_t>(force_enabled_origin_trials.size()));
for (const auto& trial : force_enabled_origin_trials)
result.push_back(trial);
return result;
}
WebVector<WebString> CopyForceEnabledOriginTrials(
const Vector<String>& force_enabled_origin_trials) {
WebVector<String> result;
for (const auto& trial : force_enabled_origin_trials)
result.emplace_back(trial);
return result;
}
bool IsPagePopupRunningInWebTest(LocalFrame* frame) {
return frame && frame->GetPage()->GetChromeClient().IsPopup() &&
WebTestSupport::IsRunningWebTest();
}
struct SameSizeAsDocumentLoader
: public GarbageCollected<SameSizeAsDocumentLoader>,
public WebDocumentLoader,
public UseCounter,
public WebNavigationBodyLoader::Client {
Member<MHTMLArchive> archive;
std::unique_ptr<WebNavigationParams> params;
std::unique_ptr<PolicyContainer> policy_container;
std::optional<ParsedPermissionsPolicy> isolated_app_permissions_policy;
DocumentToken token;
KURL url;
KURL original_url;
AtomicString http_method;
AtomicString referrer;
scoped_refptr<EncodedFormData> http_body;
AtomicString http_content_type;
scoped_refptr<const SecurityOrigin> requestor_origin;
KURL unreachable_url;
KURL pre_redirect_url_for_failed_navigations;
std::unique_ptr<WebNavigationBodyLoader> body_loader;
bool grant_load_local_resources;
std::optional<blink::mojom::FetchCacheMode> force_fetch_cache_mode;
FramePolicy frame_policy;
Member<LocalFrame> frame;
Member<HistoryItem> history_item;
Member<DocumentParser> parser;
Member<SubresourceFilter> subresource_filter;
AtomicString original_referrer;
ResourceResponse response;
mutable WrappedResourceResponse response_wrapper;
WebFrameLoadType load_type;
bool is_client_redirect;
bool replaces_current_history_item;
bool data_received;
bool is_error_page_for_failed_navigation;
HeapMojoRemote<mojom::blink::ContentSecurityNotifier>
content_security_notifier_;
scoped_refptr<SecurityOrigin> origin_to_commit;
AtomicString origin_calculation_debug_info;
BlinkStorageKey storage_key;
BlinkStorageKey session_storage_key;
WebNavigationType navigation_type;
DocumentLoadTiming document_load_timing;
base::TimeTicks time_of_last_data_received;
mojom::blink::ControllerServiceWorkerMode
service_worker_initial_controller_mode;
std::unique_ptr<WebServiceWorkerNetworkProvider>
service_worker_network_provider;
DocumentPolicy::ParsedDocumentPolicy document_policy;
bool was_blocked_by_document_policy;
Vector<PolicyParserMessageBuffer::Message> document_policy_parsing_messages;
ClientHintsPreferences client_hints_preferences;
DocumentLoader::InitialScrollState initial_scroll_state;
DocumentLoader::State state;
int parser_blocked_count;
bool finish_loading_when_parser_resumed;
bool in_commit_data;
scoped_refptr<SharedBuffer> data_buffer;
Vector<DocumentLoader::DecodedBodyData> decoded_data_buffer_;
base::UnguessableToken devtools_navigation_token;
base::Uuid base_auction_nonce;
LoaderFreezeMode defers_loading;
bool last_navigation_had_transient_user_activation;
bool had_sticky_activation;
bool is_browser_initiated;
bool is_prerendering;
bool is_same_origin_navigation;
bool has_text_fragment_token;
bool was_discarded;
bool loading_main_document_from_mhtml_archive;
bool loading_srcdoc;
KURL fallback_base_url;
bool loading_url_as_empty_document;
bool is_static_data;
CommitReason commit_reason;
uint64_t main_resource_identifier;
mojom::blink::ResourceTimingInfoPtr resource_timing_info_for_parent;
WebScopedVirtualTimePauser virtual_time_pauser;
Member<PrefetchedSignedExchangeManager> prefetched_signed_exchange_manager;
ukm::SourceId ukm_source_id;
UseCounterImpl use_counter;
const base::TickClock* clock;
const Vector<mojom::blink::OriginTrialFeature>
initiator_origin_trial_features;
const Vector<String> force_enabled_origin_trials;
bool navigation_scroll_allowed;
bool origin_agent_cluster;
bool origin_agent_cluster_left_as_default;
bool is_cross_site_cross_browsing_context_group;
bool should_have_sticky_user_activation;
WebVector<WebHistoryItem> navigation_api_back_entries;
WebVector<WebHistoryItem> navigation_api_forward_entries;
Member<HistoryItem> navigation_api_previous_entry;
std::unique_ptr<CodeCacheHost> code_cache_host;
mojo::PendingRemote<mojom::blink::CodeCacheHost>
pending_code_cache_host_for_background;
HashMap<KURL, EarlyHintsPreloadEntry> early_hints_preloaded_resources;
std::optional<Vector<KURL>> ad_auction_components;
std::unique_ptr<ExtraData> extra_data;
AtomicString reduced_accept_language;
network::mojom::NavigationDeliveryType navigation_delivery_type;
std::optional<ViewTransitionState> view_transition_state;
std::optional<FencedFrame::RedactedFencedFrameProperties>
fenced_frame_properties;
bool has_storage_access;
mojom::blink::ParentResourceTimingAccess parent_resource_timing_access;
const std::optional<BrowsingContextGroupInfo> browsing_context_group_info;
const base::flat_map<mojom::blink::RuntimeFeature, bool>
modified_runtime_features;
AtomicString cookie_deprecation_label;
mojom::RendererContentSettingsPtr content_settings;
int64_t body_size_from_service_worker;
};
// Asserts size of DocumentLoader, so that whenever a new attribute is added to
// DocumentLoader, the assert will fail. When hitting this assert failure,
// please ensure that the attribute is copied correctly (if appropriate) in
// DocumentLoader::CreateWebNavigationParamsToCloneDocument().
ASSERT_SIZE(DocumentLoader, SameSizeAsDocumentLoader);
void WarnIfSandboxIneffective(LocalDOMWindow* window) {
if (window->document()->IsInitialEmptyDocument())
return;
if (window->IsInFencedFrame())
return;
const Frame* frame = window->GetFrame();
if (!frame)
return;
using WebSandboxFlags = network::mojom::blink::WebSandboxFlags;
const WebSandboxFlags& sandbox =
window->GetSecurityContext().GetSandboxFlags();
auto allow = [sandbox](WebSandboxFlags flag) {
return (sandbox & flag) == WebSandboxFlags::kNone;
};
if (allow(WebSandboxFlags::kAll))
return;
// "allow-scripts" + "allow-same-origin" allows escaping the sandbox, by
// accessing the parent via `eval` or `document.open`.
//
// Similarly to Firefox, warn only when this is a simply nested same-origin
// iframe
if (allow(WebSandboxFlags::kOrigin) && allow(WebSandboxFlags::kScripts) &&
window->parent() && window->parent()->GetFrame()->IsMainFrame() &&
!frame->IsCrossOriginToNearestMainFrame()) {
window->AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>(
mojom::blink::ConsoleMessageSource::kSecurity,
mojom::blink::ConsoleMessageLevel::kWarning,
"An iframe which has both allow-scripts and allow-same-origin for its "
"sandbox attribute can escape its sandboxing."));
window->CountUse(WebFeature::kSandboxIneffectiveAllowOriginAllowScript);
}
// Note: It would be interesting to add additional warning. For instance,
// Firefox warn that "allow-top-navigation-by-user-activation" is useless if
// "allow-top-navigation" is set.
}
bool ShouldEmitNewNavigationHistogram(WebNavigationType navigation_type) {
switch (navigation_type) {
case kWebNavigationTypeBackForward:
case kWebNavigationTypeReload:
case kWebNavigationTypeRestore:
case kWebNavigationTypeFormResubmittedBackForward:
case kWebNavigationTypeFormResubmittedReload:
return false;
case kWebNavigationTypeLinkClicked:
case kWebNavigationTypeFormSubmitted:
case kWebNavigationTypeOther:
return true;
}
}
} // namespace
// Base class for body data received by the loader. This allows abstracting away
// whether encoded or decoded data was received by the loader.
class DocumentLoader::BodyData {
public:
virtual ~BodyData() = default;
virtual void AppendToParser(DocumentLoader* loader) = 0;
virtual void Buffer(DocumentLoader* loader) = 0;
virtual base::span<const char> EncodedData() const = 0;
};
// Wraps encoded data received by the loader.
class DocumentLoader::EncodedBodyData : public BodyData {
public:
explicit EncodedBodyData(base::span<const char> data) : data_(data) {
DCHECK(data.data());
DCHECK(data.size());
}
void AppendToParser(DocumentLoader* loader) override {
loader->parser_->AppendBytes(data_.data(), data_.size());
}
void Buffer(DocumentLoader* loader) override {
loader->data_buffer_->Append(data_.data(), data_.size());
}
base::span<const char> EncodedData() const override { return data_; }
private:
base::span<const char> data_;
};
// Wraps decoded data received by the loader.
class DocumentLoader::DecodedBodyData : public BodyData {
public:
DecodedBodyData(const String& data,
const DocumentEncodingData& encoding_data,
base::span<const char> encoded_data)
: data_(data),
encoding_data_(encoding_data),
encoded_data_(encoded_data) {}
void AppendToParser(DocumentLoader* loader) override {
loader->parser_->AppendDecodedData(data_, encoding_data_);
}
void Buffer(DocumentLoader* loader) override {
loader->decoded_data_buffer_.push_back(*this);
}
base::span<const char> EncodedData() const override { return encoded_data_; }
private:
String data_;
DocumentEncodingData encoding_data_;
base::span<const char> encoded_data_;
};
DocumentLoader::DocumentLoader(
LocalFrame* frame,
WebNavigationType navigation_type,
std::unique_ptr<WebNavigationParams> navigation_params,
std::unique_ptr<PolicyContainer> policy_container,
std::unique_ptr<ExtraData> extra_data)
: params_(std::move(navigation_params)),
policy_container_(std::move(policy_container)),
initial_permissions_policy_(params_->permissions_policy_override),
token_(params_->document_token),
url_(params_->url),
original_url_(params_->url),
http_method_(static_cast<String>(params_->http_method)),
referrer_(static_cast<String>(params_->referrer)),
http_body_(params_->http_body),
http_content_type_(static_cast<String>(params_->http_content_type)),
requestor_origin_(params_->requestor_origin),
unreachable_url_(params_->unreachable_url),
pre_redirect_url_for_failed_navigations_(
params_->pre_redirect_url_for_failed_navigations),
grant_load_local_resources_(params_->grant_load_local_resources),
force_fetch_cache_mode_(params_->force_fetch_cache_mode),
frame_policy_(params_->frame_policy.value_or(FramePolicy())),
frame_(frame),
// For back/forward navigations, the browser passed a history item to use
// at commit time in |params_|. Set it as the current history item of this
// DocumentLoader. For other navigations, |history_item_| will be created
// when the FrameLoader calls SetHistoryItemStateForCommit.
history_item_(params_->history_item),
original_referrer_(referrer_),
response_(params_->response.ToResourceResponse()),
response_wrapper_(response_),
load_type_(params_->frame_load_type),
is_client_redirect_(params_->is_client_redirect),
replaces_current_history_item_(load_type_ ==
WebFrameLoadType::kReplaceCurrentItem),
data_received_(false),
is_error_page_for_failed_navigation_(
SchemeRegistry::ShouldTreatURLSchemeAsError(
response_.ResponseUrl().Protocol())),
content_security_notifier_(nullptr),
origin_to_commit_(params_->origin_to_commit.IsNull()
? nullptr
: params_->origin_to_commit.Get()->IsolatedCopy()),
storage_key_(std::move(params_->storage_key)),
session_storage_key_(std::move(params_->session_storage_key)),
navigation_type_(navigation_type),
document_load_timing_(*this),
service_worker_network_provider_(
std::move(params_->service_worker_network_provider)),
was_blocked_by_document_policy_(false),
state_(kNotStarted),
in_commit_data_(false),
data_buffer_(SharedBuffer::Create()),
devtools_navigation_token_(params_->devtools_navigation_token),
base_auction_nonce_(params_->base_auction_nonce),
last_navigation_had_transient_user_activation_(
params_->had_transient_user_activation),
had_sticky_activation_(params_->is_user_activated),
is_browser_initiated_(params_->is_browser_initiated),
was_discarded_(params_->was_discarded),
loading_srcdoc_(url_.IsAboutSrcdocURL()),
fallback_base_url_(params_->fallback_base_url),
loading_url_as_empty_document_(!params_->is_static_data &&
WillLoadUrlAsEmpty(url_)),
is_static_data_(params_->is_static_data),
ukm_source_id_(params_->document_ukm_source_id),
clock_(params_->tick_clock ? params_->tick_clock
: base::DefaultTickClock::GetInstance()),
initiator_origin_trial_features_(
CopyInitiatorOriginTrials(params_->initiator_origin_trial_features)),
force_enabled_origin_trials_(
CopyForceEnabledOriginTrials(params_->force_enabled_origin_trials)),
origin_agent_cluster_(params_->origin_agent_cluster),
origin_agent_cluster_left_as_default_(
params_->origin_agent_cluster_left_as_default),
is_cross_site_cross_browsing_context_group_(
params_->is_cross_site_cross_browsing_context_group),
should_have_sticky_user_activation_(
params_->should_have_sticky_user_activation),
navigation_api_back_entries_(params_->navigation_api_back_entries),
navigation_api_forward_entries_(params_->navigation_api_forward_entries),
navigation_api_previous_entry_(params_->navigation_api_previous_entry),
extra_data_(std::move(extra_data)),
reduced_accept_language_(params_->reduced_accept_language),
navigation_delivery_type_(params_->navigation_delivery_type),
view_transition_state_(std::move(params_->view_transition_state)),
load_with_storage_access_(params_->load_with_storage_access),
browsing_context_group_info_(params_->browsing_context_group_info),
modified_runtime_features_(std::move(params_->modified_runtime_features)),
cookie_deprecation_label_(params_->cookie_deprecation_label),
content_settings_(std::move(params_->content_settings)) {
TRACE_EVENT_WITH_FLOW0("loading", "DocumentLoader::DocumentLoader",
TRACE_ID_LOCAL(this), TRACE_EVENT_FLAG_FLOW_OUT);
DCHECK(frame_);
DCHECK(params_);
// See `archive_` attribute documentation.
if (!frame_->IsMainFrame()) {
if (auto* parent = DynamicTo<LocalFrame>(frame_->Tree().Parent()))
archive_ = parent->Loader().GetDocumentLoader()->archive_;
}
// Determine if this document should have a text fragment permission token.
// We can either generate a new one from this navigation, if it's user
// activated, or receive one propagated from the prior navigation that didn't
// consume its token.
has_text_fragment_token_ = TextFragmentAnchor::GenerateNewToken(*this) ||
params_->has_text_fragment_token;
document_policy_ = CreateDocumentPolicy();
WebNavigationTimings& timings = params_->navigation_timings;
parent_resource_timing_access_ = timings.parent_resource_timing_access;
if (!timings.input_start.is_null())
document_load_timing_.SetInputStart(timings.input_start);
if (timings.navigation_start.is_null()) {
// If we don't have any navigation timings yet, it starts now.
document_load_timing_.SetNavigationStart(clock_->NowTicks());
} else {
document_load_timing_.SetNavigationStart(timings.navigation_start);
if (!timings.redirect_start.is_null()) {
document_load_timing_.SetRedirectStart(timings.redirect_start);
document_load_timing_.SetRedirectEnd(timings.redirect_end);
}
if (!timings.fetch_start.is_null()) {
// If we started fetching, we should have started the navigation.
DCHECK(!timings.navigation_start.is_null());
document_load_timing_.SetFetchStart(timings.fetch_start);
}
}
document_load_timing_.SetSystemEntropyAtNavigationStart(
params_->navigation_timings.system_entropy_at_navigation_start);
document_load_timing_.SetCriticalCHRestart(
params_->navigation_timings.critical_ch_restart);
if (was_blocked_by_document_policy_)
ReplaceWithEmptyDocument();
for (const auto& resource : params_->early_hints_preloaded_resources)
early_hints_preloaded_resources_.insert(resource, EarlyHintsPreloadEntry());
CHECK_EQ(IsBackForwardOrRestore(params_->frame_load_type), !!history_item_);
if (params_->ad_auction_components) {
ad_auction_components_.emplace();
for (const WebURL& url : *params_->ad_auction_components) {
ad_auction_components_->emplace_back(KURL(url));
}
}
if (service_worker_network_provider_) {
service_worker_initial_controller_mode_ =
service_worker_network_provider_->GetControllerServiceWorkerMode();
}
if (params_->fenced_frame_properties) {
fenced_frame_properties_ = std::move(params_->fenced_frame_properties);
if (frame_->GetPage()) {
frame_->GetPage()->SetDeprecatedFencedFrameMode(
fenced_frame_properties_->mode());
}
}
frame_->SetAncestorOrSelfHasCSPEE(params_->ancestor_or_self_has_cspee);
frame_->Client()->DidCreateDocumentLoader(this);
}
std::unique_ptr<WebNavigationParams>
DocumentLoader::CreateWebNavigationParamsToCloneDocument() {
// From the browser process point of view, committing the result of evaluating
// a javascript URL or an XSLT document are all a no-op. Since we will use the
// resulting |params| to create a clone of this DocumentLoader, many
// attributes of DocumentLoader should be copied/inherited to the new
// DocumentLoader's WebNavigationParams. The current heuristic is largely
// based on copying fields that are populated in the DocumentLoader
// constructor. Some exclusions:
// |history_item_| is set in SetHistoryItemStateForCommit().
// |response_| will use the newly committed response.
// |load_type_| will use default kStandard value.
// |replaces_current_history_item_| will be false.
// |permissions_policy_| and |document_policy_| are set in CommitNavigation(),
// with the sandbox flags set in CalculateSandboxFlags().
// |is_client_redirect_| is not copied since future same-document navigations
// will reset the state anyways.
// |archive_| and other states might need to be copied, but we need to add
// fields to WebNavigationParams and create WebMHTMLArchive, etc.
// TODO(https://crbug.com/1151954): Copy |archive_| and other attributes.
auto params = std::make_unique<WebNavigationParams>();
LocalDOMWindow* window = frame_->DomWindow();
params->document_token = frame_->GetDocument()->Token();
params->url = window->Url();
params->fallback_base_url = fallback_base_url_;
params->unreachable_url = unreachable_url_;
params->referrer = referrer_;
// All the security properties of the document must be preserved. Note that
// sandbox flags and various policies are copied separately during commit in
// CommitNavigation() and CalculateSandboxFlags().
params->storage_key = window->GetStorageKey();
params->origin_agent_cluster = origin_agent_cluster_;
params->origin_agent_cluster_left_as_default =
origin_agent_cluster_left_as_default_;
params->grant_load_local_resources = grant_load_local_resources_;
// Various attributes that relates to the last "real" navigation that is known
// by the browser must be carried over.
params->http_method = http_method_;
params->http_status_code = GetResponse().HttpStatusCode();
params->http_body = http_body_;
params->pre_redirect_url_for_failed_navigations =
pre_redirect_url_for_failed_navigations_;
params->force_fetch_cache_mode = force_fetch_cache_mode_;
params->service_worker_network_provider =
std::move(service_worker_network_provider_);
params->devtools_navigation_token = devtools_navigation_token_;
params->base_auction_nonce = base_auction_nonce_;
params->is_user_activated = had_sticky_activation_;
params->had_transient_user_activation =
last_navigation_had_transient_user_activation_;
params->is_browser_initiated = is_browser_initiated_;
params->was_discarded = was_discarded_;
params->document_ukm_source_id = ukm_source_id_;
params->is_cross_site_cross_browsing_context_group =
is_cross_site_cross_browsing_context_group_;
// Required for javascript: URL commits to propagate sticky user activation.
params->should_have_sticky_user_activation =
frame_->HasStickyUserActivation() && !frame_->IsMainFrame();
params->has_text_fragment_token = has_text_fragment_token_;
// Origin trials must still work on the cloned document.
params->initiator_origin_trial_features =
CopyInitiatorOriginTrials(initiator_origin_trial_features_);
params->force_enabled_origin_trials =
CopyForceEnabledOriginTrials(force_enabled_origin_trials_);
for (const auto& pair : early_hints_preloaded_resources_)
params->early_hints_preloaded_resources.push_back(pair.key);
if (ad_auction_components_) {
params->ad_auction_components.emplace();
for (const KURL& url : *ad_auction_components_) {
params->ad_auction_components->emplace_back(KURL(url));
}
}
params->reduced_accept_language = reduced_accept_language_;
params->navigation_delivery_type = navigation_delivery_type_;
params->load_with_storage_access = load_with_storage_access_;
params->modified_runtime_features = modified_runtime_features_;
params->cookie_deprecation_label = cookie_deprecation_label_;
params->content_settings = content_settings_->Clone();
return params;
}
FrameLoader& DocumentLoader::GetFrameLoader() const {
DCHECK(frame_);
return frame_->Loader();
}
LocalFrameClient& DocumentLoader::GetLocalFrameClient() const {
DCHECK(frame_);
LocalFrameClient* client = frame_->Client();
// LocalFrame clears its |m_client| only after detaching all DocumentLoaders
// (i.e. calls detachFromFrame() which clears |frame_|) owned by the
// LocalFrame's FrameLoader. So, if |frame_| is non nullptr, |client| is
// also non nullptr.
DCHECK(client);
return *client;
}
DocumentLoader::~DocumentLoader() {
TRACE_EVENT_WITH_FLOW0("loading", "DocumentLoader::~DocumentLoader",
TRACE_ID_LOCAL(this), TRACE_EVENT_FLAG_FLOW_IN);
DCHECK(!frame_);
DCHECK_EQ(state_, kSentDidFinishLoad);
}
void DocumentLoader::Trace(Visitor* visitor) const {
visitor->Trace(archive_);
visitor->Trace(frame_);
visitor->Trace(history_item_);
visitor->Trace(parser_);
visitor->Trace(subresource_filter_);
visitor->Trace(content_security_notifier_);
visitor->Trace(document_load_timing_);
visitor->Trace(prefetched_signed_exchange_manager_);
visitor->Trace(use_counter_);
visitor->Trace(navigation_api_previous_entry_);
}
uint64_t DocumentLoader::MainResourceIdentifier() const {
return main_resource_identifier_;
}
WebString DocumentLoader::OriginalReferrer() const {
return original_referrer_;
}
const KURL& DocumentLoader::Url() const {
return url_;
}
WebString DocumentLoader::HttpMethod() const {
return http_method_;
}
const AtomicString& DocumentLoader::GetReferrer() const {
return referrer_;
}
const SecurityOrigin* DocumentLoader::GetRequestorOrigin() const {
return requestor_origin_.get();
}
void DocumentLoader::SetServiceWorkerNetworkProvider(
std::unique_ptr<WebServiceWorkerNetworkProvider> provider) {
service_worker_network_provider_ = std::move(provider);
}
void DocumentLoader::DispatchLinkHeaderPreloads(
const ViewportDescription* viewport,
PreloadHelper::LoadLinksFromHeaderMode mode) {
DCHECK_GE(state_, kCommitted);
PreloadHelper::LoadLinksFromHeader(
GetResponse().HttpHeaderField(http_names::kLink),
GetResponse().CurrentRequestUrl(), *frame_, frame_->GetDocument(), mode,
viewport, nullptr /* alternate_resource_info */,
nullptr /* recursive_prefetch_token */);
}
void DocumentLoader::DispatchLcppFontPreloads(
const ViewportDescription* viewport,
PreloadHelper::LoadLinksFromHeaderMode mode) {
DCHECK_GE(state_, kCommitted);
StringBuilder fonts_link;
LCPCriticalPathPredictor* lcpp = frame_->GetLCPP();
if (!lcpp) {
return;
}
// Generate link header for fonts.
for (const auto& font : lcpp->fetched_fonts()) {
if (!fonts_link.empty()) {
fonts_link.Append(",");
}
fonts_link.Append("<");
fonts_link.Append(font.GetString());
fonts_link.Append(">; rel=\"preload\"; as=\"font\"");
}
PreloadHelper::LoadLinksFromHeader(fonts_link.ToString(),
GetResponse().CurrentRequestUrl(), *frame_,
frame_->GetDocument(), mode, viewport,
nullptr /* alternate_resource_info */,
nullptr /* recursive_prefetch_token */);
base::UmaHistogramCounts1000("Blink.LCPP.PreloadedFontCount",
lcpp->fetched_fonts().size());
}
void DocumentLoader::DidChangePerformanceTiming() {
if (frame_ && state_ >= kCommitted) {
GetLocalFrameClient().DidChangePerformanceTiming();
}
}
void DocumentLoader::DidObserveLoadingBehavior(LoadingBehaviorFlag behavior) {
if (frame_) {
DCHECK_GE(state_, kCommitted);
GetLocalFrameClient().DidObserveLoadingBehavior(behavior);
}
}
void DocumentLoader::DidObserveJavaScriptFrameworks(
const JavaScriptFrameworkDetectionResult& result) {
if (frame_) {
DCHECK_GE(state_, kCommitted);
GetLocalFrameClient().DidObserveJavaScriptFrameworks(result);
InjectAutoSpeculationRules(result);
}
}
void DocumentLoader::InjectAutoSpeculationRules(
const JavaScriptFrameworkDetectionResult& result) {
if (!base::FeatureList::IsEnabled(features::kAutoSpeculationRules)) {
return;
}
const auto& config = AutoSpeculationRulesConfig::GetInstance();
for (const auto& detected_version : result.detected_versions) {
if (String speculation_rules =
config.ForFramework(detected_version.first)) {
auto* source = SpeculationRuleSet::Source::FromBrowserInjected(
speculation_rules, this->Url());
auto* rule_set = SpeculationRuleSet::Parse(source, frame_->DomWindow());
CHECK(rule_set);
// The JSON string in speculation_rules comes from a potentially-fallible
// remote config, so this should not be a CHECK failure.
if (rule_set->HasError()) {
LOG(ERROR) << "Failed to parse speculation rules for "
<< detected_version.first << ": " << speculation_rules;
continue;
}
DocumentSpeculationRules::From(*frame_->GetDocument())
.AddRuleSet(rule_set);
}
}
}
// static
WebHistoryCommitType LoadTypeToCommitType(WebFrameLoadType type) {
switch (type) {
case WebFrameLoadType::kStandard:
return kWebStandardCommit;
case WebFrameLoadType::kBackForward:
case WebFrameLoadType::kRestore:
return kWebBackForwardCommit;
case WebFrameLoadType::kReload:
case WebFrameLoadType::kReplaceCurrentItem:
case WebFrameLoadType::kReloadBypassingCache:
return kWebHistoryInertCommit;
}
NOTREACHED();
return kWebHistoryInertCommit;
}
void DocumentLoader::RunURLAndHistoryUpdateSteps(
const KURL& new_url,
HistoryItem* history_item,
mojom::blink::SameDocumentNavigationType same_document_navigation_type,
scoped_refptr<SerializedScriptValue> data,
WebFrameLoadType type,
bool is_browser_initiated,
bool is_synchronously_committed) {
// We use the security origin of this frame since callers of this method must
// already have performed same origin checks.
// is_browser_initiated is false and is_synchronously_committed is true
// because anything invoking this algorithm is a renderer-initiated navigation
// in this process.
UpdateForSameDocumentNavigation(
new_url, history_item, same_document_navigation_type, std::move(data),
type, frame_->DomWindow()->GetSecurityOrigin(), is_browser_initiated,
is_synchronously_committed, std::nullopt);
}
void DocumentLoader::UpdateForSameDocumentNavigation(
const KURL& new_url,
HistoryItem* history_item,
mojom::blink::SameDocumentNavigationType same_document_navigation_type,
scoped_refptr<SerializedScriptValue> data,
WebFrameLoadType type,
const SecurityOrigin* initiator_origin,
bool is_browser_initiated,
bool is_synchronously_committed,
std::optional<scheduler::TaskAttributionId>
soft_navigation_heuristics_task_id) {
CHECK_EQ(IsBackForwardOrRestore(type), !!history_item);
TRACE_EVENT1("blink", "FrameLoader::updateForSameDocumentNavigation", "url",
new_url.GetString().Ascii());
bool same_item_sequence_number =
history_item_ && history_item &&
history_item_->ItemSequenceNumber() == history_item->ItemSequenceNumber();
if (history_item)
history_item_ = history_item;
// Spec "URL and history update steps", step 4 [1]:
// " If document's is initial about:blank is true, then set historyHandling to
// 'replace'."
// [1]: https://html.spec.whatwg.org/C/#url-and-history-update-steps
if (type == WebFrameLoadType::kStandard &&
GetFrameLoader().IsOnInitialEmptyDocument()) {
type = WebFrameLoadType::kReplaceCurrentItem;
}
// Generate start and stop notifications only when loader is completed so that
// we don't fire them for fragment redirection that happens in window.onload
// handler. See https://bugs.webkit.org/show_bug.cgi?id=31838
// Do not fire the notifications if the frame is concurrently navigating away
// from the document, since a new document is already loading.
bool was_loading = frame_->IsLoading();
if (!was_loading) {
GetFrameLoader().Progress().ProgressStarted();
}
// Update the data source's request with the new URL to fake the URL change
frame_->GetDocument()->SetURL(new_url);
KURL old_url = url_;
url_ = new_url;
replaces_current_history_item_ = type != WebFrameLoadType::kStandard;
bool is_history_api_or_app_history_navigation =
(same_document_navigation_type !=
mojom::blink::SameDocumentNavigationType::kFragment);
if (is_history_api_or_app_history_navigation) {
// See spec:
// https://html.spec.whatwg.org/multipage/history.html#url-and-history-update-steps
http_method_ = http_names::kGET;
http_body_ = nullptr;
}
last_navigation_had_trusted_initiator_ =
initiator_origin ? initiator_origin->IsSameOriginWith(
frame_->DomWindow()->GetSecurityOrigin()) &&
Url().ProtocolIsInHTTPFamily()
: true;
// We want to allow same-document text fragment navigations if they're coming
// from the browser or same-origin. Do this only on a standard navigation so
// that we don't unintentionally clear the token when we reach here from the
// history API.
if (type == WebFrameLoadType::kStandard ||
same_document_navigation_type ==
mojom::blink::SameDocumentNavigationType::kFragment) {
has_text_fragment_token_ =
TextFragmentAnchor::GenerateNewTokenForSameDocument(
*this, type, same_document_navigation_type);
}
SetHistoryItemStateForCommit(history_item_.Get(), type,
is_history_api_or_app_history_navigation
? HistoryNavigationType::kHistoryApi
: HistoryNavigationType::kFragment,
CommitReason::kRegular);
history_item_->SetDocumentState(frame_->GetDocument()->GetDocumentState());
if (is_history_api_or_app_history_navigation)
history_item_->SetStateObject(std::move(data));
WebHistoryCommitType commit_type = LoadTypeToCommitType(type);
frame_->GetFrameScheduler()->DidCommitProvisionalLoad(
commit_type == kWebHistoryInertCommit,
FrameScheduler::NavigationType::kSameDocument);
GetLocalFrameClient().DidFinishSameDocumentNavigation(
commit_type, is_synchronously_committed, same_document_navigation_type,
is_client_redirect_, is_browser_initiated);
probe::DidNavigateWithinDocument(frame_);
// If intercept() was called during this same-document navigation's
// NavigateEvent, the navigation will finish asynchronously, so
// don't immediately call DidStopLoading() in that case.
bool should_send_stop_notification =
!was_loading &&
same_document_navigation_type !=
mojom::blink::SameDocumentNavigationType::kNavigationApiIntercept;
if (should_send_stop_notification)
GetFrameLoader().Progress().ProgressCompleted();
if (!same_item_sequence_number) {
// If the item sequence number didn't change, there's no need to update any
// Navigation API state or fire associated events. It's possible to get a
// same-document navigation to a same ISN when a history navigation targets
// a frame that no longer exists (https://crbug.com/705550).
frame_->DomWindow()->navigation()->UpdateForNavigation(*history_item_,
type);
}
if (!frame_)
return;
std::optional<SoftNavigationHeuristics::EventScope>
soft_navigation_event_scope;
SoftNavigationHeuristics* heuristics = nullptr;
if (frame_->IsMainFrame() &&
base::FeatureList::IsEnabled(features::kSoftNavigationDetection)) {
if (auto* script_state = ToScriptStateForMainWorld(frame_->DomWindow())) {
CHECK(frame_->DomWindow());
heuristics = SoftNavigationHeuristics::From(*frame_->DomWindow());
if (is_browser_initiated && heuristics) {
// For browser-initiated navigations, we never started the soft
// navigation (as this is the first we hear of it in the renderer). We
// need to do that now.
soft_navigation_event_scope = heuristics->CreateEventScope(
SoftNavigationHeuristics::EventScope::Type::kNavigate,
/*is_new_interaction=*/true, script_state);
heuristics->SameDocumentNavigationStarted();
}
}
}
scheduler::TaskAttributionInfo* parent_task = nullptr;
if (heuristics && soft_navigation_heuristics_task_id) {
// If `heuristics` exists, it means we're in an outermost main frame; if
// `soft_navigation_heuristics_task_id` exists, it means the task state
// being propagated was captured in a main world history API call.
if (auto* tracker = scheduler::TaskAttributionTracker::From(
frame_->DomWindow()->GetIsolate())) {
// Get the TaskId from tracker. We're passing that to dispatchEvent
// further down, but regardless, we want to get it and previous tasks out
// of the tracker's task queue, to enable them to get garbage collected if
// needed, even if popstate is never called.
parent_task = tracker->CommitSameDocumentNavigation(
soft_navigation_heuristics_task_id.value());
}
}
// Anything except a history.pushState/replaceState is considered a new
// navigation that resets whether the user has scrolled and fires popstate.
if (same_document_navigation_type !=
mojom::blink::SameDocumentNavigationType::kHistoryApi) {
initial_scroll_state_.was_scrolled_by_user = false;
// If the item sequence number didn't change, there's no need to trigger
// popstate. It's possible to get a same-document navigation
// to a same ISN when a history navigation targets a frame that no longer
// exists (https://crbug.com/705550).
if (!same_item_sequence_number) {
scoped_refptr<SerializedScriptValue> state_object =
history_item ? history_item->StateObject()
: SerializedScriptValue::NullValue();
frame_->DomWindow()->DispatchPopstateEvent(std::move(state_object),
parent_task);
}
}
if (heuristics && new_url != old_url) {
// if `heuristics` exists it means we're in an outermost main frame.
//
// TODO(crbug.com/1521100): `heuristics` existing does not imply this
// navigation was initiated in the main world.
heuristics->SameDocumentNavigationCommitted(new_url);
}
}
const KURL& DocumentLoader::UrlForHistory() const {
return UnreachableURL().IsEmpty() ? Url() : UnreachableURL();
}
void DocumentLoader::DidOpenDocumentInputStream(const KURL& url) {
url_ = url;
// Let the browser know that we have done a document.open().
GetLocalFrameClient().DispatchDidOpenDocumentInputStream(url_);
}
void DocumentLoader::SetHistoryItemStateForCommit(
HistoryItem* old_item,
WebFrameLoadType load_type,
HistoryNavigationType navigation_type,
CommitReason commit_reason) {
if (!history_item_ || !IsBackForwardOrRestore(load_type)) {
history_item_ = MakeGarbageCollected<HistoryItem>();
}
history_item_->SetURL(UrlForHistory());
history_item_->SetReferrer(referrer_.GetString());
if (EqualIgnoringASCIICase(http_method_, "POST")) {
// FIXME: Eventually we have to make this smart enough to handle the case
// where we have a stream for the body to handle the "data interspersed with
// files" feature.
history_item_->SetFormData(http_body_);
history_item_->SetFormContentType(http_content_type_);
} else {
history_item_->SetFormData(nullptr);
history_item_->SetFormContentType(g_null_atom);
}
// Don't propagate state from the old item to the new item if there isn't an
// old item (obviously), or if this is a back/forward navigation, since we
// explicitly want to restore the state we just committed.
if (!old_item || IsBackForwardOrRestore(load_type)) {
return;
}
// The navigation API key corresponds to a "slot" in the back/forward list,
// and should be shared for all replacing navigations so long as the
// navigation isn't cross-origin.
WebHistoryCommitType history_commit_type = LoadTypeToCommitType(load_type);
if (history_commit_type == kWebHistoryInertCommit &&
SecurityOrigin::Create(old_item->Url())
->CanAccess(SecurityOrigin::Create(history_item_->Url()).get())) {
history_item_->SetNavigationApiKey(old_item->GetNavigationApiKey());
}
// The navigation API id corresponds to a "session history entry", and so
// should be carried over across reloads.
if (IsReloadLoadType(load_type))
history_item_->SetNavigationApiId(old_item->GetNavigationApiId());
// The navigation API's state is stickier than the legacy History state. It
// always propagates by default to a same-document navigation.
if (navigation_type == HistoryNavigationType::kFragment ||
IsReloadLoadType(load_type)) {
history_item_->SetNavigationApiState(old_item->GetNavigationApiState());
}
// Don't propagate state from the old item if this is a different-document
// navigation, unless the before and after pages are logically related. This
// means they have the same url (ignoring fragment) and the new item was
// loaded via reload or client redirect.
if (navigation_type == HistoryNavigationType::kDifferentDocument &&
(history_commit_type != kWebHistoryInertCommit ||
!EqualIgnoringFragmentIdentifier(old_item->Url(), history_item_->Url())))
return;
history_item_->SetDocumentSequenceNumber(old_item->DocumentSequenceNumber());
history_item_->CopyViewStateFrom(old_item);
history_item_->SetScrollRestorationType(old_item->ScrollRestorationType());
// The item sequence number determines whether items are "the same", such
// back/forward navigation between items with the same item sequence number is
// a no-op. Only treat this as identical if the navigation did not create a
// back/forward entry and the url is identical or it was loaded via
// history.replaceState().
if (history_commit_type == kWebHistoryInertCommit &&
(navigation_type == HistoryNavigationType::kHistoryApi ||
old_item->Url() == history_item_->Url())) {
history_item_->SetStateObject(old_item->StateObject());
history_item_->SetItemSequenceNumber(old_item->ItemSequenceNumber());
}
}
void DocumentLoader::BodyDataReceived(base::span<const char> data) {
EncodedBodyData body_data(data);
BodyDataReceivedImpl(body_data);
}
void DocumentLoader::DecodedBodyDataReceived(
const WebString& data,
const WebEncodingData& encoding_data,
base::span<const char> encoded_data) {
// Decoding has already happened, we don't need the decoder anymore.
parser_->SetDecoder(nullptr);
if (response_.WasFetchedViaServiceWorker()) {
total_body_size_from_service_worker_ += data.length();
}
DecodedBodyData body_data(data, DocumentEncodingData(encoding_data),
encoded_data);
BodyDataReceivedImpl(body_data);
}
DocumentLoader::ProcessBackgroundDataCallback
DocumentLoader::TakeProcessBackgroundDataCallback() {
auto callback = parser_->TakeBackgroundScanCallback();
if (!callback)
return ProcessBackgroundDataCallback();
return CrossThreadBindRepeating(
[](const DocumentParser::BackgroundScanCallback& callback,
const WebString& data) { callback.Run(data); },
std::move(callback));
}
void DocumentLoader::BodyDataReceivedImpl(BodyData& data) {
TRACE_EVENT_WITH_FLOW0("loading", "DocumentLoader::BodyDataReceivedImpl",
TRACE_ID_LOCAL(this),
TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT);
base::span<const char> encoded_data = data.EncodedData();
if (encoded_data.size()) {
GetFrameLoader().Progress().IncrementProgress(main_resource_identifier_,
encoded_data.size());
probe::DidReceiveData(probe::ToCoreProbeSink(GetFrame()),
main_resource_identifier_, this, encoded_data.data(),
encoded_data.size());
}
TRACE_EVENT_WITH_FLOW1("loading", "DocumentLoader::HandleData",
TRACE_ID_LOCAL(this),
TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT,
"length", encoded_data.size());
DCHECK(!frame_->GetPage()->Paused());
time_of_last_data_received_ = clock_->NowTicks();
if (loading_main_document_from_mhtml_archive_) {
// 1) Ftp directory listings accumulate data buffer and transform it later
// to the actual document content.
// 2) Mhtml archives accumulate data buffer and parse it as mhtml later
// to retrieve the actual document content.
data.Buffer(this);
return;
}
ProcessDataBuffer(&data);
}
void DocumentLoader::BodyLoadingFinished(
base::TimeTicks completion_time,
int64_t total_encoded_data_length,
int64_t total_encoded_body_length,
int64_t total_decoded_body_length,
const std::optional<WebURLError>& error) {
TRACE_EVENT_WITH_FLOW0("loading", "DocumentLoader::BodyLoadingFinished",
TRACE_ID_LOCAL(this),
TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT);
DCHECK(frame_);
if (!error) {
GetFrameLoader().Progress().CompleteProgress(main_resource_identifier_);
probe::DidFinishLoading(
probe::ToCoreProbeSink(GetFrame()), main_resource_identifier_, this,
completion_time, total_encoded_data_length, total_decoded_body_length);
if (response_.WasFetchedViaServiceWorker()) {
// See https://w3c.github.io/ServiceWorker/#dom-fetchevent-respondwith
// in "chunk steps": there is no difference between encoded/decoded body
// size, as encoding is handled inside the service worker.
total_encoded_body_length = total_body_size_from_service_worker_;
total_decoded_body_length = total_body_size_from_service_worker_;
}
DOMWindowPerformance::performance(*frame_->DomWindow())
->OnBodyLoadFinished(total_encoded_body_length,
total_decoded_body_length);
if (resource_timing_info_for_parent_) {
// Note that we already checked for Timing-Allow-Origin, otherwise we
// wouldn't have a resource_timing_info_for_parent_ in the first place
// and we would resort to fallback timing.
if (!RuntimeEnabledFeatures::ResourceTimingUseCORSForBodySizesEnabled() ||
(IsSameOriginInitiator() &&
!document_load_timing_.HasCrossOriginRedirect())) {
resource_timing_info_for_parent_->encoded_body_size =
total_encoded_body_length;
resource_timing_info_for_parent_->decoded_body_size =
total_decoded_body_length;
}
// Note that we currently lose timing info for empty documents,
// which will be fixed with synchronous commit.
// Main resource timing information is reported through the owner
// to be passed to the parent frame, if appropriate.
resource_timing_info_for_parent_->response_end = completion_time;
frame_->Owner()->AddResourceTiming(
std::move(resource_timing_info_for_parent_));
}
FinishedLoading(completion_time);
return;
}
ResourceError resource_error(*error);
if (network_utils::IsCertificateTransparencyRequiredError(
resource_error.ErrorCode())) {
CountUse(WebFeature::kCertificateTransparencyRequiredErrorOnResourceLoad);
}
GetFrameLoader().Progress().CompleteProgress(main_resource_identifier_);
probe::DidFailLoading(probe::ToCoreProbeSink(GetFrame()),
main_resource_identifier_, this, resource_error,
frame_->GetDevToolsFrameToken());
GetFrame()->Console().DidFailLoading(this, main_resource_identifier_,
resource_error);
LoadFailed(resource_error);
}
void DocumentLoader::LoadFailed(const ResourceError& error) {
TRACE_EVENT1("navigation,rail", "DocumentLoader::LoadFailed", "error",
error.ErrorCode());
body_loader_.reset();
virtual_time_pauser_.UnpauseVirtualTime();
// `LoadFailed()` should never be called for a navigation failure in a frame
// owned by <object>. Browser-side navigation must handle these (whether
// network errors, blocked by CSP/XFO, or otherwise) and never delegate to the
// renderer.
//
// `LoadFailed()` *can* be called for a frame owned by <object> if the
// navigation body load is cancelled, e.g.:
// - `StartLoadingResponse()` calls `StopLoading()` when loading a
// `MediaDocument`.
// - `LocalFrame::Detach()` calls `StopLoading()`.
// - `window.stop()` calls `StopAllLoaders()` which calls `StopLoading()`.
DCHECK(!IsA<HTMLObjectElement>(frame_->Owner()) || error.IsCancellation());
WebHistoryCommitType history_commit_type = LoadTypeToCommitType(load_type_);
DCHECK_EQ(kCommitted, state_);
if (frame_->GetDocument()->Parser())
frame_->GetDocument()->Parser()->StopParsing();
state_ = kSentDidFinishLoad;
GetLocalFrameClient().DispatchDidFailLoad(error, history_commit_type);
GetFrameLoader().DidFinishNavigation(
FrameLoader::NavigationFinishState::kFailure);
DCHECK_EQ(kSentDidFinishLoad, state_);
params_ = nullptr;
}
void DocumentLoader::FinishedLoading(base::TimeTicks finish_time) {
TRACE_EVENT_WITH_FLOW0("loading", "DocumentLoader::FinishedLoading",
TRACE_ID_LOCAL(this),
TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT);
body_loader_.reset();
virtual_time_pauser_.UnpauseVirtualTime();
DCHECK(commit_reason_ == CommitReason::kInitialization ||
!frame_->GetPage()->Paused() ||
MainThreadDebugger::Instance(frame_->DomWindow()->GetIsolate())
->IsPaused());
if (loading_main_document_from_mhtml_archive_ && state_ < kCommitted) {
// The browser process should block any navigation to an MHTML archive
// inside iframes. See NavigationRequest::OnResponseStarted().
CHECK(frame_->IsMainFrame());
archive_ = MHTMLArchive::Create(url_, std::move(data_buffer_));
}
// We should not call FinishedLoading before committing navigation,
// except for the mhtml case. When loading an MHTML archive, the whole archive
// has to be validated before committing the navigation. The validation
// process loads the entire body of the archive, which will move the state to
// FinishedLoading.
if (!loading_main_document_from_mhtml_archive_)
DCHECK_GE(state_, kCommitted);
base::TimeTicks response_end_time = finish_time;
if (response_end_time.is_null())
response_end_time = time_of_last_data_received_;
if (response_end_time.is_null())
response_end_time = clock_->NowTicks();
GetTiming().SetResponseEnd(response_end_time);
if (!frame_)
return;
if (parser_) {
if (parser_blocked_count_) {
finish_loading_when_parser_resumed_ = true;
} else {
parser_->Finish();
parser_.Clear();
}
}
}
void DocumentLoader::HandleRedirect(
WebNavigationParams::RedirectInfo& redirect) {
ResourceResponse redirect_response =
redirect.redirect_response.ToResourceResponse();
const KURL& url_before_redirect = redirect_response.CurrentRequestUrl();
url_ = redirect.new_url;
const KURL& url_after_redirect = url_;
// Update the HTTP method of this document to the method used by the redirect.
AtomicString new_http_method = redirect.new_http_method;
if (http_method_ != new_http_method) {
http_body_ = nullptr;
http_content_type_ = g_null_atom;
http_method_ = new_http_method;
}
referrer_ = redirect.new_referrer;
probe::WillSendNavigationRequest(
probe::ToCoreProbeSink(GetFrame()), main_resource_identifier_, this,
url_after_redirect, http_method_, http_body_.get());
DCHECK(!GetTiming().FetchStart().is_null());
GetTiming().AddRedirect(url_before_redirect, url_after_redirect);
}
void DocumentLoader::ConsoleError(const String& message) {
auto* console_message = MakeGarbageCollected<ConsoleMessage>(
mojom::ConsoleMessageSource::kSecurity,
mojom::ConsoleMessageLevel::kError, message,
response_.CurrentRequestUrl(), this, MainResourceIdentifier());
frame_->DomWindow()->AddConsoleMessage(console_message);
}
void DocumentLoader::ReplaceWithEmptyDocument() {
DCHECK(params_);
KURL blocked_url = SecurityOrigin::UrlWithUniqueOpaqueOrigin();
url_ = blocked_url;
params_->url = blocked_url;
WebNavigationParams::FillStaticResponse(params_.get(), "text/html", "UTF-8",
"");
}
DocumentPolicy::ParsedDocumentPolicy DocumentLoader::CreateDocumentPolicy() {
// For URLs referring to local content to parent frame, they have no way to
// specify the document policy they use. If the parent frame requires a
// document policy on them, use the required policy as effective policy.
if (url_.IsEmpty() || url_.ProtocolIsAbout() || url_.ProtocolIsData() ||
url_.ProtocolIs("blob") || url_.ProtocolIs("filesystem"))
return {frame_policy_.required_document_policy, {} /* endpoint_map */};
PolicyParserMessageBuffer header_logger("Document-Policy HTTP header: ");
PolicyParserMessageBuffer require_header_logger(
"Require-Document-Policy HTTP header: ");
// Filtering out features that are disabled by origin trial is done
// in SecurityContextInit when origin trial context is available.
auto parsed_policy =
DocumentPolicyParser::Parse(
response_.HttpHeaderField(http_names::kDocumentPolicy), header_logger)
.value_or(DocumentPolicy::ParsedDocumentPolicy{});
// |parsed_policy| can have policies that are disabled by origin trial,
// but |frame_policy_.required_document_policy| cannot.
// It is safe to call |IsPolicyCompatible| as long as required policy is
// checked against origin trial.
if (!DocumentPolicy::IsPolicyCompatible(
frame_policy_.required_document_policy,
parsed_policy.feature_state)) {
was_blocked_by_document_policy_ = true;
// When header policy is less strict than required policy, use required
// policy to initialize document policy for the document.
parsed_policy = {frame_policy_.required_document_policy,
{} /* endpoint_map */};
}
// Initialize required document policy for subtree.
//
// If the document is blocked by document policy, there won't be content
// in the sub-frametree, thus no need to initialize required_policy for
// subtree.
if (!was_blocked_by_document_policy_) {
// Require-Document-Policy header only affects subtree of current document,
// but not the current document.
const DocumentPolicyFeatureState header_required_policy =
DocumentPolicyParser::Parse(
response_.HttpHeaderField(http_names::kRequireDocumentPolicy),
require_header_logger)
.value_or(DocumentPolicy::ParsedDocumentPolicy{})
.feature_state;
frame_->SetRequiredDocumentPolicy(DocumentPolicy::MergeFeatureState(
header_required_policy, frame_policy_.required_document_policy));
}
document_policy_parsing_messages_.AppendVector(header_logger.GetMessages());
document_policy_parsing_messages_.AppendVector(
require_header_logger.GetMessages());
return parsed_policy;
}
void DocumentLoader::HandleResponse() {
DCHECK(frame_);
if (response_.IsHTTP() &&
!network::IsSuccessfulStatus(response_.HttpStatusCode())) {
DCHECK(!IsA<HTMLObjectElement>(frame_->Owner()));
}
}
void DocumentLoader::CommitData(BodyData& data) {
TRACE_EVENT_WITH_FLOW1("loading", "DocumentLoader::CommitData",
TRACE_ID_LOCAL(this),
TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT,
"length", data.EncodedData().size());
// This can happen if document.close() is called by an event handler while
// there's still pending incoming data.
// TODO(dgozman): we should stop body loader when stopping the parser to
// avoid unnecessary work. This may happen, for example, when we abort current
// committed document which is still loading when initiating a new navigation.
if (!frame_ || !frame_->GetDocument()->Parsing() || !parser_)
return;
base::AutoReset<bool> reentrancy_protector(&in_commit_data_, true);
if (data.EncodedData().size())
data_received_ = true;
data.AppendToParser(this);
}
mojom::CommitResult DocumentLoader::CommitSameDocumentNavigation(
const KURL& url,
WebFrameLoadType frame_load_type,
HistoryItem* history_item,
ClientRedirectPolicy client_redirect_policy,
bool has_transient_user_activation,
const SecurityOrigin* initiator_origin,
bool is_synchronously_committed,
Element* source_element,
mojom::blink::TriggeringEventInfo triggering_event_info,
bool is_browser_initiated,
std::optional<scheduler::TaskAttributionId>
soft_navigation_heuristics_task_id) {
DCHECK(!IsReloadLoadType(frame_load_type));
DCHECK(frame_->GetDocument());
DCHECK(!is_browser_initiated || !is_synchronously_committed);
CHECK(frame_->IsNavigationAllowed());
if (Page* page = frame_->GetPage())
page->HistoryNavigationVirtualTimePauser().UnpauseVirtualTime();
if (frame_->GetDocument()->IsFrameSet()) {
// Navigations in a frameset are always cross-document. Renderer-initiated
// navigations in a frameset will be deferred to the browser, and all
// renderer-initiated navigations are treated as cross-document. So this one
// must have been browser-initiated, where it was not aware that the
// document is a frameset. In that case we just restart the navigation,
// making it cross-document. This gives a consistent outcome for all
// navigations in a frameset.
return mojom::blink::CommitResult::RestartCrossDocument;
}
if (!IsBackForwardOrRestore(frame_load_type)) {
// For the browser to send a same-document navigation, it will always have a
// fragment. When no fragment is present, the browser loads a new document.
CHECK(url.HasFragmentIdentifier());
if (!EqualIgnoringFragmentIdentifier(frame_->GetDocument()->Url(), url)) {
// A race condition has occurred! The renderer has changed the current
// document's URL through history.pushState(). This change was performed
// as a synchronous same-document navigation in the renderer process,
// though the URL of that document is changed as a result. The browser
// will hear about this and update its current URL too, but there's a time
// window before it hears about it. During that time, it may try to
// perform a same-document navigation based on the old URL. That would
// arrive here. There are effectively 2 incompatible navigations in flight
// at the moment, and the history.pushState() one was already performed.
// We will reorder the incoming navigation from the browser to be
// performed after the history.pushState() by bouncing it back through the
// browser. The way we do that is by sending RestartCrossDocument, which
// is not strictly what we want. We just want the browser to restart the
// navigation. However, since the document address has changed, the
// restarted navigation will probably be cross-document, and this prevents
// a resulting same-document navigation from getting bounced and restarted
// yet again by a renderer performing another history.pushState(). See
// https://crbug.com/1209772.
return mojom::blink::CommitResult::RestartCrossDocument;
}
}
// If the item sequence number didn't change, there's no need to trigger
// the navigate event. It's possible to get a same-document navigation
// to a same ISN when a history navigation targets a frame that no longer
// exists (https://crbug.com/705550).
bool same_item_sequence_number =
history_item_ && history_item &&
history_item_->ItemSequenceNumber() == history_item->ItemSequenceNumber();
if (!same_item_sequence_number) {
auto* params = MakeGarbageCollected<NavigateEventDispatchParams>(
url, NavigateEventType::kFragment, frame_load_type);
if (is_browser_initiated) {
params->involvement = UserNavigationInvolvement::kBrowserUI;
} else if (triggering_event_info ==
mojom::blink::TriggeringEventInfo::kFromTrustedEvent) {
params->involvement = UserNavigationInvolvement::kActivation;
}
params->source_element = source_element;
params->destination_item = history_item;
params->is_browser_initiated = is_browser_initiated;
params->is_synchronously_committed_same_document =
is_synchronously_committed;
auto dispatch_result =
frame_->DomWindow()->navigation()->DispatchNavigateEvent(params);
if (dispatch_result == NavigationApi::DispatchResult::kAbort) {
return mojom::blink::CommitResult::Aborted;
} else if (dispatch_result == NavigationApi::DispatchResult::kIntercept) {
return mojom::blink::CommitResult::Ok;
}
}
mojom::blink::SameDocumentNavigationType same_document_navigation_type =
mojom::blink::SameDocumentNavigationType::kFragment;
// If the requesting document is cross-origin, perform the navigation
// asynchronously to minimize the navigator's ability to execute timing
// attacks. If |is_synchronously_committed| is false, the navigation is
// already asynchronous since it's coming from the browser so there's no need
// to post it again.
if (is_synchronously_committed && initiator_origin &&
!initiator_origin->CanAccess(frame_->DomWindow()->GetSecurityOrigin())) {
frame_->GetTaskRunner(TaskType::kInternalLoading)
->PostTask(
FROM_HERE,
WTF::BindOnce(
&DocumentLoader::CommitSameDocumentNavigationInternal,
WrapWeakPersistent(this), url, frame_load_type,
WrapPersistent(history_item), same_document_navigation_type,
client_redirect_policy, has_transient_user_activation,
WTF::RetainedRef(initiator_origin), is_browser_initiated,
is_synchronously_committed, triggering_event_info,
soft_navigation_heuristics_task_id));
} else {
CommitSameDocumentNavigationInternal(
url, frame_load_type, history_item, same_document_navigation_type,
client_redirect_policy, has_transient_user_activation, initiator_origin,
is_browser_initiated, is_synchronously_committed, triggering_event_info,
soft_navigation_heuristics_task_id);
}
return mojom::CommitResult::Ok;
}
void DocumentLoader::CommitSameDocumentNavigationInternal(
const KURL& url,
WebFrameLoadType frame_load_type,
HistoryItem* history_item,
mojom::blink::SameDocumentNavigationType same_document_navigation_type,
ClientRedirectPolicy client_redirect,
bool has_transient_user_activation,
const SecurityOrigin* initiator_origin,
bool is_browser_initiated,
bool is_synchronously_committed,
mojom::blink::TriggeringEventInfo triggering_event_info,
std::optional<scheduler::TaskAttributionId>
soft_navigation_heuristics_task_id) {
// If this function was scheduled to run asynchronously, this DocumentLoader
// might have been detached before the task ran.
if (!frame_)
return;
if (!IsBackForwardOrRestore(frame_load_type)) {
SetNavigationType(triggering_event_info !=
mojom::blink::TriggeringEventInfo::kNotFromEvent
? kWebNavigationTypeLinkClicked
: kWebNavigationTypeOther);
}
// If we have a client navigation for a different document, a fragment
// scroll should cancel it.
// Note: see fragment-change-does-not-cancel-pending-navigation, where
// this does not actually happen.
GetFrameLoader().DidFinishNavigation(
FrameLoader::NavigationFinishState::kSuccess);
// GetFrameLoader().DidFinishNavigation can lead to DetachFromFrame so need
// to check again if frame_ is null.
if (!frame_ || !frame_->GetPage())
return;
GetFrameLoader().SaveScrollState();
KURL old_url = frame_->GetDocument()->Url();
bool hash_change = EqualIgnoringFragmentIdentifier(url, old_url) &&
url.FragmentIdentifier() != old_url.FragmentIdentifier();
if (hash_change) {
// If we were in the autoscroll/middleClickAutoscroll mode we want to stop
// it before following the link to the anchor
frame_->GetEventHandler().StopAutoscroll();
frame_->DomWindow()->EnqueueHashchangeEvent(old_url, url);
}
is_client_redirect_ =
client_redirect == ClientRedirectPolicy::kClientRedirect;
last_navigation_had_transient_user_activation_ =
has_transient_user_activation;
// Events fired in UpdateForSameDocumentNavigation() might change view state,
// so stash for later restore.
std::optional<HistoryItem::ViewState> view_state;
mojom::blink::ScrollRestorationType scroll_restoration_type =
mojom::blink::ScrollRestorationType::kAuto;
if (history_item) {
view_state = history_item->GetViewState();
scroll_restoration_type = history_item->ScrollRestorationType();
}
UpdateForSameDocumentNavigation(
url, history_item, same_document_navigation_type, nullptr,
frame_load_type, initiator_origin, is_browser_initiated,
is_synchronously_committed, soft_navigation_heuristics_task_id);
if (!frame_)
return;
if (!frame_->GetDocument()->LoadEventStillNeeded() && frame_->Owner() &&
initiator_origin &&
!initiator_origin->CanAccess(frame_->DomWindow()->GetSecurityOrigin()) &&
frame_->Tree().Parent()->GetSecurityContext()->GetSecurityOrigin()) {
// If this same-document navigation was initiated by a cross-origin iframe
// and is cross-origin to its parent, fire onload on the owner iframe.
// Normally, the owner iframe's onload fires if and only if the window's
// onload fires (i.e., when a navigation to a different document completes).
// However, a cross-origin initiator can use the presence or absence of a
// load event to detect whether the navigation was same- or cross-document,
// and can therefore try to guess the url of a cross-origin iframe. Fire the
// iframe's onload to prevent this technique. https://crbug.com/1248444
frame_->Owner()->DispatchLoad();
}
GetFrameLoader().ProcessScrollForSameDocumentNavigation(
url, frame_load_type, view_state, scroll_restoration_type);
}
void DocumentLoader::ProcessDataBuffer(BodyData* data) {
DCHECK_GE(state_, kCommitted);
if (parser_blocked_count_ || in_commit_data_) {
// 1) If parser is blocked, we buffer data and process it upon resume.
// 2) If this function is reentered, we defer processing of the additional
// data to the top-level invocation. Reentrant calls can occur because
// of web platform (mis-)features that require running a nested run loop:
// - alert(), confirm(), prompt()
// - Detach of plugin elements.
// - Synchronous XMLHTTPRequest
if (data)
data->Buffer(this);
return;
}
if (data)
CommitData(*data);
// Process data received in reentrant invocations. Note that the invocations
// of CommitData() may queue more data in reentrant invocations, so iterate
// until it's empty.
DCHECK(data_buffer_->empty() || decoded_data_buffer_.empty());
for (const auto& span : *data_buffer_) {
EncodedBodyData body_data(span);
CommitData(body_data);
}
for (auto& decoded_data : decoded_data_buffer_)
CommitData(decoded_data);
// All data has been consumed, so flush the buffer.
data_buffer_->Clear();
decoded_data_buffer_.clear();
}
void DocumentLoader::StopLoading() {
if (frame_ && GetFrameLoader().GetDocumentLoader() == this)
frame_->GetDocument()->Fetcher()->StopFetching();
body_loader_.reset();
virtual_time_pauser_.UnpauseVirtualTime();
if (!SentDidFinishLoad())
LoadFailed(ResourceError::CancelledError(Url()));
}
void DocumentLoader::SetDefersLoading(LoaderFreezeMode mode) {
freeze_mode_ = mode;
if (body_loader_)
body_loader_->SetDefersLoading(mode);
}
void DocumentLoader::DetachFromFrame(bool flush_microtask_queue) {
DCHECK(frame_);
StopLoading();
// `frame_` may become null because this method can get re-entered. If it
// is null we've already run the code below so just return early.
if (!frame_)
return;
if (flush_microtask_queue) {
// Flush microtask queue so that they all run on pre-navigation context.
// TODO(dcheng): This is a temporary hack that should be removed. This is
// only here because it's currently not possible to drop the microtasks
// queued for a Document when the Document is navigated away; instead, the
// entire microtask queue needs to be flushed. Unfortunately, running the
// microtasks any later results in violating internal invariants, since
// Blink does not expect the DocumentLoader for a not-yet-detached Document
// to be null. It is also not possible to flush microtasks any earlier,
// since flushing microtasks can only be done after any other JS (which can
// queue additional microtasks) has run. Once it is possible to associate
// microtasks with a v8::Context, remove this hack.
frame_->GetDocument()
->GetAgent()
.event_loop()
->PerformMicrotaskCheckpoint();
}
ScriptForbiddenScope forbid_scripts;
// If that load cancellation triggered another detach, leave.
// (fast/frames/detach-frame-nested-no-crash.html is an example of this.)
if (!frame_)
return;
extra_data_.reset();
service_worker_network_provider_ = nullptr;
WeakIdentifierMap<DocumentLoader>::NotifyObjectDestroyed(this);
frame_ = nullptr;
}
const KURL& DocumentLoader::UnreachableURL() const {
return unreachable_url_;
}
const std::optional<blink::mojom::FetchCacheMode>&
DocumentLoader::ForceFetchCacheMode() const {
return force_fetch_cache_mode_;
}
bool DocumentLoader::WillLoadUrlAsEmpty(const KURL& url) {
if (url.IsEmpty())
return true;
// Usually, we load urls with about: scheme as empty.
// However, about:srcdoc is only used as a marker for non-existent
// url of iframes with srcdoc attribute, which have possibly non-empty
// content of the srcdoc attribute used as document's html.
if (url.IsAboutSrcdocURL())
return false;
return SchemeRegistry::ShouldLoadURLSchemeAsEmptyDocument(url.Protocol());
}
bool WebDocumentLoader::WillLoadUrlAsEmpty(const WebURL& url) {
return DocumentLoader::WillLoadUrlAsEmpty(url);
}
void DocumentLoader::InitializeEmptyResponse() {
response_ = ResourceResponse(url_);
response_.SetMimeType(AtomicString("text/html"));
response_.SetTextEncodingName(AtomicString("utf-8"));
}
void DocumentLoader::StartLoading() {
probe::LifecycleEvent(frame_, this, "init",
base::TimeTicks::Now().since_origin().InSecondsF());
StartLoadingInternal();
params_ = nullptr;
}
void DocumentLoader::StartLoadingInternal() {
GetTiming().MarkNavigationStart();
DCHECK_EQ(state_, kNotStarted);
DCHECK(params_);
state_ = kProvisional;
if (url_.IsEmpty() && commit_reason_ != CommitReason::kInitialization)
url_ = BlankURL();
if (loading_url_as_empty_document_) {
InitializeEmptyResponse();
return;
}
body_loader_ = std::move(params_->body_loader);
DCHECK(body_loader_);
DCHECK(!GetTiming().NavigationStart().is_null());
// The fetch has already started in the browser,
// so we don't MarkFetchStart here.
main_resource_identifier_ = CreateUniqueIdentifier();
virtual_time_pauser_ =
frame_->GetFrameScheduler()->CreateWebScopedVirtualTimePauser(
url_.GetString(),
WebScopedVirtualTimePauser::VirtualTaskDuration::kNonInstant);
virtual_time_pauser_.PauseVirtualTime();
// Many parties are interested in resource loading, so we will notify
// them through various DispatchXXX methods on FrameFetchContext.
GetFrameLoader().Progress().WillStartLoading(main_resource_identifier_,
ResourceLoadPriority::kVeryHigh);
probe::WillSendNavigationRequest(probe::ToCoreProbeSink(GetFrame()),
main_resource_identifier_, this, url_,
http_method_, http_body_.get());
for (WebNavigationParams::RedirectInfo& redirect : params_->redirects) {
HandleRedirect(redirect);
}
ApplyClientHintsConfig(params_->enabled_client_hints);
PreloadHelper::LoadLinksFromHeader(
response_.HttpHeaderField(http_names::kLink),
response_.CurrentRequestUrl(), *GetFrame(), nullptr,
PreloadHelper::LoadLinksFromHeaderMode::kDocumentBeforeCommit,
nullptr /* viewport_description */, nullptr /* alternate_resource_info */,
nullptr /* recursive_prefetch_token */);
GetFrameLoader().Progress().IncrementProgress(main_resource_identifier_,
response_);
probe::DidReceiveResourceResponse(probe::ToCoreProbeSink(GetFrame()),
main_resource_identifier_, this, response_,
nullptr /* resource */);
HandleResponse();
loading_main_document_from_mhtml_archive_ =
EqualIgnoringASCIICase("multipart/related", response_.MimeType()) ||
EqualIgnoringASCIICase("message/rfc822", response_.MimeType());
if (loading_main_document_from_mhtml_archive_) {
// The browser process should block any navigation to an MHTML archive
// inside iframes. See NavigationRequest::OnResponseStarted().
CHECK(frame_->IsMainFrame());
// To commit an mhtml archive synchronously we have to load the whole body
// synchronously and parse it, and it's already loaded in a buffer usually.
// This means we should not defer, and we'll finish loading synchronously
// from StartLoadingBody().
body_loader_->StartLoadingBody(this);
return;
}
InitializePrefetchedSignedExchangeManager();
body_loader_->SetDefersLoading(freeze_mode_);
}
void DocumentLoader::StartLoadingResponse() {
TRACE_EVENT_WITH_FLOW0("loading", "DocumentLoader::StartLoadingResponse",
TRACE_ID_LOCAL(this),
TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT);
// TODO(dcheng): Clean up the null checks in this helper.
if (!frame_)
return;
CHECK_GE(state_, kCommitted);
CreateParserPostCommit();
// The main document from an MHTML archive is not loaded from its HTTP
// response, but from the main resource within the archive (in the response).
if (loading_main_document_from_mhtml_archive_) {
// If the `archive_` contains a main resource, load the main document from
// the archive, else it will remain empty.
if (ArchiveResource* resource = archive_->MainResource()) {
DCHECK_EQ(archive_->LoadResult(),
mojom::blink::MHTMLLoadResult::kSuccess);
data_buffer_ = resource->Data();
ProcessDataBuffer();
FinishedLoading(base::TimeTicks::Now());
return;
}
// Log attempts loading a malformed archive.
DCHECK_NE(archive_->LoadResult(), mojom::blink::MHTMLLoadResult::kSuccess);
frame_->Console().AddMessage(MakeGarbageCollected<ConsoleMessage>(
mojom::blink::ConsoleMessageSource::kJavaScript,
mojom::blink::ConsoleMessageLevel::kError,
"Malformed multipart archive: " + url_.GetString()));
FinishedLoading(base::TimeTicks::Now());
return;
}
// Empty documents are empty by definition. Nothing to load.
if (loading_url_as_empty_document_) {
FinishedLoading(base::TimeTicks::Now());
return;
}
// Implements "Then, the user agent must act as if it had stopped parsing."
// from https://html.spec.whatwg.org/C/browsing-the-web.html#read-media
//
// This is an oddity of navigating to a media resource: the original request
// for the media resource—which resulted in a committed navigation—is simply
// discarded, while the media element created inside the MediaDocument then
// makes *another new* request for the same media resource.
//
// TODO(dcheng): Barring something really strange and unusual, there should
// always be a frame here.
if (frame_ && frame_->GetDocument()->IsMediaDocument()) {
parser_->Finish();
StopLoading();
return;
}
// Committing can run unload handlers, which can detach this frame or
// stop this loader.
if (!frame_ || !body_loader_)
return;
if (!url_.ProtocolIsInHTTPFamily()) {
body_loader_->StartLoadingBody(this);
return;
}
if (parser_->IsPreloading()) {
// If we were waiting for the document loader, the body has already
// started loading and it is safe to continue parsing.
parser_->CommitPreloadedData();
} else {
body_loader_->StartLoadingBody(this);
}
}
void DocumentLoader::DidInstallNewDocument(Document* document) {
TRACE_EVENT_WITH_FLOW0("loading", "DocumentLoader::DidInstallNewDocument",
TRACE_ID_LOCAL(this),
TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT);
// This was called already during `InitializeWindow`, but it could be that we
// didn't have a Document then (which happens when `InitializeWindow` reuses
// the window and calls `LocalDOMWindow::ClearForReuse()`). This is
// idempotent, so it is safe to do it again (in fact, it will be called again
// also when parsing origin trials delivered in meta tags).
frame_->DomWindow()->GetOriginTrialContext()->InitializePendingFeatures();
frame_->DomWindow()->BindContentSecurityPolicy();
if (history_item_ && IsBackForwardOrRestore(load_type_)) {
document->SetStateForNewControls(history_item_->GetDocumentState());
}
DCHECK(document->GetFrame());
// TODO(dgozman): modify frame's client hints directly once we commit
// synchronously.
document->GetFrame()->GetClientHintsPreferences().UpdateFrom(
client_hints_preferences_);
document->GetFrame()->SetReducedAcceptLanguage(reduced_accept_language_);
const AtomicString& dns_prefetch_control =
response_.HttpHeaderField(http_names::kXDNSPrefetchControl);
if (!dns_prefetch_control.empty())
document->ParseDNSPrefetchControlHeader(dns_prefetch_control);
String header_content_language =
response_.HttpHeaderField(http_names::kContentLanguage);
if (!header_content_language.empty()) {
wtf_size_t comma_index = header_content_language.find(',');
// kNotFound == -1 == don't truncate
header_content_language.Truncate(comma_index);
header_content_language =
header_content_language.StripWhiteSpace(IsHTMLSpace<UChar>);
if (!header_content_language.empty())
document->SetContentLanguage(AtomicString(header_content_language));
}
for (const auto& message : document_policy_parsing_messages_) {
document->AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>(
mojom::blink::ConsoleMessageSource::kOther, message.level,
message.content));
}
document_policy_parsing_messages_.clear();
WarnIfSandboxIneffective(document->domWindow());
StartViewTransitionIfNeeded(*document);
// This also enqueues the event for a Document that's loading while
// prerendered; however, the event still fires at the correct time (first
// render opportunity after activation) since the event is fired as part of
// updating the rendering which is suppressed until the prerender is
// activated.
if (RuntimeEnabledFeatures::PageRevealEventEnabled()) {
document->EnqueuePageRevealEvent();
}
}
void DocumentLoader::WillCommitNavigation() {
if (commit_reason_ != CommitReason::kRegular)
return;
probe::WillCommitLoad(frame_, this);
frame_->GetIdlenessDetector()->WillCommitLoad();
}
void DocumentLoader::DidCommitNavigation() {
if (commit_reason_ != CommitReason::kRegular)
return;
// When committing a new document, the FrameScheduler might need to carry over
// the previous document's FrameScheduler's `UnreportedTaskTime()`, as that
// value should be aggregated across all documents that ever committed in the
// same frame.
base::TimeDelta previous_document_unreported_task_time =
frame_->GetFrameScheduler()->UnreportedTaskTime();
if (OldDocumentInfoForCommit* old_document_info =
ScopedOldDocumentInfoForCommitCapturer::CurrentInfo()) {
previous_document_unreported_task_time =
old_document_info->frame_scheduler_unreported_task_time;
}
WebHistoryCommitType commit_type = LoadTypeToCommitType(load_type_);
frame_->GetFrameScheduler()->DidCommitProvisionalLoad(
commit_type == kWebHistoryInertCommit,
load_type_ == WebFrameLoadType::kReload
? FrameScheduler::NavigationType::kReload
: FrameScheduler::NavigationType::kOther,
{previous_document_unreported_task_time});
if (response_.CacheControlContainsNoCache()) {
GetFrame()->GetFrameScheduler()->RegisterStickyFeature(
SchedulingPolicy::Feature::kMainResourceHasCacheControlNoCache,
{SchedulingPolicy::DisableBackForwardCache()});
}
if (response_.CacheControlContainsNoStore()) {
GetFrame()->GetFrameScheduler()->RegisterStickyFeature(
SchedulingPolicy::Feature::kMainResourceHasCacheControlNoStore,
{SchedulingPolicy::DisableBackForwardCache()});
}
// Reset the global |FontPerformance| counter.
if (GetFrame()->IsMainFrame() &&
GetFrame()->GetDocument()->ShouldMarkFontPerformance())
FontPerformance::Reset();
// When a new navigation commits in the frame, subresource loading should be
// resumed.
frame_->ResumeSubresourceLoading();
Document* document = frame_->GetDocument();
InteractiveDetector* interactive_detector =
InteractiveDetector::From(*document);
if (interactive_detector)
interactive_detector->SetNavigationStartTime(GetTiming().NavigationStart());
DEVTOOLS_TIMELINE_TRACE_EVENT("CommitLoad", inspector_commit_load_event::Data,
frame_);
// Needs to run before dispatching preloads, as it may evict the memory cache.
probe::DidCommitLoad(frame_, this);
frame_->GetPage()->DidCommitLoad(frame_);
}
Frame* DocumentLoader::CalculateOwnerFrame() {
// For "about:srcdoc", the parent is the owner frame.
if (url_.IsAboutSrcdocURL())
return frame_->Tree().Parent();
// Consider the parent or the opener for 1) about:blank" (including
// "about:mumble" - see https://crbug.com/1220186) and 2) the initial empty
// document (with an empty `url_`)..
DCHECK(url_.ProtocolIsAbout() || url_.IsEmpty()) << "url_ = " << url_;
Frame* owner_frame = frame_->Tree().Parent();
if (!owner_frame)
owner_frame = frame_->Opener();
// No other checks are needed for the initial empty document.
if (url_.IsEmpty())
return owner_frame;
// For about:blank the owner frame should be the actual initiator/requestor of
// the navigation - see:
// https://html.spec.whatwg.org/multipage/browsers.html#determining-the-origin
//
// This requires a few extra checks below.
DCHECK(url_.ProtocolIsAbout()) << "url_ = " << url_;
// Browser-initiated navigations to about:blank should always commit with an
// opaque origin (i.e. they should not inherit the origin and other properties
// of the `owner_frame`).
if (!requestor_origin_)
return nullptr;
// The parent-or-owner heuristic above might not find the actual initiator of
// the navigation (e.g. see the SameSiteSiblingToAboutBlank_CrossSiteTop
// testcase). To limit (but not eliminate :-/) incorrect cases we require
// that `owner_frame`'s origin is same origin with `requestor_origin_`.
//
// TODO(https://crbug.com/1176291): Improve heuristics for finding the
// correct initiator, to properly inherit/alias `document.domain` in more
// cases.
if (owner_frame &&
owner_frame->GetSecurityContext()->GetSecurityOrigin()->IsSameOriginWith(
requestor_origin_.get())) {
return owner_frame;
} else {
return nullptr;
}
}
scoped_refptr<SecurityOrigin> DocumentLoader::CalculateOrigin(
Document* owner_document) {
scoped_refptr<SecurityOrigin> origin;
StringBuilder debug_info_builder;
// Whether the origin is newly created within this call, instead of copied
// from an existing document's origin or from `origin_to_commit_`. If this is
// true, we won't try to compare the nonce of this origin (if it's opaque) to
// the browser-calculated origin later on.
bool origin_is_newly_created = false;
if (origin_to_commit_) {
// Origin to commit is specified by the browser process, it must be taken
// and used directly. It is currently supplied only for failed navigations
// and data: URL navigations.
CHECK(is_error_page_for_failed_navigation_ || url_.ProtocolIsData());
CHECK(origin_to_commit_->IsOpaque());
origin = origin_to_commit_;
debug_info_builder.Append("use_origin_to_commit");
} else if (IsPagePopupRunningInWebTest(frame_)) {
// If we are a page popup in LayoutTests ensure we use the popup
// owner's security origin so the tests can possibly access the
// document via internals API.
auto* owner_context = frame_->PagePopupOwner()->GetExecutionContext();
origin = owner_context->GetSecurityOrigin()->IsolatedCopy();
debug_info_builder.Append("use_popup_owner_origin");
} else if (owner_document && owner_document->domWindow()) {
// Prefer taking `origin` from `owner_document` if one is available - this
// will correctly inherit/alias `SecurityOrigin::domain_` from the
// `owner_document` (note that the
// `SecurityOrigin::CreateWithReferenceOrigin` fallback below A) doesn't
// preserve `domain_` via `url::Origin` and B) doesn't alias the origin /
// `domain_` - changes in the "about:blank" document do not affect the
// initiator document).
//
// TODO(dcheng): if we're aliasing an origin, do we need to go through any
// of the other checks below? This seems like it could have potentially
// surprising side effects: for example, if the web security setting toggle
// is disabled, this will affect the owner document's origin too...
//
// TODO(dcheng): maybe FrameLoader::Init() should specify origin_to_commit_?
// But origin_to_commit_ is currently cloned with IsolatedCopy() which
// breaks aliasing...
origin = owner_document->domWindow()->GetMutableSecurityOrigin();
debug_info_builder.Append("use_owner_document_origin(");
// Add debug information about the owner document too.
if (owner_document->GetFrame() == frame_->Tree().Parent()) {
debug_info_builder.Append("parent");
} else {
debug_info_builder.Append("opener");
}
debug_info_builder.Append(":");
debug_info_builder.Append(
owner_document->Loader()->origin_calculation_debug_info_);
debug_info_builder.Append(", url=");
debug_info_builder.Append(owner_document->Url().BaseAsString());
debug_info_builder.Append(")");
} else if (url_.IsAboutSrcdocURL()) {
// If there's no owner_document and this is a srcdoc load, then get the
// origin from the remote parent. A srcdoc navigation with no owner_document
// can only happen if the iframe is sandboxed and isolated sandboxed frames
// is enabled In that case, copy the origin from the remote parent
// here—though this origin will only be used to derive the actual opaque
// origin later.
// TODO(https://crbug.com/328279696): this block can be removed once the
// about:srcdoc navigation blocking finishes rolling out, as then the
// initiator can never be cross-origin to the parent.
debug_info_builder.Append("about_srcdoc_with_remote_parent[origin=");
// Verify this is a sandboxed srcdoc frame.
CHECK((policy_container_->GetPolicies().sandbox_flags &
network::mojom::blink::WebSandboxFlags::kOrigin) !=
network::mojom::blink::WebSandboxFlags::kNone);
origin = frame_->Tree()
.Parent()
->GetSecurityContext()
->GetSecurityOrigin()
->IsolatedCopy();
debug_info_builder.Append(origin->ToString());
debug_info_builder.Append("]");
} else {
debug_info_builder.Append("use_url_with_precursor");
// Otherwise, create an origin that propagates precursor information
// as needed. For non-opaque origins, this creates a standard tuple
// origin, but for opaque origins, it creates an origin with the
// initiator origin as the precursor.
origin = SecurityOrigin::CreateWithReferenceOrigin(url_,
requestor_origin_.get());
origin_is_newly_created = true;
}
if ((policy_container_->GetPolicies().sandbox_flags &
network::mojom::blink::WebSandboxFlags::kOrigin) !=
network::mojom::blink::WebSandboxFlags::kNone) {
debug_info_builder.Append(", add_sandbox[new_origin_precursor=");
auto sandbox_origin = origin->DeriveNewOpaqueOrigin();
debug_info_builder.Append(
sandbox_origin->GetOriginOrPrecursorOriginIfOpaque()->ToString());
debug_info_builder.Append("]");
// If we're supposed to inherit our security origin from our
// owner, but we're also sandboxed, the only things we inherit are
// the origin's potential trustworthiness and the ability to
// load local resources. The latter lets about:blank iframes in
// file:// URL documents load images and other resources from
// the file system.
//
// Note: Sandboxed about:srcdoc iframe without "allow-same-origin" aren't
// allowed to load user's file, even if its parent can.
if (url_.IsAboutSrcdocURL()) {
// We should only have a sandboxed, srcdoc frame without an owner document
// if isolated-sandboxed-iframes is enabled. Only cases that would
// normally inherit the origin need to be handled here, and a sandboxed
// about:blank document won't be moved out of process. Also, data: urls
// don't get secure contexts, so needn't be considered here.
CHECK(owner_document ||
base::FeatureList::IsEnabled(features::kIsolateSandboxedIframes));
bool is_potentially_trustworthy =
origin->GetOriginOrPrecursorOriginIfOpaque()
->IsPotentiallyTrustworthy();
if (is_potentially_trustworthy) {
sandbox_origin->SetOpaqueOriginIsPotentiallyTrustworthy(true);
debug_info_builder.Append(", _potentially_trustworthy");
}
} else if (owner_document) {
if (origin->IsPotentiallyTrustworthy()) {
sandbox_origin->SetOpaqueOriginIsPotentiallyTrustworthy(true);
debug_info_builder.Append(", _potentially_trustworthy");
}
if (origin->CanLoadLocalResources()) {
sandbox_origin->GrantLoadLocalResources();
debug_info_builder.Append(", _load_local");
}
}
origin = sandbox_origin;
origin_is_newly_created = true;
}
if (commit_reason_ == CommitReason::kInitialization &&
frame_->GetSettings()->GetShouldReuseGlobalForUnownedMainFrame() &&
!frame_->Parent() && !frame_->Opener()) {
// For legacy reasons, grant universal access to a top-level initial empty
// Document in Android WebView. This allows the WebView embedder to inject
// arbitrary script into about:blank and have it persist when the frame is
// navigated.
CHECK(origin->IsOpaque());
origin->GrantUniversalAccess();
debug_info_builder.Append(", universal_access_webview");
} else if (!frame_->GetSettings()->GetWebSecurityEnabled()) {
// Web security is turned off. We should let this document access
// every other document. This is used primary by testing harnesses for
// web sites.
origin->GrantUniversalAccess();
debug_info_builder.Append(", universal_access_no_web_security");
} else if (origin->IsLocal()) {
if (frame_->GetSettings()->GetAllowUniversalAccessFromFileURLs()) {
// Some clients want local URLs to have universal access, but that
// setting is dangerous for other clients.
origin->GrantUniversalAccess();
debug_info_builder.Append(", universal_access_allow_file");
} else if (!frame_->GetSettings()->GetAllowFileAccessFromFileURLs()) {
// Some clients do not want local URLs to have access to other local
// URLs.
origin->BlockLocalAccessFromLocalOrigin();
debug_info_builder.Append(", universal_access_block_file");
}
}
if (grant_load_local_resources_) {
origin->GrantLoadLocalResources();
debug_info_builder.Append(", grant_load_local_resources");
}
if (origin->IsOpaque()) {
KURL url = url_.IsEmpty() ? BlankURL() : url_;
if (SecurityOrigin::Create(url)->IsPotentiallyTrustworthy()) {
origin->SetOpaqueOriginIsPotentiallyTrustworthy(true);
debug_info_builder.Append(", is_potentially_trustworthy");
}
}
if (origin_is_newly_created) {
// This information will be used by the browser side to figure out if it can
// do browser vs renderer calculated origin equality check. Note that this
// information must be the last part of the debug info string.
// TODO(https://crbug.com/888079): Consider adding a separate boolean that
// tracks this instead of piggybacking `origin_calculation_debug_info_`.
debug_info_builder.Append(", is_newly_created");
}
origin_calculation_debug_info_ = debug_info_builder.ToAtomicString();
return origin;
}
bool ShouldReuseDOMWindow(LocalDOMWindow* window,
SecurityOrigin* security_origin,
bool window_anonymous_matching) {
if (!window) {
return false;
}
// Anonymous is tracked per-Window, so if it does not match, do not reuse it.
if (!window_anonymous_matching) {
return false;
}
// Only navigations from the initial empty document can reuse the window.
if (!window->document()->IsInitialEmptyDocument()) {
return false;
}
// The new origin must match the origin of the initial empty document.
return window->GetSecurityOrigin()->CanAccess(security_origin);
}
namespace {
bool HasPotentialUniversalAccessPrivilege(LocalFrame* frame) {
return !frame->GetSettings()->GetWebSecurityEnabled() ||
frame->GetSettings()->GetAllowUniversalAccessFromFileURLs();
}
} // namespace
WindowAgent* GetWindowAgentForOrigin(
LocalFrame* frame,
SecurityOrigin* origin,
bool is_origin_agent_cluster,
bool origin_agent_cluster_left_as_default) {
// TODO(keishi): Also check if AllowUniversalAccessFromFileURLs might
// dynamically change.
return frame->window_agent_factory().GetAgentForOrigin(
HasPotentialUniversalAccessPrivilege(frame), origin,
is_origin_agent_cluster, origin_agent_cluster_left_as_default);
}
// Inheriting cases use their agent's "is origin-keyed" value, which is set
// by whatever they're inheriting from.
//
// javascript: URLs use the calling page as their Url() value, so we need to
// include them explicitly.
bool ShouldInheritExplicitOriginKeying(const KURL& url, CommitReason reason) {
return Document::ShouldInheritSecurityOriginFromOwner(url) ||
reason == CommitReason::kJavascriptUrl;
}
bool DocumentLoader::IsSameOriginInitiator() const {
return requestor_origin_ &&
requestor_origin_->IsSameOriginWith(
SecurityOrigin::Create(Url()).get()) &&
Url().ProtocolIsInHTTPFamily();
}
void DocumentLoader::InitializeWindow(Document* owner_document) {
TRACE_EVENT_WITH_FLOW0("loading", "DocumentLoader::InitializeWindow",
TRACE_ID_LOCAL(this),
TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT);
// Javascript URLs and XSLT committed document must not pass a new
// policy_container_, since they must keep the previous document one.
DCHECK((commit_reason_ != CommitReason::kJavascriptUrl &&
commit_reason_ != CommitReason::kXSLT) ||
!policy_container_);
bool did_have_policy_container = (policy_container_ != nullptr);
// The old window's PolicyContainer must be accessed before being potentially
// extracted below.
const bool old_window_is_credentialless =
frame_->DomWindow() && frame_->DomWindow()
->GetPolicyContainer()
->GetPolicies()
.is_credentialless;
// DocumentLoader::InitializeWindow is called either on FrameLoader::Init or
// on FrameLoader::CommitNavigation. FrameLoader::Init always initializes a
// non null |policy_container_|. If |policy_container_| is null, this is
// committing a navigation without a policy container. This can happen in a
// few circumstances:
// 1. for a javascript or a xslt document,
// 2. when loading html in a page for testing,
// 3. this is the synchronous navigation to 'about:blank'.
// (On the other side notice that all navigations committed by the browser
// have a non null |policy_container_|). In all the cases 1-3 above, we should
// keep the PolicyContainer of the previous document (since the browser does
// not know about this and is not changing the RenderFrameHost's
// PolicyContainerHost).
if (frame_->DomWindow() && !policy_container_) {
policy_container_ = frame_->DomWindow()->TakePolicyContainer();
}
// Every window must have a policy container.
DCHECK(policy_container_);
const bool window_anonymous_matching =
old_window_is_credentialless ==
policy_container_->GetPolicies().is_credentialless;
ContentSecurityPolicy* csp = CreateCSP();
scoped_refptr<SecurityOrigin> security_origin;
if (frame_->IsProvisional()) {
// Provisional frames shouldn't be doing anything other than act as a
// placeholder. Enforce a strict sandbox and ensure a unique opaque origin.
// TODO(dcheng): Actually enforce strict sandbox flags for provisional
// frame. For some reason, doing so breaks some random devtools tests.
security_origin = SecurityOrigin::CreateUniqueOpaque();
} else if (commit_reason_ == CommitReason::kJavascriptUrl ||
commit_reason_ == CommitReason::kXSLT) {
// For javascript: URL and XSLT commits, which don't go through the browser
// process and reuses the same DocumentLoader, reuse the previous origin.
// TODO(dcheng): Is it a problem that the previous origin is copied with
// isolated copy? This probably has observable side effects (e.g. executing
// a javascript: URL in an about:blank frame that inherited an origin will
// cause the origin to no longer be aliased).
security_origin = frame_->DomWindow()->GetSecurityOrigin()->IsolatedCopy();
} else {
security_origin = CalculateOrigin(owner_document);
}
bool origin_agent_cluster = origin_agent_cluster_;
// Note: this code must be kept in sync with
// WindowAgentFactory::GetAgentForOrigin(), as the two conditions below hand
// out universal WindowAgent objects, and thus override OAC.
if (HasPotentialUniversalAccessPrivilege(frame_.Get()) ||
security_origin->IsLocal()) {
// In this case we either have AllowUniversalAccessFromFileURLs enabled, or
// WebSecurity is disabled, or it's a local scheme such as file://; any of
// these cases forces us to use a common WindowAgent for all origins, so
// don't attempt to use OriginAgentCluster. Note:
// AllowUniversalAccessFromFileURLs is deprecated as of Android R, so
// eventually this use case will diminish.
origin_agent_cluster = false;
} else if (ShouldInheritExplicitOriginKeying(Url(), commit_reason_) &&
owner_document && owner_document->domWindow()) {
// Since we're inheriting the owner document's origin, we should also use
// its OriginAgentCluster (OAC) in determining which WindowAgent to use,
// overriding the OAC value sent in the commit params. For example, when
// about:blank is loaded, it has OAC = false, but if we have an owner, then
// we are using the owner's SecurityOrigin, we should match the OAC value
// also. JavaScript URLs also use their owner's SecurityOrigins, and don't
// set OAC as part of their commit params.
// TODO(wjmaclean,domenic): we're currently verifying that the OAC
// inheritance is correct for both XSLT documents and non-initial
// about:blank cases. Given the relationship between OAC, SecurityOrigin,
// and COOP/COEP, a single inheritance pathway would make sense; this work
// is being tracked in https://crbug.com/1183935.
origin_agent_cluster =
owner_document->domWindow()->GetAgent()->IsOriginKeyedForInheritance();
}
bool inherited_has_storage_access = false;
// In some rare cases, we'll re-use a LocalDOMWindow for a new Document. For
// example, when a script calls window.open("..."), the browser gives
// JavaScript a window synchronously but kicks off the load in the window
// asynchronously. Web sites expect that modifications that they make to the
// window object synchronously won't be blown away when the network load
// commits. To make that happen, we "securely transition" the existing
// LocalDOMWindow to the Document that results from the network load. See also
// Document::IsSecureTransitionTo.
if (!ShouldReuseDOMWindow(frame_->DomWindow(), security_origin.get(),
window_anonymous_matching)) {
auto* agent = GetWindowAgentForOrigin(
frame_.Get(), security_origin.get(), origin_agent_cluster,
origin_agent_cluster_left_as_default_);
frame_->SetDOMWindow(MakeGarbageCollected<LocalDOMWindow>(*frame_, agent));
// TODO(https://crbug.com/1111897): This call is likely to happen happen
// multiple times per agent, since navigations can happen multiple times per
// agent. This is subpar.
if (!ShouldInheritExplicitOriginKeying(Url(), commit_reason_) &&
origin_agent_cluster) {
agent->ForceOriginKeyedBecauseOfInheritance();
}
if (load_with_storage_access_) {
frame_->DomWindow()->SetHasStorageAccess();
inherited_has_storage_access = true;
}
} else {
if (frame_->GetSettings()->GetShouldReuseGlobalForUnownedMainFrame() &&
frame_->IsMainFrame()) {
// When GetShouldReuseGlobalForUnownedMainFrame() causes a main frame's
// window to be reused, we should not inherit the initial empty document's
// Agent, which was a universal access Agent.
// This happens only in android webview.
frame_->DomWindow()->ResetWindowAgent(GetWindowAgentForOrigin(
frame_.Get(), security_origin.get(), origin_agent_cluster,
origin_agent_cluster_left_as_default_));
}
frame_->DomWindow()->ClearForReuse();
// If one of the two following things is true:
// 1. JS called window.open(), Blink created a new auxiliary browsing
// context, and the target URL is resolved to 'about:blank'.
// 2. A new iframe is attached, and the target URL is resolved to
// 'about:blank'.
// then Blink immediately synchronously navigates to about:blank after
// creating the new browsing context and has initialized it with the initial
// empty document. In those cases, we must not pass a PolicyContainer, as
// this does not trigger a corresponding browser-side navigation, and we
// must reuse the PolicyContainer.
//
// TODO(antoniosartori): Improve this DCHECK to match exactly the condition
// above.
DCHECK(did_have_policy_container || WillLoadUrlAsEmpty(Url()));
}
content_security_notifier_ =
HeapMojoRemote<mojom::blink::ContentSecurityNotifier>(
frame_->DomWindow());
base::UmaHistogramBoolean("API.StorageAccess.DocumentLoadedWithStorageAccess",
frame_->DomWindow()->HasStorageAccess());
base::UmaHistogramBoolean("API.StorageAccess.DocumentInheritedStorageAccess",
inherited_has_storage_access);
frame_->DomWindow()->SetPolicyContainer(std::move(policy_container_));
frame_->DomWindow()->SetContentSecurityPolicy(csp);
BlinkStorageKey storage_key(storage_key_);
// TODO(crbug.com/1199077): For some reason `storage_key_` is occasionally
// null. If that's the case this will create one based on the
// `security_origin`.
// TODO(crbug.com/1199077): Some tests (potentially other code?) rely on an
// opaque origin + nonce. Investigate whether this combination should be
// disallowed.
if (storage_key.GetSecurityOrigin()->IsOpaque() && !storage_key.GetNonce()) {
storage_key = BlinkStorageKey::CreateFirstParty(security_origin);
}
// Now that we have the final window and Agent, ensure the security origin has
// the appropriate agent cluster id. This may derive a new security origin.
security_origin = security_origin->GetOriginForAgentCluster(
frame_->DomWindow()->GetAgent()->cluster_id());
// TODO(https://crbug.com/888079): Just use the storage key sent by the
// browser once the browser will be able to compute the origin in all cases.
frame_->DomWindow()->SetStorageKey(storage_key.WithOrigin(security_origin));
if (storage_key == session_storage_key_ ||
storage_key.GetSecurityOrigin()->IsOpaque() ||
session_storage_key_.GetSecurityOrigin()->IsOpaque()) {
// If the `storage_key` and `session_storage_key_` match (or either are
// opaque), we should just use whatever storage key was built above as we
// aren't preventing partition.
frame_->DomWindow()->SetSessionStorageKey(
frame_->DomWindow()->GetStorageKey());
} else {
// Otherwise, we first must verify that the requested StorageKey to use for
// binding session storage has the same SecurityOrigin as the actual
// storage key. The purpose of this path is to change the partition for a
// given origin, not to allow access to another origin's data.
DCHECK(session_storage_key_ ==
BlinkStorageKey::CreateFirstParty(storage_key_.GetSecurityOrigin()));
// We use the renderer side origin when setting the StorageKey on the path
// above, so we check that the renderer's understanding of the origin
// matches the session storage StorageKey. This is another precaution to
// to prevent cross-origin partition binding.
// TODO(https://crbug.com/888079): Depend on the origin in the StorageKey.
if (session_storage_key_.GetSecurityOrigin()->IsSameOriginWith(
security_origin.get())) {
frame_->DomWindow()->SetSessionStorageKey(session_storage_key_);
} else {
frame_->DomWindow()->SetSessionStorageKey(
frame_->DomWindow()->GetStorageKey());
}
}
// Conceptually, SecurityOrigin doesn't have to be initialized after sandbox
// flags are applied, but there's a UseCounter in SetSecurityOrigin() that
// wants to inspect sandbox flags.
SecurityContext& security_context = frame_->DomWindow()->GetSecurityContext();
security_context.SetSecurityOrigin(std::move(security_origin));
// Requires SecurityOrigin to be initialized.
OriginTrialContext::AddTokensFromHeader(
frame_->DomWindow(), response_.HttpHeaderField(http_names::kOriginTrial));
if (auto* parent = frame_->Tree().Parent()) {
const SecurityContext* parent_context = parent->GetSecurityContext();
security_context.SetInsecureRequestPolicy(
parent_context->GetInsecureRequestPolicy());
for (auto to_upgrade : parent_context->InsecureNavigationsToUpgrade())
security_context.AddInsecureNavigationUpgrade(to_upgrade);
}
String referrer_policy_header =
response_.HttpHeaderField(http_names::kReferrerPolicy);
if (!referrer_policy_header.IsNull()) {
CountUse(WebFeature::kReferrerPolicyHeader);
frame_->DomWindow()->ParseAndSetReferrerPolicy(referrer_policy_header,
kPolicySourceHttpHeader);
}
}
void DocumentLoader::CommitNavigation() {
TRACE_EVENT_WITH_FLOW0("loading", "DocumentLoader::CommitNavigation",
TRACE_ID_LOCAL(this),
TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT);
base::ElapsedTimer timer;
DCHECK_LT(state_, kCommitted);
DCHECK(frame_->GetPage());
DCHECK(!frame_->GetDocument() || !frame_->GetDocument()->IsActive());
DCHECK_EQ(frame_->Tree().ChildCount(), 0u);
DCHECK(!frame_->GetDocument() ||
frame_->GetDocument()->ConnectedSubframeCount() == 0);
state_ = kCommitted;
// Prepare a DocumentInit before clearing the frame, because it may need to
// inherit an aliased security context.
Document* owner_document = nullptr;
// Calculate `owner_document` from which the committing navigation should
// inherit the cookie URL and inherit/alias the SecurityOrigin.
if (Document::ShouldInheritSecurityOriginFromOwner(Url())) {
Frame* owner_frame = CalculateOwnerFrame();
if (auto* owner_local_frame = DynamicTo<LocalFrame>(owner_frame))
owner_document = owner_local_frame->GetDocument();
}
LocalDOMWindow* previous_window = frame_->DomWindow();
InitializeWindow(owner_document);
frame_->DomWindow()
->GetRuntimeFeatureStateOverrideContext()
->ApplyOverrideValuesFromParams(modified_runtime_features_);
// Previous same-document navigation tasks are not relevant once a
// cross-document navigation has happened.
if (auto* tracker = scheduler::TaskAttributionTracker::From(
frame_->DomWindow()->GetIsolate())) {
tracker->ResetSameDocumentNavigationTasks();
}
MaybeStartLoadingBodyInBackground(body_loader_.get(), frame_, url_,
response_);
// Record if we have navigated to a non-secure page served from a IP address
// in the private address space.
//
// Use response_.AddressSpace() instead of frame_->DomWindow()->AddressSpace()
// since the latter isn't populated in unit tests.
if (frame_->IsOutermostMainFrame()) {
auto address_space = response_.AddressSpace();
if ((address_space == network::mojom::blink::IPAddressSpace::kPrivate ||
address_space == network::mojom::blink::IPAddressSpace::kLocal) &&
!frame_->DomWindow()->IsSecureContext()) {
CountUse(WebFeature::kMainFrameNonSecurePrivateAddressSpace);
}
}
SecurityContextInit security_init(frame_->DomWindow());
// The document constructed by XSLTProcessor and ScriptController should
// inherit Permissions Policy and Document Policy from the previous Document.
// Note: In XSLT commit and JavaScript commit, |response_| no longer holds
// header fields. Going through regular initialization will cause empty policy
// even if there is header on xml document.
if (commit_reason_ == CommitReason::kXSLT ||
commit_reason_ == CommitReason::kJavascriptUrl) {
DCHECK(response_.HttpHeaderField(http_names::kFeaturePolicy).empty());
DCHECK(response_.HttpHeaderField(http_names::kPermissionsPolicy).empty());
DCHECK(response_.HttpHeaderField(http_names::kDocumentPolicy).empty());
security_init.InitPermissionsPolicyFrom(
previous_window->GetSecurityContext());
security_init.InitDocumentPolicyFrom(previous_window->GetSecurityContext());
} else {
// PermissionsPolicy and DocumentPolicy require SecurityOrigin and origin
// trials to be initialized.
// TODO(iclelland): Add Permissions-Policy-Report-Only to Origin Policy.
security_init.ApplyPermissionsPolicy(
*frame_.Get(), response_, frame_policy_, initial_permissions_policy_,
FencedFrameProperties());
// |document_policy_| is parsed in document loader because it is
// compared with |frame_policy.required_document_policy| to decide
// whether to block the document load or not.
// |report_only_document_policy| does not block the page load. Its
// initialization is delayed to
// SecurityContextInit::InitializeDocumentPolicy(), similar to
// |report_only_permissions_policy|.
security_init.ApplyDocumentPolicy(
document_policy_,
response_.HttpHeaderField(http_names::kDocumentPolicyReportOnly));
}
navigation_scroll_allowed_ = !frame_->DomWindow()->IsFeatureEnabled(
mojom::blink::DocumentPolicyFeature::kForceLoadAtTop);
WillCommitNavigation();
is_prerendering_ = frame_->GetPage()->IsPrerendering();
Document* document = frame_->DomWindow()->InstallNewDocument(
DocumentInit::Create()
.WithWindow(frame_->DomWindow(), owner_document)
.WithToken(token_)
.ForInitialEmptyDocument(commit_reason_ ==
CommitReason::kInitialization)
.ForPrerendering(is_prerendering_)
.WithURL(Url())
.WithTypeFrom(MimeType())
.WithSrcdocDocument(loading_srcdoc_)
.WithJavascriptURL(commit_reason_ == CommitReason::kJavascriptUrl)
.WithFallbackBaseURL(fallback_base_url_)
.WithUkmSourceId(ukm_source_id_)
.WithBaseAuctionNonce(base_auction_nonce_));
RecordUseCountersForCommit();
RecordConsoleMessagesForCommit();
for (const auto& policy : security_init.PermissionsPolicyHeader()) {
if (policy.deprecated_feature.has_value()) {
Deprecation::CountDeprecation(frame_->DomWindow(),
*policy.deprecated_feature);
}
}
frame_->ClearScrollSnapshotClients();
// Determine whether to give the frame sticky user activation. These checks
// mirror the check in Navigator::DidNavigate(). Main frame navigations and
// cross-site navigations should not hold on to the sticky user activation
// state of the previously navigated page. Same-site navigations should retain
// the previous document's sticky user activation state, regardless of whether
// the navigation resulted in a new process being created.
// See: crbug.com/41493458
// TODO(crbug.com/736415): Clear this bit unconditionally for all frames.
if (!should_have_sticky_user_activation_) {
frame_->ClearUserActivation();
} else {
frame_->SetStickyUserActivationState();
}
// The DocumentLoader was flagged as activated if it needs to notify the frame
// that it was activated before navigation. Update the frame state based on
// the new value.
OldDocumentInfoForCommit* old_document_info_for_commit =
ScopedOldDocumentInfoForCommitCapturer::CurrentInfo();
bool had_sticky_activation_before_navigation =
old_document_info_for_commit
? old_document_info_for_commit
->had_sticky_activation_before_navigation
: false;
if (had_sticky_activation_before_navigation != had_sticky_activation_) {
frame_->SetHadStickyUserActivationBeforeNavigation(had_sticky_activation_);
frame_->GetLocalFrameHostRemote()
.HadStickyUserActivationBeforeNavigationChanged(had_sticky_activation_);
}
bool should_clear_window_name =
previous_window && frame_->IsOutermostMainFrame() && !frame_->Opener() &&
!frame_->DomWindow()->GetSecurityOrigin()->IsSameOriginWith(
previous_window->GetSecurityOrigin());
if (should_clear_window_name) {
// TODO(andypaicu): experimentalSetNullName will just record the fact
// that the name would be nulled and if the name is accessed after we will
// fire a UseCounter. If we decide to move forward with this change, we'd
// actually clean the name here.
// frame_->tree().setName(g_null_atom);
frame_->Tree().ExperimentalSetNulledName();
}
bool should_clear_cross_site_cross_browsing_context_group_window_name =
previous_window && frame_->IsOutermostMainFrame() &&
is_cross_site_cross_browsing_context_group_;
if (should_clear_cross_site_cross_browsing_context_group_window_name) {
// TODO(shuuran): CrossSiteCrossBrowsingContextGroupSetNulledName will just
// record the fact that the name would be nulled and if the name is accessed
// after we will fire a UseCounter.
frame_->Tree().CrossSiteCrossBrowsingContextGroupSetNulledName();
}
// MHTML archive's URL is usually a local file. However the main resource
// within the archive has a public URL and must be used to resolve all the
// relative links.
if (loading_main_document_from_mhtml_archive_) {
ArchiveResource* main_resource = archive_->MainResource();
KURL main_resource_url = main_resource ? main_resource->Url() : KURL();
if (!main_resource_url.IsEmpty())
document->SetBaseURLOverride(main_resource_url);
}
// The navigation API is not initialized on the initial about:blank document
// or opaque-origin documents.
if (commit_reason_ != CommitReason::kInitialization &&
!frame_->DomWindow()->GetSecurityOrigin()->IsOpaque()) {
frame_->DomWindow()->navigation()->InitializeForNewWindow(
*history_item_, load_type_, commit_reason_,
previous_window->navigation(), navigation_api_back_entries_,
navigation_api_forward_entries_, navigation_api_previous_entry_);
// Now that the navigation API's entries array is initialized, we don't need
// to retain the state from which it was initialized.
navigation_api_back_entries_.clear();
navigation_api_forward_entries_.clear();
navigation_api_previous_entry_ = WebHistoryItem();
}
if (commit_reason_ == CommitReason::kXSLT)
DocumentXSLT::SetHasTransformSource(*document);
// If we've received browsing context group information, update the Page's
// browsing context group. This can only ever happen for a top-level frame,
// because subframes can never change browsing context group, and the
// value is omitted by the browser process at commit time.
if (browsing_context_group_info_.has_value()) {
CHECK(frame_->IsMainFrame());
frame_->GetPage()->UpdateBrowsingContextGroup(
browsing_context_group_info_.value());
}
DidInstallNewDocument(document);
// This must be called before the document is opened, otherwise HTML parser
// will use stale values from HTMLParserOption.
DidCommitNavigation();
// This must be called after DidInstallNewDocument which sets the content
// language for the document.
if (url_.ProtocolIsInHTTPFamily()) {
RecordAcceptLanguageAndContentLanguageMetric();
RecordParentAndChildContentLanguageMetric();
}
bool is_same_origin_initiator = IsSameOriginInitiator();
// No requestor origin means it's browser-initiated (which includes *all*
// history navigations, including those initiated from `window.history`
// API).
last_navigation_had_trusted_initiator_ =
!requestor_origin_ || is_same_origin_initiator;
// The PaintHolding feature defers compositor commits until content has
// been painted or 500ms have passed, whichever comes first. The additional
// PaintHoldingCrossOrigin feature allows PaintHolding even for cross-origin
// navigations, otherwise only same-origin navigations have deferred commits.
// We also require that this be an html document served via http.
if (base::FeatureList::IsEnabled(blink::features::kPaintHolding) &&
IsA<HTMLDocument>(document) && Url().ProtocolIsInHTTPFamily() &&
(is_same_origin_initiator ||
base::FeatureList::IsEnabled(
blink::features::kPaintHoldingCrossOrigin))) {
document->SetDeferredCompositorCommitIsAllowed(true);
} else {
document->SetDeferredCompositorCommitIsAllowed(false);
}
// We only report resource timing info to the parent if:
// 1. The navigation is container-initiated (e.g. iframe changed src)
// 2. TAO passed.
if ((response_.ShouldPopulateResourceTiming() ||
is_error_page_for_failed_navigation_) &&
parent_resource_timing_access_ !=
mojom::blink::ParentResourceTimingAccess::kDoNotReport &&
response_.TimingAllowPassed()) {
ResourceResponse response_for_parent(response_);
if (parent_resource_timing_access_ ==
mojom::blink::ParentResourceTimingAccess::
kReportWithoutResponseDetails) {
response_for_parent.SetType(network::mojom::FetchResponseType::kOpaque);
}
DCHECK(frame_->Owner());
DCHECK(GetRequestorOrigin());
resource_timing_info_for_parent_ = CreateResourceTimingInfo(
GetTiming().NavigationStart(), original_url_, &response_for_parent);
resource_timing_info_for_parent_->last_redirect_end_time =
document_load_timing_.RedirectEnd();
}
// TimingAllowPassed only applies to resource
// timing reporting. Navigation timing is always same-origin with the
// document that holds to the timing entry, as navigation timing represents
// the timing of that document itself.
response_.SetTimingAllowPassed(true);
mojom::blink::ResourceTimingInfoPtr navigation_timing_info =
CreateResourceTimingInfo(base::TimeTicks(),
is_error_page_for_failed_navigation_
? pre_redirect_url_for_failed_navigations_
: url_,
&response_);
navigation_timing_info->last_redirect_end_time =
document_load_timing_.RedirectEnd();
DCHECK(frame_->DomWindow());
// TODO(crbug.com/1476866): We should check for protocols and not emit
// performance timeline entries for file protocol navigations.
DOMWindowPerformance::performance(*frame_->DomWindow())
->CreateNavigationTimingInstance(std::move(navigation_timing_info));
{
// Notify the browser process about the commit.
FrameNavigationDisabler navigation_disabler(*frame_);
if (commit_reason_ == CommitReason::kInitialization) {
// There's no observers yet so nothing to notify.
} else if (IsJavaScriptURLOrXSLTCommit()) {
GetLocalFrameClient().DidCommitDocumentReplacementNavigation(this);
} else {
GetLocalFrameClient().DispatchDidCommitLoad(
history_item_.Get(), LoadTypeToCommitType(load_type_),
previous_window != frame_->DomWindow(),
security_init.PermissionsPolicyHeader(),
document_policy_.feature_state);
}
// TODO(dgozman): make DidCreateScriptContext notification call currently
// triggered by installing new document happen here, after commit.
}
// Note: this must be called after DispatchDidCommitLoad() for
// metrics to be correctly sent to the browser process.
if (commit_reason_ != CommitReason::kInitialization)
use_counter_.DidCommitLoad(frame_);
if (IsBackForwardOrRestore(load_type_)) {
if (Page* page = frame_->GetPage())
page->HistoryNavigationVirtualTimePauser().UnpauseVirtualTime();
}
// If profiling is enabled by document policy, ensure that profiling metadata
// is available by tracking the execution context's lifetime.
ProfilerGroup::InitializeIfEnabled(frame_->DomWindow());
if (Url().ProtocolIsInHTTPFamily() && frame_->IsOutermostMainFrame() &&
ShouldEmitNewNavigationHistogram(navigation_type_)) {
base::UmaHistogramTimes(
"Blink.DocumentLoader.CommitNavigationToStartLoadingResponse.Time"
".OutermostMainFrame.NewNavigation.IsHTTPOrHTTPS",
timer.Elapsed());
}
// Load the document if needed.
StartLoadingResponse();
}
void DocumentLoader::CreateParserPostCommit() {
TRACE_EVENT_WITH_FLOW0("loading", "DocumentLoader::CreateParserPostCommit",
TRACE_ID_LOCAL(this),
TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT);
base::ElapsedTimer timer;
SpeculationRulesHeader::ProcessHeadersForDocumentResponse(
response_, *frame_->DomWindow());
if (navigation_delivery_type_ ==
network::mojom::NavigationDeliveryType::kNavigationalPrefetch) {
CountUse(WebFeature::kDocumentLoaderDeliveryTypeNavigationalPrefetch);
}
// DidObserveLoadingBehavior() must be called after DispatchDidCommitLoad() is
// called for the metrics tracking logic to handle it properly.
if (service_worker_network_provider_ &&
service_worker_network_provider_->GetControllerServiceWorkerMode() ==
mojom::blink::ControllerServiceWorkerMode::kControlled) {
LoadingBehaviorFlag loading_behavior =
kLoadingBehaviorServiceWorkerControlled;
if (service_worker_network_provider_->GetFetchHandlerType() !=
mojom::blink::ServiceWorkerFetchHandlerType::kNotSkippable) {
DCHECK_NE(service_worker_network_provider_->GetFetchHandlerType(),
mojom::blink::ServiceWorkerFetchHandlerType::kNoHandler);
// LoadingBehaviorFlag is a bit stream, and `|` should work.
loading_behavior = static_cast<LoadingBehaviorFlag>(
loading_behavior |
kLoadingBehaviorServiceWorkerFetchHandlerSkippable);
}
if (!response_.WasFetchedViaServiceWorker()) {
loading_behavior = static_cast<LoadingBehaviorFlag>(
loading_behavior |
kLoadingBehaviorServiceWorkerMainResourceFetchFallback);
}
if (service_worker_network_provider_->GetFetchHandlerBypassOption() ==
mojom::blink::ServiceWorkerFetchHandlerBypassOption::
kRaceNetworkRequest ||
service_worker_network_provider_->GetFetchHandlerBypassOption() ==
mojom::blink::ServiceWorkerFetchHandlerBypassOption::
kRaceNetworkRequestHoldback) {
loading_behavior = static_cast<LoadingBehaviorFlag>(
loading_behavior | kLoadingBehaviorServiceWorkerRaceNetworkRequest);
}
GetLocalFrameClient().DidObserveLoadingBehavior(loading_behavior);
}
// Links with media values need more information (like viewport information).
// This happens after the first chunk is parsed in HTMLDocumentParser.
DispatchLinkHeaderPreloads(nullptr /* viewport */,
PreloadHelper::LoadLinksFromHeaderMode::
kDocumentAfterCommitWithoutViewport);
// Initializing origin trials might force window proxy initialization,
// which later triggers CHECK when swapping in via WebFrame::Swap().
// We can safely omit installing original trials on initial empty document
// and wait for the real load.
if (commit_reason_ != CommitReason::kInitialization) {
LocalDOMWindow* window = frame_->DomWindow();
if (frame_->GetSettings()
->GetForceTouchEventFeatureDetectionForInspector()) {
window->GetOriginTrialContext()->AddFeature(
mojom::blink::OriginTrialFeature::kTouchEventFeatureDetection);
}
// Enable any origin trials that have been force enabled for this commit.
window->GetOriginTrialContext()->AddForceEnabledTrials(
force_enabled_origin_trials_);
OriginTrialContext::ActivateNavigationFeaturesFromInitiator(
window, &initiator_origin_trial_features_);
}
ParserSynchronizationPolicy parsing_policy = kAllowDeferredParsing;
if (IsJavaScriptURLOrXSLTCommit() ||
Document::ForceSynchronousParsingForTesting()) {
parsing_policy = kForceSynchronousParsing;
}
const AtomicString& encoding = commit_reason_ == CommitReason::kXSLT
? AtomicString("UTF-8")
: response_.TextEncodingName();
Document* document = frame_->GetDocument();
parser_ = document->OpenForNavigation(parsing_policy, MimeType(), encoding);
// XSLT processing converts the response into UTF-8 before sending it through
// the DocumentParser, but we should still report the original encoding when
// script queries it via document.characterSet.
if (commit_reason_ == CommitReason::kXSLT) {
DocumentEncodingData data;
data.SetEncoding(WTF::TextEncoding(response_.TextEncodingName()));
document->SetEncodingData(data);
}
if (frame_ && body_loader_ && !loading_main_document_from_mhtml_archive_ &&
!loading_url_as_empty_document_ && url_.ProtocolIsInHTTPFamily() &&
!is_static_data_ && frame_->IsMainFrame() &&
!document->IsPrefetchOnly() && MimeType() == "text/html") {
parser_->SetIsPreloading(true);
body_loader_->StartLoadingBody(this);
if (!frame_ || !body_loader_)
return;
}
frame_->DomWindow()->GetScriptController().UpdateDocument();
GetFrameLoader().DispatchDidClearDocumentOfWindowObject();
parser_->SetDocumentWasLoadedAsPartOfNavigation();
if (was_discarded_)
document->SetWasDiscarded(true);
document->MaybeHandleHttpRefresh(
response_.HttpHeaderField(http_names::kRefresh),
Document::kHttpRefreshFromHeader);
// The parser may have collected preloads in the background, flush them now.
parser_->FlushPendingPreloads();
if (Url().ProtocolIsInHTTPFamily() && frame_->IsOutermostMainFrame() &&
ShouldEmitNewNavigationHistogram(navigation_type_)) {
base::UmaHistogramTimes(
"Blink.DocumentLoader.CreateParserPostCommit.Time"
".OutermostMainFrame.NewNavigation.IsHTTPOrHTTPS",
timer.Elapsed());
}
}
const AtomicString& DocumentLoader::MimeType() const {
// In the case of mhtml archive, |response_| has an archive mime type,
// while the document has a different mime type.
if (loading_main_document_from_mhtml_archive_) {
if (ArchiveResource* main_resource = archive_->MainResource())
return main_resource->MimeType();
}
return response_.MimeType();
}
void DocumentLoader::BlockParser() {
parser_blocked_count_++;
}
void DocumentLoader::ResumeParser() {
parser_blocked_count_--;
DCHECK_GE(parser_blocked_count_, 0);
if (parser_blocked_count_ != 0)
return;
ProcessDataBuffer();
if (finish_loading_when_parser_resumed_) {
finish_loading_when_parser_resumed_ = false;
parser_->Finish();
parser_.Clear();
}
}
void DocumentLoader::CountUse(mojom::WebFeature feature) {
return use_counter_.Count(feature, GetFrame());
}
void DocumentLoader::CountDeprecation(mojom::WebFeature feature) {
return use_counter_.Count(feature, GetFrame());
}
void DocumentLoader::RecordAcceptLanguageAndContentLanguageMetric() {
// Get document Content-Language value, which has been set as the top-most
// content language value from http head.
constexpr const char language_histogram_name[] =
"LanguageUsage.AcceptLanguageAndContentLanguageUsage";
const AtomicString& content_language =
frame_->GetDocument()->ContentLanguage();
if (!content_language) {
base::UmaHistogramEnumeration(
language_histogram_name,
AcceptLanguageAndContentLanguageUsage::kContentLanguageEmpty);
return;
}
if (content_language == "*") {
base::UmaHistogramEnumeration(
language_histogram_name,
AcceptLanguageAndContentLanguageUsage::kContentLanguageWildcard);
return;
}
// Get Accept-Language header value from Prefs
bool is_accept_language_dirty =
frame_->DomWindow()->navigator()->IsLanguagesDirty();
const Vector<String>& accept_languages =
frame_->DomWindow()->navigator()->languages();
// Match content languages and accept languages list:
// 1. If any value in content languages matches the top-most accept languages
// 2. If there are any overlap between content languages and accept languages
if (accept_languages.front() == content_language) {
base::UmaHistogramEnumeration(
language_histogram_name,
AcceptLanguageAndContentLanguageUsage::
kContentLanguageMatchesPrimaryAcceptLanguage);
}
if (base::Contains(accept_languages, content_language)) {
base::UmaHistogramEnumeration(language_histogram_name,
AcceptLanguageAndContentLanguageUsage::
kContentLanguageMatchesAnyAcceptLanguage);
}
// navigator()->languages() is a potential update operation, it could set
// |is_dirty_language| to false which causes future override operations
// can't update the accep_language list. We should reset the language to
// dirty if accept language is dirty before we read from Prefs.
if (is_accept_language_dirty) {
frame_->DomWindow()->navigator()->SetLanguagesDirty();
}
}
void DocumentLoader::RecordParentAndChildContentLanguageMetric() {
// Check child frame and parent frame content language value.
if (auto* parent = DynamicTo<LocalFrame>(frame_->Tree().Parent())) {
const AtomicString& content_language =
frame_->GetDocument()->ContentLanguage();
const AtomicString& parent_content_language =
parent->GetDocument()->ContentLanguage();
if (parent_content_language != content_language) {
base::UmaHistogramEnumeration(
"LanguageUsage.AcceptLanguageAndContentLanguageUsage",
AcceptLanguageAndContentLanguageUsage::
kContentLanguageSubframeDiffers);
}
}
}
void DocumentLoader::RecordUseCountersForCommit() {
TRACE_EVENT_WITH_FLOW0("loading",
"DocumentLoader::RecordUseCountersForCommit",
TRACE_ID_LOCAL(this),
TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT);
// Pre-commit state, count usage the use counter associated with "this"
// (provisional document loader) instead of frame_'s document loader.
if (response_.DidServiceWorkerNavigationPreload())
CountUse(WebFeature::kServiceWorkerNavigationPreload);
if (frame_->DomWindow()->IsFeatureEnabled(
mojom::blink::DocumentPolicyFeature::kForceLoadAtTop)) {
CountUse(WebFeature::kForceLoadAtTop);
}
AtomicString content_encoding =
response_.HttpHeaderField(http_names::kContentEncoding);
if (content_encoding.LowerASCII() == "zstd") {
CountUse(WebFeature::kZstdContentEncoding);
if (frame_->IsOutermostMainFrame()) {
ukm::builders::MainFrameNavigation_ZstdContentEncoding builder(
ukm_source_id_);
builder.SetUsedZstd(true);
builder.Record(frame_->GetDocument()->UkmRecorder());
}
}
if (response_.DidUseSharedDictionary()) {
CountUse(WebFeature::kSharedDictionaryUsed);
CountUse(WebFeature::kSharedDictionaryUsedForNavigation);
CountUse(frame_->IsOutermostMainFrame()
? WebFeature::kSharedDictionaryUsedForMainFrameNavigation
: WebFeature::kSharedDictionaryUsedForSubFrameNavigation);
if (content_encoding.LowerASCII() ==
network::GetSharedBrotliContentEncodingName()) {
CountUse(WebFeature::kSharedDictionaryUsedWithSharedBrotli);
} else if (content_encoding.LowerASCII() ==
network::GetSharedZstdContentEncodingName()) {
CountUse(WebFeature::kSharedDictionaryUsedWithSharedZstd);
}
}
if (response_.IsSignedExchangeInnerResponse()) {
CountUse(WebFeature::kSignedExchangeInnerResponse);
CountUse(frame_->IsOutermostMainFrame()
? WebFeature::kSignedExchangeInnerResponseInMainFrame
: WebFeature::kSignedExchangeInnerResponseInSubFrame);
}
if (!response_.HttpHeaderField(http_names::kRequireDocumentPolicy).IsNull())
CountUse(WebFeature::kRequireDocumentPolicyHeader);
if (!response_.HttpHeaderField(http_names::kNoVarySearch).IsNull())
CountUse(WebFeature::kNoVarySearch);
if (was_blocked_by_document_policy_)
CountUse(WebFeature::kDocumentPolicyCausedPageUnload);
// Required document policy can either come from iframe attribute or HTTP
// header 'Require-Document-Policy'.
if (!frame_policy_.required_document_policy.empty())
CountUse(WebFeature::kRequiredDocumentPolicy);
FrameClientHintsPreferencesContext hints_context(frame_);
for (const auto& elem : network::GetClientHintToNameMap()) {
const auto& type = elem.first;
if (client_hints_preferences_.ShouldSend(type))
hints_context.CountClientHints(type);
}
if (!early_hints_preloaded_resources_.empty()) {
CountUse(WebFeature::kEarlyHintsPreload);
}
if (frame_->IsOutermostMainFrame() &&
!(Url().User().empty() && Url().Pass().empty())) {
// We're only measuring top-level documents here, as embedded documents
// with credentials are blocked (unless they match the credentials in the
// top-level document).
CountUse(WebFeature::kTopLevelDocumentWithEmbeddedCredentials);
}
#if BUILDFLAG(IS_ANDROID)
// Record whether this window was requested to be opened as a Popup.
// Android doesn't treat popup windows any differently from normal windows
// today, but we might want to change that.
if (frame_->GetPage()->GetWindowFeatures().is_popup) {
CountUse(WebFeature::kWindowOpenedAsPopupOnMobile);
}
#endif
}
void DocumentLoader::RecordConsoleMessagesForCommit() {
if (was_blocked_by_document_policy_) {
// TODO(chenleihu): Add which document policy violated in error string,
// instead of just displaying serialized required document policy.
ConsoleError(
"Refused to display '" + response_.CurrentRequestUrl().ElidedString() +
"' because it violates the following document policy "
"required by its embedder: '" +
DocumentPolicy::Serialize(frame_policy_.required_document_policy)
.value_or("[Serialization Error]")
.c_str() +
"'.");
}
// Report the ResourceResponse now that the new Document has been created and
// console messages will be properly displayed.
frame_->Console().ReportResourceResponseReceived(
this, main_resource_identifier_, response_);
}
void DocumentLoader::ApplyClientHintsConfig(
const WebVector<network::mojom::WebClientHintsType>& enabled_client_hints) {
for (auto ch : enabled_client_hints) {
client_hints_preferences_.SetShouldSend(ch);
}
}
void DocumentLoader::InitializePrefetchedSignedExchangeManager() {
if (params_->prefetched_signed_exchanges.empty())
return;
// |prefetched_signed_exchanges| is set only when the page is loaded from a
// signed exchange.
DCHECK(GetResponse().IsSignedExchangeInnerResponse());
// When the page is loaded from a signed exchange, |last_redirect| must be the
// synthesized redirect for the signed exchange.
DCHECK(params_->redirects.size());
const WebNavigationParams::RedirectInfo& last_redirect =
params_->redirects[params_->redirects.size() - 1];
prefetched_signed_exchange_manager_ =
PrefetchedSignedExchangeManager::MaybeCreate(
GetFrame(),
last_redirect.redirect_response.HttpHeaderField(http_names::kLink),
GetResponse().HttpHeaderField(http_names::kLink),
std::move(params_->prefetched_signed_exchanges));
}
PrefetchedSignedExchangeManager*
DocumentLoader::GetPrefetchedSignedExchangeManager() const {
return prefetched_signed_exchange_manager_.Get();
}
base::TimeDelta DocumentLoader::RemainingTimeToLCPLimit() const {
// We shouldn't call this function before navigation start
DCHECK(!document_load_timing_.NavigationStart().is_null());
base::TimeTicks lcp_limit =
document_load_timing_.NavigationStart() +
base::Milliseconds(
features::kAlignFontDisplayAutoTimeoutWithLCPGoalTimeoutParam.Get());
base::TimeTicks now = clock_->NowTicks();
if (now < lcp_limit)
return lcp_limit - now;
return base::TimeDelta();
}
base::TimeDelta
DocumentLoader::RemainingTimeToRenderBlockingFontMaxBlockingTime() const {
DCHECK(base::FeatureList::IsEnabled(features::kRenderBlockingFonts));
// We shouldn't call this function before navigation start
DCHECK(!document_load_timing_.NavigationStart().is_null());
base::TimeTicks max_blocking_time =
document_load_timing_.NavigationStart() +
base::Milliseconds(
features::kMaxBlockingTimeMsForRenderBlockingFonts.Get());
base::TimeTicks now = clock_->NowTicks();
if (now < max_blocking_time) {
return max_blocking_time - now;
}
return base::TimeDelta();
}
mojom::blink::ContentSecurityNotifier&
DocumentLoader::GetContentSecurityNotifier() {
CHECK(frame_);
if (!content_security_notifier_.is_bound()) {
GetFrame()->Client()->GetBrowserInterfaceBroker().GetInterface(
content_security_notifier_.BindNewPipeAndPassReceiver(
frame_->GetTaskRunner(TaskType::kInternalLoading)));
}
return *content_security_notifier_.get();
}
bool DocumentLoader::ConsumeTextFragmentToken() {
bool token_value = has_text_fragment_token_;
has_text_fragment_token_ = false;
return token_value;
}
void DocumentLoader::NotifyPrerenderingDocumentActivated(
const mojom::blink::PrerenderPageActivationParams& params) {
DCHECK(!frame_->GetDocument()->IsPrerendering());
DCHECK(is_prerendering_);
is_prerendering_ = false;
// A prerendered document won't have user activation, but when it gets moved
// to the primary frame, the primary frame might have sticky user activation.
// In that case, propagate the sticky user activation to the activated
// prerendered document
bool had_sticky_activation =
params.was_user_activated == mojom::blink::WasActivatedOption::kYes;
if (frame_->IsMainFrame() && had_sticky_activation) {
DCHECK(!had_sticky_activation_);
had_sticky_activation_ = had_sticky_activation;
// Update Frame::had_sticky_user_activation_before_nav_. On regular
// navigation, this is updated on DocumentLoader::CommitNavigation, but
// that function is not called on prerender page activation.
DCHECK(!frame_->HadStickyUserActivationBeforeNavigation());
frame_->SetHadStickyUserActivationBeforeNavigation(had_sticky_activation);
// Unlike CommitNavigation, there's no need to call
// HadStickyUserActivationBeforeNavigationChanged here as the browser
// process already knows it.
}
GetTiming().SetActivationStart(params.activation_start);
if (params.view_transition_state) {
CHECK(!view_transition_state_);
view_transition_state_ = std::move(params.view_transition_state);
}
StartViewTransitionIfNeeded(*frame_->GetDocument());
}
HashMap<KURL, EarlyHintsPreloadEntry>
DocumentLoader::GetEarlyHintsPreloadedResources() {
return early_hints_preloaded_resources_;
}
bool DocumentLoader::IsReloadedOrFormSubmitted() const {
switch (navigation_type_) {
case WebNavigationType::kWebNavigationTypeReload:
case WebNavigationType::kWebNavigationTypeFormSubmitted:
case WebNavigationType::kWebNavigationTypeFormResubmittedBackForward:
case WebNavigationType::kWebNavigationTypeFormResubmittedReload:
return true;
default:
return false;
}
}
void DocumentLoader::MaybeRecordServiceWorkerFallbackMainResource(
bool was_subresource_fetched_via_service_worker) {
if (was_subresource_fetched_via_service_worker &&
!response_.WasFetchedViaServiceWorker() &&
service_worker_initial_controller_mode_ ==
mojom::blink::ControllerServiceWorkerMode::kControlled) {
CountUse(WebFeature::kSerivceWorkerFallbackMainResource);
}
}
// static
void DocumentLoader::MaybeStartLoadingBodyInBackground(
WebNavigationBodyLoader* body_loader,
LocalFrame* frame,
const KURL& url,
const ResourceResponse& response) {
if (!body_loader ||
!base::FeatureList::IsEnabled(features::kThreadedBodyLoader) ||
!EqualIgnoringASCIICase(response.MimeType(), "text/html")) {
return;
}
auto* navigation_body_loader = DynamicTo<NavigationBodyLoader>(*body_loader);
if (!navigation_body_loader)
return;
auto decoder = BuildTextResourceDecoder(frame, url, response.MimeType(),
response.TextEncodingName());
navigation_body_loader->StartLoadingBodyInBackground(
std::move(decoder),
// The network inspector needs the raw data.
probe::ToCoreProbeSink(frame)->HasInspectorNetworkAgents());
}
ContentSecurityPolicy* DocumentLoader::CreateCSP() {
ContentSecurityPolicy* csp = MakeGarbageCollected<ContentSecurityPolicy>();
if (GetFrame()->GetSettings()->GetBypassCSP())
return csp; // Empty CSP.
// Add policies from the policy container. If this is a XSLT or javascript:
// document, this will just keep the current policies. If this is a local
// scheme document, the policy container contains the right policies (as
// inherited in the NavigationRequest in the browser). If this is a network
// scheme document, the policy container will contain the parsed CSP from the
// response. If CSP Embedded Enforcement was used on this frame and the
// response allowed blanket enforcement, the policy container includes the
// enforced policy.
csp->AddPolicies(
mojo::Clone(policy_container_->GetPolicies().content_security_policies));
// Check if the embedder wants to add any default policies, and add them.
WebVector<WebContentSecurityPolicyHeader> embedder_default_csp;
Platform::Current()->AppendContentSecurityPolicy(WebURL(Url()),
&embedder_default_csp);
for (const auto& header : embedder_default_csp) {
Vector<network::mojom::blink::ContentSecurityPolicyPtr>
parsed_embedder_policies = ParseContentSecurityPolicies(
header.header_value, header.type, header.source, Url());
policy_container_->AddContentSecurityPolicies(
mojo::Clone(parsed_embedder_policies));
csp->AddPolicies(std::move(parsed_embedder_policies));
}
return csp;
}
bool& GetDisableCodeCacheForTesting() {
static bool disable_code_cache_for_testing = false;
return disable_code_cache_for_testing;
}
CodeCacheHost* DocumentLoader::GetCodeCacheHost() {
if (!code_cache_host_) {
if (GetDisableCodeCacheForTesting()) {
return nullptr;
}
// TODO(crbug.com/1083097) When NavigationThreadingOptimizations feature is
// enabled by default CodeCacheHost interface will be sent along with
// CommitNavigation message and the following code would not be required and
// we should just return nullptr here.
mojo::Remote<mojom::blink::CodeCacheHost> remote;
GetLocalFrameClient().GetBrowserInterfaceBroker().GetInterface(
remote.BindNewPipeAndPassReceiver());
code_cache_host_ = std::make_unique<CodeCacheHost>(std::move(remote));
}
return code_cache_host_.get();
}
scoped_refptr<BackgroundCodeCacheHost>
DocumentLoader::CreateBackgroundCodeCacheHost() {
if (!pending_code_cache_host_for_background_) {
return nullptr;
}
return base::MakeRefCounted<BackgroundCodeCacheHost>(
std::move(pending_code_cache_host_for_background_));
}
mojo::PendingRemote<mojom::blink::CodeCacheHost>
DocumentLoader::CreateWorkerCodeCacheHost() {
if (GetDisableCodeCacheForTesting())
return mojo::NullRemote();
mojo::PendingRemote<mojom::blink::CodeCacheHost> pending_code_cache_host;
GetLocalFrameClient().GetBrowserInterfaceBroker().GetInterface(
pending_code_cache_host.InitWithNewPipeAndPassReceiver());
return pending_code_cache_host;
}
void DocumentLoader::SetCodeCacheHost(
CrossVariantMojoRemote<mojom::blink::CodeCacheHostInterfaceBase>
code_cache_host,
CrossVariantMojoRemote<mojom::blink::CodeCacheHostInterfaceBase>
code_cache_host_for_background) {
code_cache_host_.reset();
// When NavigationThreadingOptimizations feature is disabled, code_cache_host
// can be a nullptr. When this feature is turned off the CodeCacheHost
// interface is requested via BrowserBrokerInterface when required.
if (code_cache_host) {
code_cache_host_ = std::make_unique<CodeCacheHost>(
mojo::Remote<mojom::blink::CodeCacheHost>(std::move(code_cache_host)));
}
pending_code_cache_host_for_background_ =
mojo::PendingRemote<mojom::blink::CodeCacheHost>(
std::move(code_cache_host_for_background));
}
void DocumentLoader::SetSubresourceFilter(
WebDocumentSubresourceFilter* subresource_filter) {
DCHECK(subresource_filter);
subresource_filter_ = MakeGarbageCollected<SubresourceFilter>(
frame_->DomWindow(), base::WrapUnique(subresource_filter));
}
WebDocumentLoader::ExtraData* DocumentLoader::GetExtraData() const {
return extra_data_.get();
}
std::unique_ptr<WebDocumentLoader::ExtraData> DocumentLoader::TakeExtraData() {
return std::move(extra_data_);
}
void DocumentLoader::SetExtraData(std::unique_ptr<ExtraData> extra_data) {
extra_data_ = std::move(extra_data);
}
WebArchiveInfo DocumentLoader::GetArchiveInfo() const {
if (archive_ &&
archive_->LoadResult() == mojom::blink::MHTMLLoadResult::kSuccess) {
return {
archive_->LoadResult(),
archive_->MainResource()->Url(),
archive_->Date(),
};
}
// TODO(arthursonzogni): Returning MHTMLLoadResult::kSuccess when there are no
// archive is very misleading. Consider adding a new enum value to
// discriminate success versus no archive.
return {
archive_ ? archive_->LoadResult()
: mojom::blink::MHTMLLoadResult::kSuccess,
WebURL(),
base::Time(),
};
}
void DocumentLoader::StartViewTransitionIfNeeded(Document& document) {
if (view_transition_state_) {
ViewTransitionSupplement::CreateFromSnapshotForNavigation(
document, std::move(*view_transition_state_));
view_transition_state_.reset();
}
}
bool DocumentLoader::HasLoadedNonInitialEmptyDocument() const {
return GetFrameLoader().HasLoadedNonInitialEmptyDocument();
}
// static
void DocumentLoader::DisableCodeCacheForTesting() {
GetDisableCodeCacheForTesting() = true;
}
void DocumentLoader::UpdateSubresourceLoadMetrics(
const SubresourceLoadMetrics& subresource_load_metrics) {
GetLocalFrameClient().DidObserveSubresourceLoad(subresource_load_metrics);
}
const mojom::RendererContentSettingsPtr& DocumentLoader::GetContentSettings() {
return content_settings_;
}
DEFINE_WEAK_IDENTIFIER_MAP(DocumentLoader)
} // namespace blink