| /* |
| * 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 "bindings/core/v8/DOMWrapperWorld.h" |
| #include "bindings/core/v8/ScriptController.h" |
| #include "bindings/core/v8/SerializedScriptValue.h" |
| #include "core/HTMLNames.h" |
| #include "core/dom/Document.h" |
| #include "core/dom/Element.h" |
| #include "core/dom/TaskRunnerHelper.h" |
| #include "core/dom/ViewportDescription.h" |
| #include "core/editing/Editor.h" |
| #include "core/events/GestureEvent.h" |
| #include "core/events/KeyboardEvent.h" |
| #include "core/events/MouseEvent.h" |
| #include "core/events/PageTransitionEvent.h" |
| #include "core/fetch/ResourceFetcher.h" |
| #include "core/frame/FrameHost.h" |
| #include "core/frame/FrameView.h" |
| #include "core/frame/LocalDOMWindow.h" |
| #include "core/frame/LocalFrame.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/html/parser/HTMLParserIdioms.h" |
| #include "core/input/EventHandler.h" |
| #include "core/inspector/ConsoleMessage.h" |
| #include "core/inspector/InspectorInstrumentation.h" |
| #include "core/inspector/InspectorTraceEvents.h" |
| #include "core/loader/DocumentLoadTiming.h" |
| #include "core/loader/DocumentLoader.h" |
| #include "core/loader/FormSubmission.h" |
| #include "core/loader/FrameLoadRequest.h" |
| #include "core/loader/FrameLoaderClient.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/origin_trials/OriginTrialContext.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/svg/graphics/SVGImage.h" |
| #include "core/xml/parser/XMLDocumentParser.h" |
| #include "platform/InstanceCounters.h" |
| #include "platform/PluginScriptForbiddenScope.h" |
| #include "platform/ScriptForbiddenScope.h" |
| #include "platform/UserGestureIndicator.h" |
| #include "platform/feature_policy/FeaturePolicy.h" |
| #include "platform/network/HTTPParsers.h" |
| #include "platform/network/ResourceRequest.h" |
| #include "platform/scroll/ScrollAnimatorBase.h" |
| #include "platform/tracing/TraceEvent.h" |
| #include "platform/weborigin/SecurityOrigin.h" |
| #include "platform/weborigin/SecurityPolicy.h" |
| #include "platform/weborigin/Suborigin.h" |
| #include "public/platform/WebCachePolicy.h" |
| #include "public/platform/WebURLRequest.h" |
| #include "wtf/AutoReset.h" |
| #include "wtf/text/CString.h" |
| #include "wtf/text/WTFString.h" |
| #include <memory> |
| |
| using blink::WebURLRequest; |
| |
| namespace blink { |
| |
| using namespace HTMLNames; |
| |
| bool isBackForwardLoadType(FrameLoadType type) { |
| return type == FrameLoadTypeBackForward || |
| type == FrameLoadTypeInitialHistoryLoad; |
| } |
| |
| bool isReloadLoadType(FrameLoadType type) { |
| return type == FrameLoadTypeReload || |
| type == FrameLoadTypeReloadMainResource || |
| type == FrameLoadTypeReloadBypassingCache; |
| } |
| |
| static bool needsHistoryItemRestore(FrameLoadType type) { |
| if (!RuntimeEnabledFeatures:: |
| reloadwithoutSubResourceCacheRevalidationEnabled() && |
| type == FrameLoadTypeReloadMainResource) |
| return false; |
| // TODO(toyoshim): Shall we return true for FrameLoadTypeInitialHistoryLoad |
| // too? |
| return type == FrameLoadTypeBackForward || isReloadLoadType(type); |
| } |
| |
| // static |
| ResourceRequest FrameLoader::resourceRequestFromHistoryItem( |
| HistoryItem* item, |
| WebCachePolicy cachePolicy) { |
| RefPtr<EncodedFormData> formData = item->formData(); |
| ResourceRequest request(item->url()); |
| request.setHTTPReferrer(item->referrer()); |
| request.setCachePolicy(cachePolicy); |
| if (formData) { |
| request.setHTTPMethod(HTTPNames::POST); |
| request.setHTTPBody(formData); |
| request.setHTTPContentType(item->formContentType()); |
| request.addHTTPOriginIfNeeded(item->referrer().referrer); |
| } |
| return request; |
| } |
| |
| ResourceRequest FrameLoader::resourceRequestForReload( |
| FrameLoadType frameLoadType, |
| const KURL& overrideURL, |
| ClientRedirectPolicy clientRedirectPolicy) { |
| DCHECK(isReloadLoadType(frameLoadType)); |
| WebCachePolicy cachePolicy = |
| frameLoadType == FrameLoadTypeReloadBypassingCache |
| ? WebCachePolicy::BypassingCache |
| : WebCachePolicy::ValidatingCacheData; |
| if (!m_currentItem) |
| return ResourceRequest(); |
| ResourceRequest request = |
| resourceRequestFromHistoryItem(m_currentItem.get(), cachePolicy); |
| |
| // 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 (clientRedirectPolicy == ClientRedirectPolicy::ClientRedirect) { |
| request.setHTTPReferrer(SecurityPolicy::generateReferrer( |
| m_frame->document()->getReferrerPolicy(), m_frame->document()->url(), |
| m_frame->document()->outgoingReferrer())); |
| } |
| |
| if (!overrideURL.isEmpty()) { |
| request.setURL(overrideURL); |
| request.clearHTTPReferrer(); |
| } |
| request.setSkipServiceWorker(frameLoadType == |
| FrameLoadTypeReloadBypassingCache |
| ? WebURLRequest::SkipServiceWorker::All |
| : WebURLRequest::SkipServiceWorker::None); |
| return request; |
| } |
| |
| FrameLoader::FrameLoader(LocalFrame* frame) |
| : m_frame(frame), |
| m_progressTracker(ProgressTracker::create(frame)), |
| m_loadType(FrameLoadTypeStandard), |
| m_inStopAllLoaders(false), |
| m_checkTimer(TaskRunnerHelper::get(TaskType::Networking, frame), |
| this, |
| &FrameLoader::checkTimerFired), |
| m_forcedSandboxFlags(SandboxNone), |
| m_dispatchingDidClearWindowObjectInMainWorld(false), |
| m_protectProvisionalLoader(false), |
| m_isNavigationHandledByClient(false) { |
| TRACE_EVENT_OBJECT_CREATED_WITH_ID("loading", "FrameLoader", this); |
| takeObjectSnapshot(); |
| } |
| |
| FrameLoader::~FrameLoader() { |
| // Verify that this FrameLoader has been detached. |
| DCHECK(!m_progressTracker); |
| } |
| |
| DEFINE_TRACE(FrameLoader) { |
| visitor->trace(m_frame); |
| visitor->trace(m_progressTracker); |
| visitor->trace(m_documentLoader); |
| visitor->trace(m_provisionalDocumentLoader); |
| visitor->trace(m_currentItem); |
| visitor->trace(m_provisionalItem); |
| visitor->trace(m_deferredHistoryLoad); |
| } |
| |
| void FrameLoader::init() { |
| ResourceRequest initialRequest(KURL(ParsedURLString, emptyString())); |
| initialRequest.setRequestContext(WebURLRequest::RequestContextInternal); |
| initialRequest.setFrameType(m_frame->isMainFrame() |
| ? WebURLRequest::FrameTypeTopLevel |
| : WebURLRequest::FrameTypeNested); |
| m_provisionalDocumentLoader = |
| client()->createDocumentLoader(m_frame, initialRequest, SubstituteData(), |
| ClientRedirectPolicy::NotClientRedirect); |
| m_provisionalDocumentLoader->startLoadingMainResource(); |
| m_frame->document()->cancelParsing(); |
| m_stateMachine.advanceTo( |
| FrameLoaderStateMachine::DisplayingInitialEmptyDocument); |
| // Self-suspend if created in an already deferred Page. Note that both |
| // startLoadingMainResource() and cancelParsing() may have already detached |
| // the frame, since they both fire JS events. |
| if (m_frame->page() && m_frame->page()->defersLoading()) |
| setDefersLoading(true); |
| takeObjectSnapshot(); |
| } |
| |
| FrameLoaderClient* FrameLoader::client() const { |
| return static_cast<FrameLoaderClient*>(m_frame->client()); |
| } |
| |
| void FrameLoader::setDefersLoading(bool defers) { |
| if (m_provisionalDocumentLoader) |
| m_provisionalDocumentLoader->fetcher()->setDefersLoading(defers); |
| |
| if (Document* document = m_frame->document()) { |
| document->fetcher()->setDefersLoading(defers); |
| if (defers) |
| document->suspendScheduledTasks(); |
| else |
| document->resumeScheduledTasks(); |
| } |
| |
| if (!defers) { |
| if (m_deferredHistoryLoad) { |
| load(FrameLoadRequest(nullptr, m_deferredHistoryLoad->m_request), |
| m_deferredHistoryLoad->m_loadType, |
| m_deferredHistoryLoad->m_item.get(), |
| m_deferredHistoryLoad->m_historyLoadType); |
| m_deferredHistoryLoad.clear(); |
| } |
| m_frame->navigationScheduler().startTimer(); |
| scheduleCheckCompleted(); |
| } |
| } |
| |
| void FrameLoader::saveScrollState() { |
| if (!m_currentItem || !m_frame->view()) |
| return; |
| |
| // Shouldn't clobber anything if we might still restore later. |
| if (needsHistoryItemRestore(m_loadType) && m_documentLoader && |
| !m_documentLoader->initialScrollState().wasScrolledByUser) |
| return; |
| |
| if (ScrollableArea* layoutScrollableArea = |
| m_frame->view()->layoutViewportScrollableArea()) |
| m_currentItem->setScrollOffset(layoutScrollableArea->scrollOffset()); |
| m_currentItem->setVisualViewportScrollOffset(toScrollOffset( |
| m_frame->host()->visualViewport().visibleRect().location())); |
| |
| if (m_frame->isMainFrame()) |
| m_currentItem->setPageScaleFactor(m_frame->page()->pageScaleFactor()); |
| |
| client()->didUpdateCurrentHistoryItem(); |
| } |
| |
| void FrameLoader::dispatchUnloadEvent() { |
| NavigationDisablerForUnload navigationDisabler; |
| |
| // If the frame is unloading, the provisional loader should no longer be |
| // protected. It will be detached soon. |
| m_protectProvisionalLoader = false; |
| saveScrollState(); |
| |
| if (m_frame->document() && !SVGImage::isInSVGImage(m_frame->document())) |
| m_frame->document()->dispatchUnloadEvents(); |
| } |
| |
| void FrameLoader::didExplicitOpen() { |
| // Calling document.open counts as committing the first real document load. |
| if (!m_stateMachine.committedFirstRealDocumentLoad()) |
| m_stateMachine.advanceTo(FrameLoaderStateMachine::CommittedFirstRealLoad); |
| |
| // 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 = m_frame->tree().parent()) { |
| if ((parent->isLocalFrame() && |
| toLocalFrame(parent)->document()->loadEventStillNeeded()) || |
| (parent->isRemoteFrame() && parent->isLoading())) { |
| m_progressTracker->progressStarted(); |
| } |
| } |
| |
| // 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. |
| m_frame->navigationScheduler().cancel(); |
| } |
| |
| void FrameLoader::clear() { |
| // clear() is called during (Local)Frame detachment or when reusing a |
| // FrameLoader by putting a new Document within it |
| // (DocumentLoader::ensureWriter().) |
| if (m_stateMachine.creatingInitialEmptyDocument()) |
| return; |
| |
| m_frame->editor().clear(); |
| m_frame->document()->removeFocusedElementOfSubtree(m_frame->document()); |
| m_frame->eventHandler().clear(); |
| if (m_frame->view()) |
| m_frame->view()->clear(); |
| |
| m_frame->script().enableEval(); |
| |
| m_frame->navigationScheduler().cancel(); |
| |
| m_checkTimer.stop(); |
| |
| if (m_stateMachine.isDisplayingInitialEmptyDocument()) |
| m_stateMachine.advanceTo(FrameLoaderStateMachine::CommittedFirstRealLoad); |
| |
| takeObjectSnapshot(); |
| } |
| |
| // 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* ownerDocument) { |
| if (!m_frame->document()->loader() || |
| m_frame->document()->pageDismissalEventBeingDispatched() != |
| Document::NoDismissal) |
| return; |
| |
| DocumentLoader* documentLoader(m_frame->document()->loader()); |
| |
| UseCounter::count(*m_frame->document(), |
| UseCounter::ReplaceDocumentViaJavaScriptURL); |
| |
| // Prepare a DocumentInit before clearing the frame, because it may need to |
| // inherit an aliased security context. |
| DocumentInit init(ownerDocument, m_frame->document()->url(), m_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(m_frame->document()); |
| m_frame->detachChildren(); |
| m_frame->document()->shutdown(); |
| clear(); |
| |
| // detachChildren() potentially detaches the frame from the document. The |
| // loading cannot continue in that case. |
| if (!m_frame->page()) |
| return; |
| |
| client()->transitionToCommittedForNewPage(); |
| documentLoader->replaceDocumentWhileExecutingJavaScriptURL(init, source); |
| } |
| |
| void FrameLoader::clearProvisionalHistoryItem() { |
| m_provisionalItem.clear(); |
| } |
| |
| void FrameLoader::setHistoryItemStateForCommit( |
| FrameLoadType loadType, |
| HistoryCommitType historyCommitType, |
| HistoryNavigationType navigationType) { |
| HistoryItem* oldItem = m_currentItem; |
| if (isBackForwardLoadType(loadType) && m_provisionalItem) |
| m_currentItem = m_provisionalItem.release(); |
| else |
| m_currentItem = HistoryItem::create(); |
| m_currentItem->setURL(m_documentLoader->urlForHistory()); |
| m_currentItem->setDocumentState(m_frame->document()->formElementsState()); |
| m_currentItem->setTarget(m_frame->tree().uniqueName()); |
| m_currentItem->setReferrer(SecurityPolicy::generateReferrer( |
| m_documentLoader->request().getReferrerPolicy(), m_currentItem->url(), |
| m_documentLoader->request().httpReferrer())); |
| m_currentItem->setFormInfoFromRequest(m_documentLoader->request()); |
| |
| // Don't propagate state from the old item to the new item if there isn't an |
| // old item (obviously), or if this is a back/forward navigation, since we |
| // explicitly want to restore the state we just committed. |
| if (!oldItem || isBackForwardLoadType(loadType)) |
| return; |
| // Don't propagate state from the old item if this is a different-document |
| // navigation, unless the before and after pages are logically related. This |
| // means they have the same url (ignoring fragment) and the new item was |
| // loaded via reload or client redirect. |
| if (navigationType == HistoryNavigationType::DifferentDocument && |
| (historyCommitType != HistoryInertCommit || |
| !equalIgnoringFragmentIdentifier(oldItem->url(), m_currentItem->url()))) |
| return; |
| m_currentItem->setDocumentSequenceNumber(oldItem->documentSequenceNumber()); |
| m_currentItem->setScrollOffset(oldItem->scrollOffset()); |
| m_currentItem->setVisualViewportScrollOffset( |
| oldItem->visualViewportScrollOffset()); |
| m_currentItem->setPageScaleFactor(oldItem->pageScaleFactor()); |
| m_currentItem->setScrollRestorationType(oldItem->scrollRestorationType()); |
| |
| // The item sequence number determines whether items are "the same", such |
| // back/forward navigation between items with the same item sequence number is |
| // a no-op. Only treat this as identical if the navigation did not create a |
| // back/forward entry and the url is identical or it was loaded via |
| // history.replaceState(). |
| if (historyCommitType == HistoryInertCommit && |
| (navigationType == HistoryNavigationType::HistoryApi || |
| oldItem->url() == m_currentItem->url())) { |
| m_currentItem->setStateObject(oldItem->stateObject()); |
| m_currentItem->setItemSequenceNumber(oldItem->itemSequenceNumber()); |
| } |
| } |
| |
| static HistoryCommitType loadTypeToCommitType(FrameLoadType type) { |
| switch (type) { |
| case FrameLoadTypeStandard: |
| return StandardCommit; |
| case FrameLoadTypeInitialInChildFrame: |
| case FrameLoadTypeInitialHistoryLoad: |
| return InitialCommitInChildFrame; |
| case FrameLoadTypeBackForward: |
| return BackForwardCommit; |
| default: |
| break; |
| } |
| return HistoryInertCommit; |
| } |
| |
| void FrameLoader::receivedFirstData() { |
| if (m_stateMachine.creatingInitialEmptyDocument()) |
| return; |
| |
| HistoryCommitType historyCommitType = loadTypeToCommitType(m_loadType); |
| if (historyCommitType == StandardCommit && |
| (m_documentLoader->urlForHistory().isEmpty() || |
| (opener() && !m_currentItem && |
| m_documentLoader->originalRequest().url().isEmpty()))) |
| historyCommitType = HistoryInertCommit; |
| setHistoryItemStateForCommit(m_loadType, historyCommitType, |
| HistoryNavigationType::DifferentDocument); |
| |
| if (!m_stateMachine.committedMultipleRealLoads() && |
| m_loadType == FrameLoadTypeStandard) { |
| m_stateMachine.advanceTo( |
| FrameLoaderStateMachine::CommittedMultipleRealLoads); |
| } |
| |
| client()->dispatchDidCommitLoad(m_currentItem.get(), historyCommitType); |
| |
| // When the embedder gets notified (above) that the new navigation has |
| // committed, the embedder will drop the old Content Security Policy and |
| // therefore now is a good time to report to the embedder the Content Security |
| // Policies that have accumulated so far for the new navigation. |
| m_frame->securityContext()->contentSecurityPolicy()->reportAccumulatedHeaders( |
| client()); |
| |
| // didObserveLoadingBehavior() must be called after dispatchDidCommitLoad() is |
| // called for the metrics tracking logic to handle it properly. |
| if (client()->isControlledByServiceWorker(*m_documentLoader)) { |
| client()->didObserveLoadingBehavior( |
| WebLoadingBehaviorServiceWorkerControlled); |
| } |
| |
| // Links with media values need more information (like viewport information). |
| // This happens after the first chunk is parsed in HTMLDocumentParser. |
| m_documentLoader->dispatchLinkHeaderPreloads(nullptr, |
| LinkLoader::OnlyLoadNonMedia); |
| |
| TRACE_EVENT1("devtools.timeline", "CommitLoad", "data", |
| InspectorCommitLoadEvent::data(m_frame)); |
| InspectorInstrumentation::didCommitLoad(m_frame, m_documentLoader.get()); |
| m_frame->page()->didCommitLoad(m_frame); |
| dispatchDidClearDocumentOfWindowObject(); |
| |
| takeObjectSnapshot(); |
| } |
| |
| void FrameLoader::didInstallNewDocument(bool dispatchWindowObjectAvailable) { |
| DCHECK(m_frame); |
| DCHECK(m_frame->document()); |
| |
| m_frame->document()->setReadyState(Document::Loading); |
| |
| if (dispatchWindowObjectAvailable) |
| dispatchDidClearDocumentOfWindowObject(); |
| |
| m_frame->document()->initContentSecurityPolicy( |
| m_documentLoader ? m_documentLoader->releaseContentSecurityPolicy() |
| : ContentSecurityPolicy::create()); |
| |
| if (m_provisionalItem && isBackForwardLoadType(m_loadType)) { |
| m_frame->document()->setStateForNewFormElements( |
| m_provisionalItem->documentState()); |
| } |
| } |
| |
| void FrameLoader::didBeginDocument() { |
| DCHECK(m_frame); |
| DCHECK(m_frame->document()); |
| DCHECK(m_frame->document()->fetcher()); |
| |
| if (m_documentLoader) { |
| String suboriginHeader = |
| m_documentLoader->response().httpHeaderField(HTTPNames::Suborigin); |
| if (!suboriginHeader.isNull()) { |
| Vector<String> messages; |
| Suborigin suborigin; |
| if (parseSuboriginHeader(suboriginHeader, &suborigin, messages)) |
| m_frame->document()->enforceSuborigin(suborigin); |
| |
| for (auto& message : messages) { |
| m_frame->document()->addConsoleMessage( |
| ConsoleMessage::create(SecurityMessageSource, ErrorMessageLevel, |
| "Error with Suborigin header: " + message)); |
| } |
| } |
| m_frame->document()->clientHintsPreferences().updateFrom( |
| m_documentLoader->clientHintsPreferences()); |
| } |
| |
| Settings* settings = m_frame->document()->settings(); |
| if (settings) { |
| m_frame->document()->fetcher()->setImagesEnabled(settings->imagesEnabled()); |
| m_frame->document()->fetcher()->setAutoLoadImages( |
| settings->loadsImagesAutomatically()); |
| } |
| |
| if (m_documentLoader) { |
| const AtomicString& dnsPrefetchControl = |
| m_documentLoader->response().httpHeaderField( |
| HTTPNames::X_DNS_Prefetch_Control); |
| if (!dnsPrefetchControl.isEmpty()) |
| m_frame->document()->parseDNSPrefetchControlHeader(dnsPrefetchControl); |
| |
| String headerContentLanguage = m_documentLoader->response().httpHeaderField( |
| HTTPNames::Content_Language); |
| if (!headerContentLanguage.isEmpty()) { |
| size_t commaIndex = headerContentLanguage.find(','); |
| headerContentLanguage.truncate( |
| commaIndex); // kNotFound == -1 == don't truncate |
| headerContentLanguage = |
| headerContentLanguage.stripWhiteSpace(isHTMLSpace<UChar>); |
| if (!headerContentLanguage.isEmpty()) { |
| m_frame->document()->setContentLanguage( |
| AtomicString(headerContentLanguage)); |
| } |
| } |
| |
| OriginTrialContext::addTokensFromHeader( |
| m_frame->document(), |
| m_documentLoader->response().httpHeaderField(HTTPNames::Origin_Trial)); |
| if (RuntimeEnabledFeatures::featurePolicyEnabled()) { |
| std::unique_ptr<FeaturePolicy> featurePolicy( |
| FeaturePolicy::createFromParentPolicy( |
| (isLoadingMainFrame() |
| ? nullptr |
| : m_frame->client()->parent()->getFeaturePolicy()), |
| m_frame->securityContext()->getSecurityOrigin())); |
| Vector<String> messages; |
| featurePolicy->setHeaderPolicy( |
| m_documentLoader->response().httpHeaderField( |
| HTTPNames::Feature_Policy), |
| messages); |
| for (auto& message : messages) { |
| m_frame->document()->addConsoleMessage(ConsoleMessage::create( |
| OtherMessageSource, ErrorMessageLevel, |
| "Error with Feature-Policy header: " + message)); |
| } |
| m_frame->setFeaturePolicy(std::move(featurePolicy)); |
| } |
| } |
| |
| if (m_documentLoader) { |
| String referrerPolicyHeader = m_documentLoader->response().httpHeaderField( |
| HTTPNames::Referrer_Policy); |
| if (!referrerPolicyHeader.isNull()) { |
| m_frame->document()->parseAndSetReferrerPolicy(referrerPolicyHeader); |
| } |
| } |
| |
| client()->didCreateNewDocument(); |
| } |
| |
| void FrameLoader::finishedParsing() { |
| if (m_stateMachine.creatingInitialEmptyDocument()) |
| return; |
| |
| m_progressTracker->finishedParsing(); |
| |
| if (client()) { |
| ScriptForbiddenScope forbidScripts; |
| client()->dispatchDidFinishDocumentLoad(); |
| } |
| |
| if (client()) { |
| client()->runScriptsAtDocumentReady( |
| m_documentLoader ? m_documentLoader->isCommittedButEmpty() : true); |
| } |
| |
| checkCompleted(); |
| |
| if (!m_frame->view()) |
| return; |
| |
| // Check if the scrollbars are really needed for the content. If not, remove |
| // them, relayout, and repaint. |
| m_frame->view()->restoreScrollbar(); |
| processFragment(m_frame->document()->url(), NavigationToDifferentDocument); |
| } |
| |
| static bool allDescendantsAreComplete(Frame* frame) { |
| for (Frame* child = frame->tree().firstChild(); child; |
| child = child->tree().traverseNext(frame)) { |
| if (child->isLoading()) |
| return false; |
| } |
| return true; |
| } |
| |
| bool FrameLoader::allAncestorsAreComplete() const { |
| for (Frame* ancestor = m_frame; ancestor; |
| ancestor = ancestor->tree().parent()) { |
| if (ancestor->isLoading()) |
| return false; |
| } |
| return true; |
| } |
| |
| static bool shouldComplete(Document* document) { |
| if (!document->frame()) |
| return false; |
| if (document->parsing() || document->isInDOMContentLoaded()) |
| return false; |
| if (!document->haveImportsLoaded()) |
| return false; |
| if (document->fetcher()->requestCount()) |
| return false; |
| if (document->isDelayingLoadEvent()) |
| return false; |
| return allDescendantsAreComplete(document->frame()); |
| } |
| |
| static bool shouldSendFinishNotification(LocalFrame* frame) { |
| // Don't send stop notifications for inital empty documents, since they don't |
| // generate start notifications. |
| if (!frame->loader().stateMachine()->committedFirstRealDocumentLoad()) |
| return false; |
| |
| // Don't send didFinishLoad more than once per DocumentLoader. |
| if (frame->loader().documentLoader()->sentDidFinishLoad()) |
| return false; |
| |
| // We might have declined to run the load event due to an imminent |
| // content-initiated navigation. |
| if (!frame->document()->loadEventFinished()) |
| return false; |
| |
| // An event might have restarted a child frame. |
| if (!allDescendantsAreComplete(frame)) |
| return false; |
| return true; |
| } |
| |
| static bool shouldSendCompleteNotification(LocalFrame* frame, |
| bool isNavigationHandledByClient) { |
| // FIXME: We might have already sent stop notifications and be re-completing. |
| if (!frame->isLoading()) |
| return false; |
| // Only send didStopLoading() if there are no navigations in progress at all, |
| // whether committed, provisional, or pending. |
| return frame->loader().documentLoader()->sentDidFinishLoad() && |
| !frame->loader().provisionalDocumentLoader() && |
| !isNavigationHandledByClient; |
| } |
| |
| void FrameLoader::checkCompleted() { |
| if (!shouldComplete(m_frame->document())) |
| return; |
| |
| // OK, completed. |
| m_frame->document()->setReadyState(Document::Complete); |
| if (m_frame->document()->loadEventStillNeeded()) |
| m_frame->document()->implicitClose(); |
| |
| m_frame->navigationScheduler().startTimer(); |
| |
| if (m_frame->view()) |
| m_frame->view()->handleLoadCompleted(); |
| |
| // The readystatechanged or load event may have disconnected this frame. |
| if (!m_frame->client()) |
| return; |
| |
| if (shouldSendFinishNotification(m_frame)) { |
| // Report mobile vs. desktop page statistics. This will only report on |
| // Android. |
| if (m_frame->isMainFrame()) |
| m_frame->document()->viewportDescription().reportMobilePageStats(m_frame); |
| m_documentLoader->setSentDidFinishLoad(); |
| client()->dispatchDidFinishLoad(); |
| // Finishing the load can detach the frame when running layout tests. |
| if (!m_frame->client()) |
| return; |
| } |
| |
| if (shouldSendCompleteNotification(m_frame, m_isNavigationHandledByClient)) { |
| m_progressTracker->progressCompleted(); |
| // Retry restoring scroll offset since finishing loading disables content |
| // size clamping. |
| restoreScrollPositionAndViewState(); |
| |
| m_loadType = FrameLoadTypeStandard; |
| m_frame->localDOMWindow()->finishedLoading(); |
| } |
| |
| Frame* parent = m_frame->tree().parent(); |
| if (parent && parent->isLocalFrame()) |
| toLocalFrame(parent)->loader().checkCompleted(); |
| } |
| |
| void FrameLoader::checkTimerFired(TimerBase*) { |
| if (Page* page = m_frame->page()) { |
| if (page->defersLoading()) |
| return; |
| } |
| checkCompleted(); |
| } |
| |
| void FrameLoader::scheduleCheckCompleted() { |
| if (!m_checkTimer.isActive()) |
| m_checkTimer.startOneShot(0, BLINK_FROM_HERE); |
| } |
| |
| 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 FrameHost has been |
| // detached. FrameClient will not be accessible, so bail early. |
| if (!client()) |
| return false; |
| Settings* settings = m_frame->settings(); |
| bool allowed = client()->allowPlugins(settings && settings->pluginsEnabled()); |
| if (!allowed && reason == AboutToInstantiatePlugin) |
| client()->didNotAllowPlugins(); |
| return allowed; |
| } |
| |
| void FrameLoader::updateForSameDocumentNavigation( |
| const KURL& newURL, |
| SameDocumentNavigationSource sameDocumentNavigationSource, |
| PassRefPtr<SerializedScriptValue> data, |
| HistoryScrollRestorationType scrollRestorationType, |
| FrameLoadType type, |
| Document* initiatingDocument) { |
| // Update the data source's request with the new URL to fake the URL change |
| m_frame->document()->setURL(newURL); |
| documentLoader()->setReplacesCurrentHistoryItem(type != |
| FrameLoadTypeStandard); |
| documentLoader()->updateForSameDocumentNavigation( |
| newURL, sameDocumentNavigationSource); |
| |
| // 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 |
| if (m_frame->document()->loadEventFinished()) |
| client()->didStartLoading(NavigationWithinSameDocument); |
| |
| HistoryCommitType historyCommitType = loadTypeToCommitType(type); |
| if (!m_currentItem) |
| historyCommitType = HistoryInertCommit; |
| if (m_frame->settings()->historyEntryRequiresUserGesture() && |
| initiatingDocument && !initiatingDocument->hasReceivedUserGesture()) { |
| historyCommitType = HistoryInertCommit; |
| } |
| |
| setHistoryItemStateForCommit( |
| type, historyCommitType, |
| sameDocumentNavigationSource == SameDocumentNavigationHistoryApi |
| ? HistoryNavigationType::HistoryApi |
| : HistoryNavigationType::Fragment); |
| if (sameDocumentNavigationSource == SameDocumentNavigationHistoryApi) { |
| m_currentItem->setStateObject(std::move(data)); |
| m_currentItem->setScrollRestorationType(scrollRestorationType); |
| } |
| client()->dispatchDidNavigateWithinPage( |
| m_currentItem.get(), historyCommitType, !!initiatingDocument); |
| client()->dispatchDidReceiveTitle(m_frame->document()->title()); |
| if (m_frame->document()->loadEventFinished()) |
| client()->didStopLoading(); |
| } |
| |
| void FrameLoader::detachDocumentLoader(Member<DocumentLoader>& loader) { |
| if (!loader) |
| return; |
| |
| FrameNavigationDisabler navigationDisabler(*m_frame); |
| loader->detachFromFrame(); |
| loader = nullptr; |
| } |
| |
| void FrameLoader::loadInSameDocument( |
| const KURL& url, |
| PassRefPtr<SerializedScriptValue> stateObject, |
| FrameLoadType frameLoadType, |
| HistoryLoadType historyLoadType, |
| ClientRedirectPolicy clientRedirect, |
| Document* initiatingDocument) { |
| // If we have a state object, we cannot also be a new navigation. |
| DCHECK(!stateObject || frameLoadType == FrameLoadTypeBackForward); |
| |
| // If we have a provisional request for a different document, a fragment |
| // scroll should cancel it. |
| detachDocumentLoader(m_provisionalDocumentLoader); |
| if (!m_frame->host()) |
| return; |
| AutoReset<FrameLoadType> loadTypeChange(&m_loadType, frameLoadType); |
| saveScrollState(); |
| |
| KURL oldURL = m_frame->document()->url(); |
| bool hashChange = equalIgnoringFragmentIdentifier(url, oldURL) && |
| url.fragmentIdentifier() != oldURL.fragmentIdentifier(); |
| if (hashChange) { |
| // If we were in the autoscroll/middleClickAutoscroll mode we want to stop |
| // it before following the link to the anchor |
| m_frame->eventHandler().stopAutoscroll(); |
| m_frame->localDOMWindow()->enqueueHashchangeEvent(oldURL, url); |
| } |
| m_documentLoader->setIsClientRedirect(clientRedirect == |
| ClientRedirectPolicy::ClientRedirect); |
| updateForSameDocumentNavigation(url, SameDocumentNavigationDefault, nullptr, |
| ScrollRestorationAuto, frameLoadType, |
| initiatingDocument); |
| |
| m_documentLoader->initialScrollState().wasScrolledByUser = false; |
| |
| checkCompleted(); |
| |
| m_frame->localDOMWindow()->statePopped( |
| stateObject ? std::move(stateObject) |
| : SerializedScriptValue::nullValue()); |
| |
| if (historyLoadType == HistorySameDocumentLoad) |
| restoreScrollPositionAndViewState(); |
| |
| // 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, NavigationWithinSameDocument); |
| takeObjectSnapshot(); |
| } |
| |
| // static |
| void FrameLoader::setReferrerForFrameRequest(FrameLoadRequest& frameRequest) { |
| ResourceRequest& request = frameRequest.resourceRequest(); |
| Document* originDocument = frameRequest.originDocument(); |
| |
| if (!originDocument) |
| return; |
| // Anchor elements with the 'referrerpolicy' attribute will have already set |
| // the referrer on the request. |
| if (request.didSetHTTPReferrer()) |
| return; |
| if (frameRequest.getShouldSendReferrer() == NeverSendReferrer) |
| 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( |
| originDocument->getReferrerPolicy(), request.url(), |
| originDocument->outgoingReferrer()); |
| |
| request.setHTTPReferrer(referrer); |
| request.addHTTPOriginIfNeeded(referrer.referrer); |
| } |
| |
| FrameLoadType FrameLoader::determineFrameLoadType( |
| const FrameLoadRequest& request) { |
| if (m_frame->tree().parent() && |
| !m_stateMachine.committedFirstRealDocumentLoad()) |
| return FrameLoadTypeInitialInChildFrame; |
| if (!m_frame->tree().parent() && !client()->backForwardLength()) |
| return FrameLoadTypeStandard; |
| if (m_provisionalDocumentLoader && |
| request.substituteData().failingURL() == |
| m_provisionalDocumentLoader->url() && |
| m_loadType == FrameLoadTypeBackForward) |
| return FrameLoadTypeBackForward; |
| if (request.resourceRequest().getCachePolicy() == |
| WebCachePolicy::ValidatingCacheData) |
| return FrameLoadTypeReload; |
| if (request.resourceRequest().getCachePolicy() == |
| WebCachePolicy::BypassingCache) |
| return FrameLoadTypeReloadBypassingCache; |
| // 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() || |
| (!m_stateMachine.committedMultipleRealLoads() && |
| equalIgnoringCase(m_frame->document()->url(), blankURL()))) |
| return FrameLoadTypeReplaceCurrentItem; |
| |
| if (request.resourceRequest().url() == m_documentLoader->urlForHistory()) { |
| if (!request.originDocument()) |
| return FrameLoadTypeReloadMainResource; |
| return request.resourceRequest().httpMethod() == HTTPNames::POST |
| ? FrameLoadTypeStandard |
| : FrameLoadTypeReplaceCurrentItem; |
| } |
| |
| if (request.substituteData().failingURL() == |
| m_documentLoader->urlForHistory() && |
| m_loadType == FrameLoadTypeReload) |
| return FrameLoadTypeReload; |
| |
| if (m_frame->settings()->historyEntryRequiresUserGesture() && |
| request.originDocument() && |
| !request.originDocument()->hasReceivedUserGesture()) |
| return FrameLoadTypeReplaceCurrentItem; |
| |
| return FrameLoadTypeStandard; |
| } |
| |
| 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.resourceRequest().url(); |
| if (m_frame->script().executeScriptIfJavaScriptURL(url, nullptr)) |
| return false; |
| |
| if (!request.originDocument()->getSecurityOrigin()->canDisplay(url)) { |
| reportLocalLoadFailed(m_frame, url.elidedString()); |
| return false; |
| } |
| |
| if (!request.form() && request.frameName().isEmpty()) |
| request.setFrameName(m_frame->document()->baseTarget()); |
| return true; |
| } |
| |
| static bool shouldNavigateTargetFrame(NavigationPolicy policy) { |
| switch (policy) { |
| case NavigationPolicyCurrentTab: |
| return true; |
| |
| // Navigation will target a *new* frame (e.g. because of a ctrl-click), |
| // so the target frame can be ignored. |
| case NavigationPolicyNewBackgroundTab: |
| case NavigationPolicyNewForegroundTab: |
| case NavigationPolicyNewWindow: |
| case NavigationPolicyNewPopup: |
| return false; |
| |
| // Navigation won't really target any specific frame, |
| // so the target frame can be ignored. |
| case NavigationPolicyIgnore: |
| case NavigationPolicyDownload: |
| return false; |
| |
| case NavigationPolicyHandledByClient: |
| // 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 frameLoadType, |
| bool isFormSubmission, |
| bool haveEvent) { |
| bool isReload = isReloadLoadType(frameLoadType); |
| bool isBackForward = isBackForwardLoadType(frameLoadType); |
| if (isFormSubmission) { |
| return (isReload || isBackForward) ? NavigationTypeFormResubmitted |
| : NavigationTypeFormSubmitted; |
| } |
| if (haveEvent) |
| return NavigationTypeLinkClicked; |
| if (isReload) |
| return NavigationTypeReload; |
| if (isBackForward) |
| return NavigationTypeBackForward; |
| return NavigationTypeOther; |
| } |
| |
| static WebURLRequest::RequestContext determineRequestContextFromNavigationType( |
| const NavigationType navigationType) { |
| switch (navigationType) { |
| case NavigationTypeLinkClicked: |
| return WebURLRequest::RequestContextHyperlink; |
| |
| case NavigationTypeOther: |
| return WebURLRequest::RequestContextLocation; |
| |
| case NavigationTypeFormResubmitted: |
| case NavigationTypeFormSubmitted: |
| return WebURLRequest::RequestContextForm; |
| |
| case NavigationTypeBackForward: |
| case NavigationTypeReload: |
| return WebURLRequest::RequestContextInternal; |
| } |
| NOTREACHED(); |
| return WebURLRequest::RequestContextHyperlink; |
| } |
| |
| static NavigationPolicy navigationPolicyForRequest( |
| const FrameLoadRequest& request) { |
| NavigationPolicy policy = NavigationPolicyCurrentTab; |
| Event* event = request.triggeringEvent(); |
| if (!event) |
| return policy; |
| |
| if (request.form() && event->underlyingEvent()) |
| event = event->underlyingEvent(); |
| |
| if (event->isMouseEvent()) { |
| MouseEvent* mouseEvent = toMouseEvent(event); |
| navigationPolicyFromMouseEvent(mouseEvent->button(), mouseEvent->ctrlKey(), |
| mouseEvent->shiftKey(), mouseEvent->altKey(), |
| mouseEvent->metaKey(), &policy); |
| } else if (event->isKeyboardEvent()) { |
| // The click is simulated when triggering the keypress event. |
| KeyboardEvent* keyEvent = toKeyboardEvent(event); |
| navigationPolicyFromMouseEvent(0, keyEvent->ctrlKey(), keyEvent->shiftKey(), |
| keyEvent->altKey(), keyEvent->metaKey(), |
| &policy); |
| } else if (event->isGestureEvent()) { |
| // The click is simulated when triggering the gesture-tap event |
| GestureEvent* gestureEvent = toGestureEvent(event); |
| navigationPolicyFromMouseEvent( |
| 0, gestureEvent->ctrlKey(), gestureEvent->shiftKey(), |
| gestureEvent->altKey(), gestureEvent->metaKey(), &policy); |
| } |
| return policy; |
| } |
| |
| void FrameLoader::load(const FrameLoadRequest& passedRequest, |
| FrameLoadType frameLoadType, |
| HistoryItem* historyItem, |
| HistoryLoadType historyLoadType) { |
| DCHECK(m_frame->document()); |
| |
| if (!m_frame->isNavigationAllowed()) |
| return; |
| |
| if (m_inStopAllLoaders) |
| return; |
| |
| if (m_frame->page()->defersLoading() && |
| isBackForwardLoadType(frameLoadType)) { |
| m_deferredHistoryLoad = DeferredHistoryLoad::create( |
| passedRequest.resourceRequest(), historyItem, frameLoadType, |
| historyLoadType); |
| return; |
| } |
| |
| FrameLoadRequest request(passedRequest); |
| request.resourceRequest().setHasUserGesture( |
| UserGestureIndicator::processingUserGesture()); |
| |
| if (!prepareRequestForThisFrame(request)) |
| return; |
| |
| if (isBackForwardLoadType(frameLoadType)) { |
| DCHECK(historyItem); |
| m_provisionalItem = historyItem; |
| } |
| |
| // Form submissions appear to need their special-case of finding the target at |
| // schedule rather than at fire. |
| Frame* targetFrame = request.form() |
| ? nullptr |
| : m_frame->findFrameForNavigation( |
| AtomicString(request.frameName()), *m_frame); |
| NavigationPolicy policy = navigationPolicyForRequest(request); |
| if (targetFrame && targetFrame != m_frame && |
| shouldNavigateTargetFrame(policy)) { |
| bool wasInSamePage = targetFrame->page() == m_frame->page(); |
| |
| request.setFrameName("_self"); |
| targetFrame->navigate(request); |
| Page* page = targetFrame->page(); |
| if (!wasInSamePage && page) |
| page->chromeClient().focus(); |
| return; |
| } |
| |
| setReferrerForFrameRequest(request); |
| |
| if (!targetFrame && !request.frameName().isEmpty()) { |
| if (policy == NavigationPolicyDownload) { |
| client()->loadURLExternally(request.resourceRequest(), |
| NavigationPolicyDownload, String(), false); |
| } else { |
| request.resourceRequest().setFrameType(WebURLRequest::FrameTypeAuxiliary); |
| createWindowForRequest(request, *m_frame, policy); |
| } |
| return; |
| } |
| |
| const KURL& url = request.resourceRequest().url(); |
| FrameLoadType newLoadType = (frameLoadType == FrameLoadTypeStandard) |
| ? determineFrameLoadType(request) |
| : frameLoadType; |
| bool sameDocumentHistoryNavigation = |
| isBackForwardLoadType(newLoadType) && |
| historyLoadType == HistorySameDocumentLoad; |
| bool sameDocumentNavigation = |
| policy == NavigationPolicyCurrentTab && |
| shouldPerformFragmentNavigation(request.form(), |
| request.resourceRequest().httpMethod(), |
| newLoadType, url); |
| |
| // Perform same document navigation. |
| if (sameDocumentHistoryNavigation || sameDocumentNavigation) { |
| DCHECK(historyItem || !sameDocumentHistoryNavigation); |
| RefPtr<SerializedScriptValue> stateObject = |
| sameDocumentHistoryNavigation ? historyItem->stateObject() : nullptr; |
| |
| if (!sameDocumentHistoryNavigation) { |
| m_documentLoader->setNavigationType(determineNavigationType( |
| newLoadType, false, request.triggeringEvent())); |
| if (shouldTreatURLAsSameAsCurrent(url)) |
| newLoadType = FrameLoadTypeReplaceCurrentItem; |
| } |
| |
| loadInSameDocument(url, stateObject, newLoadType, historyLoadType, |
| request.clientRedirect(), request.originDocument()); |
| return; |
| } |
| |
| startLoad(request, newLoadType, policy); |
| } |
| |
| SubstituteData FrameLoader::defaultSubstituteDataForURL(const KURL& url) { |
| if (!shouldTreatURLAsSrcdocDocument(url)) |
| return SubstituteData(); |
| String srcdoc = m_frame->deprecatedLocalOwner()->fastGetAttribute(srcdocAttr); |
| DCHECK(!srcdoc.isNull()); |
| CString encodedSrcdoc = srcdoc.utf8(); |
| return SubstituteData( |
| SharedBuffer::create(encodedSrcdoc.data(), encodedSrcdoc.length()), |
| "text/html", "UTF-8", KURL()); |
| } |
| |
| void FrameLoader::reportLocalLoadFailed(LocalFrame* frame, const String& url) { |
| DCHECK(!url.isEmpty()); |
| if (!frame) |
| return; |
| |
| frame->document()->addConsoleMessage( |
| ConsoleMessage::create(SecurityMessageSource, ErrorMessageLevel, |
| "Not allowed to load local resource: " + url)); |
| } |
| |
| void FrameLoader::stopAllLoaders() { |
| if (m_frame->document()->pageDismissalEventBeingDispatched() != |
| Document::NoDismissal) |
| return; |
| |
| // If this method is called from within this method, infinite recursion can |
| // occur (3442218). Avoid this. |
| if (m_inStopAllLoaders) |
| return; |
| |
| m_inStopAllLoaders = true; |
| |
| m_isNavigationHandledByClient = false; |
| |
| for (Frame* child = m_frame->tree().firstChild(); child; |
| child = child->tree().nextSibling()) { |
| if (child->isLocalFrame()) |
| toLocalFrame(child)->loader().stopAllLoaders(); |
| } |
| |
| m_frame->document()->suppressLoadEvent(); |
| if (m_documentLoader) |
| m_documentLoader->fetcher()->stopFetching(); |
| m_frame->document()->cancelParsing(); |
| if (!m_protectProvisionalLoader) |
| detachDocumentLoader(m_provisionalDocumentLoader); |
| |
| m_checkTimer.stop(); |
| m_frame->navigationScheduler().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 (!m_provisionalDocumentLoader && m_documentLoader && |
| m_frame->isLoading()) { |
| loadFailed(m_documentLoader.get(), |
| ResourceError::cancelledError(m_documentLoader->url())); |
| } |
| |
| m_inStopAllLoaders = 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 forbidScripts; |
| if (client()) |
| client()->didAccessInitialDocument(); |
| } |
| } |
| |
| bool FrameLoader::prepareForCommit() { |
| PluginScriptForbiddenScope forbidPluginDestructorScripting; |
| DocumentLoader* pdl = m_provisionalDocumentLoader; |
| |
| if (m_frame->document()) { |
| unsigned nodeCount = 0; |
| for (Frame* frame = m_frame; frame; frame = frame->tree().traverseNext()) { |
| if (frame->isLocalFrame()) { |
| LocalFrame* localFrame = toLocalFrame(frame); |
| nodeCount += localFrame->document()->nodeCount(); |
| } |
| } |
| unsigned totalNodeCount = |
| InstanceCounters::counterValue(InstanceCounters::NodeCounter); |
| float ratio = static_cast<float>(nodeCount) / totalNodeCount; |
| 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(m_frame->document()); |
| if (m_documentLoader) { |
| client()->dispatchWillCommitProvisionalLoad(); |
| dispatchUnloadEvent(); |
| } |
| m_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 != m_provisionalDocumentLoader) |
| 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 (m_documentLoader) { |
| AutoReset<bool> inDetachDocumentLoader(&m_protectProvisionalLoader, true); |
| detachDocumentLoader(m_documentLoader); |
| } |
| // 'abort' listeners can also detach the frame. |
| if (!m_frame->client()) |
| return false; |
| DCHECK_EQ(m_provisionalDocumentLoader, 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 (m_frame->document()) |
| m_frame->document()->shutdown(); |
| m_documentLoader = m_provisionalDocumentLoader.release(); |
| 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 (m_frame->document()) { |
| RefPtr<SecurityOrigin> securityOrigin = |
| SecurityOrigin::create(m_provisionalDocumentLoader->request().url()); |
| m_provisionalDocumentLoader->timing().setHasSameOriginAsPreviousDocument( |
| securityOrigin->canRequest(m_frame->document()->url())); |
| } |
| |
| if (!prepareForCommit()) |
| return; |
| |
| if (isLoadingMainFrame()) { |
| m_frame->page()->chromeClient().setEventListenerProperties( |
| WebEventListenerClass::TouchStartOrMove, |
| WebEventListenerProperties::Nothing); |
| m_frame->page()->chromeClient().setEventListenerProperties( |
| WebEventListenerClass::MouseWheel, WebEventListenerProperties::Nothing); |
| m_frame->page()->chromeClient().setEventListenerProperties( |
| WebEventListenerClass::TouchEndOrCancel, |
| WebEventListenerProperties::Nothing); |
| } |
| |
| client()->transitionToCommittedForNewPage(); |
| m_frame->navigationScheduler().cancel(); |
| m_frame->editor().clearLastEditCommand(); |
| |
| // If we are still in the process of initializing an empty document then its |
| // frame is not in a consistent state for rendering, so avoid |
| // setJSStatusBarText since it may cause clients to attempt to render the |
| // frame. |
| if (!m_stateMachine.creatingInitialEmptyDocument()) { |
| DOMWindow* window = m_frame->domWindow(); |
| window->setStatus(String()); |
| window->setDefaultStatus(String()); |
| } |
| } |
| |
| bool FrameLoader::isLoadingMainFrame() const { |
| return m_frame->isMainFrame(); |
| } |
| |
| FrameLoadType FrameLoader::loadType() const { |
| return m_loadType; |
| } |
| |
| void FrameLoader::restoreScrollPositionAndViewState() { |
| FrameView* view = m_frame->view(); |
| if (!m_frame->page() || !view || !view->layoutViewportScrollableArea() || |
| !m_currentItem || !m_stateMachine.committedFirstRealDocumentLoad() || |
| !documentLoader()) { |
| return; |
| } |
| |
| if (!needsHistoryItemRestore(m_loadType)) |
| return; |
| |
| bool shouldRestoreScroll = |
| m_currentItem->scrollRestorationType() != ScrollRestorationManual; |
| bool shouldRestoreScale = m_currentItem->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 canRestoreWithoutClamping = |
| view->layoutViewportScrollableArea()->clampScrollOffset( |
| m_currentItem->scrollOffset()) == m_currentItem->scrollOffset(); |
| bool canRestoreWithoutAnnoyingUser = |
| !documentLoader()->initialScrollState().wasScrolledByUser && |
| (canRestoreWithoutClamping || !m_frame->isLoading() || |
| !shouldRestoreScroll); |
| if (!canRestoreWithoutAnnoyingUser) |
| return; |
| |
| if (shouldRestoreScroll) { |
| view->layoutViewportScrollableArea()->setScrollOffset( |
| m_currentItem->scrollOffset(), ProgrammaticScroll); |
| } |
| |
| // For main frame restore scale and visual viewport position |
| if (m_frame->isMainFrame()) { |
| ScrollOffset visualViewportOffset( |
| m_currentItem->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 (visualViewportOffset.width() == -1 && |
| visualViewportOffset.height() == -1) { |
| visualViewportOffset = |
| m_currentItem->scrollOffset() - |
| view->layoutViewportScrollableArea()->scrollOffset(); |
| } |
| |
| VisualViewport& visualViewport = m_frame->host()->visualViewport(); |
| if (shouldRestoreScale && shouldRestoreScroll) { |
| visualViewport.setScaleAndLocation(m_currentItem->pageScaleFactor(), |
| FloatPoint(visualViewportOffset)); |
| } else if (shouldRestoreScale) { |
| visualViewport.setScale(m_currentItem->pageScaleFactor()); |
| } else if (shouldRestoreScroll) { |
| visualViewport.setLocation(FloatPoint(visualViewportOffset)); |
| } |
| |
| if (ScrollingCoordinator* scrollingCoordinator = |
| m_frame->page()->scrollingCoordinator()) |
| scrollingCoordinator->frameViewRootLayerDidChange(view); |
| } |
| |
| documentLoader()->initialScrollState().didRestoreFromHistory = true; |
| } |
| |
| String FrameLoader::userAgent() const { |
| String userAgent = client()->userAgent(); |
| InspectorInstrumentation::applyUserAgentOverride(m_frame, &userAgent); |
| return userAgent; |
| } |
| |
| void FrameLoader::detach() { |
| detachDocumentLoader(m_documentLoader); |
| detachDocumentLoader(m_provisionalDocumentLoader); |
| |
| Frame* parent = m_frame->tree().parent(); |
| if (parent && parent->isLocalFrame()) |
| toLocalFrame(parent)->loader().scheduleCheckCompleted(); |
| if (m_progressTracker) { |
| m_progressTracker->dispose(); |
| m_progressTracker.clear(); |
| } |
| |
| TRACE_EVENT_OBJECT_DELETED_WITH_ID("loading", "FrameLoader", this); |
| } |
| |
| void FrameLoader::loadFailed(DocumentLoader* loader, |
| const ResourceError& error) { |
| if (!error.isCancellation() && m_frame->owner()) { |
| // FIXME: For now, fallback content doesn't work cross process. |
| if (m_frame->owner()->isLocal()) |
| m_frame->deprecatedLocalOwner()->renderFallbackContent(); |
| } |
| |
| HistoryCommitType historyCommitType = loadTypeToCommitType(m_loadType); |
| if (loader == m_provisionalDocumentLoader) { |
| client()->dispatchDidFailProvisionalLoad(error, historyCommitType); |
| if (loader != m_provisionalDocumentLoader) |
| return; |
| detachDocumentLoader(m_provisionalDocumentLoader); |
| m_progressTracker->progressCompleted(); |
| } else { |
| DCHECK_EQ(loader, m_documentLoader); |
| if (m_frame->document()->parser()) |
| m_frame->document()->parser()->stopParsing(); |
| m_documentLoader->setSentDidFinishLoad(); |
| if (!m_provisionalDocumentLoader && m_frame->isLoading()) { |
| client()->dispatchDidFailLoad(error, historyCommitType); |
| m_progressTracker->progressCompleted(); |
| } |
| } |
| checkCompleted(); |
| } |
| |
| bool FrameLoader::shouldPerformFragmentNavigation(bool isFormSubmission, |
| const String& httpMethod, |
| FrameLoadType loadType, |
| 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 (!isFormSubmission || equalIgnoringCase(httpMethod, HTTPNames::GET)) && |
| !isReloadLoadType(loadType) && loadType != FrameLoadTypeBackForward && |
| url.hasFragmentIdentifier() && |
| equalIgnoringFragmentIdentifier(m_frame->document()->url(), url) |
| // We don't want to just scroll if a link from within a frameset is |
| // trying to reload the frameset into _top. |
| && !m_frame->document()->isFrameSet(); |
| } |
| |
| void FrameLoader::processFragment(const KURL& url, |
| LoadStartType loadStartType) { |
| FrameView* view = m_frame->view(); |
| if (!view) |
| return; |
| |
| // Leaking scroll position to a cross-origin ancestor would permit the |
| // so-called "framesniffing" attack. |
| Frame* boundaryFrame = |
| url.hasFragmentIdentifier() |
| ? m_frame->findUnsafeParentScrollPropagationBoundary() |
| : 0; |
| |
| // FIXME: Handle RemoteFrames |
| if (boundaryFrame && boundaryFrame->isLocalFrame()) { |
| toLocalFrame(boundaryFrame) |
| ->view() |
| ->setSafeToPropagateScrollToParent(false); |
| } |
| |
| // If scroll position is restored from history fragment then we should not |
| // override it unless this is a same document reload. |
| bool shouldScrollToFragment = |
| (loadStartType == NavigationWithinSameDocument && |
| !isBackForwardLoadType(m_loadType)) || |
| (documentLoader() && |
| !documentLoader()->initialScrollState().didRestoreFromHistory); |
| |
| view->processUrlFragment(url, shouldScrollToFragment |
| ? FrameView::UrlFragmentScroll |
| : FrameView::UrlFragmentDontScroll); |
| |
| if (boundaryFrame && boundaryFrame->isLocalFrame()) |
| toLocalFrame(boundaryFrame)->view()->setSafeToPropagateScrollToParent(true); |
| } |
| |
| bool FrameLoader::shouldClose(bool isReload) { |
| Page* page = m_frame->page(); |
| if (!page || !page->chromeClient().canOpenBeforeUnloadConfirmPanel()) |
| return true; |
| |
| // Store all references to each subframe in advance since beforeunload's event |
| // handler may modify frame |
| HeapVector<Member<LocalFrame>> targetFrames; |
| targetFrames.append(m_frame); |
| for (Frame* child = m_frame->tree().firstChild(); child; |
| child = child->tree().traverseNext(m_frame)) { |
| // FIXME: There is not yet any way to dispatch events to out-of-process |
| // frames. |
| if (child->isLocalFrame()) |
| targetFrames.append(toLocalFrame(child)); |
| } |
| |
| bool shouldClose = false; |
| { |
| NavigationDisablerForUnload navigationDisabler; |
| size_t i; |
| |
| bool didAllowNavigation = false; |
| for (i = 0; i < targetFrames.size(); i++) { |
| if (!targetFrames[i]->tree().isDescendantOf(m_frame)) |
| continue; |
| if (!targetFrames[i]->document()->dispatchBeforeUnloadEvent( |
| page->chromeClient(), isReload, didAllowNavigation)) |
| break; |
| } |
| |
| if (i == targetFrames.size()) |
| shouldClose = true; |
| } |
| |
| return shouldClose; |
| } |
| |
| bool FrameLoader::shouldContinueForNavigationPolicy( |
| const ResourceRequest& request, |
| const SubstituteData& substituteData, |
| DocumentLoader* loader, |
| ContentSecurityPolicyDisposition shouldCheckMainWorldContentSecurityPolicy, |
| NavigationType type, |
| NavigationPolicy policy, |
| bool replacesCurrentHistoryItem, |
| bool isClientRedirect, |
| HTMLFormElement* form) { |
| m_isNavigationHandledByClient = false; |
| |
| // Don't ask if we are loading an empty URL. |
| if (request.url().isEmpty() || substituteData.isValid()) |
| return true; |
| |
| // If we're loading content into |m_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 == NavigationPolicyCurrentTab && |
| shouldCheckMainWorldContentSecurityPolicy == CheckContentSecurityPolicy) { |
| Frame* parentFrame = m_frame->tree().parent(); |
| if (parentFrame) { |
| ContentSecurityPolicy* parentPolicy = |
| parentFrame->securityContext()->contentSecurityPolicy(); |
| if (!parentPolicy->allowChildFrameFromSource(request.url(), |
| request.redirectStatus())) { |
| // 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. |
| m_frame->document()->enforceSandboxFlags(SandboxOrigin); |
| m_frame->owner()->dispatchLoad(); |
| return false; |
| } |
| } |
| } |
| |
| bool isFormSubmission = type == NavigationTypeFormSubmitted || |
| type == NavigationTypeFormResubmitted; |
| if (isFormSubmission && |
| !m_frame->document()->contentSecurityPolicy()->allowFormAction( |
| request.url())) |
| return false; |
| |
| policy = client()->decidePolicyForNavigation(request, loader, type, policy, |
| replacesCurrentHistoryItem, |
| isClientRedirect); |
| if (policy == NavigationPolicyCurrentTab) |
| return true; |
| if (policy == NavigationPolicyIgnore) |
| return false; |
| if (policy == NavigationPolicyHandledByClient) { |
| m_isNavigationHandledByClient = true; |
| // Mark the frame as loading since the embedder is handling the navigation. |
| m_progressTracker->progressStarted(); |
| |
| // If this is a form submit, dispatch that a form is being submitted |
| // since the embedder is handling the navigation. |
| if (form) |
| client()->dispatchWillSubmitForm(form); |
| |
| return false; |
| } |
| if (!LocalDOMWindow::allowPopUp(*m_frame) && |
| !UserGestureIndicator::utilizeUserGesture()) |
| return false; |
| client()->loadURLExternally(request, policy, String(), |
| replacesCurrentHistoryItem); |
| return false; |
| } |
| |
| void FrameLoader::startLoad(FrameLoadRequest& frameLoadRequest, |
| FrameLoadType type, |
| NavigationPolicy navigationPolicy) { |
| DCHECK(client()->hasWebView()); |
| if (m_frame->document()->pageDismissalEventBeingDispatched() != |
| Document::NoDismissal) |
| return; |
| |
| NavigationType navigationType = determineNavigationType( |
| type, |
| frameLoadRequest.resourceRequest().httpBody() || frameLoadRequest.form(), |
| frameLoadRequest.triggeringEvent()); |
| frameLoadRequest.resourceRequest().setRequestContext( |
| determineRequestContextFromNavigationType(navigationType)); |
| frameLoadRequest.resourceRequest().setFrameType( |
| m_frame->isMainFrame() ? WebURLRequest::FrameTypeTopLevel |
| : WebURLRequest::FrameTypeNested); |
| ResourceRequest& request = frameLoadRequest.resourceRequest(); |
| |
| // Record the latest requiredCSP value that will be used when sending this |
| // request. |
| recordLatestRequiredCSP(); |
| modifyRequestForCSP(request, nullptr); |
| if (!shouldContinueForNavigationPolicy( |
| request, frameLoadRequest.substituteData(), nullptr, |
| frameLoadRequest.shouldCheckMainWorldContentSecurityPolicy(), |
| navigationType, navigationPolicy, |
| type == FrameLoadTypeReplaceCurrentItem, |
| frameLoadRequest.clientRedirect() == |
| ClientRedirectPolicy::ClientRedirect, |
| frameLoadRequest.form())) |
| return; |
| |
| m_frame->document()->cancelParsing(); |
| detachDocumentLoader(m_provisionalDocumentLoader); |
| |
| // beforeunload fired above, and detaching a DocumentLoader can fire events, |
| // which can detach this frame. |
| if (!m_frame->host()) |
| return; |
| |
| m_provisionalDocumentLoader = client()->createDocumentLoader( |
| m_frame, request, frameLoadRequest.substituteData().isValid() |
| ? frameLoadRequest.substituteData() |
| : defaultSubstituteDataForURL(request.url()), |
| frameLoadRequest.clientRedirect()); |
| m_provisionalDocumentLoader->setNavigationType(navigationType); |
| m_provisionalDocumentLoader->setReplacesCurrentHistoryItem( |
| type == FrameLoadTypeReplaceCurrentItem); |
| m_frame->navigationScheduler().cancel(); |
| m_checkTimer.stop(); |
| |
| m_loadType = type; |
| |
| if (frameLoadRequest.form()) |
| client()->dispatchWillSubmitForm(frameLoadRequest.form()); |
| |
| m_progressTracker->progressStarted(); |
| m_provisionalDocumentLoader->appendRedirect( |
| m_provisionalDocumentLoader->request().url()); |
| double triggeringEventTime = |
| frameLoadRequest.triggeringEvent() |
| ? frameLoadRequest.triggeringEvent()->platformTimeStamp() |
| : 0; |
| client()->dispatchDidStartProvisionalLoad(triggeringEventTime); |
| DCHECK(m_provisionalDocumentLoader); |
| m_provisionalDocumentLoader->startLoadingMainResource(); |
| |
| takeObjectSnapshot(); |
| } |
| |
| void FrameLoader::applyUserAgent(ResourceRequest& request) { |
| String userAgent = this->userAgent(); |
| DCHECK(!userAgent.isNull()); |
| request.setHTTPUserAgent(AtomicString(userAgent)); |
| } |
| |
| bool FrameLoader::shouldInterruptLoadForXFrameOptions( |
| const String& content, |
| const KURL& url, |
| unsigned long requestIdentifier) { |
| UseCounter::count(m_frame->domWindow()->document(), |
| UseCounter::XFrameOptions); |
| |
| Frame* topFrame = m_frame->tree().top(); |
| if (m_frame == topFrame) |
| return false; |
| |
| XFrameOptionsDisposition disposition = parseXFrameOptionsHeader(content); |
| |
| switch (disposition) { |
| case XFrameOptionsSameOrigin: { |
| UseCounter::count(m_frame->domWindow()->document(), |
| UseCounter::XFrameOptionsSameOrigin); |
| RefPtr<SecurityOrigin> origin = SecurityOrigin::create(url); |
| // Out-of-process ancestors are always a different origin. |
| if (!topFrame->isLocalFrame() || |
| !origin->isSameSchemeHostPort( |
| toLocalFrame(topFrame)->document()->getSecurityOrigin())) |
| return true; |
| for (Frame* frame = m_frame->tree().parent(); frame; |
| frame = frame->tree().parent()) { |
| if (!frame->isLocalFrame() || |
| !origin->isSameSchemeHostPort( |
| toLocalFrame(frame)->document()->getSecurityOrigin())) { |
| UseCounter::count( |
| m_frame->domWindow()->document(), |
| UseCounter::XFrameOptionsSameOriginWithBadAncestorChain); |
| break; |
| } |
| } |
| return false; |
| } |
| case XFrameOptionsDeny: |
| return true; |
| case XFrameOptionsAllowAll: |
| return false; |
| case XFrameOptionsConflict: { |
| ConsoleMessage* consoleMessage = ConsoleMessage::createForRequest( |
| JSMessageSource, ErrorMessageLevel, |
| "Multiple 'X-Frame-Options' headers with conflicting values ('" + |
| content + "') encountered when loading '" + url.elidedString() + |
| "'. Falling back to 'DENY'.", |
| url, requestIdentifier); |
| m_frame->document()->addConsoleMessage(consoleMessage); |
| return true; |
| } |
| case XFrameOptionsInvalid: { |
| ConsoleMessage* consoleMessage = ConsoleMessage::createForRequest( |
| JSMessageSource, ErrorMessageLevel, |
| "Invalid 'X-Frame-Options' header encountered when loading '" + |
| url.elidedString() + "': '" + content + |
| "' is not a recognized directive. The header will be ignored.", |
| url, requestIdentifier); |
| m_frame->document()->addConsoleMessage(consoleMessage); |
| return false; |
| } |
| default: |
| NOTREACHED(); |
| return false; |
| } |
| } |
| |
| bool FrameLoader::shouldTreatURLAsSameAsCurrent(const KURL& url) const { |
| return m_currentItem && url == m_currentItem->url(); |
| } |
| |
| bool FrameLoader::shouldTreatURLAsSrcdocDocument(const KURL& url) const { |
| if (!url.isAboutSrcdocURL()) |
| return false; |
| HTMLFrameOwnerElement* ownerElement = m_frame->deprecatedLocalOwner(); |
| if (!isHTMLIFrameElement(ownerElement)) |
| return false; |
| return ownerElement->fastHasAttribute(srcdocAttr); |
| } |
| |
| void FrameLoader::dispatchDocumentElementAvailable() { |
| ScriptForbiddenScope forbidScripts; |
| client()->documentElementAvailable(); |
| } |
| |
| void FrameLoader::runScriptsAtDocumentElementAvailable() { |
| client()->runScriptsAtDocumentElementAvailable(); |
| // The frame might be detached at this point. |
| } |
| |
| void FrameLoader::dispatchDidClearDocumentOfWindowObject() { |
| if (!m_frame->script().canExecuteScripts(NotAboutToExecuteScript)) |
| return; |
| |
| Settings* settings = m_frame->settings(); |
| if (settings && settings->forceMainWorldInitialization()) |
| m_frame->script().initializeMainWorld(); |
| InspectorInstrumentation::didClearDocumentOfWindowObject(m_frame); |
| |
| if (m_dispatchingDidClearWindowObjectInMainWorld) |
| return; |
| AutoReset<bool> inDidClearWindowObject( |
| &m_dispatchingDidClearWindowObjectInMainWorld, 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 (!m_frame->script().canExecuteScripts(NotAboutToExecuteScript)) |
| return; |
| |
| if (m_dispatchingDidClearWindowObjectInMainWorld) |
| return; |
| AutoReset<bool> inDidClearWindowObject( |
| &m_dispatchingDidClearWindowObjectInMainWorld, true); |
| client()->dispatchDidClearWindowObjectInMainWorld(); |
| } |
| |
| SandboxFlags FrameLoader::effectiveSandboxFlags() const { |
| SandboxFlags flags = m_forcedSandboxFlags; |
| if (FrameOwner* frameOwner = m_frame->owner()) |
| flags |= frameOwner->getSandboxFlags(); |
| // Frames need to inherit the sandbox flags of their parent frame. |
| if (Frame* parentFrame = m_frame->tree().parent()) |
| flags |= parentFrame->securityContext()->getSandboxFlags(); |
| return flags; |
| } |
| |
| WebInsecureRequestPolicy FrameLoader::getInsecureRequestPolicy() const { |
| Frame* parentFrame = m_frame->tree().parent(); |
| if (!parentFrame) |
| return kLeaveInsecureRequestsAlone; |
| |
| return parentFrame->securityContext()->getInsecureRequestPolicy(); |
| } |
| |
| SecurityContext::InsecureNavigationsSet* |
| FrameLoader::insecureNavigationsToUpgrade() const { |
| DCHECK(m_frame); |
| Frame* parentFrame = m_frame->tree().parent(); |
| if (!parentFrame) |
| 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 (!parentFrame->isLocalFrame()) |
| return nullptr; |
| |
| DCHECK(toLocalFrame(parentFrame)->document()); |
| return toLocalFrame(parentFrame)->document()->insecureNavigationsToUpgrade(); |
| } |
| |
| void FrameLoader::modifyRequestForCSP(ResourceRequest& resourceRequest, |
| Document* document) const { |
| if (RuntimeEnabledFeatures::embedderCSPEnforcementEnabled() && |
| !requiredCSP().isEmpty()) { |
| // TODO(amalika): Strengthen this DCHECK that requiredCSP has proper format |
| DCHECK(requiredCSP().getString().containsOnlyASCII()); |
| resourceRequest.setHTTPHeaderField(HTTPNames::Embedding_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 (resourceRequest.frameType() != WebURLRequest::FrameTypeNone) { |
| // Early return if the request has already been upgraded. |
| if (resourceRequest.httpHeaderField("Upgrade-Insecure-Requests") == |
| AtomicString("1")) |
| return; |
| |
| resourceRequest.addHTTPHeaderField("Upgrade-Insecure-Requests", "1"); |
| } |
| |
| upgradeInsecureRequest(resourceRequest, document); |
| } |
| |
| void FrameLoader::upgradeInsecureRequest(ResourceRequest& resourceRequest, |
| Document* document) const { |
| KURL url = resourceRequest.url(); |
| |
| // If we don't yet have an |m_document| (because we're loading an iframe, for |
| // instance), check the FrameLoader's policy. |
| WebInsecureRequestPolicy relevantPolicy = |
| document ? document->getInsecureRequestPolicy() |
| : getInsecureRequestPolicy(); |
| SecurityContext::InsecureNavigationsSet* relevantNavigationSet = |
| document ? document->insecureNavigationsToUpgrade() |
| : insecureNavigationsToUpgrade(); |
| |
| if (url.protocolIs("http") && relevantPolicy & 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 (resourceRequest.frameType() == WebURLRequest::FrameTypeNone || |
| resourceRequest.frameType() == WebURLRequest::FrameTypeNested || |
| resourceRequest.requestContext() == WebURLRequest::RequestContextForm || |
| (!url.host().isNull() && |
| relevantNavigationSet->contains(url.host().impl()->hash()))) { |
| UseCounter::count(document, |
| UseCounter::UpgradeInsecureRequestsUpgradedRequest); |
| url.setProtocol("https"); |
| if (url.port() == 80) |
| url.setPort(443); |
| resourceRequest.setURL(url); |
| } |
| } |
| } |
| |
| void FrameLoader::recordLatestRequiredCSP() { |
| m_requiredCSP = m_frame->owner() ? m_frame->owner()->csp() : nullAtom; |
| } |
| |
| std::unique_ptr<TracedValue> FrameLoader::toTracedValue() const { |
| std::unique_ptr<TracedValue> tracedValue = TracedValue::create(); |
| tracedValue->beginDictionary("frame"); |
| tracedValue->setString( |
| "id_ref", |
| String::format( |
| "0x%" PRIx64, |
| static_cast<uint64_t>(reinterpret_cast<uintptr_t>(m_frame.get())))); |
| tracedValue->endDictionary(); |
| tracedValue->setBoolean("isLoadingMainFrame", isLoadingMainFrame()); |
| tracedValue->setString("stateMachine", m_stateMachine.toString()); |
| tracedValue->setString("provisionalDocumentLoaderURL", |
| m_provisionalDocumentLoader |
| ? m_provisionalDocumentLoader->url() |
| : String()); |
| tracedValue->setString("documentLoaderURL", |
| m_documentLoader ? m_documentLoader->url() : String()); |
| return tracedValue; |
| } |
| |
| inline void FrameLoader::takeObjectSnapshot() const { |
| TRACE_EVENT_OBJECT_SNAPSHOT_WITH_ID("loading", "FrameLoader", this, |
| toTracedValue()); |
| } |
| |
| } // namespace blink |