1/* 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. 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 "NavigationScheduler.h" 34 35#include "BackForwardController.h" 36#include "DOMWindow.h" 37#include "DocumentLoader.h" 38#include "Event.h" 39#include "FormState.h" 40#include "FormSubmission.h" 41#include "Frame.h" 42#include "FrameLoadRequest.h" 43#include "FrameLoader.h" 44#include "FrameLoaderStateMachine.h" 45#include "HTMLFormElement.h" 46#include "HTMLFrameOwnerElement.h" 47#include "HistoryItem.h" 48#include "Page.h" 49#include "UserGestureIndicator.h" 50#include <wtf/CurrentTime.h> 51 52namespace WebCore { 53 54unsigned NavigationDisablerForBeforeUnload::s_navigationDisableCount = 0; 55 56class ScheduledNavigation { 57 WTF_MAKE_NONCOPYABLE(ScheduledNavigation); WTF_MAKE_FAST_ALLOCATED; 58public: 59 ScheduledNavigation(double delay, bool lockHistory, bool lockBackForwardList, bool wasDuringLoad, bool isLocationChange) 60 : m_delay(delay) 61 , m_lockHistory(lockHistory) 62 , m_lockBackForwardList(lockBackForwardList) 63 , m_wasDuringLoad(wasDuringLoad) 64 , m_isLocationChange(isLocationChange) 65 , m_wasUserGesture(ScriptController::processingUserGesture()) 66 { 67 } 68 virtual ~ScheduledNavigation() { } 69 70 virtual void fire(Frame*) = 0; 71 72 virtual bool shouldStartTimer(Frame*) { return true; } 73 virtual void didStartTimer(Frame*, Timer<NavigationScheduler>*) { } 74 virtual void didStopTimer(Frame*, bool /* newLoadInProgress */) { } 75 76 double delay() const { return m_delay; } 77 bool lockHistory() const { return m_lockHistory; } 78 bool lockBackForwardList() const { return m_lockBackForwardList; } 79 bool wasDuringLoad() const { return m_wasDuringLoad; } 80 bool isLocationChange() const { return m_isLocationChange; } 81 bool wasUserGesture() const { return m_wasUserGesture; } 82 83protected: 84 void clearUserGesture() { m_wasUserGesture = false; } 85 86private: 87 double m_delay; 88 bool m_lockHistory; 89 bool m_lockBackForwardList; 90 bool m_wasDuringLoad; 91 bool m_isLocationChange; 92 bool m_wasUserGesture; 93}; 94 95class ScheduledURLNavigation : public ScheduledNavigation { 96protected: 97 ScheduledURLNavigation(double delay, PassRefPtr<SecurityOrigin> securityOrigin, const String& url, const String& referrer, bool lockHistory, bool lockBackForwardList, bool duringLoad, bool isLocationChange) 98 : ScheduledNavigation(delay, lockHistory, lockBackForwardList, duringLoad, isLocationChange) 99 , m_securityOrigin(securityOrigin) 100 , m_url(url) 101 , m_referrer(referrer) 102 , m_haveToldClient(false) 103 { 104 } 105 106 virtual void fire(Frame* frame) 107 { 108 UserGestureIndicator gestureIndicator(wasUserGesture() ? DefinitelyProcessingUserGesture : DefinitelyNotProcessingUserGesture); 109 frame->loader()->changeLocation(m_securityOrigin, KURL(ParsedURLString, m_url), m_referrer, lockHistory(), lockBackForwardList(), false); 110 } 111 112 virtual void didStartTimer(Frame* frame, Timer<NavigationScheduler>* timer) 113 { 114 if (m_haveToldClient) 115 return; 116 m_haveToldClient = true; 117 frame->loader()->clientRedirected(KURL(ParsedURLString, m_url), delay(), currentTime() + timer->nextFireInterval(), lockBackForwardList()); 118 } 119 120 virtual void didStopTimer(Frame* frame, bool newLoadInProgress) 121 { 122 if (!m_haveToldClient) 123 return; 124 frame->loader()->clientRedirectCancelledOrFinished(newLoadInProgress); 125 } 126 127 SecurityOrigin* securityOrigin() const { return m_securityOrigin.get(); } 128 String url() const { return m_url; } 129 String referrer() const { return m_referrer; } 130 131private: 132 RefPtr<SecurityOrigin> m_securityOrigin; 133 String m_url; 134 String m_referrer; 135 bool m_haveToldClient; 136}; 137 138class ScheduledRedirect : public ScheduledURLNavigation { 139public: 140 ScheduledRedirect(double delay, PassRefPtr<SecurityOrigin> securityOrigin, const String& url, bool lockHistory, bool lockBackForwardList) 141 : ScheduledURLNavigation(delay, securityOrigin, url, String(), lockHistory, lockBackForwardList, false, false) 142 { 143 clearUserGesture(); 144 } 145 146 virtual bool shouldStartTimer(Frame* frame) { return frame->loader()->allAncestorsAreComplete(); } 147}; 148 149class ScheduledLocationChange : public ScheduledURLNavigation { 150public: 151 ScheduledLocationChange(PassRefPtr<SecurityOrigin> securityOrigin, const String& url, const String& referrer, bool lockHistory, bool lockBackForwardList, bool duringLoad) 152 : ScheduledURLNavigation(0.0, securityOrigin, url, referrer, lockHistory, lockBackForwardList, duringLoad, true) { } 153}; 154 155class ScheduledRefresh : public ScheduledURLNavigation { 156public: 157 ScheduledRefresh(PassRefPtr<SecurityOrigin> securityOrigin, const String& url, const String& referrer) 158 : ScheduledURLNavigation(0.0, securityOrigin, url, referrer, true, true, false, true) 159 { 160 } 161 162 virtual void fire(Frame* frame) 163 { 164 UserGestureIndicator gestureIndicator(wasUserGesture() ? DefinitelyProcessingUserGesture : DefinitelyNotProcessingUserGesture); 165 frame->loader()->changeLocation(securityOrigin(), KURL(ParsedURLString, url()), referrer(), lockHistory(), lockBackForwardList(), true); 166 } 167}; 168 169class ScheduledHistoryNavigation : public ScheduledNavigation { 170public: 171 explicit ScheduledHistoryNavigation(int historySteps) 172 : ScheduledNavigation(0, false, false, false, true) 173 , m_historySteps(historySteps) 174 { 175 } 176 177 virtual void fire(Frame* frame) 178 { 179 UserGestureIndicator gestureIndicator(wasUserGesture() ? DefinitelyProcessingUserGesture : DefinitelyNotProcessingUserGesture); 180 181 if (!m_historySteps) { 182 // Special case for go(0) from a frame -> reload only the frame 183 // To follow Firefox and IE's behavior, history reload can only navigate the self frame. 184 frame->loader()->urlSelected(frame->document()->url(), "_self", 0, lockHistory(), lockBackForwardList(), SendReferrer); 185 return; 186 } 187 // go(i!=0) from a frame navigates into the history of the frame only, 188 // in both IE and NS (but not in Mozilla). We can't easily do that. 189 frame->page()->backForward()->goBackOrForward(m_historySteps); 190 } 191 192private: 193 int m_historySteps; 194}; 195 196class ScheduledFormSubmission : public ScheduledNavigation { 197public: 198 ScheduledFormSubmission(PassRefPtr<FormSubmission> submission, bool lockBackForwardList, bool duringLoad) 199 : ScheduledNavigation(0, submission->lockHistory(), lockBackForwardList, duringLoad, true) 200 , m_submission(submission) 201 , m_haveToldClient(false) 202 { 203 ASSERT(m_submission->state()); 204 } 205 206 virtual void fire(Frame* frame) 207 { 208 UserGestureIndicator gestureIndicator(wasUserGesture() ? DefinitelyProcessingUserGesture : DefinitelyNotProcessingUserGesture); 209 210 // The submitForm function will find a target frame before using the redirection timer. 211 // Now that the timer has fired, we need to repeat the security check which normally is done when 212 // selecting a target, in case conditions have changed. Other code paths avoid this by targeting 213 // without leaving a time window. If we fail the check just silently drop the form submission. 214 Frame* requestingFrame = m_submission->state()->sourceFrame(); 215 if (!requestingFrame->loader()->shouldAllowNavigation(frame)) 216 return; 217 FrameLoadRequest frameRequest(requestingFrame->document()->securityOrigin()); 218 m_submission->populateFrameLoadRequest(frameRequest); 219 frame->loader()->loadFrameRequest(frameRequest, lockHistory(), lockBackForwardList(), m_submission->event(), m_submission->state(), SendReferrer); 220 } 221 222 virtual void didStartTimer(Frame* frame, Timer<NavigationScheduler>* timer) 223 { 224 if (m_haveToldClient) 225 return; 226 m_haveToldClient = true; 227 frame->loader()->clientRedirected(m_submission->requestURL(), delay(), currentTime() + timer->nextFireInterval(), lockBackForwardList()); 228 } 229 230 virtual void didStopTimer(Frame* frame, bool newLoadInProgress) 231 { 232 if (!m_haveToldClient) 233 return; 234 frame->loader()->clientRedirectCancelledOrFinished(newLoadInProgress); 235 } 236 237private: 238 RefPtr<FormSubmission> m_submission; 239 bool m_haveToldClient; 240}; 241 242NavigationScheduler::NavigationScheduler(Frame* frame) 243 : m_frame(frame) 244 , m_timer(this, &NavigationScheduler::timerFired) 245{ 246} 247 248NavigationScheduler::~NavigationScheduler() 249{ 250} 251 252bool NavigationScheduler::redirectScheduledDuringLoad() 253{ 254 return m_redirect && m_redirect->wasDuringLoad(); 255} 256 257bool NavigationScheduler::locationChangePending() 258{ 259 return m_redirect && m_redirect->isLocationChange(); 260} 261 262void NavigationScheduler::clear() 263{ 264 m_timer.stop(); 265 m_redirect.clear(); 266} 267 268inline bool NavigationScheduler::shouldScheduleNavigation() const 269{ 270 return m_frame->page(); 271} 272 273inline bool NavigationScheduler::shouldScheduleNavigation(const String& url) const 274{ 275 return shouldScheduleNavigation() && (protocolIsJavaScript(url) || NavigationDisablerForBeforeUnload::isNavigationAllowed()); 276} 277 278void NavigationScheduler::scheduleRedirect(double delay, const String& url) 279{ 280 if (!shouldScheduleNavigation(url)) 281 return; 282 if (delay < 0 || delay > INT_MAX / 1000) 283 return; 284 if (url.isEmpty()) 285 return; 286 287 // We want a new back/forward list item if the refresh timeout is > 1 second. 288 if (!m_redirect || delay <= m_redirect->delay()) 289 schedule(adoptPtr(new ScheduledRedirect(delay, m_frame->document()->securityOrigin(), url, true, delay <= 1))); 290} 291 292bool NavigationScheduler::mustLockBackForwardList(Frame* targetFrame) 293{ 294 // Non-user navigation before the page has finished firing onload should not create a new back/forward item. 295 // See https://webkit.org/b/42861 for the original motivation for this. 296 if (!ScriptController::processingUserGesture() && targetFrame->loader()->documentLoader() && !targetFrame->loader()->documentLoader()->wasOnloadHandled()) 297 return true; 298 299 // Navigation of a subframe during loading of an ancestor frame does not create a new back/forward item. 300 // The definition of "during load" is any time before all handlers for the load event have been run. 301 // See https://bugs.webkit.org/show_bug.cgi?id=14957 for the original motivation for this. 302 for (Frame* ancestor = targetFrame->tree()->parent(); ancestor; ancestor = ancestor->tree()->parent()) { 303 Document* document = ancestor->document(); 304 if (!ancestor->loader()->isComplete() || (document && document->processingLoadEvent())) 305 return true; 306 } 307 return false; 308} 309 310void NavigationScheduler::scheduleLocationChange(PassRefPtr<SecurityOrigin> securityOrigin, const String& url, const String& referrer, bool lockHistory, bool lockBackForwardList) 311{ 312 if (!shouldScheduleNavigation(url)) 313 return; 314 if (url.isEmpty()) 315 return; 316 317 lockBackForwardList = lockBackForwardList || mustLockBackForwardList(m_frame); 318 319 FrameLoader* loader = m_frame->loader(); 320 321 // If the URL we're going to navigate to is the same as the current one, except for the 322 // fragment part, we don't need to schedule the location change. 323 KURL parsedURL(ParsedURLString, url); 324 if (parsedURL.hasFragmentIdentifier() && equalIgnoringFragmentIdentifier(m_frame->document()->url(), parsedURL)) { 325 loader->changeLocation(securityOrigin, loader->completeURL(url), referrer, lockHistory, lockBackForwardList); 326 return; 327 } 328 329 // Handle a location change of a page with no document as a special case. 330 // This may happen when a frame changes the location of another frame. 331 bool duringLoad = !loader->stateMachine()->committedFirstRealDocumentLoad(); 332 333 schedule(adoptPtr(new ScheduledLocationChange(securityOrigin, url, referrer, lockHistory, lockBackForwardList, duringLoad))); 334} 335 336void NavigationScheduler::scheduleFormSubmission(PassRefPtr<FormSubmission> submission) 337{ 338 ASSERT(m_frame->page()); 339 340 // FIXME: Do we need special handling for form submissions where the URL is the same 341 // as the current one except for the fragment part? See scheduleLocationChange above. 342 343 // Handle a location change of a page with no document as a special case. 344 // This may happen when a frame changes the location of another frame. 345 bool duringLoad = !m_frame->loader()->stateMachine()->committedFirstRealDocumentLoad(); 346 347 // If this is a child frame and the form submission was triggered by a script, lock the back/forward list 348 // to match IE and Opera. 349 // See https://bugs.webkit.org/show_bug.cgi?id=32383 for the original motivation for this. 350 bool lockBackForwardList = mustLockBackForwardList(m_frame) 351 || (submission->state()->formSubmissionTrigger() == SubmittedByJavaScript 352 && m_frame->tree()->parent() && !ScriptController::processingUserGesture()); 353 354 schedule(adoptPtr(new ScheduledFormSubmission(submission, lockBackForwardList, duringLoad))); 355} 356 357void NavigationScheduler::scheduleRefresh() 358{ 359 if (!shouldScheduleNavigation()) 360 return; 361 const KURL& url = m_frame->document()->url(); 362 if (url.isEmpty()) 363 return; 364 365 schedule(adoptPtr(new ScheduledRefresh(m_frame->document()->securityOrigin(), url.string(), m_frame->loader()->outgoingReferrer()))); 366} 367 368void NavigationScheduler::scheduleHistoryNavigation(int steps) 369{ 370 if (!shouldScheduleNavigation()) 371 return; 372 373 // Invalid history navigations (such as history.forward() during a new load) have the side effect of cancelling any scheduled 374 // redirects. We also avoid the possibility of cancelling the current load by avoiding the scheduled redirection altogether. 375 BackForwardController* backForward = m_frame->page()->backForward(); 376 if (steps > backForward->forwardCount() || -steps > backForward->backCount()) { 377 cancel(); 378 return; 379 } 380 381 // In all other cases, schedule the history traversal to occur asynchronously. 382 schedule(adoptPtr(new ScheduledHistoryNavigation(steps))); 383} 384 385void NavigationScheduler::timerFired(Timer<NavigationScheduler>*) 386{ 387 if (!m_frame->page()) 388 return; 389 if (m_frame->page()->defersLoading()) 390 return; 391 392 OwnPtr<ScheduledNavigation> redirect(m_redirect.release()); 393 redirect->fire(m_frame); 394} 395 396void NavigationScheduler::schedule(PassOwnPtr<ScheduledNavigation> redirect) 397{ 398 ASSERT(m_frame->page()); 399 400 // If a redirect was scheduled during a load, then stop the current load. 401 // Otherwise when the current load transitions from a provisional to a 402 // committed state, pending redirects may be cancelled. 403 if (redirect->wasDuringLoad()) { 404 if (DocumentLoader* provisionalDocumentLoader = m_frame->loader()->provisionalDocumentLoader()) 405 provisionalDocumentLoader->stopLoading(); 406 m_frame->loader()->stopLoading(UnloadEventPolicyUnloadAndPageHide); 407 } 408 409 cancel(); 410 m_redirect = redirect; 411 412 if (!m_frame->loader()->isComplete() && m_redirect->isLocationChange()) 413 m_frame->loader()->completed(); 414 415 startTimer(); 416} 417 418void NavigationScheduler::startTimer() 419{ 420 if (!m_redirect) 421 return; 422 423 ASSERT(m_frame->page()); 424 if (m_timer.isActive()) 425 return; 426 if (!m_redirect->shouldStartTimer(m_frame)) 427 return; 428 429 m_timer.startOneShot(m_redirect->delay()); 430 m_redirect->didStartTimer(m_frame, &m_timer); 431} 432 433void NavigationScheduler::cancel(bool newLoadInProgress) 434{ 435 m_timer.stop(); 436 437 OwnPtr<ScheduledNavigation> redirect(m_redirect.release()); 438 if (redirect) 439 redirect->didStopTimer(m_frame, newLoadInProgress); 440} 441 442} // namespace WebCore 443