WebKitWebView.cpp   [plain text]


/*
 * Copyright (C) 2011 Igalia S.L.
 * Portions Copyright (c) 2011 Motorola Mobility, Inc.  All rights reserved.
 * Copyright (C) 2014 Collabora Ltd.
 * Copyright (C) 2017 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 "WebKitWebView.h"

#include "APIData.h"
#include "APINavigation.h"
#include "APISerializedScriptValue.h"
#include "DataReference.h"
#include "ImageOptions.h"
#include "WebCertificateInfo.h"
#include "WebContextMenuItem.h"
#include "WebContextMenuItemData.h"
#include "WebKitAuthenticationRequestPrivate.h"
#include "WebKitBackForwardListPrivate.h"
#include "WebKitContextMenuClient.h"
#include "WebKitContextMenuItemPrivate.h"
#include "WebKitContextMenuPrivate.h"
#include "WebKitDownloadPrivate.h"
#include "WebKitEditingCommands.h"
#include "WebKitEditorStatePrivate.h"
#include "WebKitEnumTypes.h"
#include "WebKitError.h"
#include "WebKitFaviconDatabasePrivate.h"
#include "WebKitFormClient.h"
#include "WebKitHitTestResultPrivate.h"
#include "WebKitIconLoadingClient.h"
#include "WebKitInstallMissingMediaPluginsPermissionRequestPrivate.h"
#include "WebKitJavascriptResultPrivate.h"
#include "WebKitNavigationClient.h"
#include "WebKitNotificationPrivate.h"
#include "WebKitPrivate.h"
#include "WebKitResponsePolicyDecision.h"
#include "WebKitScriptDialogPrivate.h"
#include "WebKitSettingsPrivate.h"
#include "WebKitUIClient.h"
#include "WebKitURIRequestPrivate.h"
#include "WebKitURIResponsePrivate.h"
#include "WebKitWebContextPrivate.h"
#include "WebKitWebResourcePrivate.h"
#include "WebKitWebViewPrivate.h"
#include "WebKitWebViewSessionStatePrivate.h"
#include "WebKitWebsiteDataManagerPrivate.h"
#include "WebKitWindowPropertiesPrivate.h"
#include <JavaScriptCore/APICast.h>
#include <JavaScriptCore/JSRetainPtr.h>
#include <jsc/JSCContextPrivate.h>
#include <WebCore/CertificateInfo.h>
#include <WebCore/GUniquePtrSoup.h>
#include <WebCore/JSDOMExceptionHandling.h>
#include <WebCore/RefPtrCairo.h>
#include <WebCore/URLSoup.h>
#include <glib/gi18n-lib.h>
#include <wtf/SetForScope.h>
#include <wtf/URL.h>
#include <wtf/glib/GRefPtr.h>
#include <wtf/glib/WTFGType.h>
#include <wtf/text/CString.h>
#include <wtf/text/StringBuilder.h>

#if PLATFORM(GTK)
#include "WebKitPrintOperationPrivate.h"
#include "WebKitWebInspectorPrivate.h"
#include "WebKitWebViewBasePrivate.h"
#include <WebCore/GUniquePtrGtk.h>
#endif

#if PLATFORM(WPE)
#include "APIViewClient.h"
#include "WPEView.h"
#include "WebKitWebViewBackendPrivate.h"
#endif

#if USE(LIBNOTIFY)
#include <libnotify/notify.h>
#endif

using namespace WebKit;
using namespace WebCore;

/**
 * SECTION: WebKitWebView
 * @Short_description: The central class of the WPE WebKit and WebKitGTK APIs
 * @Title: WebKitWebView
 *
 * #WebKitWebView is the central class of the WPE WebKit and WebKitGTK
 * APIs. It is responsible for managing the drawing of the content and
 * forwarding of events. You can load any URI into the #WebKitWebView or
 * a data string. With #WebKitSettings you can control various aspects
 * of the rendering and loading of the content.
 *
 * Note that in WebKitGTK, #WebKitWebView is scrollable by itself, so
 * you don't need to embed it in a #GtkScrolledWindow.
 */

enum {
    LOAD_CHANGED,
    LOAD_FAILED,
    LOAD_FAILED_WITH_TLS_ERRORS,

    CREATE,
    READY_TO_SHOW,
    RUN_AS_MODAL,
    CLOSE,

    SCRIPT_DIALOG,

    DECIDE_POLICY,
    PERMISSION_REQUEST,

    MOUSE_TARGET_CHANGED,

#if PLATFORM(GTK)
    PRINT,
#endif

    RESOURCE_LOAD_STARTED,

    ENTER_FULLSCREEN,
    LEAVE_FULLSCREEN,

    RUN_FILE_CHOOSER,

    CONTEXT_MENU,
    CONTEXT_MENU_DISMISSED,

    SUBMIT_FORM,

    INSECURE_CONTENT_DETECTED,

#if PLATFORM(GTK)
    WEB_PROCESS_CRASHED,
#endif
    WEB_PROCESS_TERMINATED,

    AUTHENTICATE,

    SHOW_NOTIFICATION,

#if PLATFORM(GTK)
    RUN_COLOR_CHOOSER,

    SHOW_OPTION_MENU,
#endif

    LAST_SIGNAL
};

enum {
    PROP_0,

#if PLATFORM(WPE)
    PROP_BACKEND,
#endif

    PROP_WEB_CONTEXT,
    PROP_RELATED_VIEW,
    PROP_SETTINGS,
    PROP_USER_CONTENT_MANAGER,
    PROP_TITLE,
    PROP_ESTIMATED_LOAD_PROGRESS,

#if PLATFORM(GTK)
    PROP_FAVICON,
#endif

    PROP_URI,
    PROP_ZOOM_LEVEL,
    PROP_IS_LOADING,
    PROP_IS_PLAYING_AUDIO,
    PROP_IS_EPHEMERAL,
    PROP_IS_CONTROLLED_BY_AUTOMATION,
    PROP_EDITABLE
};

typedef HashMap<uint64_t, GRefPtr<WebKitWebResource> > LoadingResourcesMap;
typedef HashMap<uint64_t, GRefPtr<GTask> > SnapshotResultsMap;

class PageLoadStateObserver;

#if PLATFORM(WPE)
static unsigned frameDisplayCallbackID;
struct FrameDisplayedCallback {
    FrameDisplayedCallback(WebKitFrameDisplayedCallback callback, gpointer userData = nullptr, GDestroyNotify destroyNotifyFunction = nullptr)
        : id(++frameDisplayCallbackID)
        , callback(callback)
        , userData(userData)
        , destroyNotifyFunction(destroyNotifyFunction)
    {
    }

    ~FrameDisplayedCallback()
    {
        if (destroyNotifyFunction)
            destroyNotifyFunction(userData);
    }

    FrameDisplayedCallback(FrameDisplayedCallback&&) = default;
    FrameDisplayedCallback(const FrameDisplayedCallback&) = delete;
    FrameDisplayedCallback& operator=(const FrameDisplayedCallback&) = delete;

    unsigned id { 0 };
    WebKitFrameDisplayedCallback callback { nullptr };
    gpointer userData { nullptr };
    GDestroyNotify destroyNotifyFunction { nullptr };
};
#endif // PLATFORM(WPE)

struct _WebKitWebViewPrivate {
    ~_WebKitWebViewPrivate()
    {
        // For modal dialogs, make sure the main loop is stopped when finalizing the webView.
        if (modalLoop && g_main_loop_is_running(modalLoop.get()))
            g_main_loop_quit(modalLoop.get());
    }

#if PLATFORM(WPE)
    GRefPtr<WebKitWebViewBackend> backend;
    std::unique_ptr<WKWPE::View> view;
    Vector<FrameDisplayedCallback> frameDisplayedCallbacks;
    bool inFrameDisplayed;
    HashSet<unsigned> frameDisplayedCallbacksToRemove;
#endif

    WebKitWebView* relatedView;
    CString title;
    CString customTextEncoding;
    CString activeURI;
    bool isActiveURIChangeBlocked;
    bool isLoading;
    bool isEphemeral;
    bool isControlledByAutomation;

    std::unique_ptr<PageLoadStateObserver> loadObserver;

    GRefPtr<WebKitBackForwardList> backForwardList;
    GRefPtr<WebKitSettings> settings;
    GRefPtr<WebKitUserContentManager> userContentManager;
    GRefPtr<WebKitWebContext> context;
    GRefPtr<WebKitWindowProperties> windowProperties;
    GRefPtr<WebKitEditorState> editorState;

    GRefPtr<GMainLoop> modalLoop;

    GRefPtr<WebKitHitTestResult> mouseTargetHitTestResult;
    OptionSet<WebEvent::Modifier> mouseTargetModifiers;

    GRefPtr<WebKitFindController> findController;

    GRefPtr<WebKitWebResource> mainResource;
    LoadingResourcesMap loadingResourcesMap;

    WebKitScriptDialog* currentScriptDialog;

#if PLATFORM(GTK)
    GRefPtr<JSCContext> jsContext;

    GRefPtr<WebKitWebInspector> inspector;

    RefPtr<cairo_surface_t> favicon;
    GRefPtr<GCancellable> faviconCancellable;

    CString faviconURI;
    unsigned long faviconChangedHandlerID;

    SnapshotResultsMap snapshotResultsMap;
#endif

    GRefPtr<WebKitAuthenticationRequest> authenticationRequest;

    GRefPtr<WebKitWebsiteDataManager> websiteDataManager;
};

static guint signals[LAST_SIGNAL] = { 0, };

#if PLATFORM(GTK)
WEBKIT_DEFINE_TYPE(WebKitWebView, webkit_web_view, WEBKIT_TYPE_WEB_VIEW_BASE)
#elif PLATFORM(WPE)
WEBKIT_DEFINE_TYPE(WebKitWebView, webkit_web_view, G_TYPE_OBJECT)
#endif

static inline WebPageProxy& getPage(WebKitWebView* webView)
{
#if PLATFORM(GTK)
    auto* page = webkitWebViewBaseGetPage(reinterpret_cast<WebKitWebViewBase*>(webView));
    ASSERT(page);
    return *page;
#elif PLATFORM(WPE)
    ASSERT(webView->priv->view);
    return webView->priv->view->page();
#endif
}

static void webkitWebViewSetIsLoading(WebKitWebView* webView, bool isLoading)
{
    if (webView->priv->isLoading == isLoading)
        return;

    webView->priv->isLoading = isLoading;
    g_object_notify(G_OBJECT(webView), "is-loading");
}

void webkitWebViewIsPlayingAudioChanged(WebKitWebView* webView)
{
    g_object_notify(G_OBJECT(webView), "is-playing-audio");
}

class PageLoadStateObserver final : public PageLoadState::Observer {
public:
    PageLoadStateObserver(WebKitWebView* webView)
        : m_webView(webView)
    {
    }

private:
    void willChangeIsLoading() override
    {
        g_object_freeze_notify(G_OBJECT(m_webView));
    }
    void didChangeIsLoading() override
    {
        webkitWebViewSetIsLoading(m_webView, getPage(m_webView).pageLoadState().isLoading());
        g_object_thaw_notify(G_OBJECT(m_webView));
    }

    void willChangeTitle() override
    {
        g_object_freeze_notify(G_OBJECT(m_webView));
    }
    void didChangeTitle() override
    {
        m_webView->priv->title = getPage(m_webView).pageLoadState().title().utf8();
        g_object_notify(G_OBJECT(m_webView), "title");
        g_object_thaw_notify(G_OBJECT(m_webView));
    }

    void willChangeActiveURL() override
    {
        if (m_webView->priv->isActiveURIChangeBlocked)
            return;
        g_object_freeze_notify(G_OBJECT(m_webView));
    }
    void didChangeActiveURL() override
    {
        if (m_webView->priv->isActiveURIChangeBlocked)
            return;
        m_webView->priv->activeURI = getPage(m_webView).pageLoadState().activeURL().utf8();
        g_object_notify(G_OBJECT(m_webView), "uri");
        g_object_thaw_notify(G_OBJECT(m_webView));
    }

    void willChangeHasOnlySecureContent() override { }
    void didChangeHasOnlySecureContent() override { }

    void willChangeEstimatedProgress() override
    {
        g_object_freeze_notify(G_OBJECT(m_webView));
    }
    void didChangeEstimatedProgress() override
    {
        g_object_notify(G_OBJECT(m_webView), "estimated-load-progress");
        g_object_thaw_notify(G_OBJECT(m_webView));
    }

    void willChangeCanGoBack() override { }
    void didChangeCanGoBack() override { }
    void willChangeCanGoForward() override { }
    void didChangeCanGoForward() override { }
    void willChangeNetworkRequestsInProgress() override { }
    void didChangeNetworkRequestsInProgress() override { }
    void willChangeCertificateInfo() override { }
    void didChangeCertificateInfo() override { }
    void willChangeWebProcessIsResponsive() override { }
    void didChangeWebProcessIsResponsive() override { }
    void didSwapWebProcesses() override { };

    WebKitWebView* m_webView;
};

#if PLATFORM(WPE)
class WebViewClient final : public API::ViewClient {
public:
    explicit WebViewClient(WebKitWebView* webView)
        : m_webView(webView)
    {
    }

private:
    void handleDownloadRequest(WKWPE::View&, DownloadProxy& downloadProxy) override
    {
        webkitWebViewHandleDownloadRequest(m_webView, &downloadProxy);
    }

    void frameDisplayed(WKWPE::View&) override
    {
        {
            SetForScope<bool> inFrameDisplayedGuard(m_webView->priv->inFrameDisplayed, true);
            for (const auto& callback : m_webView->priv->frameDisplayedCallbacks) {
                if (!m_webView->priv->frameDisplayedCallbacksToRemove.contains(callback.id))
                    callback.callback(m_webView, callback.userData);
            }
        }

        while (!m_webView->priv->frameDisplayedCallbacksToRemove.isEmpty()) {
            auto id = m_webView->priv->frameDisplayedCallbacksToRemove.takeAny();
            m_webView->priv->frameDisplayedCallbacks.removeFirstMatching([id](const auto& item) {
                return item.id == id;
            });
        }
    }

    void willStartLoad(WKWPE::View&) override
    {
        webkitWebViewWillStartLoad(m_webView);
    }

    WebKitWebView* m_webView;
};
#endif

static gboolean webkitWebViewLoadFail(WebKitWebView* webView, WebKitLoadEvent, const char* failingURI, GError* error)
{
    if (g_error_matches(error, WEBKIT_NETWORK_ERROR, WEBKIT_NETWORK_ERROR_CANCELLED)
        || g_error_matches(error, WEBKIT_POLICY_ERROR, WEBKIT_POLICY_ERROR_FRAME_LOAD_INTERRUPTED_BY_POLICY_CHANGE)
        || g_error_matches(error, WEBKIT_PLUGIN_ERROR, WEBKIT_PLUGIN_ERROR_WILL_HANDLE_LOAD))
        return FALSE;

    GUniquePtr<char> htmlString(g_strdup_printf("<html><body>%s</body></html>", error->message));
    webkit_web_view_load_alternate_html(webView, htmlString.get(), failingURI, 0);

    return TRUE;
}

#if PLATFORM(GTK)
static GtkWidget* webkitWebViewCreate(WebKitWebView*, WebKitNavigationAction*)
{
    return nullptr;
}
#else
static WebKitWebView* webkitWebViewCreate(WebKitWebView*, WebKitNavigationAction*)
{
    return nullptr;
}
#endif

static gboolean webkitWebViewDecidePolicy(WebKitWebView*, WebKitPolicyDecision* decision, WebKitPolicyDecisionType decisionType)
{
    if (decisionType != WEBKIT_POLICY_DECISION_TYPE_RESPONSE) {
        webkit_policy_decision_use(decision);
        return TRUE;
    }

    WebKitURIResponse* response = webkit_response_policy_decision_get_response(WEBKIT_RESPONSE_POLICY_DECISION(decision));
    const ResourceResponse& resourceResponse = webkitURIResponseGetResourceResponse(response);
    if (resourceResponse.isAttachment()) {
        webkit_policy_decision_download(decision);
        return TRUE;
    }

    if (webkit_response_policy_decision_is_mime_type_supported(WEBKIT_RESPONSE_POLICY_DECISION(decision)))
        webkit_policy_decision_use(decision);
    else
        webkit_policy_decision_ignore(decision);

    return TRUE;
}

static gboolean webkitWebViewPermissionRequest(WebKitWebView*, WebKitPermissionRequest* request)
{
    webkit_permission_request_deny(request);
    return TRUE;
}

static void allowModalDialogsChanged(WebKitSettings* settings, GParamSpec*, WebKitWebView* webView)
{
    getPage(webView).setCanRunModal(webkit_settings_get_allow_modal_dialogs(settings));
}

static void zoomTextOnlyChanged(WebKitSettings* settings, GParamSpec*, WebKitWebView* webView)
{
    auto& page = getPage(webView);
    gboolean zoomTextOnly = webkit_settings_get_zoom_text_only(settings);
    gdouble pageZoomLevel = zoomTextOnly ? 1 : page.textZoomFactor();
    gdouble textZoomLevel = zoomTextOnly ? page.pageZoomFactor() : 1;
    page.setPageAndTextZoomFactors(pageZoomLevel, textZoomLevel);
}

static void userAgentChanged(WebKitSettings* settings, GParamSpec*, WebKitWebView* webView)
{
    getPage(webView).setCustomUserAgent(String::fromUTF8(webkit_settings_get_user_agent(settings)));
}

#if PLATFORM(GTK)
static void enableBackForwardNavigationGesturesChanged(WebKitSettings* settings, GParamSpec*, WebKitWebView* webView)
{
    gboolean enable = webkit_settings_get_enable_back_forward_navigation_gestures(settings);
    webkitWebViewBaseSetEnableBackForwardNavigationGesture(WEBKIT_WEB_VIEW_BASE(webView), enable);
}

static void webkitWebViewUpdateFavicon(WebKitWebView* webView, cairo_surface_t* favicon)
{
    WebKitWebViewPrivate* priv = webView->priv;
    if (priv->favicon.get() == favicon)
        return;

    priv->favicon = favicon;
    g_object_notify(G_OBJECT(webView), "favicon");
}

static void webkitWebViewCancelFaviconRequest(WebKitWebView* webView)
{
    if (!webView->priv->faviconCancellable)
        return;

    g_cancellable_cancel(webView->priv->faviconCancellable.get());
    webView->priv->faviconCancellable = 0;
}

static void gotFaviconCallback(GObject* object, GAsyncResult* result, gpointer userData)
{
    GUniqueOutPtr<GError> error;
    RefPtr<cairo_surface_t> favicon = adoptRef(webkit_favicon_database_get_favicon_finish(WEBKIT_FAVICON_DATABASE(object), result, &error.outPtr()));
    if (g_error_matches(error.get(), G_IO_ERROR, G_IO_ERROR_CANCELLED))
        return;

    WebKitWebView* webView = WEBKIT_WEB_VIEW(userData);
    webkitWebViewUpdateFavicon(webView, favicon.get());
    webView->priv->faviconCancellable = 0;
}

static void webkitWebViewRequestFavicon(WebKitWebView* webView)
{
    webkitWebViewCancelFaviconRequest(webView);

    WebKitWebViewPrivate* priv = webView->priv;
    priv->faviconCancellable = adoptGRef(g_cancellable_new());
    WebKitFaviconDatabase* database = webkit_web_context_get_favicon_database(priv->context.get());
    webkit_favicon_database_get_favicon(database, priv->activeURI.data(), priv->faviconCancellable.get(), gotFaviconCallback, webView);
}

static void webkitWebViewUpdateFaviconURI(WebKitWebView* webView, const char* faviconURI)
{
    if (webView->priv->faviconURI == faviconURI)
        return;

    webView->priv->faviconURI = faviconURI;
    webkitWebViewRequestFavicon(webView);
}

static void faviconChangedCallback(WebKitFaviconDatabase*, const char* pageURI, const char* faviconURI, WebKitWebView* webView)
{
    if (webView->priv->activeURI != pageURI)
        return;

    webkitWebViewUpdateFaviconURI(webView, faviconURI);
}
#endif

static bool webkitWebViewIsConstructed(WebKitWebView* webView)
{
    // The loadObserver is set in webkitWebViewConstructed, right after the
    // WebPageProxy is created, so we use it to check if the view has been
    // constructed instead of adding a boolean member only for that.
    return !!webView->priv->loadObserver;
}

static void webkitWebViewUpdateSettings(WebKitWebView* webView)
{
    // The "settings" property is set on construction, and in that
    // case webkit_web_view_set_settings() will be called *before* the
    // WebPageProxy has been created so we should do an early return.
    if (!webkitWebViewIsConstructed(webView))
        return;

    auto& page = getPage(webView);
    WebKitSettings* settings = webView->priv->settings.get();
    page.setPreferences(*webkitSettingsGetPreferences(settings));
    page.setCanRunModal(webkit_settings_get_allow_modal_dialogs(settings));
    page.setCustomUserAgent(String::fromUTF8(webkit_settings_get_user_agent(settings)));
#if PLATFORM(GTK)
    enableBackForwardNavigationGesturesChanged(settings, nullptr, webView);
#endif

    g_signal_connect(settings, "notify::allow-modal-dialogs", G_CALLBACK(allowModalDialogsChanged), webView);
    g_signal_connect(settings, "notify::zoom-text-only", G_CALLBACK(zoomTextOnlyChanged), webView);
    g_signal_connect(settings, "notify::user-agent", G_CALLBACK(userAgentChanged), webView);
#if PLATFORM(GTK)
    g_signal_connect(settings, "notify::enable-back-forward-navigation-gestures", G_CALLBACK(enableBackForwardNavigationGesturesChanged), webView);
#endif
}

static void webkitWebViewDisconnectSettingsSignalHandlers(WebKitWebView* webView)
{
    if (!webkitWebViewIsConstructed(webView))
        return;

    WebKitSettings* settings = webView->priv->settings.get();
    g_signal_handlers_disconnect_by_func(settings, reinterpret_cast<gpointer>(allowModalDialogsChanged), webView);
    g_signal_handlers_disconnect_by_func(settings, reinterpret_cast<gpointer>(zoomTextOnlyChanged), webView);
    g_signal_handlers_disconnect_by_func(settings, reinterpret_cast<gpointer>(userAgentChanged), webView);
#if PLATFORM(GTK)
    g_signal_handlers_disconnect_by_func(settings, reinterpret_cast<gpointer>(enableBackForwardNavigationGesturesChanged), webView);
#endif
}

#if PLATFORM(GTK)
static void webkitWebViewWatchForChangesInFavicon(WebKitWebView* webView)
{
    WebKitWebViewPrivate* priv = webView->priv;
    if (priv->faviconChangedHandlerID)
        return;

    WebKitFaviconDatabase* database = webkit_web_context_get_favicon_database(priv->context.get());
    priv->faviconChangedHandlerID = g_signal_connect(database, "favicon-changed", G_CALLBACK(faviconChangedCallback), webView);
}

static void webkitWebViewDisconnectFaviconDatabaseSignalHandlers(WebKitWebView* webView)
{
    WebKitWebViewPrivate* priv = webView->priv;
    if (priv->faviconChangedHandlerID)
        g_signal_handler_disconnect(webkit_web_context_get_favicon_database(priv->context.get()), priv->faviconChangedHandlerID);
    priv->faviconChangedHandlerID = 0;
}
#endif

#if USE(LIBNOTIFY)
static const char* gNotifyNotificationID = "wk-notify-notification";

static void notifyNotificationClosed(NotifyNotification*, WebKitNotification* webNotification)
{
    g_object_set_data(G_OBJECT(webNotification), gNotifyNotificationID, nullptr);
    webkit_notification_close(webNotification);
}

static void notifyNotificationClicked(NotifyNotification*, char*, WebKitNotification* webNotification)
{
    webkit_notification_clicked(webNotification);
}

static void webNotificationClosed(WebKitNotification* webNotification)
{
    NotifyNotification* notification = NOTIFY_NOTIFICATION(g_object_get_data(G_OBJECT(webNotification), gNotifyNotificationID));
    if (!notification)
        return;

    notify_notification_close(notification, nullptr);
    g_object_set_data(G_OBJECT(webNotification), gNotifyNotificationID, nullptr);
}
#endif // USE(LIBNOTIFY)

static gboolean webkitWebViewShowNotification(WebKitWebView*, WebKitNotification* webNotification)
{
#if USE(LIBNOTIFY)
    if (!notify_is_initted())
        notify_init(g_get_prgname());

    NotifyNotification* notification = NOTIFY_NOTIFICATION(g_object_get_data(G_OBJECT(webNotification), gNotifyNotificationID));
    if (!notification) {
        notification = notify_notification_new(webkit_notification_get_title(webNotification),
            webkit_notification_get_body(webNotification), nullptr);

        notify_notification_add_action(notification, "default", _("Acknowledge"), NOTIFY_ACTION_CALLBACK(notifyNotificationClicked), webNotification, nullptr);

        g_signal_connect_object(notification, "closed", G_CALLBACK(notifyNotificationClosed), webNotification, static_cast<GConnectFlags>(0));
        g_signal_connect(webNotification, "closed", G_CALLBACK(webNotificationClosed), nullptr);
        g_object_set_data_full(G_OBJECT(webNotification), gNotifyNotificationID, notification, static_cast<GDestroyNotify>(g_object_unref));
    } else {
        notify_notification_update(notification, webkit_notification_get_title(webNotification),
            webkit_notification_get_body(webNotification), nullptr);
    }

    notify_notification_show(notification, nullptr);
    return TRUE;
#else
    UNUSED_PARAM(webNotification);
    return FALSE;
#endif
}

static void webkitWebViewConstructed(GObject* object)
{
    G_OBJECT_CLASS(webkit_web_view_parent_class)->constructed(object);

    WebKitWebView* webView = WEBKIT_WEB_VIEW(object);
    WebKitWebViewPrivate* priv = webView->priv;
    if (priv->relatedView) {
        priv->context = webkit_web_view_get_context(priv->relatedView);
        priv->isEphemeral = webkit_web_view_is_ephemeral(priv->relatedView);
        priv->isControlledByAutomation = webkit_web_view_is_controlled_by_automation(priv->relatedView);
    } else if (!priv->context)
        priv->context = webkit_web_context_get_default();
    else if (!priv->isEphemeral)
        priv->isEphemeral = webkit_web_context_is_ephemeral(priv->context.get());

    if (!priv->settings)
        priv->settings = adoptGRef(webkit_settings_new());

    if (!priv->userContentManager)
        priv->userContentManager = adoptGRef(webkit_user_content_manager_new());

    if (priv->isEphemeral && !webkit_web_context_is_ephemeral(priv->context.get())) {
        priv->websiteDataManager = adoptGRef(webkit_website_data_manager_new_ephemeral());
        webkitWebsiteDataManagerAddProcessPool(priv->websiteDataManager.get(), webkitWebContextGetProcessPool(priv->context.get()));
    }

    webkitWebContextCreatePageForWebView(priv->context.get(), webView, priv->userContentManager.get(), priv->relatedView);

    priv->loadObserver = std::make_unique<PageLoadStateObserver>(webView);
    getPage(webView).pageLoadState().addObserver(*priv->loadObserver);

    // The related view is only valid during the construction.
    priv->relatedView = nullptr;

    attachNavigationClientToView(webView);
    attachUIClientToView(webView);
    attachContextMenuClientToView(webView);
    attachFormClientToView(webView);

#if PLATFORM(GTK)
    attachIconLoadingClientToView(webView);
#endif

#if PLATFORM(WPE)
    priv->view->setClient(std::make_unique<WebViewClient>(webView));
#endif

    // This needs to be after attachUIClientToView() because WebPageProxy::setUIClient() calls setCanRunModal() with true.
    // See https://bugs.webkit.org/show_bug.cgi?id=135412.
    webkitWebViewUpdateSettings(webView);

    priv->backForwardList = adoptGRef(webkitBackForwardListCreate(&getPage(webView).backForwardList()));
    priv->windowProperties = adoptGRef(webkitWindowPropertiesCreate());
}

static void webkitWebViewSetProperty(GObject* object, guint propId, const GValue* value, GParamSpec* paramSpec)
{
    WebKitWebView* webView = WEBKIT_WEB_VIEW(object);

    switch (propId) {
#if PLATFORM(WPE)
    case PROP_BACKEND: {
        gpointer backend = g_value_get_boxed(value);
        webView->priv->backend = backend ? adoptGRef(static_cast<WebKitWebViewBackend*>(backend)) : nullptr;
        break;
    }
#endif
    case PROP_WEB_CONTEXT: {
        gpointer webContext = g_value_get_object(value);
        webView->priv->context = webContext ? WEBKIT_WEB_CONTEXT(webContext) : nullptr;
        break;
    }
    case PROP_RELATED_VIEW: {
        gpointer relatedView = g_value_get_object(value);
        webView->priv->relatedView = relatedView ? WEBKIT_WEB_VIEW(relatedView) : nullptr;
        break;
    }
    case PROP_SETTINGS: {
        if (gpointer settings = g_value_get_object(value))
            webkit_web_view_set_settings(webView, WEBKIT_SETTINGS(settings));
        break;
    }
    case PROP_USER_CONTENT_MANAGER: {
        gpointer userContentManager = g_value_get_object(value);
        webView->priv->userContentManager = userContentManager ? WEBKIT_USER_CONTENT_MANAGER(userContentManager) : nullptr;
        break;
    }
    case PROP_ZOOM_LEVEL:
        webkit_web_view_set_zoom_level(webView, g_value_get_double(value));
        break;
    case PROP_IS_EPHEMERAL:
        webView->priv->isEphemeral = g_value_get_boolean(value);
        break;
    case PROP_IS_CONTROLLED_BY_AUTOMATION:
        webView->priv->isControlledByAutomation = g_value_get_boolean(value);
        break;
    case PROP_EDITABLE:
        webkit_web_view_set_editable(webView, g_value_get_boolean(value));
        break;
    default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propId, paramSpec);
    }
}

static void webkitWebViewGetProperty(GObject* object, guint propId, GValue* value, GParamSpec* paramSpec)
{
    WebKitWebView* webView = WEBKIT_WEB_VIEW(object);

    switch (propId) {
#if PLATFORM(WPE)
    case PROP_BACKEND:
        g_value_set_static_boxed(value, webView->priv->backend.get());
        break;
#endif
    case PROP_WEB_CONTEXT:
        g_value_set_object(value, webView->priv->context.get());
        break;
    case PROP_SETTINGS:
        g_value_set_object(value, webkit_web_view_get_settings(webView));
        break;
    case PROP_USER_CONTENT_MANAGER:
        g_value_set_object(value, webkit_web_view_get_user_content_manager(webView));
        break;
    case PROP_TITLE:
        g_value_set_string(value, webView->priv->title.data());
        break;
    case PROP_ESTIMATED_LOAD_PROGRESS:
        g_value_set_double(value, webkit_web_view_get_estimated_load_progress(webView));
        break;
#if PLATFORM(GTK)
    case PROP_FAVICON:
        g_value_set_pointer(value, webkit_web_view_get_favicon(webView));
        break;
#endif
    case PROP_URI:
        g_value_set_string(value, webkit_web_view_get_uri(webView));
        break;
    case PROP_ZOOM_LEVEL:
        g_value_set_double(value, webkit_web_view_get_zoom_level(webView));
        break;
    case PROP_IS_LOADING:
        g_value_set_boolean(value, webkit_web_view_is_loading(webView));
        break;
    case PROP_IS_PLAYING_AUDIO:
        g_value_set_boolean(value, webkit_web_view_is_playing_audio(webView));
        break;
    case PROP_IS_EPHEMERAL:
        g_value_set_boolean(value, webkit_web_view_is_ephemeral(webView));
        break;
    case PROP_IS_CONTROLLED_BY_AUTOMATION:
        g_value_set_boolean(value, webkit_web_view_is_controlled_by_automation(webView));
        break;
    case PROP_EDITABLE:
        g_value_set_boolean(value, webkit_web_view_is_editable(webView));
        break;
    default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propId, paramSpec);
    }
}

static void webkitWebViewDispose(GObject* object)
{
    WebKitWebView* webView = WEBKIT_WEB_VIEW(object);

#if PLATFORM(GTK)
    webkitWebViewCancelFaviconRequest(webView);
    webkitWebViewDisconnectFaviconDatabaseSignalHandlers(webView);
#endif

    webkitWebViewDisconnectSettingsSignalHandlers(webView);

    if (webView->priv->loadObserver) {
        getPage(webView).pageLoadState().removeObserver(*webView->priv->loadObserver);
        webView->priv->loadObserver.reset();

        // We notify the context here to ensure it's called only once. Ideally we should
        // call this in finalize, not dispose, but finalize is used internally and we don't
        // have access to the instance pointer from the private struct destructor.
        webkitWebContextWebViewDestroyed(webView->priv->context.get(), webView);
    }

    if (webView->priv->websiteDataManager) {
        webkitWebsiteDataManagerRemoveProcessPool(webView->priv->websiteDataManager.get(), webkitWebContextGetProcessPool(webView->priv->context.get()));
        webView->priv->websiteDataManager = nullptr;
    }

    if (webView->priv->currentScriptDialog) {
        webkit_script_dialog_close(webView->priv->currentScriptDialog);
        ASSERT(!webView->priv->currentScriptDialog);
    }

#if PLATFORM(WPE)
    webView->priv->view->close();
#endif

    G_OBJECT_CLASS(webkit_web_view_parent_class)->dispose(object);
}

static gboolean webkitWebViewAccumulatorObjectHandled(GSignalInvocationHint*, GValue* returnValue, const GValue* handlerReturn, gpointer)
{
    void* object = g_value_get_object(handlerReturn);
    if (object)
        g_value_set_object(returnValue, object);

    return !object;
}

static void webkit_web_view_class_init(WebKitWebViewClass* webViewClass)
{
    GObjectClass* gObjectClass = G_OBJECT_CLASS(webViewClass);

    gObjectClass->constructed = webkitWebViewConstructed;
    gObjectClass->set_property = webkitWebViewSetProperty;
    gObjectClass->get_property = webkitWebViewGetProperty;
    gObjectClass->dispose = webkitWebViewDispose;

    webViewClass->load_failed = webkitWebViewLoadFail;
    webViewClass->create = webkitWebViewCreate;
    webViewClass->script_dialog = webkitWebViewScriptDialog;
    webViewClass->decide_policy = webkitWebViewDecidePolicy;
    webViewClass->permission_request = webkitWebViewPermissionRequest;
    webViewClass->run_file_chooser = webkitWebViewRunFileChooser;
    webViewClass->authenticate = webkitWebViewAuthenticate;
    webViewClass->show_notification = webkitWebViewShowNotification;

#if PLATFORM(WPE)
    /**
     * WebKitWebView:backend:
     *
     * The #WebKitWebViewBackend of the view.
     *
     * since: 2.20
     */
    g_object_class_install_property(
        gObjectClass,
        PROP_BACKEND,
        g_param_spec_boxed(
            "backend",
            _("Backend"),
            _("The backend for the web view"),
            WEBKIT_TYPE_WEB_VIEW_BACKEND,
            static_cast<GParamFlags>(WEBKIT_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)));
#endif

    /**
     * WebKitWebView:web-context:
     *
     * The #WebKitWebContext of the view.
     */
    g_object_class_install_property(gObjectClass,
                                    PROP_WEB_CONTEXT,
                                    g_param_spec_object("web-context",
                                                        _("Web Context"),
                                                        _("The web context for the view"),
                                                        WEBKIT_TYPE_WEB_CONTEXT,
                                                        static_cast<GParamFlags>(WEBKIT_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)));
    /**
     * WebKitWebView:related-view:
     *
     * The related #WebKitWebView used when creating the view to share the
     * same web process. This property is not readable because the related
     * web view is only valid during the object construction.
     *
     * Since: 2.4
     */
    g_object_class_install_property(
        gObjectClass,
        PROP_RELATED_VIEW,
        g_param_spec_object(
            "related-view",
            _("Related WebView"),
            _("The related WebKitWebView used when creating the view to share the same web process"),
            WEBKIT_TYPE_WEB_VIEW,
            static_cast<GParamFlags>(WEBKIT_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY)));

    /**
     * WebKitWebView:settings:
     *
     * The #WebKitSettings of the view.
     *
     * Since: 2.6
     */
    g_object_class_install_property(
        gObjectClass,
        PROP_SETTINGS,
        g_param_spec_object(
            "settings",
            _("WebView settings"),
            _("The WebKitSettings of the view"),
            WEBKIT_TYPE_SETTINGS,
            static_cast<GParamFlags>(WEBKIT_PARAM_WRITABLE | G_PARAM_CONSTRUCT)));

    /**
     * WebKitWebView:user-content-manager:
     *
     * The #WebKitUserContentManager of the view.
     *
     * Since: 2.6
     */
    g_object_class_install_property(
        gObjectClass,
        PROP_USER_CONTENT_MANAGER,
        g_param_spec_object(
            "user-content-manager",
            _("WebView user content manager"),
            _("The WebKitUserContentManager of the view"),
            WEBKIT_TYPE_USER_CONTENT_MANAGER,
            static_cast<GParamFlags>(WEBKIT_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)));

    /**
     * WebKitWebView:title:
     *
     * The main frame document title of this #WebKitWebView. If
     * the title has not been received yet, it will be %NULL.
     */
    g_object_class_install_property(gObjectClass,
                                    PROP_TITLE,
                                    g_param_spec_string("title",
                                                        _("Title"),
                                                        _("Main frame document title"),
                                                        0,
                                                        WEBKIT_PARAM_READABLE));

    /**
     * WebKitWebView:estimated-load-progress:
     *
     * An estimate of the percent completion for the current loading operation.
     * This value will range from 0.0 to 1.0 and, once a load completes,
     * will remain at 1.0 until a new load starts, at which point it
     * will be reset to 0.0.
     * The value is an estimate based on the total number of bytes expected
     * to be received for a document, including all its possible subresources
     * and child documents.
     */
    g_object_class_install_property(gObjectClass,
                                    PROP_ESTIMATED_LOAD_PROGRESS,
                                    g_param_spec_double("estimated-load-progress",
                                                        _("Estimated Load Progress"),
                                                        _("An estimate of the percent completion for a document load"),
                                                        0.0, 1.0, 0.0,
                                                        WEBKIT_PARAM_READABLE));
#if PLATFORM(GTK)
    /**
     * WebKitWebView:favicon:
     *
     * The favicon currently associated to the #WebKitWebView.
     * See webkit_web_view_get_favicon() for more details.
     */
    g_object_class_install_property(gObjectClass,
                                    PROP_FAVICON,
                                    g_param_spec_pointer("favicon",
                                                         _("Favicon"),
                                                         _("The favicon associated to the view, if any"),
                                                         WEBKIT_PARAM_READABLE));
#endif

    /**
     * WebKitWebView:uri:
     *
     * The current active URI of the #WebKitWebView.
     * See webkit_web_view_get_uri() for more details.
     */
    g_object_class_install_property(gObjectClass,
                                    PROP_URI,
                                    g_param_spec_string("uri",
                                                        _("URI"),
                                                        _("The current active URI of the view"),
                                                        0,
                                                        WEBKIT_PARAM_READABLE));

    /**
     * WebKitWebView:zoom-level:
     *
     * The zoom level of the #WebKitWebView content.
     * See webkit_web_view_set_zoom_level() for more details.
     */
    g_object_class_install_property(
        gObjectClass,
        PROP_ZOOM_LEVEL,
        g_param_spec_double(
            "zoom-level",
            _("Zoom level"),
            _("The zoom level of the view content"),
            0, G_MAXDOUBLE, 1,
            WEBKIT_PARAM_READWRITE));

    /**
     * WebKitWebView:is-loading:
     *
     * Whether the #WebKitWebView is currently loading a page. This property becomes
     * %TRUE as soon as a new load operation is requested and before the
     * #WebKitWebView::load-changed signal is emitted with %WEBKIT_LOAD_STARTED and
     * at that point the active URI is the requested one.
     * When the load operation finishes the property is set to %FALSE before
     * #WebKitWebView::load-changed is emitted with %WEBKIT_LOAD_FINISHED.
     */
    g_object_class_install_property(
        gObjectClass,
        PROP_IS_LOADING,
        g_param_spec_boolean(
            "is-loading",
            _("Is Loading"),
            _("Whether the view is loading a page"),
            FALSE,
            WEBKIT_PARAM_READABLE));

    /**
     * WebKitWebView:is-playing-audio:
     *
     * Whether the #WebKitWebView is currently playing audio from a page.
     * This property becomes %TRUE as soon as web content starts playing any
     * kind of audio. When a page is no longer playing any kind of sound,
     * the property is set back to %FALSE.
     *
     * Since: 2.8
     */
    g_object_class_install_property(
        gObjectClass,
        PROP_IS_PLAYING_AUDIO,
        g_param_spec_boolean(
            "is-playing-audio",
            "Is Playing Audio",
            _("Whether the view is playing audio"),
            FALSE,
            WEBKIT_PARAM_READABLE));

    /**
     * WebKitWebView:is-ephemeral:
     *
     * Whether the #WebKitWebView is ephemeral. An ephemeral web view never writes
     * website data to the client storage, no matter what #WebKitWebsiteDataManager
     * its context is using. This is normally used to implement private browsing mode.
     * This is a %G_PARAM_CONSTRUCT_ONLY property, so you have to create a ephemeral
     * #WebKitWebView and it can't be changed. Note that all #WebKitWebView<!-- -->s
     * created with an ephemeral #WebKitWebContext will be ephemeral automatically.
     * See also webkit_web_context_new_ephemeral().
     *
     * Since: 2.16
     */
    g_object_class_install_property(
        gObjectClass,
        PROP_IS_EPHEMERAL,
        g_param_spec_boolean(
            "is-ephemeral",
            "Is Ephemeral",
            _("Whether the web view is ephemeral"),
            FALSE,
            static_cast<GParamFlags>(WEBKIT_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)));

    /**
     * WebKitWebView:is-controlled-by-automation:
     *
     * Whether the #WebKitWebView is controlled by automation. This should only be used when
     * creating a new #WebKitWebView as a response to #WebKitAutomationSession::create-web-view
     * signal request.
     *
     * Since: 2.18
     */
    g_object_class_install_property(
        gObjectClass,
        PROP_IS_CONTROLLED_BY_AUTOMATION,
        g_param_spec_boolean(
            "is-controlled-by-automation",
            "Is Controlled By Automation",
            _("Whether the web view is controlled by automation"),
            FALSE,
            static_cast<GParamFlags>(WEBKIT_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)));

    /**
     * WebKitWebView:editable:
     *
     * Whether the pages loaded inside #WebKitWebView are editable. For more
     * information see webkit_web_view_set_editable().
     *
     * Since: 2.8
     */
    g_object_class_install_property(
        gObjectClass,
        PROP_EDITABLE,
        g_param_spec_boolean(
            "editable",
            _("Editable"),
            _("Whether the content can be modified by the user."),
            FALSE,
            WEBKIT_PARAM_READWRITE));

    /**
     * WebKitWebView::load-changed:
     * @web_view: the #WebKitWebView on which the signal is emitted
     * @load_event: the #WebKitLoadEvent
     *
     * Emitted when a load operation in @web_view changes.
     * The signal is always emitted with %WEBKIT_LOAD_STARTED when a
     * new load request is made and %WEBKIT_LOAD_FINISHED when the load
     * finishes successfully or due to an error. When the ongoing load
     * operation fails #WebKitWebView::load-failed signal is emitted
     * before #WebKitWebView::load-changed is emitted with
     * %WEBKIT_LOAD_FINISHED.
     * If a redirection is received from the server, this signal is emitted
     * with %WEBKIT_LOAD_REDIRECTED after the initial emission with
     * %WEBKIT_LOAD_STARTED and before %WEBKIT_LOAD_COMMITTED.
     * When the page content starts arriving the signal is emitted with
     * %WEBKIT_LOAD_COMMITTED event.
     *
     * You can handle this signal and use a switch to track any ongoing
     * load operation.
     *
     * <informalexample><programlisting>
     * static void web_view_load_changed (WebKitWebView  *web_view,
     *                                    WebKitLoadEvent load_event,
     *                                    gpointer        user_data)
     * {
     *     switch (load_event) {
     *     case WEBKIT_LOAD_STARTED:
     *         /<!-- -->* New load, we have now a provisional URI *<!-- -->/
     *         provisional_uri = webkit_web_view_get_uri (web_view);
     *         /<!-- -->* Here we could start a spinner or update the
     *          <!-- -->* location bar with the provisional URI *<!-- -->/
     *         break;
     *     case WEBKIT_LOAD_REDIRECTED:
     *         redirected_uri = webkit_web_view_get_uri (web_view);
     *         break;
     *     case WEBKIT_LOAD_COMMITTED:
     *         /<!-- -->* The load is being performed. Current URI is
     *          <!-- -->* the final one and it won't change unless a new
     *          <!-- -->* load is requested or a navigation within the
     *          <!-- -->* same page is performed *<!-- -->/
     *         uri = webkit_web_view_get_uri (web_view);
     *         break;
     *     case WEBKIT_LOAD_FINISHED:
     *         /<!-- -->* Load finished, we can now stop the spinner *<!-- -->/
     *         break;
     *     }
     * }
     * </programlisting></informalexample>
     */
    signals[LOAD_CHANGED] =
        g_signal_new("load-changed",
                     G_TYPE_FROM_CLASS(webViewClass),
                     G_SIGNAL_RUN_LAST,
                     G_STRUCT_OFFSET(WebKitWebViewClass, load_changed),
                     0, 0,
                     g_cclosure_marshal_VOID__ENUM,
                     G_TYPE_NONE, 1,
                     WEBKIT_TYPE_LOAD_EVENT);

    /**
     * WebKitWebView::load-failed:
     * @web_view: the #WebKitWebView on which the signal is emitted
     * @load_event: the #WebKitLoadEvent of the load operation
     * @failing_uri: the URI that failed to load
     * @error: the #GError that was triggered
     *
     * Emitted when an error occurs during a load operation.
     * If the error happened when starting to load data for a page
     * @load_event will be %WEBKIT_LOAD_STARTED. If it happened while
     * loading a committed data source @load_event will be %WEBKIT_LOAD_COMMITTED.
     * Since a load error causes the load operation to finish, the signal
     * WebKitWebView::load-changed will always be emitted with
     * %WEBKIT_LOAD_FINISHED event right after this one.
     *
     * By default, if the signal is not handled, a stock error page will be displayed.
     * You need to handle the signal if you want to provide your own error page.
     *
     * Returns: %TRUE to stop other handlers from being invoked for the event.
     *    %FALSE to propagate the event further.
     */
    signals[LOAD_FAILED] =
        g_signal_new(
            "load-failed",
            G_TYPE_FROM_CLASS(webViewClass),
            G_SIGNAL_RUN_LAST,
            G_STRUCT_OFFSET(WebKitWebViewClass, load_failed),
            g_signal_accumulator_true_handled, 0,
            g_cclosure_marshal_generic,
            G_TYPE_BOOLEAN, 3,
            WEBKIT_TYPE_LOAD_EVENT,
            G_TYPE_STRING,
            G_TYPE_ERROR | G_SIGNAL_TYPE_STATIC_SCOPE);

    /**
     * WebKitWebView::load-failed-with-tls-errors:
     * @web_view: the #WebKitWebView on which the signal is emitted
     * @failing_uri: the URI that failed to load
     * @certificate: a #GTlsCertificate
     * @errors: a #GTlsCertificateFlags with the verification status of @certificate
     *
     * Emitted when a TLS error occurs during a load operation.
     * To allow an exception for this @certificate
     * and the host of @failing_uri use webkit_web_context_allow_tls_certificate_for_host().
     *
     * To handle this signal asynchronously you should call g_object_ref() on @certificate
     * and return %TRUE.
     *
     * If %FALSE is returned, #WebKitWebView::load-failed will be emitted. The load
     * will finish regardless of the returned value.
     *
     * Returns: %TRUE to stop other handlers from being invoked for the event.
     *   %FALSE to propagate the event further.
     *
     * Since: 2.6
     */
    signals[LOAD_FAILED_WITH_TLS_ERRORS] =
        g_signal_new("load-failed-with-tls-errors",
            G_TYPE_FROM_CLASS(webViewClass),
            G_SIGNAL_RUN_LAST,
            G_STRUCT_OFFSET(WebKitWebViewClass, load_failed_with_tls_errors),
            g_signal_accumulator_true_handled, 0 /* accumulator data */,
            g_cclosure_marshal_generic,
            G_TYPE_BOOLEAN, 3,
            G_TYPE_STRING,
            G_TYPE_TLS_CERTIFICATE,
            G_TYPE_TLS_CERTIFICATE_FLAGS);

    /**
     * WebKitWebView::create:
     * @web_view: the #WebKitWebView on which the signal is emitted
     * @navigation_action: a #WebKitNavigationAction
     *
     * Emitted when the creation of a new #WebKitWebView is requested.
     * If this signal is handled the signal handler should return the
     * newly created #WebKitWebView.
     *
     * The #WebKitNavigationAction parameter contains information about the
     * navigation action that triggered this signal.
     *
     * When using %WEBKIT_PROCESS_MODEL_MULTIPLE_SECONDARY_PROCESSES
     * process model, the new #WebKitWebView should be related to
     * @web_view to share the same web process, see webkit_web_view_new_with_related_view()
     * for more details.
     *
     * The new #WebKitWebView should not be displayed to the user
     * until the #WebKitWebView::ready-to-show signal is emitted.
     *
     * Returns: (transfer full): a newly allocated #WebKitWebView widget
     *    or %NULL to propagate the event further.
     */
    signals[CREATE] = g_signal_new(
        "create",
        G_TYPE_FROM_CLASS(webViewClass),
        G_SIGNAL_RUN_LAST,
        G_STRUCT_OFFSET(WebKitWebViewClass, create),
        webkitWebViewAccumulatorObjectHandled, 0,
        g_cclosure_marshal_generic,
#if PLATFORM(GTK)
        GTK_TYPE_WIDGET,
#else
        WEBKIT_TYPE_WEB_VIEW,
#endif
        1,
        WEBKIT_TYPE_NAVIGATION_ACTION | G_SIGNAL_TYPE_STATIC_SCOPE);

    /**
     * WebKitWebView::ready-to-show:
     * @web_view: the #WebKitWebView on which the signal is emitted
     *
     * Emitted after #WebKitWebView::create on the newly created #WebKitWebView
     * when it should be displayed to the user. When this signal is emitted
     * all the information about how the window should look, including
     * size, position, whether the location, status and scrollbars
     * should be displayed, is already set on the #WebKitWindowProperties
     * of @web_view. See also webkit_web_view_get_window_properties().
     */
    signals[READY_TO_SHOW] =
        g_signal_new("ready-to-show",
                     G_TYPE_FROM_CLASS(webViewClass),
                     G_SIGNAL_RUN_LAST,
                     G_STRUCT_OFFSET(WebKitWebViewClass, ready_to_show),
                     0, 0,
                     g_cclosure_marshal_VOID__VOID,
                     G_TYPE_NONE, 0);

     /**
     * WebKitWebView::run-as-modal:
     * @web_view: the #WebKitWebView on which the signal is emitted
     *
     * Emitted after #WebKitWebView::ready-to-show on the newly
     * created #WebKitWebView when JavaScript code calls
     * <function>window.showModalDialog</function>. The purpose of
     * this signal is to allow the client application to prepare the
     * new view to behave as modal. Once the signal is emitted a new
     * main loop will be run to block user interaction in the parent
     * #WebKitWebView until the new dialog is closed.
     */
    signals[RUN_AS_MODAL] =
            g_signal_new("run-as-modal",
                         G_TYPE_FROM_CLASS(webViewClass),
                         G_SIGNAL_RUN_LAST,
                         G_STRUCT_OFFSET(WebKitWebViewClass, run_as_modal),
                         0, 0,
                         g_cclosure_marshal_VOID__VOID,
                         G_TYPE_NONE, 0);

    /**
     * WebKitWebView::close:
     * @web_view: the #WebKitWebView on which the signal is emitted
     *
     * Emitted when closing a #WebKitWebView is requested. This occurs when a
     * call is made from JavaScript's <function>window.close</function> function or
     * after trying to close the @web_view with webkit_web_view_try_close().
     * It is the owner's responsibility to handle this signal to hide or
     * destroy the #WebKitWebView, if necessary.
     */
    signals[CLOSE] =
        g_signal_new("close",
                     G_TYPE_FROM_CLASS(webViewClass),
                     G_SIGNAL_RUN_LAST,
                     G_STRUCT_OFFSET(WebKitWebViewClass, close),
                     0, 0,
                     g_cclosure_marshal_VOID__VOID,
                     G_TYPE_NONE, 0);

    /**
     * WebKitWebView::script-dialog:
     * @web_view: the #WebKitWebView on which the signal is emitted
     * @dialog: the #WebKitScriptDialog to show
     *
     * Emitted when JavaScript code calls <function>window.alert</function>,
     * <function>window.confirm</function> or <function>window.prompt</function>,
     * or when <function>onbeforeunload</function> event is fired.
     * The @dialog parameter should be used to build the dialog.
     * If the signal is not handled a different dialog will be built and shown depending
     * on the dialog type:
     * <itemizedlist>
     * <listitem><para>
     *  %WEBKIT_SCRIPT_DIALOG_ALERT: message dialog with a single Close button.
     * </para></listitem>
     * <listitem><para>
     *  %WEBKIT_SCRIPT_DIALOG_CONFIRM: message dialog with OK and Cancel buttons.
     * </para></listitem>
     * <listitem><para>
     *  %WEBKIT_SCRIPT_DIALOG_PROMPT: message dialog with OK and Cancel buttons and
     *  a text entry with the default text.
     * </para></listitem>
     * <listitem><para>
     *  %WEBKIT_SCRIPT_DIALOG_BEFORE_UNLOAD_CONFIRM: message dialog with Stay and Leave buttons.
     * </para></listitem>
     * </itemizedlist>
     *
     * It is possible to handle the script dialog request asynchronously, by simply
     * caling webkit_script_dialog_ref() on the @dialog argument and calling
     * webkit_script_dialog_close() when done.
     * If the last reference is removed on a #WebKitScriptDialog and the dialog has not been
     * closed, webkit_script_dialog_close() will be called.
     *
     * Returns: %TRUE to stop other handlers from being invoked for the event.
     *    %FALSE to propagate the event further.
     */
    signals[SCRIPT_DIALOG] = g_signal_new(
        "script-dialog",
        G_TYPE_FROM_CLASS(webViewClass),
        G_SIGNAL_RUN_LAST,
        G_STRUCT_OFFSET(WebKitWebViewClass, script_dialog),
        g_signal_accumulator_true_handled, nullptr,
        g_cclosure_marshal_generic,
        G_TYPE_BOOLEAN, 1,
        WEBKIT_TYPE_SCRIPT_DIALOG);

    /**
     * WebKitWebView::decide-policy:
     * @web_view: the #WebKitWebView on which the signal is emitted
     * @decision: the #WebKitPolicyDecision
     * @decision_type: a #WebKitPolicyDecisionType denoting the type of @decision
     *
     * This signal is emitted when WebKit is requesting the client to decide a policy
     * decision, such as whether to navigate to a page, open a new window or whether or
     * not to download a resource. The #WebKitNavigationPolicyDecision passed in the
     * @decision argument is a generic type, but should be casted to a more
     * specific type when making the decision. For example:
     *
     * <informalexample><programlisting>
     * static gboolean
     * decide_policy_cb (WebKitWebView *web_view,
     *                   WebKitPolicyDecision *decision,
     *                   WebKitPolicyDecisionType type)
     * {
     *     switch (type) {
     *     case WEBKIT_POLICY_DECISION_TYPE_NAVIGATION_ACTION: {
     *         WebKitNavigationPolicyDecision *navigation_decision = WEBKIT_NAVIGATION_POLICY_DECISION (decision);
     *         /<!-- -->* Make a policy decision here. *<!-- -->/
     *         break;
     *     }
     *     case WEBKIT_POLICY_DECISION_TYPE_NEW_WINDOW_ACTION: {
     *         WebKitNavigationPolicyDecision *navigation_decision = WEBKIT_NAVIGATION_POLICY_DECISION (decision);
     *         /<!-- -->* Make a policy decision here. *<!-- -->/
     *         break;
     *     }
     *     case WEBKIT_POLICY_DECISION_TYPE_RESPONSE:
     *         WebKitResponsePolicyDecision *response = WEBKIT_RESPONSE_POLICY_DECISION (decision);
     *         /<!-- -->* Make a policy decision here. *<!-- -->/
     *         break;
     *     default:
     *         /<!-- -->* Making no decision results in webkit_policy_decision_use(). *<!-- -->/
     *         return FALSE;
     *     }
     *     return TRUE;
     * }
     * </programlisting></informalexample>
     *
     * It is possible to make policy decision asynchronously, by simply calling g_object_ref()
     * on the @decision argument and returning %TRUE to block the default signal handler.
     * If the last reference is removed on a #WebKitPolicyDecision and no decision has been
     * made explicitly, webkit_policy_decision_use() will be the default policy decision. The
     * default signal handler will simply call webkit_policy_decision_use(). Only the first
     * policy decision chosen for a given #WebKitPolicyDecision will have any affect.
     *
     * Returns: %TRUE to stop other handlers from being invoked for the event.
     *   %FALSE to propagate the event further.
     *
     */
    signals[DECIDE_POLICY] = g_signal_new(
        "decide-policy",
        G_TYPE_FROM_CLASS(webViewClass),
        G_SIGNAL_RUN_LAST,
        G_STRUCT_OFFSET(WebKitWebViewClass, decide_policy),
        g_signal_accumulator_true_handled, nullptr /* accumulator data */,
        g_cclosure_marshal_generic,
        G_TYPE_BOOLEAN, 2, /* number of parameters */
        WEBKIT_TYPE_POLICY_DECISION,
        WEBKIT_TYPE_POLICY_DECISION_TYPE);

    /**
     * WebKitWebView::permission-request:
     * @web_view: the #WebKitWebView on which the signal is emitted
     * @request: the #WebKitPermissionRequest
     *
     * This signal is emitted when WebKit is requesting the client to
     * decide about a permission request, such as allowing the browser
     * to switch to fullscreen mode, sharing its location or similar
     * operations.
     *
     * A possible way to use this signal could be through a dialog
     * allowing the user decide what to do with the request:
     *
     * <informalexample><programlisting>
     * static gboolean permission_request_cb (WebKitWebView *web_view,
     *                                        WebKitPermissionRequest *request,
     *                                        GtkWindow *parent_window)
     * {
     *     GtkWidget *dialog = gtk_message_dialog_new (parent_window,
     *                                                 GTK_DIALOG_MODAL,
     *                                                 GTK_MESSAGE_QUESTION,
     *                                                 GTK_BUTTONS_YES_NO,
     *                                                 "Allow Permission Request?");
     *     gtk_widget_show (dialog);
     *     gint result = gtk_dialog_run (GTK_DIALOG (dialog));
     *
     *     switch (result) {
     *     case GTK_RESPONSE_YES:
     *         webkit_permission_request_allow (request);
     *         break;
     *     default:
     *         webkit_permission_request_deny (request);
     *         break;
     *     }
     *     gtk_widget_destroy (dialog);
     *
     *     return TRUE;
     * }
     * </programlisting></informalexample>
     *
     * It is possible to handle permission requests asynchronously, by
     * simply calling g_object_ref() on the @request argument and
     * returning %TRUE to block the default signal handler.  If the
     * last reference is removed on a #WebKitPermissionRequest and the
     * request has not been handled, webkit_permission_request_deny()
     * will be the default action.
     *
     * If the signal is not handled, the @request will be completed automatically
     * by the specific #WebKitPermissionRequest that could allow or deny it. Check the
     * documentation of classes implementing #WebKitPermissionRequest interface to know
     * their default action.
     *
     * Returns: %TRUE to stop other handlers from being invoked for the event.
     *   %FALSE to propagate the event further.
     *
     */
    signals[PERMISSION_REQUEST] = g_signal_new(
        "permission-request",
        G_TYPE_FROM_CLASS(webViewClass),
        G_SIGNAL_RUN_LAST,
        G_STRUCT_OFFSET(WebKitWebViewClass, permission_request),
        g_signal_accumulator_true_handled, nullptr /* accumulator data */,
        g_cclosure_marshal_generic,
        G_TYPE_BOOLEAN, 1, /* number of parameters */
        WEBKIT_TYPE_PERMISSION_REQUEST);
    /**
     * WebKitWebView::mouse-target-changed:
     * @web_view: the #WebKitWebView on which the signal is emitted
     * @hit_test_result: a #WebKitHitTestResult
     * @modifiers: a bitmask of #GdkModifierType
     *
     * This signal is emitted when the mouse cursor moves over an
     * element such as a link, image or a media element. To determine
     * what type of element the mouse cursor is over, a Hit Test is performed
     * on the current mouse coordinates and the result is passed in the
     * @hit_test_result argument. The @modifiers argument is a bitmask of
     * #GdkModifierType flags indicating the state of modifier keys.
     * The signal is emitted again when the mouse is moved out of the
     * current element with a new @hit_test_result.
     */
    signals[MOUSE_TARGET_CHANGED] = g_signal_new(
        "mouse-target-changed",
        G_TYPE_FROM_CLASS(webViewClass),
        G_SIGNAL_RUN_LAST,
        G_STRUCT_OFFSET(WebKitWebViewClass, mouse_target_changed),
        nullptr, nullptr,
        g_cclosure_marshal_generic,
        G_TYPE_NONE, 2,
        WEBKIT_TYPE_HIT_TEST_RESULT,
        G_TYPE_UINT);

#if PLATFORM(GTK)
    /**
     * WebKitWebView::print:
     * @web_view: the #WebKitWebView on which the signal is emitted
     * @print_operation: the #WebKitPrintOperation that will handle the print request
     *
     * Emitted when printing is requested on @web_view, usually by a JavaScript call,
     * before the print dialog is shown. This signal can be used to set the initial
     * print settings and page setup of @print_operation to be used as default values in
     * the print dialog. You can call webkit_print_operation_set_print_settings() and
     * webkit_print_operation_set_page_setup() and then return %FALSE to propagate the
     * event so that the print dialog is shown.
     *
     * You can connect to this signal and return %TRUE to cancel the print operation
     * or implement your own print dialog.
     *
     * Returns: %TRUE to stop other handlers from being invoked for the event.
     *    %FALSE to propagate the event further.
     */
    signals[PRINT] = g_signal_new(
        "print",
        G_TYPE_FROM_CLASS(webViewClass),
        G_SIGNAL_RUN_LAST,
        G_STRUCT_OFFSET(WebKitWebViewClass, print),
        g_signal_accumulator_true_handled, nullptr,
        g_cclosure_marshal_generic,
        G_TYPE_BOOLEAN, 1,
        WEBKIT_TYPE_PRINT_OPERATION);
#endif // PLATFORM(GTK)

    /**
     * WebKitWebView::resource-load-started:
     * @web_view: the #WebKitWebView on which the signal is emitted
     * @resource: a #WebKitWebResource
     * @request: a #WebKitURIRequest
     *
     * Emitted when a new resource is going to be loaded. The @request parameter
     * contains the #WebKitURIRequest that will be sent to the server.
     * You can monitor the load operation by connecting to the different signals
     * of @resource.
     */
    signals[RESOURCE_LOAD_STARTED] = g_signal_new(
        "resource-load-started",
        G_TYPE_FROM_CLASS(webViewClass),
        G_SIGNAL_RUN_LAST,
        G_STRUCT_OFFSET(WebKitWebViewClass, resource_load_started),
        nullptr, nullptr,
        g_cclosure_marshal_generic,
        G_TYPE_NONE, 2,
        WEBKIT_TYPE_WEB_RESOURCE,
                     WEBKIT_TYPE_URI_REQUEST);

    /**
     * WebKitWebView::enter-fullscreen:
     * @web_view: the #WebKitWebView on which the signal is emitted.
     *
     * Emitted when JavaScript code calls
     * <function>element.webkitRequestFullScreen</function>. If the
     * signal is not handled the #WebKitWebView will proceed to full screen
     * its top level window. This signal can be used by client code to
     * request permission to the user prior doing the full screen
     * transition and eventually prepare the top-level window
     * (e.g. hide some widgets that would otherwise be part of the
     * full screen window).
     *
     * Returns: %TRUE to stop other handlers from being invoked for the event.
     *    %FALSE to continue emission of the event.
     */
    signals[ENTER_FULLSCREEN] = g_signal_new(
        "enter-fullscreen",
        G_TYPE_FROM_CLASS(webViewClass),
        G_SIGNAL_RUN_LAST,
        G_STRUCT_OFFSET(WebKitWebViewClass, enter_fullscreen),
        g_signal_accumulator_true_handled, nullptr,
        g_cclosure_marshal_generic,
        G_TYPE_BOOLEAN, 0);

    /**
     * WebKitWebView::leave-fullscreen:
     * @web_view: the #WebKitWebView on which the signal is emitted.
     *
     * Emitted when the #WebKitWebView is about to restore its top level
     * window out of its full screen state. This signal can be used by
     * client code to restore widgets hidden during the
     * #WebKitWebView::enter-fullscreen stage for instance.
     *
     * Returns: %TRUE to stop other handlers from being invoked for the event.
     *    %FALSE to continue emission of the event.
     */
    signals[LEAVE_FULLSCREEN] = g_signal_new(
        "leave-fullscreen",
        G_TYPE_FROM_CLASS(webViewClass),
        G_SIGNAL_RUN_LAST,
        G_STRUCT_OFFSET(WebKitWebViewClass, leave_fullscreen),
        g_signal_accumulator_true_handled, nullptr,
        g_cclosure_marshal_generic,
        G_TYPE_BOOLEAN, 0);

     /**
     * WebKitWebView::run-file-chooser:
     * @web_view: the #WebKitWebView on which the signal is emitted
     * @request: a #WebKitFileChooserRequest
     *
     * This signal is emitted when the user interacts with a &lt;input
     * type='file' /&gt; HTML element, requesting from WebKit to show
     * a dialog to select one or more files to be uploaded. To let the
     * application know the details of the file chooser, as well as to
     * allow the client application to either cancel the request or
     * perform an actual selection of files, the signal will pass an
     * instance of the #WebKitFileChooserRequest in the @request
     * argument.
     *
     * The default signal handler will asynchronously run a regular
     * #GtkFileChooserDialog for the user to interact with.
     *
     * Returns: %TRUE to stop other handlers from being invoked for the event.
     *   %FALSE to propagate the event further.
     *
     */
    signals[RUN_FILE_CHOOSER] = g_signal_new(
        "run-file-chooser",
        G_TYPE_FROM_CLASS(webViewClass),
        G_SIGNAL_RUN_LAST,
        G_STRUCT_OFFSET(WebKitWebViewClass, run_file_chooser),
        g_signal_accumulator_true_handled, nullptr /* accumulator data */,
        g_cclosure_marshal_generic,
        G_TYPE_BOOLEAN, 1, /* number of parameters */
        WEBKIT_TYPE_FILE_CHOOSER_REQUEST);

    /**
     * WebKitWebView::context-menu:
     * @web_view: the #WebKitWebView on which the signal is emitted
     * @context_menu: the proposed #WebKitContextMenu
     * @event: the #GdkEvent that triggered the context menu
     * @hit_test_result: a #WebKitHitTestResult
     *
     * Emitted when a context menu is about to be displayed to give the application
     * a chance to customize the proposed menu, prevent the menu from being displayed,
     * or build its own context menu.
     * <itemizedlist>
     * <listitem><para>
     *  To customize the proposed menu you can use webkit_context_menu_prepend(),
     *  webkit_context_menu_append() or webkit_context_menu_insert() to add new
     *  #WebKitContextMenuItem<!-- -->s to @context_menu, webkit_context_menu_move_item()
     *  to reorder existing items, or webkit_context_menu_remove() to remove an
     *  existing item. The signal handler should return %FALSE, and the menu represented
     *  by @context_menu will be shown.
     * </para></listitem>
     * <listitem><para>
     *  To prevent the menu from being displayed you can just connect to this signal
     *  and return %TRUE so that the proposed menu will not be shown.
     * </para></listitem>
     * <listitem><para>
     *  To build your own menu, you can remove all items from the proposed menu with
     *  webkit_context_menu_remove_all(), add your own items and return %FALSE so
     *  that the menu will be shown. You can also ignore the proposed #WebKitContextMenu,
     *  build your own #GtkMenu and return %TRUE to prevent the proposed menu from being shown.
     * </para></listitem>
     * <listitem><para>
     *  If you just want the default menu to be shown always, simply don't connect to this
     *  signal because showing the proposed context menu is the default behaviour.
     * </para></listitem>
     * </itemizedlist>
     *
     * The @event is expected to be one of the following types:
     * <itemizedlist>
     * <listitem><para>
     * a #GdkEventButton of type %GDK_BUTTON_PRESS when the context menu
     * was triggered with mouse.
     * </para></listitem>
     * <listitem><para>
     * a #GdkEventKey of type %GDK_KEY_PRESS if the keyboard was used to show
     * the menu.
     * </para></listitem>
     * <listitem><para>
     * a generic #GdkEvent of type %GDK_NOTHING when the #GtkWidget::popup-menu
     * signal was used to show the context menu.
     * </para></listitem>
     * </itemizedlist>
     *
     * If the signal handler returns %FALSE the context menu represented by @context_menu
     * will be shown, if it return %TRUE the context menu will not be shown.
     *
     * The proposed #WebKitContextMenu passed in @context_menu argument is only valid
     * during the signal emission.
     *
     * Returns: %TRUE to stop other handlers from being invoked for the event.
     *    %FALSE to propagate the event further.
     */
    signals[CONTEXT_MENU] = g_signal_new(
        "context-menu",
        G_TYPE_FROM_CLASS(webViewClass),
        G_SIGNAL_RUN_LAST,
        G_STRUCT_OFFSET(WebKitWebViewClass, context_menu),
        g_signal_accumulator_true_handled, nullptr,
        g_cclosure_marshal_generic,
        G_TYPE_BOOLEAN, 3,
        WEBKIT_TYPE_CONTEXT_MENU,
#if PLATFORM(GTK)
        GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE,
#elif PLATFORM(WPE)
        G_TYPE_POINTER, // FIXME: use a wpe thing here. I'm not sure we want to expose libwpe in the API.
#endif
        WEBKIT_TYPE_HIT_TEST_RESULT);

    /**
     * WebKitWebView::context-menu-dismissed:
     * @web_view: the #WebKitWebView on which the signal is emitted
     *
     * Emitted after #WebKitWebView::context-menu signal, if the context menu is shown,
     * to notify that the context menu is dismissed.
     */
    signals[CONTEXT_MENU_DISMISSED] =
        g_signal_new("context-menu-dismissed",
                     G_TYPE_FROM_CLASS(webViewClass),
                     G_SIGNAL_RUN_LAST,
                     G_STRUCT_OFFSET(WebKitWebViewClass, context_menu_dismissed),
                     0, 0,
                     g_cclosure_marshal_VOID__VOID,
                     G_TYPE_NONE, 0);

    /**
     * WebKitWebView::submit-form:
     * @web_view: the #WebKitWebView on which the signal is emitted
     * @request: a #WebKitFormSubmissionRequest
     *
     * This signal is emitted when a form is about to be submitted. The @request
     * argument passed contains information about the text fields of the form. This
     * is typically used to store login information that can be used later to
     * pre-fill the form.
     * The form will not be submitted until webkit_form_submission_request_submit() is called.
     *
     * It is possible to handle the form submission request asynchronously, by
     * simply calling g_object_ref() on the @request argument and calling
     * webkit_form_submission_request_submit() when done to continue with the form submission.
     * If the last reference is removed on a #WebKitFormSubmissionRequest and the
     * form has not been submitted, webkit_form_submission_request_submit() will be called.
     */
    signals[SUBMIT_FORM] =
        g_signal_new("submit-form",
                     G_TYPE_FROM_CLASS(webViewClass),
                     G_SIGNAL_RUN_LAST,
                     G_STRUCT_OFFSET(WebKitWebViewClass, submit_form),
                     0, 0,
                     g_cclosure_marshal_VOID__OBJECT,
                     G_TYPE_NONE, 1,
                     WEBKIT_TYPE_FORM_SUBMISSION_REQUEST);

    /**
     * WebKitWebView::insecure-content-detected:
     * @web_view: the #WebKitWebView on which the signal is emitted
     * @event: the #WebKitInsecureContentEvent
     *
     * This signal is emitted when insecure content has been detected
     * in a page loaded through a secure connection. This typically
     * means that a external resource from an unstrusted source has
     * been run or displayed, resulting in a mix of HTTPS and
     * non-HTTPS content.
     *
     * You can check the @event parameter to know exactly which kind
     * of event has been detected (see #WebKitInsecureContentEvent).
     */
    signals[INSECURE_CONTENT_DETECTED] =
        g_signal_new("insecure-content-detected",
            G_TYPE_FROM_CLASS(webViewClass),
            G_SIGNAL_RUN_LAST,
            G_STRUCT_OFFSET(WebKitWebViewClass, insecure_content_detected),
            0, 0,
            g_cclosure_marshal_VOID__ENUM,
            G_TYPE_NONE, 1,
            WEBKIT_TYPE_INSECURE_CONTENT_EVENT);

#if PLATFORM(GTK)
    /**
     * WebKitWebView::web-process-crashed:
     * @web_view: the #WebKitWebView
     *
     * This signal is emitted when the web process crashes.
     *
     * Returns: %TRUE to stop other handlers from being invoked for the event.
     *    %FALSE to propagate the event further.
     *
     * Deprecated: 2.20: Use WebKitWebView::web-process-terminated instead.
     */
    signals[WEB_PROCESS_CRASHED] = g_signal_new(
        "web-process-crashed",
        G_TYPE_FROM_CLASS(webViewClass),
        G_SIGNAL_RUN_LAST,
        G_STRUCT_OFFSET(WebKitWebViewClass, web_process_crashed),
        g_signal_accumulator_true_handled, nullptr,
        g_cclosure_marshal_generic,
        G_TYPE_BOOLEAN, 0);
#endif

    /**
     * WebKitWebView::web-process-terminated:
     * @web_view: the #WebKitWebView
     * @reason: the a #WebKitWebProcessTerminationReason
     *
     * This signal is emitted when the web process terminates abnormally due
     * to @reason.
     *
     * Since: 2.20
     */
    signals[WEB_PROCESS_TERMINATED] = g_signal_new(
        "web-process-terminated",
        G_TYPE_FROM_CLASS(webViewClass),
        G_SIGNAL_RUN_LAST,
        G_STRUCT_OFFSET(WebKitWebViewClass, web_process_terminated),
        0, 0,
        g_cclosure_marshal_VOID__ENUM,
        G_TYPE_NONE, 1,
        WEBKIT_TYPE_WEB_PROCESS_TERMINATION_REASON);

    /**
     * WebKitWebView::authenticate:
     * @web_view: the #WebKitWebView on which the signal is emitted
     * @request: a #WebKitAuthenticationRequest
     *
     * This signal is emitted when the user is challenged with HTTP
     * authentication. To let the  application access or supply
     * the credentials as well as to allow the client application
     * to either cancel the request or perform the authentication,
     * the signal will pass an instance of the
     * #WebKitAuthenticationRequest in the @request argument.
     * To handle this signal asynchronously you should keep a ref
     * of the request and return %TRUE. To disable HTTP authentication
     * entirely, connect to this signal and simply return %TRUE.
     *
     * The default signal handler will run a default authentication
     * dialog asynchronously for the user to interact with.
     *
     * Returns: %TRUE to stop other handlers from being invoked for the event.
     *   %FALSE to propagate the event further.
     *
     * Since: 2.2
     */
    signals[AUTHENTICATE] = g_signal_new(
        "authenticate",
        G_TYPE_FROM_CLASS(webViewClass),
        G_SIGNAL_RUN_LAST,
        G_STRUCT_OFFSET(WebKitWebViewClass, authenticate),
        g_signal_accumulator_true_handled, nullptr /* accumulator data */,
        g_cclosure_marshal_generic,
        G_TYPE_BOOLEAN, 1, /* number of parameters */
        WEBKIT_TYPE_AUTHENTICATION_REQUEST);

    /**
     * WebKitWebView::show-notification:
     * @web_view: the #WebKitWebView
     * @notification: a #WebKitNotification
     *
     * This signal is emitted when a notification should be presented to the
     * user. The @notification is kept alive until either: 1) the web page cancels it
     * or 2) a navigation happens.
     *
     * The default handler will emit a notification using libnotify, if built with
     * support for it.
     *
     * Returns: %TRUE to stop other handlers from being invoked. %FALSE otherwise.
     *
     * Since: 2.8
     */
    signals[SHOW_NOTIFICATION] = g_signal_new(
        "show-notification",
        G_TYPE_FROM_CLASS(gObjectClass),
        G_SIGNAL_RUN_LAST,
        G_STRUCT_OFFSET(WebKitWebViewClass, show_notification),
        g_signal_accumulator_true_handled, nullptr /* accumulator data */,
        g_cclosure_marshal_generic,
        G_TYPE_BOOLEAN, 1,
        WEBKIT_TYPE_NOTIFICATION);

#if PLATFORM(GTK)
     /**
      * WebKitWebView::run-color-chooser:
      * @web_view: the #WebKitWebView on which the signal is emitted
      * @request: a #WebKitColorChooserRequest
      *
      * This signal is emitted when the user interacts with a &lt;input
      * type='color' /&gt; HTML element, requesting from WebKit to show
      * a dialog to select a color. To let the application know the details of
      * the color chooser, as well as to allow the client application to either
      * cancel the request or perform an actual color selection, the signal will
      * pass an instance of the #WebKitColorChooserRequest in the @request
      * argument.
      *
      * It is possible to handle this request asynchronously by increasing the
      * reference count of the request.
      *
      * The default signal handler will asynchronously run a regular
      * #GtkColorChooser for the user to interact with.
      *
      * Returns: %TRUE to stop other handlers from being invoked for the event.
      *   %FALSE to propagate the event further.
      *
      * Since: 2.8
      */
    signals[RUN_COLOR_CHOOSER] = g_signal_new(
        "run-color-chooser",
        G_TYPE_FROM_CLASS(webViewClass),
        G_SIGNAL_RUN_LAST,
        G_STRUCT_OFFSET(WebKitWebViewClass, run_color_chooser),
        g_signal_accumulator_true_handled, nullptr,
        g_cclosure_marshal_generic,
        G_TYPE_BOOLEAN, 1,
        WEBKIT_TYPE_COLOR_CHOOSER_REQUEST);

    /**
     * WebKitWebView::show-option-menu:
     * @web_view: the #WebKitWebView on which the signal is emitted
     * @menu: the #WebKitOptionMenu
     * @event: the #GdkEvent that triggered the menu, or %NULL
     * @rectangle: the option element area
     *
     * This signal is emitted when a select element in @web_view needs to display a
     * dropdown menu. This signal can be used to show a custom menu, using @menu to get
     * the details of all items that should be displayed. The area of the element in the
     * #WebKitWebView is given as @rectangle parameter, it can be used to position the
     * menu. If this was triggered by a user interaction, like a mouse click,
     * @event parameter provides the #GdkEvent.
     * To handle this signal asynchronously you should keep a ref of the @menu.
     *
     * The default signal handler will pop up a #GtkMenu.
     *
     * Returns: %TRUE to stop other handlers from being invoked for the event.
     *   %FALSE to propagate the event further.
     *
     * Since: 2.18
     */
    signals[SHOW_OPTION_MENU] = g_signal_new(
        "show-option-menu",
        G_TYPE_FROM_CLASS(webViewClass),
        G_SIGNAL_RUN_LAST,
        G_STRUCT_OFFSET(WebKitWebViewClass, show_option_menu),
        g_signal_accumulator_true_handled, nullptr,
        g_cclosure_marshal_generic,
        G_TYPE_BOOLEAN, 3,
        WEBKIT_TYPE_OPTION_MENU,
        GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE,
        GDK_TYPE_RECTANGLE | G_SIGNAL_TYPE_STATIC_SCOPE);
#endif // PLATFORM(GTK)
}

static void webkitWebViewCancelAuthenticationRequest(WebKitWebView* webView)
{
    if (!webView->priv->authenticationRequest)
        return;

    webkit_authentication_request_cancel(webView->priv->authenticationRequest.get());
    webView->priv->authenticationRequest.clear();
}

void webkitWebViewCreatePage(WebKitWebView* webView, Ref<API::PageConfiguration>&& configuration)
{
#if PLATFORM(GTK)
    webkitWebViewBaseCreateWebPage(WEBKIT_WEB_VIEW_BASE(webView), WTFMove(configuration));
#elif PLATFORM(WPE)
    webView->priv->view.reset(WKWPE::View::create(webkit_web_view_backend_get_wpe_backend(webView->priv->backend.get()), configuration.get()));
#endif
}

WebPageProxy& webkitWebViewGetPage(WebKitWebView* webView)
{
    return getPage(webView);
}

void webkitWebViewWillStartLoad(WebKitWebView* webView)
{
    // Ignore the active URI changes happening before WEBKIT_LOAD_STARTED. If they are not user-initiated,
    // they could be a malicious attempt to trick users by loading an invalid URI on a trusted host, with the load
    // intended to stall, or perhaps be repeated. If we trust the URI here and display it to the user, then the user's
    // only indication that something is wrong would be a page loading indicator. If the load request is not
    // user-initiated, we must not trust it until WEBKIT_LOAD_COMMITTED. If the load is triggered by API
    // request, then the active URI is already the pending API request URL, so the blocking is harmless and the
    // client application will still see the URI update immediately. Otherwise, the URI update will be delayed a bit.
    webView->priv->isActiveURIChangeBlocked = true;

    // This is called before NavigationClient::didStartProvisionalNavigation(), the page load state hasn't been committed yet.
    auto& pageLoadState = getPage(webView).pageLoadState();
    if (pageLoadState.isFinished())
        return;

    GUniquePtr<GError> error(g_error_new_literal(WEBKIT_NETWORK_ERROR, WEBKIT_NETWORK_ERROR_CANCELLED, _("Load request cancelled")));
    webkitWebViewLoadFailed(webView, pageLoadState.isProvisional() ? WEBKIT_LOAD_STARTED : WEBKIT_LOAD_COMMITTED,
        webView->priv->activeURI.data(), error.get());
}

void webkitWebViewLoadChanged(WebKitWebView* webView, WebKitLoadEvent loadEvent)
{
    WebKitWebViewPrivate* priv = webView->priv;
    switch (loadEvent) {
    case WEBKIT_LOAD_STARTED:
#if PLATFORM(GTK)
        webkitWebViewCancelFaviconRequest(webView);
        webkitWebViewWatchForChangesInFavicon(webView);
#endif
        webkitWebViewCancelAuthenticationRequest(webView);
        priv->loadingResourcesMap.clear();
        priv->mainResource = nullptr;
        webView->priv->isActiveURIChangeBlocked = false;
        break;
    case WEBKIT_LOAD_COMMITTED: {
        auto activeURL = getPage(webView).pageLoadState().activeURL().utf8();
        // Active URL is trusted now. If it's different to our active URI, due to the
        // update block before WEBKIT_LOAD_STARTED, we update it here to be in sync
        // again with the page load state.
        if (activeURL != priv->activeURI) {
            priv->activeURI = activeURL;
            g_object_notify(G_OBJECT(webView), "uri");
        }
#if PLATFORM(GTK)
        WebKitFaviconDatabase* database = webkit_web_context_get_favicon_database(priv->context.get());
        GUniquePtr<char> faviconURI(webkit_favicon_database_get_favicon_uri(database, priv->activeURI.data()));
        webkitWebViewUpdateFaviconURI(webView, faviconURI.get());
#endif
        break;
    }
    case WEBKIT_LOAD_FINISHED:
        webkitWebViewCancelAuthenticationRequest(webView);
        break;
    default:
        break;
    }

    g_signal_emit(webView, signals[LOAD_CHANGED], 0, loadEvent);
}

void webkitWebViewLoadFailed(WebKitWebView* webView, WebKitLoadEvent loadEvent, const char* failingURI, GError *error)
{
    webkitWebViewCancelAuthenticationRequest(webView);

    gboolean returnValue;
    g_signal_emit(webView, signals[LOAD_FAILED], 0, loadEvent, failingURI, error, &returnValue);
    g_signal_emit(webView, signals[LOAD_CHANGED], 0, WEBKIT_LOAD_FINISHED);
}

void webkitWebViewLoadFailedWithTLSErrors(WebKitWebView* webView, const char* failingURI, GError* error, GTlsCertificateFlags tlsErrors, GTlsCertificate* certificate)
{
    webkitWebViewCancelAuthenticationRequest(webView);

    WebKitTLSErrorsPolicy tlsErrorsPolicy = webkit_web_context_get_tls_errors_policy(webView->priv->context.get());
    if (tlsErrorsPolicy == WEBKIT_TLS_ERRORS_POLICY_FAIL) {
        gboolean returnValue;
        g_signal_emit(webView, signals[LOAD_FAILED_WITH_TLS_ERRORS], 0, failingURI, certificate, tlsErrors, &returnValue);
        if (!returnValue)
            g_signal_emit(webView, signals[LOAD_FAILED], 0, WEBKIT_LOAD_STARTED, failingURI, error, &returnValue);
    }

    g_signal_emit(webView, signals[LOAD_CHANGED], 0, WEBKIT_LOAD_FINISHED);
}

#if PLATFORM(GTK)
void webkitWebViewGetLoadDecisionForIcon(WebKitWebView* webView, const LinkIcon& icon, Function<void(bool)>&& completionHandler)
{
    WebKitFaviconDatabase* database = webkit_web_context_get_favicon_database(webView->priv->context.get());
    webkitFaviconDatabaseGetLoadDecisionForIcon(database, icon, getPage(webView).pageLoadState().activeURL(), WTFMove(completionHandler));
}

void webkitWebViewSetIcon(WebKitWebView* webView, const LinkIcon& icon, API::Data& iconData)
{
    WebKitFaviconDatabase* database = webkit_web_context_get_favicon_database(webView->priv->context.get());
    webkitFaviconDatabaseSetIconForPageURL(database, icon, iconData, getPage(webView).pageLoadState().activeURL());
}
#endif

WebPageProxy* webkitWebViewCreateNewPage(WebKitWebView* webView, const WindowFeatures& windowFeatures, WebKitNavigationAction* navigationAction)
{
    WebKitWebView* newWebView;
    g_signal_emit(webView, signals[CREATE], 0, navigationAction, &newWebView);
    if (!newWebView)
        return 0;

    webkitWindowPropertiesUpdateFromWebWindowFeatures(newWebView->priv->windowProperties.get(), windowFeatures);

    RefPtr<WebPageProxy> newPage = &getPage(newWebView);
    return newPage.leakRef();
}

void webkitWebViewReadyToShowPage(WebKitWebView* webView)
{
    g_signal_emit(webView, signals[READY_TO_SHOW], 0, NULL);
}

void webkitWebViewRunAsModal(WebKitWebView* webView)
{
    g_signal_emit(webView, signals[RUN_AS_MODAL], 0, NULL);

    webView->priv->modalLoop = adoptGRef(g_main_loop_new(0, FALSE));

#if PLATFORM(GTK)
// This is to suppress warnings about gdk_threads_leave and gdk_threads_enter.
    ALLOW_DEPRECATED_DECLARATIONS_BEGIN
    gdk_threads_leave();
#endif

    g_main_loop_run(webView->priv->modalLoop.get());

#if PLATFORM(GTK)
    gdk_threads_enter();
    ALLOW_DEPRECATED_DECLARATIONS_END
#endif
}

void webkitWebViewClosePage(WebKitWebView* webView)
{
    g_signal_emit(webView, signals[CLOSE], 0, NULL);
}

void webkitWebViewRunJavaScriptAlert(WebKitWebView* webView, const CString& message, Function<void()>&& completionHandler)
{
    ASSERT(!webView->priv->currentScriptDialog);
    webView->priv->currentScriptDialog = webkitScriptDialogCreate(WEBKIT_SCRIPT_DIALOG_ALERT, message, { }, [webView, completionHandler = WTFMove(completionHandler)](bool, const String&) {
        completionHandler();
        webView->priv->currentScriptDialog = nullptr;
    });
    gboolean returnValue;
    g_signal_emit(webView, signals[SCRIPT_DIALOG], 0, webView->priv->currentScriptDialog, &returnValue);
    webkit_script_dialog_unref(webView->priv->currentScriptDialog);
}

void webkitWebViewRunJavaScriptConfirm(WebKitWebView* webView, const CString& message, Function<void(bool)>&& completionHandler)
{
    ASSERT(!webView->priv->currentScriptDialog);
    webView->priv->currentScriptDialog = webkitScriptDialogCreate(WEBKIT_SCRIPT_DIALOG_CONFIRM, message, { }, [webView, completionHandler = WTFMove(completionHandler)](bool result, const String&) {
        completionHandler(result);
        webView->priv->currentScriptDialog = nullptr;
    });
    gboolean returnValue;
    g_signal_emit(webView, signals[SCRIPT_DIALOG], 0, webView->priv->currentScriptDialog, &returnValue);
    webkit_script_dialog_unref(webView->priv->currentScriptDialog);
}

void webkitWebViewRunJavaScriptPrompt(WebKitWebView* webView, const CString& message, const CString& defaultText, Function<void(const String&)>&& completionHandler)
{
    ASSERT(!webView->priv->currentScriptDialog);
    webView->priv->currentScriptDialog = webkitScriptDialogCreate(WEBKIT_SCRIPT_DIALOG_PROMPT, message, defaultText, [webView, completionHandler = WTFMove(completionHandler)](bool, const String& result) {
        completionHandler(result);
        webView->priv->currentScriptDialog = nullptr;
    });
    gboolean returnValue;
    g_signal_emit(webView, signals[SCRIPT_DIALOG], 0, webView->priv->currentScriptDialog, &returnValue);
    webkit_script_dialog_unref(webView->priv->currentScriptDialog);
}

void webkitWebViewRunJavaScriptBeforeUnloadConfirm(WebKitWebView* webView, const CString& message, Function<void(bool)>&& completionHandler)
{
    ASSERT(!webView->priv->currentScriptDialog);
    webView->priv->currentScriptDialog = webkitScriptDialogCreate(WEBKIT_SCRIPT_DIALOG_BEFORE_UNLOAD_CONFIRM, message, { }, [webView, completionHandler = WTFMove(completionHandler)](bool result, const String&) {
        completionHandler(result);
        webView->priv->currentScriptDialog = nullptr;
    });
    gboolean returnValue;
    g_signal_emit(webView, signals[SCRIPT_DIALOG], 0, webView->priv->currentScriptDialog, &returnValue);
    webkit_script_dialog_unref(webView->priv->currentScriptDialog);
}

bool webkitWebViewIsShowingScriptDialog(WebKitWebView* webView)
{
    if (!webView->priv->currentScriptDialog)
        return false;

    // FIXME: Add API to ask the user in case default implementation is not being used.
    return webkitScriptDialogIsRunning(webView->priv->currentScriptDialog);
}

String webkitWebViewGetCurrentScriptDialogMessage(WebKitWebView* webView)
{
    if (!webView->priv->currentScriptDialog)
        return { };

    return String::fromUTF8(webView->priv->currentScriptDialog->message);
}

void webkitWebViewSetCurrentScriptDialogUserInput(WebKitWebView* webView, const String& userInput)
{
    if (!webView->priv->currentScriptDialog)
        return;

    // FIXME: Add API to ask the user in case default implementation is not being used.
    if (webkitScriptDialogIsRunning(webView->priv->currentScriptDialog))
        webkitScriptDialogSetUserInput(webView->priv->currentScriptDialog, userInput);
}

void webkitWebViewAcceptCurrentScriptDialog(WebKitWebView* webView)
{
    if (!webView->priv->currentScriptDialog)
        return;

    // FIXME: Add API to ask the user in case default implementation is not being used.
    if (webkitScriptDialogIsRunning(webView->priv->currentScriptDialog))
        webkitScriptDialogAccept(webView->priv->currentScriptDialog);
}

void webkitWebViewDismissCurrentScriptDialog(WebKitWebView* webView)
{
    if (!webView->priv->currentScriptDialog)
        return;

    // FIXME: Add API to ask the user in case default implementation is not being used.
    if (webkitScriptDialogIsRunning(webView->priv->currentScriptDialog))
        webkitScriptDialogDismiss(webView->priv->currentScriptDialog);
}

Optional<WebKitScriptDialogType> webkitWebViewGetCurrentScriptDialogType(WebKitWebView* webView)
{
    if (!webView->priv->currentScriptDialog)
        return WTF::nullopt;

    return static_cast<WebKitScriptDialogType>(webView->priv->currentScriptDialog->type);
}

void webkitWebViewMakePolicyDecision(WebKitWebView* webView, WebKitPolicyDecisionType type, WebKitPolicyDecision* decision)
{
    gboolean returnValue;
    g_signal_emit(webView, signals[DECIDE_POLICY], 0, decision, type, &returnValue);
}

void webkitWebViewMakePermissionRequest(WebKitWebView* webView, WebKitPermissionRequest* request)
{
    gboolean returnValue;
    g_signal_emit(webView, signals[PERMISSION_REQUEST], 0, request, &returnValue);
}

void webkitWebViewMouseTargetChanged(WebKitWebView* webView, const WebHitTestResultData& hitTestResult, OptionSet<WebEvent::Modifier> modifiers)
{
#if PLATFORM(GTK)
    webkitWebViewBaseSetTooltipArea(WEBKIT_WEB_VIEW_BASE(webView), hitTestResult.elementBoundingBox);
#endif

    WebKitWebViewPrivate* priv = webView->priv;
    if (priv->mouseTargetHitTestResult
        && priv->mouseTargetModifiers == modifiers
        && webkitHitTestResultCompare(priv->mouseTargetHitTestResult.get(), hitTestResult))
        return;

    priv->mouseTargetModifiers = modifiers;
    priv->mouseTargetHitTestResult = adoptGRef(webkitHitTestResultCreate(hitTestResult));
    g_signal_emit(webView, signals[MOUSE_TARGET_CHANGED], 0, priv->mouseTargetHitTestResult.get(), toPlatformModifiers(modifiers));
}

void webkitWebViewHandleDownloadRequest(WebKitWebView* webView, DownloadProxy* downloadProxy)
{
    ASSERT(downloadProxy);
    GRefPtr<WebKitDownload> download = webkitWebContextGetOrCreateDownload(downloadProxy);
    webkitDownloadSetWebView(download.get(), webView);
}

#if PLATFORM(GTK)
void webkitWebViewPrintFrame(WebKitWebView* webView, WebFrameProxy* frame)
{
    auto printOperation = adoptGRef(webkit_print_operation_new(webView));
    webkitPrintOperationSetPrintMode(printOperation.get(), PrintInfo::PrintModeSync);
    gboolean returnValue;
    g_signal_emit(webView, signals[PRINT], 0, printOperation.get(), &returnValue);
    if (returnValue)
        return;

    WebKitPrintOperationResponse response = webkitPrintOperationRunDialogForFrame(printOperation.get(), 0, frame);
    if (response == WEBKIT_PRINT_OPERATION_RESPONSE_CANCEL)
        return;
    g_signal_connect(printOperation.leakRef(), "finished", G_CALLBACK(g_object_unref), 0);
}
#endif

void webkitWebViewResourceLoadStarted(WebKitWebView* webView, WebFrameProxy* frame, uint64_t resourceIdentifier, WebKitURIRequest* request)
{
    WebKitWebViewPrivate* priv = webView->priv;
    bool isMainResource = frame->isMainFrame() && !priv->mainResource;
    WebKitWebResource* resource = webkitWebResourceCreate(frame, request, isMainResource);
    if (isMainResource)
        priv->mainResource = resource;
    priv->loadingResourcesMap.set(resourceIdentifier, adoptGRef(resource));
    g_signal_emit(webView, signals[RESOURCE_LOAD_STARTED], 0, resource, request);
}

WebKitWebResource* webkitWebViewGetLoadingWebResource(WebKitWebView* webView, uint64_t resourceIdentifier)
{
    GRefPtr<WebKitWebResource> resource = webView->priv->loadingResourcesMap.get(resourceIdentifier);
    return resource.get();
}

void webkitWebViewRemoveLoadingWebResource(WebKitWebView* webView, uint64_t resourceIdentifier)
{
    WebKitWebViewPrivate* priv = webView->priv;
    ASSERT(priv->loadingResourcesMap.contains(resourceIdentifier));
    priv->loadingResourcesMap.remove(resourceIdentifier);
}

void webkitWebViewEnterFullScreen(WebKitWebView* webView)
{
#if ENABLE(FULLSCREEN_API)
    gboolean returnValue;
    g_signal_emit(webView, signals[ENTER_FULLSCREEN], 0, &returnValue);
#if PLATFORM(GTK)
    if (!returnValue)
        webkitWebViewBaseEnterFullScreen(WEBKIT_WEB_VIEW_BASE(webView));
#endif
#endif
}

void webkitWebViewExitFullScreen(WebKitWebView* webView)
{
#if ENABLE(FULLSCREEN_API)
    gboolean returnValue;
    g_signal_emit(webView, signals[LEAVE_FULLSCREEN], 0, &returnValue);
#if PLATFORM(GTK)
    if (!returnValue)
        webkitWebViewBaseExitFullScreen(WEBKIT_WEB_VIEW_BASE(webView));
#endif
#endif
}

void webkitWebViewRunFileChooserRequest(WebKitWebView* webView, WebKitFileChooserRequest* request)
{
    gboolean returnValue;
    g_signal_emit(webView, signals[RUN_FILE_CHOOSER], 0, request, &returnValue);
}

#if PLATFORM(GTK)
static void contextMenuDismissed(GtkMenuShell*, WebKitWebView* webView)
{
    g_signal_emit(webView, signals[CONTEXT_MENU_DISMISSED], 0, NULL);
}

void webkitWebViewPopulateContextMenu(WebKitWebView* webView, const Vector<WebContextMenuItemData>& proposedMenu, const WebHitTestResultData& hitTestResultData, GVariant* userData)
{
    WebKitWebViewBase* webViewBase = WEBKIT_WEB_VIEW_BASE(webView);
    WebContextMenuProxyGtk* contextMenuProxy = webkitWebViewBaseGetActiveContextMenuProxy(webViewBase);
    ASSERT(contextMenuProxy);

    GRefPtr<WebKitContextMenu> contextMenu = adoptGRef(webkitContextMenuCreate(proposedMenu));
    if (userData)
        webkit_context_menu_set_user_data(WEBKIT_CONTEXT_MENU(contextMenu.get()), userData);

    GRefPtr<WebKitHitTestResult> hitTestResult = adoptGRef(webkitHitTestResultCreate(hitTestResultData));
    GUniquePtr<GdkEvent> contextMenuEvent(webkitWebViewBaseTakeContextMenuEvent(webViewBase));
    gboolean returnValue;
    g_signal_emit(webView, signals[CONTEXT_MENU], 0, contextMenu.get(), contextMenuEvent.get(), hitTestResult.get(), &returnValue);
    if (returnValue)
        return;

    Vector<WebContextMenuItemGlib> contextMenuItems;
    webkitContextMenuPopulate(contextMenu.get(), contextMenuItems);
    contextMenuProxy->populate(contextMenuItems);

    g_signal_connect(contextMenuProxy->gtkMenu(), "deactivate", G_CALLBACK(contextMenuDismissed), webView);

    // Clear the menu to make sure it's useless after signal emission.
    webkit_context_menu_remove_all(contextMenu.get());
}
#elif PLATFORM(WPE)
void webkitWebViewPopulateContextMenu(WebKitWebView* webView, const Vector<WebContextMenuItemData>& proposedMenu, const WebHitTestResultData& hitTestResultData, GVariant* userData)
{
    GRefPtr<WebKitContextMenu> contextMenu = adoptGRef(webkitContextMenuCreate(proposedMenu));
    if (userData)
        webkit_context_menu_set_user_data(WEBKIT_CONTEXT_MENU(contextMenu.get()), userData);
    GRefPtr<WebKitHitTestResult> hitTestResult = adoptGRef(webkitHitTestResultCreate(hitTestResultData));
    gboolean returnValue;
    g_signal_emit(webView, signals[CONTEXT_MENU], 0, contextMenu.get(), nullptr, hitTestResult.get(), &returnValue);
}
#endif

void webkitWebViewSubmitFormRequest(WebKitWebView* webView, WebKitFormSubmissionRequest* request)
{
    g_signal_emit(webView, signals[SUBMIT_FORM], 0, request);
}

void webkitWebViewHandleAuthenticationChallenge(WebKitWebView* webView, AuthenticationChallengeProxy* authenticationChallenge)
{
#if PLATFORM(GTK)
    G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
    gboolean privateBrowsingEnabled = webView->priv->isEphemeral || webkit_settings_get_enable_private_browsing(webView->priv->settings.get());
    G_GNUC_END_IGNORE_DEPRECATIONS;
#else
    gboolean privateBrowsingEnabled = webView->priv->isEphemeral;
#endif
    webView->priv->authenticationRequest = adoptGRef(webkitAuthenticationRequestCreate(authenticationChallenge, privateBrowsingEnabled));
    gboolean returnValue;
    g_signal_emit(webView, signals[AUTHENTICATE], 0, webView->priv->authenticationRequest.get(), &returnValue);
}

void webkitWebViewInsecureContentDetected(WebKitWebView* webView, WebKitInsecureContentEvent type)
{
    g_signal_emit(webView, signals[INSECURE_CONTENT_DETECTED], 0, type);
}

bool webkitWebViewEmitShowNotification(WebKitWebView* webView, WebKitNotification* webNotification)
{
    gboolean handled;
    g_signal_emit(webView, signals[SHOW_NOTIFICATION], 0, webNotification, &handled);
    return handled;
}

#if PLATFORM(GTK)
bool webkitWebViewEmitRunColorChooser(WebKitWebView* webView, WebKitColorChooserRequest* request)
{
    gboolean handled;
    g_signal_emit(webView, signals[RUN_COLOR_CHOOSER], 0, request, &handled);
    return handled;
}
#endif

void webkitWebViewSelectionDidChange(WebKitWebView* webView)
{
    if (!webView->priv->editorState)
        return;

    webkitEditorStateChanged(webView->priv->editorState.get(), getPage(webView).editorState());
}

void webkitWebViewRequestInstallMissingMediaPlugins(WebKitWebView* webView, InstallMissingMediaPluginsPermissionRequest& request)
{
#if ENABLE(VIDEO)
    GRefPtr<WebKitInstallMissingMediaPluginsPermissionRequest> installMediaPluginsPermissionRequest = adoptGRef(webkitInstallMissingMediaPluginsPermissionRequestCreate(request));
    webkitWebViewMakePermissionRequest(webView, WEBKIT_PERMISSION_REQUEST(installMediaPluginsPermissionRequest.get()));
#else
    ASSERT_NOT_REACHED();
#endif
}

WebKitWebsiteDataManager* webkitWebViewGetWebsiteDataManager(WebKitWebView* webView)
{
    return webView->priv->websiteDataManager.get();
}

#if PLATFORM(GTK)
bool webkitWebViewShowOptionMenu(WebKitWebView* webView, const IntRect& rect, WebKitOptionMenu* menu, const GdkEvent* event)
{
    GdkRectangle menuRect = rect;
    gboolean handled;
    g_signal_emit(webView, signals[SHOW_OPTION_MENU], 0, menu, event, &menuRect, &handled);
    return handled;
}
#endif

#if PLATFORM(WPE)
/**
 * webkit_web_view_get_backend:
 * @web_view: a #WebKitWebView
 *
 * Get the #WebKitWebViewBackend of @web_view
 *
 * Returns: (transfer none): the #WebKitWebViewBackend of @web_view
 *
 * Since: 2.20
 */
WebKitWebViewBackend* webkit_web_view_get_backend(WebKitWebView* webView)
{
    g_return_val_if_fail(WEBKIT_IS_WEB_VIEW(webView), nullptr);

    return webView->priv->backend.get();
}
#endif

/**
 * webkit_web_view_get_context:
 * @web_view: a #WebKitWebView
 *
 * Gets the web context of @web_view.
 *
 * Returns: (transfer none): the #WebKitWebContext of the view
 */
WebKitWebContext* webkit_web_view_get_context(WebKitWebView *webView)
{
    g_return_val_if_fail(WEBKIT_IS_WEB_VIEW(webView), 0);

    return webView->priv->context.get();
}

/**
 * webkit_web_view_get_user_content_manager:
 * @web_view: a #WebKitWebView
 *
 * Gets the user content manager associated to @web_view.
 *
 * Returns: (transfer none): the #WebKitUserContentManager associated with the view
 *
 * Since: 2.6
 */
WebKitUserContentManager* webkit_web_view_get_user_content_manager(WebKitWebView* webView)
{
    g_return_val_if_fail(WEBKIT_IS_WEB_VIEW(webView), nullptr);

    return webView->priv->userContentManager.get();
}

/**
 * webkit_web_view_is_ephemeral:
 * @web_view: a #WebKitWebView
 *
 * Get whether a #WebKitWebView is ephemeral. To create an ephemeral #WebKitWebView you need to
 * use g_object_new() and pass is-ephemeral property with %TRUE value. See
 * #WebKitWebView:is-ephemeral for more details.
 * If @web_view was created with a ephemeral #WebKitWebView:related-view or an
 * ephemeral #WebKitWebView:web-context it will also be ephemeral.
 *
 * Returns: %TRUE if @web_view is ephemeral or %FALSE otherwise.
 *
 * Since: 2.16
 */
gboolean webkit_web_view_is_ephemeral(WebKitWebView* webView)
{
    g_return_val_if_fail(WEBKIT_IS_WEB_VIEW(webView), FALSE);

    return webView->priv->isEphemeral;
}

/**
 * webkit_web_view_is_controlled_by_automation:
 * @web_view: a #WebKitWebView
 *
 * Get whether a #WebKitWebView was created with #WebKitWebView:is-controlled-by-automation
 * property enabled. Only #WebKitWebView<!-- -->s controlled by automation can be used in an
 * automation session.
 *
 * Returns: %TRUE if @web_view is controlled by automation, or %FALSE otherwise.
 *
 * Since: 2.18
 */
gboolean webkit_web_view_is_controlled_by_automation(WebKitWebView* webView)
{
    g_return_val_if_fail(WEBKIT_IS_WEB_VIEW(webView), FALSE);

    return webView->priv->isControlledByAutomation;
}

/**
 * webkit_web_view_get_website_data_manager:
 * @web_view: a #WebKitWebView
 *
 * Get the #WebKitWebsiteDataManager associated to @web_view. If @web_view is not ephemeral,
 * the returned #WebKitWebsiteDataManager will be the same as the #WebKitWebsiteDataManager
 * of @web_view's #WebKitWebContext.
 *
 * Returns: (transfer none): a #WebKitWebsiteDataManager
 *
 * Since: 2.16
 */
WebKitWebsiteDataManager* webkit_web_view_get_website_data_manager(WebKitWebView* webView)
{
    g_return_val_if_fail(WEBKIT_IS_WEB_VIEW(webView), nullptr);

    if (webView->priv->websiteDataManager)
        return webView->priv->websiteDataManager.get();

    return webkit_web_context_get_website_data_manager(webView->priv->context.get());
}

/**
 * webkit_web_view_try_close:
 * @web_view: a #WebKitWebView
 *
 * Tries to close the @web_view. This will fire the onbeforeunload event
 * to ask the user for confirmation to close the page. If there isn't an
 * onbeforeunload event handler or the user confirms to close the page,
 * the #WebKitWebView::close signal is emitted, otherwise nothing happens.
 *
 * Since: 2.12
 */
void webkit_web_view_try_close(WebKitWebView *webView)
{
    g_return_if_fail(WEBKIT_IS_WEB_VIEW(webView));
    if (getPage(webView).tryClose())
        webkitWebViewClosePage(webView);
}

/**
 * webkit_web_view_load_uri:
 * @web_view: a #WebKitWebView
 * @uri: an URI string
 *
 * Requests loading of the specified URI string.
 * You can monitor the load operation by connecting to
 * #WebKitWebView::load-changed signal.
 */
void webkit_web_view_load_uri(WebKitWebView* webView, const gchar* uri)
{
    g_return_if_fail(WEBKIT_IS_WEB_VIEW(webView));
    g_return_if_fail(uri);

    GUniquePtr<SoupURI> soupURI(soup_uri_new(uri));
    getPage(webView).loadRequest(soupURIToURL(soupURI.get()));
}

/**
 * webkit_web_view_load_html:
 * @web_view: a #WebKitWebView
 * @content: The HTML string to load
 * @base_uri: (allow-none): The base URI for relative locations or %NULL
 *
 * Load the given @content string with the specified @base_uri.
 * If @base_uri is not %NULL, relative URLs in the @content will be
 * resolved against @base_uri and absolute local paths must be children of the @base_uri.
 * For security reasons absolute local paths that are not children of @base_uri
 * will cause the web process to terminate.
 * If you need to include URLs in @content that are local paths in a different
 * directory than @base_uri you can build a data URI for them. When @base_uri is %NULL,
 * it defaults to "about:blank". The mime type of the document will be "text/html".
 * You can monitor the load operation by connecting to #WebKitWebView::load-changed signal.
 */
void webkit_web_view_load_html(WebKitWebView* webView, const gchar* content, const gchar* baseURI)
{
    g_return_if_fail(WEBKIT_IS_WEB_VIEW(webView));
    g_return_if_fail(content);

    getPage(webView).loadData({ reinterpret_cast<const uint8_t*>(content), content ? strlen(content) : 0 }, "text/html"_s, "UTF-8"_s, String::fromUTF8(baseURI));
}

/**
 * webkit_web_view_load_alternate_html:
 * @web_view: a #WebKitWebView
 * @content: the new content to display as the main page of the @web_view
 * @content_uri: the URI for the alternate page content
 * @base_uri: (allow-none): the base URI for relative locations or %NULL
 *
 * Load the given @content string for the URI @content_uri.
 * This allows clients to display page-loading errors in the #WebKitWebView itself.
 * When this method is called from #WebKitWebView::load-failed signal to show an
 * error page, then the back-forward list is maintained appropriately.
 * For everything else this method works the same way as webkit_web_view_load_html().
 */
void webkit_web_view_load_alternate_html(WebKitWebView* webView, const gchar* content, const gchar* contentURI, const gchar* baseURI)
{
    g_return_if_fail(WEBKIT_IS_WEB_VIEW(webView));
    g_return_if_fail(content);
    g_return_if_fail(contentURI);

    getPage(webView).loadAlternateHTML({ reinterpret_cast<const uint8_t*>(content), content ? strlen(content) : 0 }, "UTF-8"_s, URL(URL(), String::fromUTF8(baseURI)), URL(URL(), String::fromUTF8(contentURI)));
}

/**
 * webkit_web_view_load_plain_text:
 * @web_view: a #WebKitWebView
 * @plain_text: The plain text to load
 *
 * Load the specified @plain_text string into @web_view. The mime type of
 * document will be "text/plain". You can monitor the load
 * operation by connecting to #WebKitWebView::load-changed signal.
 */
void webkit_web_view_load_plain_text(WebKitWebView* webView, const gchar* plainText)
{
    g_return_if_fail(WEBKIT_IS_WEB_VIEW(webView));
    g_return_if_fail(plainText);

    getPage(webView).loadData({ reinterpret_cast<const uint8_t*>(plainText), plainText ? strlen(plainText) : 0 }, "text/plain"_s, "UTF-8"_s, WTF::blankURL().string());
}

/**
 * webkit_web_view_load_bytes:
 * @web_view: a #WebKitWebView
 * @bytes: input data to load
 * @mime_type: (allow-none): the MIME type of @bytes, or %NULL
 * @encoding: (allow-none): the character encoding of @bytes, or %NULL
 * @base_uri: (allow-none): the base URI for relative locations or %NULL
 *
 * Load the specified @bytes into @web_view using the given @mime_type and @encoding.
 * When @mime_type is %NULL, it defaults to "text/html".
 * When @encoding is %NULL, it defaults to "UTF-8".
 * When @base_uri is %NULL, it defaults to "about:blank".
 * You can monitor the load operation by connecting to #WebKitWebView::load-changed signal.
 *
 * Since: 2.6
 */
void webkit_web_view_load_bytes(WebKitWebView* webView, GBytes* bytes, const char* mimeType, const char* encoding, const char* baseURI)
{
    g_return_if_fail(WEBKIT_IS_WEB_VIEW(webView));
    g_return_if_fail(bytes);

    gsize bytesDataSize;
    gconstpointer bytesData = g_bytes_get_data(bytes, &bytesDataSize);
    g_return_if_fail(bytesDataSize);

    getPage(webView).loadData({ reinterpret_cast<const uint8_t*>(bytesData), bytesDataSize }, mimeType ? String::fromUTF8(mimeType) : String::fromUTF8("text/html"),
        encoding ? String::fromUTF8(encoding) : String::fromUTF8("UTF-8"), String::fromUTF8(baseURI));
}

/**
 * webkit_web_view_load_request:
 * @web_view: a #WebKitWebView
 * @request: a #WebKitURIRequest to load
 *
 * Requests loading of the specified #WebKitURIRequest.
 * You can monitor the load operation by connecting to
 * #WebKitWebView::load-changed signal.
 */
void webkit_web_view_load_request(WebKitWebView* webView, WebKitURIRequest* request)
{
    g_return_if_fail(WEBKIT_IS_WEB_VIEW(webView));
    g_return_if_fail(WEBKIT_IS_URI_REQUEST(request));

    ResourceRequest resourceRequest;
    webkitURIRequestGetResourceRequest(request, resourceRequest);
    getPage(webView).loadRequest(WTFMove(resourceRequest));
}

/**
 * webkit_web_view_get_page_id:
 * @web_view: a #WebKitWebView
 *
 * Get the identifier of the #WebKitWebPage corresponding to
 * the #WebKitWebView
 *
 * Returns: the page ID of @web_view.
 */
guint64 webkit_web_view_get_page_id(WebKitWebView* webView)
{
    g_return_val_if_fail(WEBKIT_IS_WEB_VIEW(webView), 0);

    return getPage(webView).pageID().toUInt64();
}

/**
 * webkit_web_view_get_title:
 * @web_view: a #WebKitWebView
 *
 * Gets the value of the #WebKitWebView:title property.
 * You can connect to notify::title signal of @web_view to
 * be notified when the title has been received.
 *
 * Returns: The main frame document title of @web_view.
 */
const gchar* webkit_web_view_get_title(WebKitWebView* webView)
{
    g_return_val_if_fail(WEBKIT_IS_WEB_VIEW(webView), 0);

    return webView->priv->title.data();
}

/**
 * webkit_web_view_reload:
 * @web_view: a #WebKitWebView
 *
 * Reloads the current contents of @web_view.
 * See also webkit_web_view_reload_bypass_cache().
 */
void webkit_web_view_reload(WebKitWebView* webView)
{
    g_return_if_fail(WEBKIT_IS_WEB_VIEW(webView));

    getPage(webView).reload({ });
}

/**
 * webkit_web_view_reload_bypass_cache:
 * @web_view: a #WebKitWebView
 *
 * Reloads the current contents of @web_view without
 * using any cached data.
 */
void webkit_web_view_reload_bypass_cache(WebKitWebView* webView)
{
    g_return_if_fail(WEBKIT_IS_WEB_VIEW(webView));

    getPage(webView).reload(ReloadOption::FromOrigin);
}

/**
 * webkit_web_view_stop_loading:
 * @web_view: a #WebKitWebView
 *
 * Stops any ongoing loading operation in @web_view.
 * This method does nothing if no content is being loaded.
 * If there is a loading operation in progress, it will be cancelled and
 * #WebKitWebView::load-failed signal will be emitted with
 * %WEBKIT_NETWORK_ERROR_CANCELLED error.
 */
void webkit_web_view_stop_loading(WebKitWebView* webView)
{
    g_return_if_fail(WEBKIT_IS_WEB_VIEW(webView));

    getPage(webView).stopLoading();
}

/**
 * webkit_web_view_is_loading:
 * @web_view: a #WebKitWebView
 *
 * Gets the value of the #WebKitWebView:is-loading property.
 * You can monitor when a #WebKitWebView is loading a page by connecting to
 * notify::is-loading signal of @web_view. This is useful when you are
 * interesting in knowing when the view is loading something but not in the
 * details about the status of the load operation, for example to start a spinner
 * when the view is loading a page and stop it when it finishes.
 *
 * Returns: %TRUE if @web_view is loading a page or %FALSE otherwise.
 */
gboolean webkit_web_view_is_loading(WebKitWebView* webView)
{
    g_return_val_if_fail(WEBKIT_IS_WEB_VIEW(webView), FALSE);

    return webView->priv->isLoading;
}

/**
 * webkit_web_view_is_playing_audio:
 * @web_view: a #WebKitWebView
 *
 * Gets the value of the #WebKitWebView:is-playing-audio property.
 * You can monitor when a page in a #WebKitWebView is playing audio by
 * connecting to the notify::is-playing-audio signal of @web_view. This
 * is useful when the application wants to provide visual feedback when a
 * page is producing sound.
 *
 * Returns: %TRUE if a page in @web_view is playing audio or %FALSE otherwise.
 *
 * Since: 2.8
 */
gboolean webkit_web_view_is_playing_audio(WebKitWebView* webView)
{
    g_return_val_if_fail(WEBKIT_IS_WEB_VIEW(webView), FALSE);

    return getPage(webView).isPlayingAudio();
}

/**
 * webkit_web_view_go_back:
 * @web_view: a #WebKitWebView
 *
 * Loads the previous history item.
 * You can monitor the load operation by connecting to
 * #WebKitWebView::load-changed signal.
 */
void webkit_web_view_go_back(WebKitWebView* webView)
{
    g_return_if_fail(WEBKIT_IS_WEB_VIEW(webView));

    getPage(webView).goBack();
}

/**
 * webkit_web_view_can_go_back:
 * @web_view: a #WebKitWebView
 *
 * Determines whether @web_view has a previous history item.
 *
 * Returns: %TRUE if able to move back or %FALSE otherwise.
 */
gboolean webkit_web_view_can_go_back(WebKitWebView* webView)
{
    g_return_val_if_fail(WEBKIT_IS_WEB_VIEW(webView), FALSE);

    return !!getPage(webView).backForwardList().backItem();
}

/**
 * webkit_web_view_go_forward:
 * @web_view: a #WebKitWebView
 *
 * Loads the next history item.
 * You can monitor the load operation by connecting to
 * #WebKitWebView::load-changed signal.
 */
void webkit_web_view_go_forward(WebKitWebView* webView)
{
    g_return_if_fail(WEBKIT_IS_WEB_VIEW(webView));

    getPage(webView).goForward();
}

/**
 * webkit_web_view_can_go_forward:
 * @web_view: a #WebKitWebView
 *
 * Determines whether @web_view has a next history item.
 *
 * Returns: %TRUE if able to move forward or %FALSE otherwise.
 */
gboolean webkit_web_view_can_go_forward(WebKitWebView* webView)
{
    g_return_val_if_fail(WEBKIT_IS_WEB_VIEW(webView), FALSE);

    return !!getPage(webView).backForwardList().forwardItem();
}

/**
 * webkit_web_view_get_uri:
 * @web_view: a #WebKitWebView
 *
 * Returns the current active URI of @web_view. The active URI might change during
 * a load operation:
 *
 * <orderedlist>
 * <listitem><para>
 *   When nothing has been loaded yet on @web_view the active URI is %NULL.
 * </para></listitem>
 * <listitem><para>
 *   When a new load operation starts the active URI is the requested URI:
 *   <itemizedlist>
 *   <listitem><para>
 *     If the load operation was started by webkit_web_view_load_uri(),
 *     the requested URI is the given one.
 *   </para></listitem>
 *   <listitem><para>
 *     If the load operation was started by webkit_web_view_load_html(),
 *     the requested URI is "about:blank".
 *   </para></listitem>
 *   <listitem><para>
 *     If the load operation was started by webkit_web_view_load_alternate_html(),
 *     the requested URI is content URI provided.
 *   </para></listitem>
 *   <listitem><para>
 *     If the load operation was started by webkit_web_view_go_back() or
 *     webkit_web_view_go_forward(), the requested URI is the original URI
 *     of the previous/next item in the #WebKitBackForwardList of @web_view.
 *   </para></listitem>
 *   <listitem><para>
 *     If the load operation was started by
 *     webkit_web_view_go_to_back_forward_list_item(), the requested URI
 *     is the opriginal URI of the given #WebKitBackForwardListItem.
 *   </para></listitem>
 *   </itemizedlist>
 * </para></listitem>
 * <listitem><para>
 *   If there is a server redirection during the load operation,
 *   the active URI is the redirected URI. When the signal
 *   #WebKitWebView::load-changed is emitted with %WEBKIT_LOAD_REDIRECTED
 *   event, the active URI is already updated to the redirected URI.
 * </para></listitem>
 * <listitem><para>
 *   When the signal #WebKitWebView::load-changed is emitted
 *   with %WEBKIT_LOAD_COMMITTED event, the active URI is the final
 *   one and it will not change unless a new load operation is started
 *   or a navigation action within the same page is performed.
 * </para></listitem>
 * </orderedlist>
 *
 * You can monitor the active URI by connecting to the notify::uri
 * signal of @web_view.
 *
 * Returns: the current active URI of @web_view or %NULL
 *    if nothing has been loaded yet.
 */
const gchar* webkit_web_view_get_uri(WebKitWebView* webView)
{
    g_return_val_if_fail(WEBKIT_IS_WEB_VIEW(webView), 0);

    return webView->priv->activeURI.data();
}

#if PLATFORM(GTK)
/**
 * webkit_web_view_get_favicon:
 * @web_view: a #WebKitWebView
 *
 * Returns favicon currently associated to @web_view, if any. You can
 * connect to notify::favicon signal of @web_view to be notified when
 * the favicon is available.
 *
 * Returns: (transfer none): a pointer to a #cairo_surface_t with the
 *    favicon or %NULL if there's no icon associated with @web_view.
 */
cairo_surface_t* webkit_web_view_get_favicon(WebKitWebView* webView)
{
    g_return_val_if_fail(WEBKIT_IS_WEB_VIEW(webView), 0);
    if (webView->priv->activeURI.isNull())
        return 0;

    return webView->priv->favicon.get();
}
#endif

/**
 * webkit_web_view_get_custom_charset:
 * @web_view: a #WebKitWebView
 *
 * Returns the current custom character encoding name of @web_view.
 *
 * Returns: the current custom character encoding name or %NULL if no
 *    custom character encoding has been set.
 */
const gchar* webkit_web_view_get_custom_charset(WebKitWebView* webView)
{
    g_return_val_if_fail(WEBKIT_IS_WEB_VIEW(webView), 0);

    String customTextEncoding = getPage(webView).customTextEncodingName();
    if (customTextEncoding.isEmpty())
        return 0;

    webView->priv->customTextEncoding = customTextEncoding.utf8();
    return webView->priv->customTextEncoding.data();
}

/**
 * webkit_web_view_set_custom_charset:
 * @web_view: a #WebKitWebView
 * @charset: (allow-none): a character encoding name or %NULL
 *
 * Sets the current custom character encoding override of @web_view. The custom
 * character encoding will override any text encoding detected via HTTP headers or
 * META tags. Calling this method will stop any current load operation and reload the
 * current page. Setting the custom character encoding to %NULL removes the character
 * encoding override.
 */
void webkit_web_view_set_custom_charset(WebKitWebView* webView, const gchar* charset)
{
    g_return_if_fail(WEBKIT_IS_WEB_VIEW(webView));

    getPage(webView).setCustomTextEncodingName(String::fromUTF8(charset));
}

/**
 * webkit_web_view_get_estimated_load_progress:
 * @web_view: a #WebKitWebView
 *
 * Gets the value of the #WebKitWebView:estimated-load-progress property.
 * You can monitor the estimated progress of a load operation by
 * connecting to the notify::estimated-load-progress signal of @web_view.
 *
 * Returns: an estimate of the of the percent complete for a document
 *     load as a range from 0.0 to 1.0.
 */
gdouble webkit_web_view_get_estimated_load_progress(WebKitWebView* webView)
{
    g_return_val_if_fail(WEBKIT_IS_WEB_VIEW(webView), 0);
    return getPage(webView).pageLoadState().estimatedProgress();
}

/**
 * webkit_web_view_get_back_forward_list:
 * @web_view: a #WebKitWebView
 *
 * Obtains the #WebKitBackForwardList associated with the given #WebKitWebView. The
 * #WebKitBackForwardList is owned by the #WebKitWebView.
 *
 * Returns: (transfer none): the #WebKitBackForwardList
 */
WebKitBackForwardList* webkit_web_view_get_back_forward_list(WebKitWebView* webView)
{
    g_return_val_if_fail(WEBKIT_IS_WEB_VIEW(webView), 0);

    return webView->priv->backForwardList.get();
}

/**
 * webkit_web_view_go_to_back_forward_list_item:
 * @web_view: a #WebKitWebView
 * @list_item: a #WebKitBackForwardListItem
 *
 * Loads the specific history item @list_item.
 * You can monitor the load operation by connecting to
 * #WebKitWebView::load-changed signal.
 */
void webkit_web_view_go_to_back_forward_list_item(WebKitWebView* webView, WebKitBackForwardListItem* listItem)
{
    g_return_if_fail(WEBKIT_IS_WEB_VIEW(webView));
    g_return_if_fail(WEBKIT_IS_BACK_FORWARD_LIST_ITEM(listItem));

    getPage(webView).goToBackForwardItem(*webkitBackForwardListItemGetItem(listItem));
}

/**
 * webkit_web_view_set_settings:
 * @web_view: a #WebKitWebView
 * @settings: a #WebKitSettings
 *
 * Sets the #WebKitSettings to be applied to @web_view. The
 * existing #WebKitSettings of @web_view will be replaced by
 * @settings. New settings are applied immediately on @web_view.
 * The same #WebKitSettings object can be shared
 * by multiple #WebKitWebView<!-- -->s.
 */
void webkit_web_view_set_settings(WebKitWebView* webView, WebKitSettings* settings)
{
    g_return_if_fail(WEBKIT_IS_WEB_VIEW(webView));
    g_return_if_fail(WEBKIT_IS_SETTINGS(settings));

    if (webView->priv->settings == settings)
        return;

    // The "settings" property is set on construction, and in that
    // case webkit_web_view_set_settings() will be called *before*
    // any settings have been assigned. In that case there are no
    // signal handlers to disconnect.
    if (webView->priv->settings)
        webkitWebViewDisconnectSettingsSignalHandlers(webView);

    webView->priv->settings = settings;
    webkitWebViewUpdateSettings(webView);
    g_object_notify(G_OBJECT(webView), "settings");
}

/**
 * webkit_web_view_get_settings:
 * @web_view: a #WebKitWebView
 *
 * Gets the #WebKitSettings currently applied to @web_view.
 * If no other #WebKitSettings have been explicitly applied to
 * @web_view with webkit_web_view_set_settings(), the default
 * #WebKitSettings will be returned. This method always returns
 * a valid #WebKitSettings object.
 * To modify any of the @web_view settings, you can either create
 * a new #WebKitSettings object with webkit_settings_new(), setting
 * the desired preferences, and then replace the existing @web_view
 * settings with webkit_web_view_set_settings() or get the existing
 * @web_view settings and update it directly. #WebKitSettings objects
 * can be shared by multiple #WebKitWebView<!-- -->s, so modifying
 * the settings of a #WebKitWebView would affect other
 * #WebKitWebView<!-- -->s using the same #WebKitSettings.
 *
 * Returns: (transfer none): the #WebKitSettings attached to @web_view
 */
WebKitSettings* webkit_web_view_get_settings(WebKitWebView* webView)
{
    g_return_val_if_fail(WEBKIT_IS_WEB_VIEW(webView), nullptr);
    return webView->priv->settings.get();
}

/**
 * webkit_web_view_get_window_properties:
 * @web_view: a #WebKitWebView
 *
 * Get the #WebKitWindowProperties object containing the properties
 * that the window containing @web_view should have.
 *
 * Returns: (transfer none): the #WebKitWindowProperties of @web_view
 */
WebKitWindowProperties* webkit_web_view_get_window_properties(WebKitWebView* webView)
{
    g_return_val_if_fail(WEBKIT_IS_WEB_VIEW(webView), 0);

    return webView->priv->windowProperties.get();
}

/**
 * webkit_web_view_set_zoom_level:
 * @web_view: a #WebKitWebView
 * @zoom_level: the zoom level
 *
 * Set the zoom level of @web_view, i.e. the factor by which the
 * view contents are scaled with respect to their original size.
 */
void webkit_web_view_set_zoom_level(WebKitWebView* webView, gdouble zoomLevel)
{
    g_return_if_fail(WEBKIT_IS_WEB_VIEW(webView));

    if (webkit_web_view_get_zoom_level(webView) == zoomLevel)
        return;

    auto& page = getPage(webView);
    page.scalePage(1.0, IntPoint()); // Reset page scale when zoom level is changed
    if (webkit_settings_get_zoom_text_only(webView->priv->settings.get()))
        page.setTextZoomFactor(zoomLevel);
    else
        page.setPageZoomFactor(zoomLevel);
    g_object_notify(G_OBJECT(webView), "zoom-level");
}

/**
 * webkit_web_view_get_zoom_level:
 * @web_view: a #WebKitWebView
 *
 * Get the zoom level of @web_view, i.e. the factor by which the
 * view contents are scaled with respect to their original size.
 *
 * Returns: the current zoom level of @web_view
 */
gdouble webkit_web_view_get_zoom_level(WebKitWebView* webView)
{
    g_return_val_if_fail(WEBKIT_IS_WEB_VIEW(webView), 1);

    auto& page = getPage(webView);
    gboolean zoomTextOnly = webkit_settings_get_zoom_text_only(webView->priv->settings.get());
    return zoomTextOnly ? page.textZoomFactor() : page.pageZoomFactor();
}

/**
 * webkit_web_view_can_execute_editing_command:
 * @web_view: a #WebKitWebView
 * @command: the command to check
 * @cancellable: (allow-none): a #GCancellable or %NULL to ignore
 * @callback: (scope async): a #GAsyncReadyCallback to call when the request is satisfied
 * @user_data: (closure): the data to pass to callback function
 *
 * Asynchronously check if it is possible to execute the given editing command.
 *
 * When the operation is finished, @callback will be called. You can then call
 * webkit_web_view_can_execute_editing_command_finish() to get the result of the operation.
 */
void webkit_web_view_can_execute_editing_command(WebKitWebView* webView, const char* command, GCancellable* cancellable, GAsyncReadyCallback callback, gpointer userData)
{
    g_return_if_fail(WEBKIT_IS_WEB_VIEW(webView));
    g_return_if_fail(command);

    GRefPtr<GTask> task = adoptGRef(g_task_new(webView, cancellable, callback, userData));
    getPage(webView).validateCommand(String::fromUTF8(command), [task = WTFMove(task)](const String&, bool isEnabled, int32_t, WebKit::CallbackBase::Error) {
        g_task_return_boolean(task.get(), isEnabled);
    });
}

/**
 * webkit_web_view_can_execute_editing_command_finish:
 * @web_view: a #WebKitWebView
 * @result: a #GAsyncResult
 * @error: return location for error or %NULL to ignore
 *
 * Finish an asynchronous operation started with webkit_web_view_can_execute_editing_command().
 *
 * Returns: %TRUE if the editing command can be executed or %FALSE otherwise
 */
gboolean webkit_web_view_can_execute_editing_command_finish(WebKitWebView* webView, GAsyncResult* result, GError** error)
{
    g_return_val_if_fail(WEBKIT_IS_WEB_VIEW(webView), FALSE);
    g_return_val_if_fail(g_task_is_valid(result, webView), FALSE);

    return g_task_propagate_boolean(G_TASK(result), error);
}

/**
 * webkit_web_view_execute_editing_command:
 * @web_view: a #WebKitWebView
 * @command: the command to execute
 *
 * Request to execute the given @command for @web_view. You can use
 * webkit_web_view_can_execute_editing_command() to check whether
 * it's possible to execute the command.
 */
void webkit_web_view_execute_editing_command(WebKitWebView* webView, const char* command)
{
    g_return_if_fail(WEBKIT_IS_WEB_VIEW(webView));
    g_return_if_fail(command);

    getPage(webView).executeEditCommand(String::fromUTF8(command));
}

/**
 * webkit_web_view_execute_editing_command_with_argument:
 * @web_view: a #WebKitWebView
 * @command: the command to execute
 * @argument: the command argument
 *
 * Request to execute the given @command with @argument for @web_view. You can use
 * webkit_web_view_can_execute_editing_command() to check whether
 * it's possible to execute the command.
 *
 * Since: 2.10
 */
void webkit_web_view_execute_editing_command_with_argument(WebKitWebView* webView, const char* command, const char* argument)
{
    g_return_if_fail(WEBKIT_IS_WEB_VIEW(webView));
    g_return_if_fail(command);
    g_return_if_fail(argument);

    getPage(webView).executeEditCommand(String::fromUTF8(command), String::fromUTF8(argument));
}

/**
 * webkit_web_view_get_find_controller:
 * @web_view: the #WebKitWebView
 *
 * Gets the #WebKitFindController that will allow the caller to query
 * the #WebKitWebView for the text to look for.
 *
 * Returns: (transfer none): the #WebKitFindController associated to
 * this particular #WebKitWebView.
 */
WebKitFindController* webkit_web_view_get_find_controller(WebKitWebView* webView)
{
    g_return_val_if_fail(WEBKIT_IS_WEB_VIEW(webView), 0);

    if (!webView->priv->findController)
        webView->priv->findController = adoptGRef(WEBKIT_FIND_CONTROLLER(g_object_new(WEBKIT_TYPE_FIND_CONTROLLER, "web-view", webView, NULL)));

    return webView->priv->findController.get();
}

#if PLATFORM(GTK)
/**
 * webkit_web_view_get_javascript_global_context: (skip)
 * @web_view: a #WebKitWebView
 *
 * Get the global JavaScript context used by @web_view to deserialize the
 * result values of scripts executed with webkit_web_view_run_javascript().
 *
 * Returns: the <function>JSGlobalContextRef</function> used by @web_view to deserialize
 *    the result values of scripts.
 *
 * Deprecated: 2.22: Use jsc_value_get_context() instead.
 */
JSGlobalContextRef webkit_web_view_get_javascript_global_context(WebKitWebView* webView)
{
    g_return_val_if_fail(WEBKIT_IS_WEB_VIEW(webView), nullptr);

    // We keep a reference to the js context in the view only when this method is called
    // for backwards compatibility.
    if (!webView->priv->jsContext)
        webView->priv->jsContext = SharedJavascriptContext::singleton().getOrCreateContext();
    return jscContextGetJSContext(webView->priv->jsContext.get());
}
#endif

static void webkitWebViewRunJavaScriptCallback(API::SerializedScriptValue* wkSerializedScriptValue, const ExceptionDetails& exceptionDetails, GTask* task)
{
    if (g_task_return_error_if_cancelled(task))
        return;

    if (!wkSerializedScriptValue) {
        StringBuilder builder;
        if (!exceptionDetails.sourceURL.isEmpty()) {
            builder.append(exceptionDetails.sourceURL);
            if (exceptionDetails.lineNumber > 0) {
                builder.append(':');
                builder.appendNumber(exceptionDetails.lineNumber);
            }
            if (exceptionDetails.columnNumber > 0) {
                builder.append(':');
                builder.appendNumber(exceptionDetails.columnNumber);
            }
            builder.appendLiteral(": ");
        }
        builder.append(exceptionDetails.message);
        g_task_return_new_error(task, WEBKIT_JAVASCRIPT_ERROR, WEBKIT_JAVASCRIPT_ERROR_SCRIPT_FAILED,
            "%s", builder.toString().utf8().data());
        return;
    }

    g_task_return_pointer(task, webkitJavascriptResultCreate(wkSerializedScriptValue->internalRepresentation()),
        reinterpret_cast<GDestroyNotify>(webkit_javascript_result_unref));
}

/**
 * webkit_web_view_run_javascript:
 * @web_view: a #WebKitWebView
 * @script: the script to run
 * @cancellable: (allow-none): a #GCancellable or %NULL to ignore
 * @callback: (scope async): a #GAsyncReadyCallback to call when the script finished
 * @user_data: (closure): the data to pass to callback function
 *
 * Asynchronously run @script in the context of the current page in @web_view. If
 * WebKitSettings:enable-javascript is FALSE, this method will do nothing.
 *
 * When the operation is finished, @callback will be called. You can then call
 * webkit_web_view_run_javascript_finish() to get the result of the operation.
 */
void webkit_web_view_run_javascript(WebKitWebView* webView, const gchar* script, GCancellable* cancellable, GAsyncReadyCallback callback, gpointer userData)
{
    g_return_if_fail(WEBKIT_IS_WEB_VIEW(webView));
    g_return_if_fail(script);

    GRefPtr<GTask> task = adoptGRef(g_task_new(webView, cancellable, callback, userData));
    getPage(webView).runJavaScriptInMainFrame(String::fromUTF8(script), true, [task = WTFMove(task)](API::SerializedScriptValue* serializedScriptValue, bool, const ExceptionDetails& exceptionDetails, WebKit::CallbackBase::Error) {
        webkitWebViewRunJavaScriptCallback(serializedScriptValue, exceptionDetails, task.get());
    });
}

/**
 * webkit_web_view_run_javascript_finish:
 * @web_view: a #WebKitWebView
 * @result: a #GAsyncResult
 * @error: return location for error or %NULL to ignore
 *
 * Finish an asynchronous operation started with webkit_web_view_run_javascript().
 *
 * This is an example of using webkit_web_view_run_javascript() with a script returning
 * a string:
 *
 * <informalexample><programlisting>
 * static void
 * web_view_javascript_finished (GObject      *object,
 *                               GAsyncResult *result,
 *                               gpointer      user_data)
 * {
 *     WebKitJavascriptResult *js_result;
 *     JSCValue               *value;
 *     GError                 *error = NULL;
 *
 *     js_result = webkit_web_view_run_javascript_finish (WEBKIT_WEB_VIEW (object), result, &error);
 *     if (!js_result) {
 *         g_warning ("Error running javascript: %s", error->message);
 *         g_error_free (error);
 *         return;
 *     }
 *
 *     value = webkit_javascript_result_get_js_value (js_result);
 *     if (jsc_value_is_string (value)) {
 *         JSCException *exception;
 *         gchar        *str_value;
 *
 *         str_value = jsc_value_to_string (value);
 *         exception = jsc_context_get_exception (jsc_value_get_context (value));
 *         if (exception)
 *             g_warning ("Error running javascript: %s", jsc_exception_get_message (exception));
 *         else
 *             g_print ("Script result: %s\n", str_value);
 *         g_free (str_value);
 *     } else {
 *         g_warning ("Error running javascript: unexpected return value");
 *     }
 *     webkit_javascript_result_unref (js_result);
 * }
 *
 * static void
 * web_view_get_link_url (WebKitWebView *web_view,
 *                        const gchar   *link_id)
 * {
 *     gchar *script;
 *
 *     script = g_strdup_printf ("window.document.getElementById('%s').href;", link_id);
 *     webkit_web_view_run_javascript (web_view, script, NULL, web_view_javascript_finished, NULL);
 *     g_free (script);
 * }
 * </programlisting></informalexample>
 *
 * Returns: (transfer full): a #WebKitJavascriptResult with the result of the last executed statement in @script
 *    or %NULL in case of error
 */
WebKitJavascriptResult* webkit_web_view_run_javascript_finish(WebKitWebView* webView, GAsyncResult* result, GError** error)
{
    g_return_val_if_fail(WEBKIT_IS_WEB_VIEW(webView), nullptr);
    g_return_val_if_fail(g_task_is_valid(result, webView), nullptr);

    return static_cast<WebKitJavascriptResult*>(g_task_propagate_pointer(G_TASK(result), error));
}

/**
 * webkit_web_view_run_javascript_in_world:
 * @web_view: a #WebKitWebView
 * @script: the script to run
 * @world_name: the name of a #WebKitScriptWorld
 * @cancellable: (allow-none): a #GCancellable or %NULL to ignore
 * @callback: (scope async): a #GAsyncReadyCallback to call when the script finished
 * @user_data: (closure): the data to pass to callback function
 *
 * Asynchronously run @script in the script world with name @world_name of the current page context in @web_view.
 * If WebKitSettings:enable-javascript is FALSE, this method will do nothing.
 *
 * When the operation is finished, @callback will be called. You can then call
 * webkit_web_view_run_javascript_in_world_finish() to get the result of the operation.
 *
 * Since: 2.22
 */
void webkit_web_view_run_javascript_in_world(WebKitWebView* webView, const gchar* script, const char* worldName, GCancellable* cancellable, GAsyncReadyCallback callback, gpointer userData)
{
    g_return_if_fail(WEBKIT_IS_WEB_VIEW(webView));
    g_return_if_fail(script);
    g_return_if_fail(worldName);

    GRefPtr<GTask> task = adoptGRef(g_task_new(webView, cancellable, callback, userData));
    getPage(webView).runJavaScriptInMainFrameScriptWorld(String::fromUTF8(script), true, String::fromUTF8(worldName), [task = WTFMove(task)](API::SerializedScriptValue* serializedScriptValue, bool, const ExceptionDetails& exceptionDetails, WebKit::CallbackBase::Error) {
        webkitWebViewRunJavaScriptCallback(serializedScriptValue, exceptionDetails, task.get());
    });
}

/**
 * webkit_web_view_run_javascript_in_world_finish:
 * @web_view: a #WebKitWebView
 * @result: a #GAsyncResult
 * @error: return location for error or %NULL to ignore
 *
 * Finish an asynchronous operation started with webkit_web_view_run_javascript_in_world().
 *
 * Returns: (transfer full): a #WebKitJavascriptResult with the result of the last executed statement in @script
 *    or %NULL in case of error
 *
 * Since: 2.22
 */
WebKitJavascriptResult* webkit_web_view_run_javascript_in_world_finish(WebKitWebView* webView, GAsyncResult* result, GError** error)
{
    g_return_val_if_fail(WEBKIT_IS_WEB_VIEW(webView), nullptr);
    g_return_val_if_fail(g_task_is_valid(result, webView), nullptr);

    return static_cast<WebKitJavascriptResult*>(g_task_propagate_pointer(G_TASK(result), error));
}

static void resourcesStreamReadCallback(GObject* object, GAsyncResult* result, gpointer userData)
{
    GRefPtr<GTask> task = adoptGRef(G_TASK(userData));

    GError* error = 0;
    g_output_stream_splice_finish(G_OUTPUT_STREAM(object), result, &error);
    if (error) {
        g_task_return_error(task.get(), error);
        return;
    }

    WebKitWebView* webView = WEBKIT_WEB_VIEW(g_task_get_source_object(task.get()));
    gpointer outputStreamData = g_memory_output_stream_get_data(G_MEMORY_OUTPUT_STREAM(object));
    getPage(webView).runJavaScriptInMainFrame(String::fromUTF8(reinterpret_cast<const gchar*>(outputStreamData)), true,
        [task](API::SerializedScriptValue* serializedScriptValue, bool, const ExceptionDetails& exceptionDetails, WebKit::CallbackBase::Error) {
            webkitWebViewRunJavaScriptCallback(serializedScriptValue, exceptionDetails, task.get());
        });
}

/**
 * webkit_web_view_run_javascript_from_gresource:
 * @web_view: a #WebKitWebView
 * @resource: the location of the resource to load
 * @cancellable: (allow-none): a #GCancellable or %NULL to ignore
 * @callback: (scope async): a #GAsyncReadyCallback to call when the script finished
 * @user_data: (closure): the data to pass to callback function
 *
 * Asynchronously run the script from @resource in the context of the
 * current page in @web_view.
 *
 * When the operation is finished, @callback will be called. You can
 * then call webkit_web_view_run_javascript_from_gresource_finish() to get the result
 * of the operation.
 */
void webkit_web_view_run_javascript_from_gresource(WebKitWebView* webView, const gchar* resource, GCancellable* cancellable, GAsyncReadyCallback callback, gpointer userData)
{
    g_return_if_fail(WEBKIT_IS_WEB_VIEW(webView));
    g_return_if_fail(resource);

    GError* error = 0;
    GRefPtr<GInputStream> inputStream = adoptGRef(g_resources_open_stream(resource, G_RESOURCE_LOOKUP_FLAGS_NONE, &error));
    if (error) {
        g_task_report_error(webView, callback, userData, 0, error);
        return;
    }

    GTask* task = g_task_new(webView, cancellable, callback, userData);
    GRefPtr<GOutputStream> outputStream = adoptGRef(g_memory_output_stream_new(0, 0, fastRealloc, fastFree));
    g_output_stream_splice_async(outputStream.get(), inputStream.get(),
        static_cast<GOutputStreamSpliceFlags>(G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE | G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET),
        G_PRIORITY_DEFAULT, cancellable, resourcesStreamReadCallback, task);
}

/**
 * webkit_web_view_run_javascript_from_gresource_finish:
 * @web_view: a #WebKitWebView
 * @result: a #GAsyncResult
 * @error: return location for error or %NULL to ignore
 *
 * Finish an asynchronous operation started with webkit_web_view_run_javascript_from_gresource().
 *
 * Check webkit_web_view_run_javascript_finish() for a usage example.
 *
 * Returns: (transfer full): a #WebKitJavascriptResult with the result of the last executed statement in @script
 *    or %NULL in case of error
 */
WebKitJavascriptResult* webkit_web_view_run_javascript_from_gresource_finish(WebKitWebView* webView, GAsyncResult* result, GError** error)
{
    g_return_val_if_fail(WEBKIT_IS_WEB_VIEW(webView), 0);
    g_return_val_if_fail(g_task_is_valid(result, webView), 0);

    return static_cast<WebKitJavascriptResult*>(g_task_propagate_pointer(G_TASK(result), error));
}

/**
 * webkit_web_view_get_main_resource:
 * @web_view: a #WebKitWebView
 *
 * Return the main resource of @web_view.
 *
 * Returns: (transfer none): the main #WebKitWebResource of the view
 *    or %NULL if nothing has been loaded.
 */
WebKitWebResource* webkit_web_view_get_main_resource(WebKitWebView* webView)
{
    g_return_val_if_fail(WEBKIT_IS_WEB_VIEW(webView), 0);

    return webView->priv->mainResource.get();
}

#if PLATFORM(GTK)
/**
 * webkit_web_view_get_inspector:
 * @web_view: a #WebKitWebView
 *
 * Get the #WebKitWebInspector associated to @web_view
 *
 * Returns: (transfer none): the #WebKitWebInspector of @web_view
 */
WebKitWebInspector* webkit_web_view_get_inspector(WebKitWebView* webView)
{
    g_return_val_if_fail(WEBKIT_IS_WEB_VIEW(webView), 0);

    if (!webView->priv->inspector)
        webView->priv->inspector = adoptGRef(webkitWebInspectorCreate(getPage(webView).inspector()));

    return webView->priv->inspector.get();
}
#endif

/**
 * webkit_web_view_can_show_mime_type:
 * @web_view: a #WebKitWebView
 * @mime_type: a MIME type
 *
 * Whether or not a MIME type can be displayed in @web_view.
 *
 * Returns: %TRUE if the MIME type @mime_type can be displayed or %FALSE otherwise
 */
gboolean webkit_web_view_can_show_mime_type(WebKitWebView* webView, const char* mimeType)
{
    g_return_val_if_fail(WEBKIT_IS_WEB_VIEW(webView), FALSE);
    g_return_val_if_fail(mimeType, FALSE);

    return getPage(webView).canShowMIMEType(String::fromUTF8(mimeType));
}

struct ViewSaveAsyncData {
    RefPtr<API::Data> webData;
    GRefPtr<GFile> file;
};
WEBKIT_DEFINE_ASYNC_DATA_STRUCT(ViewSaveAsyncData)

static void fileReplaceContentsCallback(GObject* object, GAsyncResult* result, gpointer data)
{
    GRefPtr<GTask> task = adoptGRef(G_TASK(data));
    GError* error = 0;
    if (!g_file_replace_contents_finish(G_FILE(object), result, 0, &error)) {
        g_task_return_error(task.get(), error);
        return;
    }

    g_task_return_boolean(task.get(), TRUE);
}

static void getContentsAsMHTMLDataCallback(API::Data* wkData, GTask* taskPtr)
{
    auto task = adoptGRef(taskPtr);
    if (g_task_return_error_if_cancelled(task.get()))
        return;

    ViewSaveAsyncData* data = static_cast<ViewSaveAsyncData*>(g_task_get_task_data(task.get()));
    // We need to retain the data until the asyncronous process
    // initiated by the user has finished completely.
    data->webData = wkData;

    // If we are saving to a file we need to write the data on disk before finishing.
    if (g_task_get_source_tag(task.get()) == webkit_web_view_save_to_file) {
        ASSERT(G_IS_FILE(data->file.get()));
        GCancellable* cancellable = g_task_get_cancellable(task.get());
        g_file_replace_contents_async(data->file.get(), reinterpret_cast<const gchar*>(data->webData->bytes()), data->webData->size(),
            0, FALSE, G_FILE_CREATE_REPLACE_DESTINATION, cancellable, fileReplaceContentsCallback, task.leakRef());
        return;
    }

    g_task_return_boolean(task.get(), TRUE);
}

/**
 * webkit_web_view_save:
 * @web_view: a #WebKitWebView
 * @save_mode: the #WebKitSaveMode specifying how the web page should be saved.
 * @cancellable: (allow-none): a #GCancellable or %NULL to ignore
 * @callback: (scope async): a #GAsyncReadyCallback to call when the request is satisfied
 * @user_data: (closure): the data to pass to callback function
 *
 * Asynchronously save the current web page associated to the
 * #WebKitWebView into a self-contained format using the mode
 * specified in @save_mode.
 *
 * When the operation is finished, @callback will be called. You can
 * then call webkit_web_view_save_finish() to get the result of the
 * operation.
 */
void webkit_web_view_save(WebKitWebView* webView, WebKitSaveMode saveMode, GCancellable* cancellable, GAsyncReadyCallback callback, gpointer userData)
{
    g_return_if_fail(WEBKIT_IS_WEB_VIEW(webView));

    // We only support MHTML at the moment.
    g_return_if_fail(saveMode == WEBKIT_SAVE_MODE_MHTML);

    GTask* task = g_task_new(webView, cancellable, callback, userData);
    g_task_set_source_tag(task, reinterpret_cast<gpointer>(webkit_web_view_save));
    g_task_set_task_data(task, createViewSaveAsyncData(), reinterpret_cast<GDestroyNotify>(destroyViewSaveAsyncData));
    getPage(webView).getContentsAsMHTMLData([task](API::Data* data, WebKit::CallbackBase::Error) {
        getContentsAsMHTMLDataCallback(data, task);
    });
}

/**
 * webkit_web_view_save_finish:
 * @web_view: a #WebKitWebView
 * @result: a #GAsyncResult
 * @error: return location for error or %NULL to ignore
 *
 * Finish an asynchronous operation started with webkit_web_view_save().
 *
 * Returns: (transfer full): a #GInputStream with the result of saving
 *    the current web page or %NULL in case of error.
 */
GInputStream* webkit_web_view_save_finish(WebKitWebView* webView, GAsyncResult* result, GError** error)
{
    g_return_val_if_fail(WEBKIT_IS_WEB_VIEW(webView), 0);
    g_return_val_if_fail(g_task_is_valid(result, webView), 0);

    GTask* task = G_TASK(result);
    if (!g_task_propagate_boolean(task, error))
        return 0;

    GInputStream* dataStream = g_memory_input_stream_new();
    ViewSaveAsyncData* data = static_cast<ViewSaveAsyncData*>(g_task_get_task_data(task));
    gsize length = data->webData->size();
    if (length)
        g_memory_input_stream_add_data(G_MEMORY_INPUT_STREAM(dataStream), g_memdup(data->webData->bytes(), length), length, g_free);

    return dataStream;
}

/**
 * webkit_web_view_save_to_file:
 * @web_view: a #WebKitWebView
 * @file: the #GFile where the current web page should be saved to.
 * @save_mode: the #WebKitSaveMode specifying how the web page should be saved.
 * @cancellable: (allow-none): a #GCancellable or %NULL to ignore
 * @callback: (scope async): a #GAsyncReadyCallback to call when the request is satisfied
 * @user_data: (closure): the data to pass to callback function
 *
 * Asynchronously save the current web page associated to the
 * #WebKitWebView into a self-contained format using the mode
 * specified in @save_mode and writing it to @file.
 *
 * When the operation is finished, @callback will be called. You can
 * then call webkit_web_view_save_to_file_finish() to get the result of the
 * operation.
 */
void webkit_web_view_save_to_file(WebKitWebView* webView, GFile* file, WebKitSaveMode saveMode, GCancellable* cancellable, GAsyncReadyCallback callback, gpointer userData)
{
    g_return_if_fail(WEBKIT_IS_WEB_VIEW(webView));
    g_return_if_fail(G_IS_FILE(file));

    // We only support MHTML at the moment.
    g_return_if_fail(saveMode == WEBKIT_SAVE_MODE_MHTML);

    GTask* task = g_task_new(webView, cancellable, callback, userData);
    g_task_set_source_tag(task, reinterpret_cast<gpointer>(webkit_web_view_save_to_file));
    ViewSaveAsyncData* data = createViewSaveAsyncData();
    data->file = file;
    g_task_set_task_data(task, data, reinterpret_cast<GDestroyNotify>(destroyViewSaveAsyncData));

    getPage(webView).getContentsAsMHTMLData([task](API::Data* data, WebKit::CallbackBase::Error) {
        getContentsAsMHTMLDataCallback(data, task);
    });
}

/**
 * webkit_web_view_save_to_file_finish:
 * @web_view: a #WebKitWebView
 * @result: a #GAsyncResult
 * @error: return location for error or %NULL to ignore
 *
 * Finish an asynchronous operation started with webkit_web_view_save_to_file().
 *
 * Returns: %TRUE if the web page was successfully saved to a file or %FALSE otherwise.
 */
gboolean webkit_web_view_save_to_file_finish(WebKitWebView* webView, GAsyncResult* result, GError** error)
{
    g_return_val_if_fail(WEBKIT_IS_WEB_VIEW(webView), FALSE);
    g_return_val_if_fail(g_task_is_valid(result, webView), FALSE);

    return g_task_propagate_boolean(G_TASK(result), error);
}

/**
 * webkit_web_view_download_uri:
 * @web_view: a #WebKitWebView
 * @uri: the URI to download
 *
 * Requests downloading of the specified URI string for @web_view.
 *
 * Returns: (transfer full): a new #WebKitDownload representing
 *    the download operation.
 */
WebKitDownload* webkit_web_view_download_uri(WebKitWebView* webView, const char* uri)
{
    g_return_val_if_fail(WEBKIT_IS_WEB_VIEW(webView), nullptr);
    g_return_val_if_fail(uri, nullptr);

    GRefPtr<WebKitDownload> download = webkitWebContextStartDownload(webView->priv->context.get(), uri, &getPage(webView));
    return download.leakRef();
}

/**
 * webkit_web_view_get_tls_info:
 * @web_view: a #WebKitWebView
 * @certificate: (out) (transfer none): return location for a #GTlsCertificate
 * @errors: (out): return location for a #GTlsCertificateFlags the verification status of @certificate
 *
 * Retrieves the #GTlsCertificate associated with the main resource of @web_view,
 * and the #GTlsCertificateFlags showing what problems, if any, have been found
 * with that certificate.
 * If the connection is not HTTPS, this function returns %FALSE.
 * This function should be called after a response has been received from the
 * server, so you can connect to #WebKitWebView::load-changed and call this function
 * when it's emitted with %WEBKIT_LOAD_COMMITTED event.
 *
 * Note that this function provides no information about the security of the web
 * page if the current #WebKitTLSErrorsPolicy is @WEBKIT_TLS_ERRORS_POLICY_IGNORE,
 * as subresources of the page may be controlled by an attacker. This function
 * may safely be used to determine the security status of the current page only
 * if the current #WebKitTLSErrorsPolicy is @WEBKIT_TLS_ERRORS_POLICY_FAIL, in
 * which case subresources that fail certificate verification will be blocked.
 *
 * Returns: %TRUE if the @web_view connection uses HTTPS and a response has been received
 *    from the server, or %FALSE otherwise.
 */
gboolean webkit_web_view_get_tls_info(WebKitWebView* webView, GTlsCertificate** certificate, GTlsCertificateFlags* errors)
{
    g_return_val_if_fail(WEBKIT_IS_WEB_VIEW(webView), FALSE);

    WebFrameProxy* mainFrame = getPage(webView).mainFrame();
    if (!mainFrame)
        return FALSE;

    auto* wkCertificateInfo = mainFrame->certificateInfo();
    g_return_val_if_fail(wkCertificateInfo, FALSE);

    const auto& certificateInfo = wkCertificateInfo->certificateInfo();
    if (certificate)
        *certificate = certificateInfo.certificate();
    if (errors)
        *errors = certificateInfo.tlsErrors();

    return !!certificateInfo.certificate();
}

#if PLATFORM(GTK)
void webKitWebViewDidReceiveSnapshot(WebKitWebView* webView, uint64_t callbackID, WebImage* webImage)
{
    GRefPtr<GTask> task = webView->priv->snapshotResultsMap.take(callbackID);
    if (g_task_return_error_if_cancelled(task.get()))
        return;

    if (!webImage) {
        g_task_return_new_error(task.get(), WEBKIT_SNAPSHOT_ERROR, WEBKIT_SNAPSHOT_ERROR_FAILED_TO_CREATE,
            _("There was an error creating the snapshot"));
        return;
    }

    g_task_return_pointer(task.get(), webImage->bitmap().createCairoSurface().leakRef(), reinterpret_cast<GDestroyNotify>(cairo_surface_destroy));
}

static inline unsigned webKitSnapshotOptionsToSnapshotOptions(WebKitSnapshotOptions options)
{
    SnapshotOptions snapshotOptions = 0;

    if (!(options & WEBKIT_SNAPSHOT_OPTIONS_INCLUDE_SELECTION_HIGHLIGHTING))
        snapshotOptions |= SnapshotOptionsExcludeSelectionHighlighting;

    return snapshotOptions;
}

static inline SnapshotRegion toSnapshotRegion(WebKitSnapshotRegion region)
{
    switch (region) {
    case WEBKIT_SNAPSHOT_REGION_VISIBLE:
        return SnapshotRegionVisible;
    case WEBKIT_SNAPSHOT_REGION_FULL_DOCUMENT:
        return SnapshotRegionFullDocument;
    default:
        ASSERT_NOT_REACHED();
        return SnapshotRegionVisible;
    }
}

static inline uint64_t generateSnapshotCallbackID()
{
    static uint64_t uniqueCallbackID = 1;
    return uniqueCallbackID++;
}

/**
 * webkit_web_view_get_snapshot:
 * @web_view: a #WebKitWebView
 * @region: the #WebKitSnapshotRegion for this snapshot
 * @options: #WebKitSnapshotOptions for the snapshot
 * @cancellable: (allow-none): a #GCancellable
 * @callback: (scope async): a #GAsyncReadyCallback
 * @user_data: (closure): user data
 *
 * Asynchronously retrieves a snapshot of @web_view for @region.
 * @options specifies how the snapshot should be rendered.
 *
 * When the operation is finished, @callback will be called. You must
 * call webkit_web_view_get_snapshot_finish() to get the result of the
 * operation.
 */
void webkit_web_view_get_snapshot(WebKitWebView* webView, WebKitSnapshotRegion region, WebKitSnapshotOptions options, GCancellable* cancellable, GAsyncReadyCallback callback, gpointer userData)
{
    g_return_if_fail(WEBKIT_IS_WEB_VIEW(webView));

    API::Dictionary::MapType message;
    uint64_t callbackID = generateSnapshotCallbackID();
    message.set(String::fromUTF8("SnapshotOptions"), API::UInt64::create(static_cast<uint64_t>(webKitSnapshotOptionsToSnapshotOptions(options))));
    message.set(String::fromUTF8("SnapshotRegion"), API::UInt64::create(static_cast<uint64_t>(toSnapshotRegion(region))));
    message.set(String::fromUTF8("CallbackID"), API::UInt64::create(callbackID));
    message.set(String::fromUTF8("TransparentBackground"), API::Boolean::create(options & WEBKIT_SNAPSHOT_OPTIONS_TRANSPARENT_BACKGROUND));

    webView->priv->snapshotResultsMap.set(callbackID, adoptGRef(g_task_new(webView, cancellable, callback, userData)));
    getPage(webView).postMessageToInjectedBundle(String::fromUTF8("GetSnapshot"), API::Dictionary::create(WTFMove(message)).ptr());
}

/**
 * webkit_web_view_get_snapshot_finish:
 * @web_view: a #WebKitWebView
 * @result: a #GAsyncResult
 * @error: return location for error or %NULL to ignore
 *
 * Finishes an asynchronous operation started with webkit_web_view_get_snapshot().
 *
 * Returns: (transfer full): a #cairo_surface_t with the retrieved snapshot or %NULL in error.
 */
cairo_surface_t* webkit_web_view_get_snapshot_finish(WebKitWebView* webView, GAsyncResult* result, GError** error)
{
    g_return_val_if_fail(WEBKIT_IS_WEB_VIEW(webView), 0);
    g_return_val_if_fail(g_task_is_valid(result, webView), 0);

    return static_cast<cairo_surface_t*>(g_task_propagate_pointer(G_TASK(result), error));
}
#endif

void webkitWebViewWebProcessTerminated(WebKitWebView* webView, WebKitWebProcessTerminationReason reason)
{
#if PLATFORM(GTK)
    if (reason == WEBKIT_WEB_PROCESS_CRASHED) {
        gboolean returnValue;
        g_signal_emit(webView, signals[WEB_PROCESS_CRASHED], 0, &returnValue);
    }
#endif
    g_signal_emit(webView, signals[WEB_PROCESS_TERMINATED], 0, reason);
}

/*
 * webkit_web_view_is_editable:
 * @web_view: a #WebKitWebView
 *
 * Gets whether the user is allowed to edit the HTML document. When @web_view
 * is not editable an element in the HTML document can only be edited if the
 * CONTENTEDITABLE attribute has been set on the element or one of its parent
 * elements. By default a #WebKitWebView is not editable.
 *
 * Returns: %TRUE if the user is allowed to edit the HTML document, or %FALSE otherwise.
 *
 * Since: 2.8
 */
gboolean webkit_web_view_is_editable(WebKitWebView* webView)
{
    g_return_val_if_fail(WEBKIT_IS_WEB_VIEW(webView), FALSE);

    return getPage(webView).isEditable();
}

/**
 * webkit_web_view_set_editable:
 * @web_view: a #WebKitWebView
 * @editable: a #gboolean indicating the editable state
 *
 * Sets whether the user is allowed to edit the HTML document.
 *
 * If @editable is %TRUE, @web_view allows the user to edit the HTML document. If
 * @editable is %FALSE, an element in @web_view's document can only be edited if the
 * CONTENTEDITABLE attribute has been set on the element or one of its parent
 * elements. By default a #WebKitWebView is not editable.
 *
 * Normally, a HTML document is not editable unless the elements within the
 * document are editable. This function provides a way to make the contents
 * of a #WebKitWebView editable without altering the document or DOM structure.
 *
 * Since: 2.8
 */
void webkit_web_view_set_editable(WebKitWebView* webView, gboolean editable)
{
    g_return_if_fail(WEBKIT_IS_WEB_VIEW(webView));

    if (editable == getPage(webView).isEditable())
        return;

    getPage(webView).setEditable(editable);

    g_object_notify(G_OBJECT(webView), "editable");
}

/**
 * webkit_web_view_get_editor_state:
 * @web_view: a #WebKitWebView
 *
 * Gets the web editor state of @web_view.
 *
 * Returns: (transfer none): the #WebKitEditorState of the view
 *
 * Since: 2.10
 */
WebKitEditorState* webkit_web_view_get_editor_state(WebKitWebView *webView)
{
    g_return_val_if_fail(WEBKIT_IS_WEB_VIEW(webView), nullptr);

    if (!webView->priv->editorState)
        webView->priv->editorState = adoptGRef(webkitEditorStateCreate(getPage(webView)));

    return webView->priv->editorState.get();
}

/**
 * webkit_web_view_get_session_state:
 * @web_view: a #WebKitWebView
 *
 * Gets the current session state of @web_view
 *
 * Returns: (transfer full): a #WebKitWebViewSessionState
 *
 * Since: 2.12
 */
WebKitWebViewSessionState* webkit_web_view_get_session_state(WebKitWebView* webView)
{
    g_return_val_if_fail(WEBKIT_IS_WEB_VIEW(webView), nullptr);

    SessionState sessionState = getPage(webView).sessionState(nullptr);
    return webkitWebViewSessionStateCreate(WTFMove(sessionState));
}

/**
 * webkit_web_view_restore_session_state:
 * @web_view: a #WebKitWebView
 * @state: a #WebKitWebViewSessionState
 *
 * Restore the @web_view session state from @state
 *
 * Since: 2.12
 */
void webkit_web_view_restore_session_state(WebKitWebView* webView, WebKitWebViewSessionState* state)
{
    g_return_if_fail(WEBKIT_IS_WEB_VIEW(webView));
    g_return_if_fail(state);

    getPage(webView).restoreFromSessionState(webkitWebViewSessionStateGetSessionState(state), false);
}

#if PLATFORM(WPE)
/**
 * webkit_web_view_add_frame_displayed_callback:
 * @web_view: a #WebKitWebView
 * @callback: a #WebKitFrameDisplayedCallback
 * @user_data: (closure): user data to pass to @callback
 * @destroy_notify: (nullable): destroy notifier for @user_data
 *
 * Add a callback to be called when the backend notifies that a frame has been displayed in @web_view.
 *
 * Returns: an identifier that should be passed to webkit_web_view_remove_frame_displayed_callback()
 *    to remove the callback.
 *
 * Since: 2.24
 */
unsigned webkit_web_view_add_frame_displayed_callback(WebKitWebView* webView, WebKitFrameDisplayedCallback callback, gpointer userData, GDestroyNotify destroyNotify)
{
    g_return_val_if_fail(WEBKIT_IS_WEB_VIEW(webView), 0);
    g_return_val_if_fail(callback, 0);

    webView->priv->frameDisplayedCallbacks.append(FrameDisplayedCallback(callback, userData, destroyNotify));
    return webView->priv->frameDisplayedCallbacks.last().id;
}

/**
 * webkit_web_view_remove_frame_displayed_callback:
 * @web_view: a #WebKitWebView
 * @id: an identifier
 *
 * Removes a #WebKitFrameDisplayedCallback previously added to @web_view with
 * webkit_web_view_add_frame_displayed_callback().
 *
 * Since: 2.24
 */
void webkit_web_view_remove_frame_displayed_callback(WebKitWebView* webView, unsigned id)
{
    g_return_if_fail(WEBKIT_IS_WEB_VIEW(webView));
    g_return_if_fail(id);

    Function<bool(const FrameDisplayedCallback&)> matchFunction = [id](const auto& item) {
        return item.id == id;
    };

    if (webView->priv->inFrameDisplayed) {
        auto index = webView->priv->frameDisplayedCallbacks.findMatching(matchFunction);
        if (index != notFound)
            webView->priv->frameDisplayedCallbacksToRemove.add(id);
    } else
        webView->priv->frameDisplayedCallbacks.removeFirstMatching(matchFunction);
}
#endif // PLATFORM(WPE)