diff options
Diffstat (limited to 'Source/WebKit2/UIProcess/API/gtk/WebKitWebViewSessionState.cpp')
-rw-r--r-- | Source/WebKit2/UIProcess/API/gtk/WebKitWebViewSessionState.cpp | 461 |
1 files changed, 461 insertions, 0 deletions
diff --git a/Source/WebKit2/UIProcess/API/gtk/WebKitWebViewSessionState.cpp b/Source/WebKit2/UIProcess/API/gtk/WebKitWebViewSessionState.cpp new file mode 100644 index 000000000..325d0141c --- /dev/null +++ b/Source/WebKit2/UIProcess/API/gtk/WebKitWebViewSessionState.cpp @@ -0,0 +1,461 @@ +/* + * Copyright (C) 2016 Igalia S.L. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "config.h" +#include "WebKitWebViewSessionState.h" + +#include "WebKitWebViewSessionStatePrivate.h" +#include <wtf/glib/GRefPtr.h> +#include <wtf/glib/GUniquePtr.h> + +using namespace WebKit; + +struct _WebKitWebViewSessionState { + _WebKitWebViewSessionState(SessionState&& state) + : sessionState(WTFMove(state)) + , referenceCount(1) + { + } + + SessionState sessionState; + int referenceCount; +}; + +G_DEFINE_BOXED_TYPE(WebKitWebViewSessionState, webkit_web_view_session_state, webkit_web_view_session_state_ref, webkit_web_view_session_state_unref) + +static const guint16 g_sessionStateVersion = 1; +#define HTTP_BODY_ELEMENT_TYPE_STRING_V1 "(uaysxmxmds)" +#define HTTP_BODY_ELEMENT_FORMAT_STRING_V1 "(uay&sxmxmd&s)" +#define HTTP_BODY_TYPE_STRING_V1 "m(sa" HTTP_BODY_ELEMENT_TYPE_STRING_V1 ")" +#define HTTP_BODY_FORMAT_STRING_V1 "m(&sa" HTTP_BODY_ELEMENT_TYPE_STRING_V1 ")" +#define FRAME_STATE_TYPE_STRING_V1 "(ssssasmayxx(ii)d" HTTP_BODY_TYPE_STRING_V1 "av)" +#define FRAME_STATE_FORMAT_STRING_V1 "(&s&s&s&sasmayxx(ii)d@" HTTP_BODY_TYPE_STRING_V1 "av)" +#define BACK_FORWARD_LIST_ITEM_TYPE_STRING_V1 "(ts" FRAME_STATE_TYPE_STRING_V1 "u)" +#define BACK_FORWARD_LIST_ITEM_FORMAT_STRING_V1 "(t&s@" FRAME_STATE_TYPE_STRING_V1 "u)" +#define SESSION_STATE_TYPE_STRING_V1 "(qa" BACK_FORWARD_LIST_ITEM_TYPE_STRING_V1 "mu)" + +// Use our own enum types to ensure the serialized format even if the core enums change. +enum ExternalURLsPolicy { + Allow, + AllowExternalSchemes, + NotAllow +}; + +static inline unsigned toExternalURLsPolicy(WebCore::ShouldOpenExternalURLsPolicy policy) +{ + switch (policy) { + case WebCore::ShouldOpenExternalURLsPolicy::ShouldAllow: + return ExternalURLsPolicy::Allow; + case WebCore::ShouldOpenExternalURLsPolicy::ShouldAllowExternalSchemes: + return ExternalURLsPolicy::AllowExternalSchemes; + case WebCore::ShouldOpenExternalURLsPolicy::ShouldNotAllow: + return ExternalURLsPolicy::NotAllow; + } + + return ExternalURLsPolicy::NotAllow; +} + +static inline WebCore::ShouldOpenExternalURLsPolicy toWebCoreExternalURLsPolicy(unsigned policy) +{ + switch (policy) { + case ExternalURLsPolicy::Allow: + return WebCore::ShouldOpenExternalURLsPolicy::ShouldAllow; + case ExternalURLsPolicy::AllowExternalSchemes: + return WebCore::ShouldOpenExternalURLsPolicy::ShouldAllowExternalSchemes; + case ExternalURLsPolicy::NotAllow: + return WebCore::ShouldOpenExternalURLsPolicy::ShouldNotAllow; + } + + return WebCore::ShouldOpenExternalURLsPolicy::ShouldNotAllow; +} + +enum HTMLBodyElementType { + Data, + File, + Blob +}; + +static inline unsigned toHTMLBodyElementType(HTTPBody::Element::Type type) +{ + switch (type) { + case HTTPBody::Element::Type::Data: + return HTMLBodyElementType::Data; + case HTTPBody::Element::Type::File: + return HTMLBodyElementType::File; + case HTTPBody::Element::Type::Blob: + return HTMLBodyElementType::Blob; + } + + return HTMLBodyElementType::Data; +} + +static inline HTTPBody::Element::Type toHTTPBodyElementType(unsigned type) +{ + switch (type) { + case HTMLBodyElementType::Data: + return HTTPBody::Element::Type::Data; + case HTMLBodyElementType::File: + return HTTPBody::Element::Type::File; + case HTMLBodyElementType::Blob: + return HTTPBody::Element::Type::Blob; + } + + return HTTPBody::Element::Type::Data; +} + +static inline void encodeHTTPBody(GVariantBuilder* sessionBuilder, const HTTPBody& httpBody) +{ + g_variant_builder_open(sessionBuilder, G_VARIANT_TYPE("(sa" HTTP_BODY_ELEMENT_TYPE_STRING_V1 ")")); + g_variant_builder_add(sessionBuilder, "s", httpBody.contentType.utf8().data()); + g_variant_builder_open(sessionBuilder, G_VARIANT_TYPE("a" HTTP_BODY_ELEMENT_TYPE_STRING_V1)); + g_variant_builder_open(sessionBuilder, G_VARIANT_TYPE(HTTP_BODY_ELEMENT_TYPE_STRING_V1)); + for (const auto& element : httpBody.elements) { + g_variant_builder_add(sessionBuilder, "u", toHTMLBodyElementType(element.type)); + g_variant_builder_open(sessionBuilder, G_VARIANT_TYPE("ay")); + for (auto item : element.data) + g_variant_builder_add(sessionBuilder, "y", item); + g_variant_builder_close(sessionBuilder); + g_variant_builder_add(sessionBuilder, "s", element.filePath.utf8().data()); + g_variant_builder_add(sessionBuilder, "x", element.fileStart); + if (element.fileLength) + g_variant_builder_add(sessionBuilder, "mx", TRUE, element.fileLength.value()); + else + g_variant_builder_add(sessionBuilder, "mx", FALSE); + if (element.expectedFileModificationTime) + g_variant_builder_add(sessionBuilder, "md", TRUE, element.expectedFileModificationTime.value()); + else + g_variant_builder_add(sessionBuilder, "md", FALSE); + g_variant_builder_add(sessionBuilder, "s", element.blobURLString.utf8().data()); + } + g_variant_builder_close(sessionBuilder); + g_variant_builder_close(sessionBuilder); + g_variant_builder_close(sessionBuilder); +} + +static inline void encodeFrameState(GVariantBuilder* sessionBuilder, const FrameState& frameState) +{ + g_variant_builder_add(sessionBuilder, "s", frameState.urlString.utf8().data()); + g_variant_builder_add(sessionBuilder, "s", frameState.originalURLString.utf8().data()); + g_variant_builder_add(sessionBuilder, "s", frameState.referrer.utf8().data()); + g_variant_builder_add(sessionBuilder, "s", frameState.target.utf8().data()); + g_variant_builder_open(sessionBuilder, G_VARIANT_TYPE("as")); + for (const auto& state : frameState.documentState) + g_variant_builder_add(sessionBuilder, "s", state.utf8().data()); + g_variant_builder_close(sessionBuilder); + if (!frameState.stateObjectData) + g_variant_builder_add(sessionBuilder, "may", FALSE); + else { + g_variant_builder_open(sessionBuilder, G_VARIANT_TYPE("may")); + g_variant_builder_open(sessionBuilder, G_VARIANT_TYPE("ay")); + for (auto item : frameState.stateObjectData.value()) + g_variant_builder_add(sessionBuilder, "y", item); + g_variant_builder_close(sessionBuilder); + g_variant_builder_close(sessionBuilder); + } + g_variant_builder_add(sessionBuilder, "x", frameState.documentSequenceNumber); + g_variant_builder_add(sessionBuilder, "x", frameState.itemSequenceNumber); + g_variant_builder_add(sessionBuilder, "(ii)", frameState.scrollPosition.x(), frameState.scrollPosition.y()); + g_variant_builder_add(sessionBuilder, "d", frameState.pageScaleFactor); + if (!frameState.httpBody) + g_variant_builder_add(sessionBuilder, HTTP_BODY_TYPE_STRING_V1, FALSE); + else { + g_variant_builder_open(sessionBuilder, G_VARIANT_TYPE(HTTP_BODY_TYPE_STRING_V1)); + encodeHTTPBody(sessionBuilder, frameState.httpBody.value()); + g_variant_builder_close(sessionBuilder); + } + g_variant_builder_open(sessionBuilder, G_VARIANT_TYPE("av")); + for (const auto& child : frameState.children) { + GVariantBuilder frameStateBuilder; + g_variant_builder_init(&frameStateBuilder, G_VARIANT_TYPE(FRAME_STATE_TYPE_STRING_V1)); + encodeFrameState(&frameStateBuilder, child); + g_variant_builder_add(sessionBuilder, "v", g_variant_builder_end(&frameStateBuilder)); + } + g_variant_builder_close(sessionBuilder); +} + +static inline void encodePageState(GVariantBuilder* sessionBuilder, const PageState& pageState) +{ + g_variant_builder_add(sessionBuilder, "s", pageState.title.utf8().data()); + g_variant_builder_open(sessionBuilder, G_VARIANT_TYPE(FRAME_STATE_TYPE_STRING_V1)); + encodeFrameState(sessionBuilder, pageState.mainFrameState); + g_variant_builder_close(sessionBuilder); + g_variant_builder_add(sessionBuilder, "u", toExternalURLsPolicy(pageState.shouldOpenExternalURLsPolicy)); +} + +static inline void encodeBackForwardListItemState(GVariantBuilder* sessionBuilder, const BackForwardListItemState& item) +{ + g_variant_builder_open(sessionBuilder, G_VARIANT_TYPE(BACK_FORWARD_LIST_ITEM_TYPE_STRING_V1)); + g_variant_builder_add(sessionBuilder, "t", item.identifier); + encodePageState(sessionBuilder, item.pageState); + g_variant_builder_close(sessionBuilder); +} + +static inline void encodeBackForwardListState(GVariantBuilder* sessionBuilder, const BackForwardListState& backForwardListState) +{ + g_variant_builder_open(sessionBuilder, G_VARIANT_TYPE("a" BACK_FORWARD_LIST_ITEM_TYPE_STRING_V1)); + for (const auto& item : backForwardListState.items) + encodeBackForwardListItemState(sessionBuilder, item); + g_variant_builder_close(sessionBuilder); + + if (backForwardListState.currentIndex) + g_variant_builder_add(sessionBuilder, "mu", TRUE, backForwardListState.currentIndex.value()); + else + g_variant_builder_add(sessionBuilder, "mu", FALSE); +} + +static GBytes* encodeSessionState(const SessionState& sessionState) +{ + GVariantBuilder sessionBuilder; + g_variant_builder_init(&sessionBuilder, G_VARIANT_TYPE(SESSION_STATE_TYPE_STRING_V1)); + g_variant_builder_add(&sessionBuilder, "q", g_sessionStateVersion); + encodeBackForwardListState(&sessionBuilder, sessionState.backForwardListState); + GRefPtr<GVariant> variant = g_variant_builder_end(&sessionBuilder); + return g_variant_get_data_as_bytes(variant.get()); +} + +static inline bool decodeHTTPBody(GVariant* httpBodyVariant, HTTPBody& httpBody) +{ + gboolean hasHTTPBody; + const char* contentType; + GUniqueOutPtr<GVariantIter> elementsIter; + g_variant_get(httpBodyVariant, HTTP_BODY_FORMAT_STRING_V1, &hasHTTPBody, &contentType, &elementsIter.outPtr()); + if (!hasHTTPBody) + return false; + httpBody.contentType = String::fromUTF8(contentType); + gsize elementsLength = g_variant_iter_n_children(elementsIter.get()); + if (!elementsLength) + return true; + httpBody.elements.reserveInitialCapacity(elementsLength); + unsigned type; + GVariantIter* dataIter; + const char* filePath; + gint64 fileStart; + gboolean hasFileLength; + gint64 fileLength; + gboolean hasFileModificationTime; + gdouble fileModificationTime; + const char* blobURLString; + while (g_variant_iter_loop(elementsIter.get(), HTTP_BODY_ELEMENT_FORMAT_STRING_V1, &type, &dataIter, &filePath, &fileStart, &hasFileLength, &fileLength, &hasFileModificationTime, &fileModificationTime, &blobURLString)) { + HTTPBody::Element element; + element.type = toHTTPBodyElementType(type); + if (gsize dataLength = g_variant_iter_n_children(dataIter)) { + element.data.reserveInitialCapacity(dataLength); + guchar dataValue; + while (g_variant_iter_next(dataIter, "y", &dataValue)) + element.data.uncheckedAppend(dataValue); + } + element.filePath = String::fromUTF8(filePath); + element.fileStart = fileStart; + if (hasFileLength) + element.fileLength = fileLength; + if (hasFileModificationTime) + element.expectedFileModificationTime = fileModificationTime; + element.blobURLString = String::fromUTF8(blobURLString); + + httpBody.elements.uncheckedAppend(WTFMove(element)); + } + + return true; +} + +static inline void decodeFrameState(GVariant* frameStateVariant, FrameState& frameState) +{ + const char* urlString; + const char* originalURLString; + const char* referrer; + const char* target; + GUniqueOutPtr<GVariantIter> documentStateIter; + GUniqueOutPtr<GVariantIter> stateObjectDataIter; + gint64 documentSequenceNumber; + gint64 itemSequenceNumber; + gint32 scrollPositionX, scrollPositionY; + gdouble pageScaleFactor; + GVariant* httpBodyVariant; + GUniqueOutPtr<GVariantIter> childrenIter; + g_variant_get(frameStateVariant, FRAME_STATE_FORMAT_STRING_V1, &urlString, &originalURLString, &referrer, &target, + &documentStateIter.outPtr(), &stateObjectDataIter.outPtr(), &documentSequenceNumber, &itemSequenceNumber, + &scrollPositionX, &scrollPositionY, &pageScaleFactor, &httpBodyVariant, &childrenIter.outPtr()); + frameState.urlString = String::fromUTF8(urlString); + frameState.originalURLString = String::fromUTF8(originalURLString); + // frameState.referrer must not be an empty string since we never want to + // send an empty Referer header. Bug #159606. + if (strlen(referrer)) + frameState.referrer = String::fromUTF8(referrer); + frameState.target = String::fromUTF8(target); + if (gsize documentStateLength = g_variant_iter_n_children(documentStateIter.get())) { + frameState.documentState.reserveInitialCapacity(documentStateLength); + const char* documentStateString; + while (g_variant_iter_next(documentStateIter.get(), "&s", &documentStateString)) + frameState.documentState.uncheckedAppend(String::fromUTF8(documentStateString)); + } + if (stateObjectDataIter) { + Vector<uint8_t> stateObjectVector; + if (gsize stateObjectDataLength = g_variant_iter_n_children(stateObjectDataIter.get())) { + stateObjectVector.reserveInitialCapacity(stateObjectDataLength); + guchar stateObjectDataValue; + while (g_variant_iter_next(stateObjectDataIter.get(), "y", &stateObjectDataValue)) + stateObjectVector.uncheckedAppend(stateObjectDataValue); + } + frameState.stateObjectData = WTFMove(stateObjectVector); + } + frameState.documentSequenceNumber = documentSequenceNumber; + frameState.itemSequenceNumber = itemSequenceNumber; + frameState.scrollPosition.setX(scrollPositionX); + frameState.scrollPosition.setY(scrollPositionY); + frameState.pageScaleFactor = pageScaleFactor; + HTTPBody httpBody; + if (decodeHTTPBody(httpBodyVariant, httpBody)) + frameState.httpBody = WTFMove(httpBody); + g_variant_unref(httpBodyVariant); + while (GRefPtr<GVariant> child = adoptGRef(g_variant_iter_next_value(childrenIter.get()))) { + FrameState childFrameState; + GRefPtr<GVariant> childVariant = adoptGRef(g_variant_get_variant(child.get())); + decodeFrameState(childVariant.get(), childFrameState); + frameState.children.append(WTFMove(childFrameState)); + } +} + +static inline void decodeBackForwardListItemState(GVariantIter* backForwardListStateIter, BackForwardListState& backForwardListState) +{ + gsize backForwardListStateLength = g_variant_iter_n_children(backForwardListStateIter); + if (!backForwardListStateLength) + return; + + backForwardListState.items.reserveInitialCapacity(backForwardListStateLength); + guint64 identifier; + const char* title; + GVariant* frameStateVariant; + unsigned shouldOpenExternalURLsPolicy; + while (g_variant_iter_loop(backForwardListStateIter, BACK_FORWARD_LIST_ITEM_FORMAT_STRING_V1, &identifier, &title, &frameStateVariant, &shouldOpenExternalURLsPolicy)) { + BackForwardListItemState state; + state.identifier = identifier; + state.pageState.title = String::fromUTF8(title); + decodeFrameState(frameStateVariant, state.pageState.mainFrameState); + state.pageState.shouldOpenExternalURLsPolicy = toWebCoreExternalURLsPolicy(shouldOpenExternalURLsPolicy); + backForwardListState.items.uncheckedAppend(WTFMove(state)); + } +} + +static bool decodeSessionState(GBytes* data, SessionState& sessionState) +{ + GRefPtr<GVariant> variant = g_variant_new_from_bytes(G_VARIANT_TYPE(SESSION_STATE_TYPE_STRING_V1), data, FALSE); + if (!g_variant_is_normal_form(variant.get())) + return false; + + guint16 version; + GUniqueOutPtr<GVariantIter> backForwardListStateIter; + gboolean hasCurrentIndex; + guint32 currentIndex; + g_variant_get(variant.get(), SESSION_STATE_TYPE_STRING_V1, &version, &backForwardListStateIter.outPtr(), &hasCurrentIndex, ¤tIndex); + if (!version || version > g_sessionStateVersion) + return false; + + decodeBackForwardListItemState(backForwardListStateIter.get(), sessionState.backForwardListState); + + if (hasCurrentIndex) + sessionState.backForwardListState.currentIndex = currentIndex; + return true; +} + +WebKitWebViewSessionState* webkitWebViewSessionStateCreate(SessionState&& sessionState) +{ + WebKitWebViewSessionState* state = static_cast<WebKitWebViewSessionState*>(fastMalloc(sizeof(WebKitWebViewSessionState))); + new (state) WebKitWebViewSessionState(WTFMove(sessionState)); + return state; +} + +const SessionState& webkitWebViewSessionStateGetSessionState(WebKitWebViewSessionState* state) +{ + return state->sessionState; +} + +/** + * webkit_web_view_session_state_new: + * @data: a #GBytes + * + * Creates a new #WebKitWebViewSessionState from serialized data. + * + * Returns: (transfer full): a new #WebKitWebViewSessionState, or %NULL if @data doesn't contain a + * valid serialized #WebKitWebViewSessionState. + * + * Since: 2.12 + */ +WebKitWebViewSessionState* webkit_web_view_session_state_new(GBytes* data) +{ + g_return_val_if_fail(data, nullptr); + + SessionState sessionState; + if (!decodeSessionState(data, sessionState)) + return nullptr; + return webkitWebViewSessionStateCreate(WTFMove(sessionState)); +} + +/** + * webkit_web_view_session_state_ref: + * @state: a #WebKitWebViewSessionState + * + * Atomically increments the reference count of @state by one. This + * function is MT-safe and may be called from any thread. + * + * Returns: The passed in #WebKitWebViewSessionState + * + * Since: 2.12 + */ +WebKitWebViewSessionState* webkit_web_view_session_state_ref(WebKitWebViewSessionState* state) +{ + g_return_val_if_fail(state, nullptr); + g_atomic_int_inc(&state->referenceCount); + return state; +} + +/** + * webkit_web_view_session_state_unref: + * @state: a #WebKitWebViewSessionState + * + * Atomically decrements the reference count of @state by one. If the + * reference count drops to 0, all memory allocated by the #WebKitWebViewSessionState is + * released. This function is MT-safe and may be called from any thread. + * + * Since: 2.12 + */ +void webkit_web_view_session_state_unref(WebKitWebViewSessionState* state) +{ + g_return_if_fail(state); + if (g_atomic_int_dec_and_test(&state->referenceCount)) { + state->~WebKitWebViewSessionState(); + fastFree(state); + } +} + +/** + * webkit_web_view_session_state_serialize: + * @state: a #WebKitWebViewSessionState + * + * Serializes a #WebKitWebViewSessionState. + * + * Returns: (transfer full): a #GBytes containing the @state serialized. + * + * Since: 2.12 + */ +GBytes* webkit_web_view_session_state_serialize(WebKitWebViewSessionState* state) +{ + g_return_val_if_fail(state, nullptr); + + return encodeSessionState(state->sessionState); +} |