2009-11-10 Zoltan Horvath <zoltan@webkit.org>
[WebKit-https.git] / WebCore / loader / RedirectScheduler.cpp
1 /*
2  * Copyright (C) 2006, 2007, 2008, 2009 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 "RedirectScheduler.h"
34
35 #include "DocumentLoader.h"
36 #include "Event.h"
37 #include "FormState.h"
38 #include "Frame.h"
39 #include "FrameLoadRequest.h"
40 #include "FrameLoader.h"
41 #include "HTMLFormElement.h"
42 #include "Page.h"
43 #include <wtf/CurrentTime.h>
44
45 namespace WebCore {
46
47 struct ScheduledRedirection : Noncopyable {
48     enum Type { redirection, locationChange, historyNavigation, formSubmission };
49
50     const Type type;
51     const double delay;
52     const String url;
53     const String referrer;
54     const FrameLoadRequest frameRequest;
55     const RefPtr<Event> event;
56     const RefPtr<FormState> formState;
57     const int historySteps;
58     const bool lockHistory;
59     const bool lockBackForwardList;
60     const bool wasUserGesture;
61     const bool wasRefresh;
62     const bool wasDuringLoad;
63     bool toldClient;
64
65     ScheduledRedirection(double delay, const String& url, bool lockHistory, bool lockBackForwardList, bool wasUserGesture, bool refresh)
66         : type(redirection)
67         , delay(delay)
68         , url(url)
69         , historySteps(0)
70         , lockHistory(lockHistory)
71         , lockBackForwardList(lockBackForwardList)
72         , wasUserGesture(wasUserGesture)
73         , wasRefresh(refresh)
74         , wasDuringLoad(false)
75         , toldClient(false)
76     {
77         ASSERT(!url.isEmpty());
78     }
79
80     ScheduledRedirection(const String& url, const String& referrer, bool lockHistory, bool lockBackForwardList, bool wasUserGesture, bool refresh, bool duringLoad)
81         : type(locationChange)
82         , delay(0)
83         , url(url)
84         , referrer(referrer)
85         , historySteps(0)
86         , lockHistory(lockHistory)
87         , lockBackForwardList(lockBackForwardList)
88         , wasUserGesture(wasUserGesture)
89         , wasRefresh(refresh)
90         , wasDuringLoad(duringLoad)
91         , toldClient(false)
92     {
93         ASSERT(!url.isEmpty());
94     }
95
96     explicit ScheduledRedirection(int historyNavigationSteps)
97         : type(historyNavigation)
98         , delay(0)
99         , historySteps(historyNavigationSteps)
100         , lockHistory(false)
101         , lockBackForwardList(false)
102         , wasUserGesture(false)
103         , wasRefresh(false)
104         , wasDuringLoad(false)
105         , toldClient(false)
106     {
107     }
108
109     ScheduledRedirection(const FrameLoadRequest& frameRequest,
110             bool lockHistory, bool lockBackForwardList, PassRefPtr<Event> event, PassRefPtr<FormState> formState,
111             bool duringLoad)
112         : type(formSubmission)
113         , delay(0)
114         , frameRequest(frameRequest)
115         , event(event)
116         , formState(formState)
117         , historySteps(0)
118         , lockHistory(lockHistory)
119         , lockBackForwardList(lockBackForwardList)
120         , wasUserGesture(false)
121         , wasRefresh(false)
122         , wasDuringLoad(duringLoad)
123         , toldClient(false)
124     {
125         ASSERT(!frameRequest.isEmpty());
126         ASSERT(this->formState);
127     }
128 };
129
130 RedirectScheduler::RedirectScheduler(Frame* frame)
131     : m_frame(frame)
132     , m_timer(this, &RedirectScheduler::timerFired)
133 {
134 }
135
136 RedirectScheduler::~RedirectScheduler()
137 {
138 }
139
140 bool RedirectScheduler::redirectScheduledDuringLoad()
141 {
142     return m_scheduledRedirection && m_scheduledRedirection->wasDuringLoad;
143 }
144
145 void RedirectScheduler::clear()
146 {
147     m_timer.stop();
148     m_scheduledRedirection.clear();
149 }
150
151 void RedirectScheduler::scheduleRedirect(double delay, const String& url)
152 {
153     if (delay < 0 || delay > INT_MAX / 1000)
154         return;
155         
156     if (!m_frame->page())
157         return;
158
159     if (url.isEmpty())
160         return;
161
162     // We want a new history item if the refresh timeout is > 1 second.
163     if (!m_scheduledRedirection || delay <= m_scheduledRedirection->delay)
164         schedule(new ScheduledRedirection(delay, url, true, delay <= 1, false, false));
165 }
166
167 bool RedirectScheduler::mustLockBackForwardList(Frame* targetFrame)
168 {
169     // Navigation of a subframe during loading of an ancestor frame does not create a new back/forward item.
170     // The definition of "during load" is any time before all handlers for the load event have been run.
171     // See https://bugs.webkit.org/show_bug.cgi?id=14957 for the original motivation for this.
172     
173     for (Frame* ancestor = targetFrame->tree()->parent(); ancestor; ancestor = ancestor->tree()->parent()) {
174         Document* document = ancestor->document();
175         if (!ancestor->loader()->isComplete() || (document && document->processingLoadEvent()))
176             return true;
177     }
178     return false;
179 }
180
181 void RedirectScheduler::scheduleLocationChange(const String& url, const String& referrer, bool lockHistory, bool lockBackForwardList, bool wasUserGesture)
182 {
183     if (!m_frame->page())
184         return;
185
186     if (url.isEmpty())
187         return;
188
189     lockBackForwardList = lockBackForwardList || mustLockBackForwardList(m_frame);
190
191     FrameLoader* loader = m_frame->loader();
192     
193     // If the URL we're going to navigate to is the same as the current one, except for the
194     // fragment part, we don't need to schedule the location change.
195     KURL parsedURL(ParsedURLString, url);
196     if (parsedURL.hasFragmentIdentifier() && equalIgnoringFragmentIdentifier(loader->url(), parsedURL)) {
197         loader->changeLocation(loader->completeURL(url), referrer, lockHistory, lockBackForwardList, wasUserGesture);
198         return;
199     }
200
201     // Handle a location change of a page with no document as a special case.
202     // This may happen when a frame changes the location of another frame.
203     bool duringLoad = !loader->committedFirstRealDocumentLoad();
204
205     schedule(new ScheduledRedirection(url, referrer, lockHistory, lockBackForwardList, wasUserGesture, false, duringLoad));
206 }
207
208 void RedirectScheduler::scheduleFormSubmission(const FrameLoadRequest& frameRequest,
209     bool lockHistory, PassRefPtr<Event> event, PassRefPtr<FormState> formState)
210 {
211     ASSERT(m_frame->page());
212     ASSERT(!frameRequest.isEmpty());
213
214     // FIXME: Do we need special handling for form submissions where the URL is the same
215     // as the current one except for the fragment part? See scheduleLocationChange above.
216
217     // Handle a location change of a page with no document as a special case.
218     // This may happen when a frame changes the location of another frame.
219     bool duringLoad = !m_frame->loader()->committedFirstRealDocumentLoad();
220
221     schedule(new ScheduledRedirection(frameRequest, lockHistory, mustLockBackForwardList(m_frame), event, formState, duringLoad));
222 }
223
224 void RedirectScheduler::scheduleRefresh(bool wasUserGesture)
225 {
226     if (!m_frame->page())
227         return;
228     
229     const KURL& url = m_frame->loader()->url();
230
231     if (url.isEmpty())
232         return;
233
234     schedule(new ScheduledRedirection(url.string(), m_frame->loader()->outgoingReferrer(), true, true, wasUserGesture, true, false));
235 }
236
237 bool RedirectScheduler::locationChangePending()
238 {
239     if (!m_scheduledRedirection)
240         return false;
241
242     switch (m_scheduledRedirection->type) {
243         case ScheduledRedirection::redirection:
244             return false;
245         case ScheduledRedirection::historyNavigation:
246         case ScheduledRedirection::locationChange:
247         case ScheduledRedirection::formSubmission:
248             return true;
249     }
250     ASSERT_NOT_REACHED();
251     return false;
252 }
253
254 void RedirectScheduler::scheduleHistoryNavigation(int steps)
255 {
256     if (!m_frame->page())
257         return;
258
259     // Invalid history navigations (such as history.forward() during a new load) have the side effect of cancelling any scheduled
260     // redirects. We also avoid the possibility of cancelling the current load by avoiding the scheduled redirection altogether.
261     if (!m_frame->page()->canGoBackOrForward(steps)) { 
262         cancel(); 
263         return; 
264     } 
265
266     schedule(new ScheduledRedirection(steps));
267 }
268
269 void RedirectScheduler::timerFired(Timer<RedirectScheduler>*)
270 {
271     if (!m_frame->page())
272         return;
273
274     if (m_frame->page()->defersLoading())
275         return;
276
277     OwnPtr<ScheduledRedirection> redirection(m_scheduledRedirection.release());
278     FrameLoader* loader = m_frame->loader();
279
280     switch (redirection->type) {
281         case ScheduledRedirection::redirection:
282         case ScheduledRedirection::locationChange:
283             loader->changeLocation(KURL(ParsedURLString, redirection->url), redirection->referrer,
284                 redirection->lockHistory, redirection->lockBackForwardList, redirection->wasUserGesture, redirection->wasRefresh);
285             return;
286         case ScheduledRedirection::historyNavigation:
287             if (redirection->historySteps == 0) {
288                 // Special case for go(0) from a frame -> reload only the frame
289                 loader->urlSelected(loader->url(), "", 0, redirection->lockHistory, redirection->lockBackForwardList, redirection->wasUserGesture, SendReferrer);
290                 return;
291             }
292             // go(i!=0) from a frame navigates into the history of the frame only,
293             // in both IE and NS (but not in Mozilla). We can't easily do that.
294             m_frame->page()->goBackOrForward(redirection->historySteps);
295             return;
296         case ScheduledRedirection::formSubmission:
297             // The submitForm function will find a target frame before using the redirection timer.
298             // Now that the timer has fired, we need to repeat the security check which normally is done when
299             // selecting a target, in case conditions have changed. Other code paths avoid this by targeting
300             // without leaving a time window. If we fail the check just silently drop the form submission.
301             if (!redirection->formState->sourceFrame()->loader()->shouldAllowNavigation(m_frame))
302                 return;
303             loader->loadFrameRequest(redirection->frameRequest, redirection->lockHistory, redirection->lockBackForwardList,
304                 redirection->event, redirection->formState, SendReferrer);
305             return;
306     }
307
308     ASSERT_NOT_REACHED();
309 }
310
311 void RedirectScheduler::schedule(PassOwnPtr<ScheduledRedirection> redirection)
312 {
313     ASSERT(m_frame->page());
314     FrameLoader* loader = m_frame->loader();
315
316     // If a redirect was scheduled during a load, then stop the current load.
317     // Otherwise when the current load transitions from a provisional to a 
318     // committed state, pending redirects may be cancelled. 
319     if (redirection->wasDuringLoad) {
320         if (DocumentLoader* provisionalDocumentLoader = loader->provisionalDocumentLoader())
321             provisionalDocumentLoader->stopLoading();
322         loader->stopLoading(UnloadEventPolicyUnloadAndPageHide);   
323     }
324
325     cancel();
326     m_scheduledRedirection = redirection;
327     if (!loader->isComplete() && m_scheduledRedirection->type != ScheduledRedirection::redirection)
328         loader->completed();
329     startTimer();
330 }
331
332 void RedirectScheduler::startTimer()
333 {
334     if (!m_scheduledRedirection)
335         return;
336
337     ASSERT(m_frame->page());
338     
339     FrameLoader* loader = m_frame->loader();
340
341     if (m_timer.isActive())
342         return;
343
344     if (m_scheduledRedirection->type == ScheduledRedirection::redirection && !loader->allAncestorsAreComplete())
345         return;
346
347     m_timer.startOneShot(m_scheduledRedirection->delay);
348
349     switch (m_scheduledRedirection->type) {
350         case ScheduledRedirection::locationChange:
351         case ScheduledRedirection::redirection:
352             if (m_scheduledRedirection->toldClient)
353                 return;
354             m_scheduledRedirection->toldClient = true;
355             loader->clientRedirected(KURL(ParsedURLString, m_scheduledRedirection->url),
356                 m_scheduledRedirection->delay,
357                 currentTime() + m_timer.nextFireInterval(),
358                 m_scheduledRedirection->lockBackForwardList);
359             return;
360         case ScheduledRedirection::formSubmission:
361             // FIXME: It would make sense to report form submissions as client redirects too.
362             // But we didn't do that in the past when form submission used a separate delay
363             // mechanism, so doing it will be a behavior change.
364             return;
365         case ScheduledRedirection::historyNavigation:
366             // Don't report history navigations.
367             return;
368     }
369     ASSERT_NOT_REACHED();
370 }
371
372 void RedirectScheduler::cancel(bool newLoadInProgress)
373 {
374     m_timer.stop();
375
376     OwnPtr<ScheduledRedirection> redirection(m_scheduledRedirection.release());
377     if (redirection && redirection->toldClient)
378         m_frame->loader()->clientRedirectCancelledOrFinished(newLoadInProgress);
379 }
380
381 } // namespace WebCore
382