Replace WTF::move with WTFMove
[WebKit-https.git] / Source / WebCore / loader / NavigationScheduler.cpp
1 /*
2  * Copyright (C) 2006, 2007, 2008, 2009, 2010 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 ScheduledSubstituteDataLoad : public ScheduledNavigation {
302 public:
303     ScheduledSubstituteDataLoad(const URL& baseURL, const SubstituteData& substituteData)
304         : ScheduledNavigation { 0, LockHistory::No, LockBackForwardList::No, false, false }
305         , m_baseURL { baseURL }
306         , m_substituteData { substituteData }
307     {
308     }
309
310     void fire(Frame& frame) override
311     {
312         UserGestureIndicator gestureIndicator { wasUserGesture() ? DefinitelyProcessingUserGesture : DefinitelyNotProcessingUserGesture };
313         frame.loader().load(FrameLoadRequest { &frame, m_baseURL, m_shouldOpenExternalURLsPolicy, m_substituteData });
314     }
315
316 private:
317     URL m_baseURL;
318     SubstituteData m_substituteData;
319 };
320
321 NavigationScheduler::NavigationScheduler(Frame& frame)
322     : m_frame(frame)
323     , m_timer(*this, &NavigationScheduler::timerFired)
324 {
325 }
326
327 NavigationScheduler::~NavigationScheduler()
328 {
329 }
330
331 bool NavigationScheduler::redirectScheduledDuringLoad()
332 {
333     return m_redirect && m_redirect->wasDuringLoad();
334 }
335
336 bool NavigationScheduler::locationChangePending()
337 {
338     return m_redirect && m_redirect->isLocationChange();
339 }
340
341 void NavigationScheduler::clear()
342 {
343     if (m_timer.isActive())
344         InspectorInstrumentation::frameClearedScheduledNavigation(m_frame);
345     m_timer.stop();
346     m_redirect = nullptr;
347 }
348
349 inline bool NavigationScheduler::shouldScheduleNavigation() const
350 {
351     return m_frame.page();
352 }
353
354 inline bool NavigationScheduler::shouldScheduleNavigation(const URL& url) const
355 {
356     if (!shouldScheduleNavigation())
357         return false;
358     if (protocolIsJavaScript(url))
359         return true;
360     return NavigationDisablerForBeforeUnload::isNavigationAllowed();
361 }
362
363 void NavigationScheduler::scheduleRedirect(Document* initiatingDocument, double delay, const URL& url)
364 {
365     if (!shouldScheduleNavigation(url))
366         return;
367     if (delay < 0 || delay > INT_MAX / 1000)
368         return;
369     if (url.isEmpty())
370         return;
371
372     // We want a new back/forward list item if the refresh timeout is > 1 second.
373     if (!m_redirect || delay <= m_redirect->delay()) {
374         LockBackForwardList lockBackForwardList = delay <= 1 ? LockBackForwardList::Yes : LockBackForwardList::No;
375         schedule(std::make_unique<ScheduledRedirect>(initiatingDocument, delay, m_frame.document()->securityOrigin(), url, LockHistory::Yes, lockBackForwardList));
376     }
377 }
378
379 LockBackForwardList NavigationScheduler::mustLockBackForwardList(Frame& targetFrame)
380 {
381     // Non-user navigation before the page has finished firing onload should not create a new back/forward item.
382     // See https://webkit.org/b/42861 for the original motivation for this.    
383     if (!ScriptController::processingUserGesture() && targetFrame.loader().documentLoader() && !targetFrame.loader().documentLoader()->wasOnloadDispatched())
384         return LockBackForwardList::Yes;
385     
386     // Navigation of a subframe during loading of an ancestor frame does not create a new back/forward item.
387     // The definition of "during load" is any time before all handlers for the load event have been run.
388     // See https://bugs.webkit.org/show_bug.cgi?id=14957 for the original motivation for this.
389     for (Frame* ancestor = targetFrame.tree().parent(); ancestor; ancestor = ancestor->tree().parent()) {
390         Document* document = ancestor->document();
391         if (!ancestor->loader().isComplete() || (document && document->processingLoadEvent()))
392             return LockBackForwardList::Yes;
393     }
394     return LockBackForwardList::No;
395 }
396
397 void NavigationScheduler::scheduleLocationChange(Document* initiatingDocument, SecurityOrigin* securityOrigin, const URL& url, const String& referrer, LockHistory lockHistory, LockBackForwardList lockBackForwardList)
398 {
399     if (!shouldScheduleNavigation(url))
400         return;
401
402     if (lockBackForwardList == LockBackForwardList::No)
403         lockBackForwardList = mustLockBackForwardList(m_frame);
404
405     FrameLoader& loader = m_frame.loader();
406
407     // If the URL we're going to navigate to is the same as the current one, except for the
408     // fragment part, we don't need to schedule the location change.
409     if (url.hasFragmentIdentifier() && equalIgnoringFragmentIdentifier(m_frame.document()->url(), url)) {
410         ResourceRequest resourceRequest(m_frame.document()->completeURL(url), referrer, UseProtocolCachePolicy);
411         ShouldOpenExternalURLsPolicy shouldOpenExternalURLsPolicy = ShouldOpenExternalURLsPolicy::ShouldNotAllow;
412         if (initiatingDocument)
413             shouldOpenExternalURLsPolicy = initiatingDocument->shouldOpenExternalURLsPolicyToPropagate();
414         FrameLoadRequest frameRequest(securityOrigin, resourceRequest, "_self", lockHistory, lockBackForwardList, MaybeSendReferrer, AllowNavigationToInvalidURL::No, NewFrameOpenerPolicy::Allow, ReplaceDocumentIfJavaScriptURL, shouldOpenExternalURLsPolicy);
415         loader.changeLocation(frameRequest);
416         return;
417     }
418
419     // Handle a location change of a page with no document as a special case.
420     // This may happen when a frame changes the location of another frame.
421     bool duringLoad = !loader.stateMachine().committedFirstRealDocumentLoad();
422
423     schedule(std::make_unique<ScheduledLocationChange>(initiatingDocument, securityOrigin, url, referrer, lockHistory, lockBackForwardList, duringLoad));
424 }
425
426 void NavigationScheduler::scheduleFormSubmission(PassRefPtr<FormSubmission> submission)
427 {
428     ASSERT(m_frame.page());
429
430     // FIXME: Do we need special handling for form submissions where the URL is the same
431     // as the current one except for the fragment part? See scheduleLocationChange above.
432
433     // Handle a location change of a page with no document as a special case.
434     // This may happen when a frame changes the location of another frame.
435     bool duringLoad = !m_frame.loader().stateMachine().committedFirstRealDocumentLoad();
436
437     // If this is a child frame and the form submission was triggered by a script, lock the back/forward list
438     // to match IE and Opera.
439     // See https://bugs.webkit.org/show_bug.cgi?id=32383 for the original motivation for this.
440     LockBackForwardList lockBackForwardList = mustLockBackForwardList(m_frame);
441     if (lockBackForwardList == LockBackForwardList::No
442         && (submission->state()->formSubmissionTrigger() == SubmittedByJavaScript && m_frame.tree().parent() && !ScriptController::processingUserGesture())) {
443         lockBackForwardList = LockBackForwardList::Yes;
444     }
445     
446     schedule(std::make_unique<ScheduledFormSubmission>(submission, lockBackForwardList, duringLoad));
447 }
448
449 void NavigationScheduler::scheduleRefresh(Document* initiatingDocument)
450 {
451     if (!shouldScheduleNavigation())
452         return;
453     const URL& url = m_frame.document()->url();
454     if (url.isEmpty())
455         return;
456
457     schedule(std::make_unique<ScheduledRefresh>(initiatingDocument, m_frame.document()->securityOrigin(), url, m_frame.loader().outgoingReferrer()));
458 }
459
460 void NavigationScheduler::scheduleHistoryNavigation(int steps)
461 {
462     if (!shouldScheduleNavigation())
463         return;
464
465     // Invalid history navigations (such as history.forward() during a new load) have the side effect of cancelling any scheduled
466     // redirects. We also avoid the possibility of cancelling the current load by avoiding the scheduled redirection altogether.
467     BackForwardController& backForward = m_frame.page()->backForward();
468     if (steps > backForward.forwardCount() || -steps > backForward.backCount()) {
469         cancel();
470         return;
471     }
472
473     // In all other cases, schedule the history traversal to occur asynchronously.
474     schedule(std::make_unique<ScheduledHistoryNavigation>(steps));
475 }
476
477 void NavigationScheduler::scheduleSubstituteDataLoad(const URL& baseURL, const SubstituteData& substituteData)
478 {
479     if (shouldScheduleNavigation())
480         schedule(std::make_unique<ScheduledSubstituteDataLoad>(baseURL, substituteData));
481 }
482
483 void NavigationScheduler::timerFired()
484 {
485     if (!m_frame.page())
486         return;
487     if (m_frame.page()->defersLoading()) {
488         InspectorInstrumentation::frameClearedScheduledNavigation(m_frame);
489         return;
490     }
491
492     Ref<Frame> protect(m_frame);
493
494     std::unique_ptr<ScheduledNavigation> redirect = WTFMove(m_redirect);
495     redirect->fire(m_frame);
496     InspectorInstrumentation::frameClearedScheduledNavigation(m_frame);
497 }
498
499 void NavigationScheduler::schedule(std::unique_ptr<ScheduledNavigation> redirect)
500 {
501     ASSERT(m_frame.page());
502
503     Ref<Frame> protect(m_frame);
504
505     // If a redirect was scheduled during a load, then stop the current load.
506     // Otherwise when the current load transitions from a provisional to a 
507     // committed state, pending redirects may be cancelled. 
508     if (redirect->wasDuringLoad()) {
509         if (DocumentLoader* provisionalDocumentLoader = m_frame.loader().provisionalDocumentLoader())
510             provisionalDocumentLoader->stopLoading();
511         m_frame.loader().stopLoading(UnloadEventPolicyUnloadAndPageHide);
512     }
513
514     cancel();
515     m_redirect = WTFMove(redirect);
516
517     if (!m_frame.loader().isComplete() && m_redirect->isLocationChange())
518         m_frame.loader().completed();
519
520     if (!m_frame.page())
521         return;
522
523     startTimer();
524 }
525
526 void NavigationScheduler::startTimer()
527 {
528     if (!m_redirect)
529         return;
530
531     ASSERT(m_frame.page());
532     if (m_timer.isActive())
533         return;
534     if (!m_redirect->shouldStartTimer(m_frame))
535         return;
536
537     double delay = m_redirect->delay();
538     m_timer.startOneShot(delay);
539     InspectorInstrumentation::frameScheduledNavigation(m_frame, delay);
540     m_redirect->didStartTimer(m_frame, m_timer); // m_redirect may be null on return (e.g. the client canceled the load)
541 }
542
543 void NavigationScheduler::cancel(bool newLoadInProgress)
544 {
545     if (m_timer.isActive())
546         InspectorInstrumentation::frameClearedScheduledNavigation(m_frame);
547     m_timer.stop();
548
549     if (std::unique_ptr<ScheduledNavigation> redirect = WTFMove(m_redirect))
550         redirect->didStopTimer(m_frame, newLoadInProgress);
551 }
552
553 } // namespace WebCore