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