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