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/core/v8/ScriptController.h"
36#include "core/events/Event.h"
37#include "core/fetch/ResourceLoaderOptions.h"
38#include "core/frame/LocalFrame.h"
39#include "core/frame/csp/ContentSecurityPolicy.h"
40#include "core/html/HTMLFormElement.h"
41#include "core/inspector/InspectorInstrumentation.h"
42#include "core/loader/DocumentLoader.h"
43#include "core/loader/FormState.h"
44#include "core/loader/FormSubmission.h"
45#include "core/loader/FrameLoadRequest.h"
46#include "core/loader/FrameLoader.h"
47#include "core/loader/FrameLoaderClient.h"
48#include "core/loader/FrameLoaderStateMachine.h"
49#include "core/page/BackForwardClient.h"
50#include "core/page/Page.h"
51#include "platform/SharedBuffer.h"
52#include "platform/UserGestureIndicator.h"
53#include "wtf/CurrentTime.h"
54
55namespace blink {
56
57unsigned NavigationDisablerForBeforeUnload::s_navigationDisableCount = 0;
58
59class ScheduledNavigation {
60    WTF_MAKE_NONCOPYABLE(ScheduledNavigation); WTF_MAKE_FAST_ALLOCATED;
61public:
62    ScheduledNavigation(double delay, bool lockBackForwardList, bool isLocationChange)
63        : m_delay(delay)
64        , m_lockBackForwardList(lockBackForwardList)
65        , m_isLocationChange(isLocationChange)
66        , m_wasUserGesture(UserGestureIndicator::processingUserGesture())
67    {
68        if (m_wasUserGesture)
69            m_userGestureToken = UserGestureIndicator::currentToken();
70    }
71    virtual ~ScheduledNavigation() { }
72
73    virtual void fire(LocalFrame*) = 0;
74
75    virtual bool shouldStartTimer(LocalFrame*) { return true; }
76
77    double delay() const { return m_delay; }
78    bool lockBackForwardList() const { return m_lockBackForwardList; }
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
87protected:
88    void clearUserGesture() { m_wasUserGesture = false; }
89
90private:
91    double m_delay;
92    bool m_lockBackForwardList;
93    bool m_isLocationChange;
94    bool m_wasUserGesture;
95    RefPtr<UserGestureToken> m_userGestureToken;
96};
97
98class ScheduledURLNavigation : public ScheduledNavigation {
99protected:
100    ScheduledURLNavigation(double delay, Document* originDocument, const String& url, const Referrer& referrer, bool lockBackForwardList, bool isLocationChange)
101        : ScheduledNavigation(delay, lockBackForwardList, isLocationChange)
102        , m_originDocument(originDocument)
103        , m_url(url)
104        , m_referrer(referrer)
105        , m_shouldCheckMainWorldContentSecurityPolicy(CheckContentSecurityPolicy)
106    {
107        if (ContentSecurityPolicy::shouldBypassMainWorld(originDocument))
108            m_shouldCheckMainWorldContentSecurityPolicy = DoNotCheckContentSecurityPolicy;
109    }
110
111    virtual void fire(LocalFrame* frame) OVERRIDE
112    {
113        OwnPtr<UserGestureIndicator> gestureIndicator = createUserGestureIndicator();
114        FrameLoadRequest request(m_originDocument.get(), ResourceRequest(KURL(ParsedURLString, m_url), m_referrer), "_self", m_shouldCheckMainWorldContentSecurityPolicy);
115        request.setLockBackForwardList(lockBackForwardList());
116        request.setClientRedirect(ClientRedirect);
117        frame->loader().load(request);
118    }
119
120    Document* originDocument() const { return m_originDocument.get(); }
121    String url() const { return m_url; }
122    const Referrer& referrer() const { return m_referrer; }
123
124private:
125    RefPtrWillBePersistent<Document> m_originDocument;
126    String m_url;
127    Referrer m_referrer;
128    ContentSecurityPolicyCheck m_shouldCheckMainWorldContentSecurityPolicy;
129};
130
131class ScheduledRedirect FINAL : public ScheduledURLNavigation {
132public:
133    ScheduledRedirect(double delay, Document* originDocument, const String& url, bool lockBackForwardList)
134        : ScheduledURLNavigation(delay, originDocument, url, Referrer(), lockBackForwardList, false)
135    {
136        clearUserGesture();
137    }
138
139    virtual bool shouldStartTimer(LocalFrame* frame) OVERRIDE { return frame->loader().allAncestorsAreComplete(); }
140
141    virtual void fire(LocalFrame* frame) OVERRIDE
142    {
143        OwnPtr<UserGestureIndicator> gestureIndicator = createUserGestureIndicator();
144        FrameLoadRequest request(originDocument(), ResourceRequest(KURL(ParsedURLString, url()), referrer()), "_self");
145        request.setLockBackForwardList(lockBackForwardList());
146        if (equalIgnoringFragmentIdentifier(frame->document()->url(), request.resourceRequest().url()))
147            request.resourceRequest().setCachePolicy(ReloadIgnoringCacheData);
148        request.setClientRedirect(ClientRedirect);
149        frame->loader().load(request);
150    }
151};
152
153class ScheduledLocationChange FINAL : public ScheduledURLNavigation {
154public:
155    ScheduledLocationChange(Document* originDocument, const String& url, const Referrer& referrer, bool lockBackForwardList)
156        : ScheduledURLNavigation(0.0, originDocument, url, referrer, lockBackForwardList, true) { }
157};
158
159class ScheduledReload FINAL : public ScheduledNavigation {
160public:
161    ScheduledReload()
162        : ScheduledNavigation(0.0, true, true)
163    {
164    }
165
166    virtual void fire(LocalFrame* frame) OVERRIDE
167    {
168        OwnPtr<UserGestureIndicator> gestureIndicator = createUserGestureIndicator();
169        frame->loader().reload(NormalReload, KURL(), nullAtom, ClientRedirect);
170    }
171};
172
173class ScheduledPageBlock FINAL : public ScheduledURLNavigation {
174public:
175    ScheduledPageBlock(Document* originDocument, const String& url, const Referrer& referrer)
176        : ScheduledURLNavigation(0.0, originDocument, url, referrer, true, true)
177    {
178    }
179
180    virtual void fire(LocalFrame* frame) OVERRIDE
181    {
182        OwnPtr<UserGestureIndicator> gestureIndicator = createUserGestureIndicator();
183        SubstituteData substituteData(SharedBuffer::create(), "text/plain", "UTF-8", KURL(), ForceSynchronousLoad);
184        FrameLoadRequest request(originDocument(), ResourceRequest(KURL(ParsedURLString, url()), referrer(), ReloadIgnoringCacheData), substituteData);
185        request.setLockBackForwardList(true);
186        request.setClientRedirect(ClientRedirect);
187        frame->loader().load(request);
188    }
189};
190
191class ScheduledHistoryNavigation FINAL : public ScheduledNavigation {
192public:
193    explicit ScheduledHistoryNavigation(int historySteps)
194        : ScheduledNavigation(0, false, true)
195        , m_historySteps(historySteps)
196    {
197    }
198
199    virtual void fire(LocalFrame* frame) OVERRIDE
200    {
201        OwnPtr<UserGestureIndicator> gestureIndicator = createUserGestureIndicator();
202        // go(i!=0) from a frame navigates into the history of the frame only,
203        // in both IE and NS (but not in Mozilla). We can't easily do that.
204        frame->page()->deprecatedLocalMainFrame()->loader().client()->navigateBackForward(m_historySteps);
205    }
206
207private:
208    int m_historySteps;
209};
210
211class ScheduledFormSubmission FINAL : public ScheduledNavigation {
212public:
213    ScheduledFormSubmission(PassRefPtrWillBeRawPtr<FormSubmission> submission, bool lockBackForwardList)
214        : ScheduledNavigation(0, lockBackForwardList, true)
215        , m_submission(submission)
216    {
217        ASSERT(m_submission->state());
218    }
219
220    virtual void fire(LocalFrame* frame) OVERRIDE
221    {
222        OwnPtr<UserGestureIndicator> gestureIndicator = createUserGestureIndicator();
223        FrameLoadRequest frameRequest(m_submission->state()->sourceDocument());
224        m_submission->populateFrameLoadRequest(frameRequest);
225        frameRequest.setLockBackForwardList(lockBackForwardList());
226        frameRequest.setTriggeringEvent(m_submission->event());
227        frameRequest.setFormState(m_submission->state());
228        frame->loader().load(frameRequest);
229    }
230
231private:
232    RefPtrWillBePersistent<FormSubmission> m_submission;
233};
234
235NavigationScheduler::NavigationScheduler(LocalFrame* frame)
236    : m_frame(frame)
237    , m_timer(this, &NavigationScheduler::timerFired)
238{
239}
240
241NavigationScheduler::~NavigationScheduler()
242{
243}
244
245bool NavigationScheduler::locationChangePending()
246{
247    return m_redirect && m_redirect->isLocationChange();
248}
249
250inline bool NavigationScheduler::shouldScheduleNavigation() const
251{
252    return m_frame->page();
253}
254
255inline bool NavigationScheduler::shouldScheduleNavigation(const String& url) const
256{
257    return shouldScheduleNavigation() && (protocolIsJavaScript(url) || NavigationDisablerForBeforeUnload::isNavigationAllowed());
258}
259
260void NavigationScheduler::scheduleRedirect(double delay, const String& url)
261{
262    if (!shouldScheduleNavigation(url))
263        return;
264    if (delay < 0 || delay > INT_MAX / 1000)
265        return;
266    if (url.isEmpty())
267        return;
268
269    // We want a new back/forward list item if the refresh timeout is > 1 second.
270    if (!m_redirect || delay <= m_redirect->delay())
271        schedule(adoptPtr(new ScheduledRedirect(delay, m_frame->document(), url, delay <= 1)));
272}
273
274bool NavigationScheduler::mustLockBackForwardList(LocalFrame* targetFrame)
275{
276    // Non-user navigation before the page has finished firing onload should not create a new back/forward item.
277    // See https://webkit.org/b/42861 for the original motivation for this.
278    if (!UserGestureIndicator::processingUserGesture() && !targetFrame->document()->loadEventFinished())
279        return true;
280
281    // From the HTML5 spec for location.assign():
282    //  "If the browsing context's session history contains only one Document,
283    //   and that was the about:blank Document created when the browsing context
284    //   was created, then the navigation must be done with replacement enabled."
285    if (!targetFrame->loader().stateMachine()->committedMultipleRealLoads()
286        && equalIgnoringCase(targetFrame->document()->url(), blankURL()))
287        return true;
288
289    // Navigation of a subframe during loading of an ancestor frame does not create a new back/forward item.
290    // The definition of "during load" is any time before all handlers for the load event have been run.
291    // See https://bugs.webkit.org/show_bug.cgi?id=14957 for the original motivation for this.
292    Frame* parentFrame = targetFrame->tree().parent();
293    return parentFrame && parentFrame->isLocalFrame() && !toLocalFrame(parentFrame)->loader().allAncestorsAreComplete();
294}
295
296void NavigationScheduler::scheduleLocationChange(Document* originDocument, const String& url, const Referrer& referrer, bool lockBackForwardList)
297{
298    if (!shouldScheduleNavigation(url))
299        return;
300    if (url.isEmpty())
301        return;
302
303    lockBackForwardList = lockBackForwardList || mustLockBackForwardList(m_frame);
304
305    // If the URL we're going to navigate to is the same as the current one, except for the
306    // fragment part, we don't need to schedule the location change. We'll skip this
307    // optimization for cross-origin navigations to minimize the navigator's ability to
308    // execute timing attacks.
309    if (originDocument->securityOrigin()->canAccess(m_frame->document()->securityOrigin())) {
310        KURL parsedURL(ParsedURLString, url);
311        if (parsedURL.hasFragmentIdentifier() && equalIgnoringFragmentIdentifier(m_frame->document()->url(), parsedURL)) {
312            FrameLoadRequest request(originDocument, ResourceRequest(m_frame->document()->completeURL(url), referrer), "_self");
313            request.setLockBackForwardList(lockBackForwardList);
314            if (lockBackForwardList)
315                request.setClientRedirect(ClientRedirect);
316            m_frame->loader().load(request);
317            return;
318        }
319    }
320
321    schedule(adoptPtr(new ScheduledLocationChange(originDocument, url, referrer, lockBackForwardList)));
322}
323
324void NavigationScheduler::schedulePageBlock(Document* originDocument, const Referrer& referrer)
325{
326    ASSERT(m_frame->page());
327    const KURL& url = m_frame->document()->url();
328    schedule(adoptPtr(new ScheduledPageBlock(originDocument, url, referrer)));
329}
330
331void NavigationScheduler::scheduleFormSubmission(PassRefPtrWillBeRawPtr<FormSubmission> submission)
332{
333    ASSERT(m_frame->page());
334    schedule(adoptPtr(new ScheduledFormSubmission(submission, mustLockBackForwardList(m_frame))));
335}
336
337void NavigationScheduler::scheduleReload()
338{
339    if (!shouldScheduleNavigation())
340        return;
341    if (m_frame->document()->url().isEmpty())
342        return;
343    schedule(adoptPtr(new ScheduledReload));
344}
345
346void NavigationScheduler::scheduleHistoryNavigation(int steps)
347{
348    if (!shouldScheduleNavigation())
349        return;
350
351    // Invalid history navigations (such as history.forward() during a new load) have the side effect of cancelling any scheduled
352    // redirects. We also avoid the possibility of cancelling the current load by avoiding the scheduled redirection altogether.
353    BackForwardClient& backForward = m_frame->page()->backForward();
354    if (steps > backForward.forwardListCount() || -steps > backForward.backListCount()) {
355        cancel();
356        return;
357    }
358
359    // In all other cases, schedule the history traversal to occur asynchronously.
360    if (steps)
361        schedule(adoptPtr(new ScheduledHistoryNavigation(steps)));
362    else
363        schedule(adoptPtr(new ScheduledReload));
364}
365
366void NavigationScheduler::timerFired(Timer<NavigationScheduler>*)
367{
368    if (!m_frame->page())
369        return;
370    if (m_frame->page()->defersLoading()) {
371        InspectorInstrumentation::frameClearedScheduledNavigation(m_frame);
372        return;
373    }
374
375    RefPtrWillBeRawPtr<LocalFrame> protect(m_frame.get());
376
377    OwnPtr<ScheduledNavigation> redirect(m_redirect.release());
378    redirect->fire(m_frame);
379    InspectorInstrumentation::frameClearedScheduledNavigation(m_frame);
380}
381
382void NavigationScheduler::schedule(PassOwnPtr<ScheduledNavigation> redirect)
383{
384    ASSERT(m_frame->page());
385
386    // In a back/forward navigation, we sometimes restore history state to iframes, even though the state was generated
387    // dynamically and JS will try to put something different in the iframe. In this case, we will load stale things
388    // and/or confuse the JS when it shortly thereafter tries to schedule a location change. Let the JS have its way.
389    // FIXME: This check seems out of place.
390    if (!m_frame->loader().stateMachine()->committedFirstRealDocumentLoad() && m_frame->loader().provisionalDocumentLoader()) {
391        RefPtrWillBeRawPtr<LocalFrame> protect(m_frame.get());
392        m_frame->loader().provisionalDocumentLoader()->stopLoading();
393        if (!m_frame->host())
394            return;
395    }
396
397    cancel();
398    m_redirect = redirect;
399    startTimer();
400}
401
402void NavigationScheduler::startTimer()
403{
404    if (!m_redirect)
405        return;
406
407    ASSERT(m_frame->page());
408    if (m_timer.isActive())
409        return;
410    if (!m_redirect->shouldStartTimer(m_frame))
411        return;
412
413    m_timer.startOneShot(m_redirect->delay(), FROM_HERE);
414    InspectorInstrumentation::frameScheduledNavigation(m_frame, m_redirect->delay());
415}
416
417void NavigationScheduler::cancel()
418{
419    if (m_timer.isActive())
420        InspectorInstrumentation::frameClearedScheduledNavigation(m_frame);
421    m_timer.stop();
422    m_redirect.clear();
423}
424
425void NavigationScheduler::trace(Visitor* visitor)
426{
427    visitor->trace(m_frame);
428}
429
430} // namespace blink
431