Home | History | Annotate | Download | only in loader
      1 /*
      2  * Copyright (C) 2006, 2007, 2008, 2009 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 "RedirectScheduler.h"
     34 
     35 #include "BackForwardList.h"
     36 #include "DocumentLoader.h"
     37 #include "Event.h"
     38 #include "FormState.h"
     39 #include "Frame.h"
     40 #include "FrameLoadRequest.h"
     41 #include "FrameLoader.h"
     42 #include "HistoryItem.h"
     43 #include "HTMLFormElement.h"
     44 #include "HTMLFrameOwnerElement.h"
     45 #include "Page.h"
     46 #include <wtf/CurrentTime.h>
     47 
     48 namespace WebCore {
     49 
     50 struct ScheduledRedirection : Noncopyable {
     51     enum Type { redirection, locationChange, historyNavigation, formSubmission };
     52 
     53     const Type type;
     54     const double delay;
     55     const String url;
     56     const String referrer;
     57     const FrameLoadRequest frameRequest;
     58     const RefPtr<Event> event;
     59     const RefPtr<FormState> formState;
     60     const int historySteps;
     61     const bool lockHistory;
     62     const bool lockBackForwardList;
     63     const bool wasUserGesture;
     64     const bool wasRefresh;
     65     const bool wasDuringLoad;
     66     bool toldClient;
     67 
     68     ScheduledRedirection(double delay, const String& url, bool lockHistory, bool lockBackForwardList, bool wasUserGesture, bool refresh)
     69         : type(redirection)
     70         , delay(delay)
     71         , url(url)
     72         , historySteps(0)
     73         , lockHistory(lockHistory)
     74         , lockBackForwardList(lockBackForwardList)
     75         , wasUserGesture(wasUserGesture)
     76         , wasRefresh(refresh)
     77         , wasDuringLoad(false)
     78         , toldClient(false)
     79     {
     80         ASSERT(!url.isEmpty());
     81     }
     82 
     83     ScheduledRedirection(const String& url, const String& referrer, bool lockHistory, bool lockBackForwardList, bool wasUserGesture, bool refresh, bool duringLoad)
     84         : type(locationChange)
     85         , delay(0)
     86         , url(url)
     87         , referrer(referrer)
     88         , historySteps(0)
     89         , lockHistory(lockHistory)
     90         , lockBackForwardList(lockBackForwardList)
     91         , wasUserGesture(wasUserGesture)
     92         , wasRefresh(refresh)
     93         , wasDuringLoad(duringLoad)
     94         , toldClient(false)
     95     {
     96         ASSERT(!url.isEmpty());
     97     }
     98 
     99     explicit ScheduledRedirection(int historyNavigationSteps)
    100         : type(historyNavigation)
    101         , delay(0)
    102         , historySteps(historyNavigationSteps)
    103         , lockHistory(false)
    104         , lockBackForwardList(false)
    105         , wasUserGesture(false)
    106         , wasRefresh(false)
    107         , wasDuringLoad(false)
    108         , toldClient(false)
    109     {
    110     }
    111 
    112     ScheduledRedirection(const FrameLoadRequest& frameRequest,
    113             bool lockHistory, bool lockBackForwardList, PassRefPtr<Event> event, PassRefPtr<FormState> formState,
    114             bool duringLoad)
    115         : type(formSubmission)
    116         , delay(0)
    117         , frameRequest(frameRequest)
    118         , event(event)
    119         , formState(formState)
    120         , historySteps(0)
    121         , lockHistory(lockHistory)
    122         , lockBackForwardList(lockBackForwardList)
    123         , wasUserGesture(false)
    124         , wasRefresh(false)
    125         , wasDuringLoad(duringLoad)
    126         , toldClient(false)
    127     {
    128         ASSERT(!frameRequest.isEmpty());
    129         ASSERT(this->formState);
    130     }
    131 };
    132 
    133 RedirectScheduler::RedirectScheduler(Frame* frame)
    134     : m_frame(frame)
    135     , m_timer(this, &RedirectScheduler::timerFired)
    136 {
    137 }
    138 
    139 RedirectScheduler::~RedirectScheduler()
    140 {
    141 }
    142 
    143 bool RedirectScheduler::redirectScheduledDuringLoad()
    144 {
    145     return m_scheduledRedirection && m_scheduledRedirection->wasDuringLoad;
    146 }
    147 
    148 void RedirectScheduler::clear()
    149 {
    150     m_timer.stop();
    151     m_scheduledRedirection.clear();
    152 }
    153 
    154 void RedirectScheduler::scheduleRedirect(double delay, const String& url)
    155 {
    156     if (delay < 0 || delay > INT_MAX / 1000)
    157         return;
    158 
    159     if (!m_frame->page())
    160         return;
    161 
    162     if (url.isEmpty())
    163         return;
    164 
    165     // We want a new history item if the refresh timeout is > 1 second.
    166     if (!m_scheduledRedirection || delay <= m_scheduledRedirection->delay)
    167         schedule(new ScheduledRedirection(delay, url, true, delay <= 1, false, false));
    168 }
    169 
    170 bool RedirectScheduler::mustLockBackForwardList(Frame* targetFrame)
    171 {
    172     // Navigation of a subframe during loading of an ancestor frame does not create a new back/forward item.
    173     // The definition of "during load" is any time before all handlers for the load event have been run.
    174     // See https://bugs.webkit.org/show_bug.cgi?id=14957 for the original motivation for this.
    175 
    176     for (Frame* ancestor = targetFrame->tree()->parent(); ancestor; ancestor = ancestor->tree()->parent()) {
    177         Document* document = ancestor->document();
    178         if (!ancestor->loader()->isComplete() || (document && document->processingLoadEvent()))
    179             return true;
    180     }
    181     return false;
    182 }
    183 
    184 void RedirectScheduler::scheduleLocationChange(const String& url, const String& referrer, bool lockHistory, bool lockBackForwardList, bool wasUserGesture)
    185 {
    186     if (!m_frame->page())
    187         return;
    188 
    189     if (url.isEmpty())
    190         return;
    191 
    192     lockBackForwardList = lockBackForwardList || mustLockBackForwardList(m_frame);
    193 
    194     FrameLoader* loader = m_frame->loader();
    195 
    196     // If the URL we're going to navigate to is the same as the current one, except for the
    197     // fragment part, we don't need to schedule the location change.
    198     KURL parsedURL(ParsedURLString, url);
    199     if (parsedURL.hasFragmentIdentifier() && equalIgnoringFragmentIdentifier(loader->url(), parsedURL)) {
    200         loader->changeLocation(loader->completeURL(url), referrer, lockHistory, lockBackForwardList, wasUserGesture);
    201         return;
    202     }
    203 
    204     // Handle a location change of a page with no document as a special case.
    205     // This may happen when a frame changes the location of another frame.
    206     bool duringLoad = !loader->committedFirstRealDocumentLoad();
    207 
    208     schedule(new ScheduledRedirection(url, referrer, lockHistory, lockBackForwardList, wasUserGesture, false, duringLoad));
    209 }
    210 
    211 void RedirectScheduler::scheduleFormSubmission(const FrameLoadRequest& frameRequest,
    212     bool lockHistory, PassRefPtr<Event> event, PassRefPtr<FormState> formState)
    213 {
    214     ASSERT(m_frame->page());
    215     ASSERT(!frameRequest.isEmpty());
    216 
    217     // FIXME: Do we need special handling for form submissions where the URL is the same
    218     // as the current one except for the fragment part? See scheduleLocationChange above.
    219 
    220     // Handle a location change of a page with no document as a special case.
    221     // This may happen when a frame changes the location of another frame.
    222     bool duringLoad = !m_frame->loader()->committedFirstRealDocumentLoad();
    223 
    224     // If this is a child frame and the form submission was triggered by a script, lock the back/forward list
    225     // to match IE and Opera.
    226     // See https://bugs.webkit.org/show_bug.cgi?id=32383 for the original motivation for this.
    227 
    228     bool lockBackForwardList = mustLockBackForwardList(m_frame) || (formState->formSubmissionTrigger() == SubmittedByJavaScript && m_frame->tree()->parent());
    229 
    230     schedule(new ScheduledRedirection(frameRequest, lockHistory, lockBackForwardList, event, formState, duringLoad));
    231 }
    232 
    233 void RedirectScheduler::scheduleRefresh(bool wasUserGesture)
    234 {
    235     if (!m_frame->page())
    236         return;
    237 
    238     const KURL& url = m_frame->loader()->url();
    239 
    240     if (url.isEmpty())
    241         return;
    242 
    243     schedule(new ScheduledRedirection(url.string(), m_frame->loader()->outgoingReferrer(), true, true, wasUserGesture, true, false));
    244 }
    245 
    246 bool RedirectScheduler::locationChangePending()
    247 {
    248     if (!m_scheduledRedirection)
    249         return false;
    250 
    251     switch (m_scheduledRedirection->type) {
    252         case ScheduledRedirection::redirection:
    253             return false;
    254         case ScheduledRedirection::historyNavigation:
    255         case ScheduledRedirection::locationChange:
    256         case ScheduledRedirection::formSubmission:
    257             return true;
    258     }
    259     ASSERT_NOT_REACHED();
    260     return false;
    261 }
    262 
    263 void RedirectScheduler::scheduleHistoryNavigation(int steps)
    264 {
    265     if (!m_frame->page())
    266         return;
    267 
    268     // Invalid history navigations (such as history.forward() during a new load) have the side effect of cancelling any scheduled
    269     // redirects. We also avoid the possibility of cancelling the current load by avoiding the scheduled redirection altogether.
    270     HistoryItem* specifiedEntry = m_frame->page()->backForwardList()->itemAtIndex(steps);
    271     if (!specifiedEntry) {
    272         cancel();
    273         return;
    274     }
    275 
    276 #if !ENABLE(HISTORY_ALWAYS_ASYNC)
    277     // If the specified entry and the current entry have the same document, this is either a state object traversal or a fragment
    278     // traversal (or both) and should be performed synchronously.
    279     HistoryItem* currentEntry = m_frame->loader()->history()->currentItem();
    280     if (currentEntry != specifiedEntry && currentEntry->documentSequenceNumber() == specifiedEntry->documentSequenceNumber()) {
    281         m_frame->loader()->history()->goToItem(specifiedEntry, FrameLoadTypeIndexedBackForward);
    282         return;
    283     }
    284 #endif
    285 
    286     // In all other cases, schedule the history traversal to occur asynchronously.
    287     schedule(new ScheduledRedirection(steps));
    288 }
    289 
    290 void RedirectScheduler::timerFired(Timer<RedirectScheduler>*)
    291 {
    292     if (!m_frame->page())
    293         return;
    294 
    295     if (m_frame->page()->defersLoading())
    296         return;
    297 
    298     OwnPtr<ScheduledRedirection> redirection(m_scheduledRedirection.release());
    299     FrameLoader* loader = m_frame->loader();
    300 
    301     switch (redirection->type) {
    302         case ScheduledRedirection::redirection:
    303         case ScheduledRedirection::locationChange:
    304             loader->changeLocation(KURL(ParsedURLString, redirection->url), redirection->referrer,
    305                 redirection->lockHistory, redirection->lockBackForwardList, redirection->wasUserGesture, redirection->wasRefresh);
    306             return;
    307         case ScheduledRedirection::historyNavigation:
    308             if (redirection->historySteps == 0) {
    309                 // Special case for go(0) from a frame -> reload only the frame
    310                 loader->urlSelected(loader->url(), "", 0, redirection->lockHistory, redirection->lockBackForwardList, redirection->wasUserGesture, SendReferrer);
    311                 return;
    312             }
    313             // go(i!=0) from a frame navigates into the history of the frame only,
    314             // in both IE and NS (but not in Mozilla). We can't easily do that.
    315             m_frame->page()->goBackOrForward(redirection->historySteps);
    316             return;
    317         case ScheduledRedirection::formSubmission:
    318             // The submitForm function will find a target frame before using the redirection timer.
    319             // Now that the timer has fired, we need to repeat the security check which normally is done when
    320             // selecting a target, in case conditions have changed. Other code paths avoid this by targeting
    321             // without leaving a time window. If we fail the check just silently drop the form submission.
    322             if (!redirection->formState->sourceFrame()->loader()->shouldAllowNavigation(m_frame))
    323                 return;
    324             loader->loadFrameRequest(redirection->frameRequest, redirection->lockHistory, redirection->lockBackForwardList,
    325                 redirection->event, redirection->formState, SendReferrer);
    326             return;
    327     }
    328 
    329     ASSERT_NOT_REACHED();
    330 }
    331 
    332 void RedirectScheduler::schedule(PassOwnPtr<ScheduledRedirection> redirection)
    333 {
    334     ASSERT(m_frame->page());
    335     FrameLoader* loader = m_frame->loader();
    336 
    337     // If a redirect was scheduled during a load, then stop the current load.
    338     // Otherwise when the current load transitions from a provisional to a
    339     // committed state, pending redirects may be cancelled.
    340     if (redirection->wasDuringLoad) {
    341         if (DocumentLoader* provisionalDocumentLoader = loader->provisionalDocumentLoader())
    342             provisionalDocumentLoader->stopLoading();
    343         loader->stopLoading(UnloadEventPolicyUnloadAndPageHide);
    344     }
    345 
    346     cancel();
    347     m_scheduledRedirection = redirection;
    348     if (!loader->isComplete() && m_scheduledRedirection->type != ScheduledRedirection::redirection)
    349         loader->completed();
    350     startTimer();
    351 }
    352 
    353 void RedirectScheduler::startTimer()
    354 {
    355     if (!m_scheduledRedirection)
    356         return;
    357 
    358     ASSERT(m_frame->page());
    359 
    360     FrameLoader* loader = m_frame->loader();
    361 
    362     if (m_timer.isActive())
    363         return;
    364 
    365     if (m_scheduledRedirection->type == ScheduledRedirection::redirection && !loader->allAncestorsAreComplete())
    366         return;
    367 
    368     m_timer.startOneShot(m_scheduledRedirection->delay);
    369 
    370     switch (m_scheduledRedirection->type) {
    371         case ScheduledRedirection::locationChange:
    372         case ScheduledRedirection::redirection:
    373             if (m_scheduledRedirection->toldClient)
    374                 return;
    375             m_scheduledRedirection->toldClient = true;
    376             loader->clientRedirected(KURL(ParsedURLString, m_scheduledRedirection->url),
    377                 m_scheduledRedirection->delay,
    378                 currentTime() + m_timer.nextFireInterval(),
    379                 m_scheduledRedirection->lockBackForwardList);
    380             return;
    381         case ScheduledRedirection::formSubmission:
    382             // FIXME: It would make sense to report form submissions as client redirects too.
    383             // But we didn't do that in the past when form submission used a separate delay
    384             // mechanism, so doing it will be a behavior change.
    385             return;
    386         case ScheduledRedirection::historyNavigation:
    387             // Don't report history navigations.
    388             return;
    389     }
    390     ASSERT_NOT_REACHED();
    391 }
    392 
    393 void RedirectScheduler::cancel(bool newLoadInProgress)
    394 {
    395     m_timer.stop();
    396 
    397     OwnPtr<ScheduledRedirection> redirection(m_scheduledRedirection.release());
    398     if (redirection && redirection->toldClient)
    399         m_frame->loader()->clientRedirectCancelledOrFinished(newLoadInProgress);
    400 }
    401 
    402 } // namespace WebCore
    403 
    404