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