/*
 * Copyright (C) 2012, 2017 Igalia S.L.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2,1 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 "WebKitWebPage.h"

#include "APIArray.h"
#include "APIDictionary.h"
#include "APIError.h"
#include "APINumber.h"
#include "APIString.h"
#include "APIURLRequest.h"
#include "APIURLResponse.h"
#include "ImageOptions.h"
#include "InjectedBundle.h"
#include "WebContextMenuItem.h"
#include "WebImage.h"
#include "WebKitConsoleMessagePrivate.h"
#include "WebKitContextMenuPrivate.h"
#include "WebKitDOMDocumentPrivate.h"
#include "WebKitDOMElementPrivate.h"
#include "WebKitDOMNodePrivate.h"
#include "WebKitFramePrivate.h"
#include "WebKitPrivate.h"
#include "WebKitScriptWorldPrivate.h"
#include "WebKitURIRequestPrivate.h"
#include "WebKitURIResponsePrivate.h"
#include "WebKitUserMessagePrivate.h"
#include "WebKitWebEditorPrivate.h"
#include "WebKitWebHitTestResultPrivate.h"
#include "WebKitWebPagePrivate.h"
#include "WebKitWebProcessEnumTypes.h"
#include "WebPageProxyMessages.h"
#include "WebProcess.h"
#include <WebCore/Document.h>
#include <WebCore/DocumentLoader.h>
#include <WebCore/Frame.h>
#include <WebCore/FrameDestructionObserver.h>
#include <WebCore/FrameLoader.h>
#include <WebCore/FrameView.h>
#include <WebCore/HTMLFormElement.h>
#include <glib/gi18n-lib.h>
#include <wtf/NeverDestroyed.h>
#include <wtf/glib/WTFGType.h>
#include <wtf/text/CString.h>
#include <wtf/text/StringBuilder.h>

using namespace WebKit;
using namespace WebCore;

enum {
    DOCUMENT_LOADED,
    SEND_REQUEST,
    CONTEXT_MENU,
    CONSOLE_MESSAGE_SENT,
    FORM_CONTROLS_ASSOCIATED,
    FORM_CONTROLS_ASSOCIATED_FOR_FRAME,
    WILL_SUBMIT_FORM,
    USER_MESSAGE_RECEIVED,

    LAST_SIGNAL
};

enum {
    PROP_0,

    PROP_URI
};

struct _WebKitWebPagePrivate {
    WebPage* webPage;

    CString uri;

    GRefPtr<WebKitWebEditor> webEditor;
};

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

WEBKIT_DEFINE_TYPE(WebKitWebPage, webkit_web_page, G_TYPE_OBJECT)

static void webFrameDestroyed(WebFrame*);

class WebKitFrameWrapper final: public FrameDestructionObserver {
    WTF_MAKE_FAST_ALLOCATED;
public:
    WebKitFrameWrapper(WebFrame& webFrame)
        : FrameDestructionObserver(webFrame.coreFrame())
        , m_webkitFrame(adoptGRef(webkitFrameCreate(&webFrame)))
    {
    }

    WebKitFrame* webkitFrame() const { return m_webkitFrame.get(); }

private:
    void frameDestroyed() override
    {
        FrameDestructionObserver::frameDestroyed();
        webFrameDestroyed(webkitFrameGetWebFrame(m_webkitFrame.get()));
    }

    GRefPtr<WebKitFrame> m_webkitFrame;
};

typedef HashMap<WebFrame*, std::unique_ptr<WebKitFrameWrapper>> WebFrameMap;

static WebFrameMap& webFrameMap()
{
    static NeverDestroyed<WebFrameMap> map;
    return map;
}

static WebKitFrame* webkitFrameGetOrCreate(WebFrame* webFrame)
{
    auto wrapperPtr = webFrameMap().get(webFrame);
    if (wrapperPtr)
        return wrapperPtr->webkitFrame();

    std::unique_ptr<WebKitFrameWrapper> wrapper = makeUnique<WebKitFrameWrapper>(*webFrame);
    wrapperPtr = wrapper.get();
    webFrameMap().set(webFrame, WTFMove(wrapper));
    return wrapperPtr->webkitFrame();
}

static void webFrameDestroyed(WebFrame* webFrame)
{
    webFrameMap().remove(webFrame);
}

static void webkitWebPageSetURI(WebKitWebPage* webPage, const CString& uri)
{
    if (webPage->priv->uri == uri)
        return;

    webPage->priv->uri = uri;
    g_object_notify(G_OBJECT(webPage), "uri");
}

static void webkitWebPageDidSendConsoleMessage(WebKitWebPage* webPage, MessageSource source, MessageLevel level, const String& message, unsigned lineNumber, const String& sourceID)
{
    WebKitConsoleMessage consoleMessage(source, level, message, lineNumber, sourceID);
    g_signal_emit(webPage, signals[CONSOLE_MESSAGE_SENT], 0, &consoleMessage);
}

class PageLoaderClient final : public API::InjectedBundle::PageLoaderClient {
public:
    explicit PageLoaderClient(WebKitWebPage* webPage)
        : m_webPage(webPage)
    {
    }

private:
    static CString getDocumentLoaderURL(DocumentLoader* documentLoader)
    {
        ASSERT(documentLoader);
        if (!documentLoader->unreachableURL().isEmpty())
            return documentLoader->unreachableURL().string().utf8();

        return documentLoader->url().string().utf8();
    }

    void didStartProvisionalLoadForFrame(WebPage&, WebFrame& frame, RefPtr<API::Object>&) override
    {
        if (!frame.isMainFrame())
            return;
        webkitWebPageSetURI(m_webPage, getDocumentLoaderURL(frame.coreFrame()->loader().provisionalDocumentLoader()));
    }

    void didReceiveServerRedirectForProvisionalLoadForFrame(WebPage&, WebFrame& frame, RefPtr<API::Object>&) override
    {
        if (!frame.isMainFrame())
            return;
        webkitWebPageSetURI(m_webPage, getDocumentLoaderURL(frame.coreFrame()->loader().provisionalDocumentLoader()));
    }

    void didSameDocumentNavigationForFrame(WebPage&, WebFrame& frame, SameDocumentNavigationType, RefPtr<API::Object>&) override
    {
        if (!frame.isMainFrame())
            return;
        webkitWebPageSetURI(m_webPage, frame.coreFrame()->document()->url().string().utf8());
    }

    void didCommitLoadForFrame(WebPage&, WebFrame& frame, RefPtr<API::Object>&) override
    {
        if (!frame.isMainFrame())
            return;
        webkitWebPageSetURI(m_webPage, getDocumentLoaderURL(frame.coreFrame()->loader().documentLoader()));
    }

    void didFinishDocumentLoadForFrame(WebPage&, WebFrame& frame, RefPtr<API::Object>&) override
    {
        if (!frame.isMainFrame())
            return;
        g_signal_emit(m_webPage, signals[DOCUMENT_LOADED], 0);
    }

    void didClearWindowObjectForFrame(WebPage&, WebFrame& frame, DOMWrapperWorld& world) override
    {
        auto injectedWorld = InjectedBundleScriptWorld::getOrCreate(world);
        if (auto* wkWorld = webkitScriptWorldGet(injectedWorld.ptr()))
            webkitScriptWorldWindowObjectCleared(wkWorld, m_webPage, webkitFrameGetOrCreate(&frame));
    }

    WebKitWebPage* m_webPage;
};


class PageResourceLoadClient final : public API::InjectedBundle::ResourceLoadClient {
public:
    explicit PageResourceLoadClient(WebKitWebPage* webPage)
        : m_webPage(webPage)
    {
    }

private:
    void didInitiateLoadForResource(WebPage& page, WebFrame& frame, uint64_t identifier, const ResourceRequest& request, bool) override
    {
        API::Dictionary::MapType message;
        message.set(String::fromUTF8("Page"), &page);
        message.set(String::fromUTF8("Frame"), &frame);
        message.set(String::fromUTF8("Identifier"), API::UInt64::create(identifier));
        message.set(String::fromUTF8("Request"), API::URLRequest::create(request));
        WebProcess::singleton().injectedBundle()->postMessage(String::fromUTF8("WebPage.DidInitiateLoadForResource"), API::Dictionary::create(WTFMove(message)).ptr());
    }

    void willSendRequestForFrame(WebPage& page, WebFrame&, uint64_t identifier, ResourceRequest& resourceRequest, const ResourceResponse& redirectResourceResponse) override
    {
        GRefPtr<WebKitURIRequest> request = adoptGRef(webkitURIRequestCreateForResourceRequest(resourceRequest));
        GRefPtr<WebKitURIResponse> redirectResponse = !redirectResourceResponse.isNull() ? adoptGRef(webkitURIResponseCreateForResourceResponse(redirectResourceResponse)) : nullptr;

        gboolean returnValue;
        g_signal_emit(m_webPage, signals[SEND_REQUEST], 0, request.get(), redirectResponse.get(), &returnValue);
        if (returnValue) {
            resourceRequest = { };
            return;
        }

        webkitURIRequestGetResourceRequest(request.get(), resourceRequest);
        resourceRequest.setInitiatingPageID(page.webPageProxyIdentifier().toUInt64());

        API::Dictionary::MapType message;
        message.set(String::fromUTF8("Page"), &page);
        message.set(String::fromUTF8("Identifier"), API::UInt64::create(identifier));
        message.set(String::fromUTF8("Request"), API::URLRequest::create(resourceRequest));
        if (!redirectResourceResponse.isNull())
            message.set(String::fromUTF8("RedirectResponse"), API::URLResponse::create(redirectResourceResponse));
        WebProcess::singleton().injectedBundle()->postMessage(String::fromUTF8("WebPage.DidSendRequestForResource"), API::Dictionary::create(WTFMove(message)).ptr());
    }

    void didReceiveResponseForResource(WebPage& page, WebFrame&, uint64_t identifier, const ResourceResponse& response) override
    {
        API::Dictionary::MapType message;
        message.set(String::fromUTF8("Page"), &page);
        message.set(String::fromUTF8("Identifier"), API::UInt64::create(identifier));
        message.set(String::fromUTF8("Response"), API::URLResponse::create(response));
        WebProcess::singleton().injectedBundle()->postMessage(String::fromUTF8("WebPage.DidReceiveResponseForResource"), API::Dictionary::create(WTFMove(message)).ptr());

        // Post on the console as well to be consistent with the inspector.
        if (response.httpStatusCode() >= 400) {
            StringBuilder errorMessage;
            errorMessage.appendLiteral("Failed to load resource: the server responded with a status of ");
            errorMessage.appendNumber(response.httpStatusCode());
            errorMessage.appendLiteral(" (");
            errorMessage.append(response.httpStatusText());
            errorMessage.append(')');
            webkitWebPageDidSendConsoleMessage(m_webPage, MessageSource::Network, MessageLevel::Error, errorMessage.toString(), 0, response.url().string());
        }
    }

    void didReceiveContentLengthForResource(WebPage& page, WebFrame&, uint64_t identifier, uint64_t contentLength) override
    {
        API::Dictionary::MapType message;
        message.set(String::fromUTF8("Page"), &page);
        message.set(String::fromUTF8("Identifier"), API::UInt64::create(identifier));
        message.set(String::fromUTF8("ContentLength"), API::UInt64::create(contentLength));
        WebProcess::singleton().injectedBundle()->postMessage(String::fromUTF8("WebPage.DidReceiveContentLengthForResource"), API::Dictionary::create(WTFMove(message)).ptr());
    }

    void didFinishLoadForResource(WebPage& page, WebFrame&, uint64_t identifier) override
    {
        API::Dictionary::MapType message;
        message.set(String::fromUTF8("Page"), &page);
        message.set(String::fromUTF8("Identifier"), API::UInt64::create(identifier));
        WebProcess::singleton().injectedBundle()->postMessage(String::fromUTF8("WebPage.DidFinishLoadForResource"), API::Dictionary::create(WTFMove(message)).ptr());
    }

    void didFailLoadForResource(WebPage& page, WebFrame&, uint64_t identifier, const ResourceError& error) override
    {
        API::Dictionary::MapType message;
        message.set(String::fromUTF8("Page"), &page);
        message.set(String::fromUTF8("Identifier"), API::UInt64::create(identifier));
        message.set(String::fromUTF8("Error"), API::Error::create(error));
        WebProcess::singleton().injectedBundle()->postMessage(String::fromUTF8("WebPage.DidFailLoadForResource"), API::Dictionary::create(WTFMove(message)).ptr());

        // Post on the console as well to be consistent with the inspector.
        if (!error.isCancellation()) {
            StringBuilder errorMessage;
            errorMessage.appendLiteral("Failed to load resource");
            if (!error.localizedDescription().isEmpty()) {
                errorMessage.appendLiteral(": ");
                errorMessage.append(error.localizedDescription());
            }
            webkitWebPageDidSendConsoleMessage(m_webPage, MessageSource::Network, MessageLevel::Error, errorMessage.toString(), 0, error.failingURL());
        }
    }

    WebKitWebPage* m_webPage;
};

class PageContextMenuClient final : public API::InjectedBundle::PageContextMenuClient {
public:
    explicit PageContextMenuClient(WebKitWebPage* webPage)
        : m_webPage(webPage)
    {
    }

private:
    bool getCustomMenuFromDefaultItems(WebPage&, const WebCore::HitTestResult& hitTestResult, const Vector<WebCore::ContextMenuItem>& defaultMenu, Vector<WebContextMenuItemData>& newMenu, RefPtr<API::Object>& userData) override
    {
        GRefPtr<WebKitContextMenu> contextMenu = adoptGRef(webkitContextMenuCreate(kitItems(defaultMenu)));
        GRefPtr<WebKitWebHitTestResult> webHitTestResult = adoptGRef(webkitWebHitTestResultCreate(hitTestResult));
        gboolean returnValue;
        g_signal_emit(m_webPage, signals[CONTEXT_MENU], 0, contextMenu.get(), webHitTestResult.get(), &returnValue);
        if (GVariant* variant = webkit_context_menu_get_user_data(contextMenu.get())) {
            GUniquePtr<gchar> dataString(g_variant_print(variant, TRUE));
            userData = API::String::create(String::fromUTF8(dataString.get()));
        }

        if (!returnValue)
            return false;

        webkitContextMenuPopulate(contextMenu.get(), newMenu);
        return true;
    }

    WebKitWebPage* m_webPage;
};

class PageUIClient final : public API::InjectedBundle::PageUIClient {
public:
    explicit PageUIClient(WebKitWebPage* webPage)
        : m_webPage(webPage)
    {
    }

private:
    void willAddMessageToConsole(WebPage*, MessageSource source, MessageLevel level, const String& message, unsigned lineNumber, unsigned /*columnNumber*/, const String& sourceID) override
    {
        webkitWebPageDidSendConsoleMessage(m_webPage, source, level, message, lineNumber, sourceID);
    }

    WebKitWebPage* m_webPage;
};

class PageFormClient final : public API::InjectedBundle::FormClient {
public:
    explicit PageFormClient(WebKitWebPage* webPage)
        : m_webPage(webPage)
    {
    }

    void willSubmitForm(WebPage*, HTMLFormElement* formElement, WebFrame* frame, WebFrame* sourceFrame, const Vector<std::pair<String, String>>& values, RefPtr<API::Object>&) override
    {
        fireFormSubmissionEvent(WEBKIT_FORM_SUBMISSION_WILL_COMPLETE, formElement, frame, sourceFrame, values);
    }

    void willSendSubmitEvent(WebPage*, HTMLFormElement* formElement, WebFrame* frame, WebFrame* sourceFrame, const Vector<std::pair<String, String>>& values) override
    {
        fireFormSubmissionEvent(WEBKIT_FORM_SUBMISSION_WILL_SEND_DOM_EVENT, formElement, frame, sourceFrame, values);
    }

    void didAssociateFormControls(WebPage*, const Vector<RefPtr<Element>>& elements, WebFrame* frame) override
    {
        GRefPtr<GPtrArray> formElements = adoptGRef(g_ptr_array_sized_new(elements.size()));
        for (size_t i = 0; i < elements.size(); ++i)
            g_ptr_array_add(formElements.get(), WebKit::kit(elements[i].get()));

        g_signal_emit(m_webPage, signals[FORM_CONTROLS_ASSOCIATED], 0, formElements.get());
        g_signal_emit(m_webPage, signals[FORM_CONTROLS_ASSOCIATED_FOR_FRAME], 0, formElements.get(), webkitFrameGetOrCreate(frame));
    }

    bool shouldNotifyOnFormChanges(WebPage*) override { return true; }

private:
    void fireFormSubmissionEvent(WebKitFormSubmissionStep step, HTMLFormElement* formElement, WebFrame* frame, WebFrame* sourceFrame, const Vector<std::pair<String, String>>& values)
    {
        WebKitFrame* webkitTargetFrame = webkitFrameGetOrCreate(frame);
        WebKitFrame* webkitSourceFrame = webkitFrameGetOrCreate(sourceFrame);

        GRefPtr<GPtrArray> textFieldNames = adoptGRef(g_ptr_array_new_full(values.size(), g_free));
        GRefPtr<GPtrArray> textFieldValues = adoptGRef(g_ptr_array_new_full(values.size(), g_free));
        for (auto& pair : values) {
            g_ptr_array_add(textFieldNames.get(), g_strdup(pair.first.utf8().data()));
            g_ptr_array_add(textFieldValues.get(), g_strdup(pair.second.utf8().data()));
        }

        g_signal_emit(m_webPage, signals[WILL_SUBMIT_FORM], 0, WEBKIT_DOM_ELEMENT(WebKit::kit(static_cast<Node*>(formElement))), step, webkitSourceFrame, webkitTargetFrame, textFieldNames.get(), textFieldValues.get());
    }

    WebKitWebPage* m_webPage;
};

static void webkitWebPageGetProperty(GObject* object, guint propId, GValue* value, GParamSpec* paramSpec)
{
    WebKitWebPage* webPage = WEBKIT_WEB_PAGE(object);

    switch (propId) {
    case PROP_URI:
        g_value_set_string(value, webkit_web_page_get_uri(webPage));
        break;
    default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propId, paramSpec);
    }
}

static void webkit_web_page_class_init(WebKitWebPageClass* klass)
{
    GObjectClass* gObjectClass = G_OBJECT_CLASS(klass);

    gObjectClass->get_property = webkitWebPageGetProperty;

    /**
     * WebKitWebPage:uri:
     *
     * The current active URI of the #WebKitWebPage.
     */
    g_object_class_install_property(
        gObjectClass,
        PROP_URI,
        g_param_spec_string(
            "uri",
            _("URI"),
            _("The current active URI of the web page"),
            0,
            WEBKIT_PARAM_READABLE));

    /**
     * WebKitWebPage::document-loaded:
     * @web_page: the #WebKitWebPage on which the signal is emitted
     *
     * This signal is emitted when the DOM document of a #WebKitWebPage has been
     * loaded.
     *
     * You can wait for this signal to get the DOM document with
     * webkit_web_page_get_dom_document().
     */
    signals[DOCUMENT_LOADED] = g_signal_new(
        "document-loaded",
        G_TYPE_FROM_CLASS(klass),
        G_SIGNAL_RUN_LAST,
        0, 0, 0,
        g_cclosure_marshal_VOID__VOID,
        G_TYPE_NONE, 0);

    /**
     * WebKitWebPage::send-request:
     * @web_page: the #WebKitWebPage on which the signal is emitted
     * @request: a #WebKitURIRequest
     * @redirected_response: a #WebKitURIResponse, or %NULL
     *
     * This signal is emitted when @request is about to be sent to
     * the server. This signal can be used to modify the #WebKitURIRequest
     * that will be sent to the server. You can also cancel the resource load
     * operation by connecting to this signal and returning %TRUE.
     *
     * In case of a server redirection this signal is
     * emitted again with the @request argument containing the new
     * request to be sent to the server due to the redirection and the
     * @redirected_response parameter containing the response
     * received by the server for the initial request.
     *
     * Modifications to the #WebKitURIRequest and its associated
     * #SoupMessageHeaders will be taken into account when the request
     * is sent over the network.
     *
     * Returns: %TRUE to stop other handlers from being invoked for the event.
     *    %FALSE to continue emission of the event.
     */
    signals[SEND_REQUEST] = g_signal_new(
        "send-request",
        G_TYPE_FROM_CLASS(klass),
        G_SIGNAL_RUN_LAST,
        0,
        g_signal_accumulator_true_handled, nullptr,
        g_cclosure_marshal_generic,
        G_TYPE_BOOLEAN, 2,
        WEBKIT_TYPE_URI_REQUEST,
        WEBKIT_TYPE_URI_RESPONSE);

    /**
     * WebKitWebPage::context-menu:
     * @web_page: the #WebKitWebPage on which the signal is emitted
     * @context_menu: the proposed #WebKitContextMenu
     * @hit_test_result: a #WebKitWebHitTestResult
     *
     * Emitted before a context menu is displayed in the UI Process to
     * give the application a chance to customize the proposed menu,
     * build its own context menu or pass user data to the UI Process.
     * This signal is useful when the information available in the UI Process
     * is not enough to build or customize the context menu, for example, to
     * add menu entries depending on the #WebKitDOMNode at the coordinates of the
     * @hit_test_result. Otherwise, it's recommended to use #WebKitWebView::context-menu
     * signal instead.
     *
     * Returns: %TRUE if the proposed @context_menu has been modified, or %FALSE otherwise.
     *
     * Since: 2.8
     */
    signals[CONTEXT_MENU] = g_signal_new(
        "context-menu",
        G_TYPE_FROM_CLASS(klass),
        G_SIGNAL_RUN_LAST,
        0,
        g_signal_accumulator_true_handled, nullptr,
        g_cclosure_marshal_generic,
        G_TYPE_BOOLEAN, 2,
        WEBKIT_TYPE_CONTEXT_MENU,
        WEBKIT_TYPE_WEB_HIT_TEST_RESULT);

    /**
     * WebKitWebPage::console-message-sent:
     * @web_page: the #WebKitWebPage on which the signal is emitted
     * @console_message: the #WebKitConsoleMessage
     *
     * Emitted when a message is sent to the console. This can be a message
     * produced by the use of JavaScript console API, a JavaScript exception,
     * a security error or other errors, warnings, debug or log messages.
     * The @console_message contains information of the message.
     *
     * Since: 2.12
     */
    signals[CONSOLE_MESSAGE_SENT] = g_signal_new(
        "console-message-sent",
        G_TYPE_FROM_CLASS(klass),
        G_SIGNAL_RUN_LAST,
        0, 0, nullptr,
        g_cclosure_marshal_VOID__BOXED,
        G_TYPE_NONE, 1,
        WEBKIT_TYPE_CONSOLE_MESSAGE | G_SIGNAL_TYPE_STATIC_SCOPE);

    /**
     * WebKitWebPage::form-controls-associated:
     * @web_page: the #WebKitWebPage on which the signal is emitted
     * @elements: (element-type WebKitDOMElement) (transfer none): a #GPtrArray of
     *     #WebKitDOMElement with the list of forms in the page
     *
     * Emitted after form elements (or form associated elements) are associated to a particular web
     * page. This is useful to implement form auto filling for web pages where form fields are added
     * dynamically. This signal might be emitted multiple times for the same web page.
     *
     * Note that this signal could be also emitted when form controls are moved between forms. In
     * that case, the @elements array carries the list of those elements which have moved.
     *
     * Clients should take a reference to the members of the @elements array if it is desired to
     * keep them alive after the signal handler returns.
     *
     * Since: 2.16
     *
     * Deprecated: 2.26, use #WebKitWebPage::form-controls-associated-for-frame instead.
     */
    signals[FORM_CONTROLS_ASSOCIATED] = g_signal_new(
        "form-controls-associated",
        G_TYPE_FROM_CLASS(klass),
        static_cast<GSignalFlags>(G_SIGNAL_RUN_LAST | G_SIGNAL_DEPRECATED),
        0, 0, nullptr,
        g_cclosure_marshal_VOID__BOXED,
        G_TYPE_NONE, 1,
        G_TYPE_PTR_ARRAY);

    /**
     * WebKitWebPage::form-controls-associated-for-frame:
     * @web_page: the #WebKitWebPage on which the signal is emitted
     * @elements: (element-type WebKitDOMElement) (transfer none): a #GPtrArray of
     *     #WebKitDOMElement with the list of forms in the page
     * @frame: the #WebKitFrame
     *
     * Emitted after form elements (or form associated elements) are associated to a particular web
     * page. This is useful to implement form auto filling for web pages where form fields are added
     * dynamically. This signal might be emitted multiple times for the same web page.
     *
     * Note that this signal could be also emitted when form controls are moved between forms. In
     * that case, the @elements array carries the list of those elements which have moved.
     *
     * Clients should take a reference to the members of the @elements array if it is desired to
     * keep them alive after the signal handler returns.
     *
     * Since: 2.26
     */
    signals[FORM_CONTROLS_ASSOCIATED_FOR_FRAME] = g_signal_new(
        "form-controls-associated-for-frame",
        G_TYPE_FROM_CLASS(klass),
        G_SIGNAL_RUN_LAST,
        0, 0, nullptr,
        g_cclosure_marshal_generic,
        G_TYPE_NONE, 2,
        G_TYPE_PTR_ARRAY,
        WEBKIT_TYPE_FRAME);

    /**
     * WebKitWebPage::will-submit-form:
     * @web_page: the #WebKitWebPage on which the signal is emitted
     * @form: the #WebKitDOMElement to be submitted, which will always correspond to an HTMLFormElement
     * @step: a #WebKitFormSubmissionEventType indicating the current
     * stage of form submission
     * @source_frame: the #WebKitFrame containing the form to be
     * submitted
     * @target_frame: the #WebKitFrame containing the form's target,
     * which may be the same as @source_frame if no target was specified
     * @text_field_names: (element-type utf8) (transfer none): names of
     * the form's text fields
     * @text_field_values: (element-type utf8) (transfer none): values
     * of the form's text fields
     *
     * This signal is emitted to indicate various points during form
     * submission. @step indicates the current stage of form submission.
     *
     * If this signal is emitted with %WEBKIT_FORM_SUBMISSION_WILL_SEND_DOM_EVENT,
     * then the DOM submit event is about to be emitted. JavaScript code
     * may rely on the submit event to detect that the user has clicked
     * on a submit button, and to possibly cancel the form submission
     * before %WEBKIT_FORM_SUBMISSION_WILL_COMPLETE. However, beware
     * that, for historical reasons, the submit event is not emitted at
     * all if the form submission is triggered by JavaScript. For these
     * reasons, %WEBKIT_FORM_SUBMISSION_WILL_SEND_DOM_EVENT may not
     * be used to reliably detect whether a form will be submitted.
     * Instead, use it to detect if a user has clicked on a form's
     * submit button even if JavaScript later cancels the form
     * submission, or to read the values of the form's fields even if
     * JavaScript later clears certain fields before submitting. This
     * may be needed, for example, to implement a robust browser
     * password manager, as some misguided websites may use such
     * techniques to attempt to thwart password managers.
     *
     * If this signal is emitted with %WEBKIT_FORM_SUBMISSION_WILL_COMPLETE,
     * the form will imminently be submitted. It can no longer be
     * cancelled. This event always occurs immediately before a form is
     * submitted to its target, so use this event to reliably detect
     * when a form is submitted. This event occurs after
     * %WEBKIT_FORM_SUBMISSION_WILL_SEND_DOM_EVENT if that event is
     * emitted.
     *
     * Since: 2.20
     */
    signals[WILL_SUBMIT_FORM] = g_signal_new(
        "will-submit-form",
        G_TYPE_FROM_CLASS(klass),
        G_SIGNAL_RUN_LAST,
        0, 0, nullptr,
        g_cclosure_marshal_generic,
        G_TYPE_NONE, 6,
        WEBKIT_DOM_TYPE_ELEMENT,
        WEBKIT_TYPE_FORM_SUBMISSION_STEP,
        WEBKIT_TYPE_FRAME,
        WEBKIT_TYPE_FRAME,
        G_TYPE_PTR_ARRAY,
        G_TYPE_PTR_ARRAY);

    /**
     * WebKitWebPage::user-message-received:
     * @web_page: the #WebKitWebPage on which the signal is emitted
     * @message: the #WebKitUserMessage received
     *
     * This signal is emitted when a #WebKitUserMessage is received from the
     * #WebKitWebView corresponding to @web_page. You can reply to the message
     * using webkit_user_message_send_reply().
     *
     * You can handle the user message asynchronously by calling g_object_ref() on
     * @message and returning %TRUE. If the last reference of @message is removed
     * and the message has been replied, the operation in the #WebKitWebView will
     * finish with error %WEBKIT_USER_MESSAGE_UNHANDLED_MESSAGE.
     *
     * Returns: %TRUE if the message was handled, or %FALSE otherwise.
     *
     * Since: 2.28
     */
    signals[USER_MESSAGE_RECEIVED] = g_signal_new(
        "user-message-received",
        G_TYPE_FROM_CLASS(klass),
        G_SIGNAL_RUN_LAST,
        0,
        g_signal_accumulator_true_handled, nullptr,
        g_cclosure_marshal_generic,
        G_TYPE_BOOLEAN, 1,
        WEBKIT_TYPE_USER_MESSAGE);
}

WebPage* webkitWebPageGetPage(WebKitWebPage *webPage)
{
    return webPage->priv->webPage;
}

WebKitWebPage* webkitWebPageCreate(WebPage* webPage)
{
    WebKitWebPage* page = WEBKIT_WEB_PAGE(g_object_new(WEBKIT_TYPE_WEB_PAGE, NULL));
    page->priv->webPage = webPage;

    webPage->setInjectedBundleResourceLoadClient(makeUnique<PageResourceLoadClient>(page));
    webPage->setInjectedBundlePageLoaderClient(makeUnique<PageLoaderClient>(page));
    webPage->setInjectedBundleContextMenuClient(makeUnique<PageContextMenuClient>(page));
    webPage->setInjectedBundleUIClient(makeUnique<PageUIClient>(page));
    webPage->setInjectedBundleFormClient(makeUnique<PageFormClient>(page));

    return page;
}

void webkitWebPageDidReceiveMessage(WebKitWebPage* page, const String& messageName, API::Dictionary& message)
{
#if PLATFORM(GTK)
    if (messageName == String("GetSnapshot")) {
        SnapshotOptions snapshotOptions = static_cast<SnapshotOptions>(static_cast<API::UInt64*>(message.get("SnapshotOptions"))->value());
        uint64_t callbackID = static_cast<API::UInt64*>(message.get("CallbackID"))->value();
        SnapshotRegion region = static_cast<SnapshotRegion>(static_cast<API::UInt64*>(message.get("SnapshotRegion"))->value());
        bool transparentBackground = static_cast<API::Boolean*>(message.get("TransparentBackground"))->value();

        RefPtr<WebImage> snapshotImage;
        WebPage* webPage = page->priv->webPage;
        if (WebCore::FrameView* frameView = webPage->mainFrameView()) {
            WebCore::IntRect snapshotRect;
            switch (region) {
            case SnapshotRegionVisible:
                snapshotRect = frameView->visibleContentRect();
                break;
            case SnapshotRegionFullDocument:
                snapshotRect = WebCore::IntRect(WebCore::IntPoint(0, 0), frameView->contentsSize());
                break;
            default:
                ASSERT_NOT_REACHED();
            }
            if (!snapshotRect.isEmpty()) {
                Color savedBackgroundColor;
                if (transparentBackground) {
                    savedBackgroundColor = frameView->baseBackgroundColor();
                    frameView->setBaseBackgroundColor(Color::transparent);
                }
                snapshotImage = webPage->scaledSnapshotWithOptions(snapshotRect, 1, snapshotOptions | SnapshotOptionsShareable);
                if (transparentBackground)
                    frameView->setBaseBackgroundColor(savedBackgroundColor);
            }
        }

        API::Dictionary::MapType messageReply;
        messageReply.set("Page", webPage);
        messageReply.set("CallbackID", API::UInt64::create(callbackID));
        messageReply.set("Snapshot", snapshotImage);
        WebProcess::singleton().injectedBundle()->postMessage("WebPage.DidGetSnapshot", API::Dictionary::create(WTFMove(messageReply)).ptr());
    } else
#endif
        ASSERT_NOT_REACHED();
}

void webkitWebPageDidReceiveUserMessage(WebKitWebPage* webPage, UserMessage&& message, CompletionHandler<void(UserMessage&&)>&& completionHandler)
{
    // Sink the floating ref.
    GRefPtr<WebKitUserMessage> userMessage = webkitUserMessageCreate(WTFMove(message), WTFMove(completionHandler));
    gboolean returnValue;
    g_signal_emit(webPage, signals[USER_MESSAGE_RECEIVED], 0, userMessage.get(), &returnValue);
}

/**
 * webkit_web_page_get_dom_document:
 * @web_page: a #WebKitWebPage
 *
 * Get the #WebKitDOMDocument currently loaded in @web_page
 *
 * Returns: (transfer none): the #WebKitDOMDocument currently loaded, or %NULL
 *    if no document is currently loaded.
 */
WebKitDOMDocument* webkit_web_page_get_dom_document(WebKitWebPage* webPage)
{
    g_return_val_if_fail(WEBKIT_IS_WEB_PAGE(webPage), nullptr);

    if (auto* coreFrame = webPage->priv->webPage->mainFrame())
        return kit(coreFrame->document());

    return nullptr;
}

/**
 * webkit_web_page_get_id:
 * @web_page: a #WebKitWebPage
 *
 * Get the identifier of the #WebKitWebPage
 *
 * Returns: the identifier of @web_page
 */
guint64 webkit_web_page_get_id(WebKitWebPage* webPage)
{
    g_return_val_if_fail(WEBKIT_IS_WEB_PAGE(webPage), 0);

    return webPage->priv->webPage->identifier().toUInt64();
}

/**
 * webkit_web_page_get_uri:
 * @web_page: a #WebKitWebPage
 *
 * Returns the current active URI of @web_page.
 *
 * You can monitor the active URI by connecting to the notify::uri
 * signal of @web_page.
 *
 * Returns: the current active URI of @web_view or %NULL if nothing has been
 *    loaded yet.
 */
const gchar* webkit_web_page_get_uri(WebKitWebPage* webPage)
{
    g_return_val_if_fail(WEBKIT_IS_WEB_PAGE(webPage), 0);

    return webPage->priv->uri.data();
}

/**
 * webkit_web_page_get_main_frame:
 * @web_page: a #WebKitWebPage
 *
 * Returns the main frame of a #WebKitWebPage.
 *
 * Returns: (transfer none): the #WebKitFrame that is the main frame of @web_page
 *
 * Since: 2.2
 */
WebKitFrame* webkit_web_page_get_main_frame(WebKitWebPage* webPage)
{
    g_return_val_if_fail(WEBKIT_IS_WEB_PAGE(webPage), 0);

    return webkitFrameGetOrCreate(webPage->priv->webPage->mainWebFrame());
}

/**
 * webkit_web_page_get_editor:
 * @web_page: a #WebKitWebPage
 *
 * Gets the #WebKitWebEditor of a #WebKitWebPage.
 *
 * Returns: (transfer none): the #WebKitWebEditor
 *
 * Since: 2.10
 */
WebKitWebEditor* webkit_web_page_get_editor(WebKitWebPage* webPage)
{
    g_return_val_if_fail(WEBKIT_IS_WEB_PAGE(webPage), nullptr);

    if (!webPage->priv->webEditor)
        webPage->priv->webEditor = adoptGRef(webkitWebEditorCreate(webPage));

    return webPage->priv->webEditor.get();
}

/**
 * webkit_web_page_send_message_to_view:
 * @web_page: a #WebKitWebPage
 * @message: a #WebKitUserMessage
 * @cancellable: (nullable): a #GCancellable or %NULL to ignore
 * @callback: (scope async): (nullable): A #GAsyncReadyCallback to call when the request is satisfied or %NULL
 * @user_data: (closure): the data to pass to callback function
 *
 * Send @message to the #WebKitWebView corresponding to @web_page. If @message is floating, it's consumed.
 *
 * If you don't expect any reply, or you simply want to ignore it, you can pass %NULL as @callback.
 * When the operation is finished, @callback will be called. You can then call
 * webkit_web_page_send_message_to_view_finish() to get the message reply.
 *
 * Since: 2.28
 */
void webkit_web_page_send_message_to_view(WebKitWebPage* webPage, WebKitUserMessage* message, GCancellable* cancellable, GAsyncReadyCallback callback, gpointer userData)
{
    g_return_if_fail(WEBKIT_IS_WEB_PAGE(webPage));
    g_return_if_fail(WEBKIT_IS_USER_MESSAGE(message));

    // We sink the reference in case of being floating.
    GRefPtr<WebKitUserMessage> adoptedMessage = message;
    if (!callback) {
        webPage->priv->webPage->send(Messages::WebPageProxy::SendMessageToWebView(webkitUserMessageGetMessage(message)));
        return;
    }

    GRefPtr<GTask> task = adoptGRef(g_task_new(webPage, cancellable, callback, userData));
    CompletionHandler<void(UserMessage&&)> completionHandler = [task = WTFMove(task)](UserMessage&& replyMessage) {
        switch (replyMessage.type) {
        case UserMessage::Type::Null:
            g_task_return_new_error(task.get(), G_IO_ERROR, G_IO_ERROR_CANCELLED, _("Operation was cancelled"));
            break;
        case UserMessage::Type::Message:
            g_task_return_pointer(task.get(), g_object_ref_sink(webkitUserMessageCreate(WTFMove(replyMessage))), static_cast<GDestroyNotify>(g_object_unref));
            break;
        case UserMessage::Type::Error:
            g_task_return_new_error(task.get(), WEBKIT_USER_MESSAGE_ERROR, replyMessage.errorCode, _("Message %s was not handled"), replyMessage.name.data());
            break;
        }
    };
    webPage->priv->webPage->sendWithAsyncReply(Messages::WebPageProxy::SendMessageToWebViewWithReply(webkitUserMessageGetMessage(message)), WTFMove(completionHandler));
}

/**
 * webkit_web_page_send_message_to_view_finish:
 * @web_page: a #WebKitWebPage
 * @result: a #GAsyncResult
 * @error: return location for error or %NULL to ignor
 *
 * Finish an asynchronous operation started with webkit_web_page_send_message_to_view().
 *
 * Returns: (transfer full): a #WebKitUserMessage with the reply or %NULL in case of error.
 *
 * Since: 2.28
 */
WebKitUserMessage* webkit_web_page_send_message_to_view_finish(WebKitWebPage* webPage, GAsyncResult* result, GError** error)
{
    g_return_val_if_fail(WEBKIT_IS_WEB_PAGE(webPage), nullptr);
    g_return_val_if_fail(g_task_is_valid(result, webPage), nullptr);

    return WEBKIT_USER_MESSAGE(g_task_propagate_pointer(G_TASK(result), error));
}
