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