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