292763f86913c06ce79e2d7b574ce8196812babc
[WebKit-https.git] / Source / WebCore / page / History.cpp
1 /*
2  * Copyright (C) 2007 Apple Inc.  All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #include "config.h"
27 #include "History.h"
28
29 #include "BackForwardController.h"
30 #include "Document.h"
31 #include "ExceptionCode.h"
32 #include "Frame.h"
33 #include "FrameLoader.h"
34 #include "FrameLoaderClient.h"
35 #include "HistoryController.h"
36 #include "HistoryItem.h"
37 #include "MainFrame.h"
38 #include "Page.h"
39 #include "ScriptController.h"
40 #include "SecurityOrigin.h"
41 #include "SerializedScriptValue.h"
42 #include <wtf/CheckedArithmetic.h>
43 #include <wtf/MainThread.h>
44
45 namespace WebCore {
46
47 History::History(Frame* frame)
48     : DOMWindowProperty(frame)
49     , m_lastStateObjectRequested(nullptr)
50 {
51 }
52
53 unsigned History::length() const
54 {
55     if (!m_frame)
56         return 0;
57     if (!m_frame->page())
58         return 0;
59     return m_frame->page()->backForward().count();
60 }
61
62 PassRefPtr<SerializedScriptValue> History::state()
63 {
64     m_lastStateObjectRequested = stateInternal();
65     return m_lastStateObjectRequested;
66 }
67
68 PassRefPtr<SerializedScriptValue> History::stateInternal() const
69 {
70     if (!m_frame)
71         return 0;
72
73     if (HistoryItem* historyItem = m_frame->loader().history().currentItem())
74         return historyItem->stateObject();
75
76     return 0;
77 }
78
79 bool History::stateChanged() const
80 {
81     return m_lastStateObjectRequested != stateInternal();
82 }
83
84 bool History::isSameAsCurrentState(SerializedScriptValue* state) const
85 {
86     return state == stateInternal().get();
87 }
88
89 void History::back()
90 {
91     go(-1);
92 }
93
94 void History::back(Document& document)
95 {
96     go(document, -1);
97 }
98
99 void History::forward()
100 {
101     go(1);
102 }
103
104 void History::forward(Document& document)
105 {
106     go(document, 1);
107 }
108
109 void History::go(int distance)
110 {
111     if (!m_frame)
112         return;
113
114     m_frame->navigationScheduler().scheduleHistoryNavigation(distance);
115 }
116
117 void History::go(Document& document, int distance)
118 {
119     if (!m_frame)
120         return;
121
122     ASSERT(isMainThread());
123
124     if (!document.canNavigate(m_frame))
125         return;
126
127     m_frame->navigationScheduler().scheduleHistoryNavigation(distance);
128 }
129
130 URL History::urlForState(const String& urlString)
131 {
132     URL baseURL = m_frame->document()->baseURL();
133     if (urlString.isEmpty())
134         return baseURL;
135
136     return URL(baseURL, urlString);
137 }
138
139 void History::stateObjectAdded(PassRefPtr<SerializedScriptValue> data, const String& title, const String& urlString, StateObjectType stateObjectType, ExceptionCodeWithMessage& ec)
140 {
141     // Each unique main-frame document is only allowed to send 64mb of state object payload to the UI client/process.
142     static uint32_t totalStateObjectPayloadLimit = 0x4000000;
143     static double stateObjectTimeSpan = 30.0;
144     static unsigned perStateObjectTimeSpanLimit = 100;
145
146     if (!m_frame || !m_frame->page())
147         return;
148
149     URL fullURL = urlForState(urlString);
150     if (!fullURL.isValid() || !m_frame->document()->securityOrigin()->canRequest(fullURL)) {
151         ec.code = SECURITY_ERR;
152         return;
153     }
154
155     Document* mainDocument = m_frame->page()->mainFrame().document();
156     History* mainHistory = nullptr;
157     if (mainDocument) {
158         if (auto* mainDOMWindow = mainDocument->domWindow())
159             mainHistory = mainDOMWindow->history();
160     }
161
162     if (!mainHistory)
163         return;
164
165     double currentTimestamp = currentTime();
166     if (currentTimestamp - mainHistory->m_currentStateObjectTimeSpanStart > stateObjectTimeSpan) {
167         mainHistory->m_currentStateObjectTimeSpanStart = currentTimestamp;
168         mainHistory->m_currentStateObjectTimeSpanObjectsAdded = 0;
169     }
170     
171     if (mainHistory->m_currentStateObjectTimeSpanObjectsAdded >= perStateObjectTimeSpanLimit) {
172         ec.code = SECURITY_ERR;
173         if (stateObjectType == StateObjectType::Replace)
174             ec.message = String::format("Attempt to use history.replaceState() more than %u times per %f seconds", perStateObjectTimeSpanLimit, stateObjectTimeSpan);
175         else
176             ec.message = String::format("Attempt to use history.pushState() more than %u times per %f seconds", perStateObjectTimeSpanLimit, stateObjectTimeSpan);
177         return;
178     }
179
180     Checked<unsigned> titleSize = title.length();
181     titleSize *= 2;
182
183     Checked<unsigned> urlSize = fullURL.string().length();
184     urlSize *= 2;
185
186     Checked<uint64_t> payloadSize = titleSize;
187     payloadSize += urlSize;
188     payloadSize += data ? data->data().size() : 0;
189
190     Checked<uint64_t> newTotalUsage = mainHistory->m_totalStateObjectUsage;
191
192     if (stateObjectType == StateObjectType::Replace)
193         newTotalUsage -= m_mostRecentStateObjectUsage;
194     newTotalUsage += payloadSize;
195
196     if (newTotalUsage > totalStateObjectPayloadLimit) {
197         ec.code = QUOTA_EXCEEDED_ERR;
198         if (stateObjectType == StateObjectType::Replace)
199             ec.message = ASCIILiteral("Attempt to store more data than allowed using history.replaceState()");
200         else
201             ec.message = ASCIILiteral("Attempt to store more data than allowed using history.pushState()");
202         return;
203     }
204
205     m_mostRecentStateObjectUsage = payloadSize.unsafeGet();
206
207     mainHistory->m_totalStateObjectUsage = newTotalUsage.unsafeGet();
208     ++mainHistory->m_currentStateObjectTimeSpanObjectsAdded;
209
210     if (!urlString.isEmpty())
211         m_frame->document()->updateURLForPushOrReplaceState(fullURL);
212
213     if (stateObjectType == StateObjectType::Push) {
214         m_frame->loader().history().pushState(data, title, fullURL.string());
215         m_frame->loader().client().dispatchDidPushStateWithinPage();
216     } else if (stateObjectType == StateObjectType::Replace) {
217         m_frame->loader().history().replaceState(data, title, fullURL.string());
218         m_frame->loader().client().dispatchDidReplaceStateWithinPage();
219     }
220 }
221
222 } // namespace WebCore