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