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.
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
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.
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.
33 #include "RedirectScheduler.h"
35 #include "DocumentLoader.h"
37 #include "FormState.h"
39 #include "FrameLoadRequest.h"
40 #include "FrameLoader.h"
41 #include "HTMLFormElement.h"
42 #include <wtf/CurrentTime.h>
46 struct ScheduledRedirection {
47 enum Type { redirection, locationChange, historyNavigation, formSubmission };
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;
64 ScheduledRedirection(double delay, const String& url, bool lockHistory, bool lockBackForwardList, bool wasUserGesture, bool refresh)
69 , lockHistory(lockHistory)
70 , lockBackForwardList(lockBackForwardList)
71 , wasUserGesture(wasUserGesture)
73 , wasDuringLoad(false)
76 ASSERT(!url.isEmpty());
79 ScheduledRedirection(const String& url, const String& referrer, bool lockHistory, bool lockBackForwardList, bool wasUserGesture, bool refresh, bool duringLoad)
80 : type(locationChange)
85 , lockHistory(lockHistory)
86 , lockBackForwardList(lockBackForwardList)
87 , wasUserGesture(wasUserGesture)
89 , wasDuringLoad(duringLoad)
92 ASSERT(!url.isEmpty());
95 explicit ScheduledRedirection(int historyNavigationSteps)
96 : type(historyNavigation)
98 , historySteps(historyNavigationSteps)
100 , lockBackForwardList(false)
101 , wasUserGesture(false)
103 , wasDuringLoad(false)
108 ScheduledRedirection(const FrameLoadRequest& frameRequest,
109 bool lockHistory, bool lockBackForwardList, PassRefPtr<Event> event, PassRefPtr<FormState> formState,
111 : type(formSubmission)
113 , frameRequest(frameRequest)
115 , formState(formState)
117 , lockHistory(lockHistory)
118 , lockBackForwardList(lockBackForwardList)
119 , wasUserGesture(false)
121 , wasDuringLoad(duringLoad)
124 ASSERT(!frameRequest.isEmpty());
125 ASSERT(this->formState);
129 RedirectScheduler::RedirectScheduler(Frame* frame)
131 , m_timer(this, &RedirectScheduler::timerFired)
135 RedirectScheduler::~RedirectScheduler()
139 bool RedirectScheduler::redirectScheduledDuringLoad()
141 return m_scheduledRedirection && m_scheduledRedirection->wasDuringLoad;
144 void RedirectScheduler::clear()
147 m_scheduledRedirection.clear();
150 void RedirectScheduler::scheduleRedirect(double delay, const String& url)
152 if (delay < 0 || delay > INT_MAX / 1000)
155 if (!m_frame->page())
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));
166 bool RedirectScheduler::mustLockBackForwardList(Frame* targetFrame)
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.
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())
180 void RedirectScheduler::scheduleLocationChange(const String& url, const String& referrer, bool lockHistory, bool lockBackForwardList, bool wasUserGesture)
182 if (!m_frame->page())
188 lockBackForwardList = lockBackForwardList || mustLockBackForwardList(m_frame);
190 FrameLoader* loader = m_frame->loader();
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);
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();
204 schedule(new ScheduledRedirection(url, referrer, lockHistory, lockBackForwardList, wasUserGesture, false, duringLoad));
207 void RedirectScheduler::scheduleFormSubmission(const FrameLoadRequest& frameRequest,
208 bool lockHistory, PassRefPtr<Event> event, PassRefPtr<FormState> formState)
210 ASSERT(m_frame->page());
211 ASSERT(!frameRequest.isEmpty());
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.
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();
220 schedule(new ScheduledRedirection(frameRequest, lockHistory, mustLockBackForwardList(m_frame), event, formState, duringLoad));
223 void RedirectScheduler::scheduleRefresh(bool wasUserGesture)
225 if (!m_frame->page())
228 const KURL& url = m_frame->loader()->url();
233 schedule(new ScheduledRedirection(url.string(), m_frame->loader()->outgoingReferrer(), true, true, wasUserGesture, true, false));
236 bool RedirectScheduler::locationChangePending()
238 if (!m_scheduledRedirection)
241 switch (m_scheduledRedirection->type) {
242 case ScheduledRedirection::redirection:
244 case ScheduledRedirection::historyNavigation:
245 case ScheduledRedirection::locationChange:
246 case ScheduledRedirection::formSubmission:
249 ASSERT_NOT_REACHED();
253 void RedirectScheduler::scheduleHistoryNavigation(int steps)
255 if (!m_frame->page())
258 schedule(new ScheduledRedirection(steps));
261 void RedirectScheduler::timerFired(Timer<RedirectScheduler>*)
263 ASSERT(m_frame->page());
265 if (m_frame->page()->defersLoading())
268 OwnPtr<ScheduledRedirection> redirection(m_scheduledRedirection.release());
269 FrameLoader* loader = m_frame->loader();
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);
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);
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);
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))
295 loader->loadFrameRequest(redirection->frameRequest, redirection->lockHistory, redirection->lockBackForwardList,
296 redirection->event, redirection->formState);
300 ASSERT_NOT_REACHED();
303 void RedirectScheduler::schedule(PassOwnPtr<ScheduledRedirection> redirection)
305 ASSERT(m_frame->page());
306 FrameLoader* loader = m_frame->loader();
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);
318 m_scheduledRedirection = redirection;
319 if (!loader->isComplete() && m_scheduledRedirection->type != ScheduledRedirection::redirection)
324 void RedirectScheduler::startTimer()
326 if (!m_scheduledRedirection)
329 ASSERT(m_frame->page());
331 FrameLoader* loader = m_frame->loader();
333 if (m_timer.isActive())
336 if (m_scheduledRedirection->type == ScheduledRedirection::redirection && !loader->allAncestorsAreComplete())
339 m_timer.startOneShot(m_scheduledRedirection->delay);
341 switch (m_scheduledRedirection->type) {
342 case ScheduledRedirection::locationChange:
343 case ScheduledRedirection::redirection:
344 if (m_scheduledRedirection->toldClient)
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);
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.
357 case ScheduledRedirection::historyNavigation:
358 // Don't report history navigations.
361 ASSERT_NOT_REACHED();
364 void RedirectScheduler::cancel(bool newLoadInProgress)
368 OwnPtr<ScheduledRedirection> redirection(m_scheduledRedirection.release());
369 if (redirection && redirection->toldClient)
370 m_frame->loader()->clientRedirectCancelledOrFinished(newLoadInProgress);
373 } // namespace WebCore