56977532b8b12be08a619cd3fb1c1875d3b76eb1
[WebKit-https.git] / Source / WebCore / loader / NavigationScheduler.cpp
1 /*
2  * Copyright (C) 2006-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 "UserGestureIndicator.h"
52 #include <wtf/CurrentTime.h>
53 #include <wtf/Ref.h>
54
55 namespace WebCore {
56
57 unsigned NavigationDisablerForBeforeUnload::s_navigationDisableCount = 0;
58
59 class ScheduledNavigation {
60     WTF_MAKE_NONCOPYABLE(ScheduledNavigation); WTF_MAKE_FAST_ALLOCATED;
61 public:
62     ScheduledNavigation(double delay, LockHistory lockHistory, LockBackForwardList lockBackForwardList, bool wasDuringLoad, bool isLocationChange)
63         : m_delay(delay)
64         , m_lockHistory(lockHistory)
65         , m_lockBackForwardList(lockBackForwardList)
66         , m_wasDuringLoad(wasDuringLoad)
67         , m_isLocationChange(isLocationChange)
68         , m_userGestureToForward(UserGestureIndicator::currentUserGesture())
69     {
70     }
71     ScheduledNavigation(double delay, LockHistory lockHistory, LockBackForwardList lockBackForwardList, bool wasDuringLoad, bool isLocationChange, ShouldOpenExternalURLsPolicy externalURLPolicy)
72         : m_delay(delay)
73         , m_lockHistory(lockHistory)
74         , m_lockBackForwardList(lockBackForwardList)
75         , m_wasDuringLoad(wasDuringLoad)
76         , m_isLocationChange(isLocationChange)
77         , m_userGestureToForward(UserGestureIndicator::currentUserGesture())
78         , m_shouldOpenExternalURLsPolicy(externalURLPolicy)
79     {
80     }
81     virtual ~ScheduledNavigation() { }
82
83     virtual void fire(Frame&) = 0;
84
85     virtual bool shouldStartTimer(Frame&) { return true; }
86     virtual void didStartTimer(Frame&, Timer&) { }
87     virtual void didStopTimer(Frame&, bool /* newLoadInProgress */) { }
88
89     double delay() const { return m_delay; }
90     LockHistory lockHistory() const { return m_lockHistory; }
91     LockBackForwardList lockBackForwardList() const { return m_lockBackForwardList; }
92     bool wasDuringLoad() const { return m_wasDuringLoad; }
93     bool isLocationChange() const { return m_isLocationChange; }
94     UserGestureToken* userGestureToForward() const { return m_userGestureToForward.get(); }
95
96 protected:
97     void clearUserGesture() { m_userGestureToForward = nullptr; }
98     ShouldOpenExternalURLsPolicy shouldOpenExternalURLs() const { return m_shouldOpenExternalURLsPolicy; }
99
100 private:
101     double m_delay;
102     LockHistory m_lockHistory;
103     LockBackForwardList m_lockBackForwardList;
104     bool m_wasDuringLoad;
105     bool m_isLocationChange;
106     RefPtr<UserGestureToken> m_userGestureToForward;
107     ShouldOpenExternalURLsPolicy m_shouldOpenExternalURLsPolicy { ShouldOpenExternalURLsPolicy::ShouldNotAllow };
108 };
109
110 class ScheduledURLNavigation : public ScheduledNavigation {
111 protected:
112     ScheduledURLNavigation(Document& initiatingDocument, double delay, SecurityOrigin* securityOrigin, const URL& url, const String& referrer, LockHistory lockHistory, LockBackForwardList lockBackForwardList, bool duringLoad, bool isLocationChange)
113         : ScheduledNavigation(delay, lockHistory, lockBackForwardList, duringLoad, isLocationChange, initiatingDocument.shouldOpenExternalURLsPolicyToPropagate())
114         , m_securityOrigin(securityOrigin)
115         , m_url(url)
116         , m_referrer(referrer)
117     {
118     }
119
120     void fire(Frame& frame) override
121     {
122         UserGestureIndicator gestureIndicator(userGestureToForward());
123
124         ResourceRequest resourceRequest(m_url, m_referrer, UseProtocolCachePolicy);
125         FrameLoadRequest frameRequest(m_securityOrigin.get(), resourceRequest, "_self", lockHistory(), lockBackForwardList(), MaybeSendReferrer, AllowNavigationToInvalidURL::Yes, NewFrameOpenerPolicy::Allow, shouldOpenExternalURLs());
126
127         frame.loader().changeLocation(frameRequest);
128     }
129
130     void didStartTimer(Frame& frame, Timer& timer) override
131     {
132         if (m_haveToldClient)
133             return;
134         m_haveToldClient = true;
135
136         UserGestureIndicator gestureIndicator(userGestureToForward());
137         frame.loader().clientRedirected(m_url, delay(), currentTime() + timer.nextFireInterval(), lockBackForwardList());
138     }
139
140     void didStopTimer(Frame& frame, bool newLoadInProgress) override
141     {
142         if (!m_haveToldClient)
143             return;
144
145         // Do not set a UserGestureIndicator because
146         // clientRedirectCancelledOrFinished() is also called from many places
147         // inside FrameLoader, where the gesture state is not set and is in
148         // fact unavailable. We need to be consistent with them, otherwise the
149         // gesture state will sometimes be set and sometimes not within
150         // dispatchDidCancelClientRedirect().
151         frame.loader().clientRedirectCancelledOrFinished(newLoadInProgress);
152     }
153
154     SecurityOrigin* securityOrigin() const { return m_securityOrigin.get(); }
155     const URL& url() const { return m_url; }
156     String referrer() const { return m_referrer; }
157
158 private:
159     RefPtr<SecurityOrigin> m_securityOrigin;
160     URL m_url;
161     String m_referrer;
162     bool m_haveToldClient { false };
163 };
164
165 class ScheduledRedirect : public ScheduledURLNavigation {
166 public:
167     ScheduledRedirect(Document& initiatingDocument, double delay, SecurityOrigin* securityOrigin, const URL& url, LockHistory lockHistory, LockBackForwardList lockBackForwardList)
168         : ScheduledURLNavigation(initiatingDocument, delay, securityOrigin, url, String(), lockHistory, lockBackForwardList, false, false)
169     {
170         clearUserGesture();
171     }
172
173     bool shouldStartTimer(Frame& frame) override
174     {
175         return frame.loader().allAncestorsAreComplete();
176     }
177
178     void fire(Frame& frame) override
179     {
180         UserGestureIndicator gestureIndicator(userGestureToForward());
181         bool refresh = equalIgnoringFragmentIdentifier(frame.document()->url(), url());
182         ResourceRequest resourceRequest(url(), referrer(), refresh ? ReloadIgnoringCacheData : UseProtocolCachePolicy);
183         FrameLoadRequest frameRequest(securityOrigin(), resourceRequest, "_self", lockHistory(), lockBackForwardList(), MaybeSendReferrer, AllowNavigationToInvalidURL::No, NewFrameOpenerPolicy::Allow, shouldOpenExternalURLs());
184
185         frame.loader().changeLocation(frameRequest);
186     }
187 };
188
189 class ScheduledLocationChange : public ScheduledURLNavigation {
190 public:
191     ScheduledLocationChange(Document& initiatingDocument, SecurityOrigin* securityOrigin, const URL& url, const String& referrer, LockHistory lockHistory, LockBackForwardList lockBackForwardList, bool duringLoad)
192         : ScheduledURLNavigation(initiatingDocument, 0.0, securityOrigin, url, referrer, lockHistory, lockBackForwardList, duringLoad, true) { }
193
194     void fire(Frame& frame) override
195     {
196         UserGestureIndicator gestureIndicator(userGestureToForward());
197
198         ResourceRequest resourceRequest(url(), referrer(), UseProtocolCachePolicy);
199         FrameLoadRequest frameRequest(securityOrigin(), resourceRequest, "_self", lockHistory(), lockBackForwardList(), MaybeSendReferrer, AllowNavigationToInvalidURL::No, NewFrameOpenerPolicy::Allow, shouldOpenExternalURLs());
200         frame.loader().changeLocation(frameRequest);
201     }
202 };
203
204 class ScheduledRefresh : public ScheduledURLNavigation {
205 public:
206     ScheduledRefresh(Document& initiatingDocument, SecurityOrigin* securityOrigin, const URL& url, const String& referrer)
207         : ScheduledURLNavigation(initiatingDocument, 0.0, securityOrigin, url, referrer, LockHistory::Yes, LockBackForwardList::Yes, false, true)
208     {
209     }
210
211     void fire(Frame& frame) override
212     {
213         UserGestureIndicator gestureIndicator(userGestureToForward());
214
215         ResourceRequest resourceRequest(url(), referrer(), ReloadIgnoringCacheData);
216         FrameLoadRequest frameRequest(securityOrigin(), resourceRequest, "_self", lockHistory(), lockBackForwardList(), MaybeSendReferrer, AllowNavigationToInvalidURL::Yes, NewFrameOpenerPolicy::Allow, shouldOpenExternalURLs());
217         frame.loader().changeLocation(frameRequest);
218     }
219 };
220
221 class ScheduledHistoryNavigation : public ScheduledNavigation {
222 public:
223     explicit ScheduledHistoryNavigation(int historySteps)
224         : ScheduledNavigation(0, LockHistory::No, LockBackForwardList::No, false, true)
225         , m_historySteps(historySteps)
226     {
227     }
228
229     void fire(Frame& frame) override
230     {
231         UserGestureIndicator gestureIndicator(userGestureToForward());
232
233         if (!m_historySteps) {
234             // Special case for go(0) from a frame -> reload only the frame
235             // To follow Firefox and IE's behavior, history reload can only navigate the self frame.
236             frame.loader().urlSelected(frame.document()->url(), "_self", 0, lockHistory(), lockBackForwardList(), MaybeSendReferrer, shouldOpenExternalURLs());
237             return;
238         }
239         
240         // go(i!=0) from a frame navigates into the history of the frame only,
241         // in both IE and NS (but not in Mozilla). We can't easily do that.
242         frame.page()->backForward().goBackOrForward(m_historySteps);
243     }
244
245 private:
246     int m_historySteps;
247 };
248
249 class ScheduledFormSubmission : public ScheduledNavigation {
250 public:
251     ScheduledFormSubmission(Ref<FormSubmission>&& submission, LockBackForwardList lockBackForwardList, bool duringLoad)
252         : ScheduledNavigation(0, submission->lockHistory(), lockBackForwardList, duringLoad, true, submission->state().sourceDocument().shouldOpenExternalURLsPolicyToPropagate())
253         , m_submission(WTFMove(submission))
254     {
255     }
256
257     void fire(Frame& frame) override
258     {
259         UserGestureIndicator gestureIndicator(userGestureToForward());
260
261         // The submitForm function will find a target frame before using the redirection timer.
262         // Now that the timer has fired, we need to repeat the security check which normally is done when
263         // selecting a target, in case conditions have changed. Other code paths avoid this by targeting
264         // without leaving a time window. If we fail the check just silently drop the form submission.
265         auto& requestingDocument = m_submission->state().sourceDocument();
266         if (!requestingDocument.canNavigate(&frame))
267             return;
268         FrameLoadRequest frameRequest(requestingDocument.securityOrigin(), lockHistory(), lockBackForwardList(), MaybeSendReferrer, AllowNavigationToInvalidURL::Yes, NewFrameOpenerPolicy::Allow, shouldOpenExternalURLs());
269         m_submission->populateFrameLoadRequest(frameRequest);
270         frame.loader().loadFrameRequest(frameRequest, m_submission->event(), &m_submission->state());
271     }
272
273     void didStartTimer(Frame& frame, Timer& timer) override
274     {
275         if (m_haveToldClient)
276             return;
277         m_haveToldClient = true;
278
279         UserGestureIndicator gestureIndicator(userGestureToForward());
280         frame.loader().clientRedirected(m_submission->requestURL(), delay(), currentTime() + timer.nextFireInterval(), lockBackForwardList());
281     }
282
283     void didStopTimer(Frame& frame, bool newLoadInProgress) override
284     {
285         if (!m_haveToldClient)
286             return;
287
288         // Do not set a UserGestureIndicator because
289         // clientRedirectCancelledOrFinished() is also called from many places
290         // inside FrameLoader, where the gesture state is not set and is in
291         // fact unavailable. We need to be consistent with them, otherwise the
292         // gesture state will sometimes be set and sometimes not within
293         // dispatchDidCancelClientRedirect().
294         frame.loader().clientRedirectCancelledOrFinished(newLoadInProgress);
295     }
296
297 private:
298     Ref<FormSubmission> m_submission;
299     bool m_haveToldClient { false };
300 };
301
302 class ScheduledPageBlock final : public ScheduledNavigation {
303 public:
304     ScheduledPageBlock(Document& originDocument)
305         : ScheduledNavigation(0, LockHistory::Yes, LockBackForwardList::Yes, false, false)
306         , m_originDocument(originDocument)
307     {
308     }
309
310     void fire(Frame& frame) override
311     {
312         UserGestureIndicator gestureIndicator(userGestureToForward());
313
314         ResourceResponse replacementResponse(m_originDocument.url(), ASCIILiteral("text/plain"), 0, ASCIILiteral("UTF-8"));
315         SubstituteData replacementData(SharedBuffer::create(), m_originDocument.url(), replacementResponse, SubstituteData::SessionHistoryVisibility::Hidden);
316
317         ResourceRequest resourceRequest(m_originDocument.url(), emptyString(), ReloadIgnoringCacheData);
318         FrameLoadRequest frameRequest(m_originDocument.securityOrigin(), resourceRequest, lockHistory(), lockBackForwardList(), MaybeSendReferrer, AllowNavigationToInvalidURL::Yes, NewFrameOpenerPolicy::Allow, shouldOpenExternalURLs());
319         frameRequest.setSubstituteData(replacementData);
320         frame.loader().load(frameRequest);
321     }
322
323 private:
324     Document& m_originDocument;
325 };
326
327 NavigationScheduler::NavigationScheduler(Frame& frame)
328     : m_frame(frame)
329     , m_timer(*this, &NavigationScheduler::timerFired)
330 {
331 }
332
333 NavigationScheduler::~NavigationScheduler()
334 {
335 }
336
337 bool NavigationScheduler::redirectScheduledDuringLoad()
338 {
339     return m_redirect && m_redirect->wasDuringLoad();
340 }
341
342 bool NavigationScheduler::locationChangePending()
343 {
344     return m_redirect && m_redirect->isLocationChange();
345 }
346
347 void NavigationScheduler::clear()
348 {
349     if (m_timer.isActive())
350         InspectorInstrumentation::frameClearedScheduledNavigation(m_frame);
351     m_timer.stop();
352     m_redirect = nullptr;
353 }
354
355 inline bool NavigationScheduler::shouldScheduleNavigation() const
356 {
357     return m_frame.page();
358 }
359
360 inline bool NavigationScheduler::shouldScheduleNavigation(const URL& url) const
361 {
362     if (!shouldScheduleNavigation())
363         return false;
364     if (protocolIsJavaScript(url))
365         return true;
366     return NavigationDisablerForBeforeUnload::isNavigationAllowed();
367 }
368
369 void NavigationScheduler::scheduleRedirect(Document& initiatingDocument, double delay, const URL& url)
370 {
371     if (!shouldScheduleNavigation(url))
372         return;
373     if (delay < 0 || delay > INT_MAX / 1000)
374         return;
375     if (url.isEmpty())
376         return;
377
378     // We want a new back/forward list item if the refresh timeout is > 1 second.
379     if (!m_redirect || delay <= m_redirect->delay()) {
380         auto lockBackForwardList = delay <= 1 ? LockBackForwardList::Yes : LockBackForwardList::No;
381         schedule(std::make_unique<ScheduledRedirect>(initiatingDocument, delay, m_frame.document()->securityOrigin(), url, LockHistory::Yes, lockBackForwardList));
382     }
383 }
384
385 LockBackForwardList NavigationScheduler::mustLockBackForwardList(Frame& targetFrame)
386 {
387     // Non-user navigation before the page has finished firing onload should not create a new back/forward item.
388     // See https://webkit.org/b/42861 for the original motivation for this.    
389     if (!UserGestureIndicator::processingUserGesture() && targetFrame.loader().documentLoader() && !targetFrame.loader().documentLoader()->wasOnloadDispatched())
390         return LockBackForwardList::Yes;
391     
392     // Navigation of a subframe during loading of an ancestor frame does not create a new back/forward item.
393     // The definition of "during load" is any time before all handlers for the load event have been run.
394     // See https://bugs.webkit.org/show_bug.cgi?id=14957 for the original motivation for this.
395     for (Frame* ancestor = targetFrame.tree().parent(); ancestor; ancestor = ancestor->tree().parent()) {
396         Document* document = ancestor->document();
397         if (!ancestor->loader().isComplete() || (document && document->processingLoadEvent()))
398             return LockBackForwardList::Yes;
399     }
400     return LockBackForwardList::No;
401 }
402
403 void NavigationScheduler::scheduleLocationChange(Document& initiatingDocument, SecurityOrigin* securityOrigin, const URL& url, const String& referrer, LockHistory lockHistory, LockBackForwardList lockBackForwardList)
404 {
405     if (!shouldScheduleNavigation(url))
406         return;
407
408     if (lockBackForwardList == LockBackForwardList::No)
409         lockBackForwardList = mustLockBackForwardList(m_frame);
410
411     FrameLoader& loader = m_frame.loader();
412
413     // If the URL we're going to navigate to is the same as the current one, except for the
414     // fragment part, we don't need to schedule the location change.
415     if (url.hasFragmentIdentifier() && equalIgnoringFragmentIdentifier(m_frame.document()->url(), url)) {
416         ResourceRequest resourceRequest(m_frame.document()->completeURL(url), referrer, UseProtocolCachePolicy);
417         FrameLoadRequest frameRequest(securityOrigin, resourceRequest, "_self", lockHistory, lockBackForwardList, MaybeSendReferrer, AllowNavigationToInvalidURL::No, NewFrameOpenerPolicy::Allow, ReplaceDocumentIfJavaScriptURL, initiatingDocument.shouldOpenExternalURLsPolicyToPropagate());
418         loader.changeLocation(frameRequest);
419         return;
420     }
421
422     // Handle a location change of a page with no document as a special case.
423     // This may happen when a frame changes the location of another frame.
424     bool duringLoad = !loader.stateMachine().committedFirstRealDocumentLoad();
425
426     schedule(std::make_unique<ScheduledLocationChange>(initiatingDocument, securityOrigin, url, referrer, lockHistory, lockBackForwardList, duringLoad));
427 }
428
429 void NavigationScheduler::scheduleFormSubmission(Ref<FormSubmission>&& submission)
430 {
431     ASSERT(m_frame.page());
432
433     // FIXME: Do we need special handling for form submissions where the URL is the same
434     // as the current one except for the fragment part? See scheduleLocationChange above.
435
436     // Handle a location change of a page with no document as a special case.
437     // This may happen when a frame changes the location of another frame.
438     bool duringLoad = !m_frame.loader().stateMachine().committedFirstRealDocumentLoad();
439
440     // If this is a child frame and the form submission was triggered by a script, lock the back/forward list
441     // to match IE and Opera.
442     // See https://bugs.webkit.org/show_bug.cgi?id=32383 for the original motivation for this.
443     LockBackForwardList lockBackForwardList = mustLockBackForwardList(m_frame);
444     if (lockBackForwardList == LockBackForwardList::No
445         && (submission->state().formSubmissionTrigger() == SubmittedByJavaScript && m_frame.tree().parent() && !UserGestureIndicator::processingUserGesture())) {
446         lockBackForwardList = LockBackForwardList::Yes;
447     }
448     
449     schedule(std::make_unique<ScheduledFormSubmission>(WTFMove(submission), lockBackForwardList, duringLoad));
450 }
451
452 void NavigationScheduler::scheduleRefresh(Document& initiatingDocument)
453 {
454     if (!shouldScheduleNavigation())
455         return;
456     const URL& url = m_frame.document()->url();
457     if (url.isEmpty())
458         return;
459
460     schedule(std::make_unique<ScheduledRefresh>(initiatingDocument, m_frame.document()->securityOrigin(), url, m_frame.loader().outgoingReferrer()));
461 }
462
463 void NavigationScheduler::scheduleHistoryNavigation(int steps)
464 {
465     if (!shouldScheduleNavigation())
466         return;
467
468     // Invalid history navigations (such as history.forward() during a new load) have the side effect of cancelling any scheduled
469     // redirects. We also avoid the possibility of cancelling the current load by avoiding the scheduled redirection altogether.
470     BackForwardController& backForward = m_frame.page()->backForward();
471     if (steps > backForward.forwardCount() || -steps > backForward.backCount()) {
472         cancel();
473         return;
474     }
475
476     // In all other cases, schedule the history traversal to occur asynchronously.
477     schedule(std::make_unique<ScheduledHistoryNavigation>(steps));
478 }
479
480 void NavigationScheduler::schedulePageBlock(Document& originDocument)
481 {
482     if (shouldScheduleNavigation())
483         schedule(std::make_unique<ScheduledPageBlock>(originDocument));
484 }
485
486 void NavigationScheduler::timerFired()
487 {
488     if (!m_frame.page())
489         return;
490     if (m_frame.page()->defersLoading()) {
491         InspectorInstrumentation::frameClearedScheduledNavigation(m_frame);
492         return;
493     }
494
495     Ref<Frame> protect(m_frame);
496
497     std::unique_ptr<ScheduledNavigation> redirect = WTFMove(m_redirect);
498     redirect->fire(m_frame);
499     InspectorInstrumentation::frameClearedScheduledNavigation(m_frame);
500 }
501
502 void NavigationScheduler::schedule(std::unique_ptr<ScheduledNavigation> redirect)
503 {
504     ASSERT(m_frame.page());
505
506     Ref<Frame> protect(m_frame);
507
508     // If a redirect was scheduled during a load, then stop the current load.
509     // Otherwise when the current load transitions from a provisional to a 
510     // committed state, pending redirects may be cancelled. 
511     if (redirect->wasDuringLoad()) {
512         if (DocumentLoader* provisionalDocumentLoader = m_frame.loader().provisionalDocumentLoader())
513             provisionalDocumentLoader->stopLoading();
514         m_frame.loader().stopLoading(UnloadEventPolicyUnloadAndPageHide);
515     }
516
517     cancel();
518     m_redirect = WTFMove(redirect);
519
520     if (!m_frame.loader().isComplete() && m_redirect->isLocationChange())
521         m_frame.loader().completed();
522
523     if (!m_frame.page())
524         return;
525
526     startTimer();
527 }
528
529 void NavigationScheduler::startTimer()
530 {
531     if (!m_redirect)
532         return;
533
534     ASSERT(m_frame.page());
535     if (m_timer.isActive())
536         return;
537     if (!m_redirect->shouldStartTimer(m_frame))
538         return;
539
540     double delay = m_redirect->delay();
541     m_timer.startOneShot(delay);
542     InspectorInstrumentation::frameScheduledNavigation(m_frame, delay);
543     m_redirect->didStartTimer(m_frame, m_timer); // m_redirect may be null on return (e.g. the client canceled the load)
544 }
545
546 void NavigationScheduler::cancel(bool newLoadInProgress)
547 {
548     if (m_timer.isActive())
549         InspectorInstrumentation::frameClearedScheduledNavigation(m_frame);
550     m_timer.stop();
551
552     if (std::unique_ptr<ScheduledNavigation> redirect = WTFMove(m_redirect))
553         redirect->didStopTimer(m_frame, newLoadInProgress);
554 }
555
556 } // namespace WebCore