diff options
author | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-06-27 06:07:23 +0000 |
---|---|---|
committer | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-06-27 06:07:23 +0000 |
commit | 1bf1084f2b10c3b47fd1a588d85d21ed0eb41d0c (patch) | |
tree | 46dcd36c86e7fbc6e5df36deb463b33e9967a6f7 /Source/WebCore/loader/FrameLoader.cpp | |
parent | 32761a6cee1d0dee366b885b7b9c777e67885688 (diff) | |
download | WebKitGtk-tarball-1bf1084f2b10c3b47fd1a588d85d21ed0eb41d0c.tar.gz |
webkitgtk-2.16.5HEADwebkitgtk-2.16.5master
Diffstat (limited to 'Source/WebCore/loader/FrameLoader.cpp')
-rw-r--r-- | Source/WebCore/loader/FrameLoader.cpp | 1942 |
1 files changed, 1089 insertions, 853 deletions
diff --git a/Source/WebCore/loader/FrameLoader.cpp b/Source/WebCore/loader/FrameLoader.cpp index c3cc3c559..f5a0a3ed1 100644 --- a/Source/WebCore/loader/FrameLoader.cpp +++ b/Source/WebCore/loader/FrameLoader.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011 Apple Inc. All rights reserved. + * Copyright (C) 2006-2016 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> @@ -12,13 +12,13 @@ * are met: * * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. + * 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 + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple 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. + * 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 @@ -43,13 +43,13 @@ #include "CachedResourceLoader.h" #include "Chrome.h" #include "ChromeClient.h" -#include "Console.h" +#include "ContentFilter.h" #include "ContentSecurityPolicy.h" -#include "DOMImplementation.h" #include "DOMWindow.h" #include "DatabaseManager.h" +#include "DiagnosticLoggingClient.h" +#include "DiagnosticLoggingKeys.h" #include "Document.h" -#include "DocumentLoadTiming.h" #include "DocumentLoader.h" #include "Editor.h" #include "EditorClient.h" @@ -65,37 +65,47 @@ #include "FrameNetworkingContext.h" #include "FrameTree.h" #include "FrameView.h" -#include "HTMLAnchorElement.h" +#include "GCController.h" #include "HTMLFormElement.h" #include "HTMLInputElement.h" #include "HTMLNames.h" #include "HTMLObjectElement.h" #include "HTMLParserIdioms.h" +#include "HTTPHeaderNames.h" #include "HTTPParsers.h" #include "HistoryController.h" #include "HistoryItem.h" #include "IconController.h" +#include "IgnoreOpensDuringUnloadCountIncrementer.h" #include "InspectorController.h" #include "InspectorInstrumentation.h" +#include "LinkLoader.h" +#include "LoadTiming.h" #include "LoaderStrategy.h" #include "Logging.h" -#include "MIMETypeRegistry.h" #include "MainFrame.h" #include "MemoryCache.h" +#include "MemoryRelease.h" +#include "NoEventDispatchAssertion.h" #include "Page.h" -#include "PageActivityAssertionToken.h" #include "PageCache.h" #include "PageTransitionEvent.h" +#include "PerformanceLogging.h" #include "PlatformStrategies.h" #include "PluginData.h" -#include "PluginDatabase.h" #include "PluginDocument.h" #include "PolicyChecker.h" #include "ProgressTracker.h" +#include "PublicSuffix.h" #include "ResourceHandle.h" +#include "ResourceLoadInfo.h" +#include "ResourceLoadObserver.h" #include "ResourceRequest.h" -#include "SchemeRegistry.h" -#include "ScriptCallStack.h" +#include "SVGDocument.h" +#include "SVGLocatable.h" +#include "SVGNames.h" +#include "SVGViewElement.h" +#include "SVGViewSpec.h" #include "ScriptController.h" #include "ScriptSourceCode.h" #include "ScrollAnimator.h" @@ -106,6 +116,7 @@ #include "Settings.h" #include "SubframeLoader.h" #include "TextResourceDecoder.h" +#include "UserContentController.h" #include "WindowFeatures.h" #include "XMLDocumentParser.h" #include <wtf/CurrentTime.h> @@ -114,61 +125,44 @@ #include <wtf/text/CString.h> #include <wtf/text/WTFString.h> -#if ENABLE(SHARED_WORKERS) -#include "SharedWorkerRepository.h" -#endif - -#if ENABLE(SVG) -#include "SVGDocument.h" -#include "SVGLocatable.h" -#include "SVGNames.h" -#include "SVGPreserveAspectRatio.h" -#include "SVGSVGElement.h" -#include "SVGViewElement.h" -#include "SVGViewSpec.h" -#endif - #if ENABLE(WEB_ARCHIVE) || ENABLE(MHTML) #include "Archive.h" #endif +#if ENABLE(DATA_DETECTION) +#include "DataDetection.h" +#endif + #if PLATFORM(IOS) #include "DocumentType.h" -#include "MemoryPressureHandler.h" #include "ResourceLoader.h" -#include "RuntimeApplicationChecksIOS.h" -#include "SystemMemory.h" +#include "RuntimeApplicationChecks.h" #include "WKContentObservation.h" #endif +#define RELEASE_LOG_IF_ALLOWED(fmt, ...) RELEASE_LOG_IF(isAlwaysOnLoggingAllowed(), Network, "%p - FrameLoader::" fmt, this, ##__VA_ARGS__) + namespace WebCore { using namespace HTMLNames; - -#if ENABLE(SVG) using namespace SVGNames; -#endif static const char defaultAcceptHeader[] = "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"; -#if PLATFORM(IOS) -const int memoryLevelThresholdToPrunePageCache = 20; -#endif - bool isBackForwardLoadType(FrameLoadType type) { switch (type) { - case FrameLoadTypeStandard: - case FrameLoadTypeReload: - case FrameLoadTypeReloadFromOrigin: - case FrameLoadTypeSame: - case FrameLoadTypeRedirectWithLockedBackForwardList: - case FrameLoadTypeReplace: - return false; - case FrameLoadTypeBack: - case FrameLoadTypeForward: - case FrameLoadTypeIndexedBackForward: - return true; + case FrameLoadType::Standard: + case FrameLoadType::Reload: + case FrameLoadType::ReloadFromOrigin: + case FrameLoadType::Same: + case FrameLoadType::RedirectWithLockedBackForwardList: + case FrameLoadType::Replace: + return false; + case FrameLoadType::Back: + case FrameLoadType::Forward: + case FrameLoadType::IndexedBackForward: + return true; } ASSERT_NOT_REACHED(); return false; @@ -180,12 +174,31 @@ bool isBackForwardLoadType(FrameLoadType type) // non-member lets us exclude it from the header file, thus keeping FrameLoader.h's // API simpler. // -static bool isDocumentSandboxed(Frame* frame, SandboxFlags mask) +static bool isDocumentSandboxed(Frame& frame, SandboxFlags mask) { - return frame->document() && frame->document()->isSandboxed(mask); + return frame.document() && frame.document()->isSandboxed(mask); } +struct ForbidPromptsScope { + ForbidPromptsScope(Page* page) : m_page(page) + { + if (!m_page) + return; + m_page->forbidPrompts(); + } + + ~ForbidPromptsScope() + { + if (!m_page) + return; + m_page->allowPrompts(); + } + + Page* m_page; +}; + class FrameLoader::FrameProgressTracker { + WTF_MAKE_FAST_ALLOCATED; public: explicit FrameProgressTracker(Frame& frame) : m_frame(frame) @@ -230,23 +243,20 @@ FrameLoader::FrameLoader(Frame& frame, FrameLoaderClient& client) , m_icon(std::make_unique<IconController>(frame)) , m_mixedContentChecker(frame) , m_state(FrameStateProvisional) - , m_loadType(FrameLoadTypeStandard) - , m_delegateIsHandlingProvisionalLoadError(false) + , m_loadType(FrameLoadType::Standard) , m_quickRedirectComing(false) , m_sentRedirectNotification(false) , m_inStopAllLoaders(false) , m_isExecutingJavaScriptFormAction(false) , m_didCallImplicitClose(true) , m_wasUnloadEventEmitted(false) - , m_pageDismissalEventBeingDispatched(NoDismissal) , m_isComplete(false) , m_needsClear(false) - , m_checkTimer(this, &FrameLoader::checkTimerFired) + , m_checkTimer(*this, &FrameLoader::checkTimerFired) , m_shouldCallCheckCompleted(false) , m_shouldCallCheckLoadComplete(false) , m_opener(nullptr) , m_loadingFromCachedPage(false) - , m_suppressOpenerInNewFrame(false) , m_currentNavigationHasShownBeforeUnloadConfirmPanel(false) , m_loadsSynchronously(false) , m_forcedSandboxFlags(SandboxNone) @@ -257,9 +267,8 @@ FrameLoader::~FrameLoader() { setOpener(nullptr); - HashSet<Frame*>::iterator end = m_openedFrames.end(); - for (HashSet<Frame*>::iterator it = m_openedFrames.begin(); it != end; ++it) - (*it)->loader().m_opener = 0; + for (auto& frame : m_openedFrames) + frame->loader().m_opener = nullptr; m_client.frameLoaderDestroyed(); @@ -270,7 +279,7 @@ FrameLoader::~FrameLoader() void FrameLoader::init() { // This somewhat odd set of steps gives the frame an initial empty document. - setPolicyDocumentLoader(m_client.createDocumentLoader(ResourceRequest(URL(ParsedURLString, emptyString())), SubstituteData()).get()); + setPolicyDocumentLoader(m_client.createDocumentLoader(ResourceRequest(URL(ParsedURLString, emptyString())), SubstituteData()).ptr()); setProvisionalDocumentLoader(m_policyDocumentLoader.get()); m_provisionalDocumentLoader->startLoadingMainResource(); @@ -289,8 +298,8 @@ void FrameLoader::initForSynthesizedDocument(const URL&) // FrameLoader::checkCompleted() will overwrite the URL of the document to be activeDocumentLoader()->documentURL(). RefPtr<DocumentLoader> loader = m_client.createDocumentLoader(ResourceRequest(URL(ParsedURLString, emptyString())), SubstituteData()); - loader->setFrame(&m_frame); - loader->setResponse(ResourceResponse(URL(), ASCIILiteral("text/html"), 0, String(), String())); + loader->attachToFrame(m_frame); + loader->setResponse(ResourceResponse(URL(), ASCIILiteral("text/html"), 0, String())); loader->setCommitted(true); setDocumentLoader(loader.get()); @@ -305,6 +314,7 @@ void FrameLoader::initForSynthesizedDocument(const URL&) m_needsClear = true; m_networkingContext = m_client.createNetworkingContext(); + m_progressTracker = std::make_unique<FrameProgressTracker>(m_frame); } #endif @@ -324,60 +334,50 @@ void FrameLoader::setDefersLoading(bool defers) } } -void FrameLoader::changeLocation(SecurityOrigin* securityOrigin, const URL& url, const String& referrer, bool lockHistory, bool lockBackForwardList, bool refresh) +void FrameLoader::changeLocation(const FrameLoadRequest& request) { - urlSelected(FrameLoadRequest(securityOrigin, ResourceRequest(url, referrer, refresh ? ReloadIgnoringCacheData : UseProtocolCachePolicy), "_self"), - 0, lockHistory, lockBackForwardList, MaybeSendReferrer, ReplaceDocumentIfJavaScriptURL); + urlSelected(request, nullptr); } -void FrameLoader::urlSelected(const URL& url, const String& passedTarget, PassRefPtr<Event> triggeringEvent, bool lockHistory, bool lockBackForwardList, ShouldSendReferrer shouldSendReferrer) +void FrameLoader::urlSelected(const URL& url, const String& passedTarget, Event* triggeringEvent, LockHistory lockHistory, LockBackForwardList lockBackForwardList, ShouldSendReferrer shouldSendReferrer, ShouldOpenExternalURLsPolicy shouldOpenExternalURLsPolicy, std::optional<NewFrameOpenerPolicy> openerPolicy, const AtomicString& downloadAttribute) { - urlSelected(FrameLoadRequest(m_frame.document()->securityOrigin(), ResourceRequest(url), passedTarget), - triggeringEvent, lockHistory, lockBackForwardList, shouldSendReferrer, DoNotReplaceDocumentIfJavaScriptURL); + NewFrameOpenerPolicy newFrameOpenerPolicy = openerPolicy.value_or(shouldSendReferrer == NeverSendReferrer ? NewFrameOpenerPolicy::Suppress : NewFrameOpenerPolicy::Allow); + urlSelected(FrameLoadRequest(m_frame.document()->securityOrigin(), ResourceRequest(url), passedTarget, lockHistory, lockBackForwardList, shouldSendReferrer, AllowNavigationToInvalidURL::Yes, newFrameOpenerPolicy, DoNotReplaceDocumentIfJavaScriptURL, shouldOpenExternalURLsPolicy, downloadAttribute), triggeringEvent); } -// The shouldReplaceDocumentIfJavaScriptURL parameter will go away when the FIXME to eliminate the -// corresponding parameter from ScriptController::executeIfJavaScriptURL() is addressed. -void FrameLoader::urlSelected(const FrameLoadRequest& passedRequest, PassRefPtr<Event> triggeringEvent, bool lockHistory, bool lockBackForwardList, ShouldSendReferrer shouldSendReferrer, ShouldReplaceDocumentIfJavaScriptURL shouldReplaceDocumentIfJavaScriptURL) +void FrameLoader::urlSelected(const FrameLoadRequest& passedRequest, Event* triggeringEvent) { - ASSERT(!m_suppressOpenerInNewFrame); - Ref<Frame> protect(m_frame); FrameLoadRequest frameRequest(passedRequest); - if (m_frame.script().executeIfJavaScriptURL(frameRequest.resourceRequest().url(), shouldReplaceDocumentIfJavaScriptURL)) + if (m_frame.script().executeIfJavaScriptURL(frameRequest.resourceRequest().url(), frameRequest.shouldReplaceDocumentIfJavaScriptURL())) return; if (frameRequest.frameName().isEmpty()) frameRequest.setFrameName(m_frame.document()->baseTarget()); - if (shouldSendReferrer == NeverSendReferrer) - m_suppressOpenerInNewFrame = true; addHTTPOriginIfNeeded(frameRequest.resourceRequest(), outgoingOrigin()); + m_frame.document()->contentSecurityPolicy()->upgradeInsecureRequestIfNeeded(frameRequest.resourceRequest(), ContentSecurityPolicy::InsecureRequestType::Navigation); - loadFrameRequest(frameRequest, lockHistory, lockBackForwardList, triggeringEvent, 0, shouldSendReferrer); - - m_suppressOpenerInNewFrame = false; + loadFrameRequest(frameRequest, triggeringEvent, nullptr); } -void FrameLoader::submitForm(PassRefPtr<FormSubmission> submission) +void FrameLoader::submitForm(Ref<FormSubmission>&& submission) { - ASSERT(submission->method() == FormSubmission::PostMethod || submission->method() == FormSubmission::GetMethod); + ASSERT(submission->method() == FormSubmission::Method::Post || submission->method() == FormSubmission::Method::Get); // FIXME: Find a good spot for these. - ASSERT(submission->data()); - ASSERT(submission->state()); - ASSERT(!submission->state()->sourceDocument()->frame() || submission->state()->sourceDocument()->frame() == &m_frame); - + ASSERT(!submission->state().sourceDocument().frame() || submission->state().sourceDocument().frame() == &m_frame); + if (!m_frame.page()) return; - + if (submission->action().isEmpty()) return; - if (isDocumentSandboxed(&m_frame, SandboxForms)) { + if (isDocumentSandboxed(m_frame, SandboxForms)) { // FIXME: This message should be moved off the console once a solution to https://bugs.webkit.org/show_bug.cgi?id=103274 exists. - m_frame.document()->addConsoleMessage(SecurityMessageSource, ErrorMessageLevel, "Blocked form submission to '" + submission->action().stringCenterEllipsizedToLength() + "' because the form's frame is sandboxed and the 'allow-forms' permission is not set."); + m_frame.document()->addConsoleMessage(MessageSource::Security, MessageLevel::Error, "Blocked form submission to '" + submission->action().stringCenterEllipsizedToLength() + "' because the form's frame is sandboxed and the 'allow-forms' permission is not set."); return; } @@ -391,12 +391,12 @@ void FrameLoader::submitForm(PassRefPtr<FormSubmission> submission) return; } - Frame* targetFrame = findFrameForNavigation(submission->target(), submission->state()->sourceDocument()); + Frame* targetFrame = findFrameForNavigation(submission->target(), &submission->state().sourceDocument()); if (!targetFrame) { - if (!DOMWindow::allowPopUp(&m_frame) && !ScriptController::processingUserGesture()) + if (!DOMWindow::allowPopUp(m_frame) && !ScriptController::processingUserGesture()) return; - // FIXME: targetFrame can be 0 for two distinct reasons: + // FIXME: targetFrame can be null for two distinct reasons: // 1. The frame was not found by name, so we should try opening a new window. // 2. The frame was found, but navigating it was not allowed, e.g. by HTML5 sandbox or by origin checks. // Continuing form submission makes no sense in the latter case. @@ -413,7 +413,7 @@ void FrameLoader::submitForm(PassRefPtr<FormSubmission> submission) // We do not want to submit more than one form from the same page, nor do we want to submit a single // form more than once. This flag prevents these from happening; not sure how other browsers prevent this. - // The flag is reset in each time we start handle a new mouse or key down event, and + // The flag is reset in each time we start dispatching a new mouse or key down event, and // also in setView since this part may get reused for a page from the back/forward cache. // The form multi-submit logic here is only needed when we are submitting a form that affects this frame. @@ -427,11 +427,11 @@ void FrameLoader::submitForm(PassRefPtr<FormSubmission> submission) m_submittedFormURL = submission->requestURL(); } - submission->data()->generateFiles(m_frame.document()); + submission->data().generateFiles(m_frame.document()); submission->setReferrer(outgoingReferrer()); submission->setOrigin(outgoingOrigin()); - targetFrame->navigationScheduler().scheduleFormSubmission(submission); + targetFrame->navigationScheduler().scheduleFormSubmission(WTFMove(submission)); } void FrameLoader::stopLoading(UnloadEventPolicy unloadEventPolicy) @@ -439,55 +439,8 @@ void FrameLoader::stopLoading(UnloadEventPolicy unloadEventPolicy) if (m_frame.document() && m_frame.document()->parser()) m_frame.document()->parser()->stopParsing(); - if (unloadEventPolicy != UnloadEventPolicyNone) { - if (m_frame.document()) { - if (m_didCallImplicitClose && !m_wasUnloadEventEmitted) { - Element* currentFocusedElement = m_frame.document()->focusedElement(); - if (currentFocusedElement && currentFocusedElement->toInputElement()) - currentFocusedElement->toInputElement()->endEditing(); - if (m_pageDismissalEventBeingDispatched == NoDismissal) { - if (unloadEventPolicy == UnloadEventPolicyUnloadAndPageHide) { - m_pageDismissalEventBeingDispatched = PageHideDismissal; - m_frame.document()->domWindow()->dispatchEvent(PageTransitionEvent::create(eventNames().pagehideEvent, m_frame.document()->inPageCache()), m_frame.document()); - } - - // FIXME: update Page Visibility state here. - // https://bugs.webkit.org/show_bug.cgi?id=116770 - - if (!m_frame.document()->inPageCache()) { - RefPtr<Event> unloadEvent(Event::create(eventNames().unloadEvent, false, false)); - // The DocumentLoader (and thus its DocumentLoadTiming) might get destroyed - // while dispatching the event, so protect it to prevent writing the end - // time into freed memory. - RefPtr<DocumentLoader> documentLoader = m_provisionalDocumentLoader; - m_pageDismissalEventBeingDispatched = UnloadDismissal; - if (documentLoader && !documentLoader->timing()->unloadEventStart() && !documentLoader->timing()->unloadEventEnd()) { - DocumentLoadTiming* timing = documentLoader->timing(); - ASSERT(timing->navigationStart()); - timing->markUnloadEventStart(); - m_frame.document()->domWindow()->dispatchEvent(unloadEvent, m_frame.document()); - timing->markUnloadEventEnd(); - } else - m_frame.document()->domWindow()->dispatchEvent(unloadEvent, m_frame.document()); - } - } - m_pageDismissalEventBeingDispatched = NoDismissal; - if (m_frame.document()) - m_frame.document()->updateStyleIfNeeded(); - m_wasUnloadEventEmitted = true; - } - } - - // Dispatching the unload event could have made m_frame.document() null. - if (m_frame.document() && !m_frame.document()->inPageCache()) { - // Don't remove event listeners from a transitional empty document (see bug 28716 for more information). - bool keepEventListeners = m_stateMachine.isDisplayingInitialEmptyDocument() && m_provisionalDocumentLoader - && m_frame.document()->isSecureTransitionTo(m_provisionalDocumentLoader->url()); - - if (!keepEventListeners) - m_frame.document()->removeAllEventListeners(); - } - } + if (unloadEventPolicy != UnloadEventPolicyNone) + dispatchUnloadEvents(unloadEventPolicy); m_isComplete = true; // to avoid calling completed() in finishedParsing() m_didCallImplicitClose = true; // don't want that one either @@ -497,15 +450,13 @@ void FrameLoader::stopLoading(UnloadEventPolicy unloadEventPolicy) m_frame.document()->setParsing(false); } - if (Document* doc = m_frame.document()) { + if (auto* document = m_frame.document()) { // FIXME: HTML5 doesn't tell us to set the state to complete when aborting, but we do anyway to match legacy behavior. // http://www.w3.org/Bugs/Public/show_bug.cgi?id=10537 - doc->setReadyState(Document::Complete); + document->setReadyState(Document::Complete); -#if ENABLE(SQL_DATABASE) // FIXME: Should the DatabaseManager watch for something like ActiveDOMObject::stop() rather than being special-cased here? - DatabaseManager::manager().stopDatabases(doc, 0); -#endif + DatabaseManager::singleton().stopDatabases(*document, nullptr); } // FIXME: This will cancel redirection timer, which really needs to be restarted when restoring the frame from b/f cache. @@ -533,18 +484,28 @@ void FrameLoader::willTransitionToCommitted() if (m_frame.editor().hasComposition()) { // The text was already present in DOM, so it's better to confirm than to cancel the composition. m_frame.editor().confirmComposition(); - if (EditorClient* editorClient = m_frame.editor().client()) + if (EditorClient* editorClient = m_frame.editor().client()) { editorClient->respondToChangedSelection(&m_frame); + editorClient->discardedComposition(&m_frame); + } } } bool FrameLoader::closeURL() { history().saveDocumentState(); - - // Should only send the pagehide event here if the current document exists and has not been placed in the page cache. + Document* currentDocument = m_frame.document(); - stopLoading(currentDocument && !currentDocument->inPageCache() ? UnloadEventPolicyUnloadAndPageHide : UnloadEventPolicyUnloadOnly); + UnloadEventPolicy unloadEventPolicy; + if (m_frame.page() && m_frame.page()->chrome().client().isSVGImageChromeClient()) { + // If this is the SVGDocument of an SVGImage, no need to dispatch events or recalcStyle. + unloadEventPolicy = UnloadEventPolicyNone; + } else { + // Should only send the pagehide event here if the current document exists and has not been placed in the page cache. + unloadEventPolicy = currentDocument && currentDocument->pageCacheState() == Document::NotInPageCache ? UnloadEventPolicyUnloadAndPageHide : UnloadEventPolicyUnloadOnly; + } + + stopLoading(unloadEventPolicy); m_frame.editor().clearUndoRedoOperations(); return true; @@ -606,6 +567,17 @@ void FrameLoader::cancelAndClear() m_frame.script().updatePlatformScriptObjects(); } +static inline bool shouldClearWindowName(const Frame& frame, const Document& newDocument) +{ + if (!frame.isMainFrame()) + return false; + + if (frame.loader().opener()) + return false; + + return !newDocument.securityOrigin().isSameOriginAs(frame.document()->securityOrigin()); +} + void FrameLoader::clear(Document* newDocument, bool clearWindowProperties, bool clearScriptObjects, bool clearFrameView) { m_frame.editor().clear(); @@ -614,33 +586,43 @@ void FrameLoader::clear(Document* newDocument, bool clearWindowProperties, bool return; m_needsClear = false; - if (!m_frame.document()->inPageCache()) { + if (m_frame.document()->pageCacheState() != Document::InPageCache) { m_frame.document()->cancelParsing(); m_frame.document()->stopActiveDOMObjects(); - if (m_frame.document()->hasLivingRenderTree()) { - m_frame.document()->prepareForDestruction(); - m_frame.document()->removeFocusedNodeOfSubtree(m_frame.document()); - } + bool hadLivingRenderTree = m_frame.document()->hasLivingRenderTree(); + m_frame.document()->prepareForDestruction(); + if (hadLivingRenderTree) + m_frame.document()->removeFocusedNodeOfSubtree(*m_frame.document()); } // Do this after detaching the document so that the unload event works. if (clearWindowProperties) { - InspectorInstrumentation::frameWindowDiscarded(&m_frame, m_frame.document()->domWindow()); - m_frame.document()->domWindow()->resetUnlessSuspendedForPageCache(); - m_frame.script().clearWindowShell(newDocument->domWindow(), m_frame.document()->inPageCache()); + InspectorInstrumentation::frameWindowDiscarded(m_frame, m_frame.document()->domWindow()); + m_frame.document()->domWindow()->resetUnlessSuspendedForDocumentSuspension(); + m_frame.script().clearWindowShellsNotMatchingDOMWindow(newDocument->domWindow(), m_frame.document()->pageCacheState() == Document::AboutToEnterPageCache); + + if (shouldClearWindowName(m_frame, *newDocument)) + m_frame.tree().setName(nullAtom); } m_frame.selection().prepareForDestruction(); - m_frame.eventHandler().clear(); + + // We may call this code during object destruction, so need to make sure eventHandler is present. + if (auto eventHandler = m_frame.eventHandlerPtr()) + eventHandler->clear(); + if (clearFrameView && m_frame.view()) m_frame.view()->clear(); // Do not drop the document before the ScriptController and view are cleared // as some destructors might still try to access the document. - m_frame.setDocument(0); + m_frame.setDocument(nullptr); subframeLoader().clear(); + if (clearWindowProperties) + m_frame.script().setDOMWindowForWindowShell(newDocument->domWindow()); + if (clearScriptObjects) m_frame.script().clearScriptObjects(); @@ -658,36 +640,35 @@ void FrameLoader::clear(Document* newDocument, bool clearWindowProperties, bool void FrameLoader::receivedFirstData() { - dispatchDidCommitLoad(); + dispatchDidCommitLoad(std::nullopt); dispatchDidClearWindowObjectsInAllWorlds(); dispatchGlobalObjectAvailableInAllWorlds(); if (m_documentLoader) { - StringWithDirection ptitle = m_documentLoader->title(); - // If we have a title let the WebView know about it. - if (!ptitle.isNull()) - m_client.dispatchDidReceiveTitle(ptitle); + auto& title = m_documentLoader->title(); + if (!title.string.isNull()) + m_client.dispatchDidReceiveTitle(title); } if (!m_documentLoader) return; - if (m_frame.document()->isViewSource()) - return; + + ASSERT(m_frame.document()); + auto& document = *m_frame.document(); + + ASSERT(m_frame.document()); + LinkLoader::loadLinksFromHeader(m_documentLoader->response().httpHeaderField(HTTPHeaderName::Link), m_frame.document()->url(), *m_frame.document()); double delay; - String url; - if (!parseHTTPRefresh(m_documentLoader->response().httpHeaderField("Refresh"), false, delay, url)) + String urlString; + if (!parseHTTPRefresh(m_documentLoader->response().httpHeaderField(HTTPHeaderName::Refresh), delay, urlString)) return; - if (url.isEmpty()) - url = m_frame.document()->url().string(); - else - url = m_frame.document()->completeURL(url).string(); - - if (!protocolIsJavaScript(url)) - m_frame.navigationScheduler().scheduleRedirect(delay, url); + auto completedURL = urlString.isEmpty() ? document.url() : document.completeURL(urlString); + if (!protocolIsJavaScript(completedURL)) + m_frame.navigationScheduler().scheduleRedirect(document, delay, completedURL); else { - String message = "Refused to refresh " + m_frame.document()->url().stringCenterEllipsizedToLength() + " to a javascript: URL"; - m_frame.document()->addConsoleMessage(SecurityMessageSource, ErrorMessageLevel, message); + auto message = "Refused to refresh " + document.url().stringCenterEllipsizedToLength() + " to a javascript: URL"; + m_frame.document()->addConsoleMessage(MessageSource::Security, MessageLevel::Error, message); } } @@ -704,8 +685,8 @@ void FrameLoader::didBeginDocument(bool dispatch) m_frame.document()->setReadyState(Document::Loading); if (m_pendingStateObject) { - m_frame.document()->statePopped(m_pendingStateObject.get()); - m_pendingStateObject.clear(); + m_frame.document()->statePopped(*m_pendingStateObject); + m_pendingStateObject = nullptr; } if (dispatch) @@ -715,31 +696,17 @@ void FrameLoader::didBeginDocument(bool dispatch) m_frame.document()->initContentSecurityPolicy(); const Settings& settings = m_frame.settings(); - m_frame.document()->cachedResourceLoader()->setImagesEnabled(settings.areImagesEnabled()); - m_frame.document()->cachedResourceLoader()->setAutoLoadImages(settings.loadsImagesAutomatically()); + m_frame.document()->cachedResourceLoader().setImagesEnabled(settings.areImagesEnabled()); + m_frame.document()->cachedResourceLoader().setAutoLoadImages(settings.loadsImagesAutomatically()); if (m_documentLoader) { - String dnsPrefetchControl = m_documentLoader->response().httpHeaderField("X-DNS-Prefetch-Control"); + String dnsPrefetchControl = m_documentLoader->response().httpHeaderField(HTTPHeaderName::XDNSPrefetchControl); if (!dnsPrefetchControl.isEmpty()) m_frame.document()->parseDNSPrefetchControlHeader(dnsPrefetchControl); - String policyValue = m_documentLoader->response().httpHeaderField("Content-Security-Policy"); - if (!policyValue.isEmpty()) - m_frame.document()->contentSecurityPolicy()->didReceiveHeader(policyValue, ContentSecurityPolicy::Enforce); - - policyValue = m_documentLoader->response().httpHeaderField("Content-Security-Policy-Report-Only"); - if (!policyValue.isEmpty()) - m_frame.document()->contentSecurityPolicy()->didReceiveHeader(policyValue, ContentSecurityPolicy::Report); - - policyValue = m_documentLoader->response().httpHeaderField("X-WebKit-CSP"); - if (!policyValue.isEmpty()) - m_frame.document()->contentSecurityPolicy()->didReceiveHeader(policyValue, ContentSecurityPolicy::PrefixedEnforce); + m_frame.document()->contentSecurityPolicy()->didReceiveHeaders(ContentSecurityPolicyResponseHeaders(m_documentLoader->response()), ContentSecurityPolicy::ReportParsingErrors::No); - policyValue = m_documentLoader->response().httpHeaderField("X-WebKit-CSP-Report-Only"); - if (!policyValue.isEmpty()) - m_frame.document()->contentSecurityPolicy()->didReceiveHeader(policyValue, ContentSecurityPolicy::PrefixedReport); - - String headerContentLanguage = m_documentLoader->response().httpHeaderField("Content-Language"); + String headerContentLanguage = m_documentLoader->response().httpHeaderField(HTTPHeaderName::ContentLanguage); if (!headerContentLanguage.isEmpty()) { size_t commaIndex = headerContentLanguage.find(','); headerContentLanguage.truncate(commaIndex); // notFound == -1 == don't truncate @@ -766,6 +733,8 @@ void FrameLoader::finishedParsing() m_client.dispatchDidFinishDocumentLoad(); + scrollToFragmentWithParentBoundary(m_frame.document()->url()); + checkCompleted(); if (!m_frame.view()) @@ -774,7 +743,6 @@ void FrameLoader::finishedParsing() // Check if the scrollbars are really needed for the content. // If not, remove them, relayout, and repaint. m_frame.view()->restoreScrollbar(); - scrollToFragmentWithParentBoundary(m_frame.document()->url()); } void FrameLoader::loadDone() @@ -802,32 +770,47 @@ bool FrameLoader::allAncestorsAreComplete() const void FrameLoader::checkCompleted() { - Ref<Frame> protect(m_frame); m_shouldCallCheckCompleted = false; // Have we completed before? if (m_isComplete) return; + // FIXME: It would be better if resource loads were kicked off after render tree update (or didn't complete synchronously). + // https://bugs.webkit.org/show_bug.cgi?id=171729 + if (m_frame.document()->inRenderTreeUpdate()) { + scheduleCheckCompleted(); + return; + } + // Are we still parsing? if (m_frame.document()->parsing()) return; // Still waiting for images/scripts? - if (m_frame.document()->cachedResourceLoader()->requestCount()) + if (m_frame.document()->cachedResourceLoader().requestCount()) return; // Still waiting for elements that don't go through a FrameLoader? if (m_frame.document()->isDelayingLoadEvent()) return; + auto* scriptableParser = m_frame.document()->scriptableDocumentParser(); + if (scriptableParser && scriptableParser->hasScriptsWaitingForStylesheets()) + return; + // Any frame that hasn't completed yet? if (!allChildrenAreComplete()) return; + // Important not to protect earlier in this function, because earlier parts + // of this function can be called in the frame's destructor, and it's not legal + // to ref an object while it's being destroyed. + Ref<Frame> protect(m_frame); + // OK, completed. m_isComplete = true; - m_requestedHistoryItem = 0; + m_requestedHistoryItem = nullptr; m_frame.document()->setReadyState(Document::Complete); #if PLATFORM(IOS) @@ -851,7 +834,7 @@ void FrameLoader::checkCompleted() checkLoadComplete(); } -void FrameLoader::checkTimerFired(Timer<FrameLoader>&) +void FrameLoader::checkTimerFired() { Ref<Frame> protect(m_frame); @@ -904,110 +887,78 @@ void FrameLoader::loadURLIntoChildFrame(const URL& url, const String& referer, F ASSERT(childFrame); #if ENABLE(WEB_ARCHIVE) || ENABLE(MHTML) - RefPtr<Archive> subframeArchive = activeDocumentLoader()->popArchiveForSubframe(childFrame->tree().uniqueName(), url); - if (subframeArchive) { - childFrame->loader().loadArchive(subframeArchive.release()); - return; + if (auto activeLoader = activeDocumentLoader()) { + if (auto subframeArchive = activeLoader->popArchiveForSubframe(childFrame->tree().uniqueName(), url)) { + childFrame->loader().loadArchive(RefPtr<Archive> { subframeArchive }.releaseNonNull()); + return; + } } -#endif // ENABLE(WEB_ARCHIVE) +#endif - HistoryItem* parentItem = history().currentItem(); // If we're moving in the back/forward list, we might want to replace the content // of this child frame with whatever was there at that point. - if (parentItem && parentItem->children().size() && isBackForwardLoadType(loadType()) - && !m_frame.document()->loadEventFinished()) { - HistoryItem* childItem = parentItem->childItemWithTarget(childFrame->tree().uniqueName()); - if (childItem) { + auto* parentItem = history().currentItem(); + if (parentItem && parentItem->children().size() && isBackForwardLoadType(loadType()) && !m_frame.document()->loadEventFinished()) { + if (auto* childItem = parentItem->childItemWithTarget(childFrame->tree().uniqueName())) { childFrame->loader().m_requestedHistoryItem = childItem; - childFrame->loader().loadDifferentDocumentItem(childItem, loadType(), MayAttemptCacheOnlyLoadForFormSubmissionItem); + childFrame->loader().loadDifferentDocumentItem(*childItem, loadType(), MayAttemptCacheOnlyLoadForFormSubmissionItem); return; } } - childFrame->loader().loadURL(url, referer, "_self", false, FrameLoadTypeRedirectWithLockedBackForwardList, 0, 0); + FrameLoadRequest frameLoadRequest(m_frame.document()->securityOrigin(), ResourceRequest(url), "_self", LockHistory::No, LockBackForwardList::Yes, ShouldSendReferrer::MaybeSendReferrer, AllowNavigationToInvalidURL::Yes, NewFrameOpenerPolicy::Suppress, ReplaceDocumentIfJavaScriptURL, ShouldOpenExternalURLsPolicy::ShouldNotAllow); + childFrame->loader().loadURL(frameLoadRequest, referer, FrameLoadType::RedirectWithLockedBackForwardList, 0, 0); } #if ENABLE(WEB_ARCHIVE) || ENABLE(MHTML) -void FrameLoader::loadArchive(PassRefPtr<Archive> archive) + +void FrameLoader::loadArchive(Ref<Archive>&& archive) { ArchiveResource* mainResource = archive->mainResource(); ASSERT(mainResource); if (!mainResource) return; - - SubstituteData substituteData(mainResource->data(), mainResource->mimeType(), mainResource->textEncoding(), URL()); + + ResourceResponse response(URL(), mainResource->mimeType(), mainResource->data().size(), mainResource->textEncoding()); + SubstituteData substituteData(&mainResource->data(), URL(), response, SubstituteData::SessionHistoryVisibility::Hidden); ResourceRequest request(mainResource->url()); -#if PLATFORM(MAC) - request.applyWebArchiveHackForMail(); -#endif - RefPtr<DocumentLoader> documentLoader = m_client.createDocumentLoader(request, substituteData); - documentLoader->setArchive(archive.get()); - load(documentLoader.get()); + auto documentLoader = m_client.createDocumentLoader(request, substituteData); + documentLoader->setArchive(WTFMove(archive)); + load(documentLoader.ptr()); } -#endif // ENABLE(WEB_ARCHIVE) || ENABLE(MHTML) - -ObjectContentType FrameLoader::defaultObjectContentType(const URL& url, const String& mimeTypeIn, bool shouldPreferPlugInsForImages) -{ - String mimeType = mimeTypeIn; - - if (mimeType.isEmpty()) - mimeType = mimeTypeFromURL(url); - -#if !PLATFORM(MAC) && !PLATFORM(EFL) // Mac has no PluginDatabase, nor does EFL - if (mimeType.isEmpty()) { - String decodedPath = decodeURLEscapeSequences(url.path()); - mimeType = PluginDatabase::installedPlugins()->MIMETypeForExtension(decodedPath.substring(decodedPath.reverseFind('.') + 1)); - } -#endif - - if (mimeType.isEmpty()) - return ObjectContentFrame; // Go ahead and hope that we can display the content. - -#if !PLATFORM(MAC) && !PLATFORM(EFL) // Mac has no PluginDatabase, nor does EFL - bool plugInSupportsMIMEType = PluginDatabase::installedPlugins()->isMIMETypeRegistered(mimeType); -#else - bool plugInSupportsMIMEType = false; -#endif - if (MIMETypeRegistry::isSupportedImageMIMEType(mimeType)) - return shouldPreferPlugInsForImages && plugInSupportsMIMEType ? WebCore::ObjectContentNetscapePlugin : WebCore::ObjectContentImage; - - if (plugInSupportsMIMEType) - return WebCore::ObjectContentNetscapePlugin; - - if (MIMETypeRegistry::isSupportedNonImageMIMEType(mimeType)) - return WebCore::ObjectContentFrame; - - return WebCore::ObjectContentNone; -} +#endif // ENABLE(WEB_ARCHIVE) || ENABLE(MHTML) String FrameLoader::outgoingReferrer() const { // See http://www.whatwg.org/specs/web-apps/current-work/#fetching-resources // for why we walk the parent chain for srcdoc documents. Frame* frame = &m_frame; - while (frame->document()->isSrcdocDocument()) { + while (frame && frame->document()->isSrcdocDocument()) { frame = frame->tree().parent(); // Srcdoc documents cannot be top-level documents, by definition, // because they need to be contained in iframes with the srcdoc. ASSERT(frame); } + if (!frame) + return emptyString(); return frame->loader().m_outgoingReferrer; } String FrameLoader::outgoingOrigin() const { - return m_frame.document()->securityOrigin()->toString(); + return m_frame.document()->securityOrigin().toString(); } -bool FrameLoader::checkIfFormActionAllowedByCSP(const URL& url) const +bool FrameLoader::checkIfFormActionAllowedByCSP(const URL& url, bool didReceiveRedirectResponse) const { if (m_submittedFormURL.isEmpty()) return true; - return m_frame.document()->contentSecurityPolicy()->allowFormAction(url); + auto redirectResponseReceived = didReceiveRedirectResponse ? ContentSecurityPolicy::RedirectResponseReceived::Yes : ContentSecurityPolicy::RedirectResponseReceived::No; + return m_frame.document()->contentSecurityPolicy()->allowFormAction(url, redirectResponseReceived); } Frame* FrameLoader::opener() @@ -1034,9 +985,9 @@ void FrameLoader::setOpener(Frame* opener) void FrameLoader::handleFallbackContent() { HTMLFrameOwnerElement* owner = m_frame.ownerElement(); - if (!owner || !owner->hasTagName(objectTag)) + if (!is<HTMLObjectElement>(owner)) return; - toHTMLObjectElement(owner)->renderFallbackContent(); + downcast<HTMLObjectElement>(*owner).renderFallbackContent(); } void FrameLoader::provisionalLoadStarted() @@ -1045,6 +996,11 @@ void FrameLoader::provisionalLoadStarted() m_stateMachine.advanceTo(FrameLoaderStateMachine::CommittedFirstRealLoad); m_frame.navigationScheduler().cancel(true); m_client.provisionalLoadStarted(); + + if (m_frame.isMainFrame()) { + if (auto* page = m_frame.page()) + page->didStartProvisionalLoad(); + } } void FrameLoader::resetMultipleFormSubmissionProtection() @@ -1068,7 +1024,7 @@ void FrameLoader::setFirstPartyForCookies(const URL& url) // This does the same kind of work that didOpenURL does, except it relies on the fact // that a higher level already checked that the URLs match and the scrolling is the right thing to do. -void FrameLoader::loadInSameDocument(const URL& url, PassRefPtr<SerializedScriptValue> stateObject, bool isNewNavigation) +void FrameLoader::loadInSameDocument(const URL& url, SerializedScriptValue* stateObject, bool isNewNavigation) { // If we have a state object, we cannot also be a new navigation. ASSERT(!stateObject || (stateObject && !isNewNavigation)); @@ -1121,7 +1077,7 @@ void FrameLoader::loadInSameDocument(const URL& url, PassRefPtr<SerializedScript m_client.dispatchDidNavigateWithinPage(); - m_frame.document()->statePopped(stateObject ? stateObject : SerializedScriptValue::nullValue()); + m_frame.document()->statePopped(stateObject ? Ref<SerializedScriptValue> { *stateObject } : SerializedScriptValue::nullValue()); m_client.dispatchDidPopStateWithinPage(); if (hashChange) { @@ -1149,45 +1105,25 @@ void FrameLoader::completed() parent->loader().checkCompleted(); if (m_frame.view()) - m_frame.view()->maintainScrollPositionAtAnchor(0); - m_activityAssertion = nullptr; + m_frame.view()->maintainScrollPositionAtAnchor(nullptr); } void FrameLoader::started() { - if (m_frame.page()) - m_activityAssertion = m_frame.page()->createActivityToken(); for (Frame* frame = &m_frame; frame; frame = frame->tree().parent()) frame->loader().m_isComplete = false; } -void FrameLoader::prepareForHistoryNavigation() -{ - // If there is no currentItem, but we still want to engage in - // history navigation we need to manufacture one, and update - // the state machine of this frame to impersonate having - // loaded it. - RefPtr<HistoryItem> currentItem = history().currentItem(); - if (!currentItem) { - currentItem = HistoryItem::create(); - currentItem->setLastVisitWasFailure(true); - history().setCurrentItem(currentItem.get()); - m_frame.page()->backForward().setCurrentItem(currentItem.get()); - - ASSERT(stateMachine()->isDisplayingInitialEmptyDocument()); - stateMachine()->advanceTo(FrameLoaderStateMachine::DisplayingInitialEmptyDocumentPostCommit); - stateMachine()->advanceTo(FrameLoaderStateMachine::CommittedFirstRealLoad); - } -} - void FrameLoader::prepareForLoadStart() { + RELEASE_LOG_IF_ALLOWED("prepareForLoadStart: Starting frame load (frame = %p, main = %d)", &m_frame, m_frame.isMainFrame()); + m_progressTracker->progressStarted(); m_client.dispatchDidStartProvisionalLoad(); if (AXObjectCache::accessibilityEnabled()) { if (AXObjectCache* cache = m_frame.document()->existingAXObjectCache()) { - AXObjectCache::AXLoadingEvent loadingEvent = loadType() == FrameLoadTypeReload ? AXObjectCache::AXLoadingReloaded : AXObjectCache::AXLoadingStarted; + AXObjectCache::AXLoadingEvent loadingEvent = loadType() == FrameLoadType::Reload ? AXObjectCache::AXLoadingReloaded : AXObjectCache::AXLoadingStarted; cache->frameLoadingEventNotification(&m_frame, loadingEvent); } } @@ -1198,15 +1134,14 @@ void FrameLoader::setupForReplace() m_client.revertToProvisionalState(m_documentLoader.get()); setState(FrameStateProvisional); m_provisionalDocumentLoader = m_documentLoader; - m_documentLoader = 0; + m_documentLoader = nullptr; detachChildren(); } -void FrameLoader::loadFrameRequest(const FrameLoadRequest& request, bool lockHistory, bool lockBackForwardList, - PassRefPtr<Event> event, PassRefPtr<FormState> formState, ShouldSendReferrer shouldSendReferrer) -{ +void FrameLoader::loadFrameRequest(const FrameLoadRequest& request, Event* event, FormState* formState) +{ // Protect frame from getting blown away inside dispatchBeforeLoadEvent in loadWithDocumentLoader. - Ref<Frame> protect(m_frame); + auto protectFrame = makeRef(m_frame); URL url = request.resourceRequest().url(); @@ -1221,25 +1156,25 @@ void FrameLoader::loadFrameRequest(const FrameLoadRequest& request, bool lockHis argsReferrer = outgoingReferrer(); String referrer = SecurityPolicy::generateReferrerHeader(m_frame.document()->referrerPolicy(), url, argsReferrer); - if (shouldSendReferrer == NeverSendReferrer) + if (request.shouldSendReferrer() == NeverSendReferrer) referrer = String(); - + FrameLoadType loadType; if (request.resourceRequest().cachePolicy() == ReloadIgnoringCacheData) - loadType = FrameLoadTypeReload; - else if (lockBackForwardList) - loadType = FrameLoadTypeRedirectWithLockedBackForwardList; + loadType = FrameLoadType::Reload; + else if (request.lockBackForwardList() == LockBackForwardList::Yes) + loadType = FrameLoadType::RedirectWithLockedBackForwardList; else - loadType = FrameLoadTypeStandard; + loadType = FrameLoadType::Standard; if (request.resourceRequest().httpMethod() == "POST") - loadPostRequest(request.resourceRequest(), referrer, request.frameName(), lockHistory, loadType, event, formState.get()); + loadPostRequest(request, referrer, loadType, event, formState); else - loadURL(request.resourceRequest().url(), referrer, request.frameName(), lockHistory, loadType, event, formState.get()); + loadURL(request, referrer, loadType, event, formState); // FIXME: It's possible this targetFrame will not be the same frame that was targeted by the actual // load if frame names have changed. - Frame* sourceFrame = formState ? formState->sourceDocument()->frame() : &m_frame; + Frame* sourceFrame = formState ? formState->sourceDocument().frame() : &m_frame; if (!sourceFrame) sourceFrame = &m_frame; Frame* targetFrame = sourceFrame->loader().findFrameForNavigation(request.frameName()); @@ -1249,48 +1184,67 @@ void FrameLoader::loadFrameRequest(const FrameLoadRequest& request, bool lockHis } } -void FrameLoader::loadURL(const URL& newURL, const String& referrer, const String& frameName, bool lockHistory, FrameLoadType newLoadType, - PassRefPtr<Event> event, PassRefPtr<FormState> prpFormState) +static ShouldOpenExternalURLsPolicy shouldOpenExternalURLsPolicyToApply(Frame& sourceFrame, ShouldOpenExternalURLsPolicy propagatedPolicy) +{ + if (!sourceFrame.isMainFrame()) + return ShouldOpenExternalURLsPolicy::ShouldNotAllow; + if (ScriptController::processingUserGesture()) + return ShouldOpenExternalURLsPolicy::ShouldAllow; + return propagatedPolicy; +} + +bool FrameLoader::isNavigationAllowed() const +{ + return m_pageDismissalEventBeingDispatched == PageDismissalType::None && NavigationDisabler::isNavigationAllowed(); +} + +void FrameLoader::loadURL(const FrameLoadRequest& frameLoadRequest, const String& referrer, FrameLoadType newLoadType, Event* event, FormState* formState) { if (m_inStopAllLoaders) return; Ref<Frame> protect(m_frame); - RefPtr<FormState> formState = prpFormState; + String frameName = frameLoadRequest.frameName(); + AllowNavigationToInvalidURL allowNavigationToInvalidURL = frameLoadRequest.allowNavigationToInvalidURL(); + NewFrameOpenerPolicy openerPolicy = frameLoadRequest.newFrameOpenerPolicy(); + LockHistory lockHistory = frameLoadRequest.lockHistory(); bool isFormSubmission = formState; - + + const URL& newURL = frameLoadRequest.resourceRequest().url(); ResourceRequest request(newURL); if (!referrer.isEmpty()) { request.setHTTPReferrer(referrer); RefPtr<SecurityOrigin> referrerOrigin = SecurityOrigin::createFromString(referrer); addHTTPOriginIfNeeded(request, referrerOrigin->toString()); } -#if ENABLE(CACHE_PARTITIONING) if (&m_frame.tree().top() != &m_frame) - request.setCachePartition(m_frame.tree().top().document()->securityOrigin()->cachePartition()); -#endif + request.setDomainForCachePartition(m_frame.tree().top().document()->securityOrigin().domainForCachePartition()); + addExtraFieldsToRequest(request, newLoadType, true); - if (newLoadType == FrameLoadTypeReload || newLoadType == FrameLoadTypeReloadFromOrigin) + if (newLoadType == FrameLoadType::Reload || newLoadType == FrameLoadType::ReloadFromOrigin) request.setCachePolicy(ReloadIgnoringCacheData); - ASSERT(newLoadType != FrameLoadTypeSame); + ASSERT(newLoadType != FrameLoadType::Same); // The search for a target frame is done earlier in the case of form submission. Frame* targetFrame = isFormSubmission ? 0 : findFrameForNavigation(frameName); if (targetFrame && targetFrame != &m_frame) { - targetFrame->loader().loadURL(newURL, referrer, "_self", lockHistory, newLoadType, event, formState.release()); + FrameLoadRequest newFrameLoadRequest(frameLoadRequest); + newFrameLoadRequest.setFrameName("_self"); + targetFrame->loader().loadURL(newFrameLoadRequest, referrer, newLoadType, event, formState); return; } - if (m_pageDismissalEventBeingDispatched != NoDismissal) + if (!isNavigationAllowed()) return; - NavigationAction action(request, newLoadType, isFormSubmission, event); + NavigationAction action(request, newLoadType, isFormSubmission, event, frameLoadRequest.shouldOpenExternalURLsPolicy(), frameLoadRequest.downloadAttribute()); if (!targetFrame && !frameName.isEmpty()) { - policyChecker().checkNewWindowPolicy(action, request, formState.release(), frameName, [this](const ResourceRequest& request, PassRefPtr<FormState> formState, const String& frameName, const NavigationAction& action, bool shouldContinue) { - continueLoadAfterNewWindowPolicy(request, formState, frameName, action, shouldContinue); + action = action.copyWithShouldOpenExternalURLsPolicy(shouldOpenExternalURLsPolicyToApply(m_frame, frameLoadRequest.shouldOpenExternalURLsPolicy())); + policyChecker().checkNewWindowPolicy(action, request, formState, frameName, [this, allowNavigationToInvalidURL, openerPolicy] (const ResourceRequest& request, FormState* formState, const String& frameName, const NavigationAction& action, bool shouldContinue) { + continueLoadAfterNewWindowPolicy(request, formState, frameName, action, shouldContinue, allowNavigationToInvalidURL, openerPolicy); }); return; } @@ -1308,7 +1262,7 @@ void FrameLoader::loadURL(const URL& newURL, const String& referrer, const Strin oldDocumentLoader->setLastCheckedRequest(ResourceRequest()); policyChecker().stopCheck(); policyChecker().setLoadType(newLoadType); - policyChecker().checkNavigationPolicy(request, oldDocumentLoader.get(), formState.release(), [this](const ResourceRequest& request, PassRefPtr<FormState>, bool shouldContinue) { + policyChecker().checkNavigationPolicy(request, false /* didReceiveRedirectResponse */, oldDocumentLoader.get(), formState, [this] (const ResourceRequest& request, FormState*, bool shouldContinue) { continueFragmentScrollAfterNavigationPolicy(request, shouldContinue); }); return; @@ -1316,16 +1270,16 @@ void FrameLoader::loadURL(const URL& newURL, const String& referrer, const Strin // must grab this now, since this load may stop the previous load and clear this flag bool isRedirect = m_quickRedirectComing; - loadWithNavigationAction(request, action, lockHistory, newLoadType, formState.release()); + loadWithNavigationAction(request, action, lockHistory, newLoadType, formState, allowNavigationToInvalidURL); if (isRedirect) { m_quickRedirectComing = false; if (m_provisionalDocumentLoader) m_provisionalDocumentLoader->setIsClientRedirect(true); - } else if (sameURL && newLoadType != FrameLoadTypeReload && newLoadType != FrameLoadTypeReloadFromOrigin) { + } else if (sameURL && newLoadType != FrameLoadType::Reload && newLoadType != FrameLoadType::ReloadFromOrigin) { // Example of this case are sites that reload the same URL with a different cookie // driving the generated content, or a master frame with links that drive a target // frame, where the user has clicked on the same link repeatedly. - m_loadType = FrameLoadTypeSame; + m_loadType = FrameLoadType::Same; } } @@ -1333,10 +1287,12 @@ SubstituteData FrameLoader::defaultSubstituteDataForURL(const URL& url) { if (!shouldTreatURLAsSrcdocDocument(url)) return SubstituteData(); - String srcdoc = m_frame.ownerElement()->fastGetAttribute(srcdocAttr); + String srcdoc = m_frame.ownerElement()->attributeWithoutSynchronization(srcdocAttr); ASSERT(!srcdoc.isNull()); CString encodedSrcdoc = srcdoc.utf8(); - return SubstituteData(SharedBuffer::create(encodedSrcdoc.data(), encodedSrcdoc.length()), "text/html", "UTF-8", URL()); + + ResourceResponse response(URL(), ASCIILiteral("text/html"), encodedSrcdoc.length(), ASCIILiteral("UTF-8")); + return SubstituteData(SharedBuffer::create(encodedSrcdoc.data(), encodedSrcdoc.length()), URL(), response, SubstituteData::SessionHistoryVisibility::Hidden); } void FrameLoader::load(const FrameLoadRequest& passedRequest) @@ -1358,8 +1314,9 @@ void FrameLoader::load(const FrameLoadRequest& passedRequest) } if (request.shouldCheckNewWindowPolicy()) { - policyChecker().checkNewWindowPolicy(NavigationAction(request.resourceRequest(), NavigationTypeOther), request.resourceRequest(), nullptr, request.frameName(), [this](const ResourceRequest& request, PassRefPtr<FormState> formState, const String& frameName, const NavigationAction& action, bool shouldContinue) { - continueLoadAfterNewWindowPolicy(request, formState, frameName, action, shouldContinue); + NavigationAction action(request.resourceRequest(), NavigationType::Other, passedRequest.shouldOpenExternalURLsPolicy()); + policyChecker().checkNewWindowPolicy(action, request.resourceRequest(), nullptr, request.frameName(), [this] (const ResourceRequest& request, FormState* formState, const String& frameName, const NavigationAction& action, bool shouldContinue) { + continueLoadAfterNewWindowPolicy(request, formState, frameName, action, shouldContinue, AllowNavigationToInvalidURL::Yes, NewFrameOpenerPolicy::Suppress); }); return; @@ -1368,23 +1325,25 @@ void FrameLoader::load(const FrameLoadRequest& passedRequest) if (!request.hasSubstituteData()) request.setSubstituteData(defaultSubstituteDataForURL(request.resourceRequest().url())); - RefPtr<DocumentLoader> loader = m_client.createDocumentLoader(request.resourceRequest(), request.substituteData()); - if (request.lockHistory() && m_documentLoader) - loader->setClientRedirectSourceForHistory(m_documentLoader->didCreateGlobalHistoryEntry() ? m_documentLoader->urlForHistory().string() : m_documentLoader->clientRedirectSourceForHistory()); - load(loader.get()); + Ref<DocumentLoader> loader = m_client.createDocumentLoader(request.resourceRequest(), request.substituteData()); + applyShouldOpenExternalURLsPolicyToNewDocumentLoader(loader, request.shouldOpenExternalURLsPolicy()); + + load(loader.ptr()); } -void FrameLoader::loadWithNavigationAction(const ResourceRequest& request, const NavigationAction& action, bool lockHistory, FrameLoadType type, PassRefPtr<FormState> formState) +void FrameLoader::loadWithNavigationAction(const ResourceRequest& request, const NavigationAction& action, LockHistory lockHistory, FrameLoadType type, FormState* formState, AllowNavigationToInvalidURL allowNavigationToInvalidURL) { - RefPtr<DocumentLoader> loader = m_client.createDocumentLoader(request, defaultSubstituteDataForURL(request.url())); - if (lockHistory && m_documentLoader) + Ref<DocumentLoader> loader = m_client.createDocumentLoader(request, defaultSubstituteDataForURL(request.url())); + applyShouldOpenExternalURLsPolicyToNewDocumentLoader(loader, action.shouldOpenExternalURLsPolicy()); + + if (lockHistory == LockHistory::Yes && m_documentLoader) loader->setClientRedirectSourceForHistory(m_documentLoader->didCreateGlobalHistoryEntry() ? m_documentLoader->urlForHistory().string() : m_documentLoader->clientRedirectSourceForHistory()); loader->setTriggeringAction(action); if (m_documentLoader) loader->setOverrideEncoding(m_documentLoader->overrideEncoding()); - loadWithDocumentLoader(loader.get(), type, formState); + loadWithDocumentLoader(loader.ptr(), type, formState, allowNavigationToInvalidURL); } void FrameLoader::load(DocumentLoader* newDocumentLoader) @@ -1395,11 +1354,13 @@ void FrameLoader::load(DocumentLoader* newDocumentLoader) if (shouldTreatURLAsSameAsCurrent(newDocumentLoader->originalRequest().url())) { r.setCachePolicy(ReloadIgnoringCacheData); - type = FrameLoadTypeSame; - } else if (shouldTreatURLAsSameAsCurrent(newDocumentLoader->unreachableURL()) && m_loadType == FrameLoadTypeReload) - type = FrameLoadTypeReload; + type = FrameLoadType::Same; + } else if (shouldTreatURLAsSameAsCurrent(newDocumentLoader->unreachableURL()) && m_loadType == FrameLoadType::Reload) + type = FrameLoadType::Reload; + else if (m_loadType == FrameLoadType::RedirectWithLockedBackForwardList && !newDocumentLoader->unreachableURL().isEmpty() && newDocumentLoader->substituteData().isValid()) + type = FrameLoadType::RedirectWithLockedBackForwardList; else - type = FrameLoadTypeStandard; + type = FrameLoadType::Standard; if (m_documentLoader) newDocumentLoader->setOverrideEncoding(m_documentLoader->overrideEncoding()); @@ -1408,7 +1369,7 @@ void FrameLoader::load(DocumentLoader* newDocumentLoader) // visiting in the history list, we treat it as a reload so the history list // is appropriately maintained. // - // FIXME: This seems like a dangerous overloading of the meaning of "FrameLoadTypeReload" ... + // FIXME: This seems like a dangerous overloading of the meaning of "FrameLoadType::Reload" ... // shouldn't a more explicit type of reload be defined, that means roughly // "load without affecting history" ? if (shouldReloadToHandleUnreachableURL(newDocumentLoader)) { @@ -1417,14 +1378,55 @@ void FrameLoader::load(DocumentLoader* newDocumentLoader) // changed and updateForBackForwardNavigation() will not be called when loading is committed. history().saveDocumentAndScrollState(); - ASSERT(type == FrameLoadTypeStandard); - type = FrameLoadTypeReload; + ASSERT(type == FrameLoadType::Standard); + type = FrameLoadType::Reload; } - loadWithDocumentLoader(newDocumentLoader, type, 0); + loadWithDocumentLoader(newDocumentLoader, type, 0, AllowNavigationToInvalidURL::Yes); +} + +static void logNavigation(MainFrame& frame, const URL& destinationURL, FrameLoadType type) +{ + if (!frame.page()) + return; + + String navigationDescription; + switch (type) { + case FrameLoadType::Standard: + navigationDescription = ASCIILiteral("standard"); + break; + case FrameLoadType::Back: + navigationDescription = ASCIILiteral("back"); + break; + case FrameLoadType::Forward: + navigationDescription = ASCIILiteral("forward"); + break; + case FrameLoadType::IndexedBackForward: + navigationDescription = ASCIILiteral("indexedBackForward"); + break; + case FrameLoadType::Reload: + navigationDescription = ASCIILiteral("reload"); + break; + case FrameLoadType::Same: + navigationDescription = ASCIILiteral("same"); + break; + case FrameLoadType::ReloadFromOrigin: + navigationDescription = ASCIILiteral("reloadFromOrigin"); + break; + case FrameLoadType::Replace: + case FrameLoadType::RedirectWithLockedBackForwardList: + // Not logging those for now. + return; + } + frame.page()->diagnosticLoggingClient().logDiagnosticMessage(DiagnosticLoggingKeys::navigationKey(), navigationDescription, ShouldSample::No); +#if ENABLE(PUBLIC_SUFFIX_LIST) + String domain = topPrivatelyControlledDomain(destinationURL.host()); + if (!domain.isEmpty()) + frame.page()->diagnosticLoggingClient().logDiagnosticMessageWithEnhancedPrivacy(DiagnosticLoggingKeys::domainVisitedKey(), domain, ShouldSample::No); +#endif } -void FrameLoader::loadWithDocumentLoader(DocumentLoader* loader, FrameLoadType type, PassRefPtr<FormState> prpFormState) +void FrameLoader::loadWithDocumentLoader(DocumentLoader* loader, FrameLoadType type, FormState* formState, AllowNavigationToInvalidURL allowNavigationToInvalidURL) { // Retain because dispatchBeforeLoadEvent may release the last reference to it. Ref<Frame> protect(m_frame); @@ -1436,17 +1438,23 @@ void FrameLoader::loadWithDocumentLoader(DocumentLoader* loader, FrameLoadType t ASSERT(m_frame.view()); - if (m_pageDismissalEventBeingDispatched != NoDismissal) + if (!isNavigationAllowed()) return; if (m_frame.document()) m_previousURL = m_frame.document()->url(); + const URL& newURL = loader->request().url(); + + // Log main frame navigation types. + if (m_frame.isMainFrame()) { + logNavigation(static_cast<MainFrame&>(m_frame), newURL, type); + static_cast<MainFrame&>(m_frame).performanceLogging().didReachPointOfInterest(PerformanceLogging::MainFrameLoadStarted); + } + policyChecker().setLoadType(type); - RefPtr<FormState> formState = prpFormState; bool isFormSubmission = formState; - const URL& newURL = loader->request().url(); const String& httpMethod = loader->request().httpMethod(); if (shouldPerformFragmentNavigation(isFormSubmission, httpMethod, policyChecker().loadType(), newURL)) { @@ -1456,7 +1464,7 @@ void FrameLoader::loadWithDocumentLoader(DocumentLoader* loader, FrameLoadType t oldDocumentLoader->setTriggeringAction(action); oldDocumentLoader->setLastCheckedRequest(ResourceRequest()); policyChecker().stopCheck(); - policyChecker().checkNavigationPolicy(loader->request(), oldDocumentLoader.get(), formState, [this](const ResourceRequest& request, PassRefPtr<FormState>, bool shouldContinue) { + policyChecker().checkNavigationPolicy(loader->request(), false /* didReceiveRedirectResponse */, oldDocumentLoader.get(), formState, [this] (const ResourceRequest& request, FormState*, bool shouldContinue) { continueFragmentScrollAfterNavigationPolicy(request, shouldContinue); }); return; @@ -1478,13 +1486,13 @@ void FrameLoader::loadWithDocumentLoader(DocumentLoader* loader, FrameLoadType t // a new URL, the parent frame shouldn't learn the URL. if (!m_stateMachine.committedFirstRealDocumentLoad() && !ownerElement->dispatchBeforeLoadEvent(loader->request().url().string())) { - continueLoadAfterNavigationPolicy(loader->request(), formState, false); + continueLoadAfterNavigationPolicy(loader->request(), formState, false, allowNavigationToInvalidURL); return; } } - policyChecker().checkNavigationPolicy(loader->request(), loader, formState, [this](const ResourceRequest& request, PassRefPtr<FormState> formState, bool shouldContinue) { - continueLoadAfterNavigationPolicy(request, formState, shouldContinue); + policyChecker().checkNavigationPolicy(loader->request(), false /* didReceiveRedirectResponse */, loader, formState, [this, allowNavigationToInvalidURL] (const ResourceRequest& request, FormState* formState, bool shouldContinue) { + continueLoadAfterNavigationPolicy(request, formState, shouldContinue, allowNavigationToInvalidURL); }); } @@ -1494,7 +1502,16 @@ void FrameLoader::reportLocalLoadFailed(Frame* frame, const String& url) if (!frame) return; - frame->document()->addConsoleMessage(SecurityMessageSource, ErrorMessageLevel, "Not allowed to load local resource: " + url); + frame->document()->addConsoleMessage(MessageSource::Security, MessageLevel::Error, "Not allowed to load local resource: " + url); +} + +void FrameLoader::reportBlockedPortFailed(Frame* frame, const String& url) +{ + ASSERT(!url.isEmpty()); + if (!frame) + return; + + frame->document()->addConsoleMessage(MessageSource::Security, MessageLevel::Error, "Not allowed to use restricted network port: " + url); } const ResourceRequest& FrameLoader::initialRequest() const @@ -1507,7 +1524,7 @@ bool FrameLoader::willLoadMediaElementURL(URL& url) #if PLATFORM(IOS) // MobileStore depends on the iOS 4.0 era client delegate method because webView:resource:willSendRequest:redirectResponse:fromDataSource // doesn't let them tell when a load request is coming from a media element. See <rdar://problem/8266916> for more details. - if (applicationIsMobileStore()) + if (IOSApplication::isMobileStore()) return m_client.shouldLoadMediaElementURL(url); #endif @@ -1516,7 +1533,7 @@ bool FrameLoader::willLoadMediaElementURL(URL& url) unsigned long identifier; ResourceError error; requestFromDelegate(request, identifier, error); - notifier().sendRemainingDelegateMessages(m_documentLoader.get(), identifier, request, ResourceResponse(url, String(), -1, String(), String()), 0, -1, -1, error); + notifier().sendRemainingDelegateMessages(m_documentLoader.get(), identifier, request, ResourceResponse(url, String(), -1, String()), 0, -1, -1, error); url = request.url(); @@ -1538,13 +1555,10 @@ bool FrameLoader::shouldReloadToHandleUnreachableURL(DocumentLoader* docLoader) // case handles well-formed URLs that can't be loaded, and the latter // case handles malformed URLs and unknown schemes. Loading alternate content // at other times behaves like a standard load. - DocumentLoader* compareDocumentLoader = 0; if (policyChecker().delegateIsDecidingNavigationPolicy() || policyChecker().delegateIsHandlingUnimplementablePolicy()) - compareDocumentLoader = m_policyDocumentLoader.get(); - else if (m_delegateIsHandlingProvisionalLoadError) - compareDocumentLoader = m_provisionalDocumentLoader.get(); + return m_policyDocumentLoader && unreachableURL == m_policyDocumentLoader->request().url(); - return compareDocumentLoader && unreachableURL == compareDocumentLoader->request().url(); + return unreachableURL == m_provisionalLoadErrorBeingHandledURL; } void FrameLoader::reloadWithOverrideEncoding(const String& encoding) @@ -1561,28 +1575,17 @@ void FrameLoader::reloadWithOverrideEncoding(const String& encoding) // We should ask the user for confirmation in this case. request.setCachePolicy(ReturnCacheDataElseLoad); - RefPtr<DocumentLoader> loader = m_client.createDocumentLoader(request, defaultSubstituteDataForURL(request.url())); - setPolicyDocumentLoader(loader.get()); - - loader->setOverrideEncoding(encoding); - - loadWithDocumentLoader(loader.get(), FrameLoadTypeReload, 0); -} + Ref<DocumentLoader> loader = m_client.createDocumentLoader(request, defaultSubstituteDataForURL(request.url())); + applyShouldOpenExternalURLsPolicyToNewDocumentLoader(loader, m_documentLoader->shouldOpenExternalURLsPolicyToPropagate()); -void FrameLoader::reloadWithOverrideURL(const URL& overrideUrl, bool endToEndReload) -{ - if (!m_documentLoader) - return; + setPolicyDocumentLoader(loader.ptr()); - if (overrideUrl.isEmpty()) - return; + loader->setOverrideEncoding(encoding); - ResourceRequest request = m_documentLoader->request(); - request.setURL(overrideUrl); - reloadWithRequest(request, endToEndReload); + loadWithDocumentLoader(loader.ptr(), FrameLoadType::Reload, 0, AllowNavigationToInvalidURL::Yes); } -void FrameLoader::reload(bool endToEndReload) +void FrameLoader::reload(bool endToEndReload, bool contentBlockersEnabled) { if (!m_documentLoader) return; @@ -1598,17 +1601,13 @@ void FrameLoader::reload(bool endToEndReload) if (!unreachableURL.isEmpty()) initialRequest.setURL(unreachableURL); - reloadWithRequest(initialRequest, endToEndReload); -} - -void FrameLoader::reloadWithRequest(const ResourceRequest& initialRequest, bool endToEndReload) -{ - ASSERT(m_documentLoader); - // Create a new document loader for the reload, this will become m_documentLoader eventually, // but first it has to be the "policy" document loader, and then the "provisional" document loader. - RefPtr<DocumentLoader> loader = m_client.createDocumentLoader(initialRequest, defaultSubstituteDataForURL(initialRequest.url())); + Ref<DocumentLoader> loader = m_client.createDocumentLoader(initialRequest, defaultSubstituteDataForURL(initialRequest.url())); + applyShouldOpenExternalURLsPolicyToNewDocumentLoader(loader, m_documentLoader->shouldOpenExternalURLsPolicyToPropagate()); + loader->setUserContentExtensionsEnabled(contentBlockersEnabled); + ResourceRequest& request = loader->request(); // FIXME: We don't have a mechanism to revalidate the main resource without reloading at the moment. @@ -1616,17 +1615,17 @@ void FrameLoader::reloadWithRequest(const ResourceRequest& initialRequest, bool // If we're about to re-post, set up action so the application can warn the user. if (request.httpMethod() == "POST") - loader->setTriggeringAction(NavigationAction(request, NavigationTypeFormResubmitted)); + loader->setTriggeringAction(NavigationAction(request, NavigationType::FormResubmitted)); loader->setOverrideEncoding(m_documentLoader->overrideEncoding()); - loadWithDocumentLoader(loader.get(), endToEndReload ? FrameLoadTypeReloadFromOrigin : FrameLoadTypeReload, 0); + loadWithDocumentLoader(loader.ptr(), endToEndReload ? FrameLoadType::ReloadFromOrigin : FrameLoadType::Reload, 0, AllowNavigationToInvalidURL::Yes); } void FrameLoader::stopAllLoaders(ClearProvisionalItemPolicy clearProvisionalItemPolicy) { - ASSERT(!m_frame.document() || !m_frame.document()->inPageCache()); - if (m_pageDismissalEventBeingDispatched != NoDismissal) + ASSERT(!m_frame.document() || m_frame.document()->pageCacheState() != Document::InPageCache); + if (!isNavigationAllowed()) return; // If this method is called from within this method, infinite recursion can occur (3442218). Avoid this. @@ -1644,7 +1643,7 @@ void FrameLoader::stopAllLoaders(ClearProvisionalItemPolicy clearProvisionalItem // If no new load is in progress, we should clear the provisional item from history // before we call stopLoading. if (clearProvisionalItemPolicy == ShouldClearProvisionalItem) - history().setProvisionalItem(0); + history().setProvisionalItem(nullptr); for (RefPtr<Frame> child = m_frame.tree().firstChild(); child; child = child->tree().nextSibling()) child->loader().stopAllLoaders(clearProvisionalItemPolicy); @@ -1653,7 +1652,7 @@ void FrameLoader::stopAllLoaders(ClearProvisionalItemPolicy clearProvisionalItem if (m_documentLoader) m_documentLoader->stopLoading(); - setProvisionalDocumentLoader(0); + setProvisionalDocumentLoader(nullptr); m_checkTimer.stop(); @@ -1662,6 +1661,9 @@ void FrameLoader::stopAllLoaders(ClearProvisionalItemPolicy clearProvisionalItem void FrameLoader::stopForUserCancel(bool deferCheckLoadComplete) { + // Calling stopAllLoaders can cause the frame to be deallocated, including the frame loader. + Ref<Frame> protectedFrame(m_frame); + stopAllLoaders(); #if PLATFORM(IOS) @@ -1731,7 +1733,7 @@ void FrameLoader::setPolicyDocumentLoader(DocumentLoader* loader) return; if (loader) - loader->setFrame(&m_frame); + loader->attachToFrame(m_frame); if (m_policyDocumentLoader && m_policyDocumentLoader != m_provisionalDocumentLoader && m_policyDocumentLoader != m_documentLoader) @@ -1752,7 +1754,8 @@ void FrameLoader::setProvisionalDocumentLoader(DocumentLoader* loader) } void FrameLoader::setState(FrameState newState) -{ +{ + FrameState oldState = m_state; m_state = newState; if (newState == FrameStateProvisional) @@ -1761,12 +1764,14 @@ void FrameLoader::setState(FrameState newState) frameLoadCompleted(); if (m_documentLoader) m_documentLoader->stopRecordingResponses(); + if (m_frame.isMainFrame() && oldState != newState) + static_cast<MainFrame&>(m_frame).didCompleteLoad(); } } void FrameLoader::clearProvisionalLoad() { - setProvisionalDocumentLoader(0); + setProvisionalDocumentLoader(nullptr); m_progressTracker->progressCompleted(); setState(FrameStateComplete); } @@ -1777,42 +1782,24 @@ void FrameLoader::commitProvisionalLoad() Ref<Frame> protect(m_frame); std::unique_ptr<CachedPage> cachedPage; - if (m_loadingFromCachedPage) - cachedPage = pageCache()->take(history().provisionalItem()); + if (m_loadingFromCachedPage && history().provisionalItem()) + cachedPage = PageCache::singleton().take(*history().provisionalItem(), m_frame.page()); LOG(PageCache, "WebCoreLoading %s: About to commit provisional load from previous URL '%s' to new URL '%s'", m_frame.tree().uniqueName().string().utf8().data(), m_frame.document() ? m_frame.document()->url().stringCenterEllipsizedToLength().utf8().data() : "", pdl ? pdl->url().stringCenterEllipsizedToLength().utf8().data() : "<no provisional DocumentLoader>"); -#if PLATFORM(IOS) - // In the case where we are not navigating to a cached page, and the system is under (speculative) memory pressure, - // we can try to preemptively release some of the pages in the cache. - // FIXME: Right now the capacity is 1 on iOS devices with 256 MB of RAM, so this will always blow away the whole - // page cache. We could still preemptively prune the page cache while navigating to a cached page if capacity > 1. - // See <rdar://problem/11779846> for more details. - if (!cachedPage) { - if (memoryPressureHandler().hasReceivedMemoryPressure()) { - LOG(MemoryPressure, "Pruning page cache because under memory pressure at: %s", __PRETTY_FUNCTION__); - LOG(PageCache, "Pruning page cache to 0 due to memory pressure"); - // Don't cache any page if we are under memory pressure. - pageCache()->pruneToCapacityNow(0); - } else if (systemMemoryLevel() <= memoryLevelThresholdToPrunePageCache) { - LOG(MemoryPressure, "Pruning page cache because system memory level is %d at: %s", systemMemoryLevel(), __PRETTY_FUNCTION__); - LOG(PageCache, "Pruning page cache to %d due to low memory (level %d less or equal to %d threshold)", pageCache()->capacity() / 2, systemMemoryLevel(), memoryLevelThresholdToPrunePageCache); - pageCache()->pruneToCapacityNow(pageCache()->capacity() / 2); - } - } -#endif - willTransitionToCommitted(); - // Check to see if we need to cache the page we are navigating away from into the back/forward cache. - // We are doing this here because we know for sure that a new page is about to be loaded. - HistoryItem* item = history().currentItem(); - if (!m_frame.tree().parent() && pageCache()->canCache(m_frame.page()) && !item->isInPageCache()) - pageCache()->add(item, *m_frame.page()); + if (!m_frame.tree().parent() && history().currentItem()) { + // Check to see if we need to cache the page we are navigating away from into the back/forward cache. + // We are doing this here because we know for sure that a new page is about to be loaded. + PageCache::singleton().addIfCacheable(*history().currentItem(), m_frame.page()); + + WebCore::jettisonExpensiveObjectsOnTopLevelNavigation(); + } - if (m_loadType != FrameLoadTypeReplace) + if (m_loadType != FrameLoadType::Replace) closeOldDataSources(); if (!cachedPage && !m_stateMachine.creatingInitialEmptyDocument()) @@ -1822,8 +1809,8 @@ void FrameLoader::commitProvisionalLoad() if (pdl && m_documentLoader) { // Check if the destination page is allowed to access the previous page's timing information. - RefPtr<SecurityOrigin> securityOrigin = SecurityOrigin::create(pdl->request().url()); - m_documentLoader->timing()->setHasSameOriginAsPreviousDocument(securityOrigin->canRequest(m_previousURL)); + Ref<SecurityOrigin> securityOrigin(SecurityOrigin::create(pdl->request().url())); + m_documentLoader->timing().setHasSameOriginAsPreviousDocument(securityOrigin.get().canRequest(m_previousURL)); } // Call clientRedirectCancelledOrFinished() here so that the frame load delegate is notified that the redirect's @@ -1839,21 +1826,44 @@ void FrameLoader::commitProvisionalLoad() // commit to happen before any changes to viewport arguments and dealing with this there is difficult. m_frame.page()->chrome().setDispatchViewportDataDidChangeSuppressed(true); #endif - prepareForCachedPageRestore(); + willRestoreFromCachedPage(); + + // Start request for the main resource and dispatch didReceiveResponse before the load is committed for + // consistency with all other loads. See https://bugs.webkit.org/show_bug.cgi?id=150927. + ResourceError mainResouceError; + unsigned long mainResourceIdentifier; + ResourceRequest mainResourceRequest(cachedPage->documentLoader()->request()); + requestFromDelegate(mainResourceRequest, mainResourceIdentifier, mainResouceError); + notifier().dispatchDidReceiveResponse(cachedPage->documentLoader(), mainResourceIdentifier, cachedPage->documentLoader()->response()); + + std::optional<HasInsecureContent> hasInsecureContent = cachedPage->cachedMainFrame()->hasInsecureContent(); + + { + // Do not dispatch DOM events as their JavaScript listeners could cause the page to be put + // into the page cache before we have finished restoring it from the page cache. + NoEventDispatchAssertion assertNoEventDispatch; + + // FIXME: This API should be turned around so that we ground CachedPage into the Page. + cachedPage->restore(*m_frame.page()); + } + + dispatchDidCommitLoad(hasInsecureContent); - // FIXME: This API should be turned around so that we ground CachedPage into the Page. - cachedPage->restore(*m_frame.page()); + didRestoreFromCachedPage(); - dispatchDidCommitLoad(); #if PLATFORM(IOS) m_frame.page()->chrome().setDispatchViewportDataDidChangeSuppressed(false); m_frame.page()->chrome().dispatchViewportPropertiesDidChange(m_frame.page()->viewportArguments()); #endif - // If we have a title let the WebView know about it. - StringWithDirection title = m_documentLoader->title(); - if (!title.isNull()) + + auto& title = m_documentLoader->title(); + if (!title.string.isNull()) m_client.dispatchDidReceiveTitle(title); + // Send remaining notifications for the main resource. + notifier().sendRemainingDelegateMessages(m_documentLoader.get(), mainResourceIdentifier, mainResourceRequest, ResourceResponse(), + nullptr, static_cast<int>(m_documentLoader->response().expectedContentLength()), 0, mainResouceError); + checkCompleted(); } else didOpenURL(); @@ -1861,28 +1871,26 @@ void FrameLoader::commitProvisionalLoad() LOG(Loading, "WebCoreLoading %s: Finished committing provisional load to URL %s", m_frame.tree().uniqueName().string().utf8().data(), m_frame.document() ? m_frame.document()->url().stringCenterEllipsizedToLength().utf8().data() : ""); - if (m_loadType == FrameLoadTypeStandard && m_documentLoader->isClientRedirect()) + if (m_loadType == FrameLoadType::Standard && m_documentLoader->isClientRedirect()) history().updateForClientRedirect(); if (m_loadingFromCachedPage) { -#if PLATFORM(IOS) // Note, didReceiveDocType is expected to be called for cached pages. See <rdar://problem/5906758> for more details. - if (m_frame.document()->doctype() && m_frame.page()) - m_frame.page()->chrome().didReceiveDocType(&m_frame); -#endif - m_frame.document()->documentDidResumeFromPageCache(); + if (auto* page = m_frame.page()) + page->chrome().didReceiveDocType(m_frame); + m_frame.document()->resume(ActiveDOMObject::PageCache); // Force a layout to update view size and thereby update scrollbars. #if PLATFORM(IOS) - m_client.forceLayoutWithoutRecalculatingStyles(); + if (!m_client.forceLayoutOnRestoreFromPageCache()) + m_frame.view()->forceLayout(); #else m_frame.view()->forceLayout(); #endif - const ResponseVector& responses = m_documentLoader->responses(); - size_t count = responses.size(); - for (size_t i = 0; i < count; i++) { - const ResourceResponse& response = responses[i]; + // Main resource delegates were already sent, so we skip the first response here. + for (unsigned i = 1; i < m_documentLoader->responses().size(); ++i) { + const auto& response = m_documentLoader->responses()[i]; // FIXME: If the WebKit client changes or cancels the request, this is not respected. ResourceError error; unsigned long identifier; @@ -1924,82 +1932,77 @@ void FrameLoader::transitionToCommitted(CachedPage* cachedPage) if (pdl != m_provisionalDocumentLoader) return; - // Nothing else can interupt this commit - set the Provisional->Committed transition in stone if (m_documentLoader) m_documentLoader->stopLoadingSubresources(); if (m_documentLoader) m_documentLoader->stopLoadingPlugIns(); + // Setting our document loader invokes the unload event handler of our child frames. + // Script can do anything. If the script initiates a new load, we need to abandon the + // current load or the two will stomp each other. setDocumentLoader(m_provisionalDocumentLoader.get()); - setProvisionalDocumentLoader(0); - - if (pdl != m_documentLoader) { - ASSERT(m_state == FrameStateComplete); + if (pdl != m_provisionalDocumentLoader) return; - } + setProvisionalDocumentLoader(nullptr); + // Nothing else can interupt this commit - set the Provisional->Committed transition in stone setState(FrameStateCommittedPage); -#if ENABLE(TOUCH_EVENTS) && !PLATFORM(IOS) - if (m_frame.isMainFrame()) - m_frame.page()->chrome().client().needTouchEvents(false); -#endif - // Handle adding the URL to the back/forward list. DocumentLoader* dl = m_documentLoader.get(); switch (m_loadType) { - case FrameLoadTypeForward: - case FrameLoadTypeBack: - case FrameLoadTypeIndexedBackForward: - if (m_frame.page()) { - // If the first load within a frame is a navigation within a back/forward list that was attached - // without any of the items being loaded then we need to update the history in a similar manner as - // for a standard load with the exception of updating the back/forward list (<rdar://problem/8091103>). - if (!m_stateMachine.committedFirstRealDocumentLoad() && m_frame.isMainFrame()) - history().updateForStandardLoad(HistoryController::UpdateAllExceptBackForwardList); - - history().updateForBackForwardNavigation(); - - // For cached pages, CachedFrame::restore will take care of firing the popstate event with the history item's state object - if (history().currentItem() && !cachedPage) - m_pendingStateObject = history().currentItem()->stateObject(); - - // Create a document view for this document, or used the cached view. - if (cachedPage) { - DocumentLoader* cachedDocumentLoader = cachedPage->documentLoader(); - ASSERT(cachedDocumentLoader); - cachedDocumentLoader->setFrame(&m_frame); - m_client.transitionToCommittedFromCachedFrame(cachedPage->cachedMainFrame()); - } else - m_client.transitionToCommittedForNewPage(); - } - break; - - case FrameLoadTypeReload: - case FrameLoadTypeReloadFromOrigin: - case FrameLoadTypeSame: - case FrameLoadTypeReplace: - history().updateForReload(); - m_client.transitionToCommittedForNewPage(); - break; - - case FrameLoadTypeStandard: - history().updateForStandardLoad(); - if (m_frame.view()) - m_frame.view()->setScrollbarsSuppressed(true); - m_client.transitionToCommittedForNewPage(); - break; - - case FrameLoadTypeRedirectWithLockedBackForwardList: - history().updateForRedirectWithLockedBackForwardList(); - m_client.transitionToCommittedForNewPage(); - break; - - // FIXME Remove this check when dummy ds is removed (whatever "dummy ds" is). - // An exception should be thrown if we're in the FrameLoadTypeUninitialized state. - default: - ASSERT_NOT_REACHED(); + case FrameLoadType::Forward: + case FrameLoadType::Back: + case FrameLoadType::IndexedBackForward: + if (m_frame.page()) { + // If the first load within a frame is a navigation within a back/forward list that was attached + // without any of the items being loaded then we need to update the history in a similar manner as + // for a standard load with the exception of updating the back/forward list (<rdar://problem/8091103>). + if (!m_stateMachine.committedFirstRealDocumentLoad() && m_frame.isMainFrame()) + history().updateForStandardLoad(HistoryController::UpdateAllExceptBackForwardList); + + history().updateForBackForwardNavigation(); + + // For cached pages, CachedFrame::restore will take care of firing the popstate event with the history item's state object + if (history().currentItem() && !cachedPage) + m_pendingStateObject = history().currentItem()->stateObject(); + + // Create a document view for this document, or used the cached view. + if (cachedPage) { + DocumentLoader* cachedDocumentLoader = cachedPage->documentLoader(); + ASSERT(cachedDocumentLoader); + cachedDocumentLoader->attachToFrame(m_frame); + m_client.transitionToCommittedFromCachedFrame(cachedPage->cachedMainFrame()); + } else + m_client.transitionToCommittedForNewPage(); + } + break; + + case FrameLoadType::Reload: + case FrameLoadType::ReloadFromOrigin: + case FrameLoadType::Same: + case FrameLoadType::Replace: + history().updateForReload(); + m_client.transitionToCommittedForNewPage(); + break; + + case FrameLoadType::Standard: + history().updateForStandardLoad(); + if (m_frame.view()) + m_frame.view()->setScrollbarsSuppressed(true); + m_client.transitionToCommittedForNewPage(); + break; + + case FrameLoadType::RedirectWithLockedBackForwardList: + history().updateForRedirectWithLockedBackForwardList(); + m_client.transitionToCommittedForNewPage(); + break; + + // FIXME Remove this check when dummy ds is removed (whatever "dummy ds" is). + // An exception should be thrown if we're in the FrameLoadTypeUninitialized state. + default: + ASSERT_NOT_REACHED(); } m_documentLoader->writer().setMIMEType(dl->responseMIMEType()); @@ -2027,7 +2030,7 @@ void FrameLoader::clientRedirectCancelledOrFinished(bool cancelWithLoadInProgres m_sentRedirectNotification = false; } -void FrameLoader::clientRedirected(const URL& url, double seconds, double fireDate, bool lockBackForwardList) +void FrameLoader::clientRedirected(const URL& url, double seconds, double fireDate, LockBackForwardList lockBackForwardList) { m_client.dispatchWillPerformClientRedirect(url, seconds, fireDate); @@ -2039,7 +2042,7 @@ void FrameLoader::clientRedirected(const URL& url, double seconds, double fireDa // load as part of the original navigation. If we don't have a document loader, we have // no "original" load on which to base a redirect, so we treat the redirect as a normal load. // Loads triggered by JavaScript form submissions never count as quick redirects. - m_quickRedirectComing = (lockBackForwardList || history().currentItemShouldBeReplaced()) && m_documentLoader && !m_isExecutingJavaScriptFormAction; + m_quickRedirectComing = (lockBackForwardList == LockBackForwardList::Yes || history().currentItemShouldBeReplaced()) && m_documentLoader && !m_isExecutingJavaScriptFormAction; } bool FrameLoader::shouldReload(const URL& currentURL, const URL& destinationURL) @@ -2066,7 +2069,7 @@ void FrameLoader::closeOldDataSources() m_client.setMainFrameDocumentReady(false); // stop giving out the actual DOMDocument to observers } -void FrameLoader::prepareForCachedPageRestore() +void FrameLoader::willRestoreFromCachedPage() { ASSERT(!m_frame.tree().parent()); ASSERT(m_frame.page()); @@ -2085,6 +2088,31 @@ void FrameLoader::prepareForCachedPageRestore() } } +void FrameLoader::didRestoreFromCachedPage() +{ + // Dispatching JavaScript events can cause frame destruction. + auto& mainFrame = m_frame.page()->mainFrame(); + Vector<Ref<Frame>> childFrames; + for (auto* child = mainFrame.tree().traverseNextInPostOrderWithWrap(true); child; child = child->tree().traverseNextInPostOrderWithWrap(false)) + childFrames.append(*child); + + for (auto& child : childFrames) { + if (!child->tree().isDescendantOf(&mainFrame)) + continue; + auto* document = child->document(); + if (!document) + continue; + + // FIXME: Update Page Visibility state here. + // https://bugs.webkit.org/show_bug.cgi?id=116770 + document->dispatchPageshowEvent(PageshowEventPersisted); + + auto* historyItem = child->loader().history().currentItem(); + if (historyItem && historyItem->stateObject()) + document->dispatchPopstateEvent(historyItem->stateObject()); + } +} + void FrameLoader::open(CachedFrameBase& cachedFrame) { m_isComplete = false; @@ -2105,7 +2133,7 @@ void FrameLoader::open(CachedFrameBase& cachedFrame) clear(document, true, true, cachedFrame.isMainFrame()); - document->setInPageCache(false); + document->setPageCacheState(Document::NotInPageCache); m_needsClear = true; m_isComplete = false; @@ -2118,13 +2146,15 @@ void FrameLoader::open(CachedFrameBase& cachedFrame) ASSERT(view); view->setWasScrolledByUser(false); - // Use the current ScrollView's frame rect. - if (m_frame.view()) - view->setFrameRect(m_frame.view()->frameRect()); + std::optional<IntRect> previousViewFrameRect = m_frame.view() ? m_frame.view()->frameRect() : std::optional<IntRect>(std::nullopt); m_frame.setView(view); + + // Use the previous ScrollView's frame rect. + if (previousViewFrameRect) + view->setFrameRect(previousViewFrameRect.value()); m_frame.setDocument(document); - document->domWindow()->resumeFromPageCache(); + document->domWindow()->resumeFromDocumentSuspension(); updateFirstPartyForCookies(); @@ -2139,12 +2169,12 @@ bool FrameLoader::isHostedByObjectElement() const bool FrameLoader::isReplacing() const { - return m_loadType == FrameLoadTypeReplace; + return m_loadType == FrameLoadType::Replace; } void FrameLoader::setReplacing() { - m_loadType = FrameLoadTypeReplace; + m_loadType = FrameLoadType::Replace; } bool FrameLoader::subframeIsLoading() const @@ -2177,10 +2207,15 @@ FrameLoadType FrameLoader::loadType() const CachePolicy FrameLoader::subresourceCachePolicy() const { + if (Page* page = m_frame.page()) { + if (page->isResourceCachingDisabled()) + return CachePolicyReload; + } + if (m_isComplete) return CachePolicyVerify; - if (m_loadType == FrameLoadTypeReloadFromOrigin) + if (m_loadType == FrameLoadType::ReloadFromOrigin) return CachePolicyReload; if (Frame* parentFrame = m_frame.tree().parent()) { @@ -2189,18 +2224,24 @@ CachePolicy FrameLoader::subresourceCachePolicy() const return parentCachePolicy; } - if (m_loadType == FrameLoadTypeReload) - return CachePolicyRevalidate; - - const ResourceRequest& request(documentLoader()->request()); -#if PLATFORM(MAC) - if (request.cachePolicy() == ReloadIgnoringCacheData && !equalIgnoringCase(request.httpMethod(), "post") && ResourceRequest::useQuickLookResourceCachingQuirks()) + switch (m_loadType) { + case FrameLoadType::Reload: return CachePolicyRevalidate; -#endif - - if (request.cachePolicy() == ReturnCacheDataElseLoad) + case FrameLoadType::Back: + case FrameLoadType::Forward: + case FrameLoadType::IndexedBackForward: return CachePolicyHistoryBuffer; + case FrameLoadType::ReloadFromOrigin: + ASSERT_NOT_REACHED(); // Already handled above. + return CachePolicyReload; + case FrameLoadType::RedirectWithLockedBackForwardList: + case FrameLoadType::Replace: + case FrameLoadType::Same: + case FrameLoadType::Standard: + return CachePolicyVerify; + } + RELEASE_ASSERT_NOT_REACHED(); return CachePolicyVerify; } @@ -2210,7 +2251,11 @@ void FrameLoader::checkLoadCompleteForThisFrame() switch (m_state) { case FrameStateProvisional: { - if (m_delegateIsHandlingProvisionalLoadError) + // FIXME: Prohibiting any provisional load failures from being sent to clients + // while handling provisional load failures is too heavy. For example, the current + // load will fail to cancel another ongoing load. That might prevent clients' page + // load state being handled properly. + if (!m_provisionalLoadErrorBeingHandledURL.isEmpty()) return; RefPtr<DocumentLoader> pdl = m_provisionalDocumentLoader; @@ -2232,9 +2277,13 @@ void FrameLoader::checkLoadCompleteForThisFrame() // Only reset if we aren't already going to a new provisional item. bool shouldReset = !history().provisionalItem(); if (!pdl->isLoadingInAPISense() || pdl->isStopping()) { - m_delegateIsHandlingProvisionalLoadError = true; + m_provisionalLoadErrorBeingHandledURL = m_provisionalDocumentLoader->url(); m_client.dispatchDidFailProvisionalLoad(error); - m_delegateIsHandlingProvisionalLoadError = false; +#if ENABLE(CONTENT_FILTERING) + if (auto contentFilter = pdl->contentFilter()) + contentFilter->handleProvisionalLoadFailure(error); +#endif + m_provisionalLoadErrorBeingHandledURL = { }; ASSERT(!pdl->isLoading()); @@ -2275,7 +2324,7 @@ void FrameLoader::checkLoadCompleteForThisFrame() // If the user had a scroll point, scroll to it, overriding the anchor point if any. if (m_frame.page()) { - if (isBackForwardLoadType(m_loadType) || m_loadType == FrameLoadTypeReload || m_loadType == FrameLoadTypeReloadFromOrigin) + if (isBackForwardLoadType(m_loadType) || m_loadType == FrameLoadType::Reload || m_loadType == FrameLoadType::ReloadFromOrigin) history().restoreScrollPositionAndViewState(); } @@ -2283,31 +2332,55 @@ void FrameLoader::checkLoadCompleteForThisFrame() return; m_progressTracker->progressCompleted(); - if (Page* page = m_frame.page()) { + Page* page = m_frame.page(); + if (page) { if (m_frame.isMainFrame()) - page->resetRelevantPaintedObjectCounter(); + page->didFinishLoad(); } const ResourceError& error = dl->mainDocumentError(); AXObjectCache::AXLoadingEvent loadingEvent; if (!error.isNull()) { + RELEASE_LOG_IF_ALLOWED("checkLoadCompleteForThisFrame: Finished frame load with error (frame = %p, main = %d, isTimeout = %d, isCancellation = %d, errorCode = %d)", &m_frame, m_frame.isMainFrame(), error.isTimeout(), error.isCancellation(), error.errorCode()); m_client.dispatchDidFailLoad(error); loadingEvent = AXObjectCache::AXLoadingFailed; } else { + RELEASE_LOG_IF_ALLOWED("checkLoadCompleteForThisFrame: Finished frame load (frame = %p, main = %d)", &m_frame, m_frame.isMainFrame()); +#if ENABLE(DATA_DETECTION) + auto* document = m_frame.document(); + if (m_frame.settings().dataDetectorTypes() != DataDetectorTypeNone && document) { + if (auto* documentElement = document->documentElement()) { + RefPtr<Range> documentRange = makeRange(firstPositionInNode(documentElement), lastPositionInNode(documentElement)); + m_frame.setDataDetectionResults(DataDetection::detectContentInRange(documentRange, m_frame.settings().dataDetectorTypes(), m_client.dataDetectionContext())); + if (m_frame.isMainFrame()) + m_client.dispatchDidFinishDataDetection(m_frame.dataDetectionResults()); + } + } +#endif m_client.dispatchDidFinishLoad(); loadingEvent = AXObjectCache::AXLoadingFinished; } // Notify accessibility. - if (AXObjectCache* cache = m_frame.document()->existingAXObjectCache()) - cache->frameLoadingEventNotification(&m_frame, loadingEvent); + if (auto* document = m_frame.document()) { + if (AXObjectCache* cache = document->existingAXObjectCache()) + cache->frameLoadingEventNotification(&m_frame, loadingEvent); + } + + // The above calls to dispatchDidFinishLoad() might have detached the Frame + // from its Page and also might have caused Page to be deleted. + // Don't assume 'page' is still available to use. + if (m_frame.isMainFrame() && m_frame.page()) { + ASSERT(&m_frame.page()->mainFrame() == &m_frame); + m_frame.page()->diagnosticLoggingClient().logDiagnosticMessageWithResult(DiagnosticLoggingKeys::pageLoadedKey(), emptyString(), error.isNull() ? DiagnosticLoggingResultPass : DiagnosticLoggingResultFail, ShouldSample::Yes); + } return; } case FrameStateComplete: - m_loadType = FrameLoadTypeStandard; + m_loadType = FrameLoadType::Standard; frameLoadCompleted(); return; } @@ -2324,76 +2397,46 @@ void FrameLoader::continueLoadAfterWillSubmitForm() // The load might be cancelled inside of prepareForLoadStart(), nulling out the m_provisionalDocumentLoader, // so we need to null check it again. - if (!m_provisionalDocumentLoader) + if (!m_provisionalDocumentLoader) { + RELEASE_LOG_IF_ALLOWED("continueLoadAfterWillSubmitForm: Frame load canceled (frame = %p, main = %d)", &m_frame, m_frame.isMainFrame()); return; + } DocumentLoader* activeDocLoader = activeDocumentLoader(); - if (activeDocLoader && activeDocLoader->isLoadingMainResource()) + if (activeDocLoader && activeDocLoader->isLoadingMainResource()) { + RELEASE_LOG_IF_ALLOWED("continueLoadAfterWillSubmitForm: Main frame already being loaded (frame = %p, main = %d)", &m_frame, m_frame.isMainFrame()); return; + } m_loadingFromCachedPage = false; m_provisionalDocumentLoader->startLoadingMainResource(); } -static URL originatingURLFromBackForwardList(Page* page) -{ - // FIXME: Can this logic be replaced with m_frame.document()->firstPartyForCookies()? - // It has the same meaning of "page a user thinks is the current one". - - URL originalURL; - int backCount = page->backForward().backCount(); - for (int backIndex = 0; backIndex <= backCount; backIndex++) { - // FIXME: At one point we had code here to check a "was user gesture" flag. - // Do we need to restore that logic? - HistoryItem* historyItem = page->backForward().itemAtIndex(-backIndex); - if (!historyItem) - continue; - - originalURL = historyItem->originalURL(); - if (!originalURL.isNull()) - return originalURL; - } - - return URL(); -} - void FrameLoader::setOriginalURLForDownloadRequest(ResourceRequest& request) { + // FIXME: Rename firstPartyForCookies back to mainDocumentURL. It was a mistake to think that it was only used for cookies. + // The originalURL is defined as the URL of the page where the download was initiated. URL originalURL; - - // If there is no referrer, assume that the download was initiated directly, so current document is - // completely unrelated to it. See <rdar://problem/5294691>. - // FIXME: Referrer is not sent in many other cases, so we will often miss this important information. - // Find a better way to decide whether the download was unrelated to current document. - if (!request.httpReferrer().isNull()) { - // find the first item in the history that was originated by the user - originalURL = originatingURLFromBackForwardList(m_frame.page()); - } - - if (originalURL.isNull()) - originalURL = request.url(); - - if (!originalURL.protocol().isEmpty() && !originalURL.host().isEmpty()) { - unsigned port = originalURL.port(); - - // Original URL is needed to show the user where a file was downloaded from. We should make a URL that won't result in downloading the file again. - // FIXME: Using host-only URL is a very heavy-handed approach. We should attempt to provide the actual page where the download was initiated from, as a reminder to the user. - String hostOnlyURLString; - if (port) - hostOnlyURLString = makeString(originalURL.protocol(), "://", originalURL.host(), ":", String::number(port)); - else - hostOnlyURLString = makeString(originalURL.protocol(), "://", originalURL.host()); - - // FIXME: Rename firstPartyForCookies back to mainDocumentURL. It was a mistake to think that it was only used for cookies. - request.setFirstPartyForCookies(URL(URL(), hostOnlyURLString)); - } + if (m_frame.document()) { + originalURL = m_frame.document()->firstPartyForCookies(); + // If there is no main document URL, it means that this document is newly opened and just for download purpose. + // In this case, we need to set the originalURL to this document's opener's main document URL. + if (originalURL.isEmpty() && opener() && opener()->document()) + originalURL = opener()->document()->firstPartyForCookies(); + } + // If the originalURL is the same as the requested URL, we are processing a download + // initiated directly without a page and do not need to specify the originalURL. + if (originalURL == request.url()) + request.setFirstPartyForCookies(URL()); + else + request.setFirstPartyForCookies(originalURL); } -void FrameLoader::didLayout(LayoutMilestones milestones) +void FrameLoader::didReachLayoutMilestone(LayoutMilestones milestones) { ASSERT(m_frame.isMainFrame()); - m_client.dispatchDidLayout(milestones); + m_client.dispatchDidReachLayoutMilestone(milestones); } void FrameLoader::didFirstLayout() @@ -2428,23 +2471,34 @@ void FrameLoader::frameLoadCompleted() void FrameLoader::detachChildren() { + // detachChildren() will fire the unload event in each subframe and the + // HTML specification states that the parent document's ignore-opens-during-unload counter while + // this event is being fired in its subframes: + // https://html.spec.whatwg.org/multipage/browsers.html#unload-a-document + IgnoreOpensDuringUnloadCountIncrementer ignoreOpensDuringUnloadCountIncrementer(m_frame.document()); + + // Any subframe inserted by unload event handlers executed in the loop below will not get unloaded + // because we create a copy of the subframes list before looping. Therefore, it would be unsafe to + // allow loading of subframes at this point. + SubframeLoadingDisabler subframeLoadingDisabler(m_frame.document()); + Vector<Ref<Frame>, 16> childrenToDetach; childrenToDetach.reserveInitialCapacity(m_frame.tree().childCount()); for (Frame* child = m_frame.tree().lastChild(); child; child = child->tree().previousSibling()) childrenToDetach.uncheckedAppend(*child); - for (unsigned i = 0; i < childrenToDetach.size(); ++i) - childrenToDetach[i]->loader().detachFromParent(); + for (auto& child : childrenToDetach) + child->loader().detachFromParent(); } -void FrameLoader::closeAndRemoveChild(Frame* child) +void FrameLoader::closeAndRemoveChild(Frame& child) { - child->tree().detachFromParent(); + child.tree().detachFromParent(); - child->setView(0); - if (child->ownerElement() && child->page()) - child->page()->decrementSubframeCount(); - child->willDetachPage(); - child->detachFromPage(); + child.setView(nullptr); + if (child.ownerElement() && child.page()) + child.page()->decrementSubframeCount(); + child.willDetachPage(); + child.detachFromPage(); m_frame.tree().removeChild(child); } @@ -2466,18 +2520,20 @@ void FrameLoader::checkLoadComplete() frames.append(*frame); // To process children before their parents, iterate the vector backwards. - for (unsigned i = frames.size(); i; --i) - frames[i - 1]->loader().checkLoadCompleteForThisFrame(); + for (auto frame = frames.rbegin(); frame != frames.rend(); ++frame) { + if ((*frame)->page()) + (*frame)->loader().checkLoadCompleteForThisFrame(); + } } int FrameLoader::numPendingOrLoadingRequests(bool recurse) const { if (!recurse) - return m_frame.document()->cachedResourceLoader()->requestCount(); + return m_frame.document()->cachedResourceLoader().requestCount(); int count = 0; for (Frame* frame = &m_frame; frame; frame = frame->tree().traverseNext(&m_frame)) - count += frame->document()->cachedResourceLoader()->requestCount(); + count += frame->document()->cachedResourceLoader().requestCount(); return count; } @@ -2486,44 +2542,53 @@ String FrameLoader::userAgent(const URL& url) const return m_client.userAgent(url); } -void FrameLoader::handledOnloadEvents() +void FrameLoader::dispatchOnloadEvents() { - m_client.dispatchDidHandleOnloadEvents(); + m_client.dispatchDidDispatchOnloadEvents(); if (documentLoader()) - documentLoader()->handledOnloadEvents(); + documentLoader()->dispatchOnloadEvents(); } void FrameLoader::frameDetached() { - stopAllLoaders(); - m_frame.document()->stopActiveDOMObjects(); + // Calling stopAllLoaders() can cause the frame to be deallocated, including the frame loader. + Ref<Frame> protectedFrame(m_frame); + + if (m_frame.document()->pageCacheState() != Document::InPageCache) { + stopAllLoaders(); + m_frame.document()->stopActiveDOMObjects(); + } + detachFromParent(); } void FrameLoader::detachFromParent() { + // Calling stopAllLoaders() can cause the frame to be deallocated, including the frame loader. Ref<Frame> protect(m_frame); closeURL(); history().saveScrollPositionAndViewStateToItem(history().currentItem()); detachChildren(); - // stopAllLoaders() needs to be called after detachChildren(), because detachedChildren() - // will trigger the unload event handlers of any child frames, and those event - // handlers might start a new subresource load in this frame. - stopAllLoaders(); + if (m_frame.document()->pageCacheState() != Document::InPageCache) { + // stopAllLoaders() needs to be called after detachChildren() if the document is not in the page cache, + // because detachedChildren() will trigger the unload event handlers of any child frames, and those event + // handlers might start a new subresource load in this frame. + stopAllLoaders(); + } - InspectorInstrumentation::frameDetachedFromParent(&m_frame); + InspectorInstrumentation::frameDetachedFromParent(m_frame); detachViewsAndDocumentLoader(); m_progressTracker = nullptr; if (Frame* parent = m_frame.tree().parent()) { - parent->loader().closeAndRemoveChild(&m_frame); + parent->loader().closeAndRemoveChild(m_frame); parent->loader().scheduleCheckCompleted(); } else { - m_frame.setView(0); + m_frame.setView(nullptr); m_frame.willDetachPage(); m_frame.detachFromPage(); } @@ -2532,7 +2597,7 @@ void FrameLoader::detachFromParent() void FrameLoader::detachViewsAndDocumentLoader() { m_client.detachedFromParent2(); - setDocumentLoader(0); + setDocumentLoader(nullptr); m_client.detachedFromParent3(); } @@ -2546,29 +2611,19 @@ void FrameLoader::addExtraFieldsToMainResourceRequest(ResourceRequest& request) // FIXME: Using m_loadType seems wrong for some callers. // If we are only preparing to load the main resource, that is previous load's load type! addExtraFieldsToRequest(request, m_loadType, true); + + // Upgrade-Insecure-Requests should only be added to main resource requests + addHTTPUpgradeInsecureRequestsIfNeeded(request); } -void FrameLoader::addExtraFieldsToRequest(ResourceRequest& request, FrameLoadType loadType, bool mainResource) +ResourceRequestCachePolicy FrameLoader::defaultRequestCachingPolicy(const ResourceRequest& request, FrameLoadType loadType, bool isMainResource) { - // Don't set the cookie policy URL if it's already been set. - // But make sure to set it on all requests regardless of protocol, as it has significance beyond the cookie policy (<rdar://problem/6616664>). - if (request.firstPartyForCookies().isEmpty()) { - if (mainResource && m_frame.isMainFrame()) - request.setFirstPartyForCookies(request.url()); - else if (Document* document = m_frame.document()) - request.setFirstPartyForCookies(document->firstPartyForCookies()); - } - - // The remaining modifications are only necessary for HTTP and HTTPS. - if (!request.url().isEmpty() && !request.url().protocolIsInHTTPFamily()) - return; - - applyUserAgent(request); - - if (!mainResource) { + if (m_overrideCachePolicyForTesting) + return m_overrideCachePolicyForTesting.value(); + if (!isMainResource) { if (request.isConditional()) - request.setCachePolicy(ReloadIgnoringCacheData); - else if (documentLoader()->isLoadingInAPISense()) { + return ReloadIgnoringCacheData; + if (documentLoader()->isLoadingInAPISense()) { // If we inherit cache policy from a main resource, we use the DocumentLoader's // original request cache policy for two reasons: // 1. For POST requests, we mutate the cache policy for the main resource, @@ -2579,26 +2634,55 @@ void FrameLoader::addExtraFieldsToRequest(ResourceRequest& request, FrameLoadTyp ResourceRequestCachePolicy mainDocumentOriginalCachePolicy = documentLoader()->originalRequest().cachePolicy(); // Back-forward navigations try to load main resource from cache only to avoid re-submitting form data, and start over (with a warning dialog) if that fails. // This policy is set on initial request too, but should not be inherited. - ResourceRequestCachePolicy subresourceCachePolicy = (mainDocumentOriginalCachePolicy == ReturnCacheDataDontLoad) ? ReturnCacheDataElseLoad : mainDocumentOriginalCachePolicy; - request.setCachePolicy(subresourceCachePolicy); - } else - request.setCachePolicy(UseProtocolCachePolicy); - + return (mainDocumentOriginalCachePolicy == ReturnCacheDataDontLoad) ? ReturnCacheDataElseLoad : mainDocumentOriginalCachePolicy; + } // FIXME: Other FrameLoader functions have duplicated code for setting cache policy of main request when reloading. // It seems better to manage it explicitly than to hide the logic inside addExtraFieldsToRequest(). - } else if (loadType == FrameLoadTypeReload || loadType == FrameLoadTypeReloadFromOrigin || request.isConditional()) + } else if (loadType == FrameLoadType::Reload || loadType == FrameLoadType::ReloadFromOrigin || request.isConditional()) + return ReloadIgnoringCacheData; + + return UseProtocolCachePolicy; +} + +void FrameLoader::addExtraFieldsToRequest(ResourceRequest& request, FrameLoadType loadType, bool isMainResource) +{ + // Don't set the cookie policy URL if it's already been set. + // But make sure to set it on all requests regardless of protocol, as it has significance beyond the cookie policy (<rdar://problem/6616664>). + if (request.firstPartyForCookies().isEmpty()) { + if (isMainResource && m_frame.isMainFrame()) + request.setFirstPartyForCookies(request.url()); + else if (Document* document = m_frame.document()) + request.setFirstPartyForCookies(document->firstPartyForCookies()); + } + + Page* page = frame().page(); + bool hasSpecificCachePolicy = request.cachePolicy() != UseProtocolCachePolicy; + + if (page && page->isResourceCachingDisabled()) { request.setCachePolicy(ReloadIgnoringCacheData); + loadType = FrameLoadType::ReloadFromOrigin; + } else if (!hasSpecificCachePolicy) + request.setCachePolicy(defaultRequestCachingPolicy(request, loadType, isMainResource)); + + // The remaining modifications are only necessary for HTTP and HTTPS. + if (!request.url().isEmpty() && !request.url().protocolIsInHTTPFamily()) + return; - if (request.cachePolicy() == ReloadIgnoringCacheData) { - if (loadType == FrameLoadTypeReload) - request.setHTTPHeaderField("Cache-Control", "max-age=0"); - else if (loadType == FrameLoadTypeReloadFromOrigin) { - request.setHTTPHeaderField("Cache-Control", "no-cache"); - request.setHTTPHeaderField("Pragma", "no-cache"); + if (!hasSpecificCachePolicy && request.cachePolicy() == ReloadIgnoringCacheData) { + if (loadType == FrameLoadType::Reload) + request.setHTTPHeaderField(HTTPHeaderName::CacheControl, "max-age=0"); + else if (loadType == FrameLoadType::ReloadFromOrigin) { + request.setHTTPHeaderField(HTTPHeaderName::CacheControl, "no-cache"); + request.setHTTPHeaderField(HTTPHeaderName::Pragma, "no-cache"); } } - - if (mainResource) + + if (m_overrideResourceLoadPriorityForTesting) + request.setPriority(m_overrideResourceLoadPriorityForTesting.value()); + + applyUserAgent(request); + + if (isMainResource) request.setHTTPAccept(defaultAcceptHeader); // Make sure we send the Origin header. @@ -2638,16 +2722,25 @@ void FrameLoader::addHTTPOriginIfNeeded(ResourceRequest& request, const String& request.setHTTPOrigin(origin); } -void FrameLoader::loadPostRequest(const ResourceRequest& inRequest, const String& referrer, const String& frameName, bool lockHistory, FrameLoadType loadType, PassRefPtr<Event> event, PassRefPtr<FormState> prpFormState) +void FrameLoader::addHTTPUpgradeInsecureRequestsIfNeeded(ResourceRequest& request) +{ + if (request.url().protocolIs("https")) { + // FIXME: Identify HSTS cases and avoid adding the header. <https://bugs.webkit.org/show_bug.cgi?id=157885> + return; + } + + request.setHTTPHeaderField(HTTPHeaderName::UpgradeInsecureRequests, ASCIILiteral("1")); +} + +void FrameLoader::loadPostRequest(const FrameLoadRequest& request, const String& referrer, FrameLoadType loadType, Event* event, FormState* formState) { - RefPtr<FormState> formState = prpFormState; + String frameName = request.frameName(); + LockHistory lockHistory = request.lockHistory(); + AllowNavigationToInvalidURL allowNavigationToInvalidURL = request.allowNavigationToInvalidURL(); + NewFrameOpenerPolicy openerPolicy = request.newFrameOpenerPolicy(); - // Previously when this method was reached, the original FrameLoadRequest had been deconstructed to build a - // bunch of parameters that would come in here and then be built back up to a ResourceRequest. In case - // any caller depends on the immutability of the original ResourceRequest, I'm rebuilding a ResourceRequest - // from scratch as it did all along. + const ResourceRequest& inRequest = request.resourceRequest(); const URL& url = inRequest.url(); - RefPtr<FormData> formData = inRequest.httpBody(); const String& contentType = inRequest.httpContentType(); String origin = inRequest.httpOrigin(); @@ -2657,28 +2750,31 @@ void FrameLoader::loadPostRequest(const ResourceRequest& inRequest, const String workingResourceRequest.setHTTPReferrer(referrer); workingResourceRequest.setHTTPOrigin(origin); workingResourceRequest.setHTTPMethod("POST"); - workingResourceRequest.setHTTPBody(formData); + workingResourceRequest.setHTTPBody(inRequest.httpBody()); workingResourceRequest.setHTTPContentType(contentType); addExtraFieldsToRequest(workingResourceRequest, loadType, true); - NavigationAction action(workingResourceRequest, loadType, true, event); + if (Document* document = m_frame.document()) + document->contentSecurityPolicy()->upgradeInsecureRequestIfNeeded(workingResourceRequest, ContentSecurityPolicy::InsecureRequestType::Load); + + NavigationAction action(workingResourceRequest, loadType, true, event, request.shouldOpenExternalURLsPolicy(), request.downloadAttribute()); if (!frameName.isEmpty()) { // The search for a target frame is done earlier in the case of form submission. if (Frame* targetFrame = formState ? 0 : findFrameForNavigation(frameName)) { - targetFrame->loader().loadWithNavigationAction(workingResourceRequest, action, lockHistory, loadType, formState.release()); + targetFrame->loader().loadWithNavigationAction(workingResourceRequest, action, lockHistory, loadType, WTFMove(formState), allowNavigationToInvalidURL); return; } - policyChecker().checkNewWindowPolicy(action, workingResourceRequest, formState.release(), frameName, [this](const ResourceRequest& request, PassRefPtr<FormState> formState, const String& frameName, const NavigationAction& action, bool shouldContinue) { - continueLoadAfterNewWindowPolicy(request, formState, frameName, action, shouldContinue); + policyChecker().checkNewWindowPolicy(action, workingResourceRequest, WTFMove(formState), frameName, [this, allowNavigationToInvalidURL, openerPolicy] (const ResourceRequest& request, FormState* formState, const String& frameName, const NavigationAction& action, bool shouldContinue) { + continueLoadAfterNewWindowPolicy(request, formState, frameName, action, shouldContinue, allowNavigationToInvalidURL, openerPolicy); }); return; } // must grab this now, since this load may stop the previous load and clear this flag bool isRedirect = m_quickRedirectComing; - loadWithNavigationAction(workingResourceRequest, action, lockHistory, loadType, formState.release()); + loadWithNavigationAction(workingResourceRequest, action, lockHistory, loadType, WTFMove(formState), allowNavigationToInvalidURL); if (isRedirect) { m_quickRedirectComing = false; if (m_provisionalDocumentLoader) @@ -2686,7 +2782,7 @@ void FrameLoader::loadPostRequest(const ResourceRequest& inRequest, const String } } -unsigned long FrameLoader::loadResourceSynchronously(const ResourceRequest& request, StoredCredentials storedCredentials, ClientCredentialPolicy clientCredentialPolicy, ResourceError& error, ResourceResponse& response, Vector<char>& data) +unsigned long FrameLoader::loadResourceSynchronously(const ResourceRequest& request, StoredCredentials storedCredentials, ClientCredentialPolicy clientCredentialPolicy, ResourceError& error, ResourceResponse& response, RefPtr<SharedBuffer>& data) { ASSERT(m_frame.document()); String referrer = SecurityPolicy::generateReferrerHeader(m_frame.document()->referrerPolicy(), request.url(), outgoingReferrer()); @@ -2706,15 +2802,37 @@ unsigned long FrameLoader::loadResourceSynchronously(const ResourceRequest& requ ResourceRequest newRequest(initialRequest); requestFromDelegate(newRequest, identifier, error); +#if ENABLE(CONTENT_EXTENSIONS) + if (error.isNull()) { + if (auto* page = m_frame.page()) { + if (m_documentLoader) { + auto blockedStatus = page->userContentProvider().processContentExtensionRulesForLoad(newRequest.url(), ResourceType::Raw, *m_documentLoader); + applyBlockedStatusToRequest(blockedStatus, newRequest); + if (blockedStatus.blockedLoad) { + newRequest = { }; + error = ResourceError(errorDomainWebKitInternal, 0, initialRequest.url(), emptyString()); + response = { }; + data = nullptr; + } + } + } + } +#endif + + m_frame.document()->contentSecurityPolicy()->upgradeInsecureRequestIfNeeded(newRequest, ContentSecurityPolicy::InsecureRequestType::Load); + if (error.isNull()) { ASSERT(!newRequest.isNull()); - - if (!documentLoader()->applicationCacheHost()->maybeLoadSynchronously(newRequest, error, response, data)) { - platformStrategies()->loaderStrategy()->loadResourceSynchronously(networkingContext(), identifier, newRequest, storedCredentials, clientCredentialPolicy, error, response, data); - documentLoader()->applicationCacheHost()->maybeLoadFallbackSynchronously(newRequest, error, response, data); + + if (!documentLoader()->applicationCacheHost().maybeLoadSynchronously(newRequest, error, response, data)) { + Vector<char> buffer; + platformStrategies()->loaderStrategy()->loadResourceSynchronously(networkingContext(), identifier, newRequest, storedCredentials, clientCredentialPolicy, error, response, buffer); + data = SharedBuffer::adoptVector(buffer); + documentLoader()->applicationCacheHost().maybeLoadFallbackSynchronously(newRequest, error, response, data); + ResourceLoadObserver::sharedObserver().logSubresourceLoading(&m_frame, newRequest, response); } } - notifier().sendRemainingDelegateMessages(m_documentLoader.get(), identifier, request, response, data.data(), data.size(), -1, error); + notifier().sendRemainingDelegateMessages(m_documentLoader.get(), identifier, request, response, data ? data->data() : nullptr, data ? data->size() : 0, -1, error); return identifier; } @@ -2765,13 +2883,17 @@ void FrameLoader::continueFragmentScrollAfterNavigationPolicy(const ResourceRequ if (!shouldContinue) return; + // Calling stopLoading() on the provisional document loader can cause the underlying + // frame to be deallocated. + Ref<Frame> protectedFrame(m_frame); + // If we have a provisional request for a different document, a fragment scroll should cancel it. if (m_provisionalDocumentLoader && !equalIgnoringFragmentIdentifier(m_provisionalDocumentLoader->request().url(), request.url())) { m_provisionalDocumentLoader->stopLoading(); - setProvisionalDocumentLoader(0); + setProvisionalDocumentLoader(nullptr); } - bool isRedirect = m_quickRedirectComing || policyChecker().loadType() == FrameLoadTypeRedirectWithLockedBackForwardList; + bool isRedirect = m_quickRedirectComing || policyChecker().loadType() == FrameLoadType::RedirectWithLockedBackForwardList; loadInSameDocument(request.url(), 0, !isRedirect); } @@ -2783,10 +2905,10 @@ bool FrameLoader::shouldPerformFragmentNavigation(bool isFormSubmission, const S // FIXME: What about load types other than Standard and Reload? - return (!isFormSubmission || equalIgnoringCase(httpMethod, "GET")) - && loadType != FrameLoadTypeReload - && loadType != FrameLoadTypeReloadFromOrigin - && loadType != FrameLoadTypeSame + return (!isFormSubmission || equalLettersIgnoringASCIICase(httpMethod, "get")) + && loadType != FrameLoadType::Reload + && loadType != FrameLoadType::ReloadFromOrigin + && loadType != FrameLoadType::Same && !shouldReload(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. @@ -2827,13 +2949,13 @@ bool FrameLoader::shouldClose() bool shouldClose = false; { - NavigationDisablerForBeforeUnload navigationDisabler; + NavigationDisabler navigationDisabler; size_t i; for (i = 0; i < targetFrames.size(); i++) { if (!targetFrames[i]->tree().isDescendantOf(&m_frame)) continue; - if (!targetFrames[i]->loader().handleBeforeUnloadEvent(page->chrome(), this)) + if (!targetFrames[i]->loader().dispatchBeforeUnloadEvent(page->chrome(), this)) break; } @@ -2848,36 +2970,105 @@ bool FrameLoader::shouldClose() return shouldClose; } -bool FrameLoader::handleBeforeUnloadEvent(Chrome& chrome, FrameLoader* frameLoaderBeingNavigated) +void FrameLoader::dispatchUnloadEvents(UnloadEventPolicy unloadEventPolicy) +{ + if (!m_frame.document()) + return; + + // We store the frame's page in a local variable because the frame might get detached inside dispatchEvent. + ForbidPromptsScope forbidPrompts(m_frame.page()); + IgnoreOpensDuringUnloadCountIncrementer ignoreOpensDuringUnloadCountIncrementer(m_frame.document()); + + if (m_didCallImplicitClose && !m_wasUnloadEventEmitted) { + auto* currentFocusedElement = m_frame.document()->focusedElement(); + if (is<HTMLInputElement>(currentFocusedElement)) + downcast<HTMLInputElement>(*currentFocusedElement).endEditing(); + if (m_pageDismissalEventBeingDispatched == PageDismissalType::None) { + if (unloadEventPolicy == UnloadEventPolicyUnloadAndPageHide) { + m_pageDismissalEventBeingDispatched = PageDismissalType::PageHide; + m_frame.document()->domWindow()->dispatchEvent(PageTransitionEvent::create(eventNames().pagehideEvent, m_frame.document()->pageCacheState() == Document::AboutToEnterPageCache), m_frame.document()); + } + + // FIXME: update Page Visibility state here. + // https://bugs.webkit.org/show_bug.cgi?id=116770 + + if (m_frame.document()->pageCacheState() == Document::NotInPageCache) { + Ref<Event> unloadEvent(Event::create(eventNames().unloadEvent, false, false)); + // The DocumentLoader (and thus its LoadTiming) might get destroyed + // while dispatching the event, so protect it to prevent writing the end + // time into freed memory. + RefPtr<DocumentLoader> documentLoader = m_provisionalDocumentLoader; + m_pageDismissalEventBeingDispatched = PageDismissalType::Unload; + if (documentLoader && documentLoader->timing().startTime() && !documentLoader->timing().unloadEventStart() && !documentLoader->timing().unloadEventEnd()) { + auto& timing = documentLoader->timing(); + timing.markUnloadEventStart(); + m_frame.document()->domWindow()->dispatchEvent(unloadEvent, m_frame.document()); + timing.markUnloadEventEnd(); + } else + m_frame.document()->domWindow()->dispatchEvent(unloadEvent, m_frame.document()); + } + } + m_pageDismissalEventBeingDispatched = PageDismissalType::None; + m_wasUnloadEventEmitted = true; + } + + // Dispatching the unload event could have made m_frame.document() null. + if (!m_frame.document()) + return; + + if (m_frame.document()->pageCacheState() != Document::NotInPageCache) + return; + + // Don't remove event listeners from a transitional empty document (see bug 28716 for more information). + bool keepEventListeners = m_stateMachine.isDisplayingInitialEmptyDocument() && m_provisionalDocumentLoader + && m_frame.document()->isSecureTransitionTo(m_provisionalDocumentLoader->url()); + + if (!keepEventListeners) + m_frame.document()->removeAllEventListeners(); +} + +static bool shouldAskForNavigationConfirmation(Document& document, const BeforeUnloadEvent& event) +{ + bool userDidInteractWithPage = document.topDocument().userDidInteractWithPage(); + // Web pages can request we ask for confirmation before navigating by: + // - Cancelling the BeforeUnloadEvent (modern way) + // - Setting the returnValue attribute on the BeforeUnloadEvent to a non-empty string. + // - Returning a non-empty string from the event handler, which is then set as returnValue + // attribute on the BeforeUnloadEvent. + return userDidInteractWithPage && (event.defaultPrevented() || !event.returnValue().isEmpty()); +} + +bool FrameLoader::dispatchBeforeUnloadEvent(Chrome& chrome, FrameLoader* frameLoaderBeingNavigated) { DOMWindow* domWindow = m_frame.document()->domWindow(); if (!domWindow) return true; RefPtr<Document> document = m_frame.document(); - if (!document->body()) + if (!document->bodyOrFrameset()) return true; - RefPtr<BeforeUnloadEvent> beforeUnloadEvent = BeforeUnloadEvent::create(); - m_pageDismissalEventBeingDispatched = BeforeUnloadDismissal; + Ref<BeforeUnloadEvent> beforeUnloadEvent = BeforeUnloadEvent::create(); + m_pageDismissalEventBeingDispatched = PageDismissalType::BeforeUnload; - // We store the frame's page in a local variable because the frame might get detached inside dispatchEvent. - Page* page = m_frame.page(); - page->incrementFrameHandlingBeforeUnloadEventCount(); - domWindow->dispatchEvent(beforeUnloadEvent.get(), domWindow->document()); - page->decrementFrameHandlingBeforeUnloadEventCount(); + { + ForbidPromptsScope forbidPrompts(m_frame.page()); + IgnoreOpensDuringUnloadCountIncrementer ignoreOpensDuringUnloadCountIncrementer(m_frame.document()); + domWindow->dispatchEvent(beforeUnloadEvent, domWindow->document()); + } - m_pageDismissalEventBeingDispatched = NoDismissal; + m_pageDismissalEventBeingDispatched = PageDismissalType::None; if (!beforeUnloadEvent->defaultPrevented()) document->defaultEventHandler(beforeUnloadEvent.get()); - if (beforeUnloadEvent->returnValue().isNull()) + + if (!shouldAskForNavigationConfirmation(*document, beforeUnloadEvent)) return true; // If the navigating FrameLoader has already shown a beforeunload confirmation panel for the current navigation attempt, // this frame is not allowed to cause another one to be shown. if (frameLoaderBeingNavigated->m_currentNavigationHasShownBeforeUnloadConfirmPanel) { - document->addConsoleMessage(JSMessageSource, ErrorMessageLevel, "Blocked attempt to show multiple beforeunload confirmation dialogs for the same navigation."); + document->addConsoleMessage(MessageSource::JS, MessageLevel::Error, ASCIILiteral("Blocked attempt to show multiple beforeunload confirmation dialogs for the same navigation.")); return true; } @@ -2889,8 +3080,8 @@ bool FrameLoader::handleBeforeUnloadEvent(Chrome& chrome, FrameLoader* frameLoad Document* parentDocument = parentFrame->document(); if (!parentDocument) return true; - if (!m_frame.document() || !m_frame.document()->securityOrigin()->canAccess(parentDocument->securityOrigin())) { - document->addConsoleMessage(JSMessageSource, ErrorMessageLevel, "Blocked attempt to show beforeunload confirmation dialog on behalf of a frame with different security origin. Protocols, domains, and ports must match."); + if (!m_frame.document() || !m_frame.document()->securityOrigin().canAccess(parentDocument->securityOrigin())) { + document->addConsoleMessage(MessageSource::JS, MessageLevel::Error, ASCIILiteral("Blocked attempt to show beforeunload confirmation dialog on behalf of a frame with different security origin. Protocols, domains, and ports must match.")); return true; } @@ -2908,10 +3099,10 @@ bool FrameLoader::handleBeforeUnloadEvent(Chrome& chrome, FrameLoader* frameLoad frameLoaderBeingNavigated->m_currentNavigationHasShownBeforeUnloadConfirmPanel = true; String text = document->displayStringModifiedByEncoding(beforeUnloadEvent->returnValue()); - return chrome.runBeforeUnloadConfirmPanel(text, &m_frame); + return chrome.runBeforeUnloadConfirmPanel(text, m_frame); } -void FrameLoader::continueLoadAfterNavigationPolicy(const ResourceRequest&, PassRefPtr<FormState> formState, bool shouldContinue) +void FrameLoader::continueLoadAfterNavigationPolicy(const ResourceRequest& request, FormState* formState, bool shouldContinue, AllowNavigationToInvalidURL allowNavigationToInvalidURL) { // If we loaded an alternate page to replace an unreachableURL, we'll get in here with a // nil policyDataSource because loading the alternate page will have passed @@ -2920,19 +3111,23 @@ void FrameLoader::continueLoadAfterNavigationPolicy(const ResourceRequest&, Pass bool isTargetItem = history().provisionalItem() ? history().provisionalItem()->isTargetItem() : false; - // Two reasons we can't continue: + bool urlIsDisallowed = allowNavigationToInvalidURL == AllowNavigationToInvalidURL::No && !request.url().isValid(); + + // Three reasons we can't continue: // 1) Navigation policy delegate said we can't so request is nil. A primary case of this // is the user responding Cancel to the form repost nag sheet. // 2) User responded Cancel to an alert popped up by the before unload event handler. - bool canContinue = shouldContinue && shouldClose(); + // 3) The request's URL is invalid and navigation to invalid URLs is disallowed. + bool canContinue = shouldContinue && shouldClose() && !urlIsDisallowed; if (!canContinue) { // If we were waiting for a quick redirect, but the policy delegate decided to ignore it, then we // need to report that the client redirect was cancelled. + // FIXME: The client should be told about ignored non-quick redirects, too. if (m_quickRedirectComing) clientRedirectCancelledOrFinished(false); - setPolicyDocumentLoader(0); + setPolicyDocumentLoader(nullptr); // If the navigation request came from the back/forward menu, and we punt on it, we have the // problem that we have optimistically moved the b/f cursor already, so move it back. For sanity, @@ -2957,22 +3152,20 @@ void FrameLoader::continueLoadAfterNavigationPolicy(const ResourceRequest&, Pass if (!m_frame.page()) return; -#if ENABLE(INSPECTOR) - if (Page* page = m_frame.page()) { - if (m_frame.isMainFrame()) - page->inspectorController().resume(); - } -#endif - setProvisionalDocumentLoader(m_policyDocumentLoader.get()); m_loadType = type; setState(FrameStateProvisional); - setPolicyDocumentLoader(0); + setPolicyDocumentLoader(nullptr); - if (isBackForwardLoadType(type) && history().provisionalItem()->isInPageCache()) { - loadProvisionalItemFromCachedPage(); - return; + if (isBackForwardLoadType(type)) { + auto& diagnosticLoggingClient = m_frame.page()->diagnosticLoggingClient(); + if (history().provisionalItem()->isInPageCache()) { + diagnosticLoggingClient.logDiagnosticMessageWithResult(DiagnosticLoggingKeys::pageCacheKey(), DiagnosticLoggingKeys::retrievalKey(), DiagnosticLoggingResultPass, ShouldSample::Yes); + loadProvisionalItemFromCachedPage(); + return; + } + diagnosticLoggingClient.logDiagnosticMessageWithResult(DiagnosticLoggingKeys::pageCacheKey(), DiagnosticLoggingKeys::retrievalKey(), DiagnosticLoggingResultFail, ShouldSample::Yes); } if (!formState) { @@ -2980,13 +3173,13 @@ void FrameLoader::continueLoadAfterNavigationPolicy(const ResourceRequest&, Pass return; } - m_client.dispatchWillSubmitForm(formState, [this](PolicyAction action) { + m_client.dispatchWillSubmitForm(*formState, [this] (PolicyAction action) { policyChecker().continueLoadAfterWillSubmitForm(action); }); } void FrameLoader::continueLoadAfterNewWindowPolicy(const ResourceRequest& request, - PassRefPtr<FormState> formState, const String& frameName, const NavigationAction& action, bool shouldContinue) + FormState* formState, const String& frameName, const NavigationAction& action, bool shouldContinue, AllowNavigationToInvalidURL allowNavigationToInvalidURL, NewFrameOpenerPolicy openerPolicy) { if (!shouldContinue) return; @@ -2996,16 +3189,20 @@ void FrameLoader::continueLoadAfterNewWindowPolicy(const ResourceRequest& reques if (!mainFrame) return; + mainFrame->loader().forceSandboxFlags(frame->loader().effectiveSandboxFlags()); + if (frameName != "_blank") mainFrame->tree().setName(frameName); mainFrame->page()->setOpenedByDOM(); mainFrame->loader().m_client.dispatchShow(); - if (!m_suppressOpenerInNewFrame) { - mainFrame->loader().setOpener(&frame.get()); + if (openerPolicy == NewFrameOpenerPolicy::Allow) { + mainFrame->loader().setOpener(frame.ptr()); mainFrame->document()->setReferrerPolicy(frame->document()->referrerPolicy()); } - mainFrame->loader().loadWithNavigationAction(request, NavigationAction(request), false, FrameLoadTypeStandard, formState); + + NavigationAction newAction(request, action.shouldOpenExternalURLsPolicy()); + mainFrame->loader().loadWithNavigationAction(request, newAction, LockHistory::No, FrameLoadType::Standard, formState, allowNavigationToInvalidURL); } void FrameLoader::requestFromDelegate(ResourceRequest& request, unsigned long& identifier, ResourceError& error) @@ -3031,8 +3228,6 @@ void FrameLoader::requestFromDelegate(ResourceRequest& request, unsigned long& i void FrameLoader::loadedResourceFromMemoryCache(CachedResource* resource, ResourceRequest& newRequest) { - newRequest = ResourceRequest(resource->url()); - Page* page = m_frame.page(); if (!page) return; @@ -3045,14 +3240,14 @@ void FrameLoader::loadedResourceFromMemoryCache(CachedResource* resource, Resour return; if (!page->areMemoryCacheClientCallsEnabled()) { - InspectorInstrumentation::didLoadResourceFromMemoryCache(page, m_documentLoader.get(), resource); + InspectorInstrumentation::didLoadResourceFromMemoryCache(*page, m_documentLoader.get(), resource); m_documentLoader->recordMemoryCacheLoadForFutureClientNotification(resource->resourceRequest()); m_documentLoader->didTellClientAboutLoad(resource->url()); return; } if (m_client.dispatchDidLoadResourceFromMemoryCache(m_documentLoader.get(), newRequest, resource->response(), resource->encodedSize())) { - InspectorInstrumentation::didLoadResourceFromMemoryCache(page, m_documentLoader.get(), resource); + InspectorInstrumentation::didLoadResourceFromMemoryCache(*page, m_documentLoader.get(), resource); m_documentLoader->didTellClientAboutLoad(resource->url()); return; } @@ -3060,7 +3255,7 @@ void FrameLoader::loadedResourceFromMemoryCache(CachedResource* resource, Resour unsigned long identifier; ResourceError error; requestFromDelegate(newRequest, identifier, error); - InspectorInstrumentation::markResourceAsCached(page, identifier); + InspectorInstrumentation::markResourceAsCached(*page, identifier); notifier().sendRemainingDelegateMessages(m_documentLoader.get(), identifier, newRequest, resource->response(), 0, resource->encodedSize(), 0, error); } @@ -3073,8 +3268,6 @@ void FrameLoader::applyUserAgent(ResourceRequest& request) bool FrameLoader::shouldInterruptLoadForXFrameOptions(const String& content, const URL& url, unsigned long requestIdentifier) { - FeatureObserver::observe(m_frame.document(), FeatureObserver::XFrameOptions); - Frame& topFrame = m_frame.tree().top(); if (&m_frame == &topFrame) return false; @@ -3083,15 +3276,12 @@ bool FrameLoader::shouldInterruptLoadForXFrameOptions(const String& content, con switch (disposition) { case XFrameOptionsSameOrigin: { - FeatureObserver::observe(m_frame.document(), FeatureObserver::XFrameOptionsSameOrigin); RefPtr<SecurityOrigin> origin = SecurityOrigin::create(url); if (!origin->isSameSchemeHostPort(topFrame.document()->securityOrigin())) return true; for (Frame* frame = m_frame.tree().parent(); frame; frame = frame->tree().parent()) { - if (!origin->isSameSchemeHostPort(frame->document()->securityOrigin())) { - FeatureObserver::observe(m_frame.document(), FeatureObserver::XFrameOptionsSameOriginWithBadAncestorChain); + if (!origin->isSameSchemeHostPort(frame->document()->securityOrigin())) break; - } } return false; } @@ -3100,15 +3290,16 @@ bool FrameLoader::shouldInterruptLoadForXFrameOptions(const String& content, con case XFrameOptionsAllowAll: return false; case XFrameOptionsConflict: - m_frame.document()->addConsoleMessage(JSMessageSource, ErrorMessageLevel, "Multiple 'X-Frame-Options' headers with conflicting values ('" + content + "') encountered when loading '" + url.stringCenterEllipsizedToLength() + "'. Falling back to 'DENY'.", requestIdentifier); + m_frame.document()->addConsoleMessage(MessageSource::JS, MessageLevel::Error, "Multiple 'X-Frame-Options' headers with conflicting values ('" + content + "') encountered when loading '" + url.stringCenterEllipsizedToLength() + "'. Falling back to 'DENY'.", requestIdentifier); return true; case XFrameOptionsInvalid: - m_frame.document()->addConsoleMessage(JSMessageSource, ErrorMessageLevel, "Invalid 'X-Frame-Options' header encountered when loading '" + url.stringCenterEllipsizedToLength() + "': '" + content + "' is not a recognized directive. The header will be ignored.", requestIdentifier); + m_frame.document()->addConsoleMessage(MessageSource::JS, MessageLevel::Error, "Invalid 'X-Frame-Options' header encountered when loading '" + url.stringCenterEllipsizedToLength() + "': '" + content + "' is not a recognized directive. The header will be ignored.", requestIdentifier); return false; - default: - ASSERT_NOT_REACHED(); + case XFrameOptionsNone: return false; } + ASSERT_NOT_REACHED(); + return false; } void FrameLoader::loadProvisionalItemFromCachedPage() @@ -3121,9 +3312,9 @@ void FrameLoader::loadProvisionalItemFromCachedPage() m_loadingFromCachedPage = true; // Should have timing data from previous time(s) the page was shown. - ASSERT(provisionalLoader->timing()->navigationStart()); + ASSERT(provisionalLoader->timing().startTime()); provisionalLoader->resetTiming(); - provisionalLoader->timing()->markNavigationStart(); + provisionalLoader->timing().markStartTime(); provisionalLoader->setCommitted(true); commitProvisionalLoad(); @@ -3138,52 +3329,35 @@ bool FrameLoader::shouldTreatURLAsSameAsCurrent(const URL& url) const bool FrameLoader::shouldTreatURLAsSrcdocDocument(const URL& url) const { - if (!equalIgnoringCase(url.string(), "about:srcdoc")) + if (!equalLettersIgnoringASCIICase(url.string(), "about:srcdoc")) return false; HTMLFrameOwnerElement* ownerElement = m_frame.ownerElement(); if (!ownerElement) return false; if (!ownerElement->hasTagName(iframeTag)) return false; - return ownerElement->fastHasAttribute(srcdocAttr); + return ownerElement->hasAttributeWithoutSynchronization(srcdocAttr); } Frame* FrameLoader::findFrameForNavigation(const AtomicString& name, Document* activeDocument) { Frame* frame = m_frame.tree().find(name); - // From http://www.whatwg.org/specs/web-apps/current-work/#seamlessLinks: - // - // If the source browsing context is the same as the browsing context - // being navigated, and this browsing context has its seamless browsing - // context flag set, and the browsing context being navigated was not - // chosen using an explicit self-navigation override, then find the - // nearest ancestor browsing context that does not have its seamless - // browsing context flag set, and continue these steps as if that - // browsing context was the one that was going to be navigated instead. - if (frame == &m_frame && name != "_self" && m_frame.document()->shouldDisplaySeamlesslyWithParent()) { - for (Frame* ancestor = &m_frame; ancestor; ancestor = ancestor->tree().parent()) { - if (!ancestor->document()->shouldDisplaySeamlesslyWithParent()) { - frame = ancestor; - break; - } - } - ASSERT(frame != &m_frame); - } - // FIXME: Eventually all callers should supply the actual activeDocument so we can call canNavigate with the right document. if (!activeDocument) activeDocument = m_frame.document(); if (!activeDocument->canNavigate(frame)) - return 0; + return nullptr; return frame; } -void FrameLoader::loadSameDocumentItem(HistoryItem* item) +void FrameLoader::loadSameDocumentItem(HistoryItem& item) { - ASSERT(item->documentSequenceNumber() == history().currentItem()->documentSequenceNumber()); + ASSERT(item.documentSequenceNumber() == history().currentItem()->documentSequenceNumber()); + + Ref<Frame> protect(m_frame); // Save user view state to the current history item here since we don't do a normal load. // FIXME: Does form state need to be saved here too? @@ -3191,10 +3365,10 @@ void FrameLoader::loadSameDocumentItem(HistoryItem* item) if (FrameView* view = m_frame.view()) view->setWasScrolledByUser(false); - history().setCurrentItem(item); + history().setCurrentItem(&item); // loadInSameDocument() actually changes the URL and notifies load delegates of a "fake" load - loadInSameDocument(item->url(), item->stateObject(), false); + loadInSameDocument(item.url(), item.stateObject(), false); // Restore user view state from the current history item here since we don't do a normal load. history().restoreScrollPositionAndViewState(); @@ -3203,38 +3377,47 @@ void FrameLoader::loadSameDocumentItem(HistoryItem* item) // FIXME: This function should really be split into a couple pieces, some of // which should be methods of HistoryController and some of which should be // methods of FrameLoader. -void FrameLoader::loadDifferentDocumentItem(HistoryItem* item, FrameLoadType loadType, FormSubmissionCacheLoadPolicy cacheLoadPolicy) +void FrameLoader::loadDifferentDocumentItem(HistoryItem& item, FrameLoadType loadType, FormSubmissionCacheLoadPolicy cacheLoadPolicy) { // Remember this item so we can traverse any child items as child frames load - history().setProvisionalItem(item); + history().setProvisionalItem(&item); - if (CachedPage* cachedPage = pageCache()->get(item)) { - loadWithDocumentLoader(cachedPage->documentLoader(), loadType, 0); + if (CachedPage* cachedPage = PageCache::singleton().get(item, m_frame.page())) { + auto documentLoader = cachedPage->documentLoader(); + m_client.updateCachedDocumentLoader(*documentLoader); + documentLoader->setTriggeringAction(NavigationAction(documentLoader->request(), loadType, false)); + documentLoader->setLastCheckedRequest(ResourceRequest()); + loadWithDocumentLoader(documentLoader, loadType, 0, AllowNavigationToInvalidURL::Yes); return; } - URL itemURL = item->url(); - URL itemOriginalURL = item->originalURL(); + URL itemURL = item.url(); + URL itemOriginalURL = item.originalURL(); URL currentURL; if (documentLoader()) currentURL = documentLoader()->url(); - RefPtr<FormData> formData = item->formData(); + RefPtr<FormData> formData = item.formData(); ResourceRequest request(itemURL); - if (!item->referrer().isNull()) - request.setHTTPReferrer(item->referrer()); - + if (!item.referrer().isNull()) + request.setHTTPReferrer(item.referrer()); + + ShouldOpenExternalURLsPolicy shouldOpenExternalURLsPolicy = shouldOpenExternalURLsPolicyToApply(m_frame, item.shouldOpenExternalURLsPolicy()); + bool isFormSubmission = false; + Event* event = nullptr; + // If this was a repost that failed the page cache, we might try to repost the form. NavigationAction action; if (formData) { formData->generateFiles(m_frame.document()); request.setHTTPMethod("POST"); - request.setHTTPBody(formData); - request.setHTTPContentType(item->formContentType()); - RefPtr<SecurityOrigin> securityOrigin = SecurityOrigin::createFromString(item->referrer()); + request.setHTTPBody(WTFMove(formData)); + request.setHTTPContentType(item.formContentType()); + RefPtr<SecurityOrigin> securityOrigin = SecurityOrigin::createFromString(item.referrer()); addHTTPOriginIfNeeded(request, securityOrigin->toString()); + addHTTPUpgradeInsecureRequestsIfNeeded(request); // Make sure to add extra fields to the request after the Origin header is added for the FormData case. // See https://bugs.webkit.org/show_bug.cgi?id=22194 for more discussion. @@ -3250,49 +3433,54 @@ void FrameLoader::loadDifferentDocumentItem(HistoryItem* item, FrameLoadType loa if (cacheLoadPolicy == MayAttemptCacheOnlyLoadForFormSubmissionItem) { request.setCachePolicy(ReturnCacheDataDontLoad); - action = NavigationAction(request, loadType, false); + action = NavigationAction(request, loadType, isFormSubmission, event, shouldOpenExternalURLsPolicy); } else { request.setCachePolicy(ReturnCacheDataElseLoad); - action = NavigationAction(request, NavigationTypeFormResubmitted); + action = NavigationAction(request, NavigationType::FormResubmitted, event, shouldOpenExternalURLsPolicy); } } else { switch (loadType) { - case FrameLoadTypeReload: - case FrameLoadTypeReloadFromOrigin: - request.setCachePolicy(ReloadIgnoringCacheData); - break; - case FrameLoadTypeBack: - case FrameLoadTypeForward: - case FrameLoadTypeIndexedBackForward: - // If the first load within a frame is a navigation within a back/forward list that was attached - // without any of the items being loaded then we should use the default caching policy (<rdar://problem/8131355>). - if (m_stateMachine.committedFirstRealDocumentLoad()) - request.setCachePolicy(ReturnCacheDataElseLoad); - break; - case FrameLoadTypeStandard: - case FrameLoadTypeRedirectWithLockedBackForwardList: - break; - case FrameLoadTypeSame: - default: - ASSERT_NOT_REACHED(); + case FrameLoadType::Reload: + case FrameLoadType::ReloadFromOrigin: + request.setCachePolicy(ReloadIgnoringCacheData); + break; + case FrameLoadType::Back: + case FrameLoadType::Forward: + case FrameLoadType::IndexedBackForward: { +#if PLATFORM(COCOA) + bool allowStaleData = true; +#else + bool allowStaleData = !item.wasRestoredFromSession(); +#endif + if (allowStaleData) + request.setCachePolicy(ReturnCacheDataElseLoad); + item.setWasRestoredFromSession(false); + break; + } + case FrameLoadType::Standard: + case FrameLoadType::RedirectWithLockedBackForwardList: + break; + case FrameLoadType::Same: + default: + ASSERT_NOT_REACHED(); } addExtraFieldsToRequest(request, loadType, true); ResourceRequest requestForOriginalURL(request); requestForOriginalURL.setURL(itemOriginalURL); - action = NavigationAction(requestForOriginalURL, loadType, false); + action = NavigationAction(requestForOriginalURL, loadType, isFormSubmission, event, shouldOpenExternalURLsPolicy); } - loadWithNavigationAction(request, action, false, loadType, 0); + loadWithNavigationAction(request, action, LockHistory::No, loadType, 0, AllowNavigationToInvalidURL::Yes); } // Loads content into this frame, as specified by history item -void FrameLoader::loadItem(HistoryItem* item, FrameLoadType loadType) +void FrameLoader::loadItem(HistoryItem& item, FrameLoadType loadType) { - m_requestedHistoryItem = item; + m_requestedHistoryItem = &item; HistoryItem* currentItem = history().currentItem(); - bool sameDocumentNavigation = currentItem && item->shouldDoSameDocumentNavigationTo(currentItem); + bool sameDocumentNavigation = currentItem && item.shouldDoSameDocumentNavigationTo(*currentItem); if (sameDocumentNavigation) loadSameDocumentItem(item); @@ -3304,13 +3492,12 @@ void FrameLoader::retryAfterFailedCacheOnlyMainResourceLoad() { ASSERT(m_state == FrameStateProvisional); ASSERT(!m_loadingFromCachedPage); - // We only use cache-only loads to avoid resubmitting forms. - ASSERT(isBackForwardLoadType(m_loadType)); + ASSERT(history().provisionalItem()); ASSERT(history().provisionalItem()->formData()); ASSERT(history().provisionalItem() == m_requestedHistoryItem.get()); FrameLoadType loadType = m_loadType; - HistoryItem* item = history().provisionalItem(); + HistoryItem& item = *history().provisionalItem(); stopAllLoaders(ShouldNotClearProvisionalItem); loadDifferentDocumentItem(item, loadType, MayNotAttemptCacheOnlyLoadForFormSubmissionItem); @@ -3319,10 +3506,31 @@ void FrameLoader::retryAfterFailedCacheOnlyMainResourceLoad() ResourceError FrameLoader::cancelledError(const ResourceRequest& request) const { ResourceError error = m_client.cancelledError(request); - error.setIsCancellation(true); + error.setType(ResourceError::Type::Cancellation); + return error; +} + +ResourceError FrameLoader::blockedByContentBlockerError(const ResourceRequest& request) const +{ + return m_client.blockedByContentBlockerError(request); +} + +ResourceError FrameLoader::blockedError(const ResourceRequest& request) const +{ + ResourceError error = m_client.blockedError(request); + error.setType(ResourceError::Type::Cancellation); return error; } +#if ENABLE(CONTENT_FILTERING) +ResourceError FrameLoader::blockedByContentFilterError(const ResourceRequest& request) const +{ + ResourceError error = m_client.blockedByContentFilterError(request); + error.setType(ResourceError::Type::General); + return error; +} +#endif + #if PLATFORM(IOS) RetainPtr<CFDictionaryRef> FrameLoader::connectionProperties(ResourceLoader* loader) { @@ -3332,7 +3540,7 @@ RetainPtr<CFDictionaryRef> FrameLoader::connectionProperties(ResourceLoader* loa String FrameLoader::referrer() const { - return m_documentLoader ? m_documentLoader->request().httpReferrer() : ""; + return m_documentLoader ? m_documentLoader->request().httpReferrer() : emptyString(); } void FrameLoader::dispatchDidClearWindowObjectsInAllWorlds() @@ -3342,8 +3550,8 @@ void FrameLoader::dispatchDidClearWindowObjectsInAllWorlds() Vector<Ref<DOMWrapperWorld>> worlds; ScriptController::getAllWorlds(worlds); - for (size_t i = 0; i < worlds.size(); ++i) - dispatchDidClearWindowObjectInWorld(worlds[i].get()); + for (auto& world : worlds) + dispatchDidClearWindowObjectInWorld(world); } void FrameLoader::dispatchDidClearWindowObjectInWorld(DOMWrapperWorld& world) @@ -3353,20 +3561,18 @@ void FrameLoader::dispatchDidClearWindowObjectInWorld(DOMWrapperWorld& world) m_client.dispatchDidClearWindowObjectInWorld(world); -#if ENABLE(INSPECTOR) if (Page* page = m_frame.page()) - page->inspectorController().didClearWindowObjectInWorld(&m_frame, world); -#endif + page->inspectorController().didClearWindowObjectInWorld(m_frame, world); - InspectorInstrumentation::didClearWindowObjectInWorld(&m_frame, world); + InspectorInstrumentation::didClearWindowObjectInWorld(m_frame, world); } void FrameLoader::dispatchGlobalObjectAvailableInAllWorlds() { Vector<Ref<DOMWrapperWorld>> worlds; ScriptController::getAllWorlds(worlds); - for (size_t i = 0; i < worlds.size(); ++i) - m_client.dispatchGlobalObjectAvailable(worlds[i].get()); + for (auto& world : worlds) + m_client.dispatchGlobalObjectAvailable(world); } SandboxFlags FrameLoader::effectiveSandboxFlags() const @@ -3398,31 +3604,24 @@ void FrameLoader::didChangeTitle(DocumentLoader* loader) #endif } -void FrameLoader::didChangeIcons(IconType type) -{ - m_client.dispatchDidChangeIcons(type); -} - -void FrameLoader::dispatchDidCommitLoad() +void FrameLoader::dispatchDidCommitLoad(std::optional<HasInsecureContent> initialHasInsecureContent) { if (m_stateMachine.creatingInitialEmptyDocument()) return; - m_client.dispatchDidCommitLoad(); + m_client.dispatchDidCommitLoad(initialHasInsecureContent); if (m_frame.isMainFrame()) { m_frame.page()->resetSeenPlugins(); m_frame.page()->resetSeenMediaEngines(); } - InspectorInstrumentation::didCommitLoad(&m_frame, m_documentLoader.get()); + InspectorInstrumentation::didCommitLoad(m_frame, m_documentLoader.get()); - if (m_frame.isMainFrame()) { - m_frame.page()->featureObserver()->didCommitLoad(); #if ENABLE(REMOTE_INSPECTOR) + if (m_frame.isMainFrame()) m_frame.page()->remoteInspectorInformationDidChange(); #endif - } } void FrameLoader::tellClientAboutPastMemoryCacheLoads() @@ -3436,9 +3635,8 @@ void FrameLoader::tellClientAboutPastMemoryCacheLoads() Vector<ResourceRequest> pastLoads; m_documentLoader->takeMemoryCacheLoadsForClientNotification(pastLoads); - size_t size = pastLoads.size(); - for (size_t i = 0; i < size; ++i) { - CachedResource* resource = memoryCache()->resourceForRequest(pastLoads[i]); + for (auto& pastLoad : pastLoads) { + CachedResource* resource = MemoryCache::singleton().resourceForRequest(pastLoad, m_frame.page()->sessionID()); // FIXME: These loads, loaded from cache, but now gone from the cache by the time // Page::setMemoryCacheClientCallsEnabled(true) is called, will not be seen by the client. @@ -3459,12 +3657,8 @@ NetworkingContext* FrameLoader::networkingContext() const void FrameLoader::loadProgressingStatusChanged() { - FrameView* view = m_frame.mainFrame().view(); - if (!view) - return; - - view->updateLayerFlushThrottlingInAllFrames(); - view->adjustTiledBackingCoverage(); + if (auto* view = m_frame.mainFrame().view()) + view->loadProgressingStatusChanged(); } void FrameLoader::forcePageTransitionIfNeeded() @@ -3472,22 +3666,40 @@ void FrameLoader::forcePageTransitionIfNeeded() m_client.forcePageTransitionIfNeeded(); } +void FrameLoader::clearTestingOverrides() +{ + m_overrideCachePolicyForTesting = std::nullopt; + m_overrideResourceLoadPriorityForTesting = std::nullopt; + m_isStrictRawResourceValidationPolicyDisabledForTesting = false; +} + +void FrameLoader::applyShouldOpenExternalURLsPolicyToNewDocumentLoader(DocumentLoader& documentLoader, ShouldOpenExternalURLsPolicy propagatedPolicy) +{ + documentLoader.setShouldOpenExternalURLsPolicy(shouldOpenExternalURLsPolicyToApply(m_frame, propagatedPolicy)); +} + +bool FrameLoader::isAlwaysOnLoggingAllowed() const +{ + return frame().isAlwaysOnLoggingAllowed(); +} + bool FrameLoaderClient::hasHTMLView() const { return true; } -PassRefPtr<Frame> createWindow(Frame* openerFrame, Frame* lookupFrame, const FrameLoadRequest& request, const WindowFeatures& features, bool& created) +RefPtr<Frame> createWindow(Frame& openerFrame, Frame& lookupFrame, const FrameLoadRequest& request, const WindowFeatures& features, bool& created) { ASSERT(!features.dialog || request.frameName().isEmpty()); + created = false; + if (!request.frameName().isEmpty() && request.frameName() != "_blank") { - if (Frame* frame = lookupFrame->loader().findFrameForNavigation(request.frameName(), openerFrame->document())) { + if (RefPtr<Frame> frame = lookupFrame.loader().findFrameForNavigation(request.frameName(), openerFrame.document())) { if (request.frameName() != "_self") { if (Page* page = frame->page()) page->chrome().focus(); } - created = false; return frame; } } @@ -3495,74 +3707,98 @@ PassRefPtr<Frame> createWindow(Frame* openerFrame, Frame* lookupFrame, const Fra // Sandboxed frames cannot open new auxiliary browsing contexts. if (isDocumentSandboxed(openerFrame, SandboxPopups)) { // FIXME: This message should be moved off the console once a solution to https://bugs.webkit.org/show_bug.cgi?id=103274 exists. - openerFrame->document()->addConsoleMessage(SecurityMessageSource, ErrorMessageLevel, "Blocked opening '" + request.resourceRequest().url().stringCenterEllipsizedToLength() + "' in a new window because the request was made in a sandboxed frame whose 'allow-popups' permission is not set."); - return 0; + openerFrame.document()->addConsoleMessage(MessageSource::Security, MessageLevel::Error, "Blocked opening '" + request.resourceRequest().url().stringCenterEllipsizedToLength() + "' in a new window because the request was made in a sandboxed frame whose 'allow-popups' permission is not set."); + return nullptr; } // FIXME: Setting the referrer should be the caller's responsibility. FrameLoadRequest requestWithReferrer = request; - String referrer = SecurityPolicy::generateReferrerHeader(openerFrame->document()->referrerPolicy(), request.resourceRequest().url(), openerFrame->loader().outgoingReferrer()); + String referrer = SecurityPolicy::generateReferrerHeader(openerFrame.document()->referrerPolicy(), request.resourceRequest().url(), openerFrame.loader().outgoingReferrer()); if (!referrer.isEmpty()) requestWithReferrer.resourceRequest().setHTTPReferrer(referrer); - FrameLoader::addHTTPOriginIfNeeded(requestWithReferrer.resourceRequest(), openerFrame->loader().outgoingOrigin()); + FrameLoader::addHTTPOriginIfNeeded(requestWithReferrer.resourceRequest(), openerFrame.loader().outgoingOrigin()); + FrameLoader::addHTTPUpgradeInsecureRequestsIfNeeded(requestWithReferrer.resourceRequest()); - Page* oldPage = openerFrame->page(); + Page* oldPage = openerFrame.page(); if (!oldPage) - return 0; + return nullptr; - NavigationAction action(requestWithReferrer.resourceRequest()); - Page* page = oldPage->chrome().createWindow(openerFrame, requestWithReferrer, features, action); + ShouldOpenExternalURLsPolicy shouldOpenExternalURLsPolicy = shouldOpenExternalURLsPolicyToApply(openerFrame, request.shouldOpenExternalURLsPolicy()); + Page* page = oldPage->chrome().createWindow(openerFrame, requestWithReferrer, features, NavigationAction(requestWithReferrer.resourceRequest(), shouldOpenExternalURLsPolicy)); if (!page) - return 0; + return nullptr; + + RefPtr<Frame> frame = &page->mainFrame(); - page->mainFrame().loader().forceSandboxFlags(openerFrame->document()->sandboxFlags()); + frame->loader().forceSandboxFlags(openerFrame.document()->sandboxFlags()); if (request.frameName() != "_blank") - page->mainFrame().tree().setName(request.frameName()); + frame->tree().setName(request.frameName()); page->chrome().setToolbarsVisible(features.toolBarVisible || features.locationBarVisible); + + if (!frame->page()) + return nullptr; page->chrome().setStatusbarVisible(features.statusBarVisible); + + if (!frame->page()) + return nullptr; page->chrome().setScrollbarsVisible(features.scrollbarsVisible); + + if (!frame->page()) + return nullptr; page->chrome().setMenubarVisible(features.menuBarVisible); + + if (!frame->page()) + return nullptr; page->chrome().setResizable(features.resizable); // 'x' and 'y' specify the location of the window, while 'width' and 'height' // specify the size of the viewport. We can only resize the window, so adjust // for the difference between the window size and the viewport size. -// FIXME: We should reconcile the initialization of viewport arguments between iOS and OpenSource. + // FIXME: We should reconcile the initialization of viewport arguments between iOS and non-IOS. #if !PLATFORM(IOS) FloatSize viewportSize = page->chrome().pageRect().size(); FloatRect windowRect = page->chrome().windowRect(); - if (features.xSet) - windowRect.setX(features.x); - if (features.ySet) - windowRect.setY(features.y); + if (features.x) + windowRect.setX(*features.x); + if (features.y) + windowRect.setY(*features.y); // Zero width and height mean using default size, not minumum one. - if (features.widthSet && features.width) - windowRect.setWidth(features.width + (windowRect.width() - viewportSize.width())); - if (features.heightSet && features.height) - windowRect.setHeight(features.height + (windowRect.height() - viewportSize.height())); + if (features.width && *features.width) + windowRect.setWidth(*features.width + (windowRect.width() - viewportSize.width())); + if (features.height && *features.height) + windowRect.setHeight(*features.height + (windowRect.height() - viewportSize.height())); // Ensure non-NaN values, minimum size as well as being within valid screen area. - FloatRect newWindowRect = DOMWindow::adjustWindowRect(page, windowRect); + FloatRect newWindowRect = DOMWindow::adjustWindowRect(*page, windowRect); + if (!frame->page()) + return nullptr; page->chrome().setWindowRect(newWindowRect); #else // On iOS, width and height refer to the viewport dimensions. ViewportArguments arguments; // Zero width and height mean using default size, not minimum one. - if (features.widthSet && features.width) - arguments.width = features.width; - if (features.heightSet && features.height) - arguments.height = features.height; - page->mainFrame().setViewportArguments(arguments); + if (features.width && *features.width) + arguments.width = *features.width; + if (features.height && *features.height) + arguments.height = *features.height; + frame->setViewportArguments(arguments); #endif + if (!frame->page()) + return nullptr; page->chrome().show(); created = true; - return &page->mainFrame(); + return frame; +} + +bool FrameLoader::shouldSuppressKeyboardInput() const +{ + return m_frame.settings().shouldSuppressKeyboardInputDuringProvisionalNavigation() && m_state == FrameStateProvisional; } } // namespace WebCore |