Home | History | Annotate | Download | only in loader
      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