XSS Auditor should navigate to empty substitute data on full page block
[WebKit-https.git] / Source / WebCore / loader / NavigationScheduler.cpp
1 /*
2  * Copyright (C) 2006-2010, 2016 Apple Inc. All rights reserved.
3  * Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies)
4  * Copyright (C) 2008, 2009 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.com/)
5  * Copyright (C) 2009 Adam Barth. All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  *
11  * 1.  Redistributions of source code must retain the above copyright
12  *     notice, this list of conditions and the following disclaimer. 
13  * 2.  Redistributions in binary form must reproduce the above copyright
14  *     notice, this list of conditions and the following disclaimer in the
15  *     documentation and/or other materials provided with the distribution. 
16  * 3.  Neither the name of Apple Inc. ("Apple") nor the names of
17  *     its contributors may be used to endorse or promote products derived
18  *     from this software without specific prior written permission. 
19  *
20  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
21  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
22  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
24  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
25  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
26  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
27  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
29  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30  */
31
32 #include "config.h"
33 #include "NavigationScheduler.h"
34
35 #include "BackForwardController.h"
36 #include "DOMWindow.h"
37 #include "DocumentLoader.h"
38 #include "Event.h"
39 #include "FormState.h"
40 #include "FormSubmission.h"
41 #include "Frame.h"
42 #include "FrameLoadRequest.h"
43 #include "FrameLoader.h"
44 #include "FrameLoaderStateMachine.h"
45 #include "HTMLFormElement.h"
46 #include "HTMLFrameOwnerElement.h"
47 #include "HistoryItem.h"
48 #include "InspectorInstrumentation.h"
49 #include "Page.h"
50 #include "ScriptController.h"
51 #include "Settings.h"
52 #include "UserGestureIndicator.h"
53 #include <wtf/CurrentTime.h>
54 #include <wtf/Ref.h>
55
56 namespace WebCore {
57
58 unsigned NavigationDisablerForBeforeUnload::s_navigationDisableCount = 0;
59
60 class ScheduledNavigation {
61     WTF_MAKE_NONCOPYABLE(ScheduledNavigation); WTF_MAKE_FAST_ALLOCATED;
62 public:
63     ScheduledNavigation(double delay, LockHistory lockHistory, LockBackForwardList lockBackForwardList, bool wasDuringLoad, bool isLocationChange)
64         : m_delay(delay)
65         , m_lockHistory(lockHistory)
66         , m_lockBackForwardList(lockBackForwardList)
67         , m_wasDuringLoad(wasDuringLoad)
68         , m_isLocationChange(isLocationChange)
69         , m_wasUserGesture(ScriptController::processingUserGesture())
70     {
71     }
72     virtual ~ScheduledNavigation() { }
73
74     virtual void fire(Frame&) = 0;
75
76     virtual bool shouldStartTimer(Frame&) { return true; }
77     virtual void didStartTimer(Frame&, Timer&) { }
78     virtual void didStopTimer(Frame&, bool /* newLoadInProgress */) { }
79
80     double delay() const { return m_delay; }
81     LockHistory lockHistory() const { return m_lockHistory; }
82     LockBackForwardList lockBackForwardList() const { return m_lockBackForwardList; }
83     bool wasDuringLoad() const { return m_wasDuringLoad; }
84     bool isLocationChange() const { return m_isLocationChange; }
85     bool wasUserGesture() const { return m_wasUserGesture; }
86
87 protected:
88     void clearUserGesture() { m_wasUserGesture = false; }
89
90     ShouldOpenExternalURLsPolicy m_shouldOpenExternalURLsPolicy { ShouldOpenExternalURLsPolicy::ShouldNotAllow };
91
92 private:
93     double m_delay;
94     LockHistory m_lockHistory;
95     LockBackForwardList m_lockBackForwardList;
96     bool m_wasDuringLoad;
97     bool m_isLocationChange;
98     bool m_wasUserGesture;
99 };
100
101 class ScheduledURLNavigation : public ScheduledNavigation {
102 protected:
103     ScheduledURLNavigation(Document* initiatingDocument, double delay, SecurityOrigin* securityOrigin, const URL& url, const String& referrer, LockHistory lockHistory, LockBackForwardList lockBackForwardList, bool duringLoad, bool isLocationChange)
104         : ScheduledNavigation(delay, lockHistory, lockBackForwardList, duringLoad, isLocationChange)
105         , m_securityOrigin(securityOrigin)
106         , m_url(url)
107         , m_referrer(referrer)
108         , m_haveToldClient(false)
109     {
110         if (initiatingDocument)
111             m_shouldOpenExternalURLsPolicy = initiatingDocument->shouldOpenExternalURLsPolicyToPropagate();
112     }
113
114     virtual void fire(Frame& frame) override
115     {
116         UserGestureIndicator gestureIndicator(wasUserGesture() ? DefinitelyProcessingUserGesture : DefinitelyNotProcessingUserGesture);
117
118         ResourceRequest resourceRequest(m_url, m_referrer, UseProtocolCachePolicy);
119         FrameLoadRequest frameRequest(m_securityOrigin.get(), resourceRequest, "_self", lockHistory(), lockBackForwardList(), MaybeSendReferrer, AllowNavigationToInvalidURL::Yes, NewFrameOpenerPolicy::Allow, m_shouldOpenExternalURLsPolicy);
120
121         frame.loader().changeLocation(frameRequest);
122     }
123
124     virtual void didStartTimer(Frame& frame, Timer& timer) override
125     {
126         if (m_haveToldClient)
127             return;
128         m_haveToldClient = true;
129
130         UserGestureIndicator gestureIndicator(wasUserGesture() ? DefinitelyProcessingUserGesture : DefinitelyNotProcessingUserGesture);
131         frame.loader().clientRedirected(m_url, delay(), currentTime() + timer.nextFireInterval(), lockBackForwardList());
132     }
133
134     virtual void didStopTimer(Frame& frame, bool newLoadInProgress) override
135     {
136         if (!m_haveToldClient)
137             return;
138
139         // Do not set a UserGestureIndicator because
140         // clientRedirectCancelledOrFinished() is also called from many places
141         // inside FrameLoader, where the gesture state is not set and is in
142         // fact unavailable. We need to be consistent with them, otherwise the
143         // gesture state will sometimes be set and sometimes not within
144         // dispatchDidCancelClientRedirect().
145         frame.loader().clientRedirectCancelledOrFinished(newLoadInProgress);
146     }
147
148     SecurityOrigin* securityOrigin() const { return m_securityOrigin.get(); }
149     const URL& url() const { return m_url; }
150     String referrer() const { return m_referrer; }
151
152 private:
153     RefPtr<SecurityOrigin> m_securityOrigin;
154     URL m_url;
155     String m_referrer;
156     bool m_haveToldClient;
157 };
158
159 class ScheduledRedirect : public ScheduledURLNavigation {
160 public:
161     ScheduledRedirect(Document* initiatingDocument, double delay, SecurityOrigin* securityOrigin, const URL& url, LockHistory lockHistory, LockBackForwardList lockBackForwardList)
162         : ScheduledURLNavigation(initiatingDocument, delay, securityOrigin, url, String(), lockHistory, lockBackForwardList, false, false)
163     {
164         clearUserGesture();
165     }
166
167     virtual bool shouldStartTimer(Frame& frame) override
168     {
169         return frame.loader().allAncestorsAreComplete();
170     }
171
172     virtual void fire(Frame& frame) override
173     {
174         UserGestureIndicator gestureIndicator(wasUserGesture() ? DefinitelyProcessingUserGesture : DefinitelyNotProcessingUserGesture);
175         bool refresh = equalIgnoringFragmentIdentifier(frame.document()->url(), url());
176         ResourceRequest resourceRequest(url(), referrer(), refresh ? ReloadIgnoringCacheData : UseProtocolCachePolicy);
177         FrameLoadRequest frameRequest(securityOrigin(), resourceRequest, "_self", lockHistory(), lockBackForwardList(), MaybeSendReferrer, AllowNavigationToInvalidURL::No, NewFrameOpenerPolicy::Allow, m_shouldOpenExternalURLsPolicy);
178
179         frame.loader().changeLocation(frameRequest);
180     }
181 };
182
183 class ScheduledLocationChange : public ScheduledURLNavigation {
184 public:
185     ScheduledLocationChange(Document* initiatingDocument, SecurityOrigin* securityOrigin, const URL& url, const String& referrer, LockHistory lockHistory, LockBackForwardList lockBackForwardList, bool duringLoad)
186         : ScheduledURLNavigation(initiatingDocument, 0.0, securityOrigin, url, referrer, lockHistory, lockBackForwardList, duringLoad, true) { }
187
188     virtual void fire(Frame& frame) override
189     {
190         UserGestureIndicator gestureIndicator(wasUserGesture() ? DefinitelyProcessingUserGesture : DefinitelyNotProcessingUserGesture);
191
192         ResourceRequest resourceRequest(url(), referrer(), UseProtocolCachePolicy);
193         FrameLoadRequest frameRequest(securityOrigin(), resourceRequest, "_self", lockHistory(), lockBackForwardList(), MaybeSendReferrer, AllowNavigationToInvalidURL::No, NewFrameOpenerPolicy::Allow, m_shouldOpenExternalURLsPolicy);
194         frame.loader().changeLocation(frameRequest);
195     }
196 };
197
198 class ScheduledRefresh : public ScheduledURLNavigation {
199 public:
200     ScheduledRefresh(Document* initiatingDocument, SecurityOrigin* securityOrigin, const URL& url, const String& referrer)
201         : ScheduledURLNavigation(initiatingDocument, 0.0, securityOrigin, url, referrer, LockHistory::Yes, LockBackForwardList::Yes, false, true)
202     {
203     }
204
205     virtual void fire(Frame& frame) override
206     {
207         UserGestureIndicator gestureIndicator(wasUserGesture() ? DefinitelyProcessingUserGesture : DefinitelyNotProcessingUserGesture);
208
209         ResourceRequest resourceRequest(url(), referrer(), ReloadIgnoringCacheData);
210         FrameLoadRequest frameRequest(securityOrigin(), resourceRequest, "_self", lockHistory(), lockBackForwardList(), MaybeSendReferrer, AllowNavigationToInvalidURL::Yes, NewFrameOpenerPolicy::Allow, m_shouldOpenExternalURLsPolicy);
211         frame.loader().changeLocation(frameRequest);
212     }
213 };
214
215 class ScheduledHistoryNavigation : public ScheduledNavigation {
216 public:
217     explicit ScheduledHistoryNavigation(int historySteps)
218         : ScheduledNavigation(0, LockHistory::No, LockBackForwardList::No, false, true)
219         , m_historySteps(historySteps)
220     {
221     }
222
223     virtual void fire(Frame& frame) override
224     {
225         UserGestureIndicator gestureIndicator(wasUserGesture() ? DefinitelyProcessingUserGesture : DefinitelyNotProcessingUserGesture);
226
227         if (!m_historySteps) {
228             // Special case for go(0) from a frame -> reload only the frame
229             // To follow Firefox and IE's behavior, history reload can only navigate the self frame.
230             frame.loader().urlSelected(frame.document()->url(), "_self", 0, lockHistory(), lockBackForwardList(), MaybeSendReferrer, m_shouldOpenExternalURLsPolicy);
231             return;
232         }
233         
234         // go(i!=0) from a frame navigates into the history of the frame only,
235         // in both IE and NS (but not in Mozilla). We can't easily do that.
236         frame.page()->backForward().goBackOrForward(m_historySteps);
237     }
238
239 private:
240     int m_historySteps;
241 };
242
243 class ScheduledFormSubmission : public ScheduledNavigation {
244 public:
245     ScheduledFormSubmission(PassRefPtr<FormSubmission> submission, LockBackForwardList lockBackForwardList, bool duringLoad)
246         : ScheduledNavigation(0, submission->lockHistory(), lockBackForwardList, duringLoad, true)
247         , m_submission(submission)
248         , m_haveToldClient(false)
249     {
250         ASSERT(m_submission->state());
251
252         if (Document* document = m_submission->state()->sourceDocument())
253             m_shouldOpenExternalURLsPolicy = document->shouldOpenExternalURLsPolicyToPropagate();
254     }
255
256     virtual void fire(Frame& frame) override
257     {
258         UserGestureIndicator gestureIndicator(wasUserGesture() ? DefinitelyProcessingUserGesture : DefinitelyNotProcessingUserGesture);
259
260         // The submitForm function will find a target frame before using the redirection timer.
261         // Now that the timer has fired, we need to repeat the security check which normally is done when
262         // selecting a target, in case conditions have changed. Other code paths avoid this by targeting
263         // without leaving a time window. If we fail the check just silently drop the form submission.
264         Document* requestingDocument = m_submission->state()->sourceDocument();
265         if (!requestingDocument->canNavigate(&frame))
266             return;
267         FrameLoadRequest frameRequest(requestingDocument->securityOrigin(), lockHistory(), lockBackForwardList(), MaybeSendReferrer, AllowNavigationToInvalidURL::Yes, NewFrameOpenerPolicy::Allow, m_shouldOpenExternalURLsPolicy);
268         m_submission->populateFrameLoadRequest(frameRequest);
269         frame.loader().loadFrameRequest(frameRequest, m_submission->event(), m_submission->state());
270     }
271     
272     virtual void didStartTimer(Frame& frame, Timer& timer) override
273     {
274         if (m_haveToldClient)
275             return;
276         m_haveToldClient = true;
277
278         UserGestureIndicator gestureIndicator(wasUserGesture() ? DefinitelyProcessingUserGesture : DefinitelyNotProcessingUserGesture);
279         frame.loader().clientRedirected(m_submission->requestURL(), delay(), currentTime() + timer.nextFireInterval(), lockBackForwardList());
280     }
281
282     virtual void didStopTimer(Frame& frame, bool newLoadInProgress) override
283     {
284         if (!m_haveToldClient)
285             return;
286
287         // Do not set a UserGestureIndicator because
288         // clientRedirectCancelledOrFinished() is also called from many places
289         // inside FrameLoader, where the gesture state is not set and is in
290         // fact unavailable. We need to be consistent with them, otherwise the
291         // gesture state will sometimes be set and sometimes not within
292         // dispatchDidCancelClientRedirect().
293         frame.loader().clientRedirectCancelledOrFinished(newLoadInProgress);
294     }
295
296 private:
297     RefPtr<FormSubmission> m_submission;
298     bool m_haveToldClient;
299 };
300
301 class ScheduledPageBlock final : public ScheduledNavigation {
302 public:
303     ScheduledPageBlock(Document& originDocument)
304         : ScheduledNavigation(0, LockHistory::Yes, LockBackForwardList::Yes, false, false)
305         , m_originDocument(originDocument)
306     {
307     }
308
309     void fire(Frame& frame) override
310     {
311         UserGestureIndicator gestureIndicator(wasUserGesture() ? DefinitelyProcessingUserGesture : DefinitelyNotProcessingUserGesture);
312
313         ResourceResponse replacementResponse(m_originDocument.url(), ASCIILiteral("text/plain"), 0, ASCIILiteral("UTF-8"));
314         SubstituteData replacementData(SharedBuffer::create(), m_originDocument.url(), replacementResponse, SubstituteData::SessionHistoryVisibility::Hidden);
315
316         ResourceRequest resourceRequest(m_originDocument.url(), emptyString(), ReloadIgnoringCacheData);
317         FrameLoadRequest frameRequest(m_originDocument.securityOrigin(), resourceRequest, lockHistory(), lockBackForwardList(), MaybeSendReferrer, AllowNavigationToInvalidURL::Yes, NewFrameOpenerPolicy::Allow, m_shouldOpenExternalURLsPolicy);
318         frameRequest.setSubstituteData(replacementData);
319         frame.loader().load(frameRequest);
320     }
321
322 private:
323     Document& m_originDocument;
324 };
325
326 class ScheduledSubstituteDataLoad : public ScheduledNavigation {
327 public:
328     ScheduledSubstituteDataLoad(const URL& baseURL, const SubstituteData& substituteData)
329         : ScheduledNavigation { 0, LockHistory::No, LockBackForwardList::No, false, false }
330         , m_baseURL { baseURL }
331         , m_substituteData { substituteData }
332     {
333     }
334
335     void fire(Frame& frame) override
336     {
337         UserGestureIndicator gestureIndicator { wasUserGesture() ? DefinitelyProcessingUserGesture : DefinitelyNotProcessingUserGesture };
338         frame.loader().load(FrameLoadRequest { &frame, m_baseURL, m_shouldOpenExternalURLsPolicy, m_substituteData });
339     }
340
341 private:
342     URL m_baseURL;
343     SubstituteData m_substituteData;
344 };
345
346 NavigationScheduler::NavigationScheduler(Frame& frame)
347     : m_frame(frame)
348     , m_timer(*this, &NavigationScheduler::timerFired)
349 {
350 }
351
352 NavigationScheduler::~NavigationScheduler()
353 {
354 }
355
356 bool NavigationScheduler::redirectScheduledDuringLoad()
357 {
358     return m_redirect && m_redirect->wasDuringLoad();
359 }
360
361 bool NavigationScheduler::locationChangePending()
362 {
363     return m_redirect && m_redirect->isLocationChange();
364 }
365
366 void NavigationScheduler::clear()
367 {
368     if (m_timer.isActive())
369         InspectorInstrumentation::frameClearedScheduledNavigation(m_frame);
370     m_timer.stop();
371     m_redirect = nullptr;
372 }
373
374 inline bool NavigationScheduler::shouldScheduleNavigation() const
375 {
376     return m_frame.page();
377 }
378
379 inline bool NavigationScheduler::shouldScheduleNavigation(const URL& url) const
380 {
381     if (!shouldScheduleNavigation())
382         return false;
383     if (protocolIsJavaScript(url))
384         return true;
385     return NavigationDisablerForBeforeUnload::isNavigationAllowed();
386 }
387
388 void NavigationScheduler::scheduleRedirect(Document* initiatingDocument, double delay, const URL& url)
389 {
390     if (!shouldScheduleNavigation(url))
391         return;
392     if (delay < 0 || delay > INT_MAX / 1000)
393         return;
394     if (url.isEmpty())
395         return;
396
397     // We want a new back/forward list item if the refresh timeout is > 1 second.
398     if (!m_redirect || delay <= m_redirect->delay()) {
399         LockBackForwardList lockBackForwardList = delay <= 1 ? LockBackForwardList::Yes : LockBackForwardList::No;
400         schedule(std::make_unique<ScheduledRedirect>(initiatingDocument, delay, m_frame.document()->securityOrigin(), url, LockHistory::Yes, lockBackForwardList));
401     }
402 }
403
404 LockBackForwardList NavigationScheduler::mustLockBackForwardList(Frame& targetFrame)
405 {
406     // Non-user navigation before the page has finished firing onload should not create a new back/forward item.
407     // See https://webkit.org/b/42861 for the original motivation for this.    
408     if (!ScriptController::processingUserGesture() && targetFrame.loader().documentLoader() && !targetFrame.loader().documentLoader()->wasOnloadDispatched())
409         return LockBackForwardList::Yes;
410     
411     // Navigation of a subframe during loading of an ancestor frame does not create a new back/forward item.
412     // The definition of "during load" is any time before all handlers for the load event have been run.
413     // See https://bugs.webkit.org/show_bug.cgi?id=14957 for the original motivation for this.
414     for (Frame* ancestor = targetFrame.tree().parent(); ancestor; ancestor = ancestor->tree().parent()) {
415         Document* document = ancestor->document();
416         if (!ancestor->loader().isComplete() || (document && document->processingLoadEvent()))
417             return LockBackForwardList::Yes;
418     }
419     return LockBackForwardList::No;
420 }
421
422 void NavigationScheduler::scheduleLocationChange(Document* initiatingDocument, SecurityOrigin* securityOrigin, const URL& url, const String& referrer, LockHistory lockHistory, LockBackForwardList lockBackForwardList)
423 {
424     if (!shouldScheduleNavigation(url))
425         return;
426
427     if (lockBackForwardList == LockBackForwardList::No)
428         lockBackForwardList = mustLockBackForwardList(m_frame);
429
430     FrameLoader& loader = m_frame.loader();
431
432     // If the URL we're going to navigate to is the same as the current one, except for the
433     // fragment part, we don't need to schedule the location change.
434     if (url.hasFragmentIdentifier() && equalIgnoringFragmentIdentifier(m_frame.document()->url(), url)) {
435         ResourceRequest resourceRequest(m_frame.document()->completeURL(url), referrer, UseProtocolCachePolicy);
436         ShouldOpenExternalURLsPolicy shouldOpenExternalURLsPolicy = ShouldOpenExternalURLsPolicy::ShouldNotAllow;
437         if (initiatingDocument)
438             shouldOpenExternalURLsPolicy = initiatingDocument->shouldOpenExternalURLsPolicyToPropagate();
439         FrameLoadRequest frameRequest(securityOrigin, resourceRequest, "_self", lockHistory, lockBackForwardList, MaybeSendReferrer, AllowNavigationToInvalidURL::No, NewFrameOpenerPolicy::Allow, ReplaceDocumentIfJavaScriptURL, shouldOpenExternalURLsPolicy);
440         loader.changeLocation(frameRequest);
441         return;
442     }
443
444     // Handle a location change of a page with no document as a special case.
445     // This may happen when a frame changes the location of another frame.
446     bool duringLoad = !loader.stateMachine().committedFirstRealDocumentLoad();
447
448     schedule(std::make_unique<ScheduledLocationChange>(initiatingDocument, securityOrigin, url, referrer, lockHistory, lockBackForwardList, duringLoad));
449 }
450
451 void NavigationScheduler::scheduleFormSubmission(PassRefPtr<FormSubmission> submission)
452 {
453     ASSERT(m_frame.page());
454
455     // FIXME: Do we need special handling for form submissions where the URL is the same
456     // as the current one except for the fragment part? See scheduleLocationChange above.
457
458     // Handle a location change of a page with no document as a special case.
459     // This may happen when a frame changes the location of another frame.
460     bool duringLoad = !m_frame.loader().stateMachine().committedFirstRealDocumentLoad();
461
462     // If this is a child frame and the form submission was triggered by a script, lock the back/forward list
463     // to match IE and Opera.
464     // See https://bugs.webkit.org/show_bug.cgi?id=32383 for the original motivation for this.
465     LockBackForwardList lockBackForwardList = mustLockBackForwardList(m_frame);
466     if (lockBackForwardList == LockBackForwardList::No
467         && (submission->state()->formSubmissionTrigger() == SubmittedByJavaScript && m_frame.tree().parent() && !ScriptController::processingUserGesture())) {
468         lockBackForwardList = LockBackForwardList::Yes;
469     }
470     
471     schedule(std::make_unique<ScheduledFormSubmission>(submission, lockBackForwardList, duringLoad));
472 }
473
474 void NavigationScheduler::scheduleRefresh(Document* initiatingDocument)
475 {
476     if (!shouldScheduleNavigation())
477         return;
478     const URL& url = m_frame.document()->url();
479     if (url.isEmpty())
480         return;
481
482     schedule(std::make_unique<ScheduledRefresh>(initiatingDocument, m_frame.document()->securityOrigin(), url, m_frame.loader().outgoingReferrer()));
483 }
484
485 void NavigationScheduler::scheduleHistoryNavigation(int steps)
486 {
487     if (!shouldScheduleNavigation())
488         return;
489
490     // Invalid history navigations (such as history.forward() during a new load) have the side effect of cancelling any scheduled
491     // redirects. We also avoid the possibility of cancelling the current load by avoiding the scheduled redirection altogether.
492     BackForwardController& backForward = m_frame.page()->backForward();
493     if (steps > backForward.forwardCount() || -steps > backForward.backCount()) {
494         cancel();
495         return;
496     }
497
498     // In all other cases, schedule the history traversal to occur asynchronously.
499     schedule(std::make_unique<ScheduledHistoryNavigation>(steps));
500 }
501
502 void NavigationScheduler::scheduleSubstituteDataLoad(const URL& baseURL, const SubstituteData& substituteData)
503 {
504     if (shouldScheduleNavigation())
505         schedule(std::make_unique<ScheduledSubstituteDataLoad>(baseURL, substituteData));
506 }
507
508 void NavigationScheduler::schedulePageBlock(Document& originDocument)
509 {
510     if (shouldScheduleNavigation())
511         schedule(std::make_unique<ScheduledPageBlock>(originDocument));
512 }
513
514 void NavigationScheduler::timerFired()
515 {
516     if (!m_frame.page())
517         return;
518     if (m_frame.page()->defersLoading()) {
519         InspectorInstrumentation::frameClearedScheduledNavigation(m_frame);
520         return;
521     }
522
523     Ref<Frame> protect(m_frame);
524
525     std::unique_ptr<ScheduledNavigation> redirect = WTFMove(m_redirect);
526     redirect->fire(m_frame);
527     InspectorInstrumentation::frameClearedScheduledNavigation(m_frame);
528 }
529
530 void NavigationScheduler::schedule(std::unique_ptr<ScheduledNavigation> redirect)
531 {
532     ASSERT(m_frame.page());
533
534     Ref<Frame> protect(m_frame);
535
536     // If a redirect was scheduled during a load, then stop the current load.
537     // Otherwise when the current load transitions from a provisional to a 
538     // committed state, pending redirects may be cancelled. 
539     if (redirect->wasDuringLoad()) {
540         if (DocumentLoader* provisionalDocumentLoader = m_frame.loader().provisionalDocumentLoader())
541             provisionalDocumentLoader->stopLoading();
542         m_frame.loader().stopLoading(UnloadEventPolicyUnloadAndPageHide);
543     }
544
545     cancel();
546     m_redirect = WTFMove(redirect);
547
548     if (!m_frame.loader().isComplete() && m_redirect->isLocationChange())
549         m_frame.loader().completed();
550
551     if (!m_frame.page())
552         return;
553
554     startTimer();
555 }
556
557 void NavigationScheduler::startTimer()
558 {
559     if (!m_redirect)
560         return;
561
562     ASSERT(m_frame.page());
563     if (m_timer.isActive())
564         return;
565     if (!m_redirect->shouldStartTimer(m_frame))
566         return;
567
568     double delay = m_redirect->delay();
569     m_timer.startOneShot(delay);
570     InspectorInstrumentation::frameScheduledNavigation(m_frame, delay);
571     m_redirect->didStartTimer(m_frame, m_timer); // m_redirect may be null on return (e.g. the client canceled the load)
572 }
573
574 void NavigationScheduler::cancel(bool newLoadInProgress)
575 {
576     if (m_timer.isActive())
577         InspectorInstrumentation::frameClearedScheduledNavigation(m_frame);
578     m_timer.stop();
579
580     if (std::unique_ptr<ScheduledNavigation> redirect = WTFMove(m_redirect))
581         redirect->didStopTimer(m_frame, newLoadInProgress);
582 }
583
584 } // namespace WebCore