| /* | |
| * Copyright (C) 2006-2026 Apple Inc. All rights reserved. | |
| * Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies) | |
| * Copyright (C) 2008, 2009 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.com/) | |
| * Copyright (C) 2008 Alp Toker <alp@atoker.com> | |
| * Copyright (C) Research In Motion Limited 2009. All rights reserved. | |
| * Copyright (C) 2011 Kris Jordan <krisjordan@gmail.com> | |
| * Copyright (C) 2011 Google Inc. All rights reserved. | |
| * | |
| * Redistribution and use in source and binary forms, with or without | |
| * modification, are permitted provided that the following conditions | |
| * are met: | |
| * | |
| * 1. Redistributions of source code must retain the above copyright | |
| * notice, this list of conditions and the following disclaimer. | |
| * 2. Redistributions in binary form must reproduce the above copyright | |
| * notice, this list of conditions and the following disclaimer in the | |
| * documentation and/or other materials provided with the distribution. | |
| * 3. Neither the name of Apple Inc. ("Apple") nor the names of | |
| * its contributors may be used to endorse or promote products derived | |
| * from this software without specific prior written permission. | |
| * | |
| * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY | |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | |
| * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |
| * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY | |
| * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | |
| * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | |
| * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | |
| * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF | |
| * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| */ | |
| #include "config.h" | |
| #include "FrameLoader.h" | |
| #include "AXObjectCache.h" | |
| #include "BackForwardCache.h" | |
| #include "BackForwardController.h" | |
| #include "BeforeUnloadEvent.h" | |
| #include "CachePolicy.h" | |
| #include "CachedPage.h" | |
| #include "Chrome.h" | |
| #include "ChromeClient.h" | |
| #include "CommonVM.h" | |
| #include "ContainerNodeInlines.h" | |
| #include "ContentFilter.h" | |
| #include "ContentRuleListResults.h" | |
| #include "ContentSecurityPolicy.h" | |
| #include "CrossOriginAccessControl.h" | |
| #include "CrossOriginEmbedderPolicy.h" | |
| #include "CrossOriginOpenerPolicy.h" | |
| #include "DNS.h" | |
| #include "DOMWrapperWorld.h" | |
| #include "DatabaseManager.h" | |
| #include "DiagnosticLoggingClient.h" | |
| #include "DiagnosticLoggingKeys.h" | |
| #include "DiagnosticLoggingResultType.h" | |
| #include "DocumentInlines.h" | |
| #include "DocumentLoader.h" | |
| #include "DocumentPage.h" | |
| #include "DocumentPrefetcher.h" | |
| #include "DocumentQuirks.h" | |
| #include "DocumentResourceLoader.h" | |
| #include "DocumentSecurityOrigin.h" | |
| #include "DocumentView.h" | |
| #include "DocumentWindow.h" | |
| #include "Editor.h" | |
| #include "EditorClient.h" | |
| #include "ElementInlines.h" | |
| #include "Event.h" | |
| #include "EventHandler.h" | |
| #include "EventLoop.h" | |
| #include "EventNames.h" | |
| #include "FloatRect.h" | |
| #include "FormState.h" | |
| #include "FormSubmission.h" | |
| #include "Frame.h" | |
| #include "FrameInlines.h" | |
| #include "FrameLoadRequest.h" | |
| #include "FrameNetworkingContext.h" | |
| #include "FrameTree.h" | |
| #include "GarbageCollectionController.h" | |
| #include "HTMLAnchorElement.h" | |
| #include "HTMLFormElement.h" | |
| #include "HTMLIFrameElement.h" | |
| #include "HTMLInputElement.h" | |
| #include "HTMLNames.h" | |
| #include "HTMLObjectElement.h" | |
| #include "HTMLParserIdioms.h" | |
| #include "HTTPHeaderNames.h" | |
| #include "HTTPHeaderValues.h" | |
| #include "HTTPParsers.h" | |
| #include "HistoryController.h" | |
| #include "HistoryItem.h" | |
| #include "InspectorInstrumentation.h" | |
| #include "IntegrityPolicy.h" | |
| #include "LinkLoader.h" | |
| #include "LoaderStrategy.h" | |
| #include "LocalDOMWindow.h" | |
| #include "LocalFrame.h" | |
| #include "LocalFrameLoaderClient.h" | |
| #include "LocalFrameView.h" | |
| #include "Logging.h" | |
| #include "MemoryCache.h" | |
| #include "MemoryRelease.h" | |
| #include "Navigation.h" | |
| #include "NavigationActivation.h" | |
| #include "NavigationDisabler.h" | |
| #include "NavigationNavigationType.h" | |
| #include "NavigationScheduler.h" | |
| #include "Node.h" | |
| #include "OriginAccessPatterns.h" | |
| #include "Page.h" | |
| #include "PageInspectorController.h" | |
| #include "PageTransitionEvent.h" | |
| #include "Performance.h" | |
| #include "PerformanceLogging.h" | |
| #include "PermissionsPolicy.h" | |
| #include "PlatformStrategies.h" | |
| #include "PluginData.h" | |
| #include "PluginDocument.h" | |
| #include "PolicyChecker.h" | |
| #include "ProgressTracker.h" | |
| #include "Quirks.h" | |
| #include "RFC8941.h" | |
| #include "RemoteFrame.h" | |
| #include "RemoteFrameClient.h" | |
| #include "RenderWidgetInlines.h" | |
| #include "ReportingScope.h" | |
| #include "ResourceLoadInfo.h" | |
| #include "ResourceLoadObserver.h" | |
| #include "ResourceRequest.h" | |
| #include "SVGNames.h" | |
| #include "SVGViewElement.h" | |
| #include "SVGViewSpec.h" | |
| #include "ScriptController.h" | |
| #include "ScriptDisallowedScope.h" | |
| #include "ScriptSourceCode.h" | |
| #include "ScrollAnimator.h" | |
| #include "SecurityOrigin.h" | |
| #include "SecurityPolicy.h" | |
| #include "SegmentedString.h" | |
| #include "SerializedScriptValue.h" | |
| #include "Settings.h" | |
| #include "ShouldTreatAsContinuingLoad.h" | |
| #include "StyleTreeResolver.h" | |
| #include "SubframeLoader.h" | |
| #include "SubresourceLoader.h" | |
| #include "TextResourceDecoder.h" | |
| #include "UnloadCountIncrementer.h" | |
| #include "UserContentController.h" | |
| #include "UserGestureIndicator.h" | |
| #include "ViewTransition.h" | |
| #include "WindowFeatures.h" | |
| #include "XMLDocumentParser.h" | |
| #include <ranges> | |
| #include <wtf/CheckedPtr.h> | |
| #include <wtf/CompletionHandler.h> | |
| #include <wtf/Ref.h> | |
| #include <wtf/SetForScope.h> | |
| #include <wtf/StdLibExtras.h> | |
| #include <wtf/SystemTracing.h> | |
| #include <wtf/URL.h> | |
| #include <wtf/text/CString.h> | |
| #include <wtf/text/MakeString.h> | |
| #include <wtf/text/TextStream.h> | |
| #include <wtf/text/WTFString.h> | |
| #include "FrameDestructionObserverInlines.h" | |
| #if ENABLE(WEB_ARCHIVE) || ENABLE(MHTML) | |
| #include "Archive.h" | |
| #endif | |
| #if ENABLE(DATA_DETECTION) | |
| #include "DataDetection.h" | |
| #include "DataDetectionResultsStorage.h" | |
| #endif | |
| #if PLATFORM(COCOA) | |
| #include <wtf/cocoa/RuntimeApplicationChecksCocoa.h> | |
| #endif | |
| #if PLATFORM(IOS_FAMILY) | |
| #include "DocumentType.h" | |
| #include "ResourceLoader.h" | |
| #include <wtf/RuntimeApplicationChecks.h> | |
| #endif | |
| #define PAGE_ID (pageID() ? pageID()->toUInt64() : 0) | |
| #define FRAME_ID (frameID().toUInt64()) | |
| #define FRAMELOADER_RELEASE_LOG(channel, fmt, ...) RELEASE_LOG(channel, "%p - [pageID=%" PRIu64 ", frameID=%" PRIu64 ", isMainFrame=%d] FrameLoader::" fmt, this, PAGE_ID, FRAME_ID, m_frame->isMainFrame(), ##__VA_ARGS__) | |
| #define FRAMELOADER_RELEASE_LOG_FORWARDABLE(fmt, ...) RELEASE_LOG_FORWARDABLE(ResourceLoading, fmt, PAGE_ID, FRAME_ID, m_frame->isMainFrame(), ##__VA_ARGS__) | |
| #define FRAMELOADER_RELEASE_LOG_ERROR(channel, fmt, ...) RELEASE_LOG_ERROR(channel, "%p - [pageID=%" PRIu64 ", frameID=%" PRIu64 ", isMainFrame=%d] FrameLoader::" fmt, this, PAGE_ID, FRAME_ID, m_frame->isMainFrame(), ##__VA_ARGS__) | |
| #if USE(APPLE_INTERNAL_SDK) && __has_include(<WebKitAdditions/FrameLoaderAdditions.h>) | |
| #import <WebKitAdditions/FrameLoaderAdditions.h> | |
| #else | |
| namespace WebCore { | |
| static void LEGACY_NODELETE verifyUserAgent(const String&) | |
| { | |
| } | |
| } | |
| #endif | |
| namespace WebCore { | |
| using namespace HTMLNames; | |
| using namespace SVGNames; | |
| bool isBackForwardLoadType(FrameLoadType type) | |
| { | |
| switch (type) { | |
| case FrameLoadType::Standard: | |
| case FrameLoadType::Reload: | |
| case FrameLoadType::ReloadFromOrigin: | |
| case FrameLoadType::ReloadExpiredOnly: | |
| case FrameLoadType::Same: | |
| case FrameLoadType::RedirectWithLockedBackForwardList: | |
| case FrameLoadType::MultipartReplace: | |
| case FrameLoadType::NavigationAPIReplace: | |
| return false; | |
| case FrameLoadType::Back: | |
| case FrameLoadType::Forward: | |
| case FrameLoadType::IndexedBackForward: | |
| return true; | |
| } | |
| ASSERT_NOT_REACHED(); | |
| return false; | |
| } | |
| bool isReload(FrameLoadType type) | |
| { | |
| switch (type) { | |
| case FrameLoadType::Reload: | |
| case FrameLoadType::ReloadFromOrigin: | |
| case FrameLoadType::ReloadExpiredOnly: | |
| return true; | |
| case FrameLoadType::Standard: | |
| case FrameLoadType::Same: | |
| case FrameLoadType::RedirectWithLockedBackForwardList: | |
| case FrameLoadType::MultipartReplace: | |
| case FrameLoadType::NavigationAPIReplace: | |
| case FrameLoadType::Back: | |
| case FrameLoadType::Forward: | |
| case FrameLoadType::IndexedBackForward: | |
| return false; | |
| } | |
| ASSERT_NOT_REACHED(); | |
| return false; | |
| } | |
| // This is not in the FrameLoader class to emphasize that it does not depend on | |
| // private FrameLoader data, and to avoid increasing the number of public functions | |
| // with access to private data. Since only this .cpp file needs it, making it | |
| // non-member lets us exclude it from the header file, thus keeping FrameLoader.h's | |
| // API simpler. | |
| // | |
| static bool LEGACY_NODELETE isDocumentSandboxed(LocalFrame& frame, SandboxFlag flag) | |
| { | |
| return frame.document() && frame.document()->isSandboxed(flag); | |
| } | |
| static bool LEGACY_NODELETE isInVisibleAndActivePage(const LocalFrame& frame) | |
| { | |
| auto* page = frame.page(); | |
| return page && page->isVisibleAndActive(); | |
| } | |
| class PageLevelForbidScope { | |
| protected: | |
| explicit PageLevelForbidScope(Page* page) | |
| : m_page(page) | |
| { | |
| } | |
| ~PageLevelForbidScope() = default; | |
| WeakPtr<Page> m_page; | |
| }; | |
| struct ForbidPromptsScope : public PageLevelForbidScope { | |
| explicit ForbidPromptsScope(Page* page) | |
| : PageLevelForbidScope(page) | |
| { | |
| if (auto* page = m_page.get()) | |
| page->forbidPrompts(); | |
| } | |
| ~ForbidPromptsScope() | |
| { | |
| if (auto* page = m_page.get()) | |
| page->allowPrompts(); | |
| } | |
| }; | |
| struct ForbidSynchronousLoadsScope : public PageLevelForbidScope { | |
| explicit ForbidSynchronousLoadsScope(Page* page) | |
| : PageLevelForbidScope(page) | |
| { | |
| if (auto* page = m_page.get()) | |
| page->forbidSynchronousLoads(); | |
| } | |
| ~ForbidSynchronousLoadsScope() | |
| { | |
| if (auto* page = m_page.get()) | |
| page->allowSynchronousLoads(); | |
| } | |
| }; | |
| struct ForbidCopyPasteScope : public PageLevelForbidScope { | |
| explicit ForbidCopyPasteScope(Page* page) | |
| : PageLevelForbidScope(page) | |
| , m_oldDOMPasteAllowed(page->settings().domPasteAllowed()) | |
| , m_oldJavaScriptCanAccessClipboard(page->settings().javaScriptCanAccessClipboard()) | |
| , m_oldClipboardAccessPolicy(page->settings().clipboardAccessPolicy()) | |
| { | |
| if (m_page) { | |
| m_page->settings().setDOMPasteAllowed(false); | |
| m_page->settings().setJavaScriptCanAccessClipboard(false); | |
| m_page->settings().setClipboardAccessPolicy(ClipboardAccessPolicy::Deny); | |
| } | |
| } | |
| ~ForbidCopyPasteScope() | |
| { | |
| if (m_page) { | |
| m_page->settings().setDOMPasteAllowed(m_oldDOMPasteAllowed); | |
| m_page->settings().setJavaScriptCanAccessClipboard(m_oldJavaScriptCanAccessClipboard); | |
| m_page->settings().setClipboardAccessPolicy(m_oldClipboardAccessPolicy); | |
| } | |
| } | |
| private: | |
| bool m_oldDOMPasteAllowed; | |
| bool m_oldJavaScriptCanAccessClipboard; | |
| ClipboardAccessPolicy m_oldClipboardAccessPolicy; | |
| }; | |
| class FrameLoader::FrameProgressTracker final : public CanMakeCheckedPtr<FrameLoader::FrameProgressTracker> { | |
| WTF_DEPRECATED_MAKE_FAST_ALLOCATED_WITH_HEAP_IDENTIFIER(FrameProgressTracker, Loader); | |
| WTF_OVERRIDE_DELETE_FOR_CHECKED_PTR(FrameProgressTracker); | |
| public: | |
| explicit FrameProgressTracker(LocalFrame& frame) | |
| : m_frame(frame) | |
| { | |
| } | |
| ~FrameProgressTracker() | |
| { | |
| if (m_inProgress && m_frame->page()) { | |
| Ref frame = m_frame.get(); | |
| protect(frame->page()->progress())->progressCompleted(frame); | |
| } | |
| } | |
| void progressStarted() | |
| { | |
| ASSERT(m_frame->page()); | |
| if (!m_inProgress) { | |
| Ref frame = m_frame.get(); | |
| protect(frame->page()->progress())->progressStarted(frame); | |
| } | |
| m_inProgress = true; | |
| } | |
| enum class LoadCompletionStatus { Success, Failure }; | |
| void progressCompleted(LoadCompletionStatus completion) | |
| { | |
| ASSERT(m_inProgress); | |
| ASSERT(m_frame->page()); | |
| m_inProgress = false; | |
| Ref frame = m_frame.get(); | |
| RefPtr page = frame->page(); | |
| protect(page->progress())->progressCompleted(frame); | |
| if (completion == LoadCompletionStatus::Success) | |
| platformStrategies()->loaderStrategy()->pageLoadCompleted(*page); | |
| } | |
| private: | |
| WeakRef<LocalFrame> m_frame; | |
| bool m_inProgress { false }; | |
| }; | |
| FrameLoader::FrameLoader(LocalFrame& frame, CompletionHandler<UniqueRef<LocalFrameLoaderClient>(LocalFrame&, FrameLoader&)>&& clientCreator) | |
| : m_frame(frame) | |
| , m_client(clientCreator(frame, *this)) | |
| , m_policyChecker(makeUniqueRef<PolicyChecker>(frame)) | |
| , m_history(makeUniqueRefWithoutRefCountedCheck<HistoryController>(frame)) | |
| , m_notifier(frame) | |
| , m_subframeLoader(makeUniqueRef<SubframeLoader>(frame)) | |
| , m_state(FrameState::Provisional) | |
| , m_loadType(FrameLoadType::Standard) | |
| , m_checkTimer(*this, &FrameLoader::checkTimerFired) | |
| , m_crossOriginParentSyntheticSameDocLoadEventTimer(*this, &FrameLoader::crossOriginParentSyntheticSameDocLoadEventTimerFired) | |
| , m_documentPrefetcher(DocumentPrefetcher::create(frame)) | |
| { | |
| } | |
| FrameLoader::~FrameLoader() | |
| { | |
| Ref frame = m_frame.get(); | |
| frame->disownOpener(); | |
| frame->detachFromAllOpenedFrames(); | |
| if (auto* networkingContext = m_networkingContext.get()) | |
| networkingContext->invalidate(); | |
| } | |
| void FrameLoader::ref() const | |
| { | |
| m_frame->ref(); | |
| } | |
| void FrameLoader::deref() const | |
| { | |
| m_frame->deref(); | |
| } | |
| LocalFrame& FrameLoader::frame() const | |
| { | |
| return m_frame; | |
| } | |
| void FrameLoader::init() | |
| { | |
| // This somewhat odd set of steps gives the frame an initial empty document. | |
| setPolicyDocumentLoader(m_client->createDocumentLoader(ResourceRequest(URL({ }, emptyString())), SubstituteData())); | |
| setProvisionalDocumentLoader(m_policyDocumentLoader.copyRef()); | |
| protect(m_provisionalDocumentLoader)->startLoadingMainResource(); | |
| setPolicyDocumentLoader(nullptr); | |
| Ref frame = m_frame.get(); | |
| protect(frame->document())->cancelParsing(); | |
| m_stateMachine.advanceTo(FrameLoaderStateMachine::DisplayingInitialEmptyDocument); | |
| m_networkingContext = m_client->createNetworkingContext(); | |
| m_progressTracker = makeUnique<FrameProgressTracker>(frame); | |
| } | |
| void FrameLoader::initForSynthesizedDocument(const URL&) | |
| { | |
| // FIXME: We need to initialize the document URL to the specified URL. Currently the URL is empty and hence | |
| // FrameLoader::checkCompleted() will overwrite the URL of the document to be activeDocumentLoader()->documentURL(). | |
| Ref frame = m_frame.get(); | |
| { | |
| Ref loader = m_client->createDocumentLoader(ResourceRequest(URL({ }, emptyString())), SubstituteData()); | |
| loader->attachToFrame(frame); | |
| loader->setResponse(ResourceResponse(URL(), String { textHTMLContentTypeAtom() }, 0, String())); | |
| loader->setCommitted(true); | |
| setDocumentLoader(WTF::move(loader)); | |
| } | |
| m_stateMachine.advanceTo(FrameLoaderStateMachine::DisplayingInitialEmptyDocument); | |
| m_stateMachine.advanceTo(FrameLoaderStateMachine::DisplayingInitialEmptyDocumentPostCommit); | |
| m_stateMachine.advanceTo(FrameLoaderStateMachine::CommittedFirstRealLoad); | |
| m_client->transitionToCommittedForNewPage(m_documentLoader && m_documentLoader->isInFinishedLoadingOfEmptyDocument() ? | |
| LocalFrameLoaderClient::InitializingIframe::Yes : LocalFrameLoaderClient::InitializingIframe::No); | |
| m_didCallImplicitClose = true; | |
| m_isComplete = true; | |
| m_state = FrameState::Complete; | |
| // Synthesized documents bypass setState(FrameState::Complete), which normally | |
| // stops recording responses on the DocumentLoader. | |
| if (RefPtr documentLoader = m_documentLoader) | |
| documentLoader->stopRecordingResponses(); | |
| m_needsClear = true; | |
| m_networkingContext = m_client->createNetworkingContext(); | |
| m_progressTracker = makeUnique<FrameProgressTracker>(frame); | |
| } | |
| std::optional<PageIdentifier> FrameLoader::pageID() const | |
| { | |
| return m_frame->page() ? m_frame->page()->identifier() : std::nullopt; | |
| } | |
| FrameIdentifier FrameLoader::frameID() const | |
| { | |
| return m_frame->frameID(); | |
| } | |
| void FrameLoader::setDefersLoading(bool defers) | |
| { | |
| if (RefPtr documentLoader = m_documentLoader) | |
| documentLoader->setDefersLoading(defers); | |
| if (RefPtr provisionalDocumentLoader = m_provisionalDocumentLoader) | |
| provisionalDocumentLoader->setDefersLoading(defers); | |
| if (RefPtr policyDocumentLoader = m_policyDocumentLoader) | |
| policyDocumentLoader->setDefersLoading(defers); | |
| history().setDefersLoading(defers); | |
| if (!defers) { | |
| protect(protect(m_frame)->navigationScheduler())->startTimer(); | |
| startCheckCompleteTimer(); | |
| } | |
| } | |
| void FrameLoader::checkContentPolicy(const ResourceResponse& response, ContentPolicyDecisionFunction&& function) | |
| { | |
| if (!activeDocumentLoader()) { | |
| // Load was cancelled | |
| function(PolicyAction::Ignore); | |
| return; | |
| } | |
| // FIXME: Validate the policy check identifier. | |
| protect(client())->dispatchDecidePolicyForResponse(response, activeDocumentLoader()->request(), activeDocumentLoader()->downloadAttribute(), WTF::move(function)); | |
| } | |
| FrameLoadRequest FrameLoader::createFrameLoadRequest(URL&& url, const String& referrer) | |
| { | |
| RefPtr frame = lexicalFrameFromCommonVM(); | |
| auto initiatedByMainFrame = frame && frame->isMainFrame() ? InitiatedByMainFrame::Yes : InitiatedByMainFrame::Unknown; | |
| ResourceRequest request { WTF::move(url) }; | |
| if (!referrer.isEmpty()) | |
| request.setHTTPReferrer(referrer); | |
| Ref document = *m_frame->document(); | |
| return FrameLoadRequest(document.copyRef(), protect(document->securityOrigin()), WTF::move(request), selfTargetFrameName(), initiatedByMainFrame, { }); | |
| } | |
| void FrameLoader::changeLocation(const URL& url, const AtomString& passedTarget, Event* triggeringEvent, const ReferrerPolicy& referrerPolicy, ShouldOpenExternalURLsPolicy shouldOpenExternalURLsPolicy, std::optional<NewFrameOpenerPolicy> openerPolicy, const AtomString& downloadAttribute, std::optional<PrivateClickMeasurement>&& privateClickMeasurement, NavigationHistoryBehavior historyBehavior, Element* sourceElement) | |
| { | |
| RefPtr frame = lexicalFrameFromCommonVM(); | |
| auto initiatedByMainFrame = frame && frame->isMainFrame() ? InitiatedByMainFrame::Yes : InitiatedByMainFrame::Unknown; | |
| Ref document = *m_frame->document(); | |
| NewFrameOpenerPolicy newFrameOpenerPolicy = openerPolicy.value_or(referrerPolicy == ReferrerPolicy::NoReferrer ? NewFrameOpenerPolicy::Suppress : NewFrameOpenerPolicy::Allow); | |
| FrameLoadRequest frameLoadRequest(document.copyRef(), protect(document->securityOrigin()), { URL { url } }, passedTarget, initiatedByMainFrame, downloadAttribute); | |
| frameLoadRequest.setNewFrameOpenerPolicy(newFrameOpenerPolicy); | |
| frameLoadRequest.setReferrerPolicy(referrerPolicy); | |
| frameLoadRequest.setShouldOpenExternalURLsPolicy(shouldOpenExternalURLsPolicy); | |
| frameLoadRequest.disableShouldReplaceDocumentIfJavaScriptURL(); | |
| frameLoadRequest.setNavigationHistoryBehavior(historyBehavior); | |
| frameLoadRequest.setSourceElement(sourceElement); | |
| changeLocation(WTF::move(frameLoadRequest), triggeringEvent, WTF::move(privateClickMeasurement)); | |
| } | |
| void FrameLoader::changeLocation(FrameLoadRequest&& frameRequest, Event* triggeringEvent, std::optional<PrivateClickMeasurement>&& privateClickMeasurement) | |
| { | |
| FRAMELOADER_RELEASE_LOG_FORWARDABLE(FrameLoaderChangeLocation); | |
| ASSERT(frameRequest.resourceRequest().httpMethod() == "GET"_s); | |
| Ref frame = m_frame.get(); | |
| if (frameRequest.frameName().isEmpty()) | |
| frameRequest.setFrameName(frame->document()->baseTarget()); | |
| if (CheckedPtr requesterCSP = frameRequest.requester().contentSecurityPolicy()) | |
| requesterCSP->upgradeInsecureRequestIfNeeded(frameRequest.resourceRequest(), ContentSecurityPolicy::InsecureRequestType::Navigation); | |
| if (RefPtr document = frame->document()) { | |
| if (document->contentSecurityPolicy() != frameRequest.requester().contentSecurityPolicy()) | |
| protect(document->contentSecurityPolicy())->upgradeInsecureRequestIfNeeded(frameRequest.resourceRequest(), ContentSecurityPolicy::InsecureRequestType::Navigation); | |
| } | |
| loadFrameRequest(WTF::move(frameRequest), triggeringEvent, { }, WTF::move(privateClickMeasurement)); | |
| } | |
| void FrameLoader::submitForm(Ref<FormSubmission>&& submission) | |
| { | |
| ASSERT(submission->method() == FormSubmission::Method::Post || submission->method() == FormSubmission::Method::Get); | |
| // FIXME: Find a good spot for these. | |
| ASSERT(!submission->state()->sourceDocument().frame() || submission->state()->sourceDocument().frame() == m_frame.ptr()); | |
| Ref frame = m_frame.get(); | |
| if (!frame->page()) | |
| return; | |
| if (submission->action().isEmpty()) | |
| return; | |
| RefPtr document = frame->document(); | |
| if (isDocumentSandboxed(frame, SandboxFlag::Forms)) { | |
| // FIXME: This message should be moved off the console once a solution to https://bugs.webkit.org/show_bug.cgi?id=103274 exists. | |
| document->addConsoleMessage(MessageSource::Security, MessageLevel::Error, makeString("Blocked form submission to '"_s, submission->action().stringCenterEllipsizedToLength(), "' because the form's frame is sandboxed and the 'allow-forms' permission is not set."_s)); | |
| return; | |
| } | |
| URL formAction = submission->action(); | |
| if (!protect(document->contentSecurityPolicy())->allowFormAction(formAction, document->currentParserSourcePosition())) | |
| return; | |
| RefPtr targetFrame = findFrameForNavigation(submission->target(), &submission->state()->sourceDocument()); | |
| if (!targetFrame) { | |
| if (!LocalDOMWindow::allowPopUp(frame) && !UserGestureIndicator::processingUserGesture()) | |
| return; | |
| // 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. | |
| // There is a repeat check after timer fires, so this is not a correctness issue. | |
| targetFrame = frame.copyRef(); | |
| } else | |
| submission->clearTarget(); | |
| if (!targetFrame->page()) | |
| return; | |
| if (frame->tree().isDescendantOf(targetFrame.get())) | |
| m_submittedFormURL = submission->requestURL(); | |
| submission->setReferrer(outgoingReferrer()); | |
| submission->setOrigin(SecurityPolicy::generateOriginHeader(frame->document()->referrerPolicy(), submission->requestURL(), protect(protect(frame->document())->securityOrigin()), OriginAccessPatternsForWebProcess::singleton())); | |
| protect(targetFrame->navigationScheduler())->scheduleFormSubmission(WTF::move(submission)); | |
| } | |
| void FrameLoader::stopLoading(UnloadEventPolicy unloadEventPolicy) | |
| { | |
| Ref frame = m_frame.get(); | |
| if (RefPtr parser = frame->document() ? frame->document()->parser() : nullptr) | |
| parser->stopParsing(); | |
| if (unloadEventPolicy != UnloadEventPolicy::None) | |
| dispatchUnloadEvents(unloadEventPolicy); | |
| m_isComplete = true; // to avoid calling completed() in finishedParsing() | |
| m_didCallImplicitClose = true; // don't want that one either | |
| if (RefPtr document = frame->document(); document && document->parsing()) { | |
| finishedParsing(); | |
| document->setParsing(false); | |
| } | |
| if (RefPtr document = frame->document()) { | |
| // FIXME: Should the DatabaseManager watch for something like ActiveDOMObject::stop() rather than being special-cased here? | |
| DatabaseManager::singleton().stopDatabases(*document, nullptr); | |
| if (document->settings().navigationAPIEnabled() && !m_doNotAbortNavigationAPI && unloadEventPolicy != UnloadEventPolicy::UnloadAndPageHide) { | |
| if (RefPtr window = document->window()) { | |
| Ref navigation = window->navigation(); | |
| if (document->backForwardCacheState() == Document::NotInBackForwardCache || navigation->hasInterceptedOngoingNavigateEvent()) | |
| navigation->abortOngoingNavigationIfNeeded(); | |
| } | |
| } | |
| } | |
| policyChecker().stopCheck(); | |
| // FIXME: This will cancel redirection timer, which really needs to be restarted when restoring the frame from b/f cache. | |
| protect(frame->navigationScheduler())->cancel(); | |
| } | |
| void FrameLoader::stop() | |
| { | |
| // http://bugs.webkit.org/show_bug.cgi?id=10854 | |
| // The frame's last ref may be removed and it will be deleted by checkCompleted(). | |
| Ref frame = m_frame.get(); | |
| if (RefPtr parser = frame->document()->parser()) { | |
| parser->stopParsing(); | |
| parser->finish(); | |
| } | |
| } | |
| void FrameLoader::closeURL() | |
| { | |
| history().saveDocumentState(); | |
| RefPtr currentDocument = m_frame->document(); | |
| 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 = UnloadEventPolicy::None; | |
| } else { | |
| // Should only send the pagehide event here if the current document exists and has not been placed in the back/forward cache. | |
| unloadEventPolicy = currentDocument && currentDocument->backForwardCacheState() == Document::NotInBackForwardCache ? UnloadEventPolicy::UnloadAndPageHide : UnloadEventPolicy::UnloadOnly; | |
| } | |
| stopLoading(unloadEventPolicy); | |
| if (currentDocument) | |
| protect(currentDocument->editor())->clearUndoRedoOperations(); | |
| } | |
| bool FrameLoader::didOpenURL() | |
| { | |
| Ref frame = m_frame.get(); | |
| if (frame->navigationScheduler().redirectScheduledDuringLoad()) { | |
| // A redirect was scheduled before the document was created. | |
| // This can happen when one frame changes another frame's location. | |
| return false; | |
| } | |
| protect(frame->navigationScheduler())->cancel(); | |
| m_isComplete = false; | |
| m_didCallImplicitClose = false; | |
| started(); | |
| return true; | |
| } | |
| void FrameLoader::didExplicitOpen() | |
| { | |
| m_isComplete = false; | |
| m_didCallImplicitClose = false; | |
| // Calling document.open counts as committing the first real document load. | |
| if (!m_stateMachine.committedFirstRealDocumentLoad()) | |
| m_stateMachine.advanceTo(FrameLoaderStateMachine::DisplayingInitialEmptyDocumentPostCommit); | |
| if (RefPtr document = m_frame->document()) | |
| m_client->dispatchDidExplicitOpen(document->url(), document->contentType()); | |
| // Prevent window.open(url) -- eg window.open("about:blank") -- from blowing away results | |
| // from a subsequent window.document.open / window.document.write call. | |
| // Canceling redirection here works for all cases because document.open | |
| // implicitly precedes document.write. | |
| protect(protect(m_frame)->navigationScheduler())->cancel(); | |
| } | |
| static inline bool shouldClearWindowName(const LocalFrame& frame, const Document& newDocument) | |
| { | |
| if (!frame.isMainFrame()) | |
| return false; | |
| if (frame.opener()) | |
| return false; | |
| return !protect(newDocument.securityOrigin())->isSameOriginAs(protect(protect(frame.document())->securityOrigin())); | |
| } | |
| void FrameLoader::clear(RefPtr<Document>&& newDocument, bool clearWindowProperties, bool clearScriptObjects, bool clearFrameView, NOESCAPE const Function<void()>& handleDOMWindowCreation) | |
| { | |
| bool neededClear = m_needsClear; | |
| m_needsClear = false; | |
| Ref frame = m_frame; | |
| RefPtr document = frame->document(); | |
| if (neededClear) | |
| document->transferViewTransitionParams(*newDocument); | |
| if (neededClear && document->backForwardCacheState() != Document::InBackForwardCache) { | |
| document->cancelParsing(); | |
| document->stopActiveDOMObjects(); | |
| // Taken before willBeRemovedFromFrame() detaches the document, which makes it no longer fully active. | |
| bool shouldAdjustFocus = document->canEverRender(); | |
| document->willBeRemovedFromFrame(); | |
| if (shouldAdjustFocus) | |
| document->adjustFocusedNodeOnNodeRemoval(*document); | |
| } | |
| if (handleDOMWindowCreation) | |
| handleDOMWindowCreation(); | |
| if (!neededClear) | |
| return; | |
| // Do this after detaching the document so that the unload event works. | |
| if (clearWindowProperties) { | |
| InspectorInstrumentation::frameWindowDiscarded(frame, protect(document->window()).get()); | |
| protect(document->window())->resetUnlessSuspendedForDocumentSuspension(); | |
| protect(frame->windowProxy())->clearJSWindowProxiesNotMatchingDOMWindow(protect(newDocument->window()).get(), frame->document()->backForwardCacheState() == Document::AboutToEnterBackForwardCache); | |
| if (shouldClearWindowName(frame, *newDocument)) | |
| frame->tree().setSpecifiedName(nullAtom()); | |
| } | |
| frame->eventHandler().clear(); | |
| if (clearFrameView && frame->view()) | |
| protect(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. | |
| frame->setDocument(nullptr); | |
| subframeLoader().clear(); | |
| if (clearWindowProperties) | |
| protect(frame->windowProxy())->setDOMWindow(protect(newDocument->window()).get()); | |
| if (clearScriptObjects) | |
| protect(frame->script())->clearScriptObjects(); | |
| if (CheckedPtr newDocumentCSP = newDocument->contentSecurityPolicy()) { | |
| bool enableEvalValue = newDocumentCSP->evalErrorMessage().isNull(); | |
| bool enableWASMValue = newDocumentCSP->webAssemblyErrorMessage().isNull(); | |
| CheckedRef script = frame->script(); | |
| script->setEvalEnabled(enableEvalValue, newDocumentCSP->evalErrorMessage()); | |
| script->setWebAssemblyEnabled(enableWASMValue, newDocumentCSP->webAssemblyErrorMessage()); | |
| } | |
| protect(frame->navigationScheduler())->clear(); | |
| m_checkTimer.stop(); | |
| m_crossOriginParentSyntheticSameDocLoadEventTimer.stop(); | |
| m_shouldCallCheckCompleted = false; | |
| m_shouldCallCheckLoadComplete = false; | |
| if (m_stateMachine.isDisplayingInitialEmptyDocument() && m_stateMachine.committedFirstRealDocumentLoad()) | |
| m_stateMachine.advanceTo(FrameLoaderStateMachine::CommittedFirstRealLoad); | |
| } | |
| void FrameLoader::receivedFirstData() | |
| { | |
| Ref frame = m_frame.get(); | |
| dispatchDidCommitLoad(std::nullopt); | |
| dispatchDidClearWindowObjectsInAllWorlds(); | |
| dispatchGlobalObjectAvailableInAllWorlds(); | |
| RefPtr documentLoader = m_documentLoader; | |
| if (!documentLoader) | |
| return; | |
| auto& title = documentLoader->title(); | |
| if (!title.string.isNull()) | |
| m_client->dispatchDidReceiveTitle(title); | |
| ASSERT(frame->document()); | |
| Ref document = *frame->document(); | |
| if (frame->isMainFrame()) | |
| document->quirks().logQuirksToConsoleIfNecessary(); | |
| LinkLoader::loadLinksFromHeader(documentLoader->response().httpHeaderField(HTTPHeaderName::Link), document->url(), document, LinkLoader::MediaAttributeCheck::MediaAttributeEmpty); | |
| scheduleRefreshIfNeeded(document, documentLoader->response().httpHeaderField(HTTPHeaderName::Refresh), IsMetaRefresh::No); | |
| // https://html.spec.whatwg.org/C#shared-document-creation-infrastructure | |
| // 18. If navigationParams's navigable is a top-level traversable, then process the `Speculation-Rules` header given document and navigationParams's response. | |
| if (frame->isMainFrame() && document->settings().speculationRulesPrefetchEnabled()) { | |
| String speculationRulesHeader = documentLoader->response().httpHeaderField(HTTPHeaderName::SpeculationRules); | |
| if (!speculationRulesHeader.isEmpty()) | |
| document->processSpeculationRulesHeader(speculationRulesHeader, documentLoader->response().url()); | |
| } | |
| } | |
| void FrameLoader::setOutgoingReferrer(const URL& url) | |
| { | |
| auto result = url.strippedForUseAsReferrer(); | |
| m_outgoingReferrer = WTF::move(result.string); | |
| if (result.stripped) | |
| m_outgoingReferrerURL = { }; | |
| else | |
| m_outgoingReferrerURL = url; | |
| } | |
| static AtomString extractContentLanguageFromHeader(const String& header) | |
| { | |
| auto commaIndex = header.find(','); | |
| if (commaIndex == notFound) | |
| return StringView(header).trim(isASCIIWhitespace).toAtomString(); | |
| return StringView(header).left(commaIndex).trim(isASCIIWhitespace).toAtomString(); | |
| } | |
| // Tells developers when COOP or COEP headers are not doing what they asked for: values WebKit does not act on (which | |
| // are parsed as 'unsafe-none'), and, for a top-level document, why it is still not cross-origin isolated since script | |
| // otherwise only sees crossOriginIsolated == false and no SharedArrayBuffer. COOP only applies to top-level documents. | |
| static void logCrossOriginIsolationDiagnostics(Document& document, const ResourceResponse& response, bool isMainFrame) | |
| { | |
| auto& headers = response.httpHeaderFields(); | |
| String coepHeader = headers.get(HTTPHeaderName::CrossOriginEmbedderPolicy); | |
| String coepReportOnlyHeader = headers.get(HTTPHeaderName::CrossOriginEmbedderPolicyReportOnly); | |
| String coopHeader; | |
| String coopReportOnlyHeader; | |
| if (isMainFrame) { | |
| coopHeader = headers.get(HTTPHeaderName::CrossOriginOpenerPolicy); | |
| coopReportOnlyHeader = headers.get(HTTPHeaderName::CrossOriginOpenerPolicyReportOnly); | |
| } | |
| if (coepHeader.isEmpty() && coepReportOnlyHeader.isEmpty() && coopHeader.isEmpty() && coopReportOnlyHeader.isEmpty()) | |
| return; | |
| auto checkValue = [&](const String& header, ASCIILiteral headerLabel, bool (*isRecognizedToken)(StringView), bool (*isUnsupportedToken)(StringView)) { | |
| if (header.isEmpty()) | |
| return false; | |
| auto parsed = RFC8941::parseItemStructuredFieldValue(header); | |
| auto* token = parsed ? std::get_if<RFC8941::Token>(&parsed->first) : nullptr; | |
| if (token && isRecognizedToken(token->string())) | |
| return false; | |
| if (token && isUnsupportedToken && isUnsupportedToken(token->string())) { | |
| document.addConsoleMessage(MessageSource::Security, MessageLevel::Warning, makeString("The "_s, headerLabel, " header value '"_s, header, "' is not supported and is ignored (treated as 'unsafe-none')."_s)); | |
| return true; | |
| } | |
| document.addConsoleMessage(MessageSource::Security, MessageLevel::Warning, makeString("The "_s, headerLabel, " header value '"_s, header, "' is not recognized and is ignored (treated as 'unsafe-none')."_s)); | |
| return true; | |
| }; | |
| bool coepValueReported = checkValue(coepHeader, "Cross-Origin-Embedder-Policy"_s, isRecognizedCrossOriginEmbedderPolicyToken, isUnsupportedCrossOriginEmbedderPolicyToken); | |
| checkValue(coepReportOnlyHeader, "Cross-Origin-Embedder-Policy-Report-Only"_s, isRecognizedCrossOriginEmbedderPolicyToken, isUnsupportedCrossOriginEmbedderPolicyToken); | |
| bool coopValueReported = checkValue(coopHeader, "Cross-Origin-Opener-Policy"_s, isRecognizedCrossOriginOpenerPolicyToken, nullptr); | |
| checkValue(coopReportOnlyHeader, "Cross-Origin-Opener-Policy-Report-Only"_s, isRecognizedCrossOriginOpenerPolicyToken, nullptr); | |
| if (!isMainFrame || (coopHeader.isEmpty() && coepHeader.isEmpty()) || document.crossOriginIsolated()) | |
| return; | |
| String reason; | |
| if (!document.isSecureContext()) | |
| reason = "the document is not a secure context (cross-origin isolation requires HTTPS)"_s; | |
| else if (coopHeader.isEmpty()) | |
| reason = "the Cross-Origin-Opener-Policy header is missing (it must be 'same-origin')"_s; | |
| else if (auto coopValue = document.crossOriginOpenerPolicy().value; !coopValueReported && coopValue != CrossOriginOpenerPolicyValue::SameOrigin && coopValue != CrossOriginOpenerPolicyValue::SameOriginPlusCOEP) | |
| reason = makeString("the Cross-Origin-Opener-Policy header value '"_s, coopHeader, "' is not 'same-origin'"_s); | |
| else if (coepHeader.isEmpty()) | |
| reason = "the Cross-Origin-Embedder-Policy header is missing (it must be 'require-corp')"_s; | |
| else if (!coepValueReported && document.crossOriginEmbedderPolicy().value != CrossOriginEmbedderPolicyValue::RequireCORP) | |
| reason = makeString("the Cross-Origin-Embedder-Policy header value '"_s, coepHeader, "' is not 'require-corp'"_s); | |
| // An unrecognized or unsupported value was already reported above; any other remaining cause is not one this message can name. | |
| if (reason.isEmpty()) | |
| return; | |
| document.addConsoleMessage(MessageSource::Security, MessageLevel::Warning, makeString("This document is not cross-origin isolated because "_s, reason, ". window.crossOriginIsolated is false and SharedArrayBuffer is unavailable."_s)); | |
| } | |
| void FrameLoader::didBeginDocument(bool dispatch, LocalDOMWindow* previousWindow) | |
| { | |
| m_needsClear = true; | |
| m_isComplete = false; | |
| m_isWaitingForDelegatedBackForwardLoad = false; | |
| m_didCallImplicitClose = false; | |
| Ref frame = m_frame.get(); | |
| Ref document = *frame->document(); | |
| document->setReadyState(Document::ReadyState::Loading); | |
| if (dispatch) | |
| dispatchDidClearWindowObjectsInAllWorlds(); | |
| document->updateHasUnpartitionedStorageAccess(protect(m_documentLoader).get()); | |
| updateFirstPartyForCookies(); | |
| document->initContentSecurityPolicy(); | |
| Ref settings = frame->settings(); | |
| protect(document->cachedResourceLoader())->setImagesEnabled(settings->areImagesEnabled()); | |
| protect(document->cachedResourceLoader())->setAutoLoadImages(settings->loadsImagesAutomatically()); | |
| std::optional<NavigationNavigationType> navigationType; | |
| if (RefPtr documentLoader = m_documentLoader) { | |
| // The DocumentLoader may have already parsed the CSP header to do some checks. If so, reuse the already parsed version instead of parsing again. | |
| if (CheckedPtr contentSecurityPolicy = documentLoader->contentSecurityPolicy()) | |
| protect(document->contentSecurityPolicy())->didReceiveHeaders(*contentSecurityPolicy, ContentSecurityPolicy::ReportParsingErrors::No); | |
| else | |
| protect(document->contentSecurityPolicy())->didReceiveHeaders(ContentSecurityPolicyResponseHeaders(documentLoader->response()), referrer(), ContentSecurityPolicy::ReportParsingErrors::No); | |
| if (document->url().protocolIsBlob()) | |
| protect(document->contentSecurityPolicy())->updateSourceSelf(SecurityOrigin::create(document->url())); | |
| if (document->url().protocolIsInHTTPFamilyOrBlob()) { | |
| auto isSecureContext = document->isSecureContext() ? IsSecureContext::Yes : IsSecureContext::No; | |
| document->setCrossOriginEmbedderPolicy(obtainCrossOriginEmbedderPolicy(documentLoader->response(), isSecureContext, document.ptr())); | |
| if (auto ipAddressSpace = documentLoader->response().ipAddressSpace(); ipAddressSpace != IPAddressSpace::Unknown) | |
| document->setIPAddressSpace(ipAddressSpace); | |
| if (!m_stateMachine.creatingInitialEmptyDocument()) { | |
| auto& agentClusterAssignment = documentLoader->agentClusterAssignment(); | |
| if (frame->settings().originAgentClusterEnabled()) | |
| document->setIsOriginKeyed(agentClusterAssignment.isOriginKeyed); | |
| // The UI process decides the value, since it determines the agent cluster. The rest is only for reporting. | |
| DocumentIsolationPolicy documentIsolationPolicy; | |
| if (settings->documentIsolationPolicyEnabled() && settings->siteIsolationEnabled()) | |
| documentIsolationPolicy = obtainDocumentIsolationPolicy(documentLoader->response(), isSecureContext); | |
| ASSERT(documentIsolationPolicy.value == agentClusterAssignment.documentIsolationPolicy); | |
| documentIsolationPolicy.value = agentClusterAssignment.documentIsolationPolicy; | |
| document->setDocumentIsolationPolicy(documentIsolationPolicy); | |
| } | |
| } | |
| // Documents that inherit their origin, such as about:blank, keep the identifier they inherited. | |
| if (auto agentClusterIdentifier = documentLoader->agentClusterAssignment().identifier; agentClusterIdentifier && !m_stateMachine.creatingInitialEmptyDocument()) | |
| document->setAgentClusterIdentifier(agentClusterIdentifier); | |
| String referrerPolicy = documentLoader->response().httpHeaderField(HTTPHeaderName::ReferrerPolicy); | |
| if (!referrerPolicy.isNull()) | |
| document->processReferrerPolicy(referrerPolicy, ReferrerPolicySource::HTTPHeader); | |
| String headerContentLanguage = documentLoader->response().httpHeaderField(HTTPHeaderName::ContentLanguage); | |
| if (!headerContentLanguage.isEmpty()) { | |
| auto contentLanguage = extractContentLanguageFromHeader(headerContentLanguage); | |
| if (!contentLanguage.isEmpty()) | |
| document->setContentLanguage(WTF::move(contentLanguage)); | |
| } | |
| String reportingEndpoints = documentLoader->response().httpHeaderField(HTTPHeaderName::ReportingEndpoints); | |
| if (!reportingEndpoints.isEmpty()) | |
| protect(document->reportingScope())->parseReportingEndpoints(reportingEndpoints, documentLoader->response().url()); | |
| // https://html.spec.whatwg.org/multipage/browsing-the-web.html#initialise-the-document-object (Step 7) | |
| if (frame->isMainFrame()) { | |
| if (auto crossOriginOpenerPolicy = documentLoader->crossOriginOpenerPolicy()) | |
| document->setCrossOriginOpenerPolicy(WTF::move(*crossOriginOpenerPolicy)); | |
| } | |
| if (!m_stateMachine.creatingInitialEmptyDocument() && document->url().protocolIsInHTTPFamily()) | |
| logCrossOriginIsolationDiagnostics(document, documentLoader->response(), frame->isMainFrame()); | |
| if (auto integrityPolicy = documentLoader->integrityPolicy()) | |
| document->setIntegrityPolicy(WTF::move(integrityPolicy)); | |
| if (auto integrityPolicyReportOnly = documentLoader->integrityPolicyReportOnly()) | |
| document->setIntegrityPolicyReportOnly(WTF::move(integrityPolicyReportOnly)); | |
| navigationType = documentLoader->triggeringAction().navigationAPIType(); | |
| } | |
| if (document->settings().navigationAPIEnabled() && document->window() && !document->securityOrigin().isOpaque()) | |
| protect(protect(document->window())->navigation())->initializeForNewWindow(navigationType, previousWindow); | |
| history().restoreDocumentState(); | |
| } | |
| void FrameLoader::finishedParsing() | |
| { | |
| LOG(Loading, "WebCoreLoading frame %" PRIu64 ": Finished parsing", m_frame->frameID().toUInt64()); | |
| Ref frame = m_frame; | |
| frame->injectUserScripts(UserScriptInjectionTime::DocumentEnd); | |
| if (m_stateMachine.creatingInitialEmptyDocument()) | |
| return; | |
| m_client->dispatchDidFinishDocumentLoad(); | |
| scrollToFragmentWithParentBoundary(protect(frame->document())->url()); | |
| checkCompleted(); | |
| RefPtr view = frame->view(); | |
| if (!view) | |
| return; // We are being destroyed by something checkCompleted called. | |
| // Check if the scrollbars are really needed for the content. | |
| // If not, remove them, relayout, and repaint. | |
| view->restoreScrollbar(); | |
| } | |
| void FrameLoader::loadDone(LoadCompletionType type) | |
| { | |
| if (type == LoadCompletionType::Finish) | |
| checkCompleted(); | |
| else | |
| scheduleCheckCompleted(); | |
| } | |
| void FrameLoader::subresourceLoadDone(LoadCompletionType type) | |
| { | |
| if (type == LoadCompletionType::Finish) | |
| checkLoadComplete(); | |
| else | |
| scheduleCheckLoadComplete(); | |
| } | |
| bool FrameLoader::allChildrenAreComplete() const | |
| { | |
| for (RefPtr child = m_frame->tree().firstChild(); child; child = child->tree().nextSibling()) { | |
| if (child->preventsParentFromBeingComplete()) | |
| return false; | |
| } | |
| return true; | |
| } | |
| bool FrameLoader::allAncestorsAreComplete() const | |
| { | |
| for (Ref localAncestor : inclusiveAncestorFrames<LocalFrame>(m_frame.get())) { | |
| if (!localAncestor->loader().m_isComplete) | |
| return false; | |
| } | |
| return true; | |
| } | |
| void FrameLoader::checkCompleted() | |
| { | |
| RELEASE_ASSERT_WITH_SECURITY_IMPLICATION(ScriptDisallowedScope::InMainThread::isScriptAllowed()); | |
| m_shouldCallCheckCompleted = false; | |
| // Have we completed before? | |
| if (m_isComplete) | |
| return; | |
| Ref frame = m_frame; | |
| Ref<Document> document = *frame->document(); | |
| // 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 (document->inRenderTreeUpdate()) { | |
| scheduleCheckCompleted(); | |
| return; | |
| } | |
| // Still waiting for images/scripts? | |
| if (document->cachedResourceLoader().requestCount()) | |
| return; | |
| auto isBlockedFromCompleting = [&] { | |
| if (document->parsing()) | |
| return true; | |
| // Still waiting for elements that don't go through a FrameLoader? | |
| if (document->isDelayingLoadEvent()) | |
| return true; | |
| RefPtr scriptableParser = document->scriptableDocumentParser(); | |
| if (scriptableParser && scriptableParser->hasScriptsWaitingForStylesheets()) | |
| return true; | |
| // Any frame that hasn't completed yet? | |
| return !allChildrenAreComplete(); | |
| }; | |
| if (isBlockedFromCompleting()) | |
| return; | |
| // OK, completed. | |
| m_requestedHistoryItem = nullptr; | |
| document->setReadyState(Document::ReadyState::Complete); | |
| // The readystatechange dispatch above ran author script, so both of these can have changed | |
| // underneath us. | |
| if (m_isComplete) | |
| return; | |
| if (isBlockedFromCompleting()) | |
| return; | |
| m_isComplete = true; | |
| checkCallImplicitClose(); // if we didn't do it before | |
| protect(frame->navigationScheduler())->startTimer(); | |
| completed(); | |
| if (frame->page()) | |
| checkLoadComplete(); | |
| } | |
| void FrameLoader::checkTimerFired() | |
| { | |
| checkCompletenessNow(); | |
| } | |
| void FrameLoader::startCrossOriginParentSyntheticSameDocLoadEventTimer() | |
| { | |
| static constexpr Seconds crossOriginParentSyntheticSameDocLoadEventDelay { 100_ms }; | |
| if (m_crossOriginParentSyntheticSameDocLoadEventTimer.isActive()) | |
| m_crossOriginParentSyntheticSameDocLoadEventTimer.stop(); | |
| m_crossOriginParentSyntheticSameDocLoadEventTimer.startOneShot(crossOriginParentSyntheticSameDocLoadEventDelay); | |
| } | |
| void FrameLoader::crossOriginParentSyntheticSameDocLoadEventTimerFired() | |
| { | |
| protect(m_frame)->dispatchLoadEventToParent(); | |
| } | |
| void FrameLoader::checkCompletenessNow() | |
| { | |
| Ref frame = m_frame.get(); | |
| if (auto* page = frame->page()) { | |
| if (page->defersLoading()) | |
| return; | |
| } | |
| if (m_shouldCallCheckCompleted) | |
| checkCompleted(); | |
| if (m_shouldCallCheckLoadComplete) | |
| checkLoadComplete(); | |
| } | |
| void FrameLoader::startCheckCompleteTimer() | |
| { | |
| if (!(m_shouldCallCheckCompleted || m_shouldCallCheckLoadComplete)) | |
| return; | |
| if (m_checkTimer.isActive()) | |
| return; | |
| m_checkTimer.startOneShot(0_s); | |
| } | |
| void FrameLoader::scheduleCheckCompleted() | |
| { | |
| m_shouldCallCheckCompleted = true; | |
| startCheckCompleteTimer(); | |
| } | |
| void FrameLoader::scheduleCheckLoadComplete() | |
| { | |
| m_shouldCallCheckLoadComplete = true; | |
| startCheckCompleteTimer(); | |
| } | |
| void FrameLoader::checkCallImplicitClose() | |
| { | |
| if (m_didCallImplicitClose) | |
| return; | |
| Ref document = *m_frame->document(); | |
| if (document->parsing() || document->isDelayingLoadEvent()) | |
| return; | |
| if (!allChildrenAreComplete()) | |
| return; // still got a frame running -> too early | |
| m_didCallImplicitClose = true; | |
| m_wasUnloadEventEmitted = false; | |
| document->implicitClose(); | |
| } | |
| void FrameLoader::loadURLIntoChildFrame(const URL& url, const String& referer, LocalFrame& childFrame) | |
| { | |
| FRAMELOADER_RELEASE_LOG_FORWARDABLE(FrameLoaderLoadUrlIntoChildFrame); | |
| #if ENABLE(WEB_ARCHIVE) || ENABLE(MHTML) | |
| if (RefPtr activeLoader = activeDocumentLoader()) { | |
| if (RefPtr subframeArchive = activeLoader->popArchiveForSubframe(childFrame.tree().uniqueName(), url)) { | |
| childFrame.loader().loadArchive(subframeArchive.releaseNonNull()); | |
| return; | |
| } | |
| } | |
| #endif | |
| // 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 (isBackForwardLoadType(loadType()) && !m_frame->document()->loadEventFinished()) { | |
| if (m_frame->page() && m_frame->page()->settings().useUIProcessForBackForwardItemLoading()) { | |
| m_client->dispatchBackForwardItemLoading(url, referer, childFrame); | |
| return; | |
| } | |
| if (loadChildHistoryItemIntoFrame(childFrame)) | |
| return; | |
| } | |
| continueLoadURLIntoChildFrame(url, referer, childFrame); | |
| } | |
| bool FrameLoader::loadChildHistoryItemIntoFrame(LocalFrame& childFrame) | |
| { | |
| ASSERT(isBackForwardLoadType(loadType())); | |
| ASSERT(!m_frame->document()->loadEventFinished()); | |
| ASSERT(!m_frame->page() || !m_frame->page()->settings().useUIProcessForBackForwardItemLoading()); | |
| RefPtr parentItem = history().currentItem(); | |
| if (!parentItem || !parentItem->children().size()) | |
| return false; | |
| RefPtr childItem = parentItem->childItemWithTarget(childFrame.tree().uniqueName()); | |
| if (!childItem) | |
| return false; | |
| Ref childLoader = childFrame.loader(); | |
| childItem->setFrameID(childFrame.frameID()); | |
| childLoader->m_requestedHistoryItem = childItem; | |
| childLoader->loadDifferentDocumentItem(*childItem, nullptr, loadType(), MayAttemptCacheOnlyLoadForFormSubmissionItem, ShouldTreatAsContinuingLoad::No); | |
| return true; | |
| } | |
| void FrameLoader::continueLoadURLIntoChildFrame(const URL& url, const String& referer, LocalFrame& childFrame) | |
| { | |
| RefPtr lexicalFrame = lexicalFrameFromCommonVM(); | |
| auto initiatedByMainFrame = lexicalFrame && lexicalFrame->isMainFrame() ? InitiatedByMainFrame::Yes : InitiatedByMainFrame::Unknown; | |
| RefPtr document = m_frame->document(); | |
| FrameLoadRequest frameLoadRequest { *document, protect(document->securityOrigin()), { URL { url } }, selfTargetFrameName(), initiatedByMainFrame }; | |
| frameLoadRequest.setNewFrameOpenerPolicy(NewFrameOpenerPolicy::Suppress); | |
| frameLoadRequest.setLockBackForwardList(LockBackForwardList::Yes); | |
| frameLoadRequest.setIsInitialFrameSrcLoad(true); | |
| childFrame.loader().loadURL(WTF::move(frameLoadRequest), referer, FrameLoadType::RedirectWithLockedBackForwardList, nullptr, { }, std::nullopt, [] { }); | |
| } | |
| #if ENABLE(WEB_ARCHIVE) || ENABLE(MHTML) | |
| void FrameLoader::loadArchive(Ref<Archive>&& archive) | |
| { | |
| FRAMELOADER_RELEASE_LOG(ResourceLoading, "loadArchive: frame load started"); | |
| RefPtr mainResource = archive->mainResource(); | |
| ASSERT(mainResource); | |
| if (!mainResource) | |
| return; | |
| ResourceResponse response(URL(), String { mainResource->mimeType() }, mainResource->data().size(), String { mainResource->textEncoding() }); | |
| SubstituteData substituteData(&mainResource->data(), URL(), WTF::move(response), SubstituteData::SessionHistoryVisibility::Hidden); | |
| ResourceRequest request(URL { mainResource->url() }); | |
| Ref documentLoader = m_client->createDocumentLoader(WTF::move(request), WTF::move(substituteData)); | |
| documentLoader->setArchive(WTF::move(archive)); | |
| load(documentLoader, nullptr); | |
| } | |
| #endif // ENABLE(WEB_ARCHIVE) || ENABLE(MHTML) | |
| RefPtr<LocalFrame> FrameLoader::nonSrcdocFrame() 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.ptr(); | |
| while (frame && is<LocalFrame>(*frame) && downcast<LocalFrame>(*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 nullptr; | |
| return dynamicDowncast<LocalFrame>(*frame); | |
| } | |
| String FrameLoader::outgoingReferrer() const | |
| { | |
| RefPtr localFrame = nonSrcdocFrame(); | |
| if (!localFrame) | |
| return emptyString(); | |
| return localFrame->loader().m_outgoingReferrer; | |
| } | |
| URL FrameLoader::outgoingReferrerURL() | |
| { | |
| RefPtr localFrame = nonSrcdocFrame(); | |
| if (!localFrame) | |
| return URL { emptyString() }; | |
| auto& loader = localFrame->loader(); | |
| if (loader.m_outgoingReferrerURL.isValid()) | |
| return loader.m_outgoingReferrerURL; | |
| URL result { loader.m_outgoingReferrer }; | |
| loader.m_outgoingReferrerURL = result; | |
| return result; | |
| } | |
| String FrameLoader::outgoingOrigin() const | |
| { | |
| return protect(protect(m_frame->document())->securityOrigin())->toString(); | |
| } | |
| bool FrameLoader::checkIfFormActionAllowedByCSP(const URL& url, bool didReceiveRedirectResponse, const URL& preRedirectURL) const | |
| { | |
| if (m_submittedFormURL.isEmpty()) | |
| return true; | |
| Ref document = *m_frame->document(); | |
| auto redirectResponseReceived = didReceiveRedirectResponse ? ContentSecurityPolicy::RedirectResponseReceived::Yes : ContentSecurityPolicy::RedirectResponseReceived::No; | |
| return protect(document->contentSecurityPolicy())->allowFormAction(url, document->currentParserSourcePosition(), redirectResponseReceived, preRedirectURL); | |
| } | |
| void FrameLoader::provisionalLoadStarted() | |
| { | |
| if (m_stateMachine.firstLayoutDone()) | |
| m_stateMachine.advanceTo(FrameLoaderStateMachine::CommittedFirstRealLoad); | |
| Ref frame = m_frame.get(); | |
| protect(frame->navigationScheduler())->cancel(NewLoadInProgress::Yes); | |
| m_client->provisionalLoadStarted(); | |
| if (frame->isMainFrame()) { | |
| tracePoint(MainResourceLoadDidStartProvisional, PAGE_ID); | |
| if (RefPtr page = frame->page()) | |
| page->didStartProvisionalLoad(); | |
| } | |
| } | |
| void FrameLoader::resetMultipleFormSubmissionProtection() | |
| { | |
| m_submittedFormURL = URL(); | |
| } | |
| void FrameLoader::updateFirstPartyForCookies() | |
| { | |
| RefPtr page = m_frame->page(); | |
| if (!page) | |
| return; | |
| auto firstPartyForCookies = page->mainFrameURL(); | |
| if (SecurityPolicy::shouldInheritSecurityOriginFromOwner(firstPartyForCookies)) { | |
| if (RefPtr opener = dynamicDowncast<LocalFrame>(page->mainFrame().opener())) { | |
| if (RefPtr openerDocument = opener->document()) | |
| firstPartyForCookies = openerDocument->firstPartyForCookies(); | |
| } | |
| } | |
| setFirstPartyForCookies(firstPartyForCookies); | |
| } | |
| void FrameLoader::setFirstPartyForCookies(const URL& url) | |
| { | |
| Ref frame = m_frame.get(); | |
| for (Ref localFrame : inclusiveDescendantFrames<LocalFrame>(frame)) { | |
| RefPtr document = localFrame->document(); | |
| if (!document) | |
| continue; | |
| document->setFirstPartyForCookies(document->hasUnpartitionedStorageAccess() ? protect(document->securityOrigin())->toURL() : url); | |
| } | |
| RegistrableDomain registrableDomain(url); | |
| for (Ref localFrame : inclusiveDescendantFrames<LocalFrame>(frame)) { | |
| RefPtr document = localFrame->document(); | |
| if (!document) | |
| continue; | |
| if (document->hasUnpartitionedStorageAccess()) | |
| document->setSiteForCookies(protect(document->securityOrigin())->toURL()); | |
| else if (SecurityPolicy::shouldInheritSecurityOriginFromOwner(document->url())) { | |
| if (RefPtr parent = dynamicDowncast<LocalFrame>(localFrame->tree().parent())) | |
| document->setSiteForCookies(parent->document()->siteForCookies()); | |
| } else if (registrableDomain.matches(document->url())) | |
| document->setSiteForCookies(url); | |
| } | |
| } | |
| static NavigationNavigationType LEGACY_NODELETE determineNavigationType(FrameLoadType loadType, NavigationHistoryBehavior historyHandling) | |
| { | |
| if (historyHandling == NavigationHistoryBehavior::Push) | |
| return NavigationNavigationType::Push; | |
| if (historyHandling == NavigationHistoryBehavior::Replace) | |
| return NavigationNavigationType::Replace; | |
| if (historyHandling == NavigationHistoryBehavior::Reload) | |
| return NavigationNavigationType::Reload; | |
| if (isBackForwardLoadType(loadType)) | |
| return NavigationNavigationType::Traverse; | |
| if (isReload(loadType)) | |
| return NavigationNavigationType::Reload; | |
| if (loadType == FrameLoadType::MultipartReplace || loadType == FrameLoadType::NavigationAPIReplace) | |
| return NavigationNavigationType::Replace; | |
| return NavigationNavigationType::Push; | |
| } | |
| // https://html.spec.whatwg.org/multipage/browsing-the-web.html#url-and-history-update-steps | |
| void FrameLoader::updateURLAndHistory(const URL& newURL, RefPtr<SerializedScriptValue>&& stateObject, NavigationHistoryBehavior historyHandling) | |
| { | |
| ASSERT(m_frame->document() && documentLoader()); | |
| if (documentLoader()->isInitialAboutBlank() == IsInitialAboutBlank::Yes) | |
| historyHandling = NavigationHistoryBehavior::Replace; | |
| Ref history = m_history.get(); | |
| // https://html.spec.whatwg.org/multipage/browsing-the-web.html#restore-the-history-object-state | |
| // FIXME: Implement "restore the history object state" deserializing (step 2). | |
| // Note: Implement "otherwise activeEntry's classic history API state" (step 3) if a caller needs that (so far | |
| // callers always set stateObject explicitly). | |
| protect(m_frame->document())->updateURLForPushOrReplaceState(newURL); | |
| if (historyHandling == NavigationHistoryBehavior::Replace) { | |
| history->replaceState(WTF::move(stateObject), newURL.string()); | |
| protect(client())->dispatchDidReplaceStateWithinPage(); | |
| } else { | |
| history->pushState(WTF::move(stateObject), newURL.string()); | |
| protect(client())->dispatchDidPushStateWithinPage(); | |
| } | |
| } | |
| // 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(URL url, RefPtr<SerializedScriptValue> stateObject, const SecurityOrigin* requesterOrigin, bool isNewNavigation, NavigationHistoryBehavior historyHandling) | |
| { | |
| FRAMELOADER_RELEASE_LOG(ResourceLoading, "loadInSameDocument: frame load started"); | |
| // If we have a state object, we cannot also be a new navigation. | |
| ASSERT(!stateObject || (stateObject && !isNewNavigation)); | |
| m_errorOccurredInLoading = false; | |
| RefPtr document = m_frame->document(); | |
| // Update the data source's request with the new URL to fake the URL change | |
| URL oldURL = document->url(); | |
| document->setURL(URL { url }); | |
| setOutgoingReferrer(url); | |
| protect(m_documentLoader)->replaceRequestURLForSameDocumentNavigation(url); | |
| if (isNewNavigation && !shouldTreatURLAsSameAsCurrent(requesterOrigin, url) && !stateObject) { | |
| // NB: must happen after replaceRequestURLForSameDocumentNavigation(), since we add | |
| // based on the current request. Must also happen before we openURL and displace the | |
| // scroll position, since adding the BF item will save away scroll state. | |
| // NB2: If we were loading a long, slow doc, and the user fragment navigated before | |
| // it was done, currItem is now set the that slow doc, and prevItem is whatever was | |
| // before it. Adding the b/f item will bump the slow doc down to prevItem, even | |
| // though its load is not yet done. I think this all works out OK, for one because | |
| // we have already saved away the scroll and doc state for the long slow load, | |
| // but it's not an obvious case. | |
| auto wasCreatedByJSWithoutUserInteraction = HistoryController::WasCreatedByJSWithoutUserInteraction::No; | |
| if (!document->hasRecentUserInteractionForNavigationFromJS() && !documentLoader()->triggeringAction().isRequestFromClientOrUserInput()) | |
| wasCreatedByJSWithoutUserInteraction = HistoryController::WasCreatedByJSWithoutUserInteraction::Yes; | |
| if (historyHandling == NavigationHistoryBehavior::Replace) | |
| history().updateBackForwardListForReplaceState(nullptr, url.string(), wasCreatedByJSWithoutUserInteraction); | |
| else | |
| history().updateBackForwardListForFragmentScroll(wasCreatedByJSWithoutUserInteraction); | |
| } | |
| bool hashChange = equalIgnoringFragmentIdentifier(url, oldURL) && !equalRespectingNullity(url.fragmentIdentifier(), oldURL.fragmentIdentifier()); | |
| history().updateForSameDocumentNavigation(); | |
| auto navigationType = determineNavigationType(m_loadType, historyHandling); | |
| if (document->settings().navigationAPIEnabled() && document->window() && history().currentItem()) { | |
| Ref navigation = protect(document->window())->navigation(); | |
| // Skip updateForNavigation() for intercepted traverse navigations, as it was already called after intercept detection in innerDispatchNavigateEvent(). | |
| if (navigationType != NavigationNavigationType::Traverse || !navigation->hasInterceptedOngoingNavigateEvent()) | |
| navigation->updateForNavigation(*history().currentItem(), navigationType, ShouldCopyStateObjectFromCurrentEntry::Yes); | |
| } | |
| // If we were in the autoscroll/panScroll mode we want to stop it before following the link to the anchor | |
| if (hashChange) | |
| protect(m_frame->eventHandler())->stopAutoscrollTimer(); | |
| // It's important to model this as a load that starts and immediately finishes. | |
| // Otherwise, the parent frame may think we never finished loading. | |
| started(); | |
| if (RefPtr ownerElement = m_frame->ownerElement()) { | |
| CheckedPtr ownerRenderer = dynamicDowncast<RenderWidget>(ownerElement->renderer()); | |
| RefPtr view = m_frame->view(); | |
| if (ownerRenderer && view) | |
| ownerRenderer->setWidget(WTF::move(view)); | |
| } | |
| // We need to scroll to the fragment whether or not a hash change occurred, since | |
| // the user might have scrolled since the previous navigation. | |
| scrollToFragmentWithParentBoundary(url, isNewNavigation); | |
| m_isComplete = false; | |
| checkCompleted(); | |
| if (isNewNavigation) { | |
| // This will clear previousItem from the rest of the frame tree that didn't | |
| // doing any loading. We need to make a pass on this now, since for fragment | |
| // navigation we'll not go through a real load and reach Completed state. | |
| checkLoadComplete(); | |
| } | |
| m_client->dispatchDidNavigateWithinPage(); | |
| document->statePopped(stateObject ? stateObject.releaseNonNull() : SerializedScriptValue::nullValue()); | |
| m_client->dispatchDidPopStateWithinPage(); | |
| if (hashChange) { | |
| document->enqueueHashchangeEvent(oldURL.string(), url.string()); | |
| m_client->dispatchDidChangeLocationWithinPage(); | |
| } | |
| // Asynchronously dispatch a synthetic load event on the iframe owner so a cross-origin | |
| // parent cannot distinguish same-doc from cross-doc navigations by load-event timing. | |
| RefPtr parentFrame = m_frame->tree().parent(); | |
| RefPtr localParentFrame = dynamicDowncast<LocalFrame>(parentFrame.get()); | |
| if (parentFrame | |
| && (document->processingLoadEvent() || document->loadEventFinished()) | |
| && (!localParentFrame || !protect(document->securityOrigin())->isSameOriginAs(protect(protect(localParentFrame->document())->securityOrigin())))) | |
| startCrossOriginParentSyntheticSameDocLoadEventTimer(); | |
| // LocalFrameLoaderClient::didFinishLoad() tells the internal load delegate the load finished with no error | |
| m_client->didFinishLoad(); | |
| } | |
| void FrameLoader::completed() | |
| { | |
| Ref frame = m_frame.get(); | |
| for (Ref descendant : descendantFrames(frame)) | |
| protect(descendant->navigationScheduler())->startTimer(); | |
| if (RefPtr parent = frame->tree().parent()) { | |
| if (RefPtr localParent = dynamicDowncast<LocalFrame>(parent.releaseNonNull())) | |
| localParent->loader().checkCompleted(); | |
| } | |
| if (RefPtr view = frame->view()) | |
| view->maintainScrollPositionAtAnchor(nullptr); | |
| } | |
| void FrameLoader::started() | |
| { | |
| for (Ref localFrame : inclusiveAncestorFrames<LocalFrame>(m_frame.get())) | |
| localFrame->loader().m_isComplete = false; | |
| } | |
| void FrameLoader::prepareForLoadStart() | |
| { | |
| FRAMELOADER_RELEASE_LOG_FORWARDABLE(FrameLoaderPrepareForLoadStart); | |
| protect(m_progressTracker)->progressStarted(); | |
| m_client->dispatchDidStartProvisionalLoad(); | |
| if (AXObjectCache::accessibilityEnabled()) { | |
| if (CheckedPtr cache = protect(m_frame->document())->existingAXObjectCache()) { | |
| auto loadingEvent = loadType() == FrameLoadType::Reload ? AXLoadingEvent::Reloaded : AXLoadingEvent::Started; | |
| cache->frameLoadingEventNotification(protect(m_frame).ptr(), loadingEvent); | |
| } | |
| } | |
| } | |
| void FrameLoader::setupForMultipartReplace() | |
| { | |
| m_client->revertToProvisionalState(protect(documentLoader())); | |
| setState(FrameState::Provisional); | |
| m_provisionalDocumentLoader = m_documentLoader; | |
| FRAMELOADER_RELEASE_LOG(ResourceLoading, "setupForMultipartReplace: Setting provisional document loader (m_provisionalDocumentLoader=%p)", m_provisionalDocumentLoader.get()); | |
| m_documentLoader = nullptr; | |
| detachChildren(); | |
| } | |
| void FrameLoader::loadFrameRequest(FrameLoadRequest&& request, Event* event, RefPtr<const FormSubmission>&& formSubmission, std::optional<PrivateClickMeasurement>&& privateClickMeasurement) | |
| { | |
| FRAMELOADER_RELEASE_LOG_FORWARDABLE(FrameLoaderLoadFrameRequestFrameLoadStarted); | |
| m_errorOccurredInLoading = false; | |
| // Protect frame from getting blown away inside dispatchBeforeLoadEvent in loadWithDocumentLoader. | |
| Ref frame = m_frame.get(); | |
| URL url = request.resourceRequest().url(); | |
| ASSERT(frame->document()); | |
| if (!protect(request.requesterSecurityOrigin())->canDisplay(url, OriginAccessPatternsForWebProcess::singleton())) { | |
| FRAMELOADER_RELEASE_LOG(ResourceLoading, "loadFrameRequest: canceling - Not allowed to load local resource"); | |
| reportLocalLoadFailed(frame.ptr(), url.stringCenterEllipsizedToLength()); | |
| return; | |
| } | |
| if (isIPAddressDisallowed(url)) { | |
| FRAMELOADER_RELEASE_LOG(ResourceLoading, "loadFrameRequest: canceling - IP address is not allowed"); | |
| reportBlockedLoadFailed(frame, url); | |
| return; | |
| } | |
| URL argsReferrer; | |
| String argsReferrerString = request.resourceRequest().httpReferrer(); | |
| if (argsReferrerString.isEmpty()) | |
| argsReferrer = outgoingReferrerURL(); | |
| else | |
| argsReferrer = URL { argsReferrerString }; | |
| ReferrerPolicy referrerPolicy = request.referrerPolicy(); | |
| if (referrerPolicy == ReferrerPolicy::EmptyString) | |
| referrerPolicy = frame->document()->referrerPolicy(); | |
| String referrer = SecurityPolicy::generateReferrerHeader(referrerPolicy, url, argsReferrer, OriginAccessPatternsForWebProcess::singleton()); | |
| FrameLoadType loadType; | |
| if (request.resourceRequest().cachePolicy() == ResourceRequestCachePolicy::ReloadIgnoringCacheData) | |
| loadType = FrameLoadType::Reload; | |
| else if (request.lockBackForwardList() == LockBackForwardList::Yes) | |
| loadType = FrameLoadType::RedirectWithLockedBackForwardList; | |
| else if (request.isFromNavigationAPI() && request.navigationHistoryBehavior() == NavigationHistoryBehavior::Replace) | |
| loadType = FrameLoadType::NavigationAPIReplace; | |
| else | |
| loadType = FrameLoadType::Standard; | |
| RefPtr formState = formSubmission ? protect(formSubmission->state()) : nullptr; | |
| auto completionHandler = [frame, formState = WeakPtr { formState }, frameName = request.frameName()] { | |
| // FIXME: It's possible this targetFrame will not be the same frame that was targeted by the actual | |
| // load if frame names have changed. | |
| RefPtr sourceFrame = formState ? formState->sourceDocument().frame() : frame.ptr(); | |
| if (!sourceFrame) | |
| sourceFrame = frame.ptr(); | |
| RefPtr targetFrame = sourceFrame->loader().findFrameForNavigation(frameName); | |
| if (targetFrame && targetFrame != sourceFrame) { | |
| if (RefPtr page = targetFrame->page(); page && isInVisibleAndActivePage(*sourceFrame)) | |
| page->chrome().focus(); | |
| } | |
| }; | |
| auto finishLoadFrameRequest = [referrer, event = RefPtr { event }, loadType] (Ref<LocalFrame>&& frame, FrameLoadRequest&& request, RefPtr<const FormSubmission>&& formSubmission, std::optional<PrivateClickMeasurement>&& privateClickMeasurement, CompletionHandler<void()>&& completionHandler) mutable { | |
| if (request.resourceRequest().httpMethod() == "POST"_s) | |
| frame->loader().loadPostRequest(WTF::move(request), referrer, loadType, event.get(), WTF::move(formSubmission), WTF::move(completionHandler)); | |
| else | |
| frame->loader().loadURL(WTF::move(request), referrer, loadType, event.get(), WTF::move(formSubmission), WTF::move(privateClickMeasurement), WTF::move(completionHandler)); | |
| }; | |
| if (loadType == FrameLoadType::Reload) { | |
| if (m_frame->document() && m_frame->document()->settings().navigationAPIEnabled()) { | |
| if (RefPtr window = frame->document()->window()) { | |
| RefPtr<SerializedScriptValue> stateObject; | |
| if (RefPtr currentItem = history().currentItem()) | |
| stateObject = currentItem->navigationAPIStateObject(); | |
| if (dispatchNavigateEvent(loadType, request, false, formState.get(), event, stateObject.get()).isNotCompleted()) | |
| return; | |
| if (!frame->page()) | |
| return; | |
| finishLoadFrameRequest(WTF::move(frame), WTF::move(request), WTF::move(formSubmission), WTF::move(privateClickMeasurement), WTF::move(completionHandler)); | |
| } | |
| return; | |
| } | |
| } | |
| finishLoadFrameRequest(WTF::move(frame), WTF::move(request), WTF::move(formSubmission), WTF::move(privateClickMeasurement), WTF::move(completionHandler)); | |
| } | |
| static ShouldOpenExternalURLsPolicy shouldOpenExternalURLsPolicyToApply(LocalFrame& currentFrame, InitiatedByMainFrame initiatedByMainFrame, ShouldOpenExternalURLsPolicy propagatedPolicy) | |
| { | |
| if (UserGestureIndicator::processingUserGesture()) | |
| return ShouldOpenExternalURLsPolicy::ShouldAllow; | |
| if (initiatedByMainFrame == InitiatedByMainFrame::Yes) | |
| return propagatedPolicy; | |
| if (!currentFrame.isMainFrame()) | |
| return ShouldOpenExternalURLsPolicy::ShouldNotAllow; | |
| return propagatedPolicy; | |
| } | |
| static ShouldOpenExternalURLsPolicy shouldOpenExternalURLsPolicyToApply(LocalFrame& currentFrame, const FrameLoadRequest& frameLoadRequest) | |
| { | |
| return shouldOpenExternalURLsPolicyToApply(currentFrame, frameLoadRequest.initiatedByMainFrame(), frameLoadRequest.shouldOpenExternalURLsPolicy()); | |
| } | |
| static void applyShouldOpenExternalURLsPolicyToNewDocumentLoader(LocalFrame& frame, DocumentLoader& documentLoader, InitiatedByMainFrame initiatedByMainFrame, ShouldOpenExternalURLsPolicy propagatedPolicy) | |
| { | |
| documentLoader.setShouldOpenExternalURLsPolicy(shouldOpenExternalURLsPolicyToApply(frame, initiatedByMainFrame, propagatedPolicy)); | |
| } | |
| static void applyShouldOpenExternalURLsPolicyToNewDocumentLoader(LocalFrame& frame, DocumentLoader& documentLoader, const FrameLoadRequest& frameLoadRequest) | |
| { | |
| documentLoader.setShouldOpenExternalURLsPolicy(shouldOpenExternalURLsPolicyToApply(frame, frameLoadRequest)); | |
| } | |
| bool FrameLoader::isNavigationAllowed() const | |
| { | |
| return m_pageDismissalEventBeingDispatched == PageDismissalType::None && !m_frame->script().willReplaceWithResultOfExecutingJavascriptURL() && NavigationDisabler::isNavigationAllowed(m_frame.get()); | |
| } | |
| bool FrameLoader::isStopLoadingAllowed() const | |
| { | |
| return m_pageDismissalEventBeingDispatched == PageDismissalType::None; | |
| } | |
| void FrameLoader::loadURL(FrameLoadRequest&& frameLoadRequest, const String& referrer, FrameLoadType newLoadType, Event* event, RefPtr<const FormSubmission>&& formSubmission, std::optional<PrivateClickMeasurement>&& privateClickMeasurement, CompletionHandler<void()>&& completionHandler) | |
| { | |
| FRAMELOADER_RELEASE_LOG_FORWARDABLE(FrameLoaderLoadUrl); | |
| ASSERT(frameLoadRequest.resourceRequest().httpMethod() == "GET"_s); | |
| m_errorOccurredInLoading = false; | |
| CompletionHandlerCallingScope completionHandlerCaller(WTF::move(completionHandler)); | |
| if (m_inStopAllLoaders || m_inClearProvisionalLoadForPolicyCheck) | |
| return; | |
| Ref frame = m_frame.get(); | |
| // Anchor target is ignored when the download attribute is set since it will download the hyperlink rather than follow it. | |
| auto effectiveFrameName = frameLoadRequest.downloadAttribute().isNull() ? frameLoadRequest.frameName() : nullAtom(); | |
| bool isFormSubmission = !!formSubmission; | |
| // The search for a target frame is done earlier in the case of form submission. | |
| RefPtr effectiveTargetFrame = findFrameForNavigation(effectiveFrameName); | |
| if (RefPtr remoteEffectiveTargetFrame = dynamicDowncast<RemoteFrame>(effectiveTargetFrame)) { | |
| updateRequestAndAddExtraFields(*remoteEffectiveTargetFrame, frameLoadRequest.resourceRequest(), IsMainResource::Yes, newLoadType, ShouldUpdateAppInitiatedValue::Yes, FrameLoader::IsServiceWorkerNavigationLoad::No, WillOpenInNewWindow::No, protect(frameLoadRequest.requester()).ptr()); | |
| // The local-frame path below attaches Private Click Measurement to the NavigationAction once the load | |
| // reaches the main frame (see the frame->isMainFrame() check below). When the main frame is remote (site | |
| // isolation) that path is never reached, so hand the data to the RemoteFrameClient directly to be | |
| // re-attached to the cross-process NavigationAction. | |
| remoteEffectiveTargetFrame->client().changeLocation(WTF::move(frameLoadRequest), remoteEffectiveTargetFrame->isMainFrame() ? WTF::move(privateClickMeasurement) : std::nullopt); | |
| return; | |
| } | |
| RefPtr targetFrame = isFormSubmission ? nullptr : dynamicDowncast<LocalFrame>(effectiveTargetFrame); | |
| if (targetFrame && targetFrame != frame.ptr()) { | |
| frameLoadRequest.setFrameName(selfTargetFrameName()); | |
| targetFrame->loader().loadURL(WTF::move(frameLoadRequest), referrer, newLoadType, event, WTF::move(formSubmission), WTF::move(privateClickMeasurement), completionHandlerCaller.release()); | |
| return; | |
| } | |
| const URL& newURL = frameLoadRequest.resourceRequest().url(); | |
| ResourceRequest request(URL { newURL }); | |
| if (!referrer.isEmpty()) | |
| request.setHTTPReferrer(referrer); | |
| auto willOpenInNewWindow = !targetFrame && !effectiveFrameName.isEmpty() ? WillOpenInNewWindow::Yes : WillOpenInNewWindow::No; | |
| if (willOpenInNewWindow == WillOpenInNewWindow::No && newLoadType == FrameLoadType::Standard && frame->tree().parent() && documentLoader() && documentLoader()->isInitialAboutBlank() == IsInitialAboutBlank::Yes) { | |
| newLoadType = FrameLoadType::RedirectWithLockedBackForwardList; | |
| frameLoadRequest.setLockBackForwardList(LockBackForwardList::Yes); | |
| } | |
| updateRequestAndAddExtraFields(request, IsMainResource::Yes, newLoadType, ShouldUpdateAppInitiatedValue::Yes, FrameLoader::IsServiceWorkerNavigationLoad::No, willOpenInNewWindow, protect(frameLoadRequest.requester()).ptr()); | |
| ASSERT(newLoadType != FrameLoadType::Same); | |
| if (!isNavigationAllowed()) | |
| return; | |
| NavigationAction action { protect(frameLoadRequest.requester()), request, frameLoadRequest.initiatedByMainFrame(), frameLoadRequest.isRequestFromClientOrUserInput(), newLoadType, isFormSubmission, event, frameLoadRequest.shouldOpenExternalURLsPolicy(), frameLoadRequest.downloadAttribute(), protect(frameLoadRequest.sourceElement()) }; | |
| action.setLockHistory(frameLoadRequest.lockHistory()); | |
| action.setLockBackForwardList(newLoadType == FrameLoadType::NavigationAPIReplace ? LockBackForwardList::Yes : frameLoadRequest.lockBackForwardList()); | |
| action.setShouldReplaceDocumentIfJavaScriptURL(frameLoadRequest.shouldReplaceDocumentIfJavaScriptURL()); | |
| action.setIsInitialFrameSrcLoad(frameLoadRequest.isInitialFrameSrcLoad()); | |
| action.setIsFromNavigationAPI(frameLoadRequest.isFromNavigationAPI()); | |
| action.setNewFrameOpenerPolicy(frameLoadRequest.newFrameOpenerPolicy()); | |
| auto historyHandling = frameLoadRequest.navigationHistoryBehavior(); | |
| RefPtr document = m_frame->document(); | |
| if (!isReload(newLoadType)) { | |
| if (historyHandling == NavigationHistoryBehavior::Auto) { | |
| // https://html.spec.whatwg.org/multipage/browsing-the-web.html#navigate (navigate-convert-to-replace) | |
| // "If url equals navigable's active document's URL, and initiatorOriginSnapshot is | |
| // same origin with navigable's active document's origin, then set historyHandling to 'replace'." | |
| if (document->url() == newURL && protect(frameLoadRequest.requesterSecurityOrigin())->isSameOriginAs(protect(document->securityOrigin()))) | |
| historyHandling = NavigationHistoryBehavior::Replace; | |
| else | |
| historyHandling = NavigationHistoryBehavior::Push; | |
| } | |
| if (newURL.protocolIsJavaScript() || (documentLoader() && documentLoader()->isInitialAboutBlank() == IsInitialAboutBlank::Yes)) | |
| historyHandling = NavigationHistoryBehavior::Replace; | |
| } | |
| frameLoadRequest.setNavigationHistoryBehavior(historyHandling); | |
| action.setNavigationAPIType(determineNavigationType(newLoadType, historyHandling)); | |
| if (privateClickMeasurement && frame->isMainFrame()) | |
| action.setPrivateClickMeasurement(WTF::move(*privateClickMeasurement)); | |
| NewFrameOpenerPolicy openerPolicy = frameLoadRequest.newFrameOpenerPolicy(); | |
| AllowNavigationToInvalidURL allowNavigationToInvalidURL = frameLoadRequest.allowNavigationToInvalidURL(); | |
| if (!targetFrame && !effectiveFrameName.isEmpty()) { | |
| action = action.copyWithShouldOpenExternalURLsPolicy(shouldOpenExternalURLsPolicyToApply(frame, frameLoadRequest)); | |
| // https://html.spec.whatwg.org/#the-rules-for-choosing-a-browsing-context-given-a-browsing-context-name (Step 8.2) | |
| if (protect(frameLoadRequest.requester())->shouldForceNoOpenerBasedOnCOOP()) { | |
| effectiveFrameName = blankTargetFrameName(); | |
| openerPolicy = NewFrameOpenerPolicy::Suppress; | |
| action.setNewFrameOpenerPolicy(NewFrameOpenerPolicy::Suppress); | |
| } | |
| if (frameLoadRequest.resourceRequest().url().protocolIsBlob() && !protect(document->securityOrigin())->isSameOriginAs(protect(document->topOrigin()))) { | |
| effectiveFrameName = blankTargetFrameName(); | |
| openerPolicy = NewFrameOpenerPolicy::Suppress; | |
| } | |
| policyChecker().checkNewWindowPolicy(WTF::move(action), WTF::move(request), WTF::move(formSubmission), effectiveFrameName, [this, protectedThis = Ref { *this }, allowNavigationToInvalidURL, openerPolicy, completionHandler = completionHandlerCaller.release()] (ResourceRequest&& request, WeakPtr<const FormSubmission>&& weakFormSubmission, const AtomString& frameName, const NavigationAction& action, ShouldContinuePolicyCheck shouldContinue) mutable { | |
| continueLoadAfterNewWindowPolicy(WTF::move(request), RefPtr { weakFormSubmission.get() }, frameName, action, shouldContinue, allowNavigationToInvalidURL, openerPolicy); | |
| completionHandler(); | |
| }); | |
| return; | |
| } | |
| RefPtr oldDocumentLoader = m_documentLoader; | |
| bool sameURL = shouldTreatURLAsSameAsCurrent(protect(frameLoadRequest.requesterSecurityOrigin()).ptr(), newURL); | |
| const String& httpMethod = request.httpMethod(); | |
| RefPtr formState = formSubmission ? protect(formSubmission->state()): nullptr; | |
| // Make sure to do scroll to fragment processing even if the URL is | |
| // exactly the same so pages with '#' links and DHTML side effects | |
| // work properly. | |
| if (shouldPerformFragmentNavigation(isFormSubmission, httpMethod, newLoadType, newURL)) { | |
| if (dispatchNavigateEvent(newLoadType, frameLoadRequest, true, formState.get(), event).isNotCompleted()) | |
| return; | |
| oldDocumentLoader->setTriggeringAction(WTF::move(action)); | |
| oldDocumentLoader->setLastCheckedRequest(ResourceRequest()); | |
| policyChecker().stopCheck(); | |
| policyChecker().setLoadType(newLoadType); | |
| RELEASE_ASSERT(!isBackForwardLoadType(newLoadType) || history().provisionalItem()); | |
| policyChecker().checkNavigationPolicy(WTF::move(request), ResourceResponse { } /* redirectResponse */, oldDocumentLoader.get(), WTF::move(formSubmission), [this, protectedThis = Ref { *this }, requesterOrigin = Ref { frameLoadRequest.requesterSecurityOrigin() }, historyHandling] (const ResourceRequest& request, WeakPtr<const FormSubmission>&&, NavigationPolicyDecision navigationPolicyDecision) { | |
| continueFragmentScrollAfterNavigationPolicy(request, requesterOrigin.ptr(), navigationPolicyDecision == NavigationPolicyDecision::ContinueLoad, historyHandling); | |
| }, IsSameDocumentNavigation::Yes, PolicyDecisionMode::Synchronous); | |
| return; | |
| } | |
| bool requesterCanAccessDocument = isPlatformObjectSameOrigin(protect(frameLoadRequest.requesterSecurityOrigin()), frameLoadRequest.requester().agentClusterIdentifier(), protect(document->securityOrigin()), document->agentClusterIdentifier()); | |
| if (requesterCanAccessDocument && newLoadType != FrameLoadType::Reload && !frameLoadRequest.isInitialFrameSrcLoad()) { | |
| // FIXME: Ideally, we'd call dispatchNavigateEvent() directly from continueLoadAfterNavigationPolicy() | |
| // instead of storing a lambda on the NavigationAction. | |
| action.setPendingDispatchNavigateEvent([weakThis = WeakPtr { *this }, newLoadType, frameLoadRequest, formState, event = RefPtr { event }] { | |
| RefPtr protectedThis = weakThis.get(); | |
| if (!protectedThis) | |
| return NavigateEventDispatchResult::aborted(); | |
| return protectedThis->dispatchNavigateEvent(newLoadType, frameLoadRequest, false, formState.get(), event.get()); | |
| }); | |
| } | |
| // Must grab this now, since this load may stop the previous load and clear this flag. | |
| bool isRedirect = m_quickRedirectComing; | |
| loadWithNavigationAction(WTF::move(request), WTF::move(action), newLoadType, WTF::move(formSubmission), allowNavigationToInvalidURL, frameLoadRequest.shouldTreatAsContinuingLoad(), ShouldRestoreFromBackForwardCache::No, std::nullopt, [this, protectedThis = Ref { *this }, isRedirect, sameURL, newLoadType, completionHandler = completionHandlerCaller.release()] mutable { | |
| if (isRedirect) { | |
| m_quickRedirectComing = false; | |
| if (auto* provisionalDocumentLoader = m_provisionalDocumentLoader.get()) | |
| provisionalDocumentLoader->setIsClientRedirect(true); | |
| else if (auto* policyDocumentLoader = m_policyDocumentLoader.get()) | |
| policyDocumentLoader->setIsClientRedirect(true); | |
| } else if (sameURL && !isReload(newLoadType)) { | |
| // 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 = FrameLoadType::Same; | |
| } | |
| completionHandler(); | |
| }); | |
| } | |
| SubstituteData FrameLoader::defaultSubstituteDataForURL(const URL& url) | |
| { | |
| if (!shouldTreatURLAsSrcdocDocument(url)) | |
| return SubstituteData(); | |
| RefPtr iframeElement = dynamicDowncast<HTMLIFrameElement>(m_frame->ownerElement()); | |
| if (!iframeElement) | |
| return SubstituteData(); | |
| auto& srcdoc = iframeElement->attributeWithoutSynchronization(srcdocAttr); | |
| ASSERT(!srcdoc.isNull()); | |
| auto encodedSrcdoc = srcdoc.string().utf8(); | |
| ResourceResponse response(URL(), String { textHTMLContentTypeAtom() }, encodedSrcdoc.length(), "UTF-8"_s); | |
| return SubstituteData(SharedBuffer::create(encodedSrcdoc.span()), URL(), WTF::move(response), iframeElement->srcdocSessionHistoryVisibility()); | |
| } | |
| bool FrameLoader::isDocumentCompletelyLoaded() const | |
| { | |
| return !m_stateMachine.committedFirstRealDocumentLoad() || !m_documentLoader || m_documentLoader->wasOnloadDispatched(); | |
| } | |
| static bool navigationFromAnotherProcessMustReplace(const FrameLoader& frameLoader, const FrameLoadRequest& request) | |
| { | |
| if (request.shouldTreatAsContinuingLoad() != ShouldTreatAsContinuingLoad::YesAfterNavigationPolicyDecision || request.navigationHistoryBehavior() != NavigationHistoryBehavior::Auto || frameLoader.client().isProvisionalFrame()) | |
| return false; | |
| RefPtr documentLoader = frameLoader.documentLoader(); | |
| if (!documentLoader) | |
| return false; | |
| // https://html.spec.whatwg.org/multipage/browsing-the-web.html#the-navigation-must-be-a-replace | |
| if (documentLoader->isInitialAboutBlank() == IsInitialAboutBlank::Yes && protect(frameLoader.frame())->tree().parent()) | |
| return true; | |
| // https://html.spec.whatwg.org/multipage/nav-history-apis.html#location-object-navigate | |
| return request.replaceIfNotCompletelyLoaded() == ReplaceIfNotCompletelyLoaded::Yes && !frameLoader.isDocumentCompletelyLoaded(); | |
| } | |
| void FrameLoader::load(FrameLoadRequest&& request, std::optional<NavigationRequester>&& crossSiteRequester) | |
| { | |
| FRAMELOADER_RELEASE_LOG_FORWARDABLE(FrameLoaderLoadFrameLoadRequest); | |
| m_errorOccurredInLoading = false; | |
| if (m_inStopAllLoaders || m_inClearProvisionalLoadForPolicyCheck) | |
| return; | |
| if (!request.frameName().isEmpty()) { | |
| if (RefPtr frame = dynamicDowncast<LocalFrame>(findFrameForNavigation(request.frameName()))) { | |
| request.setShouldCheckNewWindowPolicy(false); | |
| if (&frame->loader() != this) { | |
| frame->loader().load(WTF::move(request)); | |
| return; | |
| } | |
| } | |
| } | |
| m_provisionalLoadHappeningInAnotherProcess = false; | |
| if (request.shouldCheckNewWindowPolicy()) { | |
| NavigationAction action { request, NavigationType::Other }; | |
| action.setNewFrameOpenerPolicy(request.newFrameOpenerPolicy()); | |
| policyChecker().checkNewWindowPolicy(WTF::move(action), WTF::move(request.resourceRequest()), { }, request.frameName(), [this, protectedThis = Ref { *this }] (ResourceRequest&& request, WeakPtr<const FormSubmission>&& weakFormSubmission, const AtomString& frameName, const NavigationAction& action, ShouldContinuePolicyCheck shouldContinue) { | |
| continueLoadAfterNewWindowPolicy(WTF::move(request), RefPtr { weakFormSubmission.get() }, frameName, action, shouldContinue, AllowNavigationToInvalidURL::Yes, NewFrameOpenerPolicy::Suppress); | |
| }); | |
| return; | |
| } | |
| if (!request.hasSubstituteData()) | |
| request.setSubstituteData(defaultSubstituteDataForURL(request.resourceRequest().url())); | |
| Ref loader = m_client->createDocumentLoader(request.takeResourceRequest(), request.takeSubstituteData(), request.takeOriginalResourceRequest()); | |
| if (auto navigationID = request.navigationID()) | |
| loader->setNavigationID(*navigationID); | |
| loader->setOriginalNavigationStartTime(request.originalNavigationStartTime()); | |
| loader->setIsContentRuleListRedirect(request.isContentRuleListRedirect()); | |
| loader->setIsRequestFromClientOrUserInput(request.isRequestFromClientOrUserInput()); | |
| loader->setHasCrossOriginRedirect(request.hasCrossOriginRedirect()); | |
| loader->setIsContinuingLoad(request.shouldTreatAsContinuingLoad()); | |
| loader->setNavigationHistoryBehavior(request.navigationHistoryBehavior()); | |
| RefPtr<const SecurityOrigin> initiatorOrigin; | |
| if (crossSiteRequester) { | |
| initiatorOrigin = crossSiteRequester->securityOrigin.copyRef(); | |
| loader->setCrossSiteRequester(WTF::move(*crossSiteRequester)); | |
| } else | |
| initiatorOrigin = &request.requesterSecurityOrigin(); | |
| if (auto advancedPrivacyProtections = request.advancedPrivacyProtections()) | |
| loader->setOriginatorAdvancedPrivacyProtections(*advancedPrivacyProtections); | |
| Ref initiator = request.requester(); | |
| addSameSiteInfoToRequestIfNeeded(loader->request(), SecurityPolicy::shouldInheritSecurityOriginFromOwner(initiator->url()) ? nullptr : initiator.ptr()); | |
| applyShouldOpenExternalURLsPolicyToNewDocumentLoader(protect(m_frame), loader, request); | |
| loader->setIsHandledByAboutSchemeHandler(request.isHandledByAboutSchemeHandler()); | |
| if (request.shouldTreatAsContinuingLoad() != ShouldTreatAsContinuingLoad::No) { | |
| loader->setClientRedirectSourceForHistory(request.clientRedirectSourceForHistory()); | |
| if (request.lockBackForwardList() == LockBackForwardList::No && navigationFromAnotherProcessMustReplace(*this, request)) { | |
| loader->setNavigationHistoryBehavior(NavigationHistoryBehavior::Replace); | |
| request.setLockBackForwardList(LockBackForwardList::Yes); | |
| } | |
| if (request.lockBackForwardList() == LockBackForwardList::Yes) { | |
| loader->setIsClientRedirect(true); | |
| m_loadType = FrameLoadType::RedirectWithLockedBackForwardList; | |
| } | |
| } | |
| SetForScope continuingLoadGuard(m_currentLoadContinuingState, request.shouldTreatAsContinuingLoad() != ShouldTreatAsContinuingLoad::No ? LoadContinuingState::ContinuingWithRequest : LoadContinuingState::NotContinuing); | |
| SetForScope crossOriginContentRuleListCancellationGuard(m_needsCancellationForContentRuleListCrossOriginRedirect, request.isContentRuleListRedirect()); | |
| load(loader.get(), initiatorOrigin.get()); | |
| } | |
| void FrameLoader::loadWithNavigationAction(ResourceRequest&& request, NavigationAction&& action, FrameLoadType type, RefPtr<const FormSubmission>&& formSubmission, AllowNavigationToInvalidURL allowNavigationToInvalidURL, ShouldTreatAsContinuingLoad shouldTreatAsContinuingLoad, ShouldRestoreFromBackForwardCache shouldRestoreFromBackForwardCache, std::optional<NavigationIdentifier> navigationIDForCacheOnlyLoadRetry, CompletionHandler<void()>&& completionHandler) | |
| { | |
| FRAMELOADER_RELEASE_LOG_FORWARDABLE(FrameLoaderLoadWithNavigationAction); | |
| m_errorOccurredInLoading = false; | |
| if (request.url().protocolIsJavaScript() && !action.isInitialFrameSrcLoad()) { | |
| if (auto requester = action.requester(); requester && requester->documentIdentifier) { | |
| if (RefPtr requestingDocument = Document::allDocumentsMap().get(requester->documentIdentifier); requestingDocument && requestingDocument->contentSecurityPolicy()) { | |
| if (!protect(requestingDocument->contentSecurityPolicy())->allowJavaScriptURLs(protect(m_frame->document())->url().string(), { }, request.url().string(), nullptr)) | |
| return completionHandler(); | |
| } | |
| } | |
| executeJavaScriptURL(request.url(), action); | |
| return completionHandler(); | |
| } | |
| auto&& substituteData = defaultSubstituteDataForURL(request.url()); | |
| Ref loader = m_client->createDocumentLoader(WTF::move(request), WTF::move(substituteData)); | |
| if (navigationIDForCacheOnlyLoadRetry) { | |
| loader->setNavigationID(*navigationIDForCacheOnlyLoadRetry); | |
| loader->markIsCacheOnlyLoadRetry(); | |
| } | |
| applyShouldOpenExternalURLsPolicyToNewDocumentLoader(protect(m_frame), loader, action.initiatedByMainFrame(), action.shouldOpenExternalURLsPolicy()); | |
| loader->setIsContinuingLoad(shouldTreatAsContinuingLoad); | |
| loader->setIsRequestFromClientOrUserInput(action.isRequestFromClientOrUserInput()); | |
| if (action.lockHistory() == LockHistory::Yes) { | |
| if (RefPtr documentLoader = m_documentLoader) | |
| loader->setClientRedirectSourceForHistory(documentLoader->didCreateGlobalHistoryEntry() ? documentLoader->urlForHistory().string() : documentLoader->clientRedirectSourceForHistory()); | |
| } | |
| loader->setTriggeringAction(WTF::move(action)); | |
| if (m_documentLoader) | |
| loader->setOverrideEncoding(m_documentLoader->overrideEncoding()); | |
| loadWithDocumentLoader(loader.ptr(), type, WTF::move(formSubmission), allowNavigationToInvalidURL, shouldRestoreFromBackForwardCache, WTF::move(completionHandler)); | |
| } | |
| void FrameLoader::load(DocumentLoader& newDocumentLoader, const SecurityOrigin* requesterOrigin) | |
| { | |
| FRAMELOADER_RELEASE_LOG_FORWARDABLE(FrameLoaderLoadDocumentLoader); | |
| m_errorOccurredInLoading = false; | |
| ResourceRequest& r = newDocumentLoader.request(); | |
| // FIXME: Using m_loadType seems wrong here. | |
| // If we are only preparing to load the main resource, that is previous load's load type! | |
| updateRequestAndAddExtraFields(r, IsMainResource::Yes, m_loadType, ShouldUpdateAppInitiatedValue::No); | |
| FrameLoadType type; | |
| if (shouldTreatURLAsSameAsCurrent(requesterOrigin, newDocumentLoader.originalRequest().url())) { | |
| r.setCachePolicy(ResourceRequestCachePolicy::ReloadIgnoringCacheData); | |
| type = FrameLoadType::Same; | |
| } else if (shouldTreatURLAsSameAsCurrent(requesterOrigin, newDocumentLoader.unreachableURL()) && isReload(m_loadType)) | |
| type = m_loadType; | |
| else if ((m_loadType == FrameLoadType::RedirectWithLockedBackForwardList || policyChecker().loadType() == FrameLoadType::RedirectWithLockedBackForwardList) && ((!newDocumentLoader.unreachableURL().isEmpty() && newDocumentLoader.substituteData().isValid()) || shouldTreatCurrentLoadAsContinuingLoad())) | |
| type = FrameLoadType::RedirectWithLockedBackForwardList; | |
| else | |
| type = FrameLoadType::Standard; | |
| if (m_documentLoader) | |
| newDocumentLoader.setOverrideEncoding(m_documentLoader->overrideEncoding()); | |
| // When we loading alternate content for an unreachable URL that we're | |
| // 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 "FrameLoadType::Reload" ... | |
| // shouldn't a more explicit type of reload be defined, that means roughly | |
| // "load without affecting history" ? | |
| if (shouldReloadToHandleUnreachableURL(newDocumentLoader)) { | |
| // shouldReloadToHandleUnreachableURL returns true only when the original load type is back-forward. | |
| // In this case we should save the document state now. Otherwise the state can be lost because load type is | |
| // changed and updateForBackForwardNavigation() will not be called when loading is committed. | |
| history().saveDocumentAndScrollState(); | |
| ASSERT(type == FrameLoadType::Standard); | |
| type = FrameLoadType::Reload; | |
| } | |
| loadWithDocumentLoader(&newDocumentLoader, type, nullptr, AllowNavigationToInvalidURL::Yes); | |
| } | |
| void FrameLoader::loadWithDocumentLoader(DocumentLoader* loader, FrameLoadType type, RefPtr<const FormSubmission>&& formSubmission, AllowNavigationToInvalidURL allowNavigationToInvalidURL, ShouldRestoreFromBackForwardCache shouldRestoreFromBackForwardCache, CompletionHandler<void()>&& completionHandler) | |
| { | |
| FRAMELOADER_RELEASE_LOG_FORWARDABLE(FrameLoaderLoadWithDocumentLoaderFrameLoadStarted); | |
| m_errorOccurredInLoading = false; | |
| Ref frame = m_frame.get(); | |
| CompletionHandlerCallingScope completionHandlerCaller(WTF::move(completionHandler)); | |
| ASSERT(m_client->hasWebView()); | |
| // Unfortunately the view must be non-nil, this is ultimately due | |
| // to parser requiring a FrameView. We should fix this dependency. | |
| ASSERT(frame->view()); | |
| if (!isNavigationAllowed()) | |
| return; | |
| if (auto* page = frame->page(); page && page->isInSwipeAnimation()) | |
| loader->setLoadStartedDuringSwipeAnimation(); | |
| if (frame->document()) | |
| m_previousURL = protect(frame->document())->url(); | |
| const URL& newURL = loader->request().url(); | |
| // Only the first iframe navigation or the first iframe navigation after about:blank should be reported. | |
| // https://www.w3.org/TR/resource-timing-2/#resources-included-in-the-performanceresourcetiming-interface | |
| if (m_shouldReportResourceTimingToParentFrame && !m_previousURL.isNull() && m_previousURL != aboutBlankURL()) | |
| m_shouldReportResourceTimingToParentFrame = false; | |
| // Log main frame navigation types. | |
| if (frame->isMainFrame()) { | |
| if (RefPtr page = frame->page()) { | |
| FRAMELOADER_RELEASE_LOG_FORWARDABLE(FrameLoaderLoadWithDocumentLoaderMainFrameLoadStarted); | |
| page->mainFrameLoadStarted(newURL, type); | |
| page->performanceLogging().didReachPointOfInterest(PerformanceLogging::MainFrameLoadStarted); | |
| } | |
| } | |
| policyChecker().setLoadType(type); | |
| RELEASE_ASSERT(!isBackForwardLoadType(type) || history().provisionalItem()); | |
| bool isFormSubmission = !!formSubmission; | |
| // The history handling was resolved by whoever started this navigation, which may be another | |
| // process handing the load over to us mid-flight (e.g. after a cross-site redirect under site | |
| // isolation). Resolving it again here would lose a location.replace() and turn it into a push. | |
| auto historyHandling = loader->navigationHistoryBehavior(); | |
| const String& httpMethod = loader->request().httpMethod(); | |
| if (!loader->isContinuingLoadAfterProvisionalLoadStarted() && shouldPerformFragmentNavigation(isFormSubmission, httpMethod, policyChecker().loadType(), newURL) && !loader->substituteData().isValid()) { | |
| RefPtr policyDocumentLoader = m_policyDocumentLoader; | |
| // A navigation that a listener starts replaces this one. | |
| if (dispatchNavigateEventForFragmentNavigationFromAnotherProcess(*loader, type, historyHandling).isNotCompleted() || m_policyDocumentLoader != policyDocumentLoader || !frame->page()) | |
| return; | |
| RefPtr oldDocumentLoader = m_documentLoader; | |
| NavigationAction action { protect(frame->document()).releaseNonNull(), loader->request(), InitiatedByMainFrame::Unknown, loader->isRequestFromClientOrUserInput(), policyChecker().loadType(), isFormSubmission }; | |
| action.setNavigationAPIType(determineNavigationType(type, historyHandling)); | |
| oldDocumentLoader->setTriggeringAction(WTF::move(action)); | |
| oldDocumentLoader->setLastCheckedRequest(ResourceRequest()); | |
| policyChecker().stopCheck(); | |
| RELEASE_ASSERT(!isBackForwardLoadType(policyChecker().loadType()) || history().provisionalItem()); | |
| RefPtr<SecurityOrigin> requesterOrigin; | |
| if (loader->crossSiteRequester()) | |
| requesterOrigin = loader->crossSiteRequester()->securityOrigin.copyRef(); | |
| else if (auto& requester = loader->triggeringAction().requester()) | |
| requesterOrigin = requester->securityOrigin.copyRef(); | |
| policyChecker().checkNavigationPolicy(ResourceRequest(loader->request()), ResourceResponse { } /* redirectResponse */, oldDocumentLoader.get(), WTF::move(formSubmission), [this, protectedThis = Ref { *this }, requesterOrigin = WTF::move(requesterOrigin), historyHandling] (const ResourceRequest& request, WeakPtr<const FormSubmission>&&, NavigationPolicyDecision navigationPolicyDecision) { | |
| continueFragmentScrollAfterNavigationPolicy(request, requesterOrigin.get(), navigationPolicyDecision == NavigationPolicyDecision::ContinueLoad, historyHandling); | |
| }, IsSameDocumentNavigation::Yes, PolicyDecisionMode::Synchronous); | |
| return; | |
| } | |
| if (RefPtr parent = dynamicDowncast<LocalFrame>(frame->tree().parent())) | |
| loader->setOverrideEncoding(parent->loader().documentLoader()->overrideEncoding()); | |
| policyChecker().stopCheck(); | |
| setPolicyDocumentLoader(loader); | |
| if (loader->triggeringAction().isEmpty()) { | |
| NavigationAction action = loader->crossSiteRequester() ? NavigationAction { *loader->crossSiteRequester(), loader->request(), InitiatedByMainFrame::Unknown, loader->isRequestFromClientOrUserInput(), policyChecker().loadType(), isFormSubmission } : NavigationAction { protect(frame->document()).releaseNonNull(), loader->request(), InitiatedByMainFrame::Unknown, loader->isRequestFromClientOrUserInput(), policyChecker().loadType(), isFormSubmission }; | |
| action.setIsContentRuleListRedirect(loader->isContentRuleListRedirect()); | |
| action.setNavigationAPIType(determineNavigationType(type, historyHandling)); | |
| loader->setTriggeringAction(WTF::move(action)); | |
| } | |
| protect(frame->navigationScheduler())->cancel(NewLoadInProgress::Yes); | |
| if (shouldTreatCurrentLoadAsContinuingLoad()) { | |
| continueLoadAfterNavigationPolicy(loader->request(), formSubmission.get(), NavigationPolicyDecision::ContinueLoad, allowNavigationToInvalidURL, shouldRestoreFromBackForwardCache); | |
| return; | |
| } | |
| if (loader->triggeringAction().isFromNavigationAPI() && !shouldCheckNavigationPolicyAfterDispatchingEvents(*loader)) | |
| return; | |
| RELEASE_ASSERT(!isBackForwardLoadType(policyChecker().loadType()) || history().provisionalItem()); | |
| policyChecker().checkNavigationPolicy(ResourceRequest(loader->request()), ResourceResponse { } /* redirectResponse */, loader, WTF::move(formSubmission), [ | |
| this, | |
| protectedThis = Ref { *this }, | |
| allowNavigationToInvalidURL, | |
| shouldRestoreFromBackForwardCache, | |
| expectedPolicyDocumentLoader = RefPtr { loader }, | |
| completionHandler = completionHandlerCaller.release() | |
| ] (const ResourceRequest& request, WeakPtr<const FormSubmission>&& weakFormSubmission, NavigationPolicyDecision navigationPolicyDecision) mutable { | |
| // A download attribute check is not cancelled by the navigation that follows it, so it can be | |
| // answered once a newer navigation owns m_policyDocumentLoader. Continuing would tear that | |
| // newer navigation down; the download, if any, has already started. | |
| if (m_policyDocumentLoader != expectedPolicyDocumentLoader) { | |
| FRAMELOADER_RELEASE_LOG(ResourceLoading, "loadWithDocumentLoader: not continuing because a newer navigation owns the policy document loader"); | |
| completionHandler(); | |
| return; | |
| } | |
| continueLoadAfterNavigationPolicy(request, RefPtr { weakFormSubmission.get() }.get(), navigationPolicyDecision, allowNavigationToInvalidURL, shouldRestoreFromBackForwardCache); | |
| completionHandler(); | |
| }, IsSameDocumentNavigation::No, PolicyDecisionMode::Asynchronous, determineNavigationType(type, historyHandling)); | |
| } | |
| NavigateEventDispatchResult FrameLoader::dispatchNavigateEventForFragmentNavigationFromAnotherProcess(const DocumentLoader& loader, FrameLoadType type, NavigationHistoryBehavior historyHandling) | |
| { | |
| if (!loader.crossSiteRequester() || !loader.isContinuingLoadAfterNavigationPolicyDecision() || !m_frame->document()) | |
| return NavigateEventDispatchResult::completed(); | |
| FrameLoadRequest frameLoadRequest { protect(m_frame), ResourceRequest { loader.request() } }; | |
| frameLoadRequest.setNavigationHistoryBehavior(historyHandling); | |
| // A navigation that a listener starts is a new one, not a continuation of this one. | |
| SetForScope notContinuing { m_currentLoadContinuingState, LoadContinuingState::NotContinuing }; | |
| return dispatchNavigateEvent(type, frameLoadRequest, true); | |
| } | |
| // navigate() must fire these before it returns, but its policy check must stay asynchronous: the UI process may | |
| // continue the load in another process, and this process must not start loading it first. | |
| bool FrameLoader::shouldCheckNavigationPolicyAfterDispatchingEvents(DocumentLoader& loader) | |
| { | |
| Ref frame = m_frame.get(); | |
| Ref protectedLoader { loader }; | |
| // Starting this navigation stops the one it replaces, even if the navigate event then cancels this one. | |
| clearProvisionalLoadForPolicyCheck(); | |
| Markable<NavigateEventIdentifier> navigateEventIdentifier; | |
| if (auto dispatchNavigateEvent = loader.triggeringAction().takePendingDispatchNavigateEvent()) { | |
| auto result = dispatchNavigateEvent(); | |
| if (m_policyDocumentLoader != &loader || !frame->page()) | |
| return false; | |
| if (result.isNotCompleted()) { | |
| setPolicyDocumentLoader(nullptr); | |
| return false; | |
| } | |
| navigateEventIdentifier = result.navigateEventIdentifier; | |
| } | |
| if (!shouldClose()) { | |
| if (RefPtr window = navigateEventIdentifier ? protect(frame->document())->window() : nullptr) | |
| protect(window->navigation())->abortOngoingNavigationIfStartedBy(*navigateEventIdentifier); | |
| if (m_policyDocumentLoader == &loader) | |
| setPolicyDocumentLoader(nullptr); | |
| return false; | |
| } | |
| if (m_policyDocumentLoader != &loader || !frame->page()) | |
| return false; | |
| loader.triggeringAction().setDispatchedEventsBeforeNavigationPolicy(navigateEventIdentifier); | |
| return true; | |
| } | |
| void FrameLoader::clearProvisionalLoadForPolicyCheck() | |
| { | |
| if (!m_policyDocumentLoader || !m_provisionalDocumentLoader || m_inClearProvisionalLoadForPolicyCheck) | |
| return; | |
| SetForScope change(m_inClearProvisionalLoadForPolicyCheck, true); | |
| protect(m_provisionalDocumentLoader)->stopLoading(); | |
| FRAMELOADER_RELEASE_LOG(ResourceLoading, "clearProvisionalLoadForPolicyCheck: Clearing provisional document loader (m_provisionalDocumentLoader=%p)", m_provisionalDocumentLoader.get()); | |
| setProvisionalDocumentLoader(nullptr); | |
| } | |
| bool FrameLoader::hasOpenedFrames() const | |
| { | |
| return m_frame->hasOpenedFrames(); | |
| } | |
| void FrameLoader::reportLocalLoadFailed(LocalFrame* frame, const String& url) | |
| { | |
| ASSERT(!url.isEmpty()); | |
| if (!frame) | |
| return; | |
| protect(frame->document())->addConsoleMessage(MessageSource::Security, MessageLevel::Error, makeString("Not allowed to load local resource: "_s, url)); | |
| } | |
| void FrameLoader::reportBlockedLoadFailed(LocalFrame& frame, const URL& url) | |
| { | |
| ASSERT(!url.isEmpty()); | |
| auto restrictedHostPort = isIPAddressDisallowed(url) ? makeString("host \""_s, url.host(), "\""_s) : makeString("port "_s, url.port().value()); | |
| auto message = makeString("Not allowed to use restricted network "_s, restrictedHostPort, ": "_s, url.stringCenterEllipsizedToLength()); | |
| protect(frame.document())->addConsoleMessage(MessageSource::Security, MessageLevel::Error, message); | |
| } | |
| bool FrameLoader::willLoadMediaElementURL(URL& url, Node& initiatorNode) | |
| { | |
| #if PLATFORM(IOS_FAMILY) | |
| // 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 (WTF::IOSApplication::isMobileStore()) | |
| return m_client->shouldLoadMediaElementURL(url); | |
| #endif | |
| ResourceRequest request(URL { url }); | |
| request.setInspectorInitiatorNodeIdentifier(InspectorInstrumentation::identifierForNode(initiatorNode)); | |
| if (m_documentLoader) | |
| request.setIsAppInitiated(m_documentLoader->lastNavigationWasAppInitiated()); | |
| ResourceError error; | |
| auto identifier = requestFromDelegate(request, error); | |
| notifier().sendRemainingDelegateMessages(protect(documentLoader()), identifier, request, ResourceResponse(URL { url }, String(), -1, String()), nullptr, -1, -1, error); | |
| url = request.url(); | |
| return error.isNull(); | |
| } | |
| bool FrameLoader::shouldReloadToHandleUnreachableURL(DocumentLoader& docLoader) | |
| { | |
| URL unreachableURL = docLoader.unreachableURL(); | |
| if (unreachableURL.isEmpty()) | |
| return false; | |
| if (!isBackForwardLoadType(policyChecker().loadType())) | |
| return false; | |
| // We only treat unreachableURLs specially during the delegate callbacks | |
| // for provisional load errors and navigation policy decisions. The former | |
| // 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. | |
| if (policyChecker().delegateIsDecidingNavigationPolicy() || policyChecker().delegateIsHandlingUnimplementablePolicy()) | |
| return m_policyDocumentLoader && unreachableURL == m_policyDocumentLoader->request().url(); | |
| return unreachableURL == m_provisionalLoadErrorBeingHandledURL; | |
| } | |
| void FrameLoader::reloadWithOverrideEncoding(const String& encoding) | |
| { | |
| RefPtr documentLoader = m_documentLoader; | |
| if (!documentLoader) | |
| return; | |
| FRAMELOADER_RELEASE_LOG(ResourceLoading, "reloadWithOverrideEncoding: frame load started"); | |
| ResourceRequest request = documentLoader->request(); | |
| URL unreachableURL = documentLoader->unreachableURL(); | |
| if (!unreachableURL.isEmpty()) | |
| request.setURL(WTF::move(unreachableURL)); | |
| // FIXME: If the resource is a result of form submission and is not cached, the form will be silently resubmitted. | |
| // We should ask the user for confirmation in this case. | |
| request.setCachePolicy(ResourceRequestCachePolicy::ReturnCacheDataElseLoad); | |
| auto&& substituteData = defaultSubstituteDataForURL(request.url()); | |
| Ref loader = m_client->createDocumentLoader(WTF::move(request), WTF::move(substituteData)); | |
| applyShouldOpenExternalURLsPolicyToNewDocumentLoader(protect(m_frame), loader, InitiatedByMainFrame::Unknown, documentLoader->shouldOpenExternalURLsPolicyToPropagate()); | |
| setPolicyDocumentLoader(loader.copyRef()); | |
| loader->setOverrideEncoding(encoding); | |
| loadWithDocumentLoader(loader.ptr(), FrameLoadType::Reload, { }, AllowNavigationToInvalidURL::Yes); | |
| } | |
| void FrameLoader::reload(OptionSet<ReloadOption> options, bool isRequestFromClientOrUserInput) | |
| { | |
| RefPtr documentLoader = m_documentLoader; | |
| if (!documentLoader) | |
| return; | |
| // If a window is created by javascript, its main frame can have an empty but non-nil URL. | |
| // Reloading in this case will lose the current contents (see 4151001). | |
| if (documentLoader->request().url().isEmpty()) | |
| return; | |
| FRAMELOADER_RELEASE_LOG_FORWARDABLE(FrameLoaderReload); | |
| // Replace error-page URL with the URL we were trying to reach. | |
| ResourceRequest initialRequest = documentLoader->request(); | |
| URL unreachableURL = documentLoader->unreachableURL(); | |
| if (!unreachableURL.isEmpty()) | |
| initialRequest.setURL(WTF::move(unreachableURL)); | |
| // 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. | |
| auto&& substituteData = defaultSubstituteDataForURL(initialRequest.url()); | |
| Ref loader = m_client->createDocumentLoader(WTF::move(initialRequest), WTF::move(substituteData)); | |
| loader->setIsRequestFromClientOrUserInput(documentLoader->isRequestFromClientOrUserInput() || isRequestFromClientOrUserInput); | |
| applyShouldOpenExternalURLsPolicyToNewDocumentLoader(protect(m_frame), loader, InitiatedByMainFrame::Unknown, documentLoader->shouldOpenExternalURLsPolicyToPropagate()); | |
| loader->setContentExtensionEnablement({ options.contains(ReloadOption::DisableContentBlockers) ? ContentExtensionDefaultEnablement::Disabled : ContentExtensionDefaultEnablement::Enabled, { } }); | |
| ResourceRequest& request = loader->request(); | |
| // FIXME: We don't have a mechanism to revalidate the main resource without reloading at the moment. | |
| request.setCachePolicy(ResourceRequestCachePolicy::ReloadIgnoringCacheData); | |
| addSameSiteInfoToRequestIfNeeded(request); | |
| // If we're about to re-post, set up action so the application can warn the user. | |
| if (request.httpMethod() == "POST"_s) | |
| loader->setTriggeringAction({ protect(m_frame->document()).releaseNonNull(), request, InitiatedByMainFrame::Unknown, loader->isRequestFromClientOrUserInput(), NavigationType::FormResubmitted }); | |
| loader->setOverrideEncoding(documentLoader->overrideEncoding()); | |
| loader->setTriggeringNavigationAPIType(NavigationNavigationType::Reload); | |
| auto frameLoadTypeForReloadOptions = [] (auto options) { | |
| if (options & ReloadOption::FromOrigin) | |
| return FrameLoadType::ReloadFromOrigin; | |
| if (options & ReloadOption::ExpiredOnly) | |
| return FrameLoadType::ReloadExpiredOnly; | |
| return FrameLoadType::Reload; | |
| }; | |
| loadWithDocumentLoader(loader.ptr(), frameLoadTypeForReloadOptions(options), { }, AllowNavigationToInvalidURL::Yes); | |
| } | |
| void FrameLoader::stopAllLoaders(ClearProvisionalItem clearProvisionalItem, StopLoadingPolicy stopLoadingPolicy) | |
| { | |
| if (m_frame->document() && m_frame->document()->backForwardCacheState() == Document::InBackForwardCache) | |
| return; | |
| if (stopLoadingPolicy == StopLoadingPolicy::PreventDuringUnloadEvents && !isStopLoadingAllowed()) | |
| return; | |
| // If this method is called from within this method, infinite recursion can occur (3442218). Avoid this. | |
| if (m_inStopAllLoaders) | |
| return; | |
| // This method might dispatch events. | |
| RELEASE_ASSERT_WITH_SECURITY_IMPLICATION(ScriptDisallowedScope::InMainThread::isScriptAllowed()); | |
| // Calling stopLoading() on the provisional document loader can blow away | |
| // the frame from underneath. | |
| Ref frame = m_frame.get(); | |
| m_inStopAllLoaders = true; | |
| policyChecker().stopCheck(); | |
| // If no new load is in progress, we should clear the provisional item from history | |
| // before we call stopLoading. | |
| if (clearProvisionalItem == ClearProvisionalItem::Yes) | |
| history().setProvisionalItem(nullptr); | |
| for (RefPtr child = frame->tree().firstChild(); child; child = child->tree().nextSibling()) { | |
| if (RefPtr localChild = dynamicDowncast<LocalFrame>(child.get())) | |
| localChild->loader().stopAllLoaders(clearProvisionalItem); | |
| } | |
| FRAMELOADER_RELEASE_LOG_FORWARDABLE(FrameLoaderStopAllLoaders, (uint64_t)m_provisionalDocumentLoader.get(), (uint64_t)m_documentLoader.get()); | |
| if (RefPtr provisionalDocumentLoader = m_provisionalDocumentLoader) | |
| provisionalDocumentLoader->stopLoading(); | |
| if (RefPtr documentLoader = m_documentLoader) | |
| documentLoader->stopLoading(); | |
| if (frame->page() && !frame->page()->chrome().client().isSVGImageChromeClient()) | |
| platformStrategies()->loaderStrategy()->browsingContextRemoved(frame); | |
| setProvisionalDocumentLoader(nullptr); | |
| m_inStopAllLoaders = false; | |
| } | |
| void FrameLoader::stopForBackForwardCache() | |
| { | |
| ASSERT(!m_inStopForBackForwardCache); | |
| SetForScope<bool> inStopForBackForwardCache { m_inStopForBackForwardCache, true }; | |
| // Stop provisional loads in subframes (The one in the main frame is about to be committed). | |
| if (!m_frame->isMainFrame()) { | |
| if (RefPtr provisionalDocumentLoader = m_provisionalDocumentLoader) | |
| provisionalDocumentLoader->stopLoading(); | |
| FRAMELOADER_RELEASE_LOG(ResourceLoading, "stopForBackForwardCache: Clearing provisional document loader (m_provisionalDocumentLoader=%p)", m_provisionalDocumentLoader.get()); | |
| setProvisionalDocumentLoader(nullptr); | |
| } | |
| // Stop current loads. | |
| if (RefPtr documentLoader = m_documentLoader) | |
| documentLoader->stopLoading(); | |
| for (RefPtr child = m_frame->tree().firstChild(); child; child = child->tree().nextSibling()) | |
| child->stopForBackForwardCache(); | |
| // We cancel pending navigations & policy checks *after* cancelling loads because cancelling loads might end up | |
| // running script, which could schedule new navigations. | |
| policyChecker().stopCheck(); | |
| protect(protect(m_frame)->navigationScheduler())->cancel(); | |
| } | |
| void FrameLoader::stopAllLoadersAndCheckCompleteness() | |
| { | |
| stopAllLoaders(); | |
| if (!m_checkTimer.isActive()) | |
| return; | |
| m_checkTimer.stop(); | |
| m_crossOriginParentSyntheticSameDocLoadEventTimer.stop(); | |
| m_checkingLoadCompleteForDetachment = true; | |
| checkCompletenessNow(); | |
| m_checkingLoadCompleteForDetachment = false; | |
| } | |
| void FrameLoader::stopForUserCancel(DeferCheckLoadComplete deferCheckLoadComplete, AbortOngoingNavigation abortOngoingNavigation) | |
| { | |
| if (m_inStopForBackForwardCache) | |
| return; | |
| // Calling stopAllLoaders can cause the frame to be deallocated, including the frame loader. | |
| Ref frame = m_frame.get(); | |
| { | |
| SetForScope doNotAbortNavigationAPI { m_doNotAbortNavigationAPI, m_doNotAbortNavigationAPI || abortOngoingNavigation == AbortOngoingNavigation::No }; | |
| stopAllLoaders(); | |
| } | |
| if (abortOngoingNavigation == AbortOngoingNavigation::Yes && frame->document()->settings().navigationAPIEnabled()) { | |
| if (RefPtr window = frame->document()->window()) | |
| protect(window->navigation())->abortOngoingNavigationIfNeeded(); | |
| } | |
| #if PLATFORM(IOS_FAMILY) | |
| // Lay out immediately when stopping to immediately clear the old page if we just committed this one | |
| // but haven't laid out/painted yet. | |
| // FIXME: Is this behavior specific to iOS? Or should we expose a setting to toggle this behavior? | |
| if (RefPtr view = frame->view(); view && !view->didFirstLayout()) | |
| protect(view->layoutContext())->layout(); | |
| #endif | |
| if (deferCheckLoadComplete == DeferCheckLoadComplete::Yes) | |
| scheduleCheckLoadComplete(); | |
| else if (frame->page()) | |
| checkLoadComplete(); | |
| } | |
| DocumentLoader* FrameLoader::activeDocumentLoader() const | |
| { | |
| if (m_state == FrameState::Provisional) | |
| return m_provisionalDocumentLoader; | |
| return m_documentLoader; | |
| } | |
| bool FrameLoader::isLoading() const | |
| { | |
| auto* documentLoader = activeDocumentLoader(); | |
| return documentLoader && documentLoader->isLoading(); | |
| } | |
| bool FrameLoader::frameHasLoaded() const | |
| { | |
| return m_stateMachine.committedFirstRealDocumentLoad() || (m_provisionalDocumentLoader && !m_stateMachine.creatingInitialEmptyDocument()); | |
| } | |
| void FrameLoader::setDocumentLoader(RefPtr<DocumentLoader>&& loader) | |
| { | |
| if (loader == m_documentLoader) | |
| return; | |
| FRAMELOADER_RELEASE_LOG_FORWARDABLE(FrameLoaderSetDocumentLoader, (uint64_t)loader.get(), (uint64_t)m_documentLoader.get()); | |
| RELEASE_ASSERT(!loader || loader->frameLoader() == this); | |
| m_client->prepareForDataSourceReplacement(); | |
| detachChildren(); | |
| // detachChildren() can trigger this frame's unload event, and therefore | |
| // script can run and do just about anything. For example, an unload event that calls | |
| // document.write("") on its parent frame can lead to a recursive detachChildren() | |
| // invocation for this frame. In that case, we can end up at this point with a | |
| // loader that hasn't been deleted but has been detached from its frame. Such a | |
| // DocumentLoader has been sufficiently detached that we'll end up in an inconsistent | |
| // state if we try to use it. | |
| if (loader && !loader->frame()) | |
| return; | |
| if (RefPtr documentLoader = m_documentLoader) | |
| documentLoader->detachFromFrame(LoadWillContinueInAnotherProcess::No); | |
| m_documentLoader = WTF::move(loader); | |
| } | |
| void FrameLoader::setPolicyDocumentLoader(RefPtr<DocumentLoader>&& loader, LoadWillContinueInAnotherProcess loadWillContinueInAnotherProcess) | |
| { | |
| if (m_policyDocumentLoader == loader) | |
| return; | |
| FRAMELOADER_RELEASE_LOG_FORWARDABLE(FrameLoaderSetPolicyDocumentLoader, (uint64_t)loader.get(), (uint64_t)m_policyDocumentLoader.get()); | |
| history().clearPolicyItem(); | |
| if (loader) | |
| loader->attachToFrame(protect(m_frame)); | |
| if (RefPtr policyDocumentLoader = m_policyDocumentLoader; policyDocumentLoader | |
| && policyDocumentLoader != m_provisionalDocumentLoader | |
| && policyDocumentLoader != m_documentLoader) { | |
| policyDocumentLoader->detachFromFrame(loadWillContinueInAnotherProcess); | |
| } | |
| m_policyDocumentLoader = WTF::move(loader); | |
| } | |
| void FrameLoader::setProvisionalDocumentLoader(RefPtr<DocumentLoader>&& loader) | |
| { | |
| if (m_provisionalDocumentLoader == loader) | |
| return; | |
| FRAMELOADER_RELEASE_LOG_FORWARDABLE(FrameLoaderSetProvisionalDocumentLoader, (uint64_t)loader.get(), (uint64_t)m_provisionalDocumentLoader.get()); | |
| ASSERT(!loader || !m_provisionalDocumentLoader); | |
| RELEASE_ASSERT(!loader || loader->frameLoader() == this); | |
| if (RefPtr provisionalDocumentLoader = m_provisionalDocumentLoader; provisionalDocumentLoader && provisionalDocumentLoader != m_documentLoader) | |
| provisionalDocumentLoader->detachFromFrame(LoadWillContinueInAnotherProcess::No); | |
| m_provisionalDocumentLoader = WTF::move(loader); | |
| } | |
| void FrameLoader::setState(FrameState newState) | |
| { | |
| FrameState oldState = m_state; | |
| m_state = newState; | |
| if (newState == FrameState::Provisional) | |
| provisionalLoadStarted(); | |
| else if (newState == FrameState::Complete) { | |
| frameLoadCompleted(); | |
| if (RefPtr documentLoader = m_documentLoader) | |
| documentLoader->stopRecordingResponses(); | |
| if (m_frame->isMainFrame() && oldState != newState) { | |
| FRAMELOADER_RELEASE_LOG_FORWARDABLE(FrameLoaderSetStateMainFrameLoadCompleted); | |
| m_frame->page()->performanceLogging().didReachPointOfInterest(PerformanceLogging::MainFrameLoadCompleted); | |
| } | |
| } | |
| } | |
| void FrameLoader::clearProvisionalLoad() | |
| { | |
| FRAMELOADER_RELEASE_LOG_FORWARDABLE(FrameLoaderClearProvisionalLoad, m_provisionalDocumentLoader && m_provisionalDocumentLoader->navigationID() ? m_provisionalDocumentLoader->navigationID()->object().toUInt64() : 0); | |
| setProvisionalDocumentLoader(nullptr); | |
| if (CheckedPtr progressTracker = m_progressTracker.get()) | |
| progressTracker->progressCompleted(FrameProgressTracker::LoadCompletionStatus::Failure); | |
| setState(FrameState::Complete); | |
| } | |
| void FrameLoader::provisionalLoadFailedInAnotherProcess() | |
| { | |
| m_provisionalLoadHappeningInAnotherProcess = false; | |
| m_isComplete = true; | |
| if (RefPtr localParent = dynamicDowncast<LocalFrame>(m_frame->tree().parent())) { | |
| Ref parentLoader = localParent->loader(); | |
| parentLoader->checkCompleted(); | |
| // checkCompleted() may short-circuit because the parent's document already finished, but the | |
| // parent's FrameLoader state machine can still be stuck in CommittedPage if subframeIsLoading() | |
| // was true while waiting for this cross-process load. Drive checkLoadComplete() so that | |
| // dispatchDidFinishLoad() can fire on the parent now that subframeIsLoading() is false. | |
| parentLoader->checkLoadComplete(); | |
| } | |
| } | |
| void FrameLoader::commitProvisionalLoad() | |
| { | |
| RefPtr pdl = m_provisionalDocumentLoader; | |
| Ref frame = m_frame.get(); | |
| // Clear prefetch resources for URLs other than the one being navigated to. | |
| // This ensures that prefetches are only used for the immediate next navigation, | |
| // not for subsequent navigations to the same URL. | |
| if (pdl) | |
| m_documentPrefetcher->clearPrefetchedResourcesExcept(pdl->url()); | |
| std::unique_ptr<CachedPage> cachedPage; | |
| if (m_loadingFromCachedPage && history().provisionalItem()) { | |
| Ref provisionalItem = *history().provisionalItem(); | |
| cachedPage = BackForwardCache::singleton().take(provisionalItem->frameItemID(), protect(frame->page()).get()); | |
| if (cachedPage) { | |
| // Fire DidTakeBackForwardItemForRestoration before any subsequent | |
| // DidCacheBackForwardItem for the outgoing page, so the UIProcess | |
| // WebBackForwardCache observes remove(provisional) → add(current) | |
| // in the same order the WebProcess BackForwardCache did. Firing | |
| // late (after the Document is swapped in) inverts that ordering | |
| // and can drop a same-process sibling SPP entry as the new | |
| // "oldest" entry under capacity pressure. | |
| m_client->didTakeBackForwardItemForRestoration(provisionalItem->itemID()); | |
| } | |
| } | |
| LOG_WITH_STREAM(BackForwardCache, stream << "WebCoreLoading frame "_s << m_frame->frameID().toUInt64() << ": About to commit provisional load from previous URL '"_s << (frame->document() ? frame->document()->url().stringCenterEllipsizedToLength() : emptyString()) << "' to new URL '"_s << (pdl ? pdl->url().stringCenterEllipsizedToLength() : "<no provisional DocumentLoader>"_s) << "' with cached page "_s << cachedPage.get()); | |
| // Already dispatched if the navigation waited for a view transition to be captured. | |
| if (!pdl || !pdl->hasDispatchedPageswapEvent()) | |
| dispatchPageswapEvent(pdl.get(), !!cachedPage, { }); | |
| if (pdl) | |
| pdl->setHasDispatchedPageswapEvent(false); | |
| if (RefPtr document = frame->document()) { | |
| // In the case where we're restoring from a cached page, our document will not | |
| // be connected to its frame yet, so the following call with be a no-op. We will | |
| // attempt to confirm any active composition once again in this scenario after we | |
| // finish restoring from the cached page. | |
| protect(document->editor())->confirmOrCancelCompositionAndNotifyClient(); | |
| } | |
| RefPtr page = frame->page(); | |
| bool isCurrentMainFrame = page && frame.ptr() == &page->mainFrame(); | |
| if (isCurrentMainFrame && history().currentItem() && (!history().provisionalItem() || history().currentItem()->itemID() != history().provisionalItem()->itemID())) { | |
| // 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. | |
| Ref currentItem = *history().currentItem(); | |
| if (BackForwardCache::singleton().addIfCacheable(currentItem.get(), page.get())) | |
| m_client->didCacheBackForwardItem(currentItem->itemID(), currentItem->frameItemID()); | |
| WebCore::jettisonExpensiveObjectsOnTopLevelNavigation(); | |
| } | |
| if (m_loadType != FrameLoadType::MultipartReplace) | |
| closeOldDataSources(); | |
| if (!cachedPage && !m_stateMachine.creatingInitialEmptyDocument()) | |
| m_client->makeRepresentation(pdl.get()); | |
| transitionToCommitted(protect(cachedPage)); | |
| if (pdl && m_documentLoader) { | |
| // Check if the destination page is allowed to access the previous page's timing information. | |
| Ref securityOrigin = SecurityOrigin::create(pdl->request().url()); | |
| m_documentLoader->timing().setHasSameOriginAsPreviousDocument(!m_documentLoader->hasCrossOriginRedirect() && securityOrigin->canRequest(m_previousURL, OriginAccessPatternsForWebProcess::singleton())); | |
| } | |
| // Call clientRedirectCancelledOrFinished() here so that the frame load delegate is notified that the redirect's | |
| // status has changed, if there was a redirect. The frame load delegate may have saved some state about | |
| // the redirect in its -webView:willPerformClientRedirectToURL:delay:fireDate:forFrame:. Since we are | |
| // just about to commit a new page, there cannot possibly be a pending redirect at this point. | |
| if (m_sentRedirectNotification) | |
| clientRedirectCancelledOrFinished(NewLoadInProgress::No); | |
| if (cachedPage && cachedPage->document()) { | |
| #if PLATFORM(IOS_FAMILY) | |
| // FIXME: CachedPage::restore() would dispatch viewport change notification. However UIKit expects load | |
| // commit to happen before any changes to viewport arguments and dealing with this there is difficult. | |
| protect(frame->page())->chrome().setDispatchViewportDataDidChangeSuppressed(true); | |
| #endif | |
| 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; | |
| ResourceRequest mainResourceRequest(cachedPage->documentLoader()->request()); | |
| auto mainResourceIdentifier = requestFromDelegate(mainResourceRequest, mainResouceError); | |
| notifier().dispatchDidReceiveResponse(protect(cachedPage->documentLoader()), mainResourceIdentifier, cachedPage->documentLoader()->response()); | |
| // The cached Document is not installed in the frame until cachedPage->restore() below. | |
| Ref restoredDocument = *cachedPage->document(); | |
| CheckedPtr restoredContentSecurityPolicy = restoredDocument->contentSecurityPolicy(); | |
| dispatchDidCommitLoad(BackForwardCacheCommitData { | |
| cachedPage->cachedMainFrame()->hasInsecureContent(), | |
| cachedPage->cachedMainFrame()->usedLegacyTLS(), | |
| cachedPage->cachedMainFrame()->wasPrivateRelayed(), | |
| restoredDocument->identifier(), | |
| restoredDocument->securityOrigin().data(), | |
| DocumentSecurityPolicy { restoredDocument->crossOriginEmbedderPolicy(), restoredDocument->crossOriginOpenerPolicy(), restoredDocument->isSecureContext() ? IsSecureContext::Yes : IsSecureContext::No, restoredDocument->agentClusterIdentifier() }, | |
| restoredContentSecurityPolicy ? restoredContentSecurityPolicy->insecureNavigationRequestsToUpgrade() : HashSet<SecurityOriginData> { }, | |
| restoredDocument->isPluginDocument() | |
| }); | |
| // FIXME: This API should be turned around so that we ground CachedPage into the Page. | |
| RefPtr page = frame->page(); | |
| cachedPage->restore(*page); | |
| #if PLATFORM(IOS_FAMILY) | |
| page->chrome().setDispatchViewportDataDidChangeSuppressed(false); | |
| #endif | |
| if (RefPtr framePage = frame->page()) { | |
| #if PLATFORM(IOS_FAMILY) | |
| page->chrome().dispatchViewportPropertiesDidChange(framePage->viewportArguments()); | |
| #endif | |
| page->chrome().dispatchDisabledAdaptationsDidChange(framePage->disabledAdaptations()); | |
| } | |
| if (RefPtr documentLoader = m_documentLoader) { | |
| auto& title = documentLoader->title(); | |
| if (!title.string.isNull()) | |
| m_client->dispatchDidReceiveTitle(title); | |
| // Send remaining notifications for the main resource. | |
| notifier().sendRemainingDelegateMessages(documentLoader.get(), mainResourceIdentifier, mainResourceRequest, ResourceResponse(), nullptr, static_cast<int>(documentLoader->response().expectedContentLength()), 0, mainResouceError); | |
| } | |
| Vector<Ref<LocalFrame>> targetFrames; | |
| targetFrames.append(frame); | |
| for (RefPtr child = frame->tree().firstChild(); child; child = child->tree().traverseNext(frame.ptr())) { | |
| if (RefPtr localChild = dynamicDowncast<LocalFrame>(child)) | |
| targetFrames.append(localChild.releaseNonNull()); | |
| } | |
| for (auto& frame : targetFrames) | |
| frame->loader().checkCompleted(); | |
| } else | |
| didOpenURL(); | |
| if (RefPtr document = frame->document()) | |
| protect(document->editor())->confirmOrCancelCompositionAndNotifyClient(); | |
| IGNORE_GCC_WARNINGS_BEGIN("format-overflow") | |
| LOG_WITH_STREAM(Loading, stream << "WebCoreLoading frame "_s << frame->frameID().toUInt64() << ": Finished committing provisional load to URL "_s << (frame->document() ? frame->document()->url().stringCenterEllipsizedToLength() : emptyString())); | |
| IGNORE_GCC_WARNINGS_END | |
| if (m_loadType == FrameLoadType::Standard && m_documentLoader && m_documentLoader->isClientRedirect()) | |
| history().updateForClientRedirect(); | |
| if (m_loadingFromCachedPage) { | |
| // Note, didReceiveDocType is expected to be called for cached pages. See <rdar://problem/5906758> for more details. | |
| if (RefPtr page = frame->page()) | |
| page->chrome().didReceiveDocType(frame); | |
| protect(frame->document())->resume(ReasonForSuspension::BackForwardCache); | |
| // Force a layout to update view size and thereby update scrollbars. | |
| auto shouldForceLayout = [&] { | |
| #if PLATFORM(IOS_FAMILY) | |
| return !m_client->forceLayoutOnRestoreFromBackForwardCache(); | |
| #endif | |
| return true; | |
| }; | |
| if (RefPtr view = frame->view(); view && shouldForceLayout()) | |
| view->forceLayout(); | |
| // Main resource delegates were already sent, so we skip the first response here. | |
| RefPtr documentLoader = m_documentLoader; | |
| unsigned responsesSize = documentLoader ? documentLoader->responses().size() : 0; | |
| for (unsigned i = 1; i < responsesSize; ++i) { | |
| const auto& response = documentLoader->responses()[i]; | |
| // FIXME: If the WebKit client changes or cancels the request, this is not respected. | |
| ResourceError error; | |
| ResourceRequest request(URL { response.url() }); | |
| request.setIsAppInitiated(documentLoader->lastNavigationWasAppInitiated()); | |
| auto identifier = requestFromDelegate(request, error); | |
| // FIXME: If we get a resource with more than 2B bytes, this code won't do the right thing. | |
| // However, with today's computers and networking speeds, this won't happen in practice. | |
| // Could be an issue with a giant local file. | |
| notifier().sendRemainingDelegateMessages(documentLoader.get(), identifier, request, response, nullptr, static_cast<int>(response.expectedContentLength()), 0, error); | |
| } | |
| // FIXME: Why only this frame and not parent frames? | |
| checkLoadCompleteForThisFrame(LoadWillContinueInAnotherProcess::No); | |
| } | |
| } | |
| void FrameLoader::transitionToCommitted(CachedPage* cachedPage) | |
| { | |
| ASSERT(m_client->hasWebView()); | |
| ASSERT(m_state == FrameState::Provisional); | |
| if (m_state != FrameState::Provisional) | |
| return; | |
| if (RefPtr view = m_frame->view()) { | |
| if (auto* scrollAnimator = view->existingScrollAnimator()) | |
| scrollAnimator->cancelAnimations(); | |
| } | |
| m_client->setCopiesOnScroll(); | |
| history().updateForCommit(); | |
| // The call to closeURL() invokes the unload event handler, which can execute arbitrary | |
| // JavaScript. If the script initiates a new load, we need to abandon the current load, | |
| // or the two will stomp each other. | |
| RefPtr originalProvisionalDocumentLoader = m_provisionalDocumentLoader; | |
| if (m_documentLoader) | |
| closeURL(); | |
| if (originalProvisionalDocumentLoader != m_provisionalDocumentLoader) | |
| return; | |
| if (RefPtr documentLoader = m_documentLoader) | |
| documentLoader->stopLoadingSubresources(); | |
| if (RefPtr documentLoader = m_documentLoader) | |
| 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.copyRef()); | |
| if (originalProvisionalDocumentLoader != m_provisionalDocumentLoader) | |
| return; | |
| FRAMELOADER_RELEASE_LOG_FORWARDABLE(FrameLoaderTransitionToCommitted, (uint64_t)m_provisionalDocumentLoader.get()); | |
| setProvisionalDocumentLoader(nullptr); | |
| // Nothing else can interrupt this commit - set the Provisional->Committed transition in stone | |
| setState(FrameState::CommittedPage); | |
| // Handle adding the URL to the back/forward list. | |
| RefPtr documentLoader = m_documentLoader; | |
| switch (m_loadType) { | |
| 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(); | |
| // Create a document view for this document, or used the cached view. | |
| if (cachedPage) { | |
| ASSERT(cachedPage->documentLoader()); | |
| protect(cachedPage->documentLoader())->attachToFrame(protect(m_frame)); | |
| m_client->transitionToCommittedFromCachedFrame(cachedPage->cachedMainFrame()); | |
| } else | |
| m_client->transitionToCommittedForNewPage(m_documentLoader && m_documentLoader->isInFinishedLoadingOfEmptyDocument() ? | |
| LocalFrameLoaderClient::InitializingIframe::Yes : LocalFrameLoaderClient::InitializingIframe::No); | |
| } | |
| break; | |
| case FrameLoadType::Reload: | |
| case FrameLoadType::ReloadFromOrigin: | |
| case FrameLoadType::ReloadExpiredOnly: | |
| case FrameLoadType::Same: | |
| case FrameLoadType::MultipartReplace: | |
| case FrameLoadType::NavigationAPIReplace: | |
| history().updateForReloadOrReplace(); | |
| m_client->transitionToCommittedForNewPage(m_documentLoader && m_documentLoader->isInFinishedLoadingOfEmptyDocument() ? | |
| LocalFrameLoaderClient::InitializingIframe::Yes : LocalFrameLoaderClient::InitializingIframe::No); | |
| break; | |
| case FrameLoadType::Standard: | |
| history().updateForStandardLoad(); | |
| if (RefPtr view = m_frame->view()) | |
| view->setScrollbarsSuppressed(true); | |
| m_client->transitionToCommittedForNewPage(m_documentLoader && m_documentLoader->isInFinishedLoadingOfEmptyDocument() ? | |
| LocalFrameLoaderClient::InitializingIframe::Yes : LocalFrameLoaderClient::InitializingIframe::No); | |
| break; | |
| case FrameLoadType::RedirectWithLockedBackForwardList: | |
| history().updateForRedirectWithLockedBackForwardList(); | |
| m_client->transitionToCommittedForNewPage(m_documentLoader && m_documentLoader->isInFinishedLoadingOfEmptyDocument() ? | |
| LocalFrameLoaderClient::InitializingIframe::Yes : LocalFrameLoaderClient::InitializingIframe::No); | |
| break; | |
| } | |
| if (documentLoader) | |
| documentLoader->writer().setMIMEType(documentLoader->responseMIMEType()); | |
| if (m_stateMachine.creatingInitialEmptyDocument()) | |
| return; | |
| if (!m_stateMachine.committedFirstRealDocumentLoad()) | |
| m_stateMachine.advanceTo(FrameLoaderStateMachine::DisplayingInitialEmptyDocumentPostCommit); | |
| } | |
| void FrameLoader::clientRedirectCancelledOrFinished(NewLoadInProgress newLoadInProgress) | |
| { | |
| // Note that -webView:didCancelClientRedirectForFrame: is called on the frame load delegate even if | |
| // the redirect succeeded. We should either rename this API, or add a new method, like | |
| // -webView:didFinishClientRedirectForFrame: | |
| m_client->dispatchDidCancelClientRedirect(); | |
| if (newLoadInProgress == NewLoadInProgress::No) | |
| m_quickRedirectComing = false; | |
| m_sentRedirectNotification = false; | |
| } | |
| void FrameLoader::clientRedirected(const URL& url, double seconds, WallTime fireDate, LockBackForwardList lockBackForwardList) | |
| { | |
| m_client->dispatchWillPerformClientRedirect(url, seconds, fireDate, lockBackForwardList); | |
| // Remember that we sent a redirect notification to the frame load delegate so that when we commit | |
| // the next provisional load, we can send a corresponding -webView:didCancelClientRedirectForFrame: | |
| m_sentRedirectNotification = true; | |
| // If a "quick" redirect comes in, we set a special mode so we treat the next | |
| // 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 == LockBackForwardList::Yes || history().currentItemShouldBeReplaced()) && m_documentLoader && !m_isExecutingJavaScriptFormAction; | |
| } | |
| bool FrameLoader::shouldReload(const URL& currentURL, const URL& destinationURL) | |
| { | |
| // This function implements the rule: "Don't reload if navigating by fragment within | |
| // the same URL, but do reload if going to a new URL or to the same URL with no | |
| // fragment identifier at all." | |
| if (!destinationURL.hasFragmentIdentifier()) | |
| return true; | |
| return !equalIgnoringFragmentIdentifier(currentURL, destinationURL); | |
| } | |
| void FrameLoader::closeOldDataSources() | |
| { | |
| // FIXME: Is it important for this traversal to be postorder instead of preorder? | |
| // If so, add helpers for postorder traversal, and use them. If not, then lets not | |
| // use a recursive algorithm here. | |
| for (RefPtr child = m_frame->tree().firstChild(); child; child = child->tree().nextSibling()) { | |
| if (RefPtr localChild = dynamicDowncast<LocalFrame>(child)) | |
| localChild->loader().closeOldDataSources(); | |
| } | |
| if (m_documentLoader) | |
| m_client->dispatchWillClose(); | |
| m_client->setMainFrameDocumentReady(false); // stop giving out the actual DOMDocument to observers | |
| } | |
| void FrameLoader::willRestoreFromCachedPage() | |
| { | |
| ASSERT(!m_frame->tree().parent()); | |
| ASSERT(m_frame->page()); | |
| ASSERT(m_frame->isMainFrame()); | |
| protect(protect(m_frame)->navigationScheduler())->cancel(); | |
| // We still have to close the previous part page. | |
| closeURL(); | |
| } | |
| void FrameLoader::open(CachedFrameBase& cachedFrame) | |
| { | |
| // Don't re-emit the load event. | |
| m_didCallImplicitClose = true; | |
| URL url = cachedFrame.url(); | |
| // FIXME: I suspect this block of code doesn't do anything. | |
| if (url.protocolIsInHTTPFamily() && !url.host().isEmpty() && url.path().isEmpty()) | |
| url.setPath("/"_s); | |
| started(); | |
| Ref document = *cachedFrame.document(); | |
| ASSERT(document->window()); | |
| clear(document.ptr(), true, true, cachedFrame.isMainFrame()); | |
| document->attachToCachedFrame(cachedFrame); | |
| document->setBackForwardCacheState(Document::NotInBackForwardCache); | |
| m_needsClear = true; | |
| m_isComplete = false; | |
| m_didCallImplicitClose = false; | |
| setOutgoingReferrer(url); | |
| RefPtr view = cachedFrame.view(); | |
| // When navigating to a CachedFrame its FrameView should never be null. If it is we'll crash in creative ways downstream. | |
| ASSERT(view); | |
| if (RefPtr localView = dynamicDowncast<LocalFrameView>(view.get())) | |
| localView->setLastUserScrollType(std::nullopt); | |
| Ref frame = m_frame.get(); | |
| std::optional<IntRect> previousViewFrameRect = frame->view() ? protect(frame->view())->frameRect() : std::optional<IntRect>(std::nullopt); | |
| if (RefPtr localView = dynamicDowncast<LocalFrameView>(view.get())) | |
| frame->setView(localView.releaseNonNull()); | |
| // Use the previous ScrollView's frame rect. | |
| if (previousViewFrameRect) | |
| view->setFrameRect(previousViewFrameRect.value()); | |
| // Setting the document builds the render tree and runs post style resolution callbacks that can do anything, | |
| // including loading a child frame before its been re-attached to the frame tree as part of this restore. | |
| // For example, the HTML object element may load its content into a frame in a post style resolution callback. | |
| Style::PostResolutionCallbackDisabler disabler(document.get()); | |
| WidgetHierarchyUpdatesSuspensionScope suspendWidgetHierarchyUpdates; | |
| NavigationDisabler disableNavigation { frame.ptr() }; | |
| frame->setDocument(document.copyRef()); | |
| protect(document->window())->resumeFromBackForwardCache(); | |
| updateFirstPartyForCookies(); | |
| cachedFrame.restore(); | |
| } | |
| bool FrameLoader::isMultipartReplacing() const | |
| { | |
| return m_loadType == FrameLoadType::MultipartReplace; | |
| } | |
| void FrameLoader::setMultipartReplacing() | |
| { | |
| m_loadType = FrameLoadType::MultipartReplace; | |
| } | |
| bool FrameLoader::subframeIsLoading() const | |
| { | |
| // It's most likely that the last added frame is the last to load so we walk backwards. | |
| for (RefPtr child = m_frame->tree().lastChild(); child; child = child->tree().previousSibling()) { | |
| RefPtr localChild = dynamicDowncast<LocalFrame>(*child); | |
| if (!localChild) { | |
| if (child->preventsParentFromBeingComplete()) | |
| return true; | |
| continue; | |
| } | |
| Ref childLoader = localChild->loader(); | |
| RefPtr documentLoader = childLoader->documentLoader(); | |
| if (documentLoader && documentLoader->isLoadingInAPISense()) | |
| return true; | |
| documentLoader = childLoader->provisionalDocumentLoader(); | |
| if (childLoader->m_provisionalLoadHappeningInAnotherProcess) | |
| return true; | |
| if (documentLoader && documentLoader->isLoadingInAPISense()) | |
| return true; | |
| documentLoader = childLoader->policyDocumentLoader(); | |
| if (documentLoader) | |
| return true; | |
| } | |
| return false; | |
| } | |
| void FrameLoader::willChangeTitle(DocumentLoader* loader) | |
| { | |
| m_client->willChangeTitle(loader); | |
| } | |
| FrameLoadType LEGACY_NODELETE FrameLoader::loadType() const | |
| { | |
| return m_loadType; | |
| } | |
| CachePolicy FrameLoader::subresourceCachePolicy(const URL& url) const | |
| { | |
| if (auto* page = m_frame->page()) { | |
| if (page->isResourceCachingDisabledByWebInspector()) | |
| return CachePolicy::Reload; | |
| } | |
| if (m_isComplete) | |
| return CachePolicy::Verify; | |
| if (m_loadType == FrameLoadType::ReloadFromOrigin) | |
| return CachePolicy::Reload; | |
| if (auto* parentFrame = dynamicDowncast<LocalFrame>(m_frame->tree().parent())) { | |
| CachePolicy parentCachePolicy = parentFrame->loader().subresourceCachePolicy(url); | |
| if (parentCachePolicy != CachePolicy::Verify) | |
| return parentCachePolicy; | |
| } | |
| switch (m_loadType) { | |
| case FrameLoadType::Reload: | |
| return CachePolicy::Revalidate; | |
| case FrameLoadType::Back: | |
| case FrameLoadType::Forward: | |
| case FrameLoadType::IndexedBackForward: | |
| return CachePolicy::HistoryBuffer; | |
| case FrameLoadType::ReloadFromOrigin: | |
| ASSERT_NOT_REACHED(); // Already handled above. | |
| return CachePolicy::Reload; | |
| case FrameLoadType::RedirectWithLockedBackForwardList: | |
| case FrameLoadType::MultipartReplace: | |
| case FrameLoadType::NavigationAPIReplace: | |
| case FrameLoadType::Same: | |
| case FrameLoadType::Standard: | |
| return CachePolicy::Verify; | |
| case FrameLoadType::ReloadExpiredOnly: | |
| // We know about expiration for HTTP and data. Do a normal reload otherwise. | |
| if (!url.protocolIsInHTTPFamily() && !url.protocolIsData()) | |
| return CachePolicy::Reload; | |
| return CachePolicy::Verify; | |
| } | |
| RELEASE_ASSERT_NOT_REACHED(); | |
| return CachePolicy::Verify; | |
| } | |
| void FrameLoader::dispatchDidFailProvisionalLoad(DocumentLoader& provisionalDocumentLoader, const ResourceError& error, WillInternallyHandleFailure willInternallyHandleFailure) | |
| { | |
| m_provisionalLoadErrorBeingHandledURL = provisionalDocumentLoader.url(); | |
| m_errorOccurredInLoading = true; | |
| #if ENABLE(CONTENT_FILTERING) | |
| auto contentFilterWillContinueLoading = false; | |
| #endif | |
| auto willContinueLoading = willInternallyHandleFailure == WillInternallyHandleFailure::Yes ? WillContinueLoading::Yes : WillContinueLoading::No; | |
| if (history().provisionalItem()) | |
| willContinueLoading = WillContinueLoading::Yes; | |
| #if ENABLE(CONTENT_FILTERING) | |
| if (provisionalDocumentLoader.contentFilterWillHandleProvisionalLoadFailure(error)) { | |
| willContinueLoading = WillContinueLoading::Yes; | |
| contentFilterWillContinueLoading = true; | |
| } | |
| #endif | |
| m_client->dispatchDidFailProvisionalLoad(error, willContinueLoading, willInternallyHandleFailure); | |
| #if ENABLE(CONTENT_FILTERING) | |
| if (contentFilterWillContinueLoading) | |
| provisionalDocumentLoader.contentFilterHandleProvisionalLoadFailure(error); | |
| #endif | |
| m_provisionalLoadErrorBeingHandledURL = { }; | |
| } | |
| void FrameLoader::checkLoadCompleteForThisFrame(LoadWillContinueInAnotherProcess loadWillContinueInAnotherProcess) | |
| { | |
| ASSERT(m_client->hasWebView()); | |
| // FIXME: Should this check be done in checkLoadComplete instead of here? | |
| // FIXME: Why does this one check need to be repeated here, and not the many others from checkCompleted? | |
| if (m_frame->document()->isDelayingLoadEvent()) | |
| return; | |
| switch (m_state) { | |
| case FrameState::Provisional: { | |
| // 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 provisionalDocumentLoader = m_provisionalDocumentLoader; | |
| if (!provisionalDocumentLoader) | |
| return; | |
| // If we've received any errors we may be stuck in the provisional state and actually complete. | |
| auto& error = provisionalDocumentLoader->mainDocumentError(); | |
| if (error.isNull()) | |
| return; | |
| bool isHTTPSByDefaultEnabled { false }; | |
| // Check all children first. | |
| RefPtr<HistoryItem> item; | |
| if (RefPtr page = m_frame->page()) { | |
| if (isBackForwardLoadType(loadType()) && !page->settings().useUIProcessForBackForwardItemLoading()) { | |
| // Reset the back forward list to the last committed history item at the top level. | |
| if (RefPtr localMainFrame = page->localMainFrame()) | |
| item = localMainFrame->loader().history().currentItem(); | |
| } | |
| isHTTPSByDefaultEnabled = page->settings().httpsByDefault(); | |
| } | |
| bool isHTTPSFirstApplicable = (isHTTPSByDefaultEnabled || provisionalDocumentLoader->httpsByDefaultMode() == HTTPSByDefaultMode::UpgradeWithAutomaticFallback) | |
| && provisionalDocumentLoader->httpsByDefaultMode() != HTTPSByDefaultMode::UpgradeWithUserMediatedFallback | |
| && !isHTTPFallbackInProgressOrUpgradeDisabled() | |
| && provisionalDocumentLoader->request().wasSchemeOptimisticallyUpgraded(); | |
| // Only reset if we aren't already going to a new provisional item. | |
| bool shouldReset = !history().provisionalItem(); | |
| if (!provisionalDocumentLoader->isLoadingInAPISense() || provisionalDocumentLoader->isStopping()) { | |
| FRAMELOADER_RELEASE_LOG_FORWARDABLE(FrameLoaderCheckLoadCompleteForThisFrameFailedProvisionalLoad, error.isTimeout(), error.isCancellation(), error.errorCode(), isHTTPSFirstApplicable); | |
| // Provisional load failed before didBeginDocument() could clear the wait state; | |
| // clear it here so this frame stops blocking its parent's completion. | |
| clearWaitingForDelegatedBackForwardLoad(); | |
| if (loadWillContinueInAnotherProcess == LoadWillContinueInAnotherProcess::No && !m_isStoppingForCacheOnlyLoadRetry) { | |
| auto willInternallyHandleFailure = (error.errorRecoveryMethod() == ResourceError::ErrorRecoveryMethod::NoRecovery || (error.errorRecoveryMethod() == ResourceError::ErrorRecoveryMethod::HTTPFallback && (!isHTTPSFirstApplicable || isHTTPFallbackInProgressOrUpgradeDisabled()))) ? WillInternallyHandleFailure::No : WillInternallyHandleFailure::Yes; | |
| if (error.isCancellation() && m_needsCancellationForContentRuleListCrossOriginRedirect) { | |
| willInternallyHandleFailure = WillInternallyHandleFailure::Yes; | |
| m_needsCancellationForContentRuleListCrossOriginRedirect = false; | |
| } | |
| dispatchDidFailProvisionalLoad(*provisionalDocumentLoader, error, willInternallyHandleFailure); | |
| } | |
| ASSERT(!provisionalDocumentLoader->isLoading()); | |
| // If we're in the middle of loading multipart data, we need to restore the document loader. | |
| if (isMultipartReplacing() && !m_documentLoader) | |
| setDocumentLoader(provisionalDocumentLoader.copyRef()); | |
| // Finish resetting the load state, but only if another load hasn't been started by the | |
| // delegate callback. | |
| if (provisionalDocumentLoader == m_provisionalDocumentLoader) | |
| clearProvisionalLoad(); | |
| else if (activeDocumentLoader()) { | |
| URL unreachableURL = activeDocumentLoader()->unreachableURL(); | |
| if (!unreachableURL.isEmpty() && unreachableURL == provisionalDocumentLoader->request().url()) | |
| shouldReset = false; | |
| } | |
| } | |
| if (shouldReset && item) { | |
| if (RefPtr page = m_frame->page()) | |
| protect(page->backForward())->setCurrentItem(*item); | |
| } | |
| return; | |
| } | |
| case FrameState::CommittedPage: { | |
| RefPtr documentLoader = m_documentLoader; | |
| if (!documentLoader) | |
| return; | |
| if (documentLoader->isLoadingInAPISense() && !documentLoader->isStopping() && !m_checkingLoadCompleteForDetachment) | |
| return; | |
| setState(FrameState::Complete); | |
| // FIXME: Is this subsequent work important if we already navigated away? | |
| // Maybe there are bugs because of that, or extra work we can skip because | |
| // the new page is ready. | |
| m_client->forceLayoutForNonHTML(); | |
| // If the user had a scroll point, scroll to it, overriding the anchor point if any. | |
| if (m_frame->page()) { | |
| if (isBackForwardLoadType(m_loadType) || isReload(m_loadType)) | |
| history().restoreScrollPositionAndViewState(); | |
| } | |
| if (m_stateMachine.creatingInitialEmptyDocument() || !m_stateMachine.committedFirstRealDocumentLoad()) | |
| return; | |
| protect(m_progressTracker)->progressCompleted(FrameProgressTracker::LoadCompletionStatus::Success); | |
| if (RefPtr page = m_frame->page()) { | |
| if (m_frame->isMainFrame()) { | |
| tracePoint(MainResourceLoadDidEnd, PAGE_ID); | |
| page->didFinishLoad(); | |
| } | |
| } | |
| auto& error = documentLoader->mainDocumentError(); | |
| auto loadingEvent = AXLoadingEvent::Failed; | |
| if (!error.isNull()) { | |
| FRAMELOADER_RELEASE_LOG(ResourceLoading, "checkLoadCompleteForThisFrame: Finished frame load with error (isTimeout = %d, isCancellation = %d, errorCode = %d)", error.isTimeout(), error.isCancellation(), error.errorCode()); | |
| m_client->dispatchDidFailLoad(error); | |
| loadingEvent = AXLoadingEvent::Failed; | |
| m_errorOccurredInLoading = true; | |
| } else { | |
| FRAMELOADER_RELEASE_LOG_FORWARDABLE(FrameLoaderCheckLoadCompleteForThisFrame); | |
| #if ENABLE(DATA_DETECTION) | |
| RefPtr document = m_frame->document(); | |
| auto types = OptionSet<DataDetectorType>::fromRaw(std::to_underlying(m_frame->settings().dataDetectorTypes())); | |
| if (document && types) { | |
| DataDetection::detectContentInFrame(protect(m_frame).ptr(), types, m_client->dataDetectionReferenceDate(), [weakThis = WeakPtr { *this }](NSArray *results) { | |
| RefPtr protectedThis = weakThis.get(); | |
| if (!protectedThis) | |
| return; | |
| Ref frame = protectedThis->frame(); | |
| frame->dataDetectionResults().setDocumentLevelResults(results); | |
| if (frame->isMainFrame()) | |
| protectedThis->m_client->dispatchDidFinishDataDetection(results); | |
| }); | |
| } | |
| #endif | |
| m_client->dispatchDidFinishLoad(); | |
| loadingEvent = AXLoadingEvent::Finished; | |
| } | |
| // Notify accessibility. | |
| if (RefPtr document = m_frame->document()) { | |
| if (CheckedPtr cache = document->existingAXObjectCache()) | |
| cache->frameLoadingEventNotification(protect(m_frame).ptr(), 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.ptr()); | |
| protect(protect(m_frame->page())->diagnosticLoggingClient())->logDiagnosticMessageWithResult(DiagnosticLoggingKeys::pageLoadedKey(), emptyString(), error.isNull() ? DiagnosticLoggingResultPass : DiagnosticLoggingResultFail, ShouldSample::Yes); | |
| } | |
| m_shouldSkipHTTPSUpgradeForSameSiteNavigation = isHTTPFallbackInProgressOrUpgradeDisabled(); | |
| resetHTTPFallbackInProgress(); | |
| return; | |
| } | |
| case FrameState::Complete: | |
| m_loadType = FrameLoadType::Standard; | |
| frameLoadCompleted(); | |
| return; | |
| } | |
| ASSERT_NOT_REACHED(); | |
| } | |
| 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; | |
| RefPtr initiator = m_frame->document(); | |
| if (initiator) { | |
| originalURL = initiator->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()) { | |
| if (RefPtr localOpener = dynamicDowncast<LocalFrame>(m_frame->opener()); localOpener && localOpener->document()) { | |
| originalURL = localOpener->document()->firstPartyForCookies(); | |
| initiator = localOpener->document(); | |
| } | |
| } | |
| } | |
| // 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); | |
| addSameSiteInfoToRequestIfNeeded(request, initiator.get()); | |
| } | |
| void FrameLoader::didReachLayoutMilestone(OptionSet<LayoutMilestone> milestones) | |
| { | |
| ASSERT(m_frame->isMainFrame()); | |
| m_client->dispatchDidReachLayoutMilestone(milestones); | |
| } | |
| void FrameLoader::didFirstLayout() | |
| { | |
| #if PLATFORM(IOS_FAMILY) | |
| // Only send layout-related delegate callbacks synchronously for the main frame to | |
| // avoid reentering layout for the main frame while delivering a layout-related delegate | |
| // callback for a subframe. | |
| if (m_frame.ptr() != &m_frame->page()->mainFrame()) | |
| return; | |
| #endif | |
| if (m_frame->page() && isBackForwardLoadType(m_loadType)) | |
| restoreScrollPositionAndViewStateSoon(); | |
| if (m_stateMachine.committedFirstRealDocumentLoad() && !m_stateMachine.isDisplayingInitialEmptyDocument() && !m_stateMachine.firstLayoutDone()) | |
| m_stateMachine.advanceTo(FrameLoaderStateMachine::FirstLayoutDone); | |
| } | |
| void FrameLoader::restoreScrollPositionAndViewStateSoon() | |
| { | |
| if (m_shouldRestoreScrollPositionAndViewState) | |
| return; | |
| m_shouldRestoreScrollPositionAndViewState = true; | |
| if (RefPtr document = m_frame->document()) | |
| document->scheduleRenderingUpdate(RenderingUpdateStep::RestoreScrollPositionAndViewState); | |
| } | |
| static bool scrollingSuppressedByNavigationAPI(Document* document) | |
| { | |
| if (!document || !document->settings().navigationAPIEnabled()) | |
| return false; | |
| RefPtr window = document->window(); | |
| return window && window->navigation().suppressNormalScrollRestoration(); | |
| } | |
| void FrameLoader::restoreScrollPositionAndViewStateNowIfNeeded() | |
| { | |
| if (!m_shouldRestoreScrollPositionAndViewState) | |
| return; | |
| m_shouldRestoreScrollPositionAndViewState = false; | |
| history().restoreScrollPositionAndViewState(); | |
| } | |
| void FrameLoader::didReachVisuallyNonEmptyState() | |
| { | |
| ASSERT(m_frame->isRootFrame()); | |
| m_client->dispatchDidReachVisuallyNonEmptyState(); | |
| } | |
| void FrameLoader::frameLoadCompleted() | |
| { | |
| // Note: Can be called multiple times. | |
| m_client->frameLoadCompleted(); | |
| history().updateForFrameLoadCompleted(); | |
| // After a canceled provisional load, firstLayoutDone is false. | |
| // Reset it to true if we're displaying a page. | |
| if (m_documentLoader && m_stateMachine.committedFirstRealDocumentLoad() && !m_stateMachine.isDisplayingInitialEmptyDocument() && !m_stateMachine.firstLayoutDone()) | |
| m_stateMachine.advanceTo(FrameLoaderStateMachine::FirstLayoutDone); | |
| } | |
| 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 | |
| UnloadCountIncrementer UnloadCountIncrementer(m_frame->document()); | |
| // detachChildren() will fire the unload event in each subframe and the | |
| // HTML specification states that navigations should be prevented during the prompt to unload algorithm: | |
| // https://html.spec.whatwg.org/multipage/browsing-the-web.html#navigate | |
| std::unique_ptr<NavigationDisabler> navigationDisabler; | |
| if (m_frame->isMainFrame()) | |
| navigationDisabler = makeUnique<NavigationDisabler>(protect(m_frame).ptr()); | |
| // 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(protect(m_frame->document()).get()); | |
| Vector<Ref<LocalFrame>, 16> childrenToDetach; | |
| childrenToDetach.reserveInitialCapacity(m_frame->tree().childCount()); | |
| for (RefPtr child = m_frame->tree().lastChild(); child; child = child->tree().previousSibling()) { | |
| if (RefPtr localChild = dynamicDowncast<LocalFrame>(child)) | |
| childrenToDetach.append(localChild.releaseNonNull()); | |
| } | |
| for (auto& child : childrenToDetach) | |
| child->loader().detachFromParent(); | |
| } | |
| void FrameLoader::closeAndRemoveChild(LocalFrame& child) | |
| { | |
| child.tree().detachFromParent(); | |
| child.setView(nullptr); | |
| child.willDetachPage(); | |
| child.detachFromPage(); | |
| m_frame->tree().removeChild(child); | |
| } | |
| // Called every time a resource is completely loaded or an error is received. | |
| void FrameLoader::checkLoadComplete(LoadWillContinueInAnotherProcess loadWillContinueInAnotherProcess) | |
| { | |
| m_shouldCallCheckLoadComplete = false; | |
| if (!m_frame->page()) | |
| return; | |
| ASSERT(m_client->hasWebView()); | |
| // FIXME: Always traversing the entire frame tree is a bit inefficient, but | |
| // is currently needed in order to null out the previous history item for all frames. | |
| Vector<Ref<LocalFrame>, 16> frames; | |
| for (RefPtr frame = m_frame->mainFrame(); frame; frame = frame->tree().traverseNext()) { | |
| if (RefPtr localFrame = dynamicDowncast<LocalFrame>(frame)) | |
| frames.append(localFrame.releaseNonNull()); | |
| } | |
| // Provisional frames that are not in the frame tree need to be included to report provisional load failures. | |
| if (m_frame->settings().siteIsolationEnabled()) { | |
| bool containsThisFrame = std::ranges::any_of(frames, [thisFrame = Ref { m_frame.get() }] (auto& frame) { | |
| return frame.ptr() == thisFrame.ptr(); | |
| }); | |
| if (!containsThisFrame) | |
| frames.append(m_frame); | |
| } | |
| // To process children before their parents, iterate the vector backwards. | |
| for (Ref frame : frames | std::views::reverse) { | |
| if (frame->page()) | |
| frame->loader().checkLoadCompleteForThisFrame(loadWillContinueInAnotherProcess); | |
| } | |
| } | |
| int FrameLoader::numPendingOrLoadingRequests(bool recurse) const | |
| { | |
| Ref frame = m_frame.get(); | |
| if (!recurse) | |
| return protect(frame->document())->cachedResourceLoader().requestCount(); | |
| int count = 0; | |
| for (RefPtr<Frame> descendantFrame = frame.ptr(); descendantFrame; descendantFrame = descendantFrame->tree().traverseNext(frame.ptr())) { | |
| if (RefPtr localFrame = dynamicDowncast<LocalFrame>(descendantFrame)) | |
| count += protect(localFrame->document())->cachedResourceLoader().requestCount(); | |
| } | |
| return count; | |
| } | |
| String FrameLoader::userAgent(const URL& url) const | |
| { | |
| String userAgent; | |
| if (RefPtr document = m_frame->document()) { | |
| if (auto userAgentQuirk = document->quirks().storageAccessUserAgentStringQuirkForDomain(url); !userAgentQuirk.isEmpty()) | |
| userAgent = userAgentQuirk; | |
| } | |
| if (userAgent.isEmpty()) { | |
| Ref mainFrame = m_frame->mainFrame(); | |
| if (m_frame->settings().needsSiteSpecificQuirks()) | |
| userAgent = mainFrame->customUserAgentAsSiteSpecificQuirks(); | |
| if (userAgent.isEmpty()) | |
| userAgent = mainFrame->customUserAgent(); | |
| } | |
| InspectorInstrumentation::applyUserAgentOverride(protect(m_frame), userAgent); | |
| if (userAgent.isEmpty() || m_client->hasCustomUserAgent()) | |
| userAgent = m_client->userAgent(url); | |
| if (m_frame->settings().needsSiteSpecificQuirks()) { | |
| if (m_client->hasCustomUserAgent()) { | |
| if (RefPtr document = m_frame->document()) { | |
| if (auto userAgentQuirk = Quirks::needsCustomUserAgentOverride(document->topURL(), { }, userAgent)) | |
| userAgent = WTF::move(*userAgentQuirk); | |
| } | |
| } | |
| if (RefPtr document = m_frame->document()) { | |
| auto topFullURL = document->topURL(); | |
| auto topFullURLPath = topFullURL.path(); | |
| if (RegistrableDomain(topFullURL).string() == "easyjet.com"_s && topFullURLPath.contains("routemap"_s)) { | |
| auto urlDomainString = RegistrableDomain(url).string(); | |
| if (urlDomainString == "bing.com"_s) { | |
| // FIXME: Move this to a proper UA override singular mechanism | |
| // https://bugs.webkit.org/show_bug.cgi?id=274374 | |
| userAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:135.0) Gecko/20100101 Firefox/135.0"_s; | |
| } | |
| } | |
| } | |
| } | |
| verifyUserAgent(userAgent); | |
| return userAgent; | |
| } | |
| String FrameLoader::navigatorPlatform() const | |
| { | |
| auto customNavigatorPlatform = protect(m_frame->mainFrame())->customNavigatorPlatform(); | |
| if (!customNavigatorPlatform.isEmpty()) | |
| return customNavigatorPlatform; | |
| return String(); | |
| } | |
| void FrameLoader::dispatchOnloadEvents() | |
| { | |
| m_client->dispatchDidDispatchOnloadEvents(); | |
| if (auto* documentLoader = this->documentLoader()) | |
| documentLoader->dispatchOnloadEvents(); | |
| } | |
| void FrameLoader::frameDetached() | |
| { | |
| // Calling stopAllLoadersAndCheckCompleteness() can cause the frame to be deallocated, including the frame loader. | |
| Ref frame = m_frame.get(); | |
| // https://html.spec.whatwg.org/multipage/document-sequences.html#destroy-a-child-navigable | |
| // Step 4: Inform the navigation API about child navigable destruction | |
| if (RefPtr document = frame->document(); document && document->settings().navigationAPIEnabled()) { | |
| if (RefPtr window = document->window()) | |
| protect(window->navigation())->informAboutChildNavigableDestruction(); | |
| } | |
| if (m_checkTimer.isActive()) { | |
| m_checkTimer.stop(); | |
| checkCompletenessNow(); | |
| } | |
| m_crossOriginParentSyntheticSameDocLoadEventTimer.stop(); | |
| if (frame->document()->backForwardCacheState() != Document::InBackForwardCache) | |
| stopAllLoadersAndCheckCompleteness(); | |
| detachFromParent(); | |
| if (frame->document()->backForwardCacheState() != Document::InBackForwardCache) | |
| protect(frame->document())->stopActiveDOMObjects(); | |
| } | |
| void FrameLoader::detachFromParent() | |
| { | |
| // Calling stopAllLoaders() can cause the frame to be deallocated, including the frame loader. | |
| Ref frame = m_frame.get(); | |
| closeURL(); | |
| history().saveScrollPositionAndViewStateToItem(protect(history().currentItem()).get()); | |
| detachChildren(); | |
| if (frame->document()->backForwardCacheState() != Document::InBackForwardCache) { | |
| // stopAllLoaders() needs to be called after detachChildren() if the document is not in the back/forward 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. | |
| // closeURL() already unloaded the document, so stopping what it still loads does not abort its navigation. | |
| SetForScope doNotAbortNavigationAPI { m_doNotAbortNavigationAPI, true }; | |
| stopAllLoaders(ClearProvisionalItem::Yes, StopLoadingPolicy::AlwaysStopLoading); | |
| } | |
| InspectorInstrumentation::frameDetachedFromParent(frame); | |
| detachViewsAndDocumentLoader(); | |
| m_progressTracker = nullptr; | |
| if (RefPtr parent = dynamicDowncast<LocalFrame>(frame->tree().parent())) { | |
| Ref parentLoader = parent->loader(); | |
| parentLoader->closeAndRemoveChild(frame); | |
| parentLoader->scheduleCheckCompleted(); | |
| parentLoader->scheduleCheckLoadComplete(); | |
| } else { | |
| if (RefPtr parent = frame->tree().parent()) | |
| parent->tree().removeChild(frame); | |
| frame->setView(nullptr); | |
| frame->willDetachPage(); | |
| frame->detachFromPage(); | |
| } | |
| } | |
| void FrameLoader::detachViewsAndDocumentLoader() | |
| { | |
| m_client->detachedFromParent2(); | |
| setDocumentLoader(nullptr); | |
| m_client->detachedFromParent3(); | |
| } | |
| ResourceRequestCachePolicy FrameLoader::defaultRequestCachingPolicy(const ResourceRequest& request, FrameLoadType loadType, bool isMainResource) | |
| { | |
| if (m_overrideCachePolicyForTesting) | |
| return m_overrideCachePolicyForTesting.value(); | |
| if (isMainResource) { | |
| if (isReload(loadType) || request.isConditional()) | |
| return ResourceRequestCachePolicy::ReloadIgnoringCacheData; | |
| return ResourceRequestCachePolicy::UseProtocolCachePolicy; | |
| } | |
| if (request.isConditional()) | |
| return ResourceRequestCachePolicy::ReloadIgnoringCacheData; | |
| RefPtr documentLoader = this->documentLoader(); | |
| if (documentLoader && 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, | |
| // but we do not want this to apply to subresources | |
| // 2. Delegates that modify the cache policy using willSendRequest: should | |
| // not affect any other resources. Such changes need to be done | |
| // per request. | |
| 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. | |
| return (mainDocumentOriginalCachePolicy == ResourceRequestCachePolicy::ReturnCacheDataDontLoad) ? ResourceRequestCachePolicy::ReturnCacheDataElseLoad : mainDocumentOriginalCachePolicy; | |
| } | |
| return ResourceRequestCachePolicy::UseProtocolCachePolicy; | |
| } | |
| void FrameLoader::updateRequestAndAddExtraFields(ResourceRequest& request, IsMainResource mainResource, FrameLoadType loadType, ShouldUpdateAppInitiatedValue shouldUpdate, IsServiceWorkerNavigationLoad isServiceWorkerNavigationLoad, WillOpenInNewWindow willOpenInNewWindow, Document* initiator) | |
| { | |
| updateRequestAndAddExtraFields(protect(m_frame), request, mainResource, loadType, shouldUpdate, isServiceWorkerNavigationLoad, willOpenInNewWindow, initiator); | |
| } | |
| URL FrameLoader::partitionedFirstPartyForCookiesForSubframeNavigation(const Document& document) | |
| { | |
| if (document.hasUnpartitionedStorageAccess()) { | |
| if (RefPtr page = document.page()) | |
| return page->mainFrameURL(); | |
| } | |
| return document.firstPartyForCookies(); | |
| } | |
| void FrameLoader::updateRequestAndAddExtraFields(Frame& targetFrame, ResourceRequest& request, IsMainResource mainResource, FrameLoadType loadType, ShouldUpdateAppInitiatedValue shouldUpdate, IsServiceWorkerNavigationLoad isServiceWorkerNavigationLoad, WillOpenInNewWindow willOpenInNewWindow, Document* initiator) | |
| { | |
| ASSERT(isServiceWorkerNavigationLoad == IsServiceWorkerNavigationLoad::No || mainResource != IsMainResource::Yes); | |
| // If the request came from a previous process due to process-swap-on-navigation then we should not modify the request. | |
| if (m_currentLoadContinuingState == LoadContinuingState::ContinuingWithRequest) | |
| return; | |
| auto* localFrame = dynamicDowncast<LocalFrame>(targetFrame); | |
| RefPtr document = localFrame ? localFrame->document() : nullptr; | |
| // 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>). | |
| bool isMainResource = mainResource == IsMainResource::Yes; | |
| bool isMainFrameMainResource = isMainResource && (targetFrame.isMainFrame() || willOpenInNewWindow == WillOpenInNewWindow::Yes); | |
| if (request.firstPartyForCookies().isEmpty()) { | |
| if (isMainFrameMainResource) | |
| request.setFirstPartyForCookies(request.url()); | |
| else if (document) | |
| request.setFirstPartyForCookies(isMainResource ? partitionedFirstPartyForCookiesForSubframeNavigation(*document) : document->firstPartyForCookies()); | |
| } | |
| RefPtr page = targetFrame.page(); | |
| if (request.isSameSiteUnspecified()) { | |
| RefPtr updatedInitiator = initiator; | |
| if (!updatedInitiator && document) { | |
| updatedInitiator = document.get(); | |
| if (isMainResource && !document->hasUnpartitionedStorageAccess()) { | |
| RefPtr ownerFrame = dynamicDowncast<LocalFrame>(localFrame->tree().parent()); | |
| if (!ownerFrame && m_stateMachine.isDisplayingInitialEmptyDocument()) { | |
| if (RefPtr localOpener = dynamicDowncast<LocalFrame>(localFrame->opener())) | |
| ownerFrame = WTF::move(localOpener); | |
| } | |
| if (ownerFrame) | |
| updatedInitiator = ownerFrame->document(); | |
| ASSERT(ownerFrame || localFrame->isMainFrame() || localFrame->settings().siteIsolationEnabled()); | |
| } | |
| } | |
| addSameSiteInfoToRequestIfNeeded(request, updatedInitiator.get()); | |
| } | |
| // In case of service worker navigation load, we inherit isTopSite from the FetchEvent request directly. | |
| if (isServiceWorkerNavigationLoad == IsServiceWorkerNavigationLoad::No) | |
| request.setIsTopSite(isMainFrameMainResource); | |
| bool hasSpecificCachePolicy = request.cachePolicy() != ResourceRequestCachePolicy::UseProtocolCachePolicy; | |
| bool cachingDisabledByWebInspector = page && page->isResourceCachingDisabledByWebInspector(); | |
| if (cachingDisabledByWebInspector) { | |
| request.setCachePolicy(ResourceRequestCachePolicy::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 (!hasSpecificCachePolicy && request.cachePolicy() == ResourceRequestCachePolicy::ReloadIgnoringCacheData) { | |
| auto overrideHeaderIfNeeded = [&] (HTTPHeaderName name, const String& value) { | |
| if (cachingDisabledByWebInspector) | |
| request.addHTTPHeaderFieldIfNotPresent(name, value); | |
| else | |
| request.setHTTPHeaderField(name, value); | |
| }; | |
| if (loadType == FrameLoadType::Reload) | |
| overrideHeaderIfNeeded(HTTPHeaderName::CacheControl, HTTPHeaderValues::maxAge0()); | |
| else if (loadType == FrameLoadType::ReloadFromOrigin) { | |
| overrideHeaderIfNeeded(HTTPHeaderName::CacheControl, HTTPHeaderValues::noCache()); | |
| overrideHeaderIfNeeded(HTTPHeaderName::Pragma, HTTPHeaderValues::noCache()); | |
| } | |
| } | |
| if (m_overrideResourceLoadPriorityForTesting) | |
| request.setPriority(m_overrideResourceLoadPriorityForTesting.value()); | |
| // Make sure we send the Origin header. | |
| addHTTPOriginIfNeeded(request, String()); | |
| applyUserAgentIfNeeded(request); | |
| if (isMainResource) | |
| request.setHTTPHeaderField(HTTPHeaderName::Accept, CachedResourceRequest::acceptHeaderValueFromType(CachedResource::Type::MainResource, request.url().protocolIsSecure())); | |
| if (document && localFrame->settings().privateTokenUsageByThirdPartyEnabled() && !localFrame->loader().client().isRemoteWorkerFrameLoaderClient()) | |
| request.setIsPrivateTokenUsageByThirdPartyAllowed(PermissionsPolicy::isFeatureEnabled(PermissionsPolicy::Feature::PrivateToken, *document, PermissionsPolicy::ShouldReportViolation::No)); | |
| // Only set fallback array if it's still empty (later attempts may be incorrect, see bug 117818). | |
| if (document && request.responseContentDispositionEncodingFallbackArray().isEmpty()) { | |
| // Always try UTF-8. If that fails, try frame encoding (if any) and then the default. | |
| request.setResponseContentDispositionEncodingFallbackArray("UTF-8"_s, document->encoding(), localFrame->settings().defaultTextEncodingName()); | |
| } | |
| if (RefPtr localMainFrame = dynamicDowncast<LocalFrame>(targetFrame.mainFrame())) { | |
| if (shouldUpdate == ShouldUpdateAppInitiatedValue::Yes) { | |
| if (RefPtr documentLoader = localMainFrame->loader().documentLoader()) | |
| request.setIsAppInitiated(documentLoader->lastNavigationWasAppInitiated()); | |
| } | |
| } | |
| if (page && isMainResource && !request.didFilterLinkDecoration()) { | |
| auto [filteredURL, didFilter] = page->chrome().client().applyLinkDecorationFilteringWithResult(request.url(), LinkDecorationFilteringTrigger::Navigation); | |
| request.setURL(WTF::move(filteredURL), didFilter == DidFilterLinkDecoration::Yes); | |
| } | |
| } | |
| void FrameLoader::scheduleRefreshIfNeeded(Document& document, const String& content, IsMetaRefresh isMetaRefresh) | |
| { | |
| double delay = 0; | |
| String urlString; | |
| if (parseMetaHTTPEquivRefresh(content, delay, urlString)) { | |
| auto completedURL = urlString.isEmpty() ? document.url() : document.encodingParseURL(urlString); | |
| if (!completedURL.protocolIsJavaScript()) | |
| protect(protect(m_frame)->navigationScheduler())->scheduleRedirect(document, delay, WTF::move(completedURL), isMetaRefresh); | |
| else { | |
| auto message = makeString("Refused to refresh "_s, document.url().stringCenterEllipsizedToLength(), " to a javascript: URL"_s); | |
| document.addConsoleMessage(MessageSource::Security, MessageLevel::Error, message); | |
| } | |
| } | |
| } | |
| void FrameLoader::addHTTPOriginIfNeeded(ResourceRequest& request, const String& origin) | |
| { | |
| if (!request.httpOrigin().isEmpty()) | |
| return; // Request already has an Origin header. | |
| // Don't send an Origin header for GET or HEAD to avoid privacy issues. | |
| // For example, if an intranet page has a hyperlink to an external web | |
| // site, we don't want to include the Origin of the request because it | |
| // will leak the internal host name. Similar privacy concerns have lead | |
| // to the widespread suppression of the Referer header at the network | |
| // layer. | |
| if (request.httpMethod() == "GET"_s || request.httpMethod() == "HEAD"_s) | |
| return; | |
| // FIXME: take referrer-policy into account. | |
| // https://bugs.webkit.org/show_bug.cgi?id=209066 | |
| // For non-GET and non-HEAD methods, always send an Origin header so the | |
| // server knows we support this feature. | |
| if (origin.isEmpty()) { | |
| // If we don't know what origin header to attach, we attach the value | |
| // for an opaque origin. | |
| request.setHTTPOrigin(SecurityOrigin::createOpaque()->toString()); | |
| return; | |
| } | |
| request.setHTTPOrigin(origin); | |
| } | |
| // Implements the "'Same-site' and 'cross-site' Requests" algorithm from <https://tools.ietf.org/html/draft-ietf-httpbis-cookie-same-site-00#section-2.1>. | |
| // The algorithm is ammended to treat URLs that inherit their security origin from their owner (e.g. about:blank) | |
| // as same-site. This matches the behavior of Chrome and Firefox. | |
| void FrameLoader::addSameSiteInfoToRequestIfNeeded(ResourceRequest& request, const Document* initiator) | |
| { | |
| if (!request.isSameSiteUnspecified()) | |
| return; | |
| if (!initiator) { | |
| request.setIsSameSite(true); | |
| return; | |
| } | |
| if (SecurityPolicy::shouldInheritSecurityOriginFromOwner(request.url())) { | |
| request.setIsSameSite(true); | |
| return; | |
| } | |
| request.setIsSameSite(initiator->isSameSiteForCookies(request.url())); | |
| } | |
| void FrameLoader::loadPostRequest(FrameLoadRequest&& request, const String& referrer, FrameLoadType loadType, Event* event, RefPtr<const FormSubmission>&& formSubmission, CompletionHandler<void()>&& completionHandler) | |
| { | |
| FRAMELOADER_RELEASE_LOG(ResourceLoading, "loadPostRequest: frame load started"); | |
| m_errorOccurredInLoading = false; | |
| Ref frame = m_frame.get(); | |
| auto frameName = request.frameName(); | |
| LockHistory lockHistory = request.lockHistory(); | |
| AllowNavigationToInvalidURL allowNavigationToInvalidURL = request.allowNavigationToInvalidURL(); | |
| NewFrameOpenerPolicy openerPolicy = request.newFrameOpenerPolicy(); | |
| const ResourceRequest& inRequest = request.resourceRequest(); | |
| const URL& url = inRequest.url(); | |
| const String& contentType = inRequest.httpContentType(); | |
| String origin = inRequest.httpOrigin(); | |
| ResourceRequest workingResourceRequest(URL { url }); | |
| if (!referrer.isEmpty()) | |
| workingResourceRequest.setHTTPReferrer(referrer); | |
| workingResourceRequest.setHTTPOrigin(origin); | |
| workingResourceRequest.setHTTPMethod("POST"_s); | |
| workingResourceRequest.setHTTPBody(inRequest.httpBody()); | |
| workingResourceRequest.setHTTPContentType(contentType); | |
| RefPtr targetFrame = formSubmission || frameName.isEmpty() ? nullptr : dynamicDowncast<LocalFrame>(findFrameForNavigation(frameName)); | |
| auto willOpenInNewWindow = !frameName.isEmpty() && !targetFrame ? WillOpenInNewWindow::Yes : WillOpenInNewWindow::No; | |
| updateRequestAndAddExtraFields(workingResourceRequest, IsMainResource::Yes, loadType, ShouldUpdateAppInitiatedValue::Yes, FrameLoader::IsServiceWorkerNavigationLoad::No, willOpenInNewWindow, protect(request.requester()).ptr()); | |
| if (RefPtr document = frame->document()) | |
| protect(document->contentSecurityPolicy())->upgradeInsecureRequestIfNeeded(workingResourceRequest, ContentSecurityPolicy::InsecureRequestType::Load); | |
| NavigationAction action { protect(request.requester()), workingResourceRequest, request.initiatedByMainFrame(), request.isRequestFromClientOrUserInput(), loadType, true, event, request.shouldOpenExternalURLsPolicy(), { } }; | |
| action.setLockHistory(lockHistory); | |
| action.setLockBackForwardList(request.lockBackForwardList()); | |
| action.setShouldReplaceDocumentIfJavaScriptURL(request.shouldReplaceDocumentIfJavaScriptURL()); | |
| action.setNewFrameOpenerPolicy(request.newFrameOpenerPolicy()); | |
| action.setNavigationAPIType(determineNavigationType(loadType, request.navigationHistoryBehavior())); | |
| if (!frameName.isEmpty()) { | |
| // The search for a target frame is done earlier in the case of form submission. | |
| if (targetFrame) { | |
| targetFrame->loader().loadWithNavigationAction(WTF::move(workingResourceRequest), WTF::move(action), loadType, WTF::move(formSubmission), allowNavigationToInvalidURL, request.shouldTreatAsContinuingLoad(), ShouldRestoreFromBackForwardCache::No, std::nullopt, WTF::move(completionHandler)); | |
| return; | |
| } | |
| // https://html.spec.whatwg.org/#the-rules-for-choosing-a-browsing-context-given-a-browsing-context-name (Step 8.2) | |
| if (protect(request.requester())->shouldForceNoOpenerBasedOnCOOP()) { | |
| frameName = blankTargetFrameName(); | |
| openerPolicy = NewFrameOpenerPolicy::Suppress; | |
| } | |
| RefPtr document = frame->document(); | |
| if (request.resourceRequest().url().protocolIsBlob() && !protect(document->securityOrigin())->isSameOriginAs(protect(document->topOrigin()))) { | |
| frameName = blankTargetFrameName(); | |
| openerPolicy = NewFrameOpenerPolicy::Suppress; | |
| } | |
| policyChecker().checkNewWindowPolicy(WTF::move(action), WTF::move(workingResourceRequest), WTF::move(formSubmission), frameName, [this, protectedThis = Ref { *this }, allowNavigationToInvalidURL, openerPolicy, completionHandler = WTF::move(completionHandler)] (ResourceRequest&& request, WeakPtr<const FormSubmission>&& weakFormSubmission, const AtomString& frameName, const NavigationAction& action, ShouldContinuePolicyCheck shouldContinue) mutable { | |
| continueLoadAfterNewWindowPolicy(WTF::move(request), RefPtr { weakFormSubmission.get() }, frameName, action, shouldContinue, allowNavigationToInvalidURL, openerPolicy); | |
| completionHandler(); | |
| }); | |
| return; | |
| } | |
| if (Ref document = *frame->document(); isPlatformObjectSameOrigin(protect(request.requesterSecurityOrigin()), request.requester().agentClusterIdentifier(), protect(document->securityOrigin()), document->agentClusterIdentifier())) { | |
| RefPtr formState = formSubmission ? protect(formSubmission->state()): nullptr; | |
| if (dispatchNavigateEvent(loadType, request, false, formState.get()).isNotCompleted()) | |
| return completionHandler(); | |
| } | |
| // must grab this now, since this load may stop the previous load and clear this flag | |
| bool isRedirect = m_quickRedirectComing; | |
| loadWithNavigationAction(WTF::move(workingResourceRequest), WTF::move(action), loadType, WTF::move(formSubmission), allowNavigationToInvalidURL, request.shouldTreatAsContinuingLoad(), ShouldRestoreFromBackForwardCache::No, std::nullopt, [this, protectedThis = Ref { *this }, isRedirect, completionHandler = WTF::move(completionHandler)] mutable { | |
| if (isRedirect) { | |
| m_quickRedirectComing = false; | |
| if (auto* provisionalDocumentLoader = m_provisionalDocumentLoader.get()) | |
| provisionalDocumentLoader->setIsClientRedirect(true); | |
| else if (auto* policyDocumentLoader = m_policyDocumentLoader.get()) | |
| policyDocumentLoader->setIsClientRedirect(true); | |
| } | |
| completionHandler(); | |
| }); | |
| } | |
| ResourceLoaderIdentifier FrameLoader::loadResourceSynchronously(const ResourceRequest& request, ClientCredentialPolicy clientCredentialPolicy, const FetchOptions& options, const HTTPHeaderMap& originalRequestHeaders, ResourceError& error, ResourceResponse& response, RefPtr<SharedBuffer>& data) | |
| { | |
| ASSERT(m_frame->document()); | |
| String referrer = SecurityPolicy::generateReferrerHeader(m_frame->document()->referrerPolicy(), request.url(), outgoingReferrerURL(), OriginAccessPatternsForWebProcess::singleton()); | |
| ResourceRequest initialRequest = request; | |
| initialRequest.setTimeoutInterval(10); | |
| if (!referrer.isEmpty()) | |
| initialRequest.setHTTPReferrer(referrer); | |
| addHTTPOriginIfNeeded(initialRequest, outgoingOrigin()); | |
| if (RefPtr page = m_frame->page()) | |
| initialRequest.setFirstPartyForCookies(page->mainFrameURL()); | |
| updateRequestAndAddExtraFields(initialRequest, IsMainResource::No); | |
| applyUserAgentIfNeeded(initialRequest); | |
| URL initialRequestURL = initialRequest.url(); | |
| ResourceRequest newRequest = WTF::move(initialRequest); | |
| auto identifier = requestFromDelegate(newRequest, error); | |
| Ref frame { m_frame.get() }; | |
| #if ENABLE(CONTENT_EXTENSIONS) | |
| if (error.isNull()) { | |
| RefPtr userContentProvider = frame->userContentProvider(); | |
| RefPtr page = frame->page(); | |
| RefPtr documentLoader = m_documentLoader; | |
| if (page && userContentProvider && documentLoader) { | |
| auto results = userContentProvider->processContentRuleListsForLoad(*page, newRequest.url(), ContentExtensions::ResourceType::Fetch, *documentLoader); | |
| bool shouldBlock = results.shouldBlock(); | |
| ContentExtensions::applyResultsToRequest(WTF::move(results), page.get(), newRequest); | |
| if (shouldBlock) { | |
| newRequest = { }; | |
| error = ResourceError(errorDomainWebKitInternal, 0, WTF::move(initialRequestURL), emptyString()); | |
| response = { }; | |
| data = nullptr; | |
| } | |
| } | |
| } | |
| #endif | |
| protect(protect(frame->document())->contentSecurityPolicy())->upgradeInsecureRequestIfNeeded(newRequest, ContentSecurityPolicy::InsecureRequestType::Load); | |
| if (error.isNull()) { | |
| ASSERT(!newRequest.isNull()); | |
| RefPtr documentLoader = this->documentLoader(); | |
| Vector<uint8_t> buffer; | |
| platformStrategies()->loaderStrategy()->loadResourceSynchronously(*this, identifier, newRequest, clientCredentialPolicy, options, originalRequestHeaders, error, response, buffer); | |
| data = SharedBuffer::create(WTF::move(buffer)); | |
| ResourceLoadObserver::singleton().logSubresourceLoading(protect(m_frame).ptr(), newRequest, response, | |
| (isScriptLikeDestination(options.destination) ? ResourceLoadObserver::FetchDestinationIsScriptLike::Yes : ResourceLoadObserver::FetchDestinationIsScriptLike::No)); | |
| } | |
| notifier().sendRemainingDelegateMessages(protect(documentLoader()), identifier, request, response, data.get(), data ? data->size() : 0, -1, error); | |
| return identifier; | |
| } | |
| void FrameLoader::receivedMainResourceError(const ResourceError& error, LoadWillContinueInAnotherProcess loadWillContinueInAnotherProcess) | |
| { | |
| // Retain because the stop may release the last reference to it. | |
| Ref frame = m_frame.get(); | |
| RefPtr loader = activeDocumentLoader(); | |
| // FIXME: Don't want to do this if an entirely new load is going, so should check | |
| // that both data sources on the frame are either this or nil. | |
| stop(); | |
| if (loadWillContinueInAnotherProcess == LoadWillContinueInAnotherProcess::No && m_client->shouldFallBack(error)) { | |
| if (RefPtr owner = dynamicDowncast<HTMLObjectElement>(frame->ownerElement())) | |
| owner->renderFallbackContent(); | |
| } | |
| if (m_state == FrameState::Provisional && m_provisionalDocumentLoader) { | |
| if (m_submittedFormURL == m_provisionalDocumentLoader->originalRequestCopy().url()) | |
| m_submittedFormURL = URL(); | |
| // We might have made a back/forward cache item, but now we're bailing out due to an error before we ever | |
| // transitioned to the new page (before WebFrameState == commit). The goal here is to restore any state | |
| // so that the existing view (that wenever got far enough to replace) can continue being used. | |
| history().invalidateCurrentItemCachedPage(); | |
| // Call clientRedirectCancelledOrFinished here so that the frame load delegate is notified that the redirect's | |
| // status has changed, if there was a redirect. The frame load delegate may have saved some state about | |
| // the redirect in its -webView:willPerformClientRedirectToURL:delay:fireDate:forFrame:. Since we are definitely | |
| // not going to use this provisional resource, as it was cancelled, notify the frame load delegate that the redirect | |
| // has ended. | |
| if (m_sentRedirectNotification) | |
| clientRedirectCancelledOrFinished(NewLoadInProgress::No); | |
| } | |
| if (frame->isMainFrame() || loadWillContinueInAnotherProcess == LoadWillContinueInAnotherProcess::No) | |
| checkCompleted(); | |
| else if (loadWillContinueInAnotherProcess == LoadWillContinueInAnotherProcess::Yes) { | |
| ASSERT(frame->settings().siteIsolationEnabled()); | |
| m_provisionalLoadHappeningInAnotherProcess = true; | |
| } | |
| if (frame->page()) | |
| checkLoadComplete(loadWillContinueInAnotherProcess); | |
| } | |
| void FrameLoader::continueFragmentScrollAfterNavigationPolicy(const ResourceRequest& request, const SecurityOrigin* requesterOrigin, bool shouldContinue, NavigationHistoryBehavior historyHandling) | |
| { | |
| m_quickRedirectComing = false; | |
| if (!shouldContinue) | |
| return; | |
| // Calling stopLoading() on the provisional document loader can cause the underlying | |
| // frame to be deallocated. | |
| Ref frame = m_frame.get(); | |
| // 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())) { | |
| protect(m_provisionalDocumentLoader)->stopLoading(); | |
| FRAMELOADER_RELEASE_LOG(ResourceLoading, "continueFragmentScrollAfterNavigationPolicy: Clearing provisional document loader (m_provisionalDocumentLoader=%p)", m_provisionalDocumentLoader.get()); | |
| setProvisionalDocumentLoader(nullptr); | |
| } | |
| bool isRedirect = m_quickRedirectComing || policyChecker().loadType() == FrameLoadType::RedirectWithLockedBackForwardList; | |
| loadInSameDocument(request.url(), nullptr, requesterOrigin, !isRedirect, historyHandling); | |
| } | |
| bool FrameLoader::shouldPerformFragmentNavigation(bool isFormSubmission, const String& httpMethod, FrameLoadType loadType, const URL& url) | |
| { | |
| // We don't do this if we are submitting a form with method other than "GET", explicitly reloading, | |
| // currently displaying a frameset, or if the URL does not have a fragment. | |
| // These rules were originally based on what KHTML was doing in KHTMLPart::openURL. | |
| // FIXME: What about load types other than Standard and Reload? | |
| return (!isFormSubmission || equalLettersIgnoringASCIICase(httpMethod, "get"_s)) | |
| && !isReload(loadType) | |
| && loadType != FrameLoadType::Same | |
| && m_frame->document()->backForwardCacheState() != Document::InBackForwardCache | |
| && !shouldReload(protect(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. | |
| && !protect(m_frame->document())->isFrameSet() | |
| && !stateMachine().isDisplayingInitialEmptyDocument(); | |
| } | |
| static bool LEGACY_NODELETE itemAllowsScrollRestoration(HistoryItem* historyItem, FrameLoadType loadType) | |
| { | |
| if (!historyItem) | |
| return true; | |
| switch (loadType) { | |
| case FrameLoadType::Back: | |
| case FrameLoadType::Forward: | |
| case FrameLoadType::IndexedBackForward: | |
| return historyItem->shouldRestoreScrollPosition(); | |
| default: | |
| break; | |
| } | |
| return true; | |
| } | |
| static bool LEGACY_NODELETE isSameDocumentReload(bool isNewNavigation, FrameLoadType loadType) | |
| { | |
| return !isNewNavigation && !isBackForwardLoadType(loadType); | |
| } | |
| void FrameLoader::scrollToFragmentWithParentBoundary(const URL& url, bool isNewNavigation) | |
| { | |
| RefPtr view = m_frame->view(); | |
| RefPtr document = m_frame->document(); | |
| if (!view || !document) | |
| return; | |
| if (isSameDocumentReload(isNewNavigation, m_loadType) || itemAllowsScrollRestoration(history().currentItem(), m_loadType)) { | |
| // https://html.spec.whatwg.org/multipage/browsing-the-web.html#try-to-scroll-to-the-fragment | |
| if (!document->haveStylesheetsLoaded()) | |
| document->setGotoAnchorNeededAfterStylesheetsLoad(true); | |
| else | |
| view->scrollToFragment(url); | |
| } | |
| } | |
| bool FrameLoader::shouldReplaceHistoryItemInChildFrame() const | |
| { | |
| if (m_frame->isMainFrame()) | |
| return false; | |
| if (m_loadType != FrameLoadType::RedirectWithLockedBackForwardList) | |
| return false; | |
| return m_documentLoader && m_documentLoader->isClientRedirect(); | |
| } | |
| bool FrameLoader::shouldClose() | |
| { | |
| Ref frame = m_frame.get(); | |
| RefPtr page = frame->page(); | |
| if (!page) | |
| return true; | |
| if (!page->chrome().canRunBeforeUnloadConfirmPanel()) | |
| return true; | |
| // Store all references to each subframe in advance since beforeunload's event handler may modify frame | |
| Vector<Ref<Frame>, 16> targetFrames; | |
| targetFrames.append(frame.copyRef()); | |
| for (RefPtr child = frame->tree().firstChild(); child; child = child->tree().traverseNext(frame.ptr())) | |
| targetFrames.append(*child); | |
| bool shouldClose = false; | |
| { | |
| NavigationDisabler navigationDisabler(frame.ptr()); | |
| UnloadCountIncrementer UnloadCountIncrementer(protect(frame->document()).get()); | |
| size_t i; | |
| for (i = 0; i < targetFrames.size(); i++) { | |
| if (!targetFrames[i]->tree().isDescendantOf(frame.ptr())) | |
| continue; | |
| if (RefPtr localTargetFrame = dynamicDowncast<LocalFrame>(targetFrames[i])) { | |
| if (!localTargetFrame->loader().dispatchBeforeUnloadEvent(page->chrome(), this)) | |
| break; | |
| } else if (RefPtr remoteTargetFrame = dynamicDowncast<RemoteFrame>(targetFrames[i])) { | |
| if (RefPtr document = frame->document()) | |
| remoteTargetFrame->client().dispatchCrossOriginBeforeUnloadCheck(document->securityOrigin().data()); | |
| continue; | |
| } | |
| } | |
| if (i == targetFrames.size()) | |
| shouldClose = true; | |
| } | |
| if (!shouldClose) | |
| m_submittedFormURL = URL(); | |
| m_currentNavigationHasShownBeforeUnloadConfirmPanel = false; | |
| return shouldClose; | |
| } | |
| void FrameLoader::dispatchUnloadEvents(UnloadEventPolicy unloadEventPolicy) | |
| { | |
| if (!m_frame->document()) | |
| return; | |
| if (m_pageDismissalEventBeingDispatched != PageDismissalType::None) | |
| return; | |
| // We store the frame's page in a local variable because the frame might get detached inside dispatchEvent. | |
| ForbidPromptsScope forbidPrompts(m_frame->page()); | |
| ForbidSynchronousLoadsScope forbidSynchronousLoads(m_frame->page()); | |
| UnloadCountIncrementer UnloadCountIncrementer(m_frame->document()); | |
| if (m_didCallImplicitClose && !m_wasUnloadEventEmitted) { | |
| if (RefPtr input = dynamicDowncast<HTMLInputElement>(m_frame->document()->focusedElement())) | |
| input->endEditing(); | |
| if (m_pageDismissalEventBeingDispatched == PageDismissalType::None) { | |
| Ref document = *m_frame->document(); | |
| if (unloadEventPolicy == UnloadEventPolicy::UnloadAndPageHide) { | |
| m_pageDismissalEventBeingDispatched = PageDismissalType::PageHide; | |
| document->dispatchPagehideEvent(document->backForwardCacheState() == Document::AboutToEnterBackForwardCache ? PageshowEventPersistence::Persisted : PageshowEventPersistence::NotPersisted); | |
| } | |
| // This takes care of firing the visibilitychange event and making sure the document is reported as hidden. | |
| document->setVisibilityHiddenDueToDismissal(true); | |
| if (document->backForwardCacheState() == Document::NotInBackForwardCache) { | |
| Ref unloadEvent = Event::create(eventNames().unloadEvent, Event::CanBubble::No, Event::IsCancelable::No); | |
| // 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 = m_provisionalDocumentLoader; | |
| auto* timing = documentLoader ? &documentLoader->timing() : nullptr; | |
| m_pageDismissalEventBeingDispatched = PageDismissalType::Unload; | |
| if (timing && !timing->unloadEventStart()) | |
| timing->markUnloadEventStart(); | |
| protect(document->window())->dispatchEvent(unloadEvent, document.ptr()); | |
| if (timing && !timing->unloadEventEnd()) | |
| timing->markUnloadEventEnd(); | |
| } | |
| } | |
| 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()->backForwardCacheState() != Document::NotInBackForwardCache) | |
| return; | |
| // Don't remove event listeners from a transitional empty document (see bug 28716 for more information). | |
| bool shouldKeepEventListeners = m_stateMachine.isDisplayingInitialEmptyDocument() && m_provisionalDocumentLoader | |
| && m_frame->document()->isSecureTransitionTo(Ref { *m_provisionalDocumentLoader }->url()); | |
| if (!shouldKeepEventListeners) | |
| protect(m_frame->document())->removeAllEventListeners(); | |
| } | |
| static bool LEGACY_NODELETE shouldAskForNavigationConfirmation(Document& document, const BeforeUnloadEvent& event) | |
| { | |
| // Confirmation dialog should not be displayed when the allow-modals flag is not set. | |
| if (document.isSandboxed(SandboxFlag::Modals)) | |
| return false; | |
| auto* page = document.page(); | |
| bool userDidInteractWithPage = page && page->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) | |
| { | |
| RefPtr window = m_frame->document()->window(); | |
| if (!window) | |
| return true; | |
| RefPtr document = m_frame->document(); | |
| if (!document->bodyOrFrameset()) | |
| return true; | |
| Ref beforeUnloadEvent = BeforeUnloadEvent::create(); | |
| { | |
| SetForScope change(m_pageDismissalEventBeingDispatched, PageDismissalType::BeforeUnload); | |
| ForbidPromptsScope forbidPrompts(protect(m_frame->page()).get()); | |
| ForbidSynchronousLoadsScope forbidSynchronousLoads(m_frame->page()); | |
| ForbidCopyPasteScope forbidCopyPaste(m_frame->page()); | |
| window->dispatchEvent(beforeUnloadEvent, protect(window->document()).get()); | |
| } | |
| if (!beforeUnloadEvent->defaultPrevented()) | |
| document->defaultEventHandler(beforeUnloadEvent.get()); | |
| 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(MessageSource::JS, MessageLevel::Error, "Blocked attempt to show multiple beforeunload confirmation dialogs for the same navigation."_s); | |
| return true; | |
| } | |
| // We should only display the beforeunload dialog for an iframe if its SecurityOrigin matches all | |
| // ancestor frame SecurityOrigins up through the navigating FrameLoader. | |
| if (frameLoaderBeingNavigated != this) { | |
| RefPtr parentFrame = dynamicDowncast<LocalFrame>(m_frame->tree().parent()); | |
| while (parentFrame) { | |
| RefPtr parentDocument = parentFrame->document(); | |
| if (!parentDocument) | |
| return true; | |
| if (RefPtr document = m_frame->document(); !document || !protect(document->securityOrigin())->isSameOriginDomain(protect(parentDocument->securityOrigin()))) { | |
| document->addConsoleMessage(MessageSource::JS, MessageLevel::Error, "Blocked attempt to show beforeunload confirmation dialog on behalf of a frame with different security origin. Protocols, domains, and ports must match."_s); | |
| return true; | |
| } | |
| if (&parentFrame->loader() == frameLoaderBeingNavigated) | |
| break; | |
| parentFrame = dynamicDowncast<LocalFrame>(parentFrame->tree().parent()); | |
| } | |
| // The navigatingFrameLoader should always be in our ancestory. | |
| ASSERT(parentFrame); | |
| ASSERT(&parentFrame->loader() == frameLoaderBeingNavigated); | |
| } | |
| frameLoaderBeingNavigated->m_currentNavigationHasShownBeforeUnloadConfirmPanel = true; | |
| String text = document->displayStringModifiedByEncoding(beforeUnloadEvent->returnValue()); | |
| return chrome.runBeforeUnloadConfirmPanel(WTF::move(text), protect(m_frame)); | |
| } | |
| void FrameLoader::executeJavaScriptURL(const URL& url, const NavigationAction& action) | |
| { | |
| ASSERT(url.protocolIsJavaScript()); | |
| bool isFirstNavigationInFrame = false; | |
| if (!m_stateMachine.committedFirstRealDocumentLoad()) { | |
| m_stateMachine.advanceTo(FrameLoaderStateMachine::DisplayingInitialEmptyDocumentPostCommit); | |
| isFirstNavigationInFrame = true; | |
| } | |
| RefPtr ownerDocument = m_frame->ownerElement() ? &m_frame->ownerElement()->document() : nullptr; | |
| if (ownerDocument) | |
| ownerDocument->incrementLoadEventDelayCount(); | |
| bool didReplaceDocument = false; | |
| bool requesterSandboxedFromScripts = action.requester() && action.requester()->sandboxFlags.contains(SandboxFlag::Scripts); | |
| if (requesterSandboxedFromScripts) { | |
| // FIXME: This message should be moved off the console once a solution to https://bugs.webkit.org/show_bug.cgi?id=103274 exists. | |
| // This message is identical to the message in ScriptController::canExecuteScripts. | |
| if (RefPtr document = m_frame->document()) | |
| document->addConsoleMessage(MessageSource::Security, MessageLevel::Error, makeString("Blocked script execution in '"_s, action.requester()->url.stringCenterEllipsizedToLength(), "' because the document's frame is sandboxed and the 'allow-scripts' permission is not set."_s)); | |
| } else | |
| protect(m_frame->script())->executeJavaScriptURL(url, action, didReplaceDocument); | |
| // We need to communicate that a load happened, even if the JavaScript URL execution didn't end up replacing the document. | |
| if (RefPtr document = m_frame->document(); isFirstNavigationInFrame && !didReplaceDocument) | |
| document->dispatchWindowLoadEvent(); | |
| checkCompleted(); | |
| if (ownerDocument) | |
| ownerDocument->decrementLoadEventDelayCount(); | |
| m_quickRedirectComing = false; | |
| } | |
| bool FrameLoader::dispatchPendingNavigateEventAfterNavigationPolicy(PendingNavigateEventIdentifier identifier) | |
| { | |
| RefPtr policyDocumentLoader = m_policyDocumentLoader; | |
| if (!policyDocumentLoader) | |
| return true; | |
| auto pendingDispatchNavigateEvent = policyDocumentLoader->triggeringAction().takePendingDispatchNavigateEvent(identifier); | |
| if (!pendingDispatchNavigateEvent) | |
| return true; | |
| return pendingDispatchNavigateEvent().isCompleted(); | |
| } | |
| void FrameLoader::continueLoadAfterNavigationPolicy(const ResourceRequest& request, const FormSubmission* formSubmission, NavigationPolicyDecision navigationPolicyDecision, AllowNavigationToInvalidURL allowNavigationToInvalidURL, ShouldRestoreFromBackForwardCache shouldRestoreFromBackForwardCache) | |
| { | |
| // 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 | |
| // through this method already, nested; otherwise, policyDataSource should still be set. | |
| ASSERT(m_policyDocumentLoader || !m_provisionalDocumentLoader->unreachableURL().isEmpty()); | |
| Ref frame = m_frame.get(); | |
| bool urlIsDisallowed = allowNavigationToInvalidURL == AllowNavigationToInvalidURL::No && !request.url().isValid(); | |
| RefPtr policyDocumentLoader = m_policyDocumentLoader; | |
| bool dispatchedEventsBeforeNavigationPolicy = policyDocumentLoader && policyDocumentLoader->triggeringAction().dispatchedEventsBeforeNavigationPolicy(); | |
| // For Navigation API traversal navigation, dispatch navigate event AFTER beforeunload. | |
| bool navigateEventAborted = false; | |
| bool shouldCloseResult = true; | |
| if (RefPtr pendingItem = std::exchange(m_pendingNavigationAPIItem, nullptr)) { | |
| // Only call shouldClose() early for Navigation API traversals | |
| shouldCloseResult = shouldClose(); | |
| if (shouldCloseResult) { | |
| RefPtr document = frame->document(); | |
| if (RefPtr window = document ? document->window() : nullptr) { | |
| if (Ref navigation = window->navigation(); navigation->frame()) { | |
| if (navigation->dispatchTraversalNavigateEvent(*pendingItem).isAborted()) | |
| navigateEventAborted = true; | |
| } | |
| } | |
| } | |
| } else { | |
| // For non-Navigation API traversals, use original behavior with short-circuit evaluation | |
| shouldCloseResult = (navigationPolicyDecision == NavigationPolicyDecision::ContinueLoad && !urlIsDisallowed) ? (dispatchedEventsBeforeNavigationPolicy || shouldClose()) : false; | |
| } | |
| bool canContinue = navigationPolicyDecision == NavigationPolicyDecision::ContinueLoad && shouldCloseResult && !navigateEventAborted && !urlIsDisallowed; | |
| bool isTargetItem = frame->loader().history().provisionalItem() ? frame->loader().history().provisionalItem()->isTargetItem() : false; | |
| if (!canContinue) { | |
| FRAMELOADER_RELEASE_LOG_FORWARDABLE(FrameLoaderContinueLoadAfterNavigationPolicyCannotContinue, static_cast<int>(allowNavigationToInvalidURL), request.url().isValid(), static_cast<int>(navigationPolicyDecision)); | |
| // 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(NewLoadInProgress::No); | |
| // The cancellation of the cache-only load was not reported to the client, so report it now | |
| // that the retry is not going to continue the navigation. | |
| if (RefPtr policyDocumentLoader = m_policyDocumentLoader; policyDocumentLoader && policyDocumentLoader->isCacheOnlyLoadRetry() && navigationPolicyDecision != NavigationPolicyDecision::LoadWillContinueInAnotherProcess) | |
| dispatchDidFailProvisionalLoad(*policyDocumentLoader, cancelledError(policyDocumentLoader->request()), WillInternallyHandleFailure::No); | |
| if (navigationPolicyDecision == NavigationPolicyDecision::LoadWillContinueInAnotherProcess) { | |
| // The navigation goes on in the other process, so the navigate event it already dispatched here is not aborted. | |
| SetForScope doNotAbortNavigationAPI { m_doNotAbortNavigationAPI, dispatchedEventsBeforeNavigationPolicy }; | |
| stopAllLoaders(); | |
| m_checkTimer.stop(); | |
| m_crossOriginParentSyntheticSameDocLoadEventTimer.stop(); | |
| } else if (auto navigateEventIdentifier = policyDocumentLoader ? policyDocumentLoader->triggeringAction().navigateEventDispatchedBeforeNavigationPolicy() : Markable<NavigateEventIdentifier> { }) { | |
| // Queued because a newer navigation cancels this one's policy check while it starts, and aborts this one itself when it dispatches its own navigate event. | |
| RefPtr document = frame->document(); | |
| if (RefPtr window = document->window()) { | |
| protect(document->eventLoop())->queueTask(TaskSource::DOMManipulation, [navigation = Ref { window->navigation() }, navigateEventIdentifier = *navigateEventIdentifier] { | |
| navigation->abortOngoingNavigationIfStartedBy(navigateEventIdentifier); | |
| }); | |
| } | |
| } | |
| setPolicyDocumentLoader(nullptr, navigationPolicyDecision == NavigationPolicyDecision::LoadWillContinueInAnotherProcess ? LoadWillContinueInAnotherProcess::Yes : LoadWillContinueInAnotherProcess::No); | |
| if (frame->isMainFrame() || navigationPolicyDecision != NavigationPolicyDecision::LoadWillContinueInAnotherProcess) | |
| checkCompleted(); | |
| else { | |
| // Don't call checkCompleted until RemoteFrame::didFinishLoadInAnotherProcess, | |
| // to prevent onload from happening until iframes finish loading in other processes. | |
| ASSERT(frame->settings().siteIsolationEnabled()); | |
| m_provisionalLoadHappeningInAnotherProcess = true; | |
| } | |
| if (navigationPolicyDecision != NavigationPolicyDecision::LoadWillContinueInAnotherProcess) | |
| checkLoadComplete(); | |
| // 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, | |
| // we only do this when punting a navigation for the target frame or top-level frame. | |
| if ((isTargetItem || frame->isMainFrame()) && isBackForwardLoadType(policyChecker().loadType())) { | |
| if (RefPtr page = frame->page(); page && !page->settings().useUIProcessForBackForwardItemLoading()) { | |
| if (RefPtr localMainFrame = frame->localMainFrame()) { | |
| if (RefPtr resetItem = localMainFrame->loader().history().currentItem()) | |
| protect(page->backForward())->setCurrentItem(*resetItem); | |
| } | |
| } | |
| } | |
| return; | |
| } | |
| if (request.url().protocolIsJavaScript()) { | |
| auto action = m_policyDocumentLoader->triggeringAction(); | |
| setPolicyDocumentLoader(nullptr); | |
| executeJavaScriptURL(request.url(), action); | |
| return; | |
| } | |
| if (auto pendingDispatchNavigateEvent = m_policyDocumentLoader ? m_policyDocumentLoader->triggeringAction().takePendingDispatchNavigateEvent() : NavigationAction::DispatchNavigateEventFunction { }) { | |
| if (pendingDispatchNavigateEvent().isNotCompleted()) | |
| return; | |
| } | |
| FrameLoadType type = policyChecker().loadType(); | |
| { | |
| SetForScope<bool> doNotAbortNavigationAPI { m_doNotAbortNavigationAPI, m_policyDocumentLoader && m_policyDocumentLoader->triggeringAction().isFromNavigationAPI() }; | |
| // A new navigation is in progress, so don't clear the history's provisional item. | |
| stopAllLoaders(ClearProvisionalItem::No); | |
| } | |
| // <rdar://problem/6250856> - In certain circumstances on pages with multiple frames, stopAllLoaders() | |
| // might detach the current FrameLoader, in which case we should bail on this newly defunct load. | |
| if (!frame->page()) { | |
| FRAMELOADER_RELEASE_LOG(ResourceLoading, "continueLoadAfterNavigationPolicy: can't continue loading frame because it became defunct"); | |
| return; | |
| } | |
| setProvisionalDocumentLoader(m_policyDocumentLoader.copyRef()); | |
| FRAMELOADER_RELEASE_LOG_FORWARDABLE(FrameLoaderContinueLoadAfterNavigationPolicy, (uint64_t)m_provisionalDocumentLoader.get()); | |
| m_loadType = type; | |
| setState(FrameState::Provisional); | |
| setPolicyDocumentLoader(nullptr); | |
| if (isBackForwardLoadType(type)) { | |
| CheckedRef diagnosticLoggingClient = protect(frame->page())->diagnosticLoggingClient(); | |
| RefPtr provisionalItem = history().provisionalItem(); | |
| bool hasCachedPage = provisionalItem && BackForwardCache::singleton().get(*provisionalItem, protect(frame->page()).get()); | |
| if (hasCachedPage && shouldRestoreFromBackForwardCache != ShouldRestoreFromBackForwardCache::No) { | |
| diagnosticLoggingClient->logDiagnosticMessageWithResult(DiagnosticLoggingKeys::backForwardCacheKey(), DiagnosticLoggingKeys::retrievalKey(), DiagnosticLoggingResultPass, ShouldSample::Yes); | |
| loadProvisionalItemFromCachedPage(); | |
| FRAMELOADER_RELEASE_LOG(ResourceLoading, "continueLoadAfterNavigationPolicy: can't continue loading frame because it will be loaded from cache"); | |
| return; | |
| } | |
| if (hasCachedPage) { | |
| FRAMELOADER_RELEASE_LOG_ERROR(ResourceLoading, "continueLoadAfterNavigationPolicy: evicting orphan cached page (UIProcess said No)"); | |
| BackForwardCache::singleton().remove(*provisionalItem); | |
| } else if (shouldRestoreFromBackForwardCache == ShouldRestoreFromBackForwardCache::Yes) | |
| FRAMELOADER_RELEASE_LOG_ERROR(ResourceLoading, "continueLoadAfterNavigationPolicy: expected to restore from back/forward cache but no cached page"); | |
| diagnosticLoggingClient->logDiagnosticMessageWithResult(DiagnosticLoggingKeys::backForwardCacheKey(), DiagnosticLoggingKeys::retrievalKey(), DiagnosticLoggingResultFail, ShouldSample::Yes); | |
| if (RefPtr provisionalDocumentLoader = m_provisionalDocumentLoader; provisionalItem && provisionalDocumentLoader && provisionalDocumentLoader->isCommitted()) { | |
| FRAMELOADER_RELEASE_LOG_ERROR(ResourceLoading, "continueLoadAfterNavigationPolicy: restarting back/forward load with a fresh document loader"); | |
| Ref item = provisionalItem.releaseNonNull(); | |
| setProvisionalDocumentLoader(nullptr); | |
| loadDifferentDocumentItem(item, protect(history().currentItem()).get(), type, MayAttemptCacheOnlyLoadForFormSubmissionItem, ShouldTreatAsContinuingLoad::No, ShouldRestoreFromBackForwardCache::No, PolicyAlreadyDecided::Yes); | |
| return; | |
| } | |
| } | |
| CompletionHandler<void()> completionHandler = [this, protectedThis = Ref { *this }] () mutable { | |
| if (!m_provisionalDocumentLoader) { | |
| FRAMELOADER_RELEASE_LOG(ResourceLoading, "continueLoadAfterNavigationPolicy (completionHandler): Frame load canceled - no provisional document loader before prepareForLoadStart"); | |
| return; | |
| } | |
| prepareForLoadStart(); | |
| // The load might be cancelled inside of prepareForLoadStart(), nulling out the m_provisionalDocumentLoader, | |
| // so we need to null check it again. | |
| if (!m_provisionalDocumentLoader) { | |
| FRAMELOADER_RELEASE_LOG(ResourceLoading, "continueLoadAfterNavigationPolicy (completionHandler): Frame load canceled - no provisional document loader after prepareForLoadStart"); | |
| return; | |
| } | |
| RefPtr activeDocLoader = activeDocumentLoader(); | |
| if (activeDocLoader && activeDocLoader->isLoadingMainResource()) { | |
| FRAMELOADER_RELEASE_LOG(ResourceLoading, "continueLoadAfterNavigationPolicy (completionHandler): Main frame already being loaded"); | |
| return; | |
| } | |
| m_loadingFromCachedPage = false; | |
| protect(m_provisionalDocumentLoader)->startLoadingMainResource(); | |
| }; | |
| if (!formSubmission) { | |
| completionHandler(); | |
| return; | |
| } | |
| auto requestURL = formSubmission->requestURL(); | |
| String method = normalizeHTTPMethod(FormSubmission::Attributes::methodString(formSubmission->method())); | |
| m_client->dispatchWillSubmitForm(protect(formSubmission->state()).releaseNonNull(), WTF::move(requestURL), WTF::move(method), WTF::move(completionHandler)); | |
| } | |
| void FrameLoader::continueLoadAfterNewWindowPolicy(ResourceRequest&& request, | |
| RefPtr<const FormSubmission>&& formSubmission, const AtomString& frameName, const NavigationAction& action, ShouldContinuePolicyCheck shouldContinue, AllowNavigationToInvalidURL allowNavigationToInvalidURL, NewFrameOpenerPolicy openerPolicy) | |
| { | |
| if (shouldContinue != ShouldContinuePolicyCheck::Yes) | |
| return; | |
| Ref frame = m_frame.get(); | |
| if (request.url().protocolIsJavaScript() && !protect(protect(frame->document())->contentSecurityPolicy())->allowJavaScriptURLs(protect(frame->document())->url().string(), { }, request.url().string(), nullptr)) | |
| return; | |
| auto name = isBlankTargetFrameName(frameName) ? emptyAtom() : frameName; | |
| RefPtr mainFrame = m_client->dispatchCreatePage(action, openerPolicy, name); | |
| if (!mainFrame) | |
| return; | |
| Ref mainFrameLoader = mainFrame->loader(); | |
| if (!isBlankTargetFrameName(frameName)) | |
| mainFrame->tree().setSpecifiedName(frameName); | |
| mainFrame->page()->setOpenedByDOM(); | |
| mainFrameLoader->m_client->dispatchShow(); | |
| if (openerPolicy == NewFrameOpenerPolicy::Allow) { | |
| ASSERT(mainFrame->opener() == frame.ptr()); | |
| mainFrame->page()->setOpenedByDOMWithOpener(true); | |
| mainFrame->document()->setReferrerPolicy(frame->document()->referrerPolicy()); | |
| } | |
| NavigationAction newAction { protect(frame->document()).releaseNonNull(), request, InitiatedByMainFrame::Unknown, action.isRequestFromClientOrUserInput(), NavigationType::Other, action.shouldOpenExternalURLsPolicy(), nullptr, action.downloadAttribute() }; | |
| newAction.setShouldReplaceDocumentIfJavaScriptURL(action.shouldReplaceDocumentIfJavaScriptURL()); | |
| mainFrameLoader->loadWithNavigationAction(WTF::move(request), WTF::move(newAction), FrameLoadType::Standard, WTF::move(formSubmission), allowNavigationToInvalidURL, ShouldTreatAsContinuingLoad::No); | |
| } | |
| ResourceLoaderIdentifier FrameLoader::requestFromDelegate(ResourceRequest& request, ResourceError& error) | |
| { | |
| ASSERT(!request.isNull()); | |
| auto identifier = ResourceLoaderIdentifier::generate(); | |
| RefPtr documentLoader = m_documentLoader; | |
| notifier().assignIdentifierToInitialRequest(identifier, documentLoader.get(), request); | |
| ResourceRequest newRequest(request); | |
| notifier().dispatchWillSendRequest(documentLoader.get(), identifier, newRequest, ResourceResponse(), nullptr); | |
| if (newRequest.isNull()) | |
| error = cancelledError(request); | |
| else | |
| error = ResourceError(); | |
| request = newRequest; | |
| return identifier; | |
| } | |
| void FrameLoader::loadedResourceFromMemoryCache(CachedResource& resource, ResourceRequest& newRequest, ResourceError& error) | |
| { | |
| RefPtr page = m_frame->page(); | |
| if (!page) | |
| return; | |
| RefPtr documentloader = m_documentLoader; | |
| if (!resource.shouldSendResourceLoadCallbacks() || documentloader->haveToldClientAboutLoad(resource.url().string())) | |
| return; | |
| // Main resource delegate messages are synthesized in MainResourceLoader, so we must not send them here. | |
| if (resource.type() == CachedResource::Type::MainResource) | |
| return; | |
| if (!page->areMemoryCacheClientCallsEnabled()) { | |
| InspectorInstrumentation::didLoadResourceFromMemoryCache(*page, documentloader.get(), &resource); | |
| documentloader->recordMemoryCacheLoadForFutureClientNotification(resource.resourceRequest()); | |
| documentloader->didTellClientAboutLoad(resource.url().string()); | |
| page->setHasPendingMemoryCacheLoadNotifications(true); | |
| return; | |
| } | |
| if (m_client->dispatchDidLoadResourceFromMemoryCache(documentloader.get(), newRequest, resource.response(), resource.encodedSize())) { | |
| InspectorInstrumentation::didLoadResourceFromMemoryCache(*page, documentloader.get(), &resource); | |
| documentloader->didTellClientAboutLoad(resource.url().string()); | |
| return; | |
| } | |
| auto identifier = requestFromDelegate(newRequest, error); | |
| ResourceResponse response = resource.response(); | |
| response.setSource(ResourceResponse::Source::MemoryCache); | |
| notifier().sendRemainingDelegateMessages(documentloader.get(), identifier, newRequest, response, nullptr, resource.encodedSize(), 0, error); | |
| } | |
| void FrameLoader::applyUserAgentIfNeeded(ResourceRequest& request) | |
| { | |
| if (!request.hasHTTPHeaderField(HTTPHeaderName::UserAgent)) { | |
| String userAgent = this->userAgent(request.url()); | |
| ASSERT(!userAgent.isNull()); | |
| request.setHTTPUserAgent(userAgent); | |
| } | |
| } | |
| bool FrameLoader::shouldInterruptLoadForXFrameOptions(const String& content, const URL& url, ResourceLoaderIdentifier requestIdentifier) | |
| { | |
| if (m_frame->settings().ignoreIframeEmbeddingProtectionsEnabled()) | |
| return false; | |
| RefPtr topFrame = dynamicDowncast<LocalFrame>(m_frame->tree().top()); | |
| if (m_frame.ptr() == topFrame) | |
| return false; | |
| XFrameOptionsDisposition disposition = parseXFrameOptionsHeader(content); | |
| switch (disposition) { | |
| case XFrameOptionsDisposition::SameOrigin: { | |
| Ref origin = SecurityOrigin::create(url); | |
| if (!topFrame || !origin->isSameSchemeHostPort(protect(protect(topFrame->document())->securityOrigin()))) | |
| return true; | |
| return std::ranges::any_of(ancestorFrames(m_frame.get()), [&](Frame& frame) { | |
| RefPtr localFrame = dynamicDowncast<LocalFrame>(frame); | |
| return !localFrame || !origin->isSameSchemeHostPort(protect(protect(localFrame->document())->securityOrigin())); | |
| }); | |
| } | |
| case XFrameOptionsDisposition::Deny: | |
| return true; | |
| case XFrameOptionsDisposition::AllowAll: | |
| return false; | |
| case XFrameOptionsDisposition::Conflict: | |
| protect(m_frame->document())->addConsoleMessage(MessageSource::JS, MessageLevel::Error, makeString("Multiple 'X-Frame-Options' headers with conflicting values ('"_s, content, "') encountered when loading '"_s, url.stringCenterEllipsizedToLength(), "'. Falling back to 'DENY'."_s), requestIdentifier.toUInt64()); | |
| return true; | |
| case XFrameOptionsDisposition::Invalid: | |
| protect(m_frame->document())->addConsoleMessage(MessageSource::JS, MessageLevel::Error, makeString("Invalid 'X-Frame-Options' header encountered when loading '"_s, url.stringCenterEllipsizedToLength(), "': '"_s, content, "' is not a recognized directive. The header will be ignored."_s), requestIdentifier.toUInt64()); | |
| return false; | |
| case XFrameOptionsDisposition::None: | |
| return false; | |
| } | |
| ASSERT_NOT_REACHED(); | |
| return false; | |
| } | |
| void FrameLoader::dispatchPageswapEvent(DocumentLoader* provisionalLoader, bool fromBackForwardCache, CompletionHandler<void()>&& proceedWithNavigation) | |
| { | |
| RefPtr document = m_frame->document(); | |
| if (!document) { | |
| if (proceedWithNavigation) | |
| proceedWithNavigation(); | |
| return; | |
| } | |
| Ref frame = m_frame.get(); | |
| auto canTriggerCrossDocumentViewTransition = CanTriggerCrossDocumentViewTransition::No; | |
| RefPtr<NavigationActivation> activation; | |
| if (provisionalLoader) { | |
| // A navigation that can't wait would tear down the old document, and its subframes, before the capture completes. | |
| if (proceedWithNavigation) | |
| canTriggerCrossDocumentViewTransition = provisionalLoader->navigationCanTriggerCrossDocumentViewTransition(*document, fromBackForwardCache); | |
| RefPtr window = document->window(); | |
| auto navigationAPIType = provisionalLoader->triggeringAction().navigationAPIType(); | |
| if (window && navigationAPIType && document->settings().navigationAPIEnabled()) { | |
| // FIXME: The NavigationActivation for pageswap should be created after the global | |
| // history update, but before the unload event (which might be delayed). Those steps | |
| // are currently intertwined, so this creates a fake/detached new history entry to | |
| // use for this purpose. | |
| RefPtr<HistoryItem> newItem; | |
| if (RefPtr page = frame->page(); page && *navigationAPIType != NavigationNavigationType::Reload) | |
| newItem = history().createItemWithLoader(page->historyItemClient(), provisionalLoader); | |
| activation = protect(window->navigation())->createForPageswapEvent(newItem.get(), provisionalLoader, fromBackForwardCache); | |
| } | |
| } | |
| SetForScope dispatchingPageSwapEvent(m_isDispatchingPageSwapEvent, true); | |
| document->dispatchPageswapEvent(canTriggerCrossDocumentViewTransition, WTF::move(activation), WTF::move(proceedWithNavigation)); | |
| } | |
| void FrameLoader::waitForOutboundViewTransitionCapture(DocumentLoader& provisionalLoader, bool fromBackForwardCache, CompletionHandler<void()>&& proceedWithNavigation) | |
| { | |
| // Only a navigation that can trigger a transition waits, and dispatches pageswap early to do so. | |
| // Every other one dispatches it when it commits, as it always has. | |
| RefPtr document = m_frame->document(); | |
| if (!document || &provisionalLoader != m_provisionalDocumentLoader || provisionalLoader.hasDispatchedPageswapEvent() | |
| || provisionalLoader.navigationCanTriggerCrossDocumentViewTransition(*document, fromBackForwardCache) == CanTriggerCrossDocumentViewTransition::No) { | |
| proceedWithNavigation(); | |
| return; | |
| } | |
| provisionalLoader.setHasDispatchedPageswapEvent(true); | |
| dispatchPageswapEvent(&provisionalLoader, fromBackForwardCache, WTF::move(proceedWithNavigation)); | |
| } | |
| void FrameLoader::loadProvisionalItemFromCachedPage() | |
| { | |
| RefPtr provisionalLoader = provisionalDocumentLoader(); | |
| LOG_WITH_STREAM(BackForwardCache, stream << "FrameLoader::loadProvisionalItemFromCachedPage Loading provisional DocumentLoader "_s << provisionalDocumentLoader() << " with URL '"_s << provisionalDocumentLoader()->url().stringCenterEllipsizedToLength() << "' from CachedPage"_s); | |
| prepareForLoadStart(); | |
| m_loadingFromCachedPage = true; | |
| // Should have timing data from previous time(s) the page was shown. | |
| ASSERT(provisionalLoader->timing().startTime()); | |
| provisionalLoader->resetTiming(); | |
| provisionalLoader->timing().markStartTime(); | |
| provisionalLoader->setCommitted(true); | |
| waitForOutboundViewTransitionCapture(*provisionalLoader, true, [frame = Ref { m_frame.get() }, provisionalLoader] { | |
| // Another navigation may have started meanwhile. Unless it waits for a transition of its own, | |
| // the current document may stay, so it stops holding its rendering for this one. | |
| if (RefPtr currentLoader = frame->loader().provisionalDocumentLoader(); currentLoader != provisionalLoader.get()) { | |
| RefPtr document = frame->document(); | |
| RefPtr viewTransition = document ? document->activeViewTransition() : nullptr; | |
| if (viewTransition && (!currentLoader || !currentLoader->hasDispatchedPageswapEvent())) | |
| viewTransition->skipTransition(); | |
| return; | |
| } | |
| protect(frame->loader())->commitProvisionalLoad(); | |
| }); | |
| } | |
| bool FrameLoader::shouldTreatURLAsSameAsCurrent(const SecurityOrigin* requesterOrigin, const URL& url) const | |
| { | |
| RefPtr currentHistoryItem = history().currentItem(); | |
| if (!currentHistoryItem) | |
| return false; | |
| if (requesterOrigin && (!m_frame->document() || !requesterOrigin->isSameOriginAs(protect(protect(m_frame->document())->securityOrigin())))) | |
| return false; | |
| return url == currentHistoryItem->url(); | |
| } | |
| bool FrameLoader::shouldTreatURLAsSrcdocDocument(const URL& url) const | |
| { | |
| if (!url.isAboutSrcDoc()) | |
| return false; | |
| RefPtr ownerElement = m_frame->ownerElement(); | |
| if (!ownerElement) | |
| return false; | |
| if (!ownerElement->hasTagName(iframeTag)) | |
| return false; | |
| return ownerElement->hasAttributeWithoutSynchronization(srcdocAttr); | |
| } | |
| RefPtr<Frame> FrameLoader::findFrameForNavigation(const AtomString& name, Document* rawActiveDocument) | |
| { | |
| // FIXME: Eventually all callers should supply the actual activeDocument so we can call canNavigate with the right document. | |
| RefPtr activeDocument = rawActiveDocument; | |
| if (!activeDocument) | |
| activeDocument = m_frame->document(); | |
| if (!activeDocument) | |
| return nullptr; | |
| RefPtr frame = m_frame->tree().findBySpecifiedName(name, protect(activeDocument->frame() ? *activeDocument->frame() : m_frame.get())); | |
| if (activeDocument->canNavigate(frame.get()) != CanNavigateState::Able) | |
| return nullptr; | |
| return frame; | |
| } | |
| NavigateEventDispatchResult FrameLoader::dispatchNavigateEvent(FrameLoadType loadType, const FrameLoadRequest& request, bool isSameDocument, FormState* formState, Event* event, SerializedScriptValue* classicHistoryAPIState) | |
| { | |
| RefPtr document = m_frame->document(); | |
| if (!document || !document->settings().navigationAPIEnabled()) | |
| return NavigateEventDispatchResult::completed(); | |
| RefPtr window = document->window(); | |
| if (!window) | |
| return NavigateEventDispatchResult::completed(); | |
| if (request.skipNavigateEvent()) | |
| return NavigateEventDispatchResult::completed(); | |
| // Download events are handled later in PolicyChecker::checkNavigationPolicy(). | |
| if (!request.downloadAttribute().isNull()) | |
| return NavigateEventDispatchResult::completed(); | |
| const URL& newURL = request.resourceRequest().url(); | |
| if (!isSameDocument && !newURL.hasFetchScheme()) | |
| return NavigateEventDispatchResult::completed(); | |
| auto navigationType = determineNavigationType(loadType, request.navigationHistoryBehavior()); | |
| if (m_policyDocumentLoader && m_policyDocumentLoader->triggeringAction().isFromNavigationAPI()) { | |
| auto& action = m_policyDocumentLoader->triggeringAction(); | |
| auto apiType = action.navigationAPIType(); | |
| // If this is from Navigation API and should be a traverse, dispatch proper traverse event. | |
| if (apiType && *apiType == NavigationNavigationType::Traverse) | |
| return NavigateEventDispatchResult::completed(); | |
| } | |
| // Traversals are handled earlier, in loadItem(). | |
| if (navigationType == NavigationNavigationType::Traverse) | |
| return NavigateEventDispatchResult::completed(); | |
| RefPtr sourceElement = request.sourceElement(); | |
| if (!sourceElement && event) | |
| sourceElement = dynamicDowncast<Element>(event->target()); | |
| return protect(window->navigation())->dispatchPushReplaceReloadNavigateEvent(newURL, navigationType, isSameDocument, formState, classicHistoryAPIState, sourceElement.get()); | |
| } | |
| void FrameLoader::loadSameDocumentItem(HistoryItem& item) | |
| { | |
| ASSERT(item.documentSequenceNumber() == history().currentItem()->documentSequenceNumber()); | |
| Ref frame = m_frame.get(); | |
| Ref history = m_history.get(); | |
| // 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? | |
| history->saveScrollPositionAndViewStateToItem(protect(history->currentItem()).get()); | |
| if (RefPtr view = frame->view()) | |
| view->setLastUserScrollType(std::nullopt); | |
| history->setCurrentItem(item); | |
| // loadInSameDocument() actually changes the URL and notifies load delegates of a "fake" load | |
| loadInSameDocument(item.url(), item.stateObject(), nullptr, false); | |
| // Restore user view state from the current history item here since we don't do a normal load. | |
| if (!scrollingSuppressedByNavigationAPI(protect(frame->document()).get())) | |
| history->restoreScrollPositionAndViewState(); | |
| } | |
| void FrameLoader::clearDeferredTraversal() | |
| { | |
| m_deferredTraversalItem = nullptr; | |
| } | |
| void FrameLoader::resumeDeferredTraversal() | |
| { | |
| RefPtr item = std::exchange(m_deferredTraversalItem, nullptr); | |
| if (!item) | |
| return; | |
| m_loadType = m_deferredTraversalLoadType; | |
| loadSameDocumentItem(*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, HistoryItem* fromItem, FrameLoadType loadType, FormSubmissionCacheLoadPolicy cacheLoadPolicy, ShouldTreatAsContinuingLoad shouldTreatAsContinuingLoad, ShouldRestoreFromBackForwardCache shouldRestoreFromBackForwardCache, PolicyAlreadyDecided policyAlreadyDecided, std::optional<NavigationIdentifier> navigationIDForCacheOnlyLoadRetry) | |
| { | |
| FRAMELOADER_RELEASE_LOG(ResourceLoading, "loadDifferentDocumentItem: frame load started"); | |
| Ref frame = m_frame.get(); | |
| // History items should not be reported to the parent. | |
| m_shouldReportResourceTimingToParentFrame = false; | |
| // Remember this item so we can traverse any child items as child frames load | |
| history().setProvisionalItem(&item); | |
| auto initiatedByMainFrame = InitiatedByMainFrame::Unknown; | |
| SetForScope continuingLoadGuard(m_currentLoadContinuingState, shouldTreatAsContinuingLoad != ShouldTreatAsContinuingLoad::No ? LoadContinuingState::ContinuingWithHistoryItem : LoadContinuingState::NotContinuing); | |
| if (CheckedPtr cachedPage = BackForwardCache::singleton().get(item, protect(frame->page()).get())) { | |
| RefPtr documentLoader = cachedPage->documentLoader(); | |
| m_client->updateCachedDocumentLoader(*documentLoader); | |
| auto action = NavigationAction { protect(frame->document()).releaseNonNull(), documentLoader->request(), initiatedByMainFrame, documentLoader->isRequestFromClientOrUserInput(), loadType, false }; | |
| action.setTargetBackForwardItemIdentifier(item.itemID()); | |
| action.setSourceBackForwardItem(fromItem); | |
| action.setNavigationAPIType(determineNavigationType(loadType, NavigationHistoryBehavior::Auto)); | |
| action.setPolicyAlreadyDecided(policyAlreadyDecided); | |
| documentLoader->setTriggeringAction(WTF::move(action)); | |
| documentLoader->setLastCheckedRequest(ResourceRequest()); | |
| cachedPage = nullptr; // Call to loadWithDocumentLoader() below may destroy the CachedPage. | |
| loadWithDocumentLoader(documentLoader.get(), loadType, { }, AllowNavigationToInvalidURL::Yes, shouldRestoreFromBackForwardCache); | |
| return; | |
| } | |
| URL itemURL = item.url(); | |
| URL itemOriginalURL = item.originalURL(); | |
| URL currentURL; | |
| if (RefPtr loader = documentLoader()) | |
| currentURL = loader->url(); | |
| RefPtr formData = item.formData(); | |
| ResourceRequest request(WTF::move(itemURL)); | |
| if (!item.referrer().isNull()) | |
| request.setHTTPReferrer(item.referrer()); | |
| ShouldOpenExternalURLsPolicy shouldOpenExternalURLsPolicy = shouldOpenExternalURLsPolicyToApply(frame, initiatedByMainFrame, item.shouldOpenExternalURLsPolicy()); | |
| bool isFormSubmission = false; | |
| if (RefPtr localFrame = dynamicDowncast<LocalFrame>(frame->mainFrame())) { | |
| if (RefPtr documentLoader = localFrame->loader().documentLoader()) | |
| request.setIsAppInitiated(documentLoader->lastNavigationWasAppInitiated()); | |
| } | |
| // If this was a repost that failed the page cache, we might try to repost the form. | |
| NavigationAction action; | |
| if (formData) { | |
| request.setHTTPMethod("POST"_s); | |
| request.setHTTPBody(WTF::move(formData)); | |
| request.setHTTPContentType(item.formContentType()); | |
| auto securityOrigin = SecurityOrigin::createFromString(item.referrer()); | |
| addHTTPOriginIfNeeded(request, securityOrigin->toString()); | |
| updateRequestAndAddExtraFields(request, IsMainResource::Yes, loadType); | |
| // FIXME: Slight hack to test if the NSURL cache contains the page we're going to. | |
| // We want to know this before talking to the policy delegate, since it affects whether | |
| // we show the DoYouReallyWantToRepost nag. | |
| // | |
| // This trick has a small bug (3123893) where we might find a cache hit, but then | |
| // have the item vanish when we try to use it in the ensuing nav. This should be | |
| // extremely rare, but in that case the user will get an error on the navigation. | |
| if (cacheLoadPolicy == MayAttemptCacheOnlyLoadForFormSubmissionItem) { | |
| request.setCachePolicy(ResourceRequestCachePolicy::ReturnCacheDataDontLoad); | |
| action = { protect(frame->document()).releaseNonNull(), request, initiatedByMainFrame, false, loadType, isFormSubmission, nullptr, shouldOpenExternalURLsPolicy }; | |
| } else { | |
| request.setCachePolicy(ResourceRequestCachePolicy::ReturnCacheDataElseLoad); | |
| action = { protect(frame->document()).releaseNonNull(), request, initiatedByMainFrame, false, NavigationType::FormResubmitted, shouldOpenExternalURLsPolicy, nullptr }; | |
| } | |
| } else { | |
| switch (loadType) { | |
| case FrameLoadType::Reload: | |
| case FrameLoadType::ReloadFromOrigin: | |
| case FrameLoadType::ReloadExpiredOnly: | |
| request.setCachePolicy(ResourceRequestCachePolicy::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(ResourceRequestCachePolicy::ReturnCacheDataElseLoad); | |
| item.setWasRestoredFromSession(false); | |
| break; | |
| } | |
| case FrameLoadType::Standard: | |
| case FrameLoadType::RedirectWithLockedBackForwardList: | |
| case FrameLoadType::NavigationAPIReplace: | |
| break; | |
| case FrameLoadType::Same: | |
| case FrameLoadType::MultipartReplace: | |
| ASSERT_NOT_REACHED(); | |
| } | |
| updateRequestAndAddExtraFields(request, IsMainResource::Yes, loadType); | |
| ResourceRequest requestForOriginalURL(request); | |
| requestForOriginalURL.setURL(WTF::move(itemOriginalURL)); | |
| action = { protect(frame->document()).releaseNonNull(), requestForOriginalURL, initiatedByMainFrame, request.isAppInitiated(), loadType, isFormSubmission, nullptr, shouldOpenExternalURLsPolicy }; | |
| } | |
| action.setTargetBackForwardItemIdentifier(item.itemID()); | |
| action.setSourceBackForwardItem(fromItem); | |
| action.setNavigationAPIType(determineNavigationType(loadType, NavigationHistoryBehavior::Auto)); | |
| action.setPolicyAlreadyDecided(policyAlreadyDecided); | |
| loadWithNavigationAction(WTF::move(request), WTF::move(action), loadType, { }, AllowNavigationToInvalidURL::Yes, shouldTreatAsContinuingLoad, shouldRestoreFromBackForwardCache, navigationIDForCacheOnlyLoadRetry); | |
| } | |
| bool FrameLoader::shouldDispatchNavigateEventForHistoryTraversal(const HistoryItem& item, const HistoryItem* fromItem) | |
| { | |
| // Only dispatch navigate event if this history item belongs to the current frame | |
| // and the navigation is same-origin. This prevents: | |
| // 1. Joint session history bug where main frame incorrectly handles iframe history | |
| // 2. Cross-origin navigations from firing navigate events | |
| // Related spec: https://html.spec.whatwg.org/multipage/nav-history-apis.html#has-entries-and-events-disabled | |
| return fromItem && item.frameID() == frame().frameID() && SecurityOrigin::create(item.url())->isSameOriginAs(SecurityOrigin::create(fromItem->url())); | |
| } | |
| // Loads content into this frame, as specified by history item | |
| void FrameLoader::loadItem(HistoryItem& item, HistoryItem* fromItem, FrameLoadType loadType, ShouldTreatAsContinuingLoad shouldTreatAsContinuingLoad, ShouldRestoreFromBackForwardCache shouldRestoreFromBackForwardCache) | |
| { | |
| m_requestedHistoryItem = item; | |
| RefPtr currentItem = history().currentItem(); | |
| // If we're continuing this history navigation in a new process, we cannot perform a same document navigation. | |
| // Otherwise, the load won't commit and navigation won't complete. | |
| bool sameDocumentNavigation = currentItem && item.shouldDoSameDocumentNavigationTo(*currentItem) && shouldTreatAsContinuingLoad == ShouldTreatAsContinuingLoad::No; | |
| // Check if Navigation API should handle this traversal | |
| if (frame().document() && frame().document()->settings().navigationAPIEnabled() && shouldDispatchNavigateEventForHistoryTraversal(item, fromItem)) { | |
| if (sameDocumentNavigation) { | |
| // For same-document navigation, dispatch navigate event immediately. | |
| // https://html.spec.whatwg.org/multipage/nav-history-apis.html#fire-a-traverse-navigate-event | |
| if (RefPtr window = frame().document()->window()) { | |
| if (RefPtr navigation = window->navigation(); navigation->frame()) { | |
| auto dispatchResult = navigation->dispatchTraversalNavigateEvent(item); | |
| if (dispatchResult.isAborted()) | |
| return; | |
| if (dispatchResult.isDeferredCommit()) { | |
| // A precommit handler is still pending, so the traverse history step must | |
| // not be applied yet. Navigation resumes it once the precommit handler | |
| // promises fulfill, and discards it if the navigation is aborted before. | |
| m_deferredTraversalItem = &item; | |
| m_deferredTraversalLoadType = loadType; | |
| return; | |
| } | |
| // In case the event detached the frame. | |
| if (!navigation->frame()) | |
| return; | |
| } | |
| } | |
| } else { | |
| // For cross-document navigation, save the item for later dispatch. | |
| // Navigate event will be dispatched after beforeunload. | |
| m_pendingNavigationAPIItem = &item; | |
| } | |
| } | |
| if (sameDocumentNavigation) { | |
| m_loadType = loadType; | |
| loadSameDocumentItem(item); | |
| } else | |
| loadDifferentDocumentItem(item, fromItem, loadType, MayAttemptCacheOnlyLoadForFormSubmissionItem, shouldTreatAsContinuingLoad, shouldRestoreFromBackForwardCache); | |
| } | |
| void FrameLoader::setRequestedHistoryItem(HistoryItem& item) | |
| { | |
| Ref frame = m_frame.get(); | |
| ASSERT(!frame->page() || !frame->page()->settings().useUIProcessForBackForwardItemLoading() || item.children().isEmpty()); | |
| item.setFrameID(frame->frameID()); | |
| m_requestedHistoryItem = item; | |
| // When UseUIProcessForBackForwardItemLoading is enabled, each child frame receives | |
| // its HistoryItem individually from UIProcess. Add it to the parent's current | |
| // HistoryItem so the tree structure matches what createItemTree would have produced | |
| // during a normal navigation. | |
| if (RefPtr parentFrame = dynamicDowncast<LocalFrame>(frame->tree().parent())) { | |
| if (RefPtr parentItem = parentFrame->loader().history().currentItem()) | |
| parentItem->setChildItem(Ref { item }); | |
| } | |
| } | |
| void FrameLoader::clearWaitingForDelegatedBackForwardLoad() | |
| { | |
| if (!m_isWaitingForDelegatedBackForwardLoad) | |
| return; | |
| m_isWaitingForDelegatedBackForwardLoad = false; | |
| Ref frame = m_frame.get(); | |
| if (RefPtr parentFrame = dynamicDowncast<LocalFrame>(frame->tree().parent())) | |
| parentFrame->loader().checkCompleted(); | |
| } | |
| void FrameLoader::loadRequestedHistoryItem(FrameLoadType loadType, PolicyAlreadyDecided policyAlreadyDecided) | |
| { | |
| ASSERT(m_requestedHistoryItem); | |
| loadDifferentDocumentItem(protect(*m_requestedHistoryItem), nullptr, loadType, MayAttemptCacheOnlyLoadForFormSubmissionItem, ShouldTreatAsContinuingLoad::No, ShouldRestoreFromBackForwardCache::No, policyAlreadyDecided); | |
| } | |
| void FrameLoader::retryAfterFailedCacheOnlyMainResourceLoad() | |
| { | |
| ASSERT(m_state == FrameState::Provisional); | |
| ASSERT(!m_loadingFromCachedPage); | |
| ASSERT(history().provisionalItem()); | |
| ASSERT(history().provisionalItem()->formData()); | |
| ASSERT(history().provisionalItem() == m_requestedHistoryItem); | |
| FrameLoadType loadType = m_loadType; | |
| RefPtr item = history().provisionalItem(); | |
| if (!item) { | |
| ASSERT_NOT_REACHED(); | |
| FRAMELOADER_RELEASE_LOG_ERROR(ResourceLoading, "retryAfterFailedCacheOnlyMainResourceLoad: Retrying load after failed cache-only main resource load failed because there is no provisional history item."); | |
| stopAllLoaders(ClearProvisionalItem::No); | |
| return; | |
| } | |
| // The retry continues the same navigation, so the client should not see the cancellation | |
| // of the cache-only load as a provisional load failure. | |
| RefPtr provisionalDocumentLoader = m_provisionalDocumentLoader; | |
| auto navigationID = provisionalDocumentLoader ? provisionalDocumentLoader->takeNavigationID() : std::nullopt; | |
| { | |
| SetForScope retryGuard(m_isStoppingForCacheOnlyLoadRetry, true); | |
| stopAllLoaders(ClearProvisionalItem::No); | |
| } | |
| loadDifferentDocumentItem(*item, protect(history().currentItem()).get(), loadType, MayNotAttemptCacheOnlyLoadForFormSubmissionItem, ShouldTreatAsContinuingLoad::No, ShouldRestoreFromBackForwardCache::Unspecified, PolicyAlreadyDecided::No, navigationID); | |
| } | |
| ResourceError FrameLoader::cancelledError(const ResourceRequest& request) | |
| { | |
| ResourceError error = platformStrategies()->loaderStrategy()->cancelledError(request); | |
| error.setType(ResourceError::Type::Cancellation); | |
| return error; | |
| } | |
| ResourceError FrameLoader::blockedByContentBlockerError(const ResourceRequest& request) | |
| { | |
| return platformStrategies()->loaderStrategy()->blockedByContentBlockerError(request); | |
| } | |
| ResourceError FrameLoader::blockedError(const ResourceRequest& request) | |
| { | |
| ResourceError error = platformStrategies()->loaderStrategy()->blockedError(request); | |
| error.setType(ResourceError::Type::Cancellation); | |
| return error; | |
| } | |
| #if ENABLE(CONTENT_FILTERING) | |
| ResourceError FrameLoader::blockedByContentFilterError(const ResourceRequest& request) | |
| { | |
| ResourceError error = platformStrategies()->loaderStrategy()->blockedByContentFilterError(request); | |
| error.setType(ResourceError::Type::General); | |
| return error; | |
| } | |
| #endif | |
| #if PLATFORM(IOS_FAMILY) | |
| RetainPtr<CFDictionaryRef> FrameLoader::connectionProperties(ResourceLoader* loader) | |
| { | |
| return m_client->connectionProperties(protect(loader->documentLoader()), *loader->identifier()); | |
| } | |
| #endif | |
| String FrameLoader::referrer() const | |
| { | |
| return m_documentLoader ? m_documentLoader->request().httpReferrer() : emptyString(); | |
| } | |
| void FrameLoader::dispatchDidClearWindowObjectsInAllWorlds() | |
| { | |
| if (!protect(m_frame->script())->canExecuteScripts(ReasonForCallingCanExecuteScripts::NotAboutToExecuteScript)) | |
| return; | |
| Vector<Ref<DOMWrapperWorld>> worlds; | |
| ScriptController::getAllWorlds(worlds); | |
| for (auto& world : worlds) | |
| dispatchDidClearWindowObjectInWorld(world); | |
| } | |
| void FrameLoader::dispatchDidClearWindowObjectInWorld(DOMWrapperWorld& world) | |
| { | |
| Ref frame = m_frame.get(); | |
| if (!protect(frame->script())->canExecuteScripts(ReasonForCallingCanExecuteScripts::NotAboutToExecuteScript) || !protect(frame->windowProxy())->existingJSWindowProxy(world)) | |
| return; | |
| m_client->dispatchDidClearWindowObjectInWorld(world); | |
| if (RefPtr page = frame->page()) | |
| page->inspectorController().didClearWindowObjectInWorld(frame, world); | |
| InspectorInstrumentation::didClearWindowObjectInWorld(frame, world); | |
| } | |
| void FrameLoader::dispatchGlobalObjectAvailableInAllWorlds() | |
| { | |
| Vector<Ref<DOMWrapperWorld>> worlds; | |
| ScriptController::getAllWorlds(worlds); | |
| for (auto& world : worlds) | |
| m_client->dispatchGlobalObjectAvailable(world); | |
| } | |
| void FrameLoader::didChangeTitle(DocumentLoader* loader) | |
| { | |
| m_client->didChangeTitle(loader); | |
| if (loader == m_documentLoader) { | |
| // Must update the entries in the back-forward list too. | |
| history().setCurrentItemTitle(loader->title()); | |
| // This must go through the WebFrame because it has the right notion of the current b/f item. | |
| m_client->setTitle(loader->title(), loader->urlForHistory()); | |
| m_client->setMainFrameDocumentReady(true); // update observers with new DOMDocument | |
| m_client->dispatchDidReceiveTitle(loader->title()); | |
| } | |
| } | |
| void FrameLoader::dispatchDidCommitLoad(const std::optional<BackForwardCacheCommitData>& backForwardCacheData) | |
| { | |
| if (m_stateMachine.creatingInitialEmptyDocument()) | |
| return; | |
| m_client->dispatchDidCommitLoad(backForwardCacheData); | |
| if (RefPtr page = m_frame->page(); page && m_frame->isMainFrame()) | |
| page->didCommitLoad(); | |
| InspectorInstrumentation::didCommitLoad(protect(m_frame), protect(documentLoader())); | |
| } | |
| void FrameLoader::tellClientAboutPastMemoryCacheLoads() | |
| { | |
| RefPtr page = m_frame->page(); | |
| ASSERT(page); | |
| ASSERT(page->areMemoryCacheClientCallsEnabled()); | |
| if (!m_documentLoader) | |
| return; | |
| RefPtr documentLoader = m_documentLoader; | |
| Vector<ResourceRequest> pastLoads; | |
| documentLoader->takeMemoryCacheLoadsForClientNotification(pastLoads); | |
| for (auto& pastLoad : pastLoads) { | |
| RefPtr resource = MemoryCache::singleton().resourceForRequest(pastLoad, 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. | |
| // Consider if there's some efficient way of remembering enough to deliver this client call. | |
| // We have the URL, but not the rest of the response or the length. | |
| if (!resource) | |
| continue; | |
| ResourceRequest request(URL { resource->url() }); | |
| m_client->dispatchDidLoadResourceFromMemoryCache(documentLoader.get(), request, resource->response(), resource->encodedSize()); | |
| } | |
| } | |
| NetworkingContext* FrameLoader::networkingContext() const | |
| { | |
| return m_networkingContext; | |
| } | |
| void FrameLoader::loadProgressingStatusChanged() | |
| { | |
| if (RefPtr localFrame = dynamicDowncast<LocalFrame>(m_frame->mainFrame())) { | |
| if (RefPtr view = localFrame->view()) | |
| view->loadProgressingStatusChanged(); | |
| } | |
| } | |
| bool FrameLoader::shouldNavigateWithHTTP(bool isSameSiteNavigation) const | |
| { | |
| if (isNavigationUpgradeToHTTPSDisabled()) | |
| return true; | |
| if (shouldSkipHTTPSUpgradeForSameSiteNavigation() && isSameSiteNavigation) | |
| return true; | |
| return false; | |
| } | |
| void FrameLoader::completePageTransitionIfNeeded() | |
| { | |
| m_client->completePageTransitionIfNeeded(); | |
| } | |
| void FrameLoader::setDocumentVisualUpdatesAllowed(bool allowed) | |
| { | |
| m_client->setDocumentVisualUpdatesAllowed(allowed); | |
| } | |
| void FrameLoader::clearTestingOverrides() | |
| { | |
| m_overrideCachePolicyForTesting = std::nullopt; | |
| m_overrideResourceLoadPriorityForTesting = std::nullopt; | |
| m_isStrictRawResourceValidationPolicyDisabledForTesting = false; | |
| } | |
| bool LEGACY_NODELETE LocalFrameLoaderClient::hasHTMLView() const | |
| { | |
| return true; | |
| } | |
| std::pair<RefPtr<Frame>, CreatedNewPage> createWindow(LocalFrame& openerFrame, FrameLoadRequest&& request, WindowFeatures&& features) | |
| { | |
| ASSERT(!features.dialog || request.frameName().isEmpty()); | |
| ASSERT(request.resourceRequest().httpMethod() == "GET"_s); | |
| // FIXME: Provide line number information with respect to the opener's document. | |
| if (request.resourceRequest().url().protocolIsJavaScript() && !protect(protect(openerFrame.document())->contentSecurityPolicy())->allowJavaScriptURLs(protect(openerFrame.document())->url().string(), { }, request.resourceRequest().url().string(), nullptr)) | |
| return { nullptr, CreatedNewPage::No }; | |
| if (!request.frameName().isEmpty() && !isBlankTargetFrameName(request.frameName())) { | |
| if (RefPtr frame = openerFrame.loader().findFrameForNavigation(request.frameName(), protect(openerFrame.document()).get())) { | |
| if (!isSelfTargetFrameName(request.frameName())) { | |
| RefPtr openerWindow = openerFrame.window(); | |
| if (RefPtr page = frame->page(); page && isInVisibleAndActivePage(openerFrame) && openerWindow && openerWindow->hasTransientActivation()) | |
| page->chrome().focus(); | |
| } | |
| if (!features.wantsNoOpener()) | |
| frame->updateOpener(openerFrame); | |
| return { frame, CreatedNewPage::No }; | |
| } | |
| } | |
| // Sandboxed frames cannot open new auxiliary browsing contexts. | |
| if (isDocumentSandboxed(openerFrame, SandboxFlag::Popups)) { | |
| // FIXME: This message should be moved off the console once a solution to https://bugs.webkit.org/show_bug.cgi?id=103274 exists. | |
| protect(openerFrame.document())->addConsoleMessage(MessageSource::Security, MessageLevel::Error, makeString("Blocked opening '"_s, request.resourceRequest().url().stringCenterEllipsizedToLength(), "' in a new window because the request was made in a sandboxed frame whose 'allow-popups' permission is not set."_s)); | |
| return { nullptr, CreatedNewPage::No }; | |
| } | |
| // FIXME: Setting the referrer should be the caller's responsibility. | |
| String referrer = features.wantsNoReferrer() ? String() : SecurityPolicy::generateReferrerHeader(openerFrame.document()->referrerPolicy(), request.resourceRequest().url(), openerFrame.loader().outgoingReferrerURL(), OriginAccessPatternsForWebProcess::singleton()); | |
| if (!referrer.isEmpty()) | |
| request.resourceRequest().setHTTPReferrer(referrer); | |
| FrameLoader::addSameSiteInfoToRequestIfNeeded(request.resourceRequest(), protect(openerFrame.document()).get()); | |
| RefPtr oldPage = openerFrame.page(); | |
| if (!oldPage) | |
| return { nullptr, CreatedNewPage::No }; | |
| #if PLATFORM(GTK) | |
| features.oldWindowRect = oldPage->chrome().windowRect(); | |
| #endif | |
| String openedMainFrameName = isBlankTargetFrameName(request.frameName()) ? String() : request.frameName(); | |
| ShouldOpenExternalURLsPolicy shouldOpenExternalURLsPolicy = shouldOpenExternalURLsPolicyToApply(openerFrame, request); | |
| NavigationAction action { request, NavigationType::Other }; | |
| action.setShouldOpenExternalURLsPolicy(shouldOpenExternalURLsPolicy); | |
| action.setNewFrameOpenerPolicy(features.wantsNoOpener() ? NewFrameOpenerPolicy::Suppress : NewFrameOpenerPolicy::Allow); | |
| RefPtr page = oldPage->chrome().createWindow(openerFrame, openedMainFrameName, features, action); | |
| if (!page) | |
| return { nullptr, CreatedNewPage::No }; | |
| Ref frame = page->mainFrame(); | |
| if (!frame->page()) | |
| return { nullptr, CreatedNewPage::No }; | |
| return { WTF::move(frame), CreatedNewPage::Yes }; | |
| } | |
| // At the moment, we do not actually create a new browsing context / frame. We merely make it so that existing windowProxy for the | |
| // current browsing context lose their browsing context. We also clear properties of the frame (opener, openees, name), so that it | |
| // appears the same as a new browsing context. | |
| void FrameLoader::switchBrowsingContextsGroup() | |
| { | |
| // Disown opener. | |
| Ref frame = m_frame.get(); | |
| frame->disownOpener(); | |
| if (auto* page = m_frame->page()) | |
| page->setOpenedByDOMWithOpener(false); | |
| frame->detachFromAllOpenedFrames(); | |
| frame->tree().clearName(); | |
| // Make sure we use fresh Window proxies. The old window proxies will keep pointing to the old window which will be frameless when | |
| // a new window is created for this frame. | |
| frame->resetScript(); | |
| // On same-origin navigation from the initial empty document, we normally reuse the window for the new document. We need to prevent | |
| // this when we want to isolate so old window proxies will indeed start pointing to a frameless window and appear closed. | |
| if (auto* window = frame->window()) | |
| window->setMayReuseForNavigation(false); | |
| } | |
| bool FrameLoader::shouldSuppressTextInputFromEditing() const | |
| { | |
| return m_frame->settings().shouldSuppressTextInputFromEditingDuringProvisionalNavigation() && m_state == FrameState::Provisional; | |
| } | |
| void FrameLoader::advanceStatePastInitialEmptyDocument() | |
| { | |
| if (stateMachine().committingFirstRealLoad()) | |
| stateMachine().advanceTo(FrameLoaderStateMachine::DisplayingInitialEmptyDocumentPostCommit); | |
| if (stateMachine().isDisplayingInitialEmptyDocument() && stateMachine().committedFirstRealDocumentLoad()) | |
| stateMachine().advanceTo(FrameLoaderStateMachine::CommittedFirstRealLoad); | |
| } | |
| RefPtr<DocumentLoader> FrameLoader::loaderForWebsitePolicies(CanIncludeCurrentDocumentLoader canIncludeCurrentDocumentLoader) const | |
| { | |
| auto* loader = policyDocumentLoader(); | |
| if (!loader) | |
| loader = provisionalDocumentLoader(); | |
| if (!loader && canIncludeCurrentDocumentLoader == CanIncludeCurrentDocumentLoader::Yes) | |
| loader = documentLoader(); | |
| return loader; | |
| } | |
| void FrameLoader::prefetchDNSIfNeeded(const URL& url) | |
| { | |
| #if ENABLE(CONTENT_EXTENSIONS) | |
| Ref frame { m_frame.get() }; | |
| RefPtr page = frame->page(); | |
| if (!page) | |
| return; | |
| RefPtr documentLoader = m_documentLoader; | |
| if (!documentLoader) | |
| return; | |
| RefPtr userContentProvider = frame->userContentProvider(); | |
| if (!userContentProvider) | |
| return; | |
| auto results = userContentProvider->processContentRuleListsForLoad(*page, url, ContentExtensions::ResourceType::Ping, *documentLoader); | |
| if (results.shouldBlock()) | |
| return; | |
| #endif | |
| if (url.isValid() && !url.isEmpty() && url.protocolIsInHTTPFamily()) | |
| client().prefetchDNS(url.host().toString()); | |
| } | |
| void FrameLoader::prefetch(const URL& url, const Vector<String>& tags, std::optional<ReferrerPolicy> referrerPolicy, bool lowPriority) | |
| { | |
| m_documentPrefetcher->prefetch(url, tags, referrerPolicy, lowPriority); | |
| } | |
| } // namespace WebCore | |
| #undef PAGE_ID | |
| #undef FRAME_ID |