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