2 * Copyright (C) 2006, 2007, 2008, 2009, 2010 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 "core/loader/NavigationScheduler.h"
35 #include "bindings/core/v8/ScriptController.h"
36 #include "core/events/Event.h"
37 #include "core/fetch/ResourceLoaderOptions.h"
38 #include "core/frame/LocalFrame.h"
39 #include "core/frame/csp/ContentSecurityPolicy.h"
40 #include "core/html/HTMLFormElement.h"
41 #include "core/inspector/InspectorInstrumentation.h"
42 #include "core/loader/DocumentLoader.h"
43 #include "core/loader/FormState.h"
44 #include "core/loader/FormSubmission.h"
45 #include "core/loader/FrameLoadRequest.h"
46 #include "core/loader/FrameLoader.h"
47 #include "core/loader/FrameLoaderClient.h"
48 #include "core/loader/FrameLoaderStateMachine.h"
49 #include "core/page/Page.h"
50 #include "platform/SharedBuffer.h"
51 #include "platform/UserGestureIndicator.h"
52 #include "wtf/CurrentTime.h"
56 unsigned NavigationDisablerForBeforeUnload::s_navigationDisableCount = 0;
58 class ScheduledNavigation {
59 WTF_MAKE_NONCOPYABLE(ScheduledNavigation); WTF_MAKE_FAST_ALLOCATED;
61 ScheduledNavigation(double delay, bool lockBackForwardList, bool isLocationChange)
63 , m_lockBackForwardList(lockBackForwardList)
64 , m_isLocationChange(isLocationChange)
65 , m_wasUserGesture(UserGestureIndicator::processingUserGesture())
68 m_userGestureToken = UserGestureIndicator::currentToken();
70 virtual ~ScheduledNavigation() { }
72 virtual void fire(LocalFrame*) = 0;
74 virtual bool shouldStartTimer(LocalFrame*) { return true; }
76 double delay() const { return m_delay; }
77 bool lockBackForwardList() const { return m_lockBackForwardList; }
78 bool isLocationChange() const { return m_isLocationChange; }
79 PassOwnPtr<UserGestureIndicator> createUserGestureIndicator()
81 if (m_wasUserGesture && m_userGestureToken)
82 return adoptPtr(new UserGestureIndicator(m_userGestureToken));
83 return adoptPtr(new UserGestureIndicator(DefinitelyNotProcessingUserGesture));
87 void clearUserGesture() { m_wasUserGesture = false; }
91 bool m_lockBackForwardList;
92 bool m_isLocationChange;
93 bool m_wasUserGesture;
94 RefPtr<UserGestureToken> m_userGestureToken;
97 class ScheduledURLNavigation : public ScheduledNavigation {
99 ScheduledURLNavigation(double delay, Document* originDocument, const String& url, bool lockBackForwardList, bool isLocationChange)
100 : ScheduledNavigation(delay, lockBackForwardList, isLocationChange)
101 , m_originDocument(originDocument)
103 , m_shouldCheckMainWorldContentSecurityPolicy(CheckContentSecurityPolicy)
105 if (ContentSecurityPolicy::shouldBypassMainWorld(originDocument))
106 m_shouldCheckMainWorldContentSecurityPolicy = DoNotCheckContentSecurityPolicy;
109 virtual void fire(LocalFrame* frame) override
111 OwnPtr<UserGestureIndicator> gestureIndicator = createUserGestureIndicator();
112 FrameLoadRequest request(m_originDocument.get(), m_url, "_self", m_shouldCheckMainWorldContentSecurityPolicy);
113 request.setLockBackForwardList(lockBackForwardList());
114 request.setClientRedirect(ClientRedirect);
115 frame->loader().load(request);
118 Document* originDocument() const { return m_originDocument.get(); }
119 String url() const { return m_url; }
122 RefPtrWillBePersistent<Document> m_originDocument;
124 ContentSecurityPolicyCheck m_shouldCheckMainWorldContentSecurityPolicy;
127 class ScheduledRedirect final : public ScheduledURLNavigation {
129 ScheduledRedirect(double delay, Document* originDocument, const String& url, bool lockBackForwardList)
130 : ScheduledURLNavigation(delay, originDocument, url, lockBackForwardList, false)
135 virtual bool shouldStartTimer(LocalFrame* frame) override { return frame->loader().allAncestorsAreComplete(); }
137 virtual void fire(LocalFrame* frame) override
139 OwnPtr<UserGestureIndicator> gestureIndicator = createUserGestureIndicator();
140 FrameLoadRequest request(originDocument(), url(), "_self");
141 request.setLockBackForwardList(lockBackForwardList());
142 if (equalIgnoringFragmentIdentifier(frame->document()->url(), request.resourceRequest().url()))
143 request.resourceRequest().setCachePolicy(ReloadIgnoringCacheData);
144 request.setClientRedirect(ClientRedirect);
145 frame->loader().load(request);
149 class ScheduledLocationChange final : public ScheduledURLNavigation {
151 ScheduledLocationChange(Document* originDocument, const String& url, bool lockBackForwardList)
152 : ScheduledURLNavigation(0.0, originDocument, url, lockBackForwardList, !protocolIsJavaScript(url)) { }
155 class ScheduledReload final : public ScheduledNavigation {
158 : ScheduledNavigation(0.0, true, true)
162 virtual void fire(LocalFrame* frame) override
164 OwnPtr<UserGestureIndicator> gestureIndicator = createUserGestureIndicator();
165 frame->loader().reload(NormalReload, KURL(), ClientRedirect);
169 class ScheduledPageBlock final : public ScheduledURLNavigation {
171 ScheduledPageBlock(Document* originDocument, const String& url)
172 : ScheduledURLNavigation(0.0, originDocument, url, true, true)
176 virtual void fire(LocalFrame* frame) override
178 OwnPtr<UserGestureIndicator> gestureIndicator = createUserGestureIndicator();
179 SubstituteData substituteData(SharedBuffer::create(), "text/plain", "UTF-8", KURL(), ForceSynchronousLoad);
180 FrameLoadRequest request(originDocument(), url(), substituteData);
181 request.setLockBackForwardList(true);
182 request.setClientRedirect(ClientRedirect);
183 frame->loader().load(request);
187 class ScheduledFormSubmission final : public ScheduledNavigation {
189 ScheduledFormSubmission(PassRefPtrWillBeRawPtr<FormSubmission> submission, bool lockBackForwardList)
190 : ScheduledNavigation(0, lockBackForwardList, true)
191 , m_submission(submission)
193 ASSERT(m_submission->state());
196 virtual void fire(LocalFrame* frame) override
198 OwnPtr<UserGestureIndicator> gestureIndicator = createUserGestureIndicator();
199 FrameLoadRequest frameRequest(m_submission->state()->sourceDocument());
200 m_submission->populateFrameLoadRequest(frameRequest);
201 frameRequest.setLockBackForwardList(lockBackForwardList());
202 frameRequest.setTriggeringEvent(m_submission->event());
203 frameRequest.setFormState(m_submission->state());
204 frame->loader().load(frameRequest);
208 RefPtrWillBePersistent<FormSubmission> m_submission;
211 NavigationScheduler::NavigationScheduler(LocalFrame* frame)
213 , m_timer(this, &NavigationScheduler::timerFired)
217 NavigationScheduler::~NavigationScheduler()
221 bool NavigationScheduler::locationChangePending()
223 return m_redirect && m_redirect->isLocationChange();
226 inline bool NavigationScheduler::shouldScheduleNavigation() const
228 return m_frame->page();
231 inline bool NavigationScheduler::shouldScheduleNavigation(const String& url) const
233 return shouldScheduleNavigation() && (protocolIsJavaScript(url) || NavigationDisablerForBeforeUnload::isNavigationAllowed());
236 void NavigationScheduler::scheduleRedirect(double delay, const String& url)
238 if (!shouldScheduleNavigation(url))
240 if (delay < 0 || delay > INT_MAX / 1000)
245 // We want a new back/forward list item if the refresh timeout is > 1 second.
246 if (!m_redirect || delay <= m_redirect->delay())
247 schedule(adoptPtr(new ScheduledRedirect(delay, m_frame->document(), url, delay <= 1)));
250 bool NavigationScheduler::mustLockBackForwardList(LocalFrame* targetFrame)
252 // Non-user navigation before the page has finished firing onload should not create a new back/forward item.
253 // See https://webkit.org/b/42861 for the original motivation for this.
254 if (!UserGestureIndicator::processingUserGesture() && !targetFrame->document()->loadEventFinished())
257 // From the HTML5 spec for location.assign():
258 // "If the browsing context's session history contains only one Document,
259 // and that was the about:blank Document created when the browsing context
260 // was created, then the navigation must be done with replacement enabled."
261 if (!targetFrame->loader().stateMachine()->committedMultipleRealLoads()
262 && equalIgnoringCase(targetFrame->document()->url(), blankURL()))
265 // Navigation of a subframe during loading of an ancestor frame does not create a new back/forward item.
266 // The definition of "during load" is any time before all handlers for the load event have been run.
267 // See https://bugs.webkit.org/show_bug.cgi?id=14957 for the original motivation for this.
268 Frame* parentFrame = targetFrame->tree().parent();
269 return parentFrame && parentFrame->isLocalFrame() && !toLocalFrame(parentFrame)->loader().allAncestorsAreComplete();
272 void NavigationScheduler::scheduleLocationChange(Document* originDocument, const String& url, bool lockBackForwardList)
274 if (!shouldScheduleNavigation(url))
279 lockBackForwardList = lockBackForwardList || mustLockBackForwardList(m_frame);
281 // If the URL we're going to navigate to is the same as the current one, except for the
282 // fragment part, we don't need to schedule the location change. We'll skip this
283 // optimization for cross-origin navigations to minimize the navigator's ability to
284 // execute timing attacks.
285 if (originDocument->securityOrigin()->canAccess(m_frame->document()->securityOrigin())) {
286 KURL parsedURL(ParsedURLString, url);
287 if (parsedURL.hasFragmentIdentifier() && equalIgnoringFragmentIdentifier(m_frame->document()->url(), parsedURL)) {
288 FrameLoadRequest request(originDocument, m_frame->document()->completeURL(url), "_self");
289 request.setLockBackForwardList(lockBackForwardList);
290 if (lockBackForwardList)
291 request.setClientRedirect(ClientRedirect);
292 m_frame->loader().load(request);
297 schedule(adoptPtr(new ScheduledLocationChange(originDocument, url, lockBackForwardList)));
300 void NavigationScheduler::schedulePageBlock(Document* originDocument)
302 ASSERT(m_frame->page());
303 const KURL& url = m_frame->document()->url();
304 schedule(adoptPtr(new ScheduledPageBlock(originDocument, url)));
307 void NavigationScheduler::scheduleFormSubmission(PassRefPtrWillBeRawPtr<FormSubmission> submission)
309 ASSERT(m_frame->page());
310 schedule(adoptPtr(new ScheduledFormSubmission(submission, mustLockBackForwardList(m_frame))));
313 void NavigationScheduler::scheduleReload()
315 if (!shouldScheduleNavigation())
317 if (m_frame->document()->url().isEmpty())
319 schedule(adoptPtr(new ScheduledReload));
322 void NavigationScheduler::timerFired(Timer<NavigationScheduler>*)
324 if (!m_frame->page())
326 if (m_frame->page()->defersLoading()) {
327 InspectorInstrumentation::frameClearedScheduledNavigation(m_frame);
331 RefPtrWillBeRawPtr<LocalFrame> protect(m_frame.get());
333 OwnPtr<ScheduledNavigation> redirect(m_redirect.release());
334 redirect->fire(m_frame);
335 InspectorInstrumentation::frameClearedScheduledNavigation(m_frame);
338 void NavigationScheduler::schedule(PassOwnPtr<ScheduledNavigation> redirect)
340 ASSERT(m_frame->page());
342 // In a back/forward navigation, we sometimes restore history state to iframes, even though the state was generated
343 // dynamically and JS will try to put something different in the iframe. In this case, we will load stale things
344 // and/or confuse the JS when it shortly thereafter tries to schedule a location change. Let the JS have its way.
345 // FIXME: This check seems out of place.
346 if (!m_frame->loader().stateMachine()->committedFirstRealDocumentLoad() && m_frame->loader().provisionalDocumentLoader()) {
347 RefPtrWillBeRawPtr<LocalFrame> protect(m_frame.get());
348 m_frame->loader().provisionalDocumentLoader()->stopLoading();
349 if (!m_frame->host())
354 m_redirect = redirect;
358 void NavigationScheduler::startTimer()
363 ASSERT(m_frame->page());
364 if (m_timer.isActive())
366 if (!m_redirect->shouldStartTimer(m_frame))
369 m_timer.startOneShot(m_redirect->delay(), FROM_HERE);
370 InspectorInstrumentation::frameScheduledNavigation(m_frame, m_redirect->delay());
373 void NavigationScheduler::cancel()
375 if (m_timer.isActive())
376 InspectorInstrumentation::frameClearedScheduledNavigation(m_frame);
381 void NavigationScheduler::trace(Visitor* visitor)
383 visitor->trace(m_frame);