/* * Copyright (C) 2006-2017 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) 2009 Adam Barth. 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 "NavigationScheduler.h" #include "BackForwardController.h" #include "CommonVM.h" #include "DOMWindow.h" #include "DocumentLoader.h" #include "Event.h" #include "FormState.h" #include "FormSubmission.h" #include "Frame.h" #include "FrameLoadRequest.h" #include "FrameLoader.h" #include "FrameLoaderStateMachine.h" #include "HTMLFormElement.h" #include "HTMLFrameOwnerElement.h" #include "HistoryItem.h" #include "InspectorInstrumentation.h" #include "Logging.h" #include "NavigationDisabler.h" #include "Page.h" #include "PolicyChecker.h" #include "ScriptController.h" #include "UserGestureIndicator.h" #include namespace WebCore { unsigned NavigationDisabler::s_globalNavigationDisableCount = 0; class ScheduledNavigation { WTF_MAKE_NONCOPYABLE(ScheduledNavigation); WTF_MAKE_FAST_ALLOCATED; public: ScheduledNavigation(double delay, LockHistory lockHistory, LockBackForwardList lockBackForwardList, bool wasDuringLoad, bool isLocationChange) : m_delay(delay) , m_lockHistory(lockHistory) , m_lockBackForwardList(lockBackForwardList) , m_wasDuringLoad(wasDuringLoad) , m_isLocationChange(isLocationChange) , m_userGestureToForward(UserGestureIndicator::currentUserGesture()) { } ScheduledNavigation(double delay, LockHistory lockHistory, LockBackForwardList lockBackForwardList, bool wasDuringLoad, bool isLocationChange, ShouldOpenExternalURLsPolicy externalURLPolicy) : m_delay(delay) , m_lockHistory(lockHistory) , m_lockBackForwardList(lockBackForwardList) , m_wasDuringLoad(wasDuringLoad) , m_isLocationChange(isLocationChange) , m_userGestureToForward(UserGestureIndicator::currentUserGesture()) , m_shouldOpenExternalURLsPolicy(externalURLPolicy) { if (auto* frame = lexicalFrameFromCommonVM()) { if (frame->isMainFrame()) m_initiatedByMainFrame = InitiatedByMainFrame::Yes; } } virtual ~ScheduledNavigation() = default; virtual void fire(Frame&) = 0; virtual bool shouldStartTimer(Frame&) { return true; } virtual void didStartTimer(Frame&, Timer&) { } virtual void didStopTimer(Frame&, NewLoadInProgress) { } double delay() const { return m_delay; } LockHistory lockHistory() const { return m_lockHistory; } LockBackForwardList lockBackForwardList() const { return m_lockBackForwardList; } bool wasDuringLoad() const { return m_wasDuringLoad; } bool isLocationChange() const { return m_isLocationChange; } UserGestureToken* userGestureToForward() const { return m_userGestureToForward.get(); } protected: void clearUserGesture() { m_userGestureToForward = nullptr; } ShouldOpenExternalURLsPolicy shouldOpenExternalURLs() const { return m_shouldOpenExternalURLsPolicy; } InitiatedByMainFrame initiatedByMainFrame() const { return m_initiatedByMainFrame; }; private: double m_delay; LockHistory m_lockHistory; LockBackForwardList m_lockBackForwardList; bool m_wasDuringLoad; bool m_isLocationChange; RefPtr m_userGestureToForward; ShouldOpenExternalURLsPolicy m_shouldOpenExternalURLsPolicy { ShouldOpenExternalURLsPolicy::ShouldNotAllow }; InitiatedByMainFrame m_initiatedByMainFrame { InitiatedByMainFrame::Unknown }; }; class ScheduledURLNavigation : public ScheduledNavigation { protected: ScheduledURLNavigation(Document& initiatingDocument, double delay, SecurityOrigin* securityOrigin, const URL& url, const String& referrer, LockHistory lockHistory, LockBackForwardList lockBackForwardList, bool duringLoad, bool isLocationChange) : ScheduledNavigation(delay, lockHistory, lockBackForwardList, duringLoad, isLocationChange, initiatingDocument.shouldOpenExternalURLsPolicyToPropagate()) , m_initiatingDocument { makeRef(initiatingDocument) } , m_securityOrigin{ makeRefPtr(securityOrigin) } , m_url { url } , m_referrer { referrer } { } void didStartTimer(Frame& frame, Timer& timer) override { if (m_haveToldClient) return; m_haveToldClient = true; UserGestureIndicator gestureIndicator(userGestureToForward()); Ref protectedFrame(frame); frame.loader().clientRedirected(URL(m_url), delay(), WallTime::now() + timer.nextFireInterval(), lockBackForwardList()); } void didStopTimer(Frame& frame, NewLoadInProgress newLoadInProgress) override { if (!m_haveToldClient) return; // Do not set a UserGestureIndicator because // clientRedirectCancelledOrFinished() is also called from many places // inside FrameLoader, where the gesture state is not set and is in // fact unavailable. We need to be consistent with them, otherwise the // gesture state will sometimes be set and sometimes not within // dispatchDidCancelClientRedirect(). frame.loader().clientRedirectCancelledOrFinished(newLoadInProgress); } Document& initiatingDocument() { return m_initiatingDocument.get(); } SecurityOrigin* securityOrigin() const { return m_securityOrigin.get(); } const URL& url() const { return m_url; } String referrer() const { return m_referrer; } private: Ref m_initiatingDocument; RefPtr m_securityOrigin; URL m_url; String m_referrer; bool m_haveToldClient { false }; }; class ScheduledRedirect : public ScheduledURLNavigation { public: ScheduledRedirect(Document& initiatingDocument, double delay, SecurityOrigin* securityOrigin, const URL& url, LockHistory lockHistory, LockBackForwardList lockBackForwardList, IsMetaRefresh isMetaRefresh) : ScheduledURLNavigation(initiatingDocument, delay, securityOrigin, url, String(), lockHistory, lockBackForwardList, false, false) , m_isMetaRefresh(isMetaRefresh) { clearUserGesture(); } bool shouldStartTimer(Frame& frame) override { return frame.loader().allAncestorsAreComplete(); } void fire(Frame& frame) override { if (m_isMetaRefresh == IsMetaRefresh::Yes) { if (auto document = frame.document(); document && document->isSandboxed(SandboxAutomaticFeatures)) { document->addConsoleMessage(MessageSource::Security, MessageLevel::Error, "Unable to do meta refresh due to sandboxing"_s); return; } } UserGestureIndicator gestureIndicator { userGestureToForward() }; bool refresh = equalIgnoringFragmentIdentifier(frame.document()->url(), url()); ResourceRequest resourceRequest { url(), referrer(), refresh ? ResourceRequestCachePolicy::ReloadIgnoringCacheData : ResourceRequestCachePolicy::UseProtocolCachePolicy }; if (initiatedByMainFrame() == InitiatedByMainFrame::Yes) resourceRequest.setRequester(ResourceRequest::Requester::Main); FrameLoadRequest frameLoadRequest { initiatingDocument(), *securityOrigin(), WTFMove(resourceRequest), "_self", initiatedByMainFrame() }; frameLoadRequest.setLockHistory(lockHistory()); frameLoadRequest.setLockBackForwardList(lockBackForwardList()); frameLoadRequest.disableNavigationToInvalidURL(); frameLoadRequest.setShouldOpenExternalURLsPolicy(shouldOpenExternalURLs()); frame.loader().changeLocation(WTFMove(frameLoadRequest)); } private: IsMetaRefresh m_isMetaRefresh; }; class ScheduledLocationChange : public ScheduledURLNavigation { public: ScheduledLocationChange(Document& initiatingDocument, SecurityOrigin* securityOrigin, const URL& url, const String& referrer, LockHistory lockHistory, LockBackForwardList lockBackForwardList, bool duringLoad, CompletionHandler&& completionHandler) : ScheduledURLNavigation(initiatingDocument, 0.0, securityOrigin, url, referrer, lockHistory, lockBackForwardList, duringLoad, true) , m_completionHandler(WTFMove(completionHandler)) { } ~ScheduledLocationChange() { if (m_completionHandler) m_completionHandler(); } void fire(Frame& frame) override { UserGestureIndicator gestureIndicator { userGestureToForward() }; ResourceRequest resourceRequest { url(), referrer(), ResourceRequestCachePolicy::UseProtocolCachePolicy }; FrameLoadRequest frameLoadRequest { initiatingDocument(), *securityOrigin(), WTFMove(resourceRequest), "_self", initiatedByMainFrame() }; frameLoadRequest.setLockHistory(lockHistory()); frameLoadRequest.setLockBackForwardList(lockBackForwardList()); frameLoadRequest.disableNavigationToInvalidURL(); frameLoadRequest.setShouldOpenExternalURLsPolicy(shouldOpenExternalURLs()); auto completionHandler = std::exchange(m_completionHandler, nullptr); frame.loader().changeLocation(WTFMove(frameLoadRequest)); completionHandler(); } private: CompletionHandler m_completionHandler; }; class ScheduledRefresh : public ScheduledURLNavigation { public: ScheduledRefresh(Document& initiatingDocument, SecurityOrigin* securityOrigin, const URL& url, const String& referrer) : ScheduledURLNavigation(initiatingDocument, 0.0, securityOrigin, url, referrer, LockHistory::Yes, LockBackForwardList::Yes, false, true) { } void fire(Frame& frame) override { UserGestureIndicator gestureIndicator { userGestureToForward() }; ResourceRequest resourceRequest { url(), referrer(), ResourceRequestCachePolicy::ReloadIgnoringCacheData }; FrameLoadRequest frameLoadRequest { initiatingDocument(), *securityOrigin(), WTFMove(resourceRequest), "_self", initiatedByMainFrame() }; frameLoadRequest.setLockHistory(lockHistory()); frameLoadRequest.setLockBackForwardList(lockBackForwardList()); frameLoadRequest.setShouldOpenExternalURLsPolicy(shouldOpenExternalURLs()); frame.loader().changeLocation(WTFMove(frameLoadRequest)); } }; class ScheduledHistoryNavigation : public ScheduledNavigation { public: explicit ScheduledHistoryNavigation(int historySteps) : ScheduledNavigation(0, LockHistory::No, LockBackForwardList::No, false, true) , m_historySteps(historySteps) { } void fire(Frame& frame) override { UserGestureIndicator gestureIndicator(userGestureToForward()); if (!m_historySteps) { // Special case for go(0) from a frame -> reload only the frame // To follow Firefox and IE's behavior, history reload can only navigate the self frame. frame.loader().changeLocation(frame.document()->url(), "_self", 0, ReferrerPolicy::EmptyString, shouldOpenExternalURLs()); return; } // go(i!=0) from a frame navigates into the history of the frame only, // in both IE and NS (but not in Mozilla). We can't easily do that. frame.page()->backForward().goBackOrForward(m_historySteps); } private: int m_historySteps; }; class ScheduledFormSubmission : public ScheduledNavigation { public: ScheduledFormSubmission(Ref&& submission, LockBackForwardList lockBackForwardList, bool duringLoad) : ScheduledNavigation(0, submission->lockHistory(), lockBackForwardList, duringLoad, true, submission->state().sourceDocument().shouldOpenExternalURLsPolicyToPropagate()) , m_submission(WTFMove(submission)) { } void fire(Frame& frame) override { if (m_submission->wasCancelled()) return; UserGestureIndicator gestureIndicator(userGestureToForward()); // The submitForm function will find a target frame before using the redirection timer. // Now that the timer has fired, we need to repeat the security check which normally is done when // selecting a target, in case conditions have changed. Other code paths avoid this by targeting // without leaving a time window. If we fail the check just silently drop the form submission. auto& requestingDocument = m_submission->state().sourceDocument(); if (!requestingDocument.canNavigate(&frame)) return; FrameLoadRequest frameLoadRequest { requestingDocument, requestingDocument.securityOrigin(), { }, { }, initiatedByMainFrame() }; frameLoadRequest.setLockHistory(lockHistory()); frameLoadRequest.setLockBackForwardList(lockBackForwardList()); frameLoadRequest.setReferrerPolicy(ReferrerPolicy::EmptyString); frameLoadRequest.setShouldOpenExternalURLsPolicy(shouldOpenExternalURLs()); m_submission->populateFrameLoadRequest(frameLoadRequest); frame.loader().loadFrameRequest(WTFMove(frameLoadRequest), m_submission->event(), m_submission->takeState()); } void didStartTimer(Frame& frame, Timer& timer) override { if (m_haveToldClient) return; m_haveToldClient = true; UserGestureIndicator gestureIndicator(userGestureToForward()); frame.loader().clientRedirected(m_submission->requestURL(), delay(), WallTime::now() + timer.nextFireInterval(), lockBackForwardList()); } void didStopTimer(Frame& frame, NewLoadInProgress newLoadInProgress) override { if (!m_haveToldClient) return; // Do not set a UserGestureIndicator because // clientRedirectCancelledOrFinished() is also called from many places // inside FrameLoader, where the gesture state is not set and is in // fact unavailable. We need to be consistent with them, otherwise the // gesture state will sometimes be set and sometimes not within // dispatchDidCancelClientRedirect(). frame.loader().clientRedirectCancelledOrFinished(newLoadInProgress); } private: Ref m_submission; bool m_haveToldClient { false }; }; class ScheduledPageBlock final : public ScheduledNavigation { public: ScheduledPageBlock(Document& originDocument) : ScheduledNavigation(0, LockHistory::Yes, LockBackForwardList::Yes, false, false) , m_originDocument(originDocument) { } void fire(Frame& frame) override { UserGestureIndicator gestureIndicator { userGestureToForward() }; ResourceResponse replacementResponse { m_originDocument.url(), "text/plain"_s, 0, "UTF-8"_s }; SubstituteData replacementData { SharedBuffer::create(), m_originDocument.url(), replacementResponse, SubstituteData::SessionHistoryVisibility::Hidden }; ResourceRequest resourceRequest { m_originDocument.url(), emptyString(), ResourceRequestCachePolicy::ReloadIgnoringCacheData }; FrameLoadRequest frameLoadRequest { m_originDocument, m_originDocument.securityOrigin(), WTFMove(resourceRequest), { }, initiatedByMainFrame() }; frameLoadRequest.setLockHistory(lockHistory()); frameLoadRequest.setLockBackForwardList(lockBackForwardList()); frameLoadRequest.setSubstituteData(replacementData); frameLoadRequest.setShouldOpenExternalURLsPolicy(shouldOpenExternalURLs()); frame.loader().load(WTFMove(frameLoadRequest)); } private: Document& m_originDocument; }; NavigationScheduler::NavigationScheduler(Frame& frame) : m_frame(frame) , m_timer(*this, &NavigationScheduler::timerFired) { } NavigationScheduler::~NavigationScheduler() = default; bool NavigationScheduler::redirectScheduledDuringLoad() { return m_redirect && m_redirect->wasDuringLoad(); } bool NavigationScheduler::locationChangePending() { return m_redirect && m_redirect->isLocationChange(); } void NavigationScheduler::clear() { if (m_timer.isActive()) InspectorInstrumentation::frameClearedScheduledNavigation(m_frame); m_timer.stop(); m_redirect = nullptr; } inline bool NavigationScheduler::shouldScheduleNavigation() const { return m_frame.page(); } inline bool NavigationScheduler::shouldScheduleNavigation(const URL& url) const { if (!shouldScheduleNavigation()) return false; if (url.protocolIsJavaScript()) return true; return NavigationDisabler::isNavigationAllowed(m_frame); } void NavigationScheduler::scheduleRedirect(Document& initiatingDocument, double delay, const URL& url, IsMetaRefresh isMetaRefresh) { if (!shouldScheduleNavigation(url)) return; if (delay < 0 || delay > INT_MAX / 1000) return; if (url.isEmpty()) return; // We want a new back/forward list item if the refresh timeout is > 1 second. if (!m_redirect || delay <= m_redirect->delay()) { auto lockBackForwardList = delay <= 1 ? LockBackForwardList::Yes : LockBackForwardList::No; schedule(makeUnique(initiatingDocument, delay, &m_frame.document()->securityOrigin(), url, LockHistory::Yes, lockBackForwardList, isMetaRefresh)); } } LockBackForwardList NavigationScheduler::mustLockBackForwardList(Frame& targetFrame) { // Non-user navigation before the page has finished firing onload should not create a new back/forward item. // See https://webkit.org/b/42861 for the original motivation for this. if (!UserGestureIndicator::processingUserGesture() && targetFrame.loader().documentLoader() && !targetFrame.loader().documentLoader()->wasOnloadDispatched()) return LockBackForwardList::Yes; // Navigation of a subframe during loading of an ancestor frame does not create a new back/forward item. // The definition of "during load" is any time before all handlers for the load event have been run. // See https://bugs.webkit.org/show_bug.cgi?id=14957 for the original motivation for this. for (Frame* ancestor = targetFrame.tree().parent(); ancestor; ancestor = ancestor->tree().parent()) { Document* document = ancestor->document(); if (!ancestor->loader().isComplete() || (document && document->processingLoadEvent())) return LockBackForwardList::Yes; } return LockBackForwardList::No; } void NavigationScheduler::scheduleLocationChange(Document& initiatingDocument, SecurityOrigin& securityOrigin, const URL& url, const String& referrer, LockHistory lockHistory, LockBackForwardList lockBackForwardList, CompletionHandler&& completionHandler) { if (!shouldScheduleNavigation(url)) return completionHandler(); if (lockBackForwardList == LockBackForwardList::No) lockBackForwardList = mustLockBackForwardList(m_frame); FrameLoader& loader = m_frame.loader(); // If the URL we're going to navigate to is the same as the current one, except for the // fragment part, we don't need to schedule the location change. if (url.hasFragmentIdentifier() && equalIgnoringFragmentIdentifier(m_frame.document()->url(), url)) { ResourceRequest resourceRequest { m_frame.document()->completeURL(url.string()), referrer, ResourceRequestCachePolicy::UseProtocolCachePolicy }; auto* frame = lexicalFrameFromCommonVM(); auto initiatedByMainFrame = frame && frame->isMainFrame() ? InitiatedByMainFrame::Yes : InitiatedByMainFrame::Unknown; FrameLoadRequest frameLoadRequest { initiatingDocument, securityOrigin, WTFMove(resourceRequest), "_self"_s, initiatedByMainFrame }; frameLoadRequest.setLockHistory(lockHistory); frameLoadRequest.setLockBackForwardList(lockBackForwardList); frameLoadRequest.disableNavigationToInvalidURL(); frameLoadRequest.setShouldOpenExternalURLsPolicy(initiatingDocument.shouldOpenExternalURLsPolicyToPropagate()); loader.changeLocation(WTFMove(frameLoadRequest)); return completionHandler(); } // Handle a location change of a page with no document as a special case. // This may happen when a frame changes the location of another frame. bool duringLoad = !loader.stateMachine().committedFirstRealDocumentLoad(); schedule(makeUnique(initiatingDocument, &securityOrigin, url, referrer, lockHistory, lockBackForwardList, duringLoad, WTFMove(completionHandler))); } void NavigationScheduler::scheduleFormSubmission(Ref&& submission) { ASSERT(m_frame.page()); // FIXME: Do we need special handling for form submissions where the URL is the same // as the current one except for the fragment part? See scheduleLocationChange above. // Handle a location change of a page with no document as a special case. // This may happen when a frame changes the location of another frame. bool duringLoad = !m_frame.loader().stateMachine().committedFirstRealDocumentLoad(); // If this is a child frame and the form submission was triggered by a script, lock the back/forward list // to match IE and Opera. // See https://bugs.webkit.org/show_bug.cgi?id=32383 for the original motivation for this. LockBackForwardList lockBackForwardList = mustLockBackForwardList(m_frame); if (lockBackForwardList == LockBackForwardList::No && (submission->state().formSubmissionTrigger() == SubmittedByJavaScript && m_frame.tree().parent() && !UserGestureIndicator::processingUserGesture())) { lockBackForwardList = LockBackForwardList::Yes; } schedule(makeUnique(WTFMove(submission), lockBackForwardList, duringLoad)); } void NavigationScheduler::scheduleRefresh(Document& initiatingDocument) { if (!shouldScheduleNavigation()) return; const URL& url = m_frame.document()->url(); if (url.isEmpty()) return; schedule(makeUnique(initiatingDocument, &m_frame.document()->securityOrigin(), url, m_frame.loader().outgoingReferrer())); } void NavigationScheduler::scheduleHistoryNavigation(int steps) { LOG(History, "NavigationScheduler %p scheduleHistoryNavigation(%d) - shouldSchedule %d", this, steps, shouldScheduleNavigation()); if (!shouldScheduleNavigation()) return; // Invalid history navigations (such as history.forward() during a new load) have the side effect of cancelling any scheduled // redirects. We also avoid the possibility of cancelling the current load by avoiding the scheduled redirection altogether. BackForwardController& backForward = m_frame.page()->backForward(); if ((steps > 0 && static_cast(steps) > backForward.forwardCount()) || (steps < 0 && static_cast(-steps) > backForward.backCount())) { cancel(); return; } // In all other cases, schedule the history traversal to occur asynchronously. schedule(makeUnique(steps)); } void NavigationScheduler::schedulePageBlock(Document& originDocument) { if (shouldScheduleNavigation()) schedule(makeUnique(originDocument)); } void NavigationScheduler::timerFired() { if (!m_frame.page()) return; if (m_frame.page()->defersLoading()) { InspectorInstrumentation::frameClearedScheduledNavigation(m_frame); return; } Ref protect(m_frame); std::unique_ptr redirect = std::exchange(m_redirect, nullptr); LOG(History, "NavigationScheduler %p timerFired - firing redirect %p", this, redirect.get()); redirect->fire(m_frame); InspectorInstrumentation::frameClearedScheduledNavigation(m_frame); } void NavigationScheduler::schedule(std::unique_ptr redirect) { ASSERT(m_frame.page()); Ref protect(m_frame); // If a redirect was scheduled during a load, then stop the current load. // Otherwise when the current load transitions from a provisional to a // committed state, pending redirects may be cancelled. if (redirect->wasDuringLoad()) { if (DocumentLoader* provisionalDocumentLoader = m_frame.loader().provisionalDocumentLoader()) provisionalDocumentLoader->stopLoading(); m_frame.loader().stopLoading(UnloadEventPolicy::UnloadAndPageHide); } cancel(); m_redirect = WTFMove(redirect); if (!m_frame.loader().isComplete() && m_redirect->isLocationChange()) m_frame.loader().completed(); if (!m_frame.page()) return; startTimer(); } void NavigationScheduler::startTimer() { if (!m_redirect) return; ASSERT(m_frame.page()); if (m_timer.isActive()) return; if (!m_redirect->shouldStartTimer(m_frame)) return; Seconds delay = 1_s * m_redirect->delay(); m_timer.startOneShot(delay); InspectorInstrumentation::frameScheduledNavigation(m_frame, delay); m_redirect->didStartTimer(m_frame, m_timer); // m_redirect may be null on return (e.g. the client canceled the load) } void NavigationScheduler::cancel(NewLoadInProgress newLoadInProgress) { LOG(History, "NavigationScheduler %p cancel(newLoadInProgress=%d)", this, newLoadInProgress == NewLoadInProgress::Yes); if (m_timer.isActive()) InspectorInstrumentation::frameClearedScheduledNavigation(m_frame); m_timer.stop(); if (auto redirect = std::exchange(m_redirect, nullptr)) redirect->didStopTimer(m_frame, newLoadInProgress); } } // namespace WebCore