| /* |
| * 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 <wtf/Ref.h> |
| |
| 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<UserGestureToken> 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 { initiatingDocument } |
| , m_securityOrigin { 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<Frame> 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<Document> m_initiatingDocument; |
| RefPtr<SecurityOrigin> 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), selfTargetFrameName(), 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<void()>&& 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), selfTargetFrameName(), 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<void()> 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), selfTargetFrameName(), 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(), selfTargetFrameName(), 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<FormSubmission>&& 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(m_submission->referrerPolicy()); |
| frameLoadRequest.setNewFrameOpenerPolicy(m_submission->newFrameOpenerPolicy()); |
| frameLoadRequest.setShouldOpenExternalURLsPolicy(shouldOpenExternalURLs()); |
| frameLoadRequest.disableShouldReplaceDocumentIfJavaScriptURL(); |
| 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<FormSubmission> 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<ScheduledRedirect>(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<void()>&& 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), selfTargetFrameName(), 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<ScheduledLocationChange>(initiatingDocument, &securityOrigin, url, referrer, lockHistory, lockBackForwardList, duringLoad, WTFMove(completionHandler))); |
| } |
| |
| void NavigationScheduler::scheduleFormSubmission(Ref<FormSubmission>&& 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; |
| } |
| |
| bool isJavaScriptURL = submission->requestURL().protocolIsJavaScript(); |
| |
| auto scheduledFormSubmission = makeUnique<ScheduledFormSubmission>(WTFMove(submission), lockBackForwardList, duringLoad); |
| |
| // FIXME: We currently run JavaScript URLs synchronously even though this doesn't appear to match the specification. |
| if (isJavaScriptURL) { |
| scheduledFormSubmission->fire(m_frame); |
| return; |
| } |
| |
| schedule(WTFMove(scheduledFormSubmission)); |
| } |
| |
| void NavigationScheduler::scheduleRefresh(Document& initiatingDocument) |
| { |
| if (!shouldScheduleNavigation()) |
| return; |
| const URL& url = m_frame.document()->url(); |
| if (url.isEmpty()) |
| return; |
| |
| schedule(makeUnique<ScheduledRefresh>(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<unsigned>(steps) > backForward.forwardCount()) |
| || (steps < 0 && static_cast<unsigned>(-steps) > backForward.backCount())) { |
| cancel(); |
| return; |
| } |
| |
| // In all other cases, schedule the history traversal to occur asynchronously. |
| schedule(makeUnique<ScheduledHistoryNavigation>(steps)); |
| } |
| |
| void NavigationScheduler::schedulePageBlock(Document& originDocument) |
| { |
| if (shouldScheduleNavigation()) |
| schedule(makeUnique<ScheduledPageBlock>(originDocument)); |
| } |
| |
| void NavigationScheduler::timerFired() |
| { |
| if (!m_frame.page()) |
| return; |
| if (m_frame.page()->defersLoading()) { |
| InspectorInstrumentation::frameClearedScheduledNavigation(m_frame); |
| return; |
| } |
| |
| Ref<Frame> protect(m_frame); |
| |
| std::unique_ptr<ScheduledNavigation> 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<ScheduledNavigation> redirect) |
| { |
| ASSERT(m_frame.page()); |
| |
| Ref<Frame> 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 |