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