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 "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 
     52 namespace WebCore {
     53 
     54 unsigned NavigationDisablerForBeforeUnload::s_navigationDisableCount = 0;
     55 
     56 class ScheduledNavigation {
     57     WTF_MAKE_NONCOPYABLE(ScheduledNavigation); WTF_MAKE_FAST_ALLOCATED;
     58 public:
     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 
     83 protected:
     84     void clearUserGesture() { m_wasUserGesture = false; }
     85 
     86 private:
     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 
     95 class ScheduledURLNavigation : public ScheduledNavigation {
     96 protected:
     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 
    131 private:
    132     RefPtr<SecurityOrigin> m_securityOrigin;
    133     String m_url;
    134     String m_referrer;
    135     bool m_haveToldClient;
    136 };
    137 
    138 class ScheduledRedirect : public ScheduledURLNavigation {
    139 public:
    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 
    149 class ScheduledLocationChange : public ScheduledURLNavigation {
    150 public:
    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 
    155 class ScheduledRefresh : public ScheduledURLNavigation {
    156 public:
    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 
    169 class ScheduledHistoryNavigation : public ScheduledNavigation {
    170 public:
    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 
    192 private:
    193     int m_historySteps;
    194 };
    195 
    196 class ScheduledFormSubmission : public ScheduledNavigation {
    197 public:
    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 
    237 private:
    238     RefPtr<FormSubmission> m_submission;
    239     bool m_haveToldClient;
    240 };
    241 
    242 NavigationScheduler::NavigationScheduler(Frame* frame)
    243     : m_frame(frame)
    244     , m_timer(this, &NavigationScheduler::timerFired)
    245 {
    246 }
    247 
    248 NavigationScheduler::~NavigationScheduler()
    249 {
    250 }
    251 
    252 bool NavigationScheduler::redirectScheduledDuringLoad()
    253 {
    254     return m_redirect && m_redirect->wasDuringLoad();
    255 }
    256 
    257 bool NavigationScheduler::locationChangePending()
    258 {
    259     return m_redirect && m_redirect->isLocationChange();
    260 }
    261 
    262 void NavigationScheduler::clear()
    263 {
    264     m_timer.stop();
    265     m_redirect.clear();
    266 }
    267 
    268 inline bool NavigationScheduler::shouldScheduleNavigation() const
    269 {
    270     return m_frame->page();
    271 }
    272 
    273 inline bool NavigationScheduler::shouldScheduleNavigation(const String& url) const
    274 {
    275     return shouldScheduleNavigation() && (protocolIsJavaScript(url) || NavigationDisablerForBeforeUnload::isNavigationAllowed());
    276 }
    277 
    278 void 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 
    292 bool 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 
    310 void 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 
    336 void 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 
    357 void 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 
    368 void 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 
    385 void 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 
    396 void 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 
    418 void 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 
    433 void 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