Add globally-unique HistoryItem identifiers (and have WebKit2 adopt them).
[WebKit-https.git] / Source / WebKit / UIProcess / API / glib / WebKitWebViewSessionState.cpp
1 /*
2  * Copyright (C) 2016 Igalia S.L.
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public License
15  * along with this library; see the file COPYING.LIB.  If not, write to
16  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17  * Boston, MA 02110-1301, USA.
18  */
19
20 #include "config.h"
21 #include "WebKitWebViewSessionState.h"
22
23 #include "WebKitWebViewSessionStatePrivate.h"
24 #include <WebCore/BackForwardItemIdentifier.h>
25 #include <wtf/glib/GRefPtr.h>
26 #include <wtf/glib/GUniquePtr.h>
27
28 using namespace WebKit;
29
30 struct _WebKitWebViewSessionState {
31     _WebKitWebViewSessionState(SessionState&& state)
32         : sessionState(WTFMove(state))
33         , referenceCount(1)
34     {
35     }
36
37     SessionState sessionState;
38     int referenceCount;
39 };
40
41 G_DEFINE_BOXED_TYPE(WebKitWebViewSessionState, webkit_web_view_session_state, webkit_web_view_session_state_ref, webkit_web_view_session_state_unref)
42
43 static const guint16 g_sessionStateVersion = 1;
44 #define HTTP_BODY_ELEMENT_TYPE_STRING_V1 "(uaysxmxmds)"
45 #define HTTP_BODY_ELEMENT_FORMAT_STRING_V1 "(uay&sxmxmd&s)"
46 #define HTTP_BODY_TYPE_STRING_V1 "m(sa" HTTP_BODY_ELEMENT_TYPE_STRING_V1 ")"
47 #define HTTP_BODY_FORMAT_STRING_V1 "m(&sa" HTTP_BODY_ELEMENT_TYPE_STRING_V1 ")"
48 #define FRAME_STATE_TYPE_STRING_V1  "(ssssasmayxx(ii)d" HTTP_BODY_TYPE_STRING_V1 "av)"
49 #define FRAME_STATE_FORMAT_STRING_V1  "(&s&s&s&sasmayxx(ii)d@" HTTP_BODY_TYPE_STRING_V1 "av)"
50 #define BACK_FORWARD_LIST_ITEM_TYPE_STRING_V1  "(ts" FRAME_STATE_TYPE_STRING_V1 "u)"
51 #define BACK_FORWARD_LIST_ITEM_FORMAT_STRING_V1  "(t&s@" FRAME_STATE_TYPE_STRING_V1 "u)"
52 #define SESSION_STATE_TYPE_STRING_V1  "(qa" BACK_FORWARD_LIST_ITEM_TYPE_STRING_V1 "mu)"
53
54 // Use our own enum types to ensure the serialized format even if the core enums change.
55 enum ExternalURLsPolicy {
56     Allow,
57     AllowExternalSchemes,
58     NotAllow
59 };
60
61 static inline unsigned toExternalURLsPolicy(WebCore::ShouldOpenExternalURLsPolicy policy)
62 {
63     switch (policy) {
64     case WebCore::ShouldOpenExternalURLsPolicy::ShouldAllow:
65         return ExternalURLsPolicy::Allow;
66     case WebCore::ShouldOpenExternalURLsPolicy::ShouldAllowExternalSchemes:
67         return ExternalURLsPolicy::AllowExternalSchemes;
68     case WebCore::ShouldOpenExternalURLsPolicy::ShouldNotAllow:
69         return ExternalURLsPolicy::NotAllow;
70     }
71
72     return ExternalURLsPolicy::NotAllow;
73 }
74
75 static inline WebCore::ShouldOpenExternalURLsPolicy toWebCoreExternalURLsPolicy(unsigned policy)
76 {
77     switch (policy) {
78     case ExternalURLsPolicy::Allow:
79         return WebCore::ShouldOpenExternalURLsPolicy::ShouldAllow;
80     case ExternalURLsPolicy::AllowExternalSchemes:
81         return WebCore::ShouldOpenExternalURLsPolicy::ShouldAllowExternalSchemes;
82     case ExternalURLsPolicy::NotAllow:
83         return WebCore::ShouldOpenExternalURLsPolicy::ShouldNotAllow;
84     }
85
86     return WebCore::ShouldOpenExternalURLsPolicy::ShouldNotAllow;
87 }
88
89 enum HTMLBodyElementType {
90     Data,
91     File,
92     Blob
93 };
94
95 static inline unsigned toHTMLBodyElementType(HTTPBody::Element::Type type)
96 {
97     switch (type) {
98     case HTTPBody::Element::Type::Data:
99         return HTMLBodyElementType::Data;
100     case HTTPBody::Element::Type::File:
101         return HTMLBodyElementType::File;
102     case HTTPBody::Element::Type::Blob:
103         return HTMLBodyElementType::Blob;
104     }
105
106     return HTMLBodyElementType::Data;
107 }
108
109 static inline HTTPBody::Element::Type toHTTPBodyElementType(unsigned type)
110 {
111     switch (type) {
112     case HTMLBodyElementType::Data:
113         return HTTPBody::Element::Type::Data;
114     case HTMLBodyElementType::File:
115         return HTTPBody::Element::Type::File;
116     case HTMLBodyElementType::Blob:
117         return HTTPBody::Element::Type::Blob;
118     }
119
120     return HTTPBody::Element::Type::Data;
121 }
122
123 static inline void encodeHTTPBody(GVariantBuilder* sessionBuilder, const HTTPBody& httpBody)
124 {
125     g_variant_builder_open(sessionBuilder, G_VARIANT_TYPE("(sa" HTTP_BODY_ELEMENT_TYPE_STRING_V1 ")"));
126     g_variant_builder_add(sessionBuilder, "s", httpBody.contentType.utf8().data());
127     g_variant_builder_open(sessionBuilder, G_VARIANT_TYPE("a" HTTP_BODY_ELEMENT_TYPE_STRING_V1));
128     g_variant_builder_open(sessionBuilder, G_VARIANT_TYPE(HTTP_BODY_ELEMENT_TYPE_STRING_V1));
129     for (const auto& element : httpBody.elements) {
130         g_variant_builder_add(sessionBuilder, "u", toHTMLBodyElementType(element.type));
131         g_variant_builder_open(sessionBuilder, G_VARIANT_TYPE("ay"));
132         for (auto item : element.data)
133             g_variant_builder_add(sessionBuilder, "y", item);
134         g_variant_builder_close(sessionBuilder);
135         g_variant_builder_add(sessionBuilder, "s", element.filePath.utf8().data());
136         g_variant_builder_add(sessionBuilder, "x", element.fileStart);
137         if (element.fileLength)
138             g_variant_builder_add(sessionBuilder, "mx", TRUE, element.fileLength.value());
139         else
140             g_variant_builder_add(sessionBuilder, "mx", FALSE);
141         if (element.expectedFileModificationTime)
142             g_variant_builder_add(sessionBuilder, "md", TRUE, element.expectedFileModificationTime.value());
143         else
144             g_variant_builder_add(sessionBuilder, "md", FALSE);
145         g_variant_builder_add(sessionBuilder, "s", element.blobURLString.utf8().data());
146     }
147     g_variant_builder_close(sessionBuilder);
148     g_variant_builder_close(sessionBuilder);
149     g_variant_builder_close(sessionBuilder);
150 }
151
152 static inline void encodeFrameState(GVariantBuilder* sessionBuilder, const FrameState& frameState)
153 {
154     g_variant_builder_add(sessionBuilder, "s", frameState.urlString.utf8().data());
155     g_variant_builder_add(sessionBuilder, "s", frameState.originalURLString.utf8().data());
156     g_variant_builder_add(sessionBuilder, "s", frameState.referrer.utf8().data());
157     g_variant_builder_add(sessionBuilder, "s", frameState.target.utf8().data());
158     g_variant_builder_open(sessionBuilder, G_VARIANT_TYPE("as"));
159     for (const auto& state : frameState.documentState)
160         g_variant_builder_add(sessionBuilder, "s", state.utf8().data());
161     g_variant_builder_close(sessionBuilder);
162     if (!frameState.stateObjectData)
163         g_variant_builder_add(sessionBuilder, "may", FALSE);
164     else {
165         g_variant_builder_open(sessionBuilder, G_VARIANT_TYPE("may"));
166         g_variant_builder_open(sessionBuilder, G_VARIANT_TYPE("ay"));
167         for (auto item : frameState.stateObjectData.value())
168             g_variant_builder_add(sessionBuilder, "y", item);
169         g_variant_builder_close(sessionBuilder);
170         g_variant_builder_close(sessionBuilder);
171     }
172     g_variant_builder_add(sessionBuilder, "x", frameState.documentSequenceNumber);
173     g_variant_builder_add(sessionBuilder, "x", frameState.itemSequenceNumber);
174     g_variant_builder_add(sessionBuilder, "(ii)", frameState.scrollPosition.x(), frameState.scrollPosition.y());
175     g_variant_builder_add(sessionBuilder, "d", static_cast<gdouble>(frameState.pageScaleFactor));
176     if (!frameState.httpBody)
177         g_variant_builder_add(sessionBuilder, HTTP_BODY_TYPE_STRING_V1, FALSE);
178     else {
179         g_variant_builder_open(sessionBuilder, G_VARIANT_TYPE(HTTP_BODY_TYPE_STRING_V1));
180         encodeHTTPBody(sessionBuilder, frameState.httpBody.value());
181         g_variant_builder_close(sessionBuilder);
182     }
183     g_variant_builder_open(sessionBuilder, G_VARIANT_TYPE("av"));
184     for (const auto& child : frameState.children) {
185         GVariantBuilder frameStateBuilder;
186         g_variant_builder_init(&frameStateBuilder, G_VARIANT_TYPE(FRAME_STATE_TYPE_STRING_V1));
187         encodeFrameState(&frameStateBuilder, child);
188         g_variant_builder_add(sessionBuilder, "v", g_variant_builder_end(&frameStateBuilder));
189     }
190     g_variant_builder_close(sessionBuilder);
191 }
192
193 static inline void encodePageState(GVariantBuilder* sessionBuilder, const PageState& pageState)
194 {
195     g_variant_builder_add(sessionBuilder, "s", pageState.title.utf8().data());
196     g_variant_builder_open(sessionBuilder, G_VARIANT_TYPE(FRAME_STATE_TYPE_STRING_V1));
197     encodeFrameState(sessionBuilder, pageState.mainFrameState);
198     g_variant_builder_close(sessionBuilder);
199     g_variant_builder_add(sessionBuilder, "u", toExternalURLsPolicy(pageState.shouldOpenExternalURLsPolicy));
200 }
201
202 static inline void encodeBackForwardListItemState(GVariantBuilder* sessionBuilder, const BackForwardListItemState& item)
203 {
204     g_variant_builder_open(sessionBuilder, G_VARIANT_TYPE(BACK_FORWARD_LIST_ITEM_TYPE_STRING_V1));
205     g_variant_builder_add(sessionBuilder, "t", item.identifier);
206     encodePageState(sessionBuilder, item.pageState);
207     g_variant_builder_close(sessionBuilder);
208 }
209
210 static inline void encodeBackForwardListState(GVariantBuilder* sessionBuilder, const BackForwardListState& backForwardListState)
211 {
212     g_variant_builder_open(sessionBuilder, G_VARIANT_TYPE("a" BACK_FORWARD_LIST_ITEM_TYPE_STRING_V1));
213     for (const auto& item : backForwardListState.items)
214         encodeBackForwardListItemState(sessionBuilder, item);
215     g_variant_builder_close(sessionBuilder);
216
217     if (backForwardListState.currentIndex)
218         g_variant_builder_add(sessionBuilder, "mu", TRUE, backForwardListState.currentIndex.value());
219     else
220         g_variant_builder_add(sessionBuilder, "mu", FALSE);
221 }
222
223 static GBytes* encodeSessionState(const SessionState& sessionState)
224 {
225     GVariantBuilder sessionBuilder;
226     g_variant_builder_init(&sessionBuilder, G_VARIANT_TYPE(SESSION_STATE_TYPE_STRING_V1));
227     g_variant_builder_add(&sessionBuilder, "q", g_sessionStateVersion);
228     encodeBackForwardListState(&sessionBuilder, sessionState.backForwardListState);
229     GRefPtr<GVariant> variant = g_variant_builder_end(&sessionBuilder);
230     return g_variant_get_data_as_bytes(variant.get());
231 }
232
233 static inline bool decodeHTTPBody(GVariant* httpBodyVariant, HTTPBody& httpBody)
234 {
235     gboolean hasHTTPBody;
236     const char* contentType;
237     GUniqueOutPtr<GVariantIter> elementsIter;
238     g_variant_get(httpBodyVariant, HTTP_BODY_FORMAT_STRING_V1, &hasHTTPBody, &contentType, &elementsIter.outPtr());
239     if (!hasHTTPBody)
240         return false;
241     httpBody.contentType = String::fromUTF8(contentType);
242     gsize elementsLength = g_variant_iter_n_children(elementsIter.get());
243     if (!elementsLength)
244         return true;
245     httpBody.elements.reserveInitialCapacity(elementsLength);
246     unsigned type;
247     GVariantIter* dataIter;
248     const char* filePath;
249     gint64 fileStart;
250     gboolean hasFileLength;
251     gint64 fileLength;
252     gboolean hasFileModificationTime;
253     gdouble fileModificationTime;
254     const char* blobURLString;
255     while (g_variant_iter_loop(elementsIter.get(), HTTP_BODY_ELEMENT_FORMAT_STRING_V1, &type, &dataIter, &filePath, &fileStart, &hasFileLength, &fileLength, &hasFileModificationTime, &fileModificationTime, &blobURLString)) {
256         HTTPBody::Element element;
257         element.type = toHTTPBodyElementType(type);
258         if (gsize dataLength = g_variant_iter_n_children(dataIter)) {
259             element.data.reserveInitialCapacity(dataLength);
260             guchar dataValue;
261             while (g_variant_iter_next(dataIter, "y", &dataValue))
262                 element.data.uncheckedAppend(dataValue);
263         }
264         element.filePath = String::fromUTF8(filePath);
265         element.fileStart = fileStart;
266         if (hasFileLength)
267             element.fileLength = fileLength;
268         if (hasFileModificationTime)
269             element.expectedFileModificationTime = fileModificationTime;
270         element.blobURLString = String::fromUTF8(blobURLString);
271
272         httpBody.elements.uncheckedAppend(WTFMove(element));
273     }
274
275     return true;
276 }
277
278 static inline void decodeFrameState(GVariant* frameStateVariant, FrameState& frameState)
279 {
280     const char* urlString;
281     const char* originalURLString;
282     const char* referrer;
283     const char* target;
284     GUniqueOutPtr<GVariantIter> documentStateIter;
285     GUniqueOutPtr<GVariantIter> stateObjectDataIter;
286     gint64 documentSequenceNumber;
287     gint64 itemSequenceNumber;
288     gint32 scrollPositionX, scrollPositionY;
289     gdouble pageScaleFactor;
290     GVariant* httpBodyVariant;
291     GUniqueOutPtr<GVariantIter> childrenIter;
292     g_variant_get(frameStateVariant, FRAME_STATE_FORMAT_STRING_V1, &urlString, &originalURLString, &referrer, &target,
293         &documentStateIter.outPtr(), &stateObjectDataIter.outPtr(), &documentSequenceNumber, &itemSequenceNumber,
294         &scrollPositionX, &scrollPositionY, &pageScaleFactor, &httpBodyVariant, &childrenIter.outPtr());
295     frameState.urlString = String::fromUTF8(urlString);
296     frameState.originalURLString = String::fromUTF8(originalURLString);
297     // frameState.referrer must not be an empty string since we never want to
298     // send an empty Referer header. Bug #159606.
299     if (strlen(referrer))
300         frameState.referrer = String::fromUTF8(referrer);
301     frameState.target = String::fromUTF8(target);
302     if (gsize documentStateLength = g_variant_iter_n_children(documentStateIter.get())) {
303         frameState.documentState.reserveInitialCapacity(documentStateLength);
304         const char* documentStateString;
305         while (g_variant_iter_next(documentStateIter.get(), "&s", &documentStateString))
306             frameState.documentState.uncheckedAppend(String::fromUTF8(documentStateString));
307     }
308     if (stateObjectDataIter) {
309         Vector<uint8_t> stateObjectVector;
310         if (gsize stateObjectDataLength = g_variant_iter_n_children(stateObjectDataIter.get())) {
311             stateObjectVector.reserveInitialCapacity(stateObjectDataLength);
312             guchar stateObjectDataValue;
313             while (g_variant_iter_next(stateObjectDataIter.get(), "y", &stateObjectDataValue))
314                 stateObjectVector.uncheckedAppend(stateObjectDataValue);
315         }
316         frameState.stateObjectData = WTFMove(stateObjectVector);
317     }
318     frameState.documentSequenceNumber = documentSequenceNumber;
319     frameState.itemSequenceNumber = itemSequenceNumber;
320     frameState.scrollPosition.setX(scrollPositionX);
321     frameState.scrollPosition.setY(scrollPositionY);
322     frameState.pageScaleFactor = pageScaleFactor;
323     HTTPBody httpBody;
324     if (decodeHTTPBody(httpBodyVariant, httpBody))
325         frameState.httpBody = WTFMove(httpBody);
326     g_variant_unref(httpBodyVariant);
327     while (GRefPtr<GVariant> child = adoptGRef(g_variant_iter_next_value(childrenIter.get()))) {
328         FrameState childFrameState;
329         GRefPtr<GVariant> childVariant = adoptGRef(g_variant_get_variant(child.get()));
330         decodeFrameState(childVariant.get(), childFrameState);
331         frameState.children.append(WTFMove(childFrameState));
332     }
333 }
334
335 static inline void decodeBackForwardListItemState(GVariantIter* backForwardListStateIter, BackForwardListState& backForwardListState)
336 {
337     gsize backForwardListStateLength = g_variant_iter_n_children(backForwardListStateIter);
338     if (!backForwardListStateLength)
339         return;
340
341     backForwardListState.items.reserveInitialCapacity(backForwardListStateLength);
342     guint64 identifier;
343     const char* title;
344     GVariant* frameStateVariant;
345     unsigned shouldOpenExternalURLsPolicy;
346     while (g_variant_iter_loop(backForwardListStateIter, BACK_FORWARD_LIST_ITEM_FORMAT_STRING_V1, &identifier, &title, &frameStateVariant, &shouldOpenExternalURLsPolicy)) {
347         BackForwardListItemState state;
348         state.identifier = { WebCore::Process::identifier(), generateObjectIdentifier<WebCore::BackForwardItemIdentifier::ItemIdentifierType>() };
349         state.pageState.title = String::fromUTF8(title);
350         decodeFrameState(frameStateVariant, state.pageState.mainFrameState);
351         state.pageState.shouldOpenExternalURLsPolicy = toWebCoreExternalURLsPolicy(shouldOpenExternalURLsPolicy);
352         backForwardListState.items.uncheckedAppend(WTFMove(state));
353     }
354 }
355
356 static bool decodeSessionState(GBytes* data, SessionState& sessionState)
357 {
358     GRefPtr<GVariant> variant = g_variant_new_from_bytes(G_VARIANT_TYPE(SESSION_STATE_TYPE_STRING_V1), data, FALSE);
359     if (!g_variant_is_normal_form(variant.get()))
360         return false;
361
362     guint16 version;
363     GUniqueOutPtr<GVariantIter> backForwardListStateIter;
364     gboolean hasCurrentIndex;
365     guint32 currentIndex;
366     g_variant_get(variant.get(), SESSION_STATE_TYPE_STRING_V1, &version, &backForwardListStateIter.outPtr(), &hasCurrentIndex, &currentIndex);
367     if (!version || version > g_sessionStateVersion)
368         return false;
369
370     decodeBackForwardListItemState(backForwardListStateIter.get(), sessionState.backForwardListState);
371
372     if (hasCurrentIndex)
373         sessionState.backForwardListState.currentIndex = std::min<uint32_t>(currentIndex, sessionState.backForwardListState.items.size() - 1);
374     return true;
375 }
376
377 WebKitWebViewSessionState* webkitWebViewSessionStateCreate(SessionState&& sessionState)
378 {
379     WebKitWebViewSessionState* state = static_cast<WebKitWebViewSessionState*>(fastMalloc(sizeof(WebKitWebViewSessionState)));
380     new (state) WebKitWebViewSessionState(WTFMove(sessionState));
381     return state;
382 }
383
384 const SessionState& webkitWebViewSessionStateGetSessionState(WebKitWebViewSessionState* state)
385 {
386     return state->sessionState;
387 }
388
389 /**
390  * webkit_web_view_session_state_new:
391  * @data: a #GBytes
392  *
393  * Creates a new #WebKitWebViewSessionState from serialized data.
394  *
395  * Returns: (transfer full): a new #WebKitWebViewSessionState, or %NULL if @data doesn't contain a
396  *     valid serialized #WebKitWebViewSessionState.
397  *
398  * Since: 2.12
399  */
400 WebKitWebViewSessionState* webkit_web_view_session_state_new(GBytes* data)
401 {
402     g_return_val_if_fail(data, nullptr);
403
404     SessionState sessionState;
405     if (!decodeSessionState(data, sessionState))
406         return nullptr;
407     return webkitWebViewSessionStateCreate(WTFMove(sessionState));
408 }
409
410 /**
411  * webkit_web_view_session_state_ref:
412  * @state: a #WebKitWebViewSessionState
413  *
414  * Atomically increments the reference count of @state by one. This
415  * function is MT-safe and may be called from any thread.
416  *
417  * Returns: The passed in #WebKitWebViewSessionState
418  *
419  * Since: 2.12
420  */
421 WebKitWebViewSessionState* webkit_web_view_session_state_ref(WebKitWebViewSessionState* state)
422 {
423     g_return_val_if_fail(state, nullptr);
424     g_atomic_int_inc(&state->referenceCount);
425     return state;
426 }
427
428 /**
429  * webkit_web_view_session_state_unref:
430  * @state: a #WebKitWebViewSessionState
431  *
432  * Atomically decrements the reference count of @state by one. If the
433  * reference count drops to 0, all memory allocated by the #WebKitWebViewSessionState is
434  * released. This function is MT-safe and may be called from any thread.
435  *
436  * Since: 2.12
437  */
438 void webkit_web_view_session_state_unref(WebKitWebViewSessionState* state)
439 {
440     g_return_if_fail(state);
441     if (g_atomic_int_dec_and_test(&state->referenceCount)) {
442         state->~WebKitWebViewSessionState();
443         fastFree(state);
444     }
445 }
446
447 /**
448  * webkit_web_view_session_state_serialize:
449  * @state: a #WebKitWebViewSessionState
450  *
451  * Serializes a #WebKitWebViewSessionState.
452  *
453  * Returns: (transfer full): a #GBytes containing the @state serialized.
454  *
455  * Since: 2.12
456  */
457 GBytes* webkit_web_view_session_state_serialize(WebKitWebViewSessionState* state)
458 {
459     g_return_val_if_fail(state, nullptr);
460
461     return encodeSessionState(state->sessionState);
462 }