| /* |
| * 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 "core/loader/FrameLoader.h" |
| |
| #include <memory> |
| #include "bindings/core/v8/ScriptController.h" |
| #include "bindings/core/v8/serialization/SerializedScriptValue.h" |
| #include "core/HTMLNames.h" |
| #include "core/dom/Document.h" |
| #include "core/dom/Element.h" |
| #include "core/dom/TaskRunnerHelper.h" |
| #include "core/dom/UserGestureIndicator.h" |
| #include "core/dom/ViewportDescription.h" |
| #include "core/events/GestureEvent.h" |
| #include "core/events/KeyboardEvent.h" |
| #include "core/events/MouseEvent.h" |
| #include "core/events/PageTransitionEvent.h" |
| #include "core/frame/ContentSettingsClient.h" |
| #include "core/frame/LocalDOMWindow.h" |
| #include "core/frame/LocalFrame.h" |
| #include "core/frame/LocalFrameClient.h" |
| #include "core/frame/LocalFrameView.h" |
| #include "core/frame/Settings.h" |
| #include "core/frame/VisualViewport.h" |
| #include "core/frame/csp/ContentSecurityPolicy.h" |
| #include "core/html/HTMLFormElement.h" |
| #include "core/html/HTMLFrameOwnerElement.h" |
| #include "core/input/EventHandler.h" |
| #include "core/inspector/ConsoleMessage.h" |
| #include "core/loader/DocumentLoadTiming.h" |
| #include "core/loader/DocumentLoader.h" |
| #include "core/loader/FormSubmission.h" |
| #include "core/loader/FrameLoadRequest.h" |
| #include "core/loader/LinkLoader.h" |
| #include "core/loader/MixedContentChecker.h" |
| #include "core/loader/NavigationScheduler.h" |
| #include "core/loader/NetworkHintsInterface.h" |
| #include "core/loader/ProgressTracker.h" |
| #include "core/loader/appcache/ApplicationCacheHost.h" |
| #include "core/page/ChromeClient.h" |
| #include "core/page/CreateWindow.h" |
| #include "core/page/FrameTree.h" |
| #include "core/page/Page.h" |
| #include "core/page/WindowFeatures.h" |
| #include "core/page/scrolling/ScrollingCoordinator.h" |
| #include "core/probe/CoreProbes.h" |
| #include "core/svg/graphics/SVGImage.h" |
| #include "core/xml/parser/XMLDocumentParser.h" |
| #include "platform/InstanceCounters.h" |
| #include "platform/PluginScriptForbiddenScope.h" |
| #include "platform/ScriptForbiddenScope.h" |
| #include "platform/bindings/DOMWrapperWorld.h" |
| #include "platform/instrumentation/tracing/TraceEvent.h" |
| #include "platform/loader/fetch/ResourceFetcher.h" |
| #include "platform/loader/fetch/ResourceRequest.h" |
| #include "platform/network/HTTPParsers.h" |
| #include "platform/network/NetworkUtils.h" |
| #include "platform/scroll/ScrollAnimatorBase.h" |
| #include "platform/weborigin/SchemeRegistry.h" |
| #include "platform/weborigin/SecurityOrigin.h" |
| #include "platform/weborigin/SecurityPolicy.h" |
| #include "platform/weborigin/Suborigin.h" |
| #include "platform/wtf/AutoReset.h" |
| #include "platform/wtf/text/CString.h" |
| #include "platform/wtf/text/WTFString.h" |
| #include "public/platform/WebCachePolicy.h" |
| #include "public/platform/WebURLRequest.h" |
| #include "public/platform/modules/serviceworker/WebServiceWorkerNetworkProvider.h" |
| |
| using blink::WebURLRequest; |
| |
| namespace blink { |
| |
| using namespace HTMLNames; |
| |
| bool IsBackForwardLoadType(FrameLoadType type) { |
| return type == kFrameLoadTypeBackForward || |
| type == kFrameLoadTypeInitialHistoryLoad; |
| } |
| |
| bool IsReloadLoadType(FrameLoadType type) { |
| return type == kFrameLoadTypeReload || |
| type == kFrameLoadTypeReloadBypassingCache; |
| } |
| |
| static bool NeedsHistoryItemRestore(FrameLoadType type) { |
| // FrameLoadtypeInitialHistoryLoad is intentionally excluded. |
| return type == kFrameLoadTypeBackForward || IsReloadLoadType(type); |
| } |
| |
| static void CheckForLegacyProtocolInSubresource( |
| const ResourceRequest& resource_request, |
| Document* document) { |
| if (resource_request.GetFrameType() == WebURLRequest::kFrameTypeTopLevel) |
| return; |
| if (!SchemeRegistry::ShouldTreatURLSchemeAsLegacy( |
| resource_request.Url().Protocol())) { |
| return; |
| } |
| if (SchemeRegistry::ShouldTreatURLSchemeAsLegacy( |
| document->GetSecurityOrigin()->Protocol())) { |
| return; |
| } |
| Deprecation::CountDeprecation( |
| document, UseCounter::kLegacyProtocolEmbeddedAsSubresource); |
| } |
| |
| static NavigationPolicy MaybeCheckCSP( |
| const ResourceRequest& request, |
| NavigationType type, |
| LocalFrame* frame, |
| NavigationPolicy policy, |
| bool should_check_main_world_content_security_policy, |
| bool browser_side_navigation_enabled, |
| ContentSecurityPolicy::CheckHeaderType check_header_type) { |
| // If we're loading content into |frame| (NavigationPolicyCurrentTab), check |
| // against the parent's Content Security Policy and kill the load if that |
| // check fails, unless we should bypass the main world's CSP. |
| if (policy == kNavigationPolicyCurrentTab && |
| should_check_main_world_content_security_policy && |
| // TODO(arthursonzogni): 'frame-src' check is disabled on the |
| // renderer side with browser-side-navigation, but is enforced on the |
| // browser side. See http://crbug.com/692595 for understanding why it |
| // can't be enforced on both sides instead. |
| !browser_side_navigation_enabled) { |
| Frame* parent_frame = frame->Tree().Parent(); |
| if (parent_frame) { |
| ContentSecurityPolicy* parent_policy = |
| parent_frame->GetSecurityContext()->GetContentSecurityPolicy(); |
| if (!parent_policy->AllowFrameFromSource( |
| request.Url(), request.GetRedirectStatus(), |
| SecurityViolationReportingPolicy::kReport, check_header_type)) { |
| // Fire a load event, as timing attacks would otherwise reveal that the |
| // frame was blocked. This way, it looks like every other cross-origin |
| // page load. |
| frame->GetDocument()->EnforceSandboxFlags(kSandboxOrigin); |
| frame->Owner()->DispatchLoad(); |
| return kNavigationPolicyIgnore; |
| } |
| } |
| } |
| |
| bool is_form_submission = type == kNavigationTypeFormSubmitted || |
| type == kNavigationTypeFormResubmitted; |
| if (is_form_submission && |
| // 'form-action' check in the frame that is navigating is disabled on the |
| // renderer side when PlzNavigate is enabled, but is enforced on the |
| // browser side instead. |
| // N.B. check in the frame that initiates the navigation stills occurs in |
| // blink and is not enforced on the browser-side. |
| // TODO(arthursonzogni) The 'form-action' check should be fully disabled |
| // in blink when browser side navigation is enabled, except when the form |
| // submission doesn't trigger a navigation(i.e. javascript urls). Please |
| // see https://crbug.com/701749 |
| !browser_side_navigation_enabled && |
| !frame->GetDocument()->GetContentSecurityPolicy()->AllowFormAction( |
| request.Url(), request.GetRedirectStatus(), |
| SecurityViolationReportingPolicy::kReport, check_header_type)) { |
| return kNavigationPolicyIgnore; |
| } |
| |
| return policy; |
| } |
| |
| ResourceRequest FrameLoader::ResourceRequestForReload( |
| FrameLoadType frame_load_type, |
| const KURL& override_url, |
| ClientRedirectPolicy client_redirect_policy) { |
| DCHECK(IsReloadLoadType(frame_load_type)); |
| WebCachePolicy cache_policy = |
| frame_load_type == kFrameLoadTypeReloadBypassingCache |
| ? WebCachePolicy::kBypassingCache |
| : WebCachePolicy::kValidatingCacheData; |
| if (!document_loader_ || !document_loader_->GetHistoryItem()) |
| return ResourceRequest(); |
| ResourceRequest request = |
| document_loader_->GetHistoryItem()->GenerateResourceRequest(cache_policy); |
| |
| // ClientRedirectPolicy is an indication that this load was triggered by some |
| // direct interaction with the page. If this reload is not a client redirect, |
| // we should reuse the referrer from the original load of the current |
| // document. If this reload is a client redirect (e.g., location.reload()), it |
| // was initiated by something in the current document and should therefore |
| // show the current document's url as the referrer. |
| if (client_redirect_policy == ClientRedirectPolicy::kClientRedirect) { |
| request.SetHTTPReferrer(SecurityPolicy::GenerateReferrer( |
| frame_->GetDocument()->GetReferrerPolicy(), |
| frame_->GetDocument()->Url(), |
| frame_->GetDocument()->OutgoingReferrer())); |
| } |
| |
| if (!override_url.IsEmpty()) { |
| request.SetURL(override_url); |
| request.ClearHTTPReferrer(); |
| } |
| request.SetServiceWorkerMode(frame_load_type == |
| kFrameLoadTypeReloadBypassingCache |
| ? WebURLRequest::ServiceWorkerMode::kNone |
| : WebURLRequest::ServiceWorkerMode::kAll); |
| return request; |
| } |
| |
| FrameLoader::FrameLoader(LocalFrame* frame) |
| : frame_(frame), |
| progress_tracker_(ProgressTracker::Create(frame)), |
| in_stop_all_loaders_(false), |
| forced_sandbox_flags_(kSandboxNone), |
| dispatching_did_clear_window_object_in_main_world_(false), |
| protect_provisional_loader_(false), |
| detached_(false) { |
| DCHECK(frame_); |
| TRACE_EVENT_OBJECT_CREATED_WITH_ID("loading", "FrameLoader", this); |
| TakeObjectSnapshot(); |
| } |
| |
| FrameLoader::~FrameLoader() { |
| DCHECK(detached_); |
| } |
| |
| DEFINE_TRACE(FrameLoader) { |
| visitor->Trace(frame_); |
| visitor->Trace(progress_tracker_); |
| visitor->Trace(document_loader_); |
| visitor->Trace(provisional_document_loader_); |
| } |
| |
| void FrameLoader::Init() { |
| ScriptForbiddenScope forbid_scripts; |
| ResourceRequest initial_request(KURL(kParsedURLString, g_empty_string)); |
| initial_request.SetRequestContext(WebURLRequest::kRequestContextInternal); |
| initial_request.SetFrameType(frame_->IsMainFrame() |
| ? WebURLRequest::kFrameTypeTopLevel |
| : WebURLRequest::kFrameTypeNested); |
| provisional_document_loader_ = |
| Client()->CreateDocumentLoader(frame_, initial_request, SubstituteData(), |
| ClientRedirectPolicy::kNotClientRedirect); |
| provisional_document_loader_->StartLoadingMainResource(); |
| frame_->GetDocument()->CancelParsing(); |
| state_machine_.AdvanceTo( |
| FrameLoaderStateMachine::kDisplayingInitialEmptyDocument); |
| // Suppress finish notifications for initial empty documents, since they don't |
| // generate start notifications. |
| document_loader_->SetSentDidFinishLoad(); |
| if (frame_->GetPage()->Suspended()) |
| SetDefersLoading(true); |
| TakeObjectSnapshot(); |
| } |
| |
| LocalFrameClient* FrameLoader::Client() const { |
| return static_cast<LocalFrameClient*>(frame_->Client()); |
| } |
| |
| void FrameLoader::SetDefersLoading(bool defers) { |
| if (provisional_document_loader_) |
| provisional_document_loader_->Fetcher()->SetDefersLoading(defers); |
| |
| if (Document* document = frame_->GetDocument()) { |
| document->Fetcher()->SetDefersLoading(defers); |
| if (defers) |
| document->SuspendScheduledTasks(); |
| else |
| document->ResumeScheduledTasks(); |
| } |
| |
| if (!defers) |
| frame_->GetNavigationScheduler().StartTimer(); |
| } |
| |
| void FrameLoader::SaveScrollState() { |
| if (!document_loader_ || !document_loader_->GetHistoryItem() || |
| !frame_->View()) |
| return; |
| |
| // Shouldn't clobber anything if we might still restore later. |
| if (NeedsHistoryItemRestore(document_loader_->LoadType()) && |
| !document_loader_->GetInitialScrollState().was_scrolled_by_user) |
| return; |
| |
| HistoryItem* history_item = document_loader_->GetHistoryItem(); |
| if (ScrollableArea* layout_scrollable_area = |
| frame_->View()->LayoutViewportScrollableArea()) |
| history_item->SetScrollOffset(layout_scrollable_area->GetScrollOffset()); |
| history_item->SetVisualViewportScrollOffset(ToScrollOffset( |
| frame_->GetPage()->GetVisualViewport().VisibleRect().Location())); |
| |
| if (frame_->IsMainFrame()) |
| history_item->SetPageScaleFactor(frame_->GetPage()->PageScaleFactor()); |
| |
| Client()->DidUpdateCurrentHistoryItem(); |
| } |
| |
| void FrameLoader::DispatchUnloadEvent() { |
| FrameNavigationDisabler navigation_disabler(*frame_); |
| |
| // If the frame is unloading, the provisional loader should no longer be |
| // protected. It will be detached soon. |
| protect_provisional_loader_ = false; |
| SaveScrollState(); |
| |
| if (frame_->GetDocument() && !SVGImage::IsInSVGImage(frame_->GetDocument())) |
| frame_->GetDocument()->DispatchUnloadEvents(); |
| } |
| |
| void FrameLoader::DidExplicitOpen() { |
| // Calling document.open counts as committing the first real document load. |
| if (!state_machine_.CommittedFirstRealDocumentLoad()) |
| state_machine_.AdvanceTo(FrameLoaderStateMachine::kCommittedFirstRealLoad); |
| |
| // Only model a document.open() as part of a navigation if its parent is not |
| // done or in the process of completing. |
| if (Frame* parent = frame_->Tree().Parent()) { |
| if ((parent->IsLocalFrame() && |
| ToLocalFrame(parent)->GetDocument()->LoadEventStillNeeded()) || |
| (parent->IsRemoteFrame() && parent->IsLoading())) { |
| progress_tracker_->ProgressStarted(document_loader_->LoadType()); |
| } |
| } |
| |
| // Prevent window.open(url) -- eg window.open("about:blank") -- from blowing |
| // away results from a subsequent window.document.open / window.document.write |
| // call. Canceling redirection here works for all cases because document.open |
| // implicitly precedes document.write. |
| frame_->GetNavigationScheduler().Cancel(); |
| } |
| |
| // This is only called by ScriptController::executeScriptIfJavaScriptURL and |
| // always contains the result of evaluating a javascript: url. This is the |
| // <iframe src="javascript:'html'"> case. |
| void FrameLoader::ReplaceDocumentWhileExecutingJavaScriptURL( |
| const String& source, |
| Document* owner_document) { |
| if (!frame_->GetDocument()->Loader() || |
| frame_->GetDocument()->PageDismissalEventBeingDispatched() != |
| Document::kNoDismissal) |
| return; |
| |
| DocumentLoader* document_loader(frame_->GetDocument()->Loader()); |
| |
| UseCounter::Count(*frame_->GetDocument(), |
| UseCounter::kReplaceDocumentViaJavaScriptURL); |
| |
| // Prepare a DocumentInit before clearing the frame, because it may need to |
| // inherit an aliased security context. |
| DocumentInit init(owner_document, frame_->GetDocument()->Url(), frame_); |
| init.WithNewRegistrationContext(); |
| |
| StopAllLoaders(); |
| // Don't allow any new child frames to load in this frame: attaching a new |
| // child frame during or after detaching children results in an attached |
| // frame on a detached DOM tree, which is bad. |
| SubframeLoadingDisabler disabler(frame_->GetDocument()); |
| frame_->DetachChildren(); |
| frame_->GetDocument()->Shutdown(); |
| |
| // detachChildren() potentially detaches the frame from the document. The |
| // loading cannot continue in that case. |
| if (!frame_->GetPage()) |
| return; |
| |
| Client()->TransitionToCommittedForNewPage(); |
| document_loader->ReplaceDocumentWhileExecutingJavaScriptURL(init, source); |
| } |
| |
| void FrameLoader::FinishedParsing() { |
| if (state_machine_.CreatingInitialEmptyDocument()) |
| return; |
| |
| progress_tracker_->FinishedParsing(); |
| |
| if (Client()) { |
| ScriptForbiddenScope forbid_scripts; |
| Client()->DispatchDidFinishDocumentLoad(); |
| } |
| |
| if (Client()) { |
| Client()->RunScriptsAtDocumentReady( |
| document_loader_ ? document_loader_->IsCommittedButEmpty() : true); |
| } |
| |
| frame_->GetDocument()->CheckCompleted(); |
| |
| if (!frame_->View()) |
| return; |
| |
| // Check if the scrollbars are really needed for the content. If not, remove |
| // them, relayout, and repaint. |
| frame_->View()->RestoreScrollbar(); |
| ProcessFragment(frame_->GetDocument()->Url(), document_loader_->LoadType(), |
| kNavigationToDifferentDocument); |
| } |
| |
| bool FrameLoader::AllAncestorsAreComplete() const { |
| for (Frame* ancestor = frame_; ancestor; |
| ancestor = ancestor->Tree().Parent()) { |
| if (ancestor->IsLoading()) |
| return false; |
| } |
| return true; |
| } |
| |
| void FrameLoader::DidFinishNavigation() { |
| // We should have either finished the provisional or committed navigation if |
| // this is called. Only delcare the whole frame finished if neither is in |
| // progress. |
| DCHECK(document_loader_->SentDidFinishLoad() || !HasProvisionalNavigation()); |
| if (!document_loader_->SentDidFinishLoad() || HasProvisionalNavigation()) |
| return; |
| |
| if (frame_->IsLoading()) { |
| progress_tracker_->ProgressCompleted(); |
| // Retry restoring scroll offset since finishing loading disables content |
| // size clamping. |
| RestoreScrollPositionAndViewState(); |
| if (document_loader_) |
| document_loader_->SetLoadType(kFrameLoadTypeStandard); |
| frame_->DomWindow()->FinishedLoading(); |
| } |
| |
| Frame* parent = frame_->Tree().Parent(); |
| if (parent && parent->IsLocalFrame()) |
| ToLocalFrame(parent)->GetDocument()->CheckCompleted(); |
| } |
| |
| Frame* FrameLoader::Opener() { |
| return Client() ? Client()->Opener() : 0; |
| } |
| |
| void FrameLoader::SetOpener(LocalFrame* opener) { |
| // If the frame is already detached, the opener has already been cleared. |
| if (Client()) |
| Client()->SetOpener(opener); |
| } |
| |
| bool FrameLoader::AllowPlugins(ReasonForCallingAllowPlugins reason) { |
| // With Oilpan, a FrameLoader might be accessed after the Page has been |
| // detached. FrameClient will not be accessible, so bail early. |
| if (!Client()) |
| return false; |
| Settings* settings = frame_->GetSettings(); |
| bool allowed = frame_->GetContentSettingsClient()->AllowPlugins( |
| settings && settings->GetPluginsEnabled()); |
| if (!allowed && reason == kAboutToInstantiatePlugin) |
| frame_->GetContentSettingsClient()->DidNotAllowPlugins(); |
| return allowed; |
| } |
| |
| void FrameLoader::UpdateForSameDocumentNavigation( |
| const KURL& new_url, |
| SameDocumentNavigationSource same_document_navigation_source, |
| PassRefPtr<SerializedScriptValue> data, |
| HistoryScrollRestorationType scroll_restoration_type, |
| FrameLoadType type, |
| Document* initiating_document) { |
| TRACE_EVENT1("blink", "FrameLoader::updateForSameDocumentNavigation", "url", |
| new_url.GetString().Ascii().data()); |
| |
| // Generate start and stop notifications only when loader is completed so that |
| // we don't fire them for fragment redirection that happens in window.onload |
| // handler. See https://bugs.webkit.org/show_bug.cgi?id=31838 |
| // Do not fire the notifications if the frame is concurrently navigating away |
| // from the document, since a new document is already loading. |
| if (frame_->GetDocument()->LoadEventFinished() && |
| !provisional_document_loader_) |
| Client()->DidStartLoading(kNavigationWithinSameDocument); |
| |
| // Update the data source's request with the new URL to fake the URL change |
| frame_->GetDocument()->SetURL(new_url); |
| GetDocumentLoader()->UpdateForSameDocumentNavigation( |
| new_url, same_document_navigation_source, std::move(data), |
| scroll_restoration_type, type, initiating_document); |
| |
| Client()->DispatchDidReceiveTitle(frame_->GetDocument()->title()); |
| if (frame_->GetDocument()->LoadEventFinished() && |
| !provisional_document_loader_) |
| Client()->DidStopLoading(); |
| } |
| |
| void FrameLoader::DetachDocumentLoader(Member<DocumentLoader>& loader) { |
| if (!loader) |
| return; |
| |
| FrameNavigationDisabler navigation_disabler(*frame_); |
| loader->DetachFromFrame(); |
| loader = nullptr; |
| } |
| |
| void FrameLoader::LoadInSameDocument( |
| const KURL& url, |
| PassRefPtr<SerializedScriptValue> state_object, |
| FrameLoadType frame_load_type, |
| HistoryItem* history_item, |
| ClientRedirectPolicy client_redirect, |
| Document* initiating_document) { |
| // If we have a state object, we cannot also be a new navigation. |
| DCHECK(!state_object || frame_load_type == kFrameLoadTypeBackForward); |
| |
| // If we have a provisional request for a different document, a fragment |
| // scroll should cancel it. |
| DetachDocumentLoader(provisional_document_loader_); |
| |
| if (!frame_->GetPage()) |
| return; |
| SaveScrollState(); |
| |
| KURL old_url = frame_->GetDocument()->Url(); |
| bool hash_change = EqualIgnoringFragmentIdentifier(url, old_url) && |
| url.FragmentIdentifier() != old_url.FragmentIdentifier(); |
| if (hash_change) { |
| // If we were in the autoscroll/middleClickAutoscroll mode we want to stop |
| // it before following the link to the anchor |
| frame_->GetEventHandler().StopAutoscroll(); |
| frame_->DomWindow()->EnqueueHashchangeEvent(old_url, url); |
| } |
| document_loader_->SetIsClientRedirect(client_redirect == |
| ClientRedirectPolicy::kClientRedirect); |
| if (history_item) |
| document_loader_->SetItemForHistoryNavigation(history_item); |
| UpdateForSameDocumentNavigation(url, kSameDocumentNavigationDefault, nullptr, |
| kScrollRestorationAuto, frame_load_type, |
| initiating_document); |
| |
| document_loader_->GetInitialScrollState().was_scrolled_by_user = false; |
| |
| frame_->GetDocument()->CheckCompleted(); |
| |
| frame_->DomWindow()->StatePopped(state_object |
| ? std::move(state_object) |
| : SerializedScriptValue::NullValue()); |
| |
| if (history_item) |
| RestoreScrollPositionAndViewStateForLoadType(frame_load_type); |
| |
| // We need to scroll to the fragment whether or not a hash change occurred, |
| // since the user might have scrolled since the previous navigation. |
| ProcessFragment(url, frame_load_type, kNavigationWithinSameDocument); |
| TakeObjectSnapshot(); |
| } |
| |
| // static |
| void FrameLoader::SetReferrerForFrameRequest(FrameLoadRequest& frame_request) { |
| ResourceRequest& request = frame_request.GetResourceRequest(); |
| Document* origin_document = frame_request.OriginDocument(); |
| |
| if (!origin_document) |
| return; |
| // Anchor elements with the 'referrerpolicy' attribute will have already set |
| // the referrer on the request. |
| if (request.DidSetHTTPReferrer()) |
| return; |
| if (frame_request.GetShouldSendReferrer() == kNeverSendReferrer) |
| return; |
| |
| // Always use the initiating document to generate the referrer. We need to |
| // generateReferrer(), because we haven't enforced ReferrerPolicy or |
| // https->http referrer suppression yet. |
| Referrer referrer = SecurityPolicy::GenerateReferrer( |
| origin_document->GetReferrerPolicy(), request.Url(), |
| origin_document->OutgoingReferrer()); |
| |
| request.SetHTTPReferrer(referrer); |
| request.AddHTTPOriginIfNeeded(referrer.referrer); |
| } |
| |
| FrameLoadType FrameLoader::DetermineFrameLoadType( |
| const FrameLoadRequest& request) { |
| if (frame_->Tree().Parent() && |
| !state_machine_.CommittedFirstRealDocumentLoad()) |
| return kFrameLoadTypeInitialInChildFrame; |
| if (!frame_->Tree().Parent() && !Client()->BackForwardLength()) { |
| if (Opener() && request.GetResourceRequest().Url().IsEmpty()) |
| return kFrameLoadTypeReplaceCurrentItem; |
| return kFrameLoadTypeStandard; |
| } |
| if (request.GetResourceRequest().GetCachePolicy() == |
| WebCachePolicy::kValidatingCacheData) |
| return kFrameLoadTypeReload; |
| if (request.GetResourceRequest().GetCachePolicy() == |
| WebCachePolicy::kBypassingCache) |
| return kFrameLoadTypeReloadBypassingCache; |
| // From the HTML5 spec for location.assign(): |
| // "If the browsing context's session history contains only one Document, |
| // and that was the about:blank Document created when the browsing context |
| // was created, then the navigation must be done with replacement enabled." |
| if (request.ReplacesCurrentItem() || |
| (!state_machine_.CommittedMultipleRealLoads() && |
| DeprecatedEqualIgnoringCase(frame_->GetDocument()->Url(), BlankURL()))) |
| return kFrameLoadTypeReplaceCurrentItem; |
| |
| if (request.GetResourceRequest().Url() == document_loader_->UrlForHistory()) { |
| if (request.GetResourceRequest().HttpMethod() == HTTPNames::POST) |
| return kFrameLoadTypeStandard; |
| if (!request.OriginDocument()) |
| return kFrameLoadTypeReload; |
| return kFrameLoadTypeReplaceCurrentItem; |
| } |
| |
| if (request.GetSubstituteData().FailingURL() == |
| document_loader_->UrlForHistory() && |
| document_loader_->LoadType() == kFrameLoadTypeReload) |
| return kFrameLoadTypeReload; |
| |
| if (request.OriginDocument() && |
| !request.OriginDocument()->CanCreateHistoryEntry()) |
| return kFrameLoadTypeReplaceCurrentItem; |
| |
| if (request.GetResourceRequest().Url().IsEmpty() && |
| request.GetSubstituteData().FailingURL().IsEmpty()) { |
| return kFrameLoadTypeReplaceCurrentItem; |
| } |
| |
| return kFrameLoadTypeStandard; |
| } |
| |
| bool FrameLoader::PrepareRequestForThisFrame(FrameLoadRequest& request) { |
| // If no origin Document* was specified, skip remaining security checks and |
| // assume the caller has fully initialized the FrameLoadRequest. |
| if (!request.OriginDocument()) |
| return true; |
| |
| KURL url = request.GetResourceRequest().Url(); |
| if (frame_->GetScriptController().ExecuteScriptIfJavaScriptURL(url, nullptr)) |
| return false; |
| |
| if (!request.OriginDocument()->GetSecurityOrigin()->CanDisplay(url)) { |
| request.OriginDocument()->AddConsoleMessage(ConsoleMessage::Create( |
| kSecurityMessageSource, kErrorMessageLevel, |
| "Not allowed to load local resource: " + url.ElidedString())); |
| return false; |
| } |
| |
| // Block renderer-initiated loads of data URLs in the top frame. If the mime |
| // type of the data URL is supported, the URL will eventually be rendered, so |
| // block it here. Otherwise, the load might be handled by a plugin or end up |
| // as a download, so allow it to let the embedder figure out what to do with |
| // it. |
| if (frame_->IsMainFrame() && |
| !request.GetResourceRequest().IsSameDocumentNavigation() && |
| !frame_->Client()->AllowContentInitiatedDataUrlNavigations( |
| request.OriginDocument()->Url()) && |
| url.ProtocolIsData() && NetworkUtils::IsDataURLMimeTypeSupported(url)) { |
| frame_->GetDocument()->AddConsoleMessage(ConsoleMessage::Create( |
| kSecurityMessageSource, kErrorMessageLevel, |
| "Not allowed to navigate top frame to data URL: " + |
| url.ElidedString())); |
| return false; |
| } |
| |
| if (!request.Form() && request.FrameName().IsEmpty()) |
| request.SetFrameName(frame_->GetDocument()->BaseTarget()); |
| return true; |
| } |
| |
| static bool ShouldNavigateTargetFrame(NavigationPolicy policy) { |
| switch (policy) { |
| case kNavigationPolicyCurrentTab: |
| return true; |
| |
| // Navigation will target a *new* frame (e.g. because of a ctrl-click), |
| // so the target frame can be ignored. |
| case kNavigationPolicyNewBackgroundTab: |
| case kNavigationPolicyNewForegroundTab: |
| case kNavigationPolicyNewWindow: |
| case kNavigationPolicyNewPopup: |
| return false; |
| |
| // Navigation won't really target any specific frame, |
| // so the target frame can be ignored. |
| case kNavigationPolicyIgnore: |
| case kNavigationPolicyDownload: |
| return false; |
| |
| case kNavigationPolicyHandledByClient: |
| // Impossible, because at this point we shouldn't yet have called |
| // client()->decidePolicyForNavigation(...). |
| NOTREACHED(); |
| return true; |
| |
| default: |
| NOTREACHED() << policy; |
| return true; |
| } |
| } |
| |
| static NavigationType DetermineNavigationType(FrameLoadType frame_load_type, |
| bool is_form_submission, |
| bool have_event) { |
| bool is_reload = IsReloadLoadType(frame_load_type); |
| bool is_back_forward = IsBackForwardLoadType(frame_load_type); |
| if (is_form_submission) { |
| return (is_reload || is_back_forward) ? kNavigationTypeFormResubmitted |
| : kNavigationTypeFormSubmitted; |
| } |
| if (have_event) |
| return kNavigationTypeLinkClicked; |
| if (is_reload) |
| return kNavigationTypeReload; |
| if (is_back_forward) |
| return kNavigationTypeBackForward; |
| return kNavigationTypeOther; |
| } |
| |
| static WebURLRequest::RequestContext DetermineRequestContextFromNavigationType( |
| const NavigationType navigation_type) { |
| switch (navigation_type) { |
| case kNavigationTypeLinkClicked: |
| return WebURLRequest::kRequestContextHyperlink; |
| |
| case kNavigationTypeOther: |
| return WebURLRequest::kRequestContextLocation; |
| |
| case kNavigationTypeFormResubmitted: |
| case kNavigationTypeFormSubmitted: |
| return WebURLRequest::kRequestContextForm; |
| |
| case kNavigationTypeBackForward: |
| case kNavigationTypeReload: |
| return WebURLRequest::kRequestContextInternal; |
| } |
| NOTREACHED(); |
| return WebURLRequest::kRequestContextHyperlink; |
| } |
| |
| static NavigationPolicy NavigationPolicyForRequest( |
| const FrameLoadRequest& request) { |
| NavigationPolicy policy = kNavigationPolicyCurrentTab; |
| Event* event = request.TriggeringEvent(); |
| if (!event) |
| return policy; |
| |
| if (request.Form() && event->UnderlyingEvent()) |
| event = event->UnderlyingEvent(); |
| |
| if (event->IsMouseEvent()) { |
| MouseEvent* mouse_event = ToMouseEvent(event); |
| NavigationPolicyFromMouseEvent( |
| mouse_event->button(), mouse_event->ctrlKey(), mouse_event->shiftKey(), |
| mouse_event->altKey(), mouse_event->metaKey(), &policy); |
| } else if (event->IsKeyboardEvent()) { |
| // The click is simulated when triggering the keypress event. |
| KeyboardEvent* key_event = ToKeyboardEvent(event); |
| NavigationPolicyFromMouseEvent(0, key_event->ctrlKey(), |
| key_event->shiftKey(), key_event->altKey(), |
| key_event->metaKey(), &policy); |
| } else if (event->IsGestureEvent()) { |
| // The click is simulated when triggering the gesture-tap event |
| GestureEvent* gesture_event = ToGestureEvent(event); |
| NavigationPolicyFromMouseEvent( |
| 0, gesture_event->ctrlKey(), gesture_event->shiftKey(), |
| gesture_event->altKey(), gesture_event->metaKey(), &policy); |
| } |
| return policy; |
| } |
| |
| void FrameLoader::Load(const FrameLoadRequest& passed_request, |
| FrameLoadType frame_load_type, |
| HistoryItem* history_item, |
| HistoryLoadType history_load_type) { |
| DCHECK(frame_->GetDocument()); |
| |
| if (IsBackForwardLoadType(frame_load_type) && !frame_->IsNavigationAllowed()) |
| return; |
| |
| if (in_stop_all_loaders_) |
| return; |
| |
| FrameLoadRequest request(passed_request); |
| request.GetResourceRequest().SetHasUserGesture( |
| UserGestureIndicator::ProcessingUserGesture()); |
| |
| if (!PrepareRequestForThisFrame(request)) |
| return; |
| |
| // Form submissions appear to need their special-case of finding the target at |
| // schedule rather than at fire. |
| Frame* target_frame = request.Form() |
| ? nullptr |
| : frame_->FindFrameForNavigation( |
| AtomicString(request.FrameName()), *frame_); |
| |
| NavigationPolicy policy = NavigationPolicyForRequest(request); |
| if (target_frame && target_frame != frame_ && |
| ShouldNavigateTargetFrame(policy)) { |
| if (target_frame->IsLocalFrame() && |
| !ToLocalFrame(target_frame)->IsNavigationAllowed()) { |
| return; |
| } |
| |
| bool was_in_same_page = target_frame->GetPage() == frame_->GetPage(); |
| |
| request.SetFrameName("_self"); |
| target_frame->Navigate(request); |
| Page* page = target_frame->GetPage(); |
| if (!was_in_same_page && page) |
| page->GetChromeClient().Focus(); |
| return; |
| } |
| |
| SetReferrerForFrameRequest(request); |
| |
| if (!target_frame && !request.FrameName().IsEmpty()) { |
| if (policy == kNavigationPolicyDownload) { |
| Client()->LoadURLExternally(request.GetResourceRequest(), |
| kNavigationPolicyDownload, String(), false); |
| return; // Navigation/download will be handled by the client. |
| } else if (ShouldNavigateTargetFrame(policy)) { |
| request.GetResourceRequest().SetFrameType( |
| WebURLRequest::kFrameTypeAuxiliary); |
| CreateWindowForRequest(request, *frame_, policy); |
| return; // Navigation will be handled by the new frame/window. |
| } |
| } |
| |
| if (!frame_->IsNavigationAllowed()) |
| return; |
| |
| const KURL& url = request.GetResourceRequest().Url(); |
| FrameLoadType new_load_type = (frame_load_type == kFrameLoadTypeStandard) |
| ? DetermineFrameLoadType(request) |
| : frame_load_type; |
| bool same_document_history_navigation = |
| IsBackForwardLoadType(new_load_type) && |
| history_load_type == kHistorySameDocumentLoad; |
| bool same_document_navigation = |
| policy == kNavigationPolicyCurrentTab && |
| ShouldPerformFragmentNavigation(request.Form(), |
| request.GetResourceRequest().HttpMethod(), |
| new_load_type, url); |
| |
| // Perform same document navigation. |
| if (same_document_history_navigation || same_document_navigation) { |
| DCHECK(history_item || !same_document_history_navigation); |
| RefPtr<SerializedScriptValue> state_object = |
| same_document_history_navigation ? history_item->StateObject() |
| : nullptr; |
| |
| if (!same_document_history_navigation) { |
| document_loader_->SetNavigationType(DetermineNavigationType( |
| new_load_type, false, request.TriggeringEvent())); |
| if (ShouldTreatURLAsSameAsCurrent(url)) |
| new_load_type = kFrameLoadTypeReplaceCurrentItem; |
| } |
| |
| LoadInSameDocument(url, state_object, new_load_type, history_item, |
| request.ClientRedirect(), request.OriginDocument()); |
| return; |
| } |
| |
| // PlzNavigate |
| // If the loader classifies this navigation as a different document navigation |
| // while the browser intended the navigation to be same-document, it means |
| // that a different navigation must have committed while the IPC was sent. |
| // This navigation is no more same-document. The navigation is simply dropped. |
| if (request.GetResourceRequest().IsSameDocumentNavigation()) |
| return; |
| |
| StartLoad(request, new_load_type, policy, history_item); |
| } |
| |
| SubstituteData FrameLoader::DefaultSubstituteDataForURL(const KURL& url) { |
| if (!ShouldTreatURLAsSrcdocDocument(url)) |
| return SubstituteData(); |
| String srcdoc = frame_->DeprecatedLocalOwner()->FastGetAttribute(srcdocAttr); |
| DCHECK(!srcdoc.IsNull()); |
| CString encoded_srcdoc = srcdoc.Utf8(); |
| return SubstituteData( |
| SharedBuffer::Create(encoded_srcdoc.data(), encoded_srcdoc.length()), |
| "text/html", "UTF-8", KURL()); |
| } |
| |
| void FrameLoader::StopAllLoaders() { |
| if (frame_->GetDocument()->PageDismissalEventBeingDispatched() != |
| Document::kNoDismissal) |
| return; |
| |
| // If this method is called from within this method, infinite recursion can |
| // occur (3442218). Avoid this. |
| if (in_stop_all_loaders_) |
| return; |
| |
| in_stop_all_loaders_ = true; |
| |
| for (Frame* child = frame_->Tree().FirstChild(); child; |
| child = child->Tree().NextSibling()) { |
| if (child->IsLocalFrame()) |
| ToLocalFrame(child)->Loader().StopAllLoaders(); |
| } |
| |
| frame_->GetDocument()->CancelParsing(); |
| if (document_loader_) |
| document_loader_->Fetcher()->StopFetching(); |
| if (!protect_provisional_loader_) |
| DetachDocumentLoader(provisional_document_loader_); |
| |
| frame_->GetNavigationScheduler().Cancel(); |
| |
| // It's possible that the above actions won't have stopped loading if load |
| // completion had been blocked on parsing or if we were in the middle of |
| // committing an empty document. In that case, emulate a failed navigation. |
| if (!provisional_document_loader_ && document_loader_ && |
| frame_->IsLoading()) { |
| document_loader_->LoadFailed( |
| ResourceError::CancelledError(document_loader_->Url())); |
| } |
| |
| in_stop_all_loaders_ = false; |
| TakeObjectSnapshot(); |
| } |
| |
| void FrameLoader::DidAccessInitialDocument() { |
| // We only need to notify the client for the main frame. |
| if (IsLoadingMainFrame()) { |
| // Forbid script execution to prevent re-entering V8, since this is called |
| // from a binding security check. |
| ScriptForbiddenScope forbid_scripts; |
| if (Client()) |
| Client()->DidAccessInitialDocument(); |
| } |
| } |
| |
| bool FrameLoader::PrepareForCommit() { |
| PluginScriptForbiddenScope forbid_plugin_destructor_scripting; |
| DocumentLoader* pdl = provisional_document_loader_; |
| |
| if (frame_->GetDocument()) { |
| unsigned node_count = 0; |
| for (Frame* frame = frame_; frame; frame = frame->Tree().TraverseNext()) { |
| if (frame->IsLocalFrame()) { |
| LocalFrame* local_frame = ToLocalFrame(frame); |
| node_count += local_frame->GetDocument()->NodeCount(); |
| } |
| } |
| unsigned total_node_count = |
| InstanceCounters::CounterValue(InstanceCounters::kNodeCounter); |
| float ratio = static_cast<float>(node_count) / total_node_count; |
| ThreadState::Current()->SchedulePageNavigationGCIfNeeded(ratio); |
| } |
| |
| // Don't allow any new child frames to load in this frame: attaching a new |
| // child frame during or after detaching children results in an attached frame |
| // on a detached DOM tree, which is bad. |
| SubframeLoadingDisabler disabler(frame_->GetDocument()); |
| if (document_loader_) { |
| Client()->DispatchWillCommitProvisionalLoad(); |
| DispatchUnloadEvent(); |
| } |
| frame_->DetachChildren(); |
| // The previous calls to dispatchUnloadEvent() and detachChildren() can |
| // execute arbitrary script via things like unload events. If the executed |
| // script intiates a new load or causes the current frame to be detached, we |
| // need to abandon the current load. |
| if (pdl != provisional_document_loader_) |
| return false; |
| // detachFromFrame() will abort XHRs that haven't completed, which can trigger |
| // event listeners for 'abort'. These event listeners might call |
| // window.stop(), which will in turn detach the provisional document loader. |
| // At this point, the provisional document loader should not detach, because |
| // then the FrameLoader would not have any attached DocumentLoaders. |
| if (document_loader_) { |
| AutoReset<bool> in_detach_document_loader(&protect_provisional_loader_, |
| true); |
| DetachDocumentLoader(document_loader_); |
| } |
| // 'abort' listeners can also detach the frame. |
| if (!frame_->Client()) |
| return false; |
| DCHECK_EQ(provisional_document_loader_, pdl); |
| // No more events will be dispatched so detach the Document. |
| // TODO(yoav): Should we also be nullifying domWindow's document (or |
| // domWindow) since the doc is now detached? |
| if (frame_->GetDocument()) |
| frame_->GetDocument()->Shutdown(); |
| document_loader_ = provisional_document_loader_.Release(); |
| if (document_loader_) |
| document_loader_->MarkAsCommitted(); |
| TakeObjectSnapshot(); |
| |
| return true; |
| } |
| |
| void FrameLoader::CommitProvisionalLoad() { |
| DCHECK(Client()->HasWebView()); |
| |
| // Check if the destination page is allowed to access the previous page's |
| // timing information. |
| if (frame_->GetDocument()) { |
| RefPtr<SecurityOrigin> security_origin = SecurityOrigin::Create( |
| provisional_document_loader_->GetRequest().Url()); |
| provisional_document_loader_->GetTiming() |
| .SetHasSameOriginAsPreviousDocument( |
| security_origin->CanRequest(frame_->GetDocument()->Url())); |
| } |
| |
| if (!PrepareForCommit()) |
| return; |
| |
| // If we are loading the mainframe, or a frame that is a local root, it is |
| // important to explicitly set the event listenener properties to Nothing as |
| // this triggers notifications to the client. Clients may assume the presence |
| // of handlers for touch and wheel events, so these notifications tell it |
| // there are (presently) no handlers. |
| if (frame_->IsLocalRoot()) { |
| frame_->GetPage()->GetChromeClient().SetEventListenerProperties( |
| frame_, WebEventListenerClass::kTouchStartOrMove, |
| WebEventListenerProperties::kNothing); |
| frame_->GetPage()->GetChromeClient().SetEventListenerProperties( |
| frame_, WebEventListenerClass::kMouseWheel, |
| WebEventListenerProperties::kNothing); |
| frame_->GetPage()->GetChromeClient().SetEventListenerProperties( |
| frame_, WebEventListenerClass::kTouchEndOrCancel, |
| WebEventListenerProperties::kNothing); |
| } |
| |
| Client()->TransitionToCommittedForNewPage(); |
| |
| frame_->GetNavigationScheduler().Cancel(); |
| } |
| |
| bool FrameLoader::IsLoadingMainFrame() const { |
| return frame_->IsMainFrame(); |
| } |
| |
| void FrameLoader::RestoreScrollPositionAndViewState() { |
| if (!frame_->GetPage() || !GetDocumentLoader()) |
| return; |
| RestoreScrollPositionAndViewStateForLoadType(GetDocumentLoader()->LoadType()); |
| } |
| |
| void FrameLoader::RestoreScrollPositionAndViewStateForLoadType( |
| FrameLoadType load_type) { |
| LocalFrameView* view = frame_->View(); |
| if (!view || !view->LayoutViewportScrollableArea() || |
| !state_machine_.CommittedFirstRealDocumentLoad()) { |
| return; |
| } |
| if (!NeedsHistoryItemRestore(load_type)) |
| return; |
| HistoryItem* history_item = document_loader_->GetHistoryItem(); |
| if (!history_item || !history_item->DidSaveScrollOrScaleState()) |
| return; |
| |
| bool should_restore_scroll = |
| history_item->ScrollRestorationType() != kScrollRestorationManual; |
| bool should_restore_scale = history_item->PageScaleFactor(); |
| |
| // This tries to balance: |
| // 1. restoring as soon as possible |
| // 2. not overriding user scroll (TODO(majidvp): also respect user scale) |
| // 3. detecting clamping to avoid repeatedly popping the scroll position down |
| // as the page height increases |
| // 4. ignore clamp detection if we are not restoring scroll or after load |
| // completes because that may be because the page will never reach its |
| // previous height |
| bool can_restore_without_clamping = |
| view->LayoutViewportScrollableArea()->ClampScrollOffset( |
| history_item->GetScrollOffset()) == history_item->GetScrollOffset(); |
| bool can_restore_without_annoying_user = |
| !GetDocumentLoader()->GetInitialScrollState().was_scrolled_by_user && |
| (can_restore_without_clamping || !frame_->IsLoading() || |
| !should_restore_scroll); |
| if (!can_restore_without_annoying_user) |
| return; |
| |
| if (should_restore_scroll) { |
| view->LayoutViewportScrollableArea()->SetScrollOffset( |
| history_item->GetScrollOffset(), kProgrammaticScroll); |
| } |
| |
| // For main frame restore scale and visual viewport position |
| if (frame_->IsMainFrame()) { |
| ScrollOffset visual_viewport_offset( |
| history_item->VisualViewportScrollOffset()); |
| |
| // If the visual viewport's offset is (-1, -1) it means the history item |
| // is an old version of HistoryItem so distribute the scroll between |
| // the main frame and the visual viewport as best as we can. |
| if (visual_viewport_offset.Width() == -1 && |
| visual_viewport_offset.Height() == -1) { |
| visual_viewport_offset = |
| history_item->GetScrollOffset() - |
| view->LayoutViewportScrollableArea()->GetScrollOffset(); |
| } |
| |
| VisualViewport& visual_viewport = frame_->GetPage()->GetVisualViewport(); |
| if (should_restore_scale && should_restore_scroll) { |
| visual_viewport.SetScaleAndLocation(history_item->PageScaleFactor(), |
| FloatPoint(visual_viewport_offset)); |
| } else if (should_restore_scale) { |
| visual_viewport.SetScale(history_item->PageScaleFactor()); |
| } else if (should_restore_scroll) { |
| visual_viewport.SetLocation(FloatPoint(visual_viewport_offset)); |
| } |
| |
| if (ScrollingCoordinator* scrolling_coordinator = |
| frame_->GetPage()->GetScrollingCoordinator()) |
| scrolling_coordinator->FrameViewRootLayerDidChange(view); |
| } |
| |
| GetDocumentLoader()->GetInitialScrollState().did_restore_from_history = true; |
| } |
| |
| String FrameLoader::UserAgent() const { |
| String user_agent = Client()->UserAgent(); |
| probe::applyUserAgentOverride(frame_, &user_agent); |
| return user_agent; |
| } |
| |
| void FrameLoader::Detach() { |
| DetachDocumentLoader(document_loader_); |
| DetachDocumentLoader(provisional_document_loader_); |
| |
| if (progress_tracker_) { |
| progress_tracker_->Dispose(); |
| progress_tracker_.Clear(); |
| } |
| |
| TRACE_EVENT_OBJECT_DELETED_WITH_ID("loading", "FrameLoader", this); |
| detached_ = true; |
| } |
| |
| void FrameLoader::DetachProvisionalDocumentLoader(DocumentLoader* loader) { |
| DCHECK_EQ(loader, provisional_document_loader_); |
| DetachDocumentLoader(provisional_document_loader_); |
| DidFinishNavigation(); |
| } |
| |
| bool FrameLoader::ShouldPerformFragmentNavigation(bool is_form_submission, |
| const String& http_method, |
| FrameLoadType load_type, |
| const KURL& url) { |
| // We don't do this if we are submitting a form with method other than "GET", |
| // explicitly reloading, currently displaying a frameset, or if the URL does |
| // not have a fragment. |
| return DeprecatedEqualIgnoringCase(http_method, HTTPNames::GET) && |
| !IsReloadLoadType(load_type) && |
| load_type != kFrameLoadTypeBackForward && |
| url.HasFragmentIdentifier() && |
| EqualIgnoringFragmentIdentifier(frame_->GetDocument()->Url(), url) |
| // We don't want to just scroll if a link from within a frameset is |
| // trying to reload the frameset into _top. |
| && !frame_->GetDocument()->IsFrameSet(); |
| } |
| |
| void FrameLoader::ProcessFragment(const KURL& url, |
| FrameLoadType frame_load_type, |
| LoadStartType load_start_type) { |
| LocalFrameView* view = frame_->View(); |
| if (!view) |
| return; |
| |
| // Leaking scroll position to a cross-origin ancestor would permit the |
| // so-called "framesniffing" attack. |
| Frame* boundary_frame = |
| url.HasFragmentIdentifier() |
| ? frame_->FindUnsafeParentScrollPropagationBoundary() |
| : 0; |
| |
| // FIXME: Handle RemoteFrames |
| if (boundary_frame && boundary_frame->IsLocalFrame()) { |
| ToLocalFrame(boundary_frame) |
| ->View() |
| ->SetSafeToPropagateScrollToParent(false); |
| } |
| |
| // If scroll position is restored from history fragment or scroll |
| // restoration type is manual, then we should not override it unless this |
| // is a same document reload. |
| bool should_scroll_to_fragment = |
| (load_start_type == kNavigationWithinSameDocument && |
| !IsBackForwardLoadType(frame_load_type)) || |
| (!GetDocumentLoader()->GetInitialScrollState().did_restore_from_history && |
| !(GetDocumentLoader()->GetHistoryItem() && |
| GetDocumentLoader()->GetHistoryItem()->ScrollRestorationType() == |
| kScrollRestorationManual)); |
| |
| view->ProcessUrlFragment(url, should_scroll_to_fragment |
| ? LocalFrameView::kUrlFragmentScroll |
| : LocalFrameView::kUrlFragmentDontScroll); |
| |
| if (boundary_frame && boundary_frame->IsLocalFrame()) |
| ToLocalFrame(boundary_frame) |
| ->View() |
| ->SetSafeToPropagateScrollToParent(true); |
| } |
| |
| bool FrameLoader::ShouldClose(bool is_reload) { |
| Page* page = frame_->GetPage(); |
| if (!page || !page->GetChromeClient().CanOpenBeforeUnloadConfirmPanel()) |
| return true; |
| |
| // Store all references to each subframe in advance since beforeunload's event |
| // handler may modify frame |
| HeapVector<Member<LocalFrame>> target_frames; |
| target_frames.push_back(frame_); |
| for (Frame* child = frame_->Tree().FirstChild(); child; |
| child = child->Tree().TraverseNext(frame_)) { |
| // FIXME: There is not yet any way to dispatch events to out-of-process |
| // frames. |
| if (child->IsLocalFrame()) |
| target_frames.push_back(ToLocalFrame(child)); |
| } |
| |
| bool should_close = false; |
| { |
| NavigationDisablerForBeforeUnload navigation_disabler; |
| size_t i; |
| |
| bool did_allow_navigation = false; |
| for (i = 0; i < target_frames.size(); i++) { |
| if (!target_frames[i]->Tree().IsDescendantOf(frame_)) |
| continue; |
| if (!target_frames[i]->GetDocument()->DispatchBeforeUnloadEvent( |
| page->GetChromeClient(), is_reload, did_allow_navigation)) |
| break; |
| } |
| |
| if (i == target_frames.size()) |
| should_close = true; |
| } |
| |
| return should_close; |
| } |
| |
| NavigationPolicy FrameLoader::ShouldContinueForNavigationPolicy( |
| const ResourceRequest& request, |
| const SubstituteData& substitute_data, |
| DocumentLoader* loader, |
| ContentSecurityPolicyDisposition |
| should_check_main_world_content_security_policy, |
| NavigationType type, |
| NavigationPolicy policy, |
| FrameLoadType frame_load_type, |
| bool is_client_redirect, |
| HTMLFormElement* form) { |
| // Don't ask if we are loading an empty URL. |
| if (request.Url().IsEmpty() || substitute_data.IsValid()) |
| return kNavigationPolicyCurrentTab; |
| |
| // Check for non-escaped new lines in the url. |
| if (request.Url().PotentiallyDanglingMarkup() && |
| request.Url().ProtocolIsInHTTPFamily()) { |
| Deprecation::CountDeprecation( |
| frame_, UseCounter::kCanRequestURLHTTPContainingNewline); |
| if (RuntimeEnabledFeatures::restrictCanRequestURLCharacterSetEnabled()) |
| return kNavigationPolicyIgnore; |
| } |
| |
| Settings* settings = frame_->GetSettings(); |
| if (MaybeCheckCSP(request, type, frame_, policy, |
| should_check_main_world_content_security_policy == |
| kCheckContentSecurityPolicy, |
| settings && settings->GetBrowserSideNavigationEnabled(), |
| ContentSecurityPolicy::CheckHeaderType::kCheckEnforce) == |
| kNavigationPolicyIgnore) { |
| return kNavigationPolicyIgnore; |
| } |
| |
| bool replaces_current_history_item = |
| frame_load_type == kFrameLoadTypeReplaceCurrentItem; |
| policy = Client()->DecidePolicyForNavigation( |
| request, loader, type, policy, replaces_current_history_item, |
| is_client_redirect, form, |
| should_check_main_world_content_security_policy); |
| if (policy == kNavigationPolicyCurrentTab || |
| policy == kNavigationPolicyIgnore || |
| policy == kNavigationPolicyHandledByClient || |
| policy == kNavigationPolicyHandledByClientForInitialHistory) { |
| return policy; |
| } |
| |
| Client()->LoadURLExternally(request, policy, String(), |
| replaces_current_history_item); |
| return kNavigationPolicyIgnore; |
| } |
| |
| NavigationPolicy FrameLoader::ShouldContinueForRedirectNavigationPolicy( |
| const ResourceRequest& request, |
| const SubstituteData& substitute_data, |
| DocumentLoader* loader, |
| ContentSecurityPolicyDisposition |
| should_check_main_world_content_security_policy, |
| NavigationType type, |
| NavigationPolicy policy, |
| FrameLoadType frame_load_type, |
| bool is_client_redirect, |
| HTMLFormElement* form) { |
| Settings* settings = frame_->GetSettings(); |
| // Check report-only CSP policies, which are not checked by |
| // ShouldContinueForNavigationPolicy. |
| MaybeCheckCSP(request, type, frame_, policy, |
| should_check_main_world_content_security_policy == |
| kCheckContentSecurityPolicy, |
| settings && settings->GetBrowserSideNavigationEnabled(), |
| ContentSecurityPolicy::CheckHeaderType::kCheckReportOnly); |
| return ShouldContinueForNavigationPolicy( |
| request, substitute_data, loader, |
| should_check_main_world_content_security_policy, type, policy, |
| frame_load_type, is_client_redirect, form); |
| } |
| |
| NavigationPolicy FrameLoader::CheckLoadCanStart( |
| FrameLoadRequest& frame_load_request, |
| FrameLoadType type, |
| NavigationPolicy navigation_policy, |
| NavigationType navigation_type) { |
| if (frame_->GetDocument()->PageDismissalEventBeingDispatched() != |
| Document::kNoDismissal) { |
| return kNavigationPolicyIgnore; |
| } |
| |
| // Record the latest requiredCSP value that will be used when sending this |
| // request. |
| ResourceRequest& resource_request = frame_load_request.GetResourceRequest(); |
| RecordLatestRequiredCSP(); |
| // Before modifying the request, check report-only CSP headers to give the |
| // site owner a chance to learn about requests that need to be modified. |
| // |
| // TODO(estark): this doesn't work with --enable-browser-side-navigation, |
| // wherein 'frame-src' is checked in the browser process. Figure out what to |
| // do; maybe with browser-side navigation the upgrade should be happening in |
| // the browser process too. See also https://crbug.com/692595 |
| Settings* settings = frame_->GetSettings(); |
| MaybeCheckCSP( |
| resource_request, navigation_type, frame_, navigation_policy, |
| frame_load_request.ShouldCheckMainWorldContentSecurityPolicy() == |
| kCheckContentSecurityPolicy, |
| settings && settings->GetBrowserSideNavigationEnabled(), |
| ContentSecurityPolicy::CheckHeaderType::kCheckReportOnly); |
| ModifyRequestForCSP(resource_request, nullptr); |
| |
| return ShouldContinueForNavigationPolicy( |
| resource_request, frame_load_request.GetSubstituteData(), nullptr, |
| frame_load_request.ShouldCheckMainWorldContentSecurityPolicy(), |
| navigation_type, navigation_policy, type, |
| frame_load_request.ClientRedirect() == |
| ClientRedirectPolicy::kClientRedirect, |
| frame_load_request.Form()); |
| } |
| |
| void FrameLoader::StartLoad(FrameLoadRequest& frame_load_request, |
| FrameLoadType type, |
| NavigationPolicy navigation_policy, |
| HistoryItem* history_item) { |
| DCHECK(Client()->HasWebView()); |
| ResourceRequest& resource_request = frame_load_request.GetResourceRequest(); |
| NavigationType navigation_type = DetermineNavigationType( |
| type, resource_request.HttpBody() || frame_load_request.Form(), |
| frame_load_request.TriggeringEvent()); |
| resource_request.SetRequestContext( |
| DetermineRequestContextFromNavigationType(navigation_type)); |
| resource_request.SetFrameType(frame_->IsMainFrame() |
| ? WebURLRequest::kFrameTypeTopLevel |
| : WebURLRequest::kFrameTypeNested); |
| |
| bool had_placeholder_client_document_loader = |
| provisional_document_loader_ && !provisional_document_loader_->DidStart(); |
| navigation_policy = CheckLoadCanStart(frame_load_request, type, |
| navigation_policy, navigation_type); |
| if (navigation_policy == kNavigationPolicyIgnore) { |
| if (had_placeholder_client_document_loader && |
| !resource_request.CheckForBrowserSideNavigation()) { |
| DetachDocumentLoader(provisional_document_loader_); |
| } |
| return; |
| } |
| |
| // For PlzNavigate placeholder DocumentLoaders, don't send failure callbacks |
| // for a placeholder simply being replaced with a new DocumentLoader. |
| if (had_placeholder_client_document_loader) |
| provisional_document_loader_->SetSentDidFinishLoad(); |
| frame_->GetDocument()->CancelParsing(); |
| DetachDocumentLoader(provisional_document_loader_); |
| |
| // beforeunload fired above, and detaching a DocumentLoader can fire events, |
| // which can detach this frame. |
| if (!frame_->GetPage()) |
| return; |
| |
| progress_tracker_->ProgressStarted(type); |
| // TODO(japhet): This case wants to flag the frame as loading and do nothing |
| // else. It'd be nice if it could go through the placeholder DocumentLoader |
| // path, too. |
| if (navigation_policy == kNavigationPolicyHandledByClientForInitialHistory) |
| return; |
| DCHECK(navigation_policy == kNavigationPolicyCurrentTab || |
| navigation_policy == kNavigationPolicyHandledByClient); |
| |
| provisional_document_loader_ = CreateDocumentLoader( |
| resource_request, frame_load_request, type, navigation_type); |
| |
| // PlzNavigate: We need to ensure that script initiated navigations are |
| // honored. |
| if (!had_placeholder_client_document_loader || |
| navigation_policy == kNavigationPolicyHandledByClient) { |
| frame_->GetNavigationScheduler().Cancel(); |
| } |
| |
| if (frame_load_request.Form()) |
| Client()->DispatchWillSubmitForm(frame_load_request.Form()); |
| |
| provisional_document_loader_->AppendRedirect( |
| provisional_document_loader_->GetRequest().Url()); |
| |
| if (IsBackForwardLoadType(type)) { |
| DCHECK(history_item); |
| provisional_document_loader_->SetItemForHistoryNavigation(history_item); |
| } |
| |
| // TODO(ananta): |
| // We should get rid of the dependency on the DocumentLoader in consumers of |
| // the didStartProvisionalLoad() notification. |
| Client()->DispatchDidStartProvisionalLoad(provisional_document_loader_, |
| resource_request); |
| DCHECK(provisional_document_loader_); |
| |
| if (navigation_policy == kNavigationPolicyCurrentTab) { |
| provisional_document_loader_->StartLoadingMainResource(); |
| // This should happen after the request is sent, so that the state |
| // the inspector stored in the matching frameScheduledClientNavigation() |
| // is available while sending the request. |
| probe::frameClearedScheduledClientNavigation(frame_); |
| } else { |
| // PlzNavigate |
| // Check for usage of legacy schemes now. Unsupported schemes will be |
| // rewritten by the client, so the FrameFetchContext will not be able to |
| // check for those when the navigation commits. |
| if (navigation_policy == kNavigationPolicyHandledByClient) |
| CheckForLegacyProtocolInSubresource(resource_request, |
| frame_->GetDocument()); |
| probe::frameScheduledClientNavigation(frame_); |
| } |
| |
| TakeObjectSnapshot(); |
| } |
| |
| void FrameLoader::ApplyUserAgent(ResourceRequest& request) { |
| String user_agent = this->UserAgent(); |
| DCHECK(!user_agent.IsNull()); |
| request.SetHTTPUserAgent(AtomicString(user_agent)); |
| } |
| |
| bool FrameLoader::ShouldTreatURLAsSameAsCurrent(const KURL& url) const { |
| return document_loader_->GetHistoryItem() && |
| url == document_loader_->GetHistoryItem()->Url(); |
| } |
| |
| bool FrameLoader::ShouldTreatURLAsSrcdocDocument(const KURL& url) const { |
| if (!url.IsAboutSrcdocURL()) |
| return false; |
| HTMLFrameOwnerElement* owner_element = frame_->DeprecatedLocalOwner(); |
| if (!isHTMLIFrameElement(owner_element)) |
| return false; |
| return owner_element->FastHasAttribute(srcdocAttr); |
| } |
| |
| void FrameLoader::DispatchDocumentElementAvailable() { |
| ScriptForbiddenScope forbid_scripts; |
| Client()->DocumentElementAvailable(); |
| } |
| |
| void FrameLoader::RunScriptsAtDocumentElementAvailable() { |
| Client()->RunScriptsAtDocumentElementAvailable(); |
| // The frame might be detached at this point. |
| } |
| |
| void FrameLoader::DispatchDidClearDocumentOfWindowObject() { |
| DCHECK(frame_->GetDocument()); |
| if (state_machine_.CreatingInitialEmptyDocument()) |
| return; |
| if (!frame_->GetDocument()->CanExecuteScripts(kNotAboutToExecuteScript)) |
| return; |
| |
| Settings* settings = frame_->GetSettings(); |
| if (settings && settings->GetForceMainWorldInitialization()) { |
| // Forcibly instantiate WindowProxy. |
| frame_->GetScriptController().WindowProxy(DOMWrapperWorld::MainWorld()); |
| } |
| probe::didClearDocumentOfWindowObject(frame_); |
| |
| if (dispatching_did_clear_window_object_in_main_world_) |
| return; |
| AutoReset<bool> in_did_clear_window_object( |
| &dispatching_did_clear_window_object_in_main_world_, true); |
| // We just cleared the document, not the entire window object, but for the |
| // embedder that's close enough. |
| Client()->DispatchDidClearWindowObjectInMainWorld(); |
| } |
| |
| void FrameLoader::DispatchDidClearWindowObjectInMainWorld() { |
| DCHECK(frame_->GetDocument()); |
| if (!frame_->GetDocument()->CanExecuteScripts(kNotAboutToExecuteScript)) |
| return; |
| |
| if (dispatching_did_clear_window_object_in_main_world_) |
| return; |
| AutoReset<bool> in_did_clear_window_object( |
| &dispatching_did_clear_window_object_in_main_world_, true); |
| Client()->DispatchDidClearWindowObjectInMainWorld(); |
| } |
| |
| SandboxFlags FrameLoader::EffectiveSandboxFlags() const { |
| SandboxFlags flags = forced_sandbox_flags_; |
| if (FrameOwner* frame_owner = frame_->Owner()) |
| flags |= frame_owner->GetSandboxFlags(); |
| // Frames need to inherit the sandbox flags of their parent frame. |
| if (Frame* parent_frame = frame_->Tree().Parent()) |
| flags |= parent_frame->GetSecurityContext()->GetSandboxFlags(); |
| return flags; |
| } |
| |
| WebInsecureRequestPolicy FrameLoader::GetInsecureRequestPolicy() const { |
| Frame* parent_frame = frame_->Tree().Parent(); |
| if (!parent_frame) |
| return kLeaveInsecureRequestsAlone; |
| |
| return parent_frame->GetSecurityContext()->GetInsecureRequestPolicy(); |
| } |
| |
| SecurityContext::InsecureNavigationsSet* |
| FrameLoader::InsecureNavigationsToUpgrade() const { |
| DCHECK(frame_); |
| Frame* parent_frame = frame_->Tree().Parent(); |
| if (!parent_frame) |
| return nullptr; |
| |
| // FIXME: We need a way to propagate insecure requests policy flags to |
| // out-of-process frames. For now, we'll always use default behavior. |
| if (!parent_frame->IsLocalFrame()) |
| return nullptr; |
| |
| DCHECK(ToLocalFrame(parent_frame)->GetDocument()); |
| return ToLocalFrame(parent_frame) |
| ->GetDocument() |
| ->InsecureNavigationsToUpgrade(); |
| } |
| |
| void FrameLoader::ModifyRequestForCSP(ResourceRequest& resource_request, |
| Document* document) const { |
| if (RuntimeEnabledFeatures::embedderCSPEnforcementEnabled() && |
| !RequiredCSP().IsEmpty()) { |
| DCHECK(ContentSecurityPolicy::IsValidCSPAttr(RequiredCSP().GetString())); |
| resource_request.SetHTTPHeaderField(HTTPNames::Required_CSP, RequiredCSP()); |
| } |
| |
| // Tack an 'Upgrade-Insecure-Requests' header to outgoing navigational |
| // requests, as described in |
| // https://w3c.github.io/webappsec/specs/upgrade/#feature-detect |
| if (resource_request.GetFrameType() != WebURLRequest::kFrameTypeNone) { |
| // Early return if the request has already been upgraded. |
| if (!resource_request.HttpHeaderField(HTTPNames::Upgrade_Insecure_Requests) |
| .IsNull()) { |
| return; |
| } |
| |
| resource_request.SetHTTPHeaderField(HTTPNames::Upgrade_Insecure_Requests, |
| "1"); |
| } |
| |
| UpgradeInsecureRequest(resource_request, document); |
| } |
| |
| void FrameLoader::UpgradeInsecureRequest(ResourceRequest& resource_request, |
| Document* document) const { |
| KURL url = resource_request.Url(); |
| |
| // If we don't yet have an |m_document| (because we're loading an iframe, for |
| // instance), check the FrameLoader's policy. |
| WebInsecureRequestPolicy relevant_policy = |
| document ? document->GetInsecureRequestPolicy() |
| : GetInsecureRequestPolicy(); |
| SecurityContext::InsecureNavigationsSet* relevant_navigation_set = |
| document ? document->InsecureNavigationsToUpgrade() |
| : InsecureNavigationsToUpgrade(); |
| |
| if (url.ProtocolIs("http") && relevant_policy & kUpgradeInsecureRequests) { |
| // We always upgrade requests that meet any of the following criteria: |
| // |
| // 1. Are for subresources (including nested frames). |
| // 2. Are form submissions. |
| // 3. Whose hosts are contained in the document's InsecureNavigationSet. |
| if (resource_request.GetFrameType() == WebURLRequest::kFrameTypeNone || |
| resource_request.GetFrameType() == WebURLRequest::kFrameTypeNested || |
| resource_request.GetRequestContext() == |
| WebURLRequest::kRequestContextForm || |
| (!url.Host().IsNull() && |
| relevant_navigation_set->Contains(url.Host().Impl()->GetHash()))) { |
| UseCounter::Count(document, |
| UseCounter::kUpgradeInsecureRequestsUpgradedRequest); |
| url.SetProtocol("https"); |
| if (url.Port() == 80) |
| url.SetPort(443); |
| resource_request.SetURL(url); |
| } |
| } |
| } |
| |
| void FrameLoader::RecordLatestRequiredCSP() { |
| required_csp_ = frame_->Owner() ? frame_->Owner()->Csp() : g_null_atom; |
| } |
| |
| std::unique_ptr<TracedValue> FrameLoader::ToTracedValue() const { |
| std::unique_ptr<TracedValue> traced_value = TracedValue::Create(); |
| traced_value->BeginDictionary("frame"); |
| traced_value->SetString( |
| "id_ref", String::Format("0x%" PRIx64, |
| static_cast<uint64_t>( |
| reinterpret_cast<uintptr_t>(frame_.Get())))); |
| traced_value->EndDictionary(); |
| traced_value->SetBoolean("isLoadingMainFrame", IsLoadingMainFrame()); |
| traced_value->SetString("stateMachine", state_machine_.ToString()); |
| traced_value->SetString("provisionalDocumentLoaderURL", |
| provisional_document_loader_ |
| ? provisional_document_loader_->Url() |
| : String()); |
| traced_value->SetString("documentLoaderURL", document_loader_ |
| ? document_loader_->Url() |
| : String()); |
| return traced_value; |
| } |
| |
| inline void FrameLoader::TakeObjectSnapshot() const { |
| if (detached_) { |
| // We already logged TRACE_EVENT_OBJECT_DELETED_WITH_ID in detach(). |
| return; |
| } |
| TRACE_EVENT_OBJECT_SNAPSHOT_WITH_ID("loading", "FrameLoader", this, |
| ToTracedValue()); |
| } |
| |
| DocumentLoader* FrameLoader::CreateDocumentLoader( |
| const ResourceRequest& request, |
| const FrameLoadRequest& frame_load_request, |
| FrameLoadType load_type, |
| NavigationType navigation_type) { |
| DocumentLoader* loader = Client()->CreateDocumentLoader( |
| frame_, request, |
| frame_load_request.GetSubstituteData().IsValid() |
| ? frame_load_request.GetSubstituteData() |
| : DefaultSubstituteDataForURL(request.Url()), |
| frame_load_request.ClientRedirect()); |
| |
| loader->SetLoadType(load_type); |
| loader->SetNavigationType(navigation_type); |
| // TODO(japhet): This is needed because the browser process DCHECKs if the |
| // first entry we commit in a new frame has replacement set. It's unclear |
| // whether the DCHECK is right, investigate removing this special case. |
| bool replace_current_item = load_type == kFrameLoadTypeReplaceCurrentItem && |
| (!Opener() || !request.Url().IsEmpty()); |
| loader->SetReplacesCurrentHistoryItem(replace_current_item); |
| return loader; |
| } |
| |
| } // namespace blink |