blob: 6dc607167d33f28a94ee4f7f5078d86f0bf1a722 [file] [log] [blame]
/*
* 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/InstanceCounters.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/PluginScriptForbiddenScope.h"
#include "platform/RuntimeEnabledFeatures.h"
#include "platform/ScriptForbiddenScope.h"
#include "platform/TraceEvent.h"
#include "platform/UserGestureIndicator.h"
#include "platform/network/HTTPParsers.h"
#include "platform/network/ResourceRequest.h"
#include "platform/scroll/ScrollAnimatorBase.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());
RefPtr<SecurityOrigin> securityOrigin =
SecurityOrigin::createFromString(item->referrer().referrer);
request.addHTTPOriginIfNeeded(securityOrigin.get());
}
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(Referrer(m_frame->document()->outgoingReferrer(),
m_frame->document()->getReferrerPolicy()));
}
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());
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->setScrollPoint(layoutScrollableArea->scrollPosition());
m_currentItem->setVisualViewportScrollPoint(
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::replaceDocumentWhileExecutingJavaScriptURL can cause the
// DocumentLoader to get deref'ed and possible destroyed, so protect it with a
// RefPtr.
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) {
if (m_frame->settings()->historyEntryRequiresUserGesture() &&
historyCommitType == StandardCommit)
UserGestureIndicator::clearProcessedUserGestureSinceLoad();
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->setScrollPoint(oldItem->scrollPoint());
m_currentItem->setVisualViewportScrollPoint(
oldItem->visualViewportScrollPoint());
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 (m_documentLoader &&
RuntimeEnabledFeatures::referrerPolicyHeaderEnabled()) {
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; // We are being destroyed by something checkCompleted called.
// 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() &&
!UserGestureIndicator::processedUserGestureSinceLoad() &&
initiatingDocument)
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);
RefPtr<SecurityOrigin> referrerOrigin =
SecurityOrigin::createFromString(referrer.referrer);
request.addHTTPOriginIfNeeded(referrerOrigin.get());
}
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() &&
!UserGestureIndicator::processedUserGestureSinceLoad() &&
request.originDocument())
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))
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 shouldOpenInNewWindow(Frame* targetFrame,
const FrameLoadRequest& request,
NavigationPolicy policy) {
if (!targetFrame && !request.frameName().isEmpty())
return true;
// FIXME: This case is a workaround for the fact that ctrl+clicking a form
// submission incorrectly sends as a GET rather than a POST if it creates a
// new window in a different process.
return request.form() && policy != NavigationPolicyCurrentTab;
}
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;
Frame* targetFrame = request.form()
? nullptr
: m_frame->findFrameForNavigation(
AtomicString(request.frameName()), *m_frame);
if (isBackForwardLoadType(frameLoadType)) {
DCHECK(historyItem);
m_provisionalItem = historyItem;
}
if (targetFrame && targetFrame != m_frame) {
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);
FrameLoadType newLoadType = (frameLoadType == FrameLoadTypeStandard)
? determineFrameLoadType(request)
: frameLoadType;
NavigationPolicy policy = navigationPolicyForRequest(request);
if (shouldOpenInNewWindow(targetFrame, request, policy)) {
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();
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()->clampScrollPosition(
m_currentItem->scrollPoint()) == m_currentItem->scrollPoint();
bool canRestoreWithoutAnnoyingUser =
!documentLoader()->initialScrollState().wasScrolledByUser &&
(canRestoreWithoutClamping || !m_frame->isLoading() ||
!shouldRestoreScroll);
if (!canRestoreWithoutAnnoyingUser)
return;
if (shouldRestoreScroll)
view->layoutViewportScrollableArea()->setScrollPosition(
m_currentItem->scrollPoint(), ProgrammaticScroll);
// For main frame restore scale and visual viewport position
if (m_frame->isMainFrame()) {
FloatPoint visualViewportOffset(m_currentItem->visualViewportScrollPoint());
// 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.x() == -1 && visualViewportOffset.y() == -1)
visualViewportOffset =
FloatPoint(m_currentItem->scrollPoint() - view->scrollPosition());
VisualViewport& visualViewport = m_frame->host()->visualViewport();
if (shouldRestoreScale && shouldRestoreScroll)
visualViewport.setScaleAndLocation(m_currentItem->pageScaleFactor(),
visualViewportOffset);
else if (shouldRestoreScale)
visualViewport.setScale(m_currentItem->pageScaleFactor());
else if (shouldRestoreScroll)
visualViewport.setLocation(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 a subframe, 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 (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();
upgradeInsecureRequest(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()));
m_provisionalDocumentLoader->setNavigationType(navigationType);
m_provisionalDocumentLoader->setReplacesCurrentHistoryItem(
type == FrameLoadTypeReplaceCurrentItem);
m_provisionalDocumentLoader->setIsClientRedirect(
frameLoadRequest.clientRedirect() ==
ClientRedirectPolicy::ClientRedirect);
m_frame->navigationScheduler().cancel();
m_checkTimer.stop();
m_loadType = type;
if (frameLoadRequest.form())
client()->dispatchWillSubmitForm(frameLoadRequest.form());
m_progressTracker->progressStarted();
if (m_provisionalDocumentLoader->isClientRedirect())
m_provisionalDocumentLoader->appendRedirect(m_frame->document()->url());
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::upgradeInsecureRequest(ResourceRequest& resourceRequest,
Document* document) const {
// 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");
}
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);
}
}
}
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