| /* |
| * Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011 Apple Inc. All rights |
| * reserved. |
| * Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies) |
| * Copyright (C) 2008, 2009 Torch Mobile Inc. All rights reserved. |
| * (http://www.torchmobile.com/) |
| * Copyright (C) 2008 Alp Toker <alp@atoker.com> |
| * Copyright (C) Research In Motion Limited 2009. All rights reserved. |
| * Copyright (C) 2011 Kris Jordan <krisjordan@gmail.com> |
| * Copyright (C) 2011 Google Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of |
| * its contributors may be used to endorse or promote products derived |
| * from this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
| * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY |
| * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
| * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
| * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
| * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
| * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "third_party/blink/renderer/core/loader/frame_loader.h" |
| |
| #include <memory> |
| #include <utility> |
| |
| #include "base/auto_reset.h" |
| #include "base/unguessable_token.h" |
| #include "mojo/public/cpp/bindings/pending_remote.h" |
| #include "services/metrics/public/cpp/ukm_builders.h" |
| #include "services/metrics/public/cpp/ukm_recorder.h" |
| #include "services/network/public/cpp/features.h" |
| #include "services/network/public/cpp/web_sandbox_flags.h" |
| #include "services/network/public/mojom/web_sandbox_flags.mojom-blink.h" |
| #include "third_party/blink/public/common/features.h" |
| #include "third_party/blink/public/mojom/fetch/fetch_api_request.mojom-blink.h" |
| #include "third_party/blink/public/mojom/frame/navigation_initiator.mojom-blink.h" |
| #include "third_party/blink/public/mojom/loader/request_context_frame_type.mojom-blink.h" |
| #include "third_party/blink/public/platform/modules/service_worker/web_service_worker_network_provider.h" |
| #include "third_party/blink/public/platform/task_type.h" |
| #include "third_party/blink/public/platform/web_content_settings_client.h" |
| #include "third_party/blink/public/platform/web_url_request.h" |
| #include "third_party/blink/public/web/web_frame_load_type.h" |
| #include "third_party/blink/public/web/web_history_item.h" |
| #include "third_party/blink/public/web/web_navigation_params.h" |
| #include "third_party/blink/renderer/bindings/core/v8/script_controller.h" |
| #include "third_party/blink/renderer/bindings/core/v8/serialization/serialized_script_value.h" |
| #include "third_party/blink/renderer/core/dom/document_init.h" |
| #include "third_party/blink/renderer/core/dom/element.h" |
| #include "third_party/blink/renderer/core/dom/ignore_opens_during_unload_count_incrementer.h" |
| #include "third_party/blink/renderer/core/events/page_transition_event.h" |
| #include "third_party/blink/renderer/core/exported/web_document_loader_impl.h" |
| #include "third_party/blink/renderer/core/exported/web_plugin_container_impl.h" |
| #include "third_party/blink/renderer/core/frame/csp/content_security_policy.h" |
| #include "third_party/blink/renderer/core/frame/csp/csp_source.h" |
| #include "third_party/blink/renderer/core/frame/csp/navigation_initiator_impl.h" |
| #include "third_party/blink/renderer/core/frame/frame_console.h" |
| #include "third_party/blink/renderer/core/frame/local_dom_window.h" |
| #include "third_party/blink/renderer/core/frame/local_frame.h" |
| #include "third_party/blink/renderer/core/frame/local_frame_client.h" |
| #include "third_party/blink/renderer/core/frame/local_frame_view.h" |
| #include "third_party/blink/renderer/core/frame/settings.h" |
| #include "third_party/blink/renderer/core/frame/visual_viewport.h" |
| #include "third_party/blink/renderer/core/frame/web_local_frame_impl.h" |
| #include "third_party/blink/renderer/core/html/forms/html_form_element.h" |
| #include "third_party/blink/renderer/core/html/html_frame_owner_element.h" |
| #include "third_party/blink/renderer/core/html_names.h" |
| #include "third_party/blink/renderer/core/inspector/console_message.h" |
| #include "third_party/blink/renderer/core/inspector/identifiers_factory.h" |
| #include "third_party/blink/renderer/core/loader/appcache/application_cache_host.h" |
| #include "third_party/blink/renderer/core/loader/document_load_timing.h" |
| #include "third_party/blink/renderer/core/loader/document_loader.h" |
| #include "third_party/blink/renderer/core/loader/form_submission.h" |
| #include "third_party/blink/renderer/core/loader/frame_load_request.h" |
| #include "third_party/blink/renderer/core/loader/mixed_content_checker.h" |
| #include "third_party/blink/renderer/core/loader/progress_tracker.h" |
| #include "third_party/blink/renderer/core/page/chrome_client.h" |
| #include "third_party/blink/renderer/core/page/frame_tree.h" |
| #include "third_party/blink/renderer/core/page/page.h" |
| #include "third_party/blink/renderer/core/page/plugin_data.h" |
| #include "third_party/blink/renderer/core/page/plugin_script_forbidden_scope.h" |
| #include "third_party/blink/renderer/core/page/scrolling/fragment_anchor.h" |
| #include "third_party/blink/renderer/core/page/scrolling/scrolling_coordinator.h" |
| #include "third_party/blink/renderer/core/page/scrolling/text_fragment_anchor.h" |
| #include "third_party/blink/renderer/core/page/viewport_description.h" |
| #include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h" |
| #include "third_party/blink/renderer/core/probe/core_probes.h" |
| #include "third_party/blink/renderer/core/scroll/scroll_animator_base.h" |
| #include "third_party/blink/renderer/core/svg/graphics/svg_image.h" |
| #include "third_party/blink/renderer/core/xml/parser/xml_document_parser.h" |
| #include "third_party/blink/renderer/platform/bindings/dom_wrapper_world.h" |
| #include "third_party/blink/renderer/platform/bindings/microtask.h" |
| #include "third_party/blink/renderer/platform/bindings/script_forbidden_scope.h" |
| #include "third_party/blink/renderer/platform/bindings/v8_dom_activity_logger.h" |
| #include "third_party/blink/renderer/platform/exported/wrapped_resource_request.h" |
| #include "third_party/blink/renderer/platform/heap/heap.h" |
| #include "third_party/blink/renderer/platform/instrumentation/instance_counters.h" |
| #include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h" |
| #include "third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h" |
| #include "third_party/blink/renderer/platform/loader/fetch/resource_fetcher_properties.h" |
| #include "third_party/blink/renderer/platform/loader/fetch/resource_request.h" |
| #include "third_party/blink/renderer/platform/mhtml/archive_resource.h" |
| #include "third_party/blink/renderer/platform/mhtml/mhtml_archive.h" |
| #include "third_party/blink/renderer/platform/network/content_security_policy_response_headers.h" |
| #include "third_party/blink/renderer/platform/network/http_parsers.h" |
| #include "third_party/blink/renderer/platform/network/mime/mime_type_registry.h" |
| #include "third_party/blink/renderer/platform/network/network_utils.h" |
| #include "third_party/blink/renderer/platform/scheduler/public/frame_scheduler.h" |
| #include "third_party/blink/renderer/platform/weborigin/security_origin.h" |
| #include "third_party/blink/renderer/platform/weborigin/security_policy.h" |
| #include "third_party/blink/renderer/platform/wtf/assertions.h" |
| #include "third_party/blink/renderer/platform/wtf/text/string_utf8_adaptor.h" |
| #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h" |
| |
| namespace blink { |
| |
| namespace { |
| |
| void ApplyOriginPolicy(ContentSecurityPolicy* csp, |
| const KURL& response_url, |
| const WebOriginPolicy& origin_policy) { |
| // When this function is called. The following lines of code happen |
| // consecutively: |
| // 1) A new empty set of CSP is created. |
| // 2) CSP(s) from the HTTP response are appended. |
| // 3) CSP(s) from the OriginPolicy are appended. [HERE] |
| // |
| // As a result, at the beginning of this function, the set of CSP must not |
| // contain any OriginPolicy's CSP yet. |
| // |
| // TODO(arthursonzogni): HasPolicyFromSource(...) is used only in this DCHECK, |
| // consider removing this function. |
| DCHECK(!csp->HasPolicyFromSource( |
| network::mojom::ContentSecurityPolicySource::kOriginPolicy)); |
| |
| DCHECK(response_url.ProtocolIsInHTTPFamily()); |
| |
| scoped_refptr<SecurityOrigin> self_origin = |
| SecurityOrigin::Create(response_url); |
| |
| for (const auto& policy : origin_policy.content_security_policies) { |
| csp->DidReceiveHeader( |
| policy, *self_origin, |
| network::mojom::ContentSecurityPolicyType::kEnforce, |
| network::mojom::ContentSecurityPolicySource::kOriginPolicy); |
| } |
| |
| for (const auto& policy : |
| origin_policy.content_security_policies_report_only) { |
| csp->DidReceiveHeader( |
| policy, *self_origin, |
| network::mojom::ContentSecurityPolicyType::kReport, |
| network::mojom::ContentSecurityPolicySource::kOriginPolicy); |
| } |
| } |
| |
| } // namespace |
| |
| bool IsBackForwardLoadType(WebFrameLoadType type) { |
| return type == WebFrameLoadType::kBackForward; |
| } |
| |
| bool IsReloadLoadType(WebFrameLoadType type) { |
| return type == WebFrameLoadType::kReload || |
| type == WebFrameLoadType::kReloadBypassingCache; |
| } |
| |
| bool FrameLoader::NeedsHistoryItemRestore(WebFrameLoadType type) { |
| return type == WebFrameLoadType::kBackForward || IsReloadLoadType(type); |
| } |
| |
| ResourceRequest FrameLoader::ResourceRequestForReload( |
| WebFrameLoadType frame_load_type, |
| ClientRedirectPolicy client_redirect_policy) { |
| DCHECK(IsReloadLoadType(frame_load_type)); |
| const auto cache_mode = |
| frame_load_type == WebFrameLoadType::kReloadBypassingCache |
| ? mojom::FetchCacheMode::kBypassCache |
| : mojom::FetchCacheMode::kValidateCache; |
| if (!document_loader_ || !document_loader_->GetHistoryItem()) |
| return ResourceRequest(); |
| |
| ResourceRequest request = |
| document_loader_->GetHistoryItem()->GenerateResourceRequest(cache_mode); |
| request.SetRequestorOrigin(frame_->GetSecurityContext()->GetSecurityOrigin()); |
| |
| // ClientRedirectPolicy is an indication that this load was triggered by some |
| // direct interaction with the page. If this reload is not a client redirect, |
| // we should reuse the referrer from the original load of the current |
| // document. If this reload is a client redirect (e.g., location.reload()), it |
| // was initiated by something in the current document and should therefore |
| // show the current document's url as the referrer. |
| if (client_redirect_policy == ClientRedirectPolicy::kClientRedirect) { |
| LocalDOMWindow* window = frame_->DomWindow(); |
| Referrer referrer = SecurityPolicy::GenerateReferrer( |
| window->GetReferrerPolicy(), window->Url(), window->OutgoingReferrer()); |
| request.SetReferrerString(referrer.referrer); |
| request.SetReferrerPolicy(referrer.referrer_policy); |
| } |
| |
| request.SetSkipServiceWorker(frame_load_type == |
| WebFrameLoadType::kReloadBypassingCache); |
| return request; |
| } |
| |
| FrameLoader::FrameLoader(LocalFrame* frame) |
| : frame_(frame), |
| progress_tracker_(MakeGarbageCollected<ProgressTracker>(frame)), |
| dispatching_did_clear_window_object_in_main_world_(false), |
| virtual_time_pauser_( |
| frame_->GetFrameScheduler()->CreateWebScopedVirtualTimePauser( |
| "FrameLoader", |
| WebScopedVirtualTimePauser::VirtualTaskDuration::kInstant)) { |
| DCHECK(frame_); |
| |
| TRACE_EVENT_OBJECT_CREATED_WITH_ID("loading", "FrameLoader", this); |
| TakeObjectSnapshot(); |
| } |
| |
| FrameLoader::~FrameLoader() { |
| DCHECK_EQ(state_, State::kDetached); |
| } |
| |
| void FrameLoader::Trace(Visitor* visitor) const { |
| visitor->Trace(frame_); |
| visitor->Trace(progress_tracker_); |
| visitor->Trace(document_loader_); |
| visitor->Trace(last_origin_window_csp_); |
| } |
| |
| void FrameLoader::Init() { |
| ScriptForbiddenScope forbid_scripts; |
| |
| // Load the initial empty document: |
| auto navigation_params = std::make_unique<WebNavigationParams>(); |
| navigation_params->url = KURL(g_empty_string); |
| navigation_params->frame_policy = |
| frame_->Owner() ? frame_->Owner()->GetFramePolicy() : FramePolicy(); |
| navigation_params->frame_policy->sandbox_flags = |
| PendingEffectiveSandboxFlags(); |
| |
| DocumentLoader* new_document_loader = Client()->CreateDocumentLoader( |
| frame_, kWebNavigationTypeOther, CreateCSPForInitialEmptyDocument(), |
| std::move(navigation_params), nullptr /* extra_data */); |
| |
| CommitDocumentLoader(new_document_loader, base::nullopt, nullptr, |
| CommitReason::kInitialization); |
| |
| frame_->GetDocument()->CancelParsing(); |
| |
| // Suppress finish notifications for initial empty documents, since they don't |
| // generate start notifications. |
| document_loader_->SetSentDidFinishLoad(); |
| // Ensure that the frame sees the correct page lifecycle state. |
| frame_->OnPageLifecycleStateUpdated(); |
| |
| TakeObjectSnapshot(); |
| |
| state_ = State::kInitialized; |
| } |
| |
| LocalFrameClient* FrameLoader::Client() const { |
| return frame_->Client(); |
| } |
| |
| void FrameLoader::SetDefersLoading(WebURLLoader::DeferType defers) { |
| if (frame_->GetDocument()) |
| frame_->GetDocument()->Fetcher()->SetDefersLoading(defers); |
| if (document_loader_) |
| document_loader_->SetDefersLoading(defers); |
| } |
| |
| void FrameLoader::SaveScrollAnchor() { |
| if (!document_loader_ || !document_loader_->GetHistoryItem() || |
| !frame_->View()) |
| return; |
| |
| // Shouldn't clobber anything if we might still restore later. |
| if (NeedsHistoryItemRestore(document_loader_->LoadType()) && |
| !document_loader_->GetInitialScrollState().was_scrolled_by_user) |
| return; |
| |
| HistoryItem* history_item = document_loader_->GetHistoryItem(); |
| if (ScrollableArea* layout_scrollable_area = |
| frame_->View()->LayoutViewport()) { |
| ScrollAnchor* scroll_anchor = layout_scrollable_area->GetScrollAnchor(); |
| DCHECK(scroll_anchor); |
| |
| const SerializedAnchor& serialized_anchor = |
| scroll_anchor->GetSerializedAnchor(); |
| if (serialized_anchor.IsValid()) { |
| history_item->SetScrollAnchorData( |
| {serialized_anchor.selector, |
| gfx::PointF(serialized_anchor.relative_offset.X(), |
| serialized_anchor.relative_offset.Y()), |
| serialized_anchor.simhash}); |
| } |
| } |
| } |
| |
| void FrameLoader::SaveScrollState() { |
| if (!document_loader_ || !document_loader_->GetHistoryItem() || |
| !frame_->View()) |
| return; |
| |
| // Shouldn't clobber anything if we might still restore later. |
| if (NeedsHistoryItemRestore(document_loader_->LoadType()) && |
| !document_loader_->GetInitialScrollState().was_scrolled_by_user) |
| return; |
| |
| HistoryItem* history_item = document_loader_->GetHistoryItem(); |
| // For performance reasons, we don't save scroll anchors as often as we save |
| // scroll offsets. In order to avoid keeping around a stale anchor, we clear |
| // it when the saved scroll offset changes. |
| history_item->SetScrollAnchorData(ScrollAnchorData()); |
| if (ScrollableArea* layout_scrollable_area = frame_->View()->LayoutViewport()) |
| history_item->SetScrollOffset(layout_scrollable_area->GetScrollOffset()); |
| history_item->SetVisualViewportScrollOffset(ToScrollOffset( |
| frame_->GetPage()->GetVisualViewport().VisibleRect().Location())); |
| |
| if (frame_->IsMainFrame()) |
| history_item->SetPageScaleFactor(frame_->GetPage()->PageScaleFactor()); |
| |
| Client()->DidUpdateCurrentHistoryItem(); |
| } |
| |
| void FrameLoader::DispatchUnloadEvent( |
| SecurityOrigin* committing_origin, |
| base::Optional<Document::UnloadEventTiming>* timing) { |
| FrameNavigationDisabler navigation_disabler(*frame_); |
| SaveScrollState(); |
| |
| if (!SVGImage::IsInSVGImage(frame_->GetDocument())) |
| frame_->GetDocument()->DispatchUnloadEvents(committing_origin, timing); |
| } |
| |
| void FrameLoader::DidExplicitOpen() { |
| probe::DidOpenDocument(frame_, GetDocumentLoader()); |
| if (empty_document_status_ == EmptyDocumentStatus::kOnlyEmpty) |
| empty_document_status_ = EmptyDocumentStatus::kOnlyEmptyButExplicitlyOpened; |
| |
| // Only model a document.open() as part of a navigation if its parent is not |
| // done or in the process of completing. |
| if (Frame* parent = frame_->Tree().Parent()) { |
| auto* parent_local_frame = DynamicTo<LocalFrame>(parent); |
| if ((parent_local_frame && |
| parent_local_frame->GetDocument()->LoadEventStillNeeded()) || |
| (parent->IsRemoteFrame() && parent->IsLoading())) { |
| progress_tracker_->ProgressStarted(); |
| } |
| } |
| } |
| |
| void FrameLoader::FinishedParsing() { |
| if (state_ == State::kUninitialized) |
| return; |
| |
| progress_tracker_->FinishedParsing(); |
| |
| frame_->GetLocalFrameHostRemote().DidFinishDocumentLoad(); |
| |
| if (Client()) { |
| ScriptForbiddenScope forbid_scripts; |
| Client()->DispatchDidFinishDocumentLoad(); |
| } |
| |
| if (Client()) { |
| Client()->RunScriptsAtDocumentReady( |
| document_loader_ ? document_loader_->IsCommittedButEmpty() : true); |
| } |
| |
| if (frame_->View()) { |
| ProcessFragment(frame_->GetDocument()->Url(), document_loader_->LoadType(), |
| kNavigationToDifferentDocument); |
| } |
| |
| frame_->GetDocument()->CheckCompleted(); |
| } |
| |
| // TODO(dgozman): we are calling this method too often, hoping that it |
| // does not do anything when navigation is in progress, or when loading |
| // has finished already. We should call it at the right times. |
| void FrameLoader::DidFinishNavigation(NavigationFinishState state) { |
| // Only declare the whole frame finished if the committed navigation is done |
| // and there is no provisional navigation in progress. |
| if ((document_loader_ && !document_loader_->SentDidFinishLoad()) || |
| HasProvisionalNavigation()) { |
| return; |
| } |
| |
| // This code in this block is meant to prepare a document for display, but |
| // this code may also run when swapping out a provisional frame. In that case, |
| // skip the display work. |
| if (frame_->IsLoading() && !frame_->IsProvisional()) { |
| progress_tracker_->ProgressCompleted(); |
| // Retry restoring scroll offset since finishing loading disables content |
| // size clamping. |
| RestoreScrollPositionAndViewState(); |
| if (document_loader_) |
| document_loader_->SetLoadType(WebFrameLoadType::kStandard); |
| frame_->FinishedLoading(state); |
| } |
| |
| // When a subframe finishes loading, the parent should check if *all* |
| // subframes have finished loading (which may mean that the parent can declare |
| // that the parent itself has finished loading). This local-subframe-focused |
| // code has a remote-subframe equivalent in |
| // WebRemoteFrameImpl::DidStopLoading. |
| Frame* parent = frame_->Tree().Parent(); |
| if (parent) |
| parent->CheckCompleted(); |
| } |
| |
| Frame* FrameLoader::Opener() { |
| return frame_->Opener(); |
| } |
| |
| void FrameLoader::SetOpener(LocalFrame* opener) { |
| // If the frame is already detached, the opener has already been cleared. |
| frame_->SetOpener(opener); |
| } |
| |
| bool FrameLoader::AllowPlugins() { |
| // With Oilpan, a FrameLoader might be accessed after the Page has been |
| // detached. FrameClient will not be accessible, so bail early. |
| if (!Client()) |
| return false; |
| Settings* settings = frame_->GetSettings(); |
| return settings && settings->GetPluginsEnabled(); |
| } |
| |
| void FrameLoader::DetachDocumentLoader(Member<DocumentLoader>& loader, |
| bool flush_microtask_queue) { |
| if (!loader) |
| return; |
| |
| FrameNavigationDisabler navigation_disabler(*frame_); |
| loader->DetachFromFrame(flush_microtask_queue); |
| loader = nullptr; |
| } |
| |
| void FrameLoader::DidFinishSameDocumentNavigation( |
| const KURL& url, |
| WebFrameLoadType frame_load_type, |
| HistoryItem* history_item) { |
| // If we have a state object, we cannot also be a new navigation. |
| scoped_refptr<SerializedScriptValue> state_object = |
| history_item ? history_item->StateObject() : nullptr; |
| DCHECK(!state_object || frame_load_type == WebFrameLoadType::kBackForward); |
| |
| // onpopstate might change view state, so stash for later restore. |
| base::Optional<HistoryItem::ViewState> view_state; |
| if (history_item) { |
| view_state = history_item->GetViewState(); |
| } |
| |
| frame_->DomWindow()->StatePopped(state_object |
| ? std::move(state_object) |
| : SerializedScriptValue::NullValue()); |
| |
| if (view_state) { |
| RestoreScrollPositionAndViewState(frame_load_type, *view_state, |
| history_item->ScrollRestorationType()); |
| } |
| |
| // We need to scroll to the fragment whether or not a hash change occurred, |
| // since the user might have scrolled since the previous navigation. |
| ProcessFragment(url, frame_load_type, kNavigationWithinSameDocument); |
| |
| TakeObjectSnapshot(); |
| } |
| |
| WebFrameLoadType FrameLoader::DetermineFrameLoadType( |
| const KURL& url, |
| const AtomicString& http_method, |
| bool has_origin_window, |
| const KURL& failing_url, |
| WebFrameLoadType frame_load_type) { |
| // TODO(dgozman): this method is rewriting the load type, which makes it hard |
| // to reason about various navigations and their desired load type. We should |
| // untangle it and detect the load type at the proper place. See, for example, |
| // location.assign() block below. |
| // Achieving that is complicated due to similar conditions in many places |
| // both in the renderer and in the browser. |
| if (frame_load_type == WebFrameLoadType::kStandard || |
| frame_load_type == WebFrameLoadType::kReplaceCurrentItem) { |
| if (frame_->Tree().Parent() && |
| empty_document_status_ == EmptyDocumentStatus::kOnlyEmpty) { |
| return WebFrameLoadType::kReplaceCurrentItem; |
| } |
| if (!frame_->Tree().Parent() && !Client()->BackForwardLength()) { |
| if (Opener() && url.IsEmpty()) |
| return WebFrameLoadType::kReplaceCurrentItem; |
| return WebFrameLoadType::kStandard; |
| } |
| } |
| if (frame_load_type != WebFrameLoadType::kStandard) |
| return frame_load_type; |
| |
| if (url == document_loader_->UrlForHistory()) { |
| if (http_method == http_names::kPOST) |
| return WebFrameLoadType::kStandard; |
| if (!has_origin_window) |
| return WebFrameLoadType::kReload; |
| return WebFrameLoadType::kReplaceCurrentItem; |
| } |
| |
| if (failing_url == document_loader_->UrlForHistory() && |
| document_loader_->LoadType() == WebFrameLoadType::kReload) |
| return WebFrameLoadType::kReload; |
| |
| if (url.IsEmpty() && failing_url.IsEmpty()) { |
| return WebFrameLoadType::kReplaceCurrentItem; |
| } |
| |
| return WebFrameLoadType::kStandard; |
| } |
| |
| bool FrameLoader::AllowRequestForThisFrame(const FrameLoadRequest& request) { |
| // If no origin Document* was specified, skip remaining security checks and |
| // assume the caller has fully initialized the FrameLoadRequest. |
| if (!request.GetOriginWindow()) |
| return true; |
| |
| const KURL& url = request.GetResourceRequest().Url(); |
| if (url.ProtocolIsJavaScript()) { |
| // Check the CSP of the caller (the "source browsing context") if required, |
| // as per https://html.spec.whatwg.org/C/#javascript-protocol. |
| bool javascript_url_is_allowed = |
| request.GetOriginWindow() |
| ->GetContentSecurityPolicyForWorld(request.JavascriptWorld().get()) |
| ->AllowInline(ContentSecurityPolicy::InlineType::kNavigation, |
| frame_->DeprecatedLocalOwner(), url.GetString(), |
| String() /* nonce */, |
| request.GetOriginWindow()->Url(), |
| OrdinalNumber::First()); |
| |
| if (!javascript_url_is_allowed) |
| return false; |
| |
| if (frame_->Owner() && ((frame_->Owner()->GetFramePolicy().sandbox_flags & |
| network::mojom::blink::WebSandboxFlags::kOrigin) != |
| network::mojom::blink::WebSandboxFlags::kNone)) { |
| return false; |
| } |
| } |
| |
| if (!request.CanDisplay(url)) { |
| request.GetOriginWindow()->AddConsoleMessage( |
| MakeGarbageCollected<ConsoleMessage>( |
| mojom::ConsoleMessageSource::kSecurity, |
| mojom::ConsoleMessageLevel::kError, |
| "Not allowed to load local resource: " + url.ElidedString())); |
| return false; |
| } |
| return true; |
| } |
| |
| static WebNavigationType DetermineNavigationType( |
| WebFrameLoadType frame_load_type, |
| bool is_form_submission, |
| bool have_event) { |
| bool is_reload = IsReloadLoadType(frame_load_type); |
| bool is_back_forward = IsBackForwardLoadType(frame_load_type); |
| if (is_form_submission) { |
| return (is_reload || is_back_forward) ? kWebNavigationTypeFormResubmitted |
| : kWebNavigationTypeFormSubmitted; |
| } |
| if (have_event) |
| return kWebNavigationTypeLinkClicked; |
| if (is_reload) |
| return kWebNavigationTypeReload; |
| if (is_back_forward) |
| return kWebNavigationTypeBackForward; |
| return kWebNavigationTypeOther; |
| } |
| |
| static mojom::blink::RequestContextType |
| DetermineRequestContextFromNavigationType( |
| const WebNavigationType navigation_type) { |
| switch (navigation_type) { |
| case kWebNavigationTypeLinkClicked: |
| return mojom::blink::RequestContextType::HYPERLINK; |
| |
| case kWebNavigationTypeOther: |
| return mojom::blink::RequestContextType::LOCATION; |
| |
| case kWebNavigationTypeFormResubmitted: |
| case kWebNavigationTypeFormSubmitted: |
| return mojom::blink::RequestContextType::FORM; |
| |
| case kWebNavigationTypeBackForward: |
| case kWebNavigationTypeReload: |
| return mojom::blink::RequestContextType::INTERNAL; |
| } |
| NOTREACHED(); |
| return mojom::blink::RequestContextType::HYPERLINK; |
| } |
| |
| static network::mojom::RequestDestination |
| DetermineRequestDestinationFromNavigationType( |
| const WebNavigationType navigation_type) { |
| switch (navigation_type) { |
| case kWebNavigationTypeLinkClicked: |
| case kWebNavigationTypeOther: |
| case kWebNavigationTypeFormResubmitted: |
| case kWebNavigationTypeFormSubmitted: |
| return network::mojom::RequestDestination::kDocument; |
| case kWebNavigationTypeBackForward: |
| case kWebNavigationTypeReload: |
| return network::mojom::RequestDestination::kEmpty; |
| } |
| NOTREACHED(); |
| return network::mojom::RequestDestination::kDocument; |
| } |
| |
| void FrameLoader::StartNavigation(FrameLoadRequest& request, |
| WebFrameLoadType frame_load_type) { |
| CHECK(!IsBackForwardLoadType(frame_load_type)); |
| DCHECK(request.GetTriggeringEventInfo() != |
| mojom::blink::TriggeringEventInfo::kUnknown); |
| DCHECK(frame_->GetDocument()); |
| if (HTMLFrameOwnerElement* element = frame_->DeprecatedLocalOwner()) |
| element->CancelPendingLazyLoad(); |
| |
| ResourceRequest& resource_request = request.GetResourceRequest(); |
| const KURL& url = resource_request.Url(); |
| LocalDOMWindow* origin_window = request.GetOriginWindow(); |
| |
| TRACE_EVENT2("navigation", "FrameLoader::StartNavigation", "url", |
| url.GetString().Utf8(), "load_type", |
| static_cast<int>(frame_load_type)); |
| |
| resource_request.SetHasUserGesture( |
| LocalFrame::HasTransientUserActivation(frame_)); |
| |
| if (!AllowRequestForThisFrame(request)) |
| return; |
| |
| // Block renderer-initiated loads of data: and filesystem: URLs in the top |
| // frame. |
| // |
| // If the mime type of the data URL is supported, the URL will |
| // eventually be rendered, so block it here. Otherwise, the load might be |
| // handled by a plugin or end up as a download, so allow it to let the |
| // embedder figure out what to do with it. Navigations to filesystem URLs are |
| // always blocked here. |
| if (frame_->IsMainFrame() && origin_window && |
| !frame_->Client()->AllowContentInitiatedDataUrlNavigations( |
| origin_window->Url()) && |
| (url.ProtocolIs("filesystem") || |
| (url.ProtocolIsData() && |
| network_utils::IsDataURLMimeTypeSupported(url)))) { |
| frame_->GetDocument()->AddConsoleMessage( |
| MakeGarbageCollected<ConsoleMessage>( |
| mojom::ConsoleMessageSource::kSecurity, |
| mojom::ConsoleMessageLevel::kError, |
| "Not allowed to navigate top frame to " + url.Protocol() + |
| " URL: " + url.ElidedString())); |
| return; |
| } |
| |
| // TODO(dgozman): merge page dismissal check and FrameNavigationDisabler. |
| if (!frame_->IsNavigationAllowed() || |
| frame_->GetDocument()->PageDismissalEventBeingDispatched() != |
| Document::kNoDismissal) { |
| return; |
| } |
| |
| frame_load_type = DetermineFrameLoadType( |
| resource_request.Url(), resource_request.HttpMethod(), origin_window, |
| KURL(), frame_load_type); |
| |
| bool same_document_navigation = |
| request.GetNavigationPolicy() == kNavigationPolicyCurrentTab && |
| ShouldPerformFragmentNavigation( |
| request.Form(), resource_request.HttpMethod(), frame_load_type, url); |
| |
| // Perform same document navigation. |
| if (same_document_navigation) { |
| document_loader_->CommitSameDocumentNavigation( |
| url, frame_load_type, nullptr, request.ClientRedirect(), |
| resource_request.HasUserGesture(), origin_window, |
| request.GetTriggeringEventInfo() != |
| mojom::blink::TriggeringEventInfo::kNotFromEvent, |
| nullptr /* extra_data */); |
| return; |
| } |
| |
| // If we're navigating and there's still a text fragment permission token on |
| // the document loader, it means this navigation didn't try to invoke a text |
| // fragment. In this case, we want to propagate this to the next document to |
| // allow text-fragments across client-side redirects. |
| bool text_fragment_token = GetDocumentLoader()->ConsumeTextFragmentToken(); |
| |
| resource_request.SetHasTextFragmentToken(text_fragment_token); |
| |
| WebNavigationType navigation_type = DetermineNavigationType( |
| frame_load_type, resource_request.HttpBody() || request.Form(), |
| request.GetTriggeringEventInfo() != |
| mojom::blink::TriggeringEventInfo::kNotFromEvent); |
| mojom::blink::RequestContextType request_context_type = |
| DetermineRequestContextFromNavigationType(navigation_type); |
| |
| // TODO(lyf): handle `frame` context type. https://crbug.com/1019716 |
| if (mojom::blink::RequestContextType::LOCATION == request_context_type && |
| !frame_->IsMainFrame()) { |
| request_context_type = mojom::blink::RequestContextType::IFRAME; |
| } |
| resource_request.SetRequestContext(request_context_type); |
| resource_request.SetRequestDestination( |
| DetermineRequestDestinationFromNavigationType(navigation_type)); |
| request.SetFrameType(frame_->IsMainFrame() |
| ? mojom::RequestContextFrameType::kTopLevel |
| : mojom::RequestContextFrameType::kNested); |
| |
| mojo::PendingRemote<mojom::blink::NavigationInitiator> navigation_initiator; |
| WTF::Vector<network::mojom::blink::ContentSecurityPolicyPtr> initiator_csp; |
| if (origin_window && origin_window->GetContentSecurityPolicy() |
| ->ExperimentalFeaturesEnabled()) { |
| ContentSecurityPolicy* origin_window_csp = |
| origin_window->GetContentSecurityPolicy(); |
| initiator_csp = mojo::Clone(origin_window_csp->GetParsedPolicies()); |
| NavigationInitiatorImpl::From(*origin_window) |
| .BindReceiver(navigation_initiator.InitWithNewPipeAndPassReceiver()); |
| } |
| |
| // Record the document that has initiated this navigation. It will be used at |
| // navigation commit time for inheritance. |
| // TODO(arthursonzogni): This looks very fragile. It seems easy to confuse the |
| // FrameLoader by starting several navigations in a row. We should get rid of |
| // this. |
| last_origin_window_csp_ = MakeGarbageCollected<ContentSecurityPolicy>(); |
| if (origin_window && origin_window->GetContentSecurityPolicy()) { |
| last_origin_window_csp_->CopyStateFrom( |
| origin_window->GetContentSecurityPolicy()); |
| } |
| |
| // TODO(arthursonzogni): 'frame-src' check is disabled on the |
| // renderer side, but is enforced on the browser side. |
| // See http://crbug.com/692595 for understanding why it |
| // can't be enforced on both sides instead. |
| |
| // 'form-action' check in the frame that is navigating is disabled on the |
| // renderer side, but is enforced on the browser side instead. |
| // N.B. check in the frame that initiates the navigation stills occurs in |
| // blink and is not enforced on the browser-side. |
| // TODO(arthursonzogni) The 'form-action' check should be fully disabled |
| // in blink, except when the form submission doesn't trigger a navigation |
| // (i.e. javascript urls). Please see https://crbug.com/701749. |
| |
| // Report-only CSP headers are checked in browser. |
| const FetchClientSettingsObject* fetch_client_settings_object = nullptr; |
| if (origin_window) { |
| fetch_client_settings_object = &origin_window->Fetcher() |
| ->GetProperties() |
| .GetFetchClientSettingsObject(); |
| } |
| ModifyRequestForCSP(resource_request, fetch_client_settings_object, |
| origin_window, request.GetFrameType()); |
| |
| DCHECK(Client()->HasWebView()); |
| // Check for non-escaped new lines in the url. |
| if (url.PotentiallyDanglingMarkup() && url.ProtocolIsInHTTPFamily()) { |
| Deprecation::CountDeprecation( |
| origin_window, WebFeature::kCanRequestURLHTTPContainingNewline); |
| return; |
| } |
| |
| if (url.ProtocolIsJavaScript()) { |
| if (!origin_window || |
| origin_window->CanExecuteScripts(kAboutToExecuteScript)) { |
| frame_->GetDocument()->ProcessJavaScriptUrl(url, |
| request.JavascriptWorld()); |
| } |
| return; |
| } |
| |
| if (frame_->IsMainFrame()) |
| LocalFrame::ConsumeTransientUserActivation(frame_); |
| |
| // The main resource request gets logged here, because V8DOMActivityLogger |
| // is looked up based on the current v8::Context. When the request actually |
| // begins, the v8::Context may no longer be on the stack. |
| if (V8DOMActivityLogger* activity_logger = |
| V8DOMActivityLogger::CurrentActivityLoggerIfIsolatedWorld()) { |
| if (!DocumentLoader::WillLoadUrlAsEmpty(url)) { |
| Vector<String> argv; |
| argv.push_back("Main resource"); |
| argv.push_back(url.GetString()); |
| activity_logger->LogEvent("blinkRequestResource", argv.size(), |
| argv.data()); |
| } |
| } |
| |
| if (request.ClientRedirectReason() != ClientNavigationReason::kNone) { |
| probe::FrameRequestedNavigation(frame_, frame_, url, |
| request.ClientRedirectReason(), |
| request.GetNavigationPolicy()); |
| } |
| |
| const network::mojom::IPAddressSpace initiator_address_space = |
| origin_window ? origin_window->AddressSpace() |
| : network::mojom::IPAddressSpace::kUnknown; |
| |
| // TODO(crbug.com/896041): Instead of just bypassing the CSP for navigations |
| // from isolated world, ideally we should enforce the isolated world CSP by |
| // plumbing the correct CSP to the browser. |
| using CSPDisposition = network::mojom::CSPDisposition; |
| CSPDisposition should_check_main_world_csp = |
| ContentSecurityPolicy::ShouldBypassMainWorld( |
| request.JavascriptWorld().get()) |
| ? CSPDisposition::DO_NOT_CHECK |
| : CSPDisposition::CHECK; |
| |
| // If this is a subframe load to a urn: URL, allow loading from a Web Bundle |
| // attached to the parent document. |
| if (url.Protocol() == "urn") { |
| auto* parent_local_frame = DynamicTo<LocalFrame>(frame_->Tree().Parent()); |
| if (parent_local_frame && |
| parent_local_frame->DomWindow() == origin_window && |
| origin_window->Fetcher()) { |
| origin_window->Fetcher()->AttachWebBundleTokenIfNeeded(resource_request); |
| } |
| } |
| |
| Client()->BeginNavigation( |
| resource_request, request.GetFrameType(), origin_window, |
| nullptr /* document_loader */, navigation_type, |
| request.GetNavigationPolicy(), frame_load_type, |
| request.ClientRedirect() == ClientRedirectPolicy::kClientRedirect, |
| request.GetTriggeringEventInfo(), request.Form(), |
| should_check_main_world_csp, request.GetBlobURLToken(), |
| request.GetInputStartTime(), request.HrefTranslate().GetString(), |
| request.Impression(), std::move(initiator_csp), initiator_address_space, |
| std::move(navigation_initiator), request.GetInitiatorFrameToken(), |
| request.TakeInitiatorPolicyContainerKeepAliveHandle()); |
| } |
| |
| static void FillStaticResponseIfNeeded(WebNavigationParams* params, |
| LocalFrame* frame) { |
| if (params->is_static_data) |
| return; |
| |
| const KURL& url = params->url; |
| // See WebNavigationParams for special case explanations. |
| if (url.IsAboutSrcdocURL()) { |
| // TODO(dgozman): instead of reaching to the owner here, we could instead: |
| // - grab the "srcdoc" value when starting a navigation right in the owner; |
| // - pass it around through BeginNavigation to CommitNavigation as |data|; |
| // - use it here instead of re-reading from the owner. |
| // This way we will get rid of extra dependency between starting and |
| // committing navigation. |
| String srcdoc; |
| HTMLFrameOwnerElement* owner_element = frame->DeprecatedLocalOwner(); |
| if (!IsA<HTMLIFrameElement>(owner_element) || |
| !owner_element->FastHasAttribute(html_names::kSrcdocAttr)) { |
| // Cannot retrieve srcdoc content anymore (perhaps, the attribute was |
| // cleared) - load empty instead. |
| } else { |
| srcdoc = owner_element->FastGetAttribute(html_names::kSrcdocAttr); |
| DCHECK(!srcdoc.IsNull()); |
| } |
| WebNavigationParams::FillStaticResponse(params, "text/html", "UTF-8", |
| StringUTF8Adaptor(srcdoc)); |
| return; |
| } |
| |
| MHTMLArchive* archive = nullptr; |
| if (auto* parent = DynamicTo<LocalFrame>(frame->Tree().Parent())) |
| archive = parent->Loader().GetDocumentLoader()->Archive(); |
| if (archive && !url.ProtocolIsData()) { |
| // If we have an archive loaded in some ancestor frame, we should |
| // retrieve document content from that archive. This is different from |
| // loading an archive into this frame, which will be handled separately |
| // once we load the body and parse it as an archive. |
| params->body_loader.reset(); |
| ArchiveResource* archive_resource = archive->SubresourceForURL(url); |
| if (archive_resource) { |
| SharedBuffer* archive_data = archive_resource->Data(); |
| WebNavigationParams::FillStaticResponse( |
| params, archive_resource->MimeType(), |
| archive_resource->TextEncoding(), |
| base::make_span(archive_data->Data(), archive_data->size())); |
| } else { |
| // The requested archive resource does not exist. In an ideal world, this |
| // would commit as a failed navigation, but the browser doesn't know |
| // anything about what resources are available in the archive. Just |
| // synthesize an empty document so that something commits still. |
| // TODO(https://crbug.com/1112965): remove these special cases by adding |
| // an URLLoaderFactory implementation for MHTML archives. |
| WebNavigationParams::FillStaticResponse( |
| params, "text/html", "UTF-8", |
| "<html><body>" |
| "<!-- failed to find resource in MHTML archive -->" |
| "</body></html>"); |
| } |
| } |
| |
| // Checking whether a URL would load as empty (e.g. about:blank) must be done |
| // after checking for content with the corresponding URL in the MHTML archive, |
| // since MHTML archives can define custom content to load for about:blank... |
| // |
| // Note that no static response needs to be filled here; instead, this is |
| // synthesised later by `DocumentLoader::InitializeEmptyResponse()`. |
| if (DocumentLoader::WillLoadUrlAsEmpty(params->url)) |
| return; |
| |
| const String& mime_type = params->response.MimeType(); |
| if (MIMETypeRegistry::IsSupportedMIMEType(mime_type)) |
| return; |
| |
| PluginData* plugin_data = frame->GetPluginData(); |
| if (!mime_type.IsEmpty() && plugin_data && |
| plugin_data->SupportsMimeType(mime_type)) { |
| return; |
| } |
| |
| // Typically, PlzNavigate checks that the MIME type can be handled on the |
| // browser side before sending it to the renderer. However, there are rare |
| // scenarios where it's possible for the renderer to send a commit request |
| // with a MIME type the renderer cannot handle: |
| // |
| // - (hypothetical) some sort of race between enabling/disabling plugins |
| // and when it's checked by the navigation URL loader / handled in the |
| // renderer. |
| // - mobile emulation disables plugins on the renderer side, but the browser |
| // navigation code is not aware of this. |
| // |
| // Similar to the missing archive resource case above, synthesise a resource |
| // to commit. |
| WebNavigationParams::FillStaticResponse( |
| params, "text/html", "UTF-8", |
| "<html><body>" |
| "<!-- no enabled plugin supports this MIME type -->" |
| "</body></html>"); |
| } |
| |
| // The browser navigation code should never send a `CommitNavigation()` request |
| // that fails this check. |
| static void AssertCanNavigate(WebNavigationParams* params, LocalFrame* frame) { |
| if (params->is_static_data) |
| return; |
| |
| if (DocumentLoader::WillLoadUrlAsEmpty(params->url)) |
| return; |
| |
| int status_code = params->response.HttpStatusCode(); |
| // If the server sends 204 or 205, this means the server does not want to |
| // replace the page contents. However, PlzNavigate should have handled it |
| // browser-side and never sent a commit request to the renderer. |
| if (status_code == 204 || status_code == 205) |
| CHECK(false); |
| |
| // If the server attached a Content-Disposition indicating that the resource |
| // is an attachment, this is actually a download. However, PlzNavigate should |
| // have handled it browser-side and never sent a commit request to the |
| // renderer. |
| if (IsContentDispositionAttachment( |
| params->response.HttpHeaderField(http_names::kContentDisposition))) { |
| CHECK(false); |
| } |
| } |
| |
| void FrameLoader::CommitNavigation( |
| std::unique_ptr<WebNavigationParams> navigation_params, |
| std::unique_ptr<WebDocumentLoader::ExtraData> extra_data, |
| CommitReason commit_reason) { |
| DCHECK(document_loader_); |
| DCHECK(frame_->GetDocument()); |
| DCHECK(Client()->HasWebView()); |
| |
| if (!frame_->IsNavigationAllowed() || |
| frame_->GetDocument()->PageDismissalEventBeingDispatched() != |
| Document::kNoDismissal) { |
| // Any of the checks above should not be necessary. |
| // Unfortunately, in the case of sync IPCs like print() there might be |
| // reentrancy and, for example, frame detach happening. |
| // See fast/loader/detach-while-printing.html for a repro. |
| // TODO(https://crbug.com/862088): we should probably ignore print() |
| // call in this case instead. |
| return; |
| } |
| |
| // TODO(dgozman): figure out the better place for this check |
| // to cancel lazy load both on start and commit. Perhaps |
| // CancelProvisionalLoaderForNewNavigation() is a good one. |
| HTMLFrameOwnerElement* frame_owner = frame_->DeprecatedLocalOwner(); |
| if (frame_owner) |
| frame_owner->CancelPendingLazyLoad(); |
| |
| navigation_params->frame_load_type = DetermineFrameLoadType( |
| navigation_params->url, navigation_params->http_method, |
| false /* has_origin_window */, navigation_params->unreachable_url, |
| navigation_params->frame_load_type); |
| |
| // Note: we might actually classify this navigation as same document |
| // right here in the following circumstances: |
| // - the loader has already committed a navigation and notified the browser |
| // process which did not receive a message about that just yet; |
| // - meanwhile, the browser process sent us a command to commit this new |
| // "cross-document" navigation, while it's actually same-document |
| // with regards to the last commit. |
| // In this rare case, we intentionally proceed as cross-document. |
| |
| if (!CancelProvisionalLoaderForNewNavigation()) |
| return; |
| |
| FillStaticResponseIfNeeded(navigation_params.get(), frame_); |
| AssertCanNavigate(navigation_params.get(), frame_); |
| |
| // Keep track of the current Document HistoryItem as the new DocumentLoader |
| // might need to copy state from it. Note that the current DocumentLoader |
| // should always exist, as the initial empty document is committed through |
| // FrameLoader::Init. |
| HistoryItem* previous_history_item = GetDocumentLoader()->GetHistoryItem(); |
| |
| // Check if the CSP of the response should block the new document from |
| // committing before unloading the current document. This will allow to report |
| // violations and display console messages properly. |
| ContentSecurityPolicy* content_security_policy = CreateCSP( |
| navigation_params->url, navigation_params->response.ToResourceResponse(), |
| navigation_params->origin_policy, last_origin_window_csp_.Release(), |
| commit_reason); |
| |
| DCHECK(content_security_policy); |
| |
| for (auto& csp : navigation_params->forced_content_security_policies) { |
| scoped_refptr<SecurityOrigin> self_origin = |
| SecurityOrigin::Create(navigation_params->url); |
| content_security_policy->DidReceiveHeader( |
| csp, *self_origin, network::mojom::ContentSecurityPolicyType::kEnforce, |
| network::mojom::ContentSecurityPolicySource::kHTTP); |
| } |
| |
| // The navigation to the initial empty document is committed directly by Blink |
| // and doesn't have a policy container, so we keep the frame's policy |
| // container (which was inherited by the parent/opener) in that case. |
| if (navigation_params->policy_container) { |
| frame_->SetPolicyContainer(PolicyContainer::CreateFromWebPolicyContainer( |
| std::move(navigation_params->policy_container))); |
| } |
| |
| // If this is a javascript: URL or XSLT commit, we must copy the ExtraData |
| // from the previous DocumentLoader to ensure the new DocumentLoader behaves |
| // the same way as the previous one. |
| if (commit_reason == CommitReason::kXSLT || |
| commit_reason == CommitReason::kJavascriptUrl) { |
| DCHECK(!extra_data); |
| if (auto* old_document_loader = |
| static_cast<WebDocumentLoaderImpl*>(document_loader_.Get())) { |
| extra_data = old_document_loader->TakeExtraData(); |
| } |
| } |
| |
| base::Optional<Document::UnloadEventTiming> unload_timing; |
| FrameSwapScope frame_swap_scope(frame_owner); |
| { |
| base::AutoReset<bool> scoped_committing(&committing_navigation_, true); |
| |
| progress_tracker_->ProgressStarted(); |
| // In DocumentLoader, the matching DidCommitLoad messages are only called |
| // for kRegular commits. Skip them here, too, to ensure we match |
| // start/commit message pairs. |
| if (commit_reason == CommitReason::kRegular) { |
| frame_->GetFrameScheduler()->DidStartProvisionalLoad( |
| frame_->IsMainFrame()); |
| probe::DidStartProvisionalLoad(frame_); |
| } |
| |
| DCHECK(Client()->HasWebView()); |
| scoped_refptr<SecurityOrigin> security_origin = |
| SecurityOrigin::Create(navigation_params->url); |
| |
| // If `frame_` is provisional, this is largely a no-op other than cleaning |
| // up the initial (and unused) empty document. Otherwise, this unloads the |
| // previous Document and detaches subframes. If `DetachDocument()` returns |
| // false, JS caused `frame_` to be removed, so just return. |
| const bool is_provisional = frame_->IsProvisional(); |
| if (!DetachDocument(security_origin.get(), &unload_timing)) { |
| DCHECK(!is_provisional); |
| return; |
| } |
| |
| // If the frame is provisional, swap it in now. However, if `Swap()` returns |
| // false, JS caused `frame_` to be removed, so just return. |
| if (is_provisional && !frame_->SwapIn()) |
| return; |
| } |
| |
| tls_version_warning_origins_.clear(); |
| |
| if (!DocumentLoader::WillLoadUrlAsEmpty(navigation_params->url)) |
| empty_document_status_ = EmptyDocumentStatus::kNonEmpty; |
| |
| // TODO(dgozman): navigation type should probably be passed by the caller. |
| // It seems incorrect to pass |false| for |have_event| and then use |
| // determined navigation type to update resource request. |
| WebNavigationType navigation_type = DetermineNavigationType( |
| navigation_params->frame_load_type, |
| !navigation_params->http_body.IsNull(), false /* have_event */); |
| |
| // TODO(dgozman): get rid of provisional document loader and most of the code |
| // below. We should probably call DocumentLoader::CommitNavigation directly. |
| DocumentLoader* new_document_loader = Client()->CreateDocumentLoader( |
| frame_, navigation_type, content_security_policy, |
| std::move(navigation_params), std::move(extra_data)); |
| |
| CommitDocumentLoader(new_document_loader, unload_timing, |
| previous_history_item, commit_reason); |
| |
| RestoreScrollPositionAndViewState(); |
| |
| TakeObjectSnapshot(); |
| } |
| |
| bool FrameLoader::WillStartNavigation(const WebNavigationInfo& info) { |
| if (!CancelProvisionalLoaderForNewNavigation()) |
| return false; |
| |
| progress_tracker_->ProgressStarted(); |
| client_navigation_ = std::make_unique<ClientNavigationState>(); |
| client_navigation_->url = info.url_request.Url(); |
| frame_->GetFrameScheduler()->DidStartProvisionalLoad(frame_->IsMainFrame()); |
| probe::DidStartProvisionalLoad(frame_); |
| virtual_time_pauser_.PauseVirtualTime(); |
| TakeObjectSnapshot(); |
| return true; |
| } |
| |
| void FrameLoader::StopAllLoaders(bool abort_client) { |
| if (!frame_->IsNavigationAllowed() || |
| frame_->GetDocument()->PageDismissalEventBeingDispatched() != |
| Document::kNoDismissal) { |
| return; |
| } |
| |
| // This method could be called from within this method, e.g. through plugin |
| // detach. Avoid infinite recursion by disabling navigations. |
| FrameNavigationDisabler navigation_disabler(*frame_); |
| |
| for (Frame* child = frame_->Tree().FirstChild(); child; |
| child = child->Tree().NextSibling()) { |
| if (auto* child_local_frame = DynamicTo<LocalFrame>(child)) |
| child_local_frame->Loader().StopAllLoaders(abort_client); |
| } |
| |
| frame_->GetDocument()->CancelParsing(); |
| if (document_loader_) |
| document_loader_->StopLoading(); |
| if (abort_client) |
| CancelClientNavigation(); |
| else |
| ClearClientNavigation(); |
| frame_->CancelFormSubmission(); |
| DidFinishNavigation(FrameLoader::NavigationFinishState::kSuccess); |
| |
| TakeObjectSnapshot(); |
| } |
| |
| void FrameLoader::DidAccessInitialDocument() { |
| if (frame_->IsMainFrame() && !has_accessed_initial_document_) { |
| has_accessed_initial_document_ = true; |
| // Forbid script execution to prevent re-entering V8, since this is called |
| // from a binding security check. |
| ScriptForbiddenScope forbid_scripts; |
| frame_->GetLocalFrameHostRemote().DidAccessInitialDocument(); |
| } |
| } |
| |
| bool FrameLoader::DetachDocument( |
| SecurityOrigin* committing_origin, |
| base::Optional<Document::UnloadEventTiming>* timing) { |
| DCHECK(frame_->GetDocument()); |
| DCHECK(document_loader_); |
| |
| PluginScriptForbiddenScope forbid_plugin_destructor_scripting; |
| ClientNavigationState* client_navigation = client_navigation_.get(); |
| |
| // Don't allow this frame to navigate anymore. This line is needed for |
| // navigation triggered from children's unload handlers. Blocking navigations |
| // triggered from this frame's unload handler is already covered in |
| // DispatchUnloadEvent(). |
| FrameNavigationDisabler navigation_disabler(*frame_); |
| // Don't allow any new child frames to load in this frame: attaching a new |
| // child frame during or after detaching children results in an attached frame |
| // on a detached DOM tree, which is bad. |
| SubframeLoadingDisabler disabler(frame_->GetDocument()); |
| // https://html.spec.whatwg.org/C/browsing-the-web.html#unload-a-document |
| // The ignore-opens-during-unload counter of a Document must be incremented |
| // both when unloading itself and when unloading its descendants. |
| IgnoreOpensDuringUnloadCountIncrementer ignore_opens_during_unload( |
| frame_->GetDocument()); |
| DispatchUnloadEvent(committing_origin, timing); |
| frame_->DetachChildren(); |
| // The previous calls to dispatchUnloadEvent() and detachChildren() can |
| // execute arbitrary script via things like unload events. If the executed |
| // script causes the current frame to be detached, we need to abandon the |
| // current load. |
| if (!frame_->Client()) |
| return false; |
| // FrameNavigationDisabler should prevent another load from starting. |
| DCHECK_EQ(client_navigation_.get(), client_navigation); |
| // Detaching the document loader will abort XHRs that haven't completed, which |
| // can trigger event listeners for 'abort'. These event listeners might call |
| // window.stop(), which will in turn detach the provisional document loader. |
| // At this point, the provisional document loader should not detach, because |
| // then the FrameLoader would not have any attached DocumentLoaders. This is |
| // guaranteed by FrameNavigationDisabler above. |
| DetachDocumentLoader(document_loader_, true); |
| // 'abort' listeners can also detach the frame. |
| if (!frame_->Client()) |
| return false; |
| // FrameNavigationDisabler should prevent another load from starting. |
| DCHECK_EQ(client_navigation_.get(), client_navigation); |
| |
| // No more events will be dispatched so detach the Document. |
| // TODO(dcheng): Why is this a conditional check? |
| // TODO(yoav): Should we also be nullifying domWindow's document (or |
| // domWindow) since the doc is now detached? |
| frame_->GetDocument()->Shutdown(); |
| document_loader_ = nullptr; |
| |
| return true; |
| } |
| |
| void FrameLoader::CommitDocumentLoader( |
| DocumentLoader* document_loader, |
| const base::Optional<Document::UnloadEventTiming>& unload_timing, |
| HistoryItem* previous_history_item, |
| CommitReason commit_reason) { |
| document_loader_ = document_loader; |
| CHECK(document_loader_); |
| |
| document_loader_->SetCommitReason(commit_reason); |
| |
| virtual_time_pauser_.PauseVirtualTime(); |
| document_loader_->StartLoading(); |
| virtual_time_pauser_.UnpauseVirtualTime(); |
| |
| if (commit_reason != CommitReason::kInitialization) { |
| // Following the call to StartLoading, the DocumentLoader state has taken |
| // into account all redirects that happened during navigation. Its |
| // HistoryItem can be properly updated for the commit, using the HistoryItem |
| // of the previous Document. |
| document_loader_->SetHistoryItemStateForCommit( |
| previous_history_item, document_loader_->LoadType(), |
| DocumentLoader::HistoryNavigationType::kDifferentDocument, |
| commit_reason); |
| } |
| |
| // Update the DocumentLoadTiming with the timings from the previous document |
| // unload event. |
| if (unload_timing.has_value()) { |
| document_loader_->GetTiming().SetCanRequestFromPreviousDocument( |
| unload_timing->can_request); |
| document_loader_->GetTiming().MarkUnloadEventStart( |
| unload_timing->unload_event_start); |
| document_loader_->GetTiming().MarkUnloadEventEnd( |
| unload_timing->unload_event_end); |
| document_loader_->GetTiming().MarkCommitNavigationEnd(); |
| } |
| |
| TakeObjectSnapshot(); |
| |
| Client()->TransitionToCommittedForNewPage(); |
| |
| document_loader_->CommitNavigation(); |
| } |
| |
| void FrameLoader::RestoreScrollPositionAndViewState() { |
| if (!frame_->GetPage() || !GetDocumentLoader() || |
| !GetDocumentLoader()->GetHistoryItem() || |
| !GetDocumentLoader()->GetHistoryItem()->GetViewState() || |
| !GetDocumentLoader()->NavigationScrollAllowed()) { |
| return; |
| } |
| RestoreScrollPositionAndViewState( |
| GetDocumentLoader()->LoadType(), |
| *GetDocumentLoader()->GetHistoryItem()->GetViewState(), |
| GetDocumentLoader()->GetHistoryItem()->ScrollRestorationType()); |
| } |
| |
| void FrameLoader::RestoreScrollPositionAndViewState( |
| WebFrameLoadType load_type, |
| const HistoryItem::ViewState& view_state, |
| mojom::blink::ScrollRestorationType scroll_restoration_type) { |
| LocalFrameView* view = frame_->View(); |
| if (!view || !view->LayoutViewport() || !frame_->IsAttached() || |
| frame_->GetDocument()->IsInitialEmptyDocument()) { |
| return; |
| } |
| if (!NeedsHistoryItemRestore(load_type)) |
| return; |
| |
| view->LayoutViewport()->SetPendingHistoryRestoreScrollOffset( |
| view_state, |
| scroll_restoration_type != mojom::blink::ScrollRestorationType::kManual); |
| view->GetScrollableArea()->SetPendingHistoryRestoreScrollOffset( |
| view_state, |
| scroll_restoration_type != mojom::blink::ScrollRestorationType::kManual); |
| |
| view->ScheduleAnimation(); |
| } |
| |
| String FrameLoader::UserAgent() const { |
| String user_agent = Client()->UserAgent(); |
| probe::ApplyUserAgentOverride(probe::ToCoreProbeSink(frame_->GetDocument()), |
| &user_agent); |
| return user_agent; |
| } |
| |
| base::Optional<blink::UserAgentMetadata> FrameLoader::UserAgentMetadata() |
| const { |
| return Client()->UserAgentMetadata(); |
| } |
| |
| void FrameLoader::Detach() { |
| frame_->GetDocument()->CancelParsing(); |
| DetachDocumentLoader(document_loader_); |
| ClearClientNavigation(); |
| committing_navigation_ = false; |
| DidFinishNavigation(FrameLoader::NavigationFinishState::kSuccess); |
| |
| if (progress_tracker_) { |
| progress_tracker_->Dispose(); |
| progress_tracker_.Clear(); |
| } |
| |
| TRACE_EVENT_OBJECT_DELETED_WITH_ID("loading", "FrameLoader", this); |
| state_ = State::kDetached; |
| virtual_time_pauser_.UnpauseVirtualTime(); |
| } |
| |
| bool FrameLoader::MaybeRenderFallbackContent() { |
| DCHECK(frame_->Owner() && frame_->Owner()->CanRenderFallbackContent()); |
| // |client_navigation_| can be null here: |
| // 1. We asked client to navigation through BeginNavigation(); |
| // 2. Meanwhile, another navigation has been started, e.g. to about:srcdoc. |
| // This navigation has been processed, |client_navigation_| has been |
| // reset, and browser process was informed about cancellation. |
| // 3. Before the cancellation reached the browser process, it decided that |
| // first navigation has failed and asks to commit the failed navigation. |
| // 4. We come here, while |client_navigation_| is null. |
| // TODO(dgozman): shouldn't we abandon the commit of navigation failure |
| // because we've already notified the client about cancellation? This needs |
| // to be double-checked, perhaps this is dead code. |
| if (!client_navigation_) |
| return false; |
| |
| frame_->Owner()->RenderFallbackContent(frame_); |
| ClearClientNavigation(); |
| DidFinishNavigation(FrameLoader::NavigationFinishState::kSuccess); |
| return true; |
| } |
| |
| bool FrameLoader::ShouldPerformFragmentNavigation(bool is_form_submission, |
| const String& http_method, |
| WebFrameLoadType load_type, |
| const KURL& url) { |
| // We don't do this if we are submitting a form with method other than "GET", |
| // explicitly reloading, currently displaying a frameset, or if the URL does |
| // not have a fragment. |
| return EqualIgnoringASCIICase(http_method, http_names::kGET) && |
| !IsReloadLoadType(load_type) && |
| load_type != WebFrameLoadType::kBackForward && |
| url.HasFragmentIdentifier() && |
| // For provisional LocalFrame, there is no real document loaded and |
| // the initial empty document should not be considered, so there is |
| // no way to get a same-document load in this case. |
| !frame_->IsProvisional() && |
| EqualIgnoringFragmentIdentifier(frame_->GetDocument()->Url(), url) |
| // We don't want to just scroll if a link from within a frameset is |
| // trying to reload the frameset into _top. |
| && !frame_->GetDocument()->IsFrameSet(); |
| } |
| |
| void FrameLoader::ProcessFragment(const KURL& url, |
| WebFrameLoadType frame_load_type, |
| LoadStartType load_start_type) { |
| LocalFrameView* view = frame_->View(); |
| if (!view) |
| return; |
| |
| // Leaking scroll position to a cross-origin ancestor would permit the |
| // so-called "framesniffing" attack. |
| Frame* boundary_frame = |
| url.HasFragmentIdentifier() |
| ? frame_->FindUnsafeParentScrollPropagationBoundary() |
| : nullptr; |
| |
| // FIXME: Handle RemoteFrames |
| if (auto* boundary_local_frame = DynamicTo<LocalFrame>(boundary_frame)) |
| boundary_local_frame->View()->SetSafeToPropagateScrollToParent(false); |
| |
| const bool is_same_document_navigation = |
| load_start_type == kNavigationWithinSameDocument; |
| |
| // Pages can opt-in to manual scroll restoration so the page will handle |
| // restoring the past scroll offset during a history navigation. In these |
| // cases we assume the scroll was restored from history (by the page). |
| const bool uses_manual_scroll_restoration = |
| frame_load_type == WebFrameLoadType::kBackForward && |
| GetDocumentLoader()->GetHistoryItem() && |
| GetDocumentLoader()->GetHistoryItem()->ScrollRestorationType() == |
| mojom::blink::ScrollRestorationType::kManual; |
| |
| // If we restored a scroll position from history, we shouldn't clobber it |
| // with the fragment. |
| const bool will_restore_scroll_from_history = |
| GetDocumentLoader()->GetInitialScrollState().did_restore_from_history || |
| uses_manual_scroll_restoration; |
| |
| // Scrolling at load can be blocked by document policy. This policy applies |
| // only to cross-document navigations. |
| const bool blocked_by_policy = |
| !is_same_document_navigation && |
| !GetDocumentLoader()->NavigationScrollAllowed(); |
| |
| // We should avoid scrolling the fragment if it would clobber a history |
| // restored scroll state but still allow it on same document navigations |
| // after (i.e. if we navigate back and restore the scroll position, the user |
| // should still be able to click on a same-document fragment link and have it |
| // jump to the anchor). |
| const bool is_same_document_non_history_nav = |
| is_same_document_navigation && !IsBackForwardLoadType(frame_load_type); |
| |
| const bool block_fragment_scroll = |
| blocked_by_policy || |
| (will_restore_scroll_from_history && !is_same_document_non_history_nav); |
| |
| view->ProcessUrlFragment(url, is_same_document_navigation, |
| !block_fragment_scroll); |
| |
| if (auto* boundary_local_frame = DynamicTo<LocalFrame>(boundary_frame)) |
| boundary_local_frame->View()->SetSafeToPropagateScrollToParent(true); |
| } |
| |
| bool FrameLoader::ShouldClose(bool is_reload) { |
| Page* page = frame_->GetPage(); |
| if (!page || !page->GetChromeClient().CanOpenBeforeUnloadConfirmPanel()) |
| return true; |
| |
| HeapVector<Member<LocalFrame>> descendant_frames; |
| for (Frame* child = frame_->Tree().FirstChild(); child; |
| child = child->Tree().TraverseNext(frame_)) { |
| // FIXME: There is not yet any way to dispatch events to out-of-process |
| // frames. |
| if (auto* child_local_frame = DynamicTo<LocalFrame>(child)) |
| descendant_frames.push_back(child_local_frame); |
| } |
| |
| { |
| FrameNavigationDisabler navigation_disabler(*frame_); |
| bool did_allow_navigation = false; |
| |
| // https://html.spec.whatwg.org/C/browsing-the-web.html#prompt-to-unload-a-document |
| |
| // First deal with this frame. |
| IgnoreOpensDuringUnloadCountIncrementer ignore_opens_during_unload( |
| frame_->GetDocument()); |
| if (!frame_->GetDocument()->DispatchBeforeUnloadEvent( |
| &page->GetChromeClient(), is_reload, did_allow_navigation)) |
| return false; |
| |
| // Then deal with descendent frames. |
| for (Member<LocalFrame>& descendant_frame : descendant_frames) { |
| if (!descendant_frame->Tree().IsDescendantOf(frame_)) |
| continue; |
| |
| // There is some confusion in the spec around what counters should be |
| // incremented for a descendant browsing context: |
| // https://github.com/whatwg/html/issues/3899 |
| // |
| // Here for implementation ease, we use the current spec behavior, which |
| // is to increment only the counter of the Document on which this is |
| // called, and that of the Document we are firing the beforeunload event |
| // on -- not any intermediate Documents that may be the parent of the |
| // frame being unloaded but is not root Document. |
| IgnoreOpensDuringUnloadCountIncrementer |
| ignore_opens_during_unload_descendant( |
| descendant_frame->GetDocument()); |
| if (!descendant_frame->GetDocument()->DispatchBeforeUnloadEvent( |
| &page->GetChromeClient(), is_reload, did_allow_navigation)) |
| return false; |
| } |
| } |
| |
| // Now that none of the unloading frames canceled the BeforeUnload, tell each |
| // of them so they can advance to the appropriate load state. |
| frame_->GetDocument()->BeforeUnloadDoneWillUnload(); |
| for (Member<LocalFrame>& descendant_frame : descendant_frames) { |
| if (!descendant_frame->Tree().IsDescendantOf(frame_)) |
| continue; |
| descendant_frame->GetDocument()->BeforeUnloadDoneWillUnload(); |
| } |
| |
| return true; |
| } |
| |
| void FrameLoader::DidDropNavigation() { |
| if (!client_navigation_) |
| return; |
| // TODO(dgozman): should we ClearClientNavigation instead and not |
| // notify the client in response to its own call? |
| CancelClientNavigation(); |
| DidFinishNavigation(FrameLoader::NavigationFinishState::kSuccess); |
| |
| // Forcibly instantiate WindowProxy for initial frame document. |
| // This is only required when frame navigation is aborted, e.g. due to |
| // mixed content. |
| // TODO(lushnikov): this should be done in Init for initial empty doc, but |
| // that breaks extensions abusing SetForceMainWorldInitialization setting |
| // and relying on the number of created window proxies. |
| Settings* settings = frame_->GetSettings(); |
| if (settings && settings->GetForceMainWorldInitialization()) { |
| // Forcibly instantiate WindowProxy. |
| frame_->DomWindow()->GetScriptController().WindowProxy( |
| DOMWrapperWorld::MainWorld()); |
| } |
| } |
| |
| bool FrameLoader::CancelProvisionalLoaderForNewNavigation() { |
| // This seems to correspond to step 9 of the specification: |
| // "9. Abort the active document of browsingContext." |
| // https://html.spec.whatwg.org/C/#navigate |
| frame_->GetDocument()->Abort(); |
| // document.onreadystatechange can fire in Abort(), which can: |
| // 1) Detach this frame. |
| // 2) Stop the provisional DocumentLoader (i.e window.stop()). |
| if (!frame_->GetPage()) |
| return false; |
| |
| // For client navigations, don't send failure callbacks when simply |
| // replacing client navigation with a DocumentLoader. |
| ClearClientNavigation(); |
| |
| // Cancel pending form submissions so they don't take precedence over this. |
| frame_->CancelFormSubmission(); |
| |
| return true; |
| } |
| |
| void FrameLoader::ClearClientNavigation() { |
| if (!client_navigation_) |
| return; |
| client_navigation_.reset(); |
| probe::DidFailProvisionalLoad(frame_); |
| virtual_time_pauser_.UnpauseVirtualTime(); |
| } |
| |
| void FrameLoader::CancelClientNavigation() { |
| if (!client_navigation_) |
| return; |
| ResourceError error = ResourceError::CancelledError(client_navigation_->url); |
| ClearClientNavigation(); |
| if (WebPluginContainerImpl* plugin = frame_->GetWebPluginContainer()) |
| plugin->DidFailLoading(error); |
| Client()->AbortClientNavigation(); |
| } |
| |
| void FrameLoader::DispatchDocumentElementAvailable() { |
| ScriptForbiddenScope forbid_scripts; |
| |
| // Notify the browser about non-blank documents loading in the top frame. |
| KURL url = frame_->GetDocument()->Url(); |
| if (url.IsValid() && !url.IsAboutBlankURL()) { |
| if (frame_->IsMainFrame()) { |
| // For now, don't remember plugin zoom values. We don't want to mix them |
| // with normal web content (i.e. a fixed layout plugin would usually want |
| // them different). |
| frame_->GetLocalFrameHostRemote().DocumentAvailableInMainFrame( |
| frame_->GetDocument()->IsPluginDocument()); |
| } |
| } |
| |
| Client()->DocumentElementAvailable(); |
| } |
| |
| void FrameLoader::RunScriptsAtDocumentElementAvailable() { |
| Client()->RunScriptsAtDocumentElementAvailable(); |
| // The frame might be detached at this point. |
| } |
| |
| void FrameLoader::DispatchDidClearDocumentOfWindowObject() { |
| if (state_ == State::kUninitialized) |
| return; |
| |
| Settings* settings = frame_->GetSettings(); |
| LocalDOMWindow* window = frame_->DomWindow(); |
| if (settings && settings->GetForceMainWorldInitialization()) { |
| // Forcibly instantiate WindowProxy, even if script is disabled. |
| window->GetScriptController().WindowProxy(DOMWrapperWorld::MainWorld()); |
| } |
| probe::DidClearDocumentOfWindowObject(frame_); |
| if (!window->CanExecuteScripts(kNotAboutToExecuteScript)) |
| return; |
| |
| if (dispatching_did_clear_window_object_in_main_world_) |
| return; |
| base::AutoReset<bool> in_did_clear_window_object( |
| &dispatching_did_clear_window_object_in_main_world_, true); |
| // We just cleared the document, not the entire window object, but for the |
| // embedder that's close enough. |
| Client()->DispatchDidClearWindowObjectInMainWorld(); |
| } |
| |
| void FrameLoader::DispatchDidClearWindowObjectInMainWorld() { |
| if (!frame_->DomWindow()->CanExecuteScripts(kNotAboutToExecuteScript)) |
| return; |
| |
| if (dispatching_did_clear_window_object_in_main_world_) |
| return; |
| base::AutoReset<bool> in_did_clear_window_object( |
| &dispatching_did_clear_window_object_in_main_world_, true); |
| Client()->DispatchDidClearWindowObjectInMainWorld(); |
| } |
| |
| network::mojom::blink::WebSandboxFlags |
| FrameLoader::PendingEffectiveSandboxFlags() const { |
| if (Frame* parent = frame_->Tree().Parent()) { |
| return parent->GetSecurityContext()->GetSandboxFlags() | |
| frame_->Owner()->GetFramePolicy().sandbox_flags; |
| } else { |
| return frame_->OpenerSandboxFlags(); |
| } |
| } |
| |
| void FrameLoader::ModifyRequestForCSP( |
| ResourceRequest& resource_request, |
| const FetchClientSettingsObject* fetch_client_settings_object, |
| LocalDOMWindow* window_for_logging, |
| mojom::RequestContextFrameType frame_type) const { |
| // Tack an 'Upgrade-Insecure-Requests' header to outgoing navigational |
| // requests, as described in |
| // https://w3c.github.io/webappsec-upgrade-insecure-requests/#feature-detect |
| if (frame_type != mojom::RequestContextFrameType::kNone) { |
| // Early return if the request has already been upgraded. |
| if (!resource_request.HttpHeaderField(http_names::kUpgradeInsecureRequests) |
| .IsNull()) { |
| return; |
| } |
| |
| resource_request.SetHttpHeaderField(http_names::kUpgradeInsecureRequests, |
| "1"); |
| } |
| |
| MixedContentChecker::UpgradeInsecureRequest( |
| resource_request, fetch_client_settings_object, window_for_logging, |
| frame_type, frame_->GetContentSettingsClient()); |
| } |
| |
| void FrameLoader::ReportLegacyTLSVersion(const KURL& url, |
| bool is_subresource, |
| bool is_ad_resource) { |
| document_loader_->GetUseCounterHelper().Count( |
| is_subresource |
| ? WebFeature::kLegacyTLSVersionInSubresource |
| : (frame_->Tree().Parent() |
| ? WebFeature::kLegacyTLSVersionInSubframeMainResource |
| : WebFeature::kLegacyTLSVersionInMainFrameResource), |
| frame_.Get()); |
| |
| // For non-main-frame loads, we have to use the main frame's document for |
| // the UKM recorder and source ID. |
| auto& root = frame_->LocalFrameRoot(); |
| ukm::builders::Net_LegacyTLSVersion(root.GetDocument()->UkmSourceID()) |
| .SetIsMainFrame(frame_->IsMainFrame()) |
| .SetIsSubresource(is_subresource) |
| .SetIsAdResource(is_ad_resource) |
| .Record(root.GetDocument()->UkmRecorder()); |
| |
| String origin = SecurityOrigin::Create(url)->ToString(); |
| // To prevent log spam, only log the message once per origin. |
| if (tls_version_warning_origins_.Contains(origin)) |
| return; |
| |
| // After |kMaxSecurityWarningMessages| warnings, stop printing messages to the |
| // console. At exactly |kMaxSecurityWarningMessages| warnings, print a message |
| // that additional resources on the page use legacy certificates without |
| // specifying which exact resources. Before |kMaxSecurityWarningMessages| |
| // messages, print the exact resource URL in the message to help the developer |
| // pinpoint the problematic resources. |
| const size_t kMaxSecurityWarningMessages = 10; |
| size_t num_warnings = tls_version_warning_origins_.size(); |
| if (num_warnings > kMaxSecurityWarningMessages) |
| return; |
| |
| String console_message; |
| if (num_warnings == kMaxSecurityWarningMessages) { |
| console_message = |
| "Additional resources on this page were loaded with TLS 1.0 or TLS " |
| "1.1, which are deprecated and will be disabled in the future. Once " |
| "disabled, users will be prevented from loading these resources. " |
| "Servers should enable TLS 1.2 or later. See " |
| "https://www.chromestatus.com/feature/5654791610957824 for more " |
| "information."; |
| } else { |
| console_message = |
| "The connection used to load resources from " + origin + |
| " used TLS 1.0 or TLS " |
| "1.1, which are deprecated and will be disabled in the future. Once " |
| "disabled, users will be prevented from loading these resources. The " |
| "server should enable TLS 1.2 or later. See " |
| "https://www.chromestatus.com/feature/5654791610957824 for more " |
| "information."; |
| } |
| tls_version_warning_origins_.insert(origin); |
| // To avoid spamming the console, use verbose message level for subframe |
| // resources, and only use the warning level for main-frame resources. |
| frame_->Console().AddMessage(MakeGarbageCollected<ConsoleMessage>( |
| mojom::ConsoleMessageSource::kOther, |
| frame_->IsMainFrame() ? mojom::ConsoleMessageLevel::kWarning |
| : mojom::ConsoleMessageLevel::kVerbose, |
| console_message)); |
| } |
| |
| void FrameLoader::WriteIntoTracedValue(perfetto::TracedValue context) const { |
| auto dict = std::move(context).WriteDictionary(); |
| { |
| auto frame_dict = dict.AddDictionary("frame"); |
| frame_dict.Add("id_ref", IdentifiersFactory::FrameId(frame_.Get())); |
| } |
| dict.Add("isLoadingMainFrame", frame_->IsMainFrame()); |
| dict.Add("documentLoaderURL", |
| document_loader_ ? document_loader_->Url().GetString() : String()); |
| } |
| |
| inline void FrameLoader::TakeObjectSnapshot() const { |
| if (state_ == State::kDetached) { |
| // We already logged TRACE_EVENT_OBJECT_DELETED_WITH_ID in detach(). |
| return; |
| } |
| TRACE_EVENT_OBJECT_SNAPSHOT_WITH_ID("loading", "FrameLoader", this, this); |
| } |
| |
| ContentSecurityPolicy* FrameLoader::CreateCSPForInitialEmptyDocument() const { |
| ContentSecurityPolicy* csp = MakeGarbageCollected<ContentSecurityPolicy>(); |
| |
| Frame* owner_frame = frame_->Parent() ? frame_->Parent() : frame_->Opener(); |
| if (owner_frame) { |
| ContentSecurityPolicy* owner_csp = |
| owner_frame->GetSecurityContext()->GetContentSecurityPolicy(); |
| csp->CopyStateFrom(owner_csp); |
| } |
| |
| return csp; |
| } |
| |
| ContentSecurityPolicy* FrameLoader::CreateCSP( |
| const KURL& url, |
| const ResourceResponse& response, |
| const base::Optional<WebOriginPolicy>& origin_policy, |
| ContentSecurityPolicy* initiator_csp, |
| CommitReason commit_reason) { |
| // about:srcdoc inherits CSP from its parent. |
| if (url.IsAboutSrcdocURL()) { |
| ContentSecurityPolicy* csp = MakeGarbageCollected<ContentSecurityPolicy>(); |
| csp->CopyStateFrom(frame_->Tree() |
| .Parent() |
| ->GetSecurityContext() |
| ->GetContentSecurityPolicy()); |
| return csp; |
| } |
| |
| // Documents constructed by XSLTProcessor inherit CSP from the previous |
| // Document. |
| if (commit_reason == CommitReason::kXSLT) { |
| ContentSecurityPolicy* csp = MakeGarbageCollected<ContentSecurityPolicy>(); |
| csp->CopyStateFrom(frame_->DomWindow()->GetContentSecurityPolicy()); |
| return csp; |
| } |
| |
| // Documents with a local-scheme inherits CSP from their navigation initiator. |
| bool is_local_scheme = url.IsEmpty() || url.ProtocolIsAbout() || |
| url.ProtocolIsData() || url.ProtocolIs("blob") || |
| url.ProtocolIs("filesystem"); |
| if (is_local_scheme) { |
| if (initiator_csp) |
| return initiator_csp; |
| return MakeGarbageCollected<ContentSecurityPolicy>(); |
| } |
| |
| // In the main case (outside of the ones above), CSP(s) are NOT inherited. |
| // Otherwise, it would allow a malicious parent/opener to block some |
| // iframe/popup's script at a fine-grained level. |
| |
| ContentSecurityPolicy* csp = MakeGarbageCollected<ContentSecurityPolicy>(); |
| |
| if (frame_->GetSettings()->GetBypassCSP()) |
| return csp; // Empty CSP. |
| |
| // Parse CSP from the HTTP response. |
| csp->DidReceiveHeaders(ContentSecurityPolicyResponseHeaders(response)); |
| |
| // Retrieve CSP stored in the OriginPolicy. |
| if (origin_policy) |
| ApplyOriginPolicy(csp, url, origin_policy.value()); |
| |
| return csp; |
| } |
| |
| } // namespace blink |