///////////////////////////////////////////////////////////////////////////// // Name: src/gtk/webview_webkit2_extension.cpp // Purpose: GTK WebKit2 extension for web view component // Author: Scott Talbert // Copyright: (c) 2017 Scott Talbert // Licence: wxWindows licence ///////////////////////////////////////////////////////////////////////////// #include "wx/defs.h" #include "wx/gtk/private/webview_webkit2_extension.h" #include #define WEBKIT_DOM_USE_UNSTABLE_API #include #include static const char introspection_xml[] = "" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " ""; class wxWebViewWebKitExtension; static gboolean wxgtk_webview_authorize_authenticated_peer_cb(GDBusAuthObserver *observer, GIOStream *stream, GCredentials *credentials, wxWebViewWebKitExtension *extension); static void wxgtk_webview_dbus_connection_created_cb(GObject *source_object, GAsyncResult *result, wxWebViewWebKitExtension *extension); static wxWebViewWebKitExtension *gs_extension = NULL; class wxWebViewWebKitExtension { public: wxWebViewWebKitExtension(WebKitWebExtension *webkit_extension, const char* server_address); void ClearSelection(GVariant *parameters, GDBusMethodInvocation *invocation); void DeleteSelection(GVariant *parameters, GDBusMethodInvocation *invocation); void GetPageSource(GVariant *parameters, GDBusMethodInvocation *invocation); void GetPageText(GVariant *parameters, GDBusMethodInvocation *invocation); void GetSelectedSource(GVariant *parameters, GDBusMethodInvocation *invocation); void GetSelectedText(GVariant *parameters, GDBusMethodInvocation *invocation); void HasSelection(GVariant *parameters, GDBusMethodInvocation *invocation); void SetDBusConnection(GDBusConnection *dbusConnection); private: WebKitWebPage* GetWebPageOrReturnError(GVariant *parameters, GDBusMethodInvocation *invocation); void ReturnDBusStringValue(GDBusMethodInvocation *invocation, gchar *value); GDBusConnection *m_dbusConnection; WebKitWebExtension *m_webkitExtension; }; wxWebViewWebKitExtension::wxWebViewWebKitExtension(WebKitWebExtension *extension, const char* server_address) { m_webkitExtension = (WebKitWebExtension*)g_object_ref(extension); GDBusAuthObserver *observer = g_dbus_auth_observer_new(); g_signal_connect(observer, "authorize-authenticated-peer", G_CALLBACK(wxgtk_webview_authorize_authenticated_peer_cb), this); g_dbus_connection_new_for_address(server_address, G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT, observer, NULL, (GAsyncReadyCallback)wxgtk_webview_dbus_connection_created_cb, this); g_object_unref(observer); } WebKitWebPage* wxWebViewWebKitExtension::GetWebPageOrReturnError(GVariant *parameters, GDBusMethodInvocation *invocation) { guint64 page_id; g_variant_get(parameters, "(t)", &page_id); WebKitWebPage *web_page = webkit_web_extension_get_page(m_webkitExtension, page_id); if (!web_page) { g_dbus_method_invocation_return_error(invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "Invalid page ID: %" G_GUINT64_FORMAT, page_id); } return web_page; } void wxWebViewWebKitExtension::GetSelectedSource(GVariant *parameters, GDBusMethodInvocation *invocation) { WebKitWebPage *web_page = GetWebPageOrReturnError(parameters, invocation); if (!web_page) { return; } WebKitDOMDocument *doc = webkit_web_page_get_dom_document(web_page); WebKitDOMDOMWindow *win = webkit_dom_document_get_default_view(doc); WebKitDOMDOMSelection *sel = webkit_dom_dom_window_get_selection(win); g_object_unref(win); if (!sel) { ReturnDBusStringValue(invocation, NULL); return; } WebKitDOMRange *range = webkit_dom_dom_selection_get_range_at(sel, 0, NULL); if (!range) { ReturnDBusStringValue(invocation, NULL); return; } WebKitDOMElement *div = webkit_dom_document_create_element(doc, "div", NULL); WebKitDOMDocumentFragment *clone = webkit_dom_range_clone_contents(range, NULL); webkit_dom_node_append_child(&div->parent_instance, &clone->parent_instance, NULL); WebKitDOMElement *html = (WebKitDOMElement*)div; #if WEBKIT_CHECK_VERSION(2, 8, 0) gchar *text = webkit_dom_element_get_inner_html(html); #else gchar *text = webkit_dom_html_element_get_inner_html(WEBKIT_DOM_HTML_ELEMENT(html)); #endif g_object_unref(range); ReturnDBusStringValue(invocation, text); } void wxWebViewWebKitExtension::GetPageSource(GVariant *parameters, GDBusMethodInvocation *invocation) { WebKitWebPage *web_page = GetWebPageOrReturnError(parameters, invocation); if (!web_page) { return; } WebKitDOMDocument *doc = webkit_web_page_get_dom_document(web_page); WebKitDOMElement *body = webkit_dom_document_get_document_element(doc); #if WEBKIT_CHECK_VERSION(2, 8, 0) gchar *source = webkit_dom_element_get_outer_html(body); #else gchar *source = webkit_dom_html_element_get_outer_html(WEBKIT_DOM_HTML_ELEMENT(body)); #endif g_dbus_method_invocation_return_value(invocation, g_variant_new("(s)", source ? source : "")); } void wxWebViewWebKitExtension::GetPageText(GVariant *parameters, GDBusMethodInvocation *invocation) { WebKitWebPage *web_page = GetWebPageOrReturnError(parameters, invocation); if (!web_page) { return; } WebKitDOMDocument *doc = webkit_web_page_get_dom_document(web_page); WebKitDOMHTMLElement *body = webkit_dom_document_get_body(doc); gchar *text = webkit_dom_html_element_get_inner_text(body); g_dbus_method_invocation_return_value(invocation, g_variant_new("(s)", text)); } void wxWebViewWebKitExtension::GetSelectedText(GVariant *parameters, GDBusMethodInvocation *invocation) { WebKitWebPage *web_page = GetWebPageOrReturnError(parameters, invocation); if (!web_page) { return; } WebKitDOMDocument *doc = webkit_web_page_get_dom_document(web_page); WebKitDOMDOMWindow *win = webkit_dom_document_get_default_view(doc); WebKitDOMDOMSelection *sel = webkit_dom_dom_window_get_selection(win); g_object_unref(win); if (!sel) { ReturnDBusStringValue(invocation, NULL); return; } WebKitDOMRange *range = webkit_dom_dom_selection_get_range_at(sel, 0, NULL); if (!range) { ReturnDBusStringValue(invocation, NULL); return; } gchar *text = webkit_dom_range_get_text(range); g_object_unref(range); ReturnDBusStringValue(invocation, text); } void wxWebViewWebKitExtension::ClearSelection(GVariant *parameters, GDBusMethodInvocation *invocation) { WebKitWebPage *web_page = GetWebPageOrReturnError(parameters, invocation); if (!web_page) { return; } WebKitDOMDocument *doc = webkit_web_page_get_dom_document(web_page); WebKitDOMDOMWindow *win = webkit_dom_document_get_default_view(doc); WebKitDOMDOMSelection *sel = webkit_dom_dom_window_get_selection(win); g_object_unref(win); if (sel) { webkit_dom_dom_selection_remove_all_ranges(sel); } g_dbus_method_invocation_return_value(invocation, NULL); } void wxWebViewWebKitExtension::HasSelection(GVariant *parameters, GDBusMethodInvocation *invocation) { WebKitWebPage *web_page = GetWebPageOrReturnError(parameters, invocation); if (!web_page) { return; } WebKitDOMDocument *doc = webkit_web_page_get_dom_document(web_page); WebKitDOMDOMWindow *win = webkit_dom_document_get_default_view(doc); WebKitDOMDOMSelection *sel = webkit_dom_dom_window_get_selection(win); g_object_unref(win); gboolean has_selection = FALSE; if (WEBKIT_DOM_IS_DOM_SELECTION(sel)) { if (!webkit_dom_dom_selection_get_is_collapsed(sel)) { has_selection = TRUE; } } g_dbus_method_invocation_return_value(invocation, g_variant_new("(b)", has_selection)); } void wxWebViewWebKitExtension::DeleteSelection(GVariant *parameters, GDBusMethodInvocation *invocation) { WebKitWebPage *web_page = GetWebPageOrReturnError(parameters, invocation); if (!web_page) { return; } WebKitDOMDocument *doc = webkit_web_page_get_dom_document(web_page); WebKitDOMDOMWindow *win = webkit_dom_document_get_default_view(doc); WebKitDOMDOMSelection *sel = webkit_dom_dom_window_get_selection(win); g_object_unref(win); if (sel) { webkit_dom_dom_selection_delete_from_document(sel); } g_dbus_method_invocation_return_value(invocation, NULL); } void wxWebViewWebKitExtension::ReturnDBusStringValue(GDBusMethodInvocation *invocation, gchar *value) { g_dbus_method_invocation_return_value(invocation, g_variant_new("(s)", value ? value : "")); } void wxWebViewWebKitExtension::SetDBusConnection(GDBusConnection *dbusConnection) { m_dbusConnection = dbusConnection; } static void wxgtk_webview_handle_method_call(GDBusConnection *connection, const char *sender, const char *object_path, const char *interface_name, const char *method_name, GVariant *parameters, GDBusMethodInvocation *invocation, gpointer user_data) { if (g_strcmp0(interface_name, WXGTK_WEB_EXTENSION_INTERFACE) != 0) { return; } wxWebViewWebKitExtension *extension = (wxWebViewWebKitExtension*)user_data; if (g_strcmp0(method_name, "ClearSelection") == 0) { extension->ClearSelection(parameters, invocation); } else if (g_strcmp0(method_name, "DeleteSelection") == 0) { extension->DeleteSelection(parameters, invocation); } else if (g_strcmp0(method_name, "GetPageSource") == 0) { extension->GetPageSource(parameters, invocation); } else if (g_strcmp0(method_name, "GetPageText") == 0) { extension->GetPageText(parameters, invocation); } else if (g_strcmp0(method_name, "GetSelectedSource") == 0) { extension->GetSelectedSource(parameters, invocation); } else if (g_strcmp0(method_name, "GetSelectedText") == 0) { extension->GetSelectedText(parameters, invocation); } else if (g_strcmp0(method_name, "HasSelection") == 0) { extension->HasSelection(parameters, invocation); } } static const GDBusInterfaceVTable interface_vtable = { wxgtk_webview_handle_method_call, NULL, NULL }; gboolean wxgtk_webview_dbus_peer_is_authorized(GCredentials *peer_credentials) { static GCredentials *own_credentials = g_credentials_new(); GError *error = NULL; if (peer_credentials && g_credentials_is_same_user(peer_credentials, own_credentials, &error)) { return TRUE; } if (error) { g_warning("Failed to authorize web extension connection: %s", error->message); g_error_free(error); } return FALSE; } static gboolean wxgtk_webview_authorize_authenticated_peer_cb(GDBusAuthObserver *observer, GIOStream *stream, GCredentials *credentials, wxWebViewWebKitExtension *extension) { return wxgtk_webview_dbus_peer_is_authorized(credentials); } static void wxgtk_webview_dbus_connection_created_cb(GObject *source_object, GAsyncResult *result, wxWebViewWebKitExtension *extension) { static GDBusNodeInfo *introspection_data = g_dbus_node_info_new_for_xml(introspection_xml, NULL); GError *error = NULL; GDBusConnection *connection = g_dbus_connection_new_for_address_finish(result, &error); if (error) { g_warning("Failed to connect to UI process: %s", error->message); g_error_free(error); return; } guint registration_id = g_dbus_connection_register_object(connection, WXGTK_WEB_EXTENSION_OBJECT_PATH, introspection_data->interfaces[0], &interface_vtable, extension, NULL, &error); if (!registration_id) { g_warning ("Failed to register web extension object: %s\n", error->message); g_error_free (error); g_object_unref (connection); return; } extension->SetDBusConnection(connection); } extern "C" WXEXPORT void webkit_web_extension_initialize_with_user_data (WebKitWebExtension *webkit_extension, GVariant *user_data) { const char *server_address; g_variant_get (user_data, "(&s)", &server_address); gs_extension = new wxWebViewWebKitExtension(webkit_extension, server_address); }