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