[evolution/wip/mcrha/webkit-jsc-api] Initial commit for use of WebKit's JavaScriptCore API
- From: Milan Crha <mcrha src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [evolution/wip/mcrha/webkit-jsc-api] Initial commit for use of WebKit's JavaScriptCore API
- Date: Mon, 23 Sep 2019 16:43:53 +0000 (UTC)
commit 67e7e00efe6de128e66231ae88c28e4ec927a7f3
Author: Milan Crha <mcrha redhat com>
Date: Mon Sep 23 18:43:46 2019 +0200
Initial commit for use of WebKit's JavaScriptCore API
src/e-util/e-web-view-jsc-utils.c | 105 +++++
src/e-util/e-web-view-jsc-utils.h | 54 +++
src/e-util/test-web-view.c | 433 +++++++++++++++++++++
.../evolution-web-process-extension.gresource.xml | 6 +
src/web-extensions/ext-utils.js | 76 ++++
5 files changed, 674 insertions(+)
---
diff --git a/src/e-util/e-web-view-jsc-utils.c b/src/e-util/e-web-view-jsc-utils.c
new file mode 100644
index 0000000000..7ab1f3dd9e
--- /dev/null
+++ b/src/e-util/e-web-view-jsc-utils.c
@@ -0,0 +1,105 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2019 Red Hat (www.redhat.com)
+ *
+ * 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.
+ *
+ * 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 Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "evolution-config.h"
+
+#include <webkit2/webkit2.h>
+
+#include "e-web-view-jsc-utils.h"
+
+static void
+ewv_jsc_call_done_cb (GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ WebKitJavascriptResult *js_result;
+ gchar *script = user_data;
+ GError *error = NULL;
+
+ js_result = webkit_web_view_run_javascript_finish (WEBKIT_WEB_VIEW (source), result, &error);
+
+ if (error) {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_warning ("Failed to call '%s' function: %s", script, error->message);
+ g_clear_error (&error);
+ }
+
+ if (js_result) {
+ JSCException *exception;
+ JSCValue *value;
+
+ value = webkit_javascript_result_get_js_value (js_result);
+ exception = jsc_context_get_exception (jsc_value_get_context (value));
+
+ if (exception)
+ g_warning ("Failed to call '%s': %s", script, jsc_exception_get_message (exception));
+
+ webkit_javascript_result_unref (js_result);
+ }
+
+ g_free (script);
+}
+
+void
+e_web_view_jsc_set_element_hidden (WebKitWebView *web_view,
+ const gchar *iframe_id,
+ const gchar *element_id,
+ gboolean value,
+ GCancellable *cancellable)
+{
+ gchar *script;
+
+ g_return_if_fail (WEBKIT_IS_WEB_VIEW (web_view));
+ g_return_if_fail (element_id != NULL);
+
+ script = g_strdup_printf ("evo.SetElementHidden(%s,%s,%d)",
+ iframe_id ? iframe_id : "",
+ element_id,
+ value ? 1 : 0);
+
+ webkit_web_view_run_javascript (web_view, script, cancellable, ewv_jsc_call_done_cb, script);
+}
+
+void
+e_web_view_jsc_set_element_style_property (WebKitWebView *web_view,
+ const gchar *iframe_id,
+ const gchar *element_id,
+ const gchar *property_name,
+ const gchar *value,
+ const gchar *priority,
+ GCancellable *cancellable)
+{
+ g_return_if_fail (WEBKIT_IS_WEB_VIEW (web_view));
+ g_return_if_fail (element_id != NULL);
+ g_return_if_fail (property_name != NULL);
+
+}
+
+void
+e_web_view_jsc_set_element_attribute (WebKitWebView *web_view,
+ const gchar *iframe_id,
+ const gchar *element_id,
+ const gchar *namespace_uri,
+ const gchar *qualified_name,
+ const gchar *value,
+ GCancellable *cancellable)
+{
+ g_return_if_fail (WEBKIT_IS_WEB_VIEW (web_view));
+ g_return_if_fail (element_id != NULL);
+ g_return_if_fail (qualified_name != NULL);
+
+}
diff --git a/src/e-util/e-web-view-jsc-utils.h b/src/e-util/e-web-view-jsc-utils.h
new file mode 100644
index 0000000000..e36a6ee7b4
--- /dev/null
+++ b/src/e-util/e-web-view-jsc-utils.h
@@ -0,0 +1,54 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2019 Red Hat (www.redhat.com)
+ *
+ * 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.
+ *
+ * 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 Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_WEB_VIEW_JSC_UTILS_H
+#define E_WEB_VIEW_JSC_UTILS_H
+
+#include <webkit2/webkit2.h>
+
+G_BEGIN_DECLS
+
+void e_web_view_jsc_set_element_hidden
+ (WebKitWebView *web_view,
+ const gchar *iframe_id,
+ const gchar *element_id,
+ gboolean value,
+ GCancellable *cancellable);
+void e_web_view_jsc_set_element_style_property
+ (WebKitWebView *web_view,
+ const gchar *iframe_id,
+ const gchar *element_id,
+ const gchar *property_name,
+ const gchar *value,
+ const gchar *priority,
+ GCancellable *cancellable);
+void e_web_view_jsc_set_element_attribute
+ (WebKitWebView *web_view,
+ const gchar *iframe_id,
+ const gchar *element_id,
+ const gchar *namespace_uri,
+ const gchar *qualified_name,
+ const gchar *value,
+ GCancellable *cancellable);
+
+G_END_DECLS
+
+#endif /* E_WEB_VIEW_JSC_UTILS_H */
diff --git a/src/e-util/test-web-view.c b/src/e-util/test-web-view.c
new file mode 100644
index 0000000000..b5282d68e1
--- /dev/null
+++ b/src/e-util/test-web-view.c
@@ -0,0 +1,433 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2019 Red Hat (www.redhat.com)
+ *
+ * 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.
+ *
+ * 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 Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "evolution-config.h"
+
+#include <glib.h>
+#include <glib/gstdio.h>
+
+#include <locale.h>
+#include <e-util/e-util.h>
+
+typedef struct _TestFlagClass {
+ GObjectClass parent_class;
+} TestFlagClass;
+
+typedef struct _TestFlag {
+ GObject parent;
+ gboolean is_set;
+} TestFlag;
+
+GType test_flag_get_type (void);
+
+G_DEFINE_TYPE (TestFlag, test_flag, G_TYPE_OBJECT)
+
+enum {
+ FLAGGED,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+static void
+test_flag_class_init (TestFlagClass *klass)
+{
+ signals[FLAGGED] = g_signal_new (
+ "flagged",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 0,
+ G_TYPE_NONE);
+}
+
+static void
+test_flag_init (TestFlag *flag)
+{
+ flag->is_set = FALSE;
+}
+
+static void
+test_flag_set (TestFlag *flag)
+{
+ flag->is_set = TRUE;
+
+ g_signal_emit (flag, signals[FLAGGED], 0, NULL);
+}
+
+typedef struct _TestFixture {
+ GtkWidget *window;
+ GtkWidget *web_view;
+
+ TestFlag *flag;
+} TestFixture;
+
+typedef void (* ETestFixtureSimpleFunc) (TestFixture *fixture);
+
+/* The tests do not use the 'user_data' argument, thus the functions avoid them and the typecast is needed.
*/
+typedef void (* ETestFixtureFunc) (TestFixture *fixture, gconstpointer user_data);
+
+static gboolean
+window_key_press_event_cb (GtkWindow *window,
+ GdkEventKey *event,
+ gpointer user_data)
+{
+ WebKitWebView *web_view = user_data;
+ WebKitWebInspector *inspector;
+ gboolean handled = FALSE;
+
+ inspector = webkit_web_view_get_inspector (web_view);
+
+ if ((event->state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK)) == (GDK_CONTROL_MASK | GDK_SHIFT_MASK) &&
+ event->keyval == GDK_KEY_I) {
+ webkit_web_inspector_show (inspector);
+ handled = TRUE;
+ }
+
+ return handled;
+}
+
+static gboolean
+window_delete_event_cb (GtkWidget *widget,
+ GdkEvent *event,
+ gpointer user_data)
+{
+ TestFixture *fixture = user_data;
+
+ gtk_widget_destroy (fixture->window);
+ fixture->window = NULL;
+ fixture->web_view = NULL;
+
+ test_flag_set (fixture->flag);
+
+ return TRUE;
+}
+
+static void
+test_utils_fixture_set_up (TestFixture *fixture,
+ gconstpointer user_data)
+{
+ WebKitSettings *settings;
+
+ fixture->window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+
+ gtk_window_set_default_size (GTK_WINDOW (fixture->window), 1280, 1024);
+
+ fixture->web_view = e_web_view_new ();
+ g_object_set (G_OBJECT (fixture->web_view),
+ "halign", GTK_ALIGN_FILL,
+ "hexpand", TRUE,
+ "valign", GTK_ALIGN_FILL,
+ "vexpand", TRUE,
+ NULL);
+
+ gtk_container_add (GTK_CONTAINER (fixture->window), fixture->web_view);
+
+ settings = webkit_web_view_get_settings (WEBKIT_WEB_VIEW (fixture->web_view));
+ webkit_settings_set_enable_developer_extras (settings, TRUE);
+
+ g_signal_connect (
+ fixture->window, "key-press-event",
+ G_CALLBACK (window_key_press_event_cb), fixture->web_view);
+
+ g_signal_connect (
+ fixture->window, "delete-event",
+ G_CALLBACK (window_delete_event_cb), fixture);
+
+ gtk_widget_show_all (fixture->window);
+
+ fixture->flag = g_object_new (test_flag_get_type (), NULL);
+}
+
+static void
+test_utils_fixture_tear_down (TestFixture *fixture,
+ gconstpointer user_data)
+{
+ if (fixture->window) {
+ gtk_widget_destroy (fixture->window);
+ fixture->web_view = NULL;
+ }
+
+ g_clear_object (&fixture->flag);
+}
+
+static void
+test_utils_add_test (const gchar *name,
+ ETestFixtureSimpleFunc func)
+{
+ g_test_add (name, TestFixture, NULL,
+ test_utils_fixture_set_up, (ETestFixtureFunc) func, test_utils_fixture_tear_down);
+}
+
+static void
+test_utils_wait (TestFixture *fixture)
+{
+ GMainLoop *loop;
+ gulong handler_id;
+
+ g_return_if_fail (fixture != NULL);
+ g_return_if_fail (fixture->window != NULL);
+ g_return_if_fail (fixture->flag != NULL);
+
+ if (fixture->flag->is_set) {
+ fixture->flag->is_set = FALSE;
+ return;
+ }
+
+ loop = g_main_loop_new (NULL, FALSE);
+
+ handler_id = g_signal_connect_swapped (fixture->flag, "flagged", G_CALLBACK (g_main_loop_quit), loop);
+
+ g_main_loop_run (loop);
+ g_main_loop_unref (loop);
+
+ g_signal_handler_disconnect (fixture->flag, handler_id);
+
+ fixture->flag->is_set = FALSE;
+}
+
+static void
+test_utils_jsc_call_done_cb (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ gchar *script = user_data;
+ WebKitJavascriptResult *js_result;
+ GError *error = NULL;
+
+ g_return_if_fail (script != NULL);
+
+ js_result = webkit_web_view_run_javascript_finish (WEBKIT_WEB_VIEW (source_object), result, &error);
+
+ if (error) {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_warning ("Failed to call '%s' function: %s", script, error->message);
+ g_clear_error (&error);
+ }
+
+ if (js_result) {
+ JSCException *exception;
+ JSCValue *value;
+
+ value = webkit_javascript_result_get_js_value (js_result);
+ exception = jsc_context_get_exception (jsc_value_get_context (value));
+
+ if (exception)
+ g_warning ("Failed to call '%s': %s", script, jsc_exception_get_message (exception));
+
+ webkit_javascript_result_unref (js_result);
+ }
+
+ g_free (script);
+}
+
+typedef struct _JSCCallData {
+ TestFixture *fixture;
+ const gchar *script;
+ JSCValue **out_result;
+} JSCCallData;
+
+static void
+test_utils_jsc_call_sync_done_cb (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ JSCCallData *jcd = user_data;
+ WebKitJavascriptResult *js_result;
+ GError *error = NULL;
+
+ g_return_if_fail (jcd != NULL);
+
+ js_result = webkit_web_view_run_javascript_finish (WEBKIT_WEB_VIEW (source_object), result, &error);
+
+ if (error) {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_warning ("Failed to call '%s': %s", jcd->script, error->message);
+ g_clear_error (&error);
+ }
+
+ if (js_result) {
+ JSCException *exception;
+ JSCValue *value;
+
+ value = webkit_javascript_result_get_js_value (js_result);
+ exception = jsc_context_get_exception (jsc_value_get_context (value));
+
+ if (exception)
+ g_warning ("Failed to call '%s': %s", jcd->script, jsc_exception_get_message
(exception));
+ else if (jcd->out_result)
+ *(jcd->out_result) = value ? g_object_ref (value) : NULL;
+
+ webkit_javascript_result_unref (js_result);
+ }
+
+ test_flag_set (jcd->fixture->flag);
+}
+
+static void
+test_utils_jsc_call (TestFixture *fixture,
+ const gchar *script)
+{
+ g_return_if_fail (fixture != NULL);
+ g_return_if_fail (fixture->web_view != NULL);
+ g_return_if_fail (script != NULL);
+
+ webkit_web_view_run_javascript (WEBKIT_WEB_VIEW (fixture->web_view), script, NULL,
test_utils_jsc_call_done_cb, g_strdup (script));
+}
+
+static void
+test_utils_jsc_call_sync (TestFixture *fixture,
+ const gchar *script,
+ JSCValue **out_result)
+{
+ JSCCallData jcd;
+
+ g_return_if_fail (fixture != NULL);
+ g_return_if_fail (fixture->web_view != NULL);
+ g_return_if_fail (script != NULL);
+
+ if (out_result)
+ *out_result = NULL;
+
+ jcd.fixture = fixture;
+ jcd.script = script;
+ jcd.out_result = out_result;
+
+ webkit_web_view_run_javascript (WEBKIT_WEB_VIEW (fixture->web_view), script, NULL,
test_utils_jsc_call_sync_done_cb, &jcd);
+
+ test_utils_wait (fixture);
+}
+
+static void
+test_utils_iframe_loaded_cb (WebKitUserContentManager *manager,
+ WebKitJavascriptResult *js_result,
+ gpointer user_data)
+{
+ TestFixture *fixture = user_data;
+ JSCValue *jsc_value;
+
+ g_return_if_fail (fixture != NULL);
+ g_return_if_fail (js_result != NULL);
+
+ jsc_value = webkit_javascript_result_get_js_value (js_result);
+ g_return_if_fail (jsc_value_is_string (jsc_value));
+
+ test_flag_set (fixture->flag);
+}
+
+static void
+test_utils_load_iframe_content (TestFixture *fixture,
+ const gchar *iframe_id,
+ const gchar *content)
+{
+ WebKitUserContentManager *manager;
+ GString *escaped_content;
+ gchar *script;
+ gulong handler_id;
+
+ manager = webkit_web_view_get_user_content_manager (WEBKIT_WEB_VIEW (fixture->web_view));
+
+ handler_id = g_signal_connect (manager, "script-message-received::iframeLoaded",
+ G_CALLBACK (test_utils_iframe_loaded_cb), fixture);
+
+ webkit_user_content_manager_register_script_message_handler (manager, "iframeLoaded");
+
+ script = g_strdup_printf (
+ "Evo.findIFrame(\"%s\").onload =
window.webkit.messageHandlers.iframeLoaded.postMessage(\"%s\");\n",
+ iframe_id, iframe_id);
+
+ test_utils_jsc_call (fixture, script);
+
+ g_free (script);
+
+ escaped_content = e_str_replace_string (content, "\"", "\\\"");
+ script = g_strdup_printf ("Evo.SetIFrameContent(\"%s\", \"%s\")", iframe_id, escaped_content->str);
+
+ test_utils_jsc_call (fixture, script);
+
+ g_string_free (escaped_content, TRUE);
+ g_free (script);
+
+ test_utils_wait (fixture);
+
+ g_signal_handler_disconnect (manager, handler_id);
+ webkit_user_content_manager_unregister_script_message_handler (manager, "iframeLoaded");
+}
+
+static void
+load_changed_cb (WebKitWebView *web_view,
+ WebKitLoadEvent load_event,
+ gpointer user_data)
+{
+ TestFixture *fixture = user_data;
+
+ g_return_if_fail (fixture != NULL);
+
+ if (load_event == WEBKIT_LOAD_FINISHED)
+ test_flag_set (fixture->flag);
+}
+
+static void
+test_utils_load_string (TestFixture *fixture,
+ const gchar *content)
+{
+ gulong handler_id;
+
+ handler_id = g_signal_connect (fixture->web_view, "load-changed",
+ G_CALLBACK (load_changed_cb), fixture);
+
+ e_web_view_load_string (E_WEB_VIEW (fixture->web_view), content);
+
+ test_utils_wait (fixture);
+
+ g_signal_handler_disconnect (fixture->web_view, handler_id);
+}
+
+static void
+test_basic (TestFixture *fixture)
+{
+ test_utils_load_string (fixture, "<html><body>Pre<br><iframe id=\"first\"
src=\"empty:///\"></iframe><br>Pos</body></html>");
+ test_utils_load_iframe_content (fixture, "first", "<html><body>second<iframe id=\"second\"
src=\"empty:///\"></iframe></body></html>");
+
+ test_utils_wait (fixture);
+}
+
+gint
+main (gint argc,
+ gchar *argv[])
+{
+ gint res;
+
+ setlocale (LC_ALL, "");
+
+ g_test_init (&argc, &argv, NULL);
+ g_test_bug_base ("https://gitlab.gnome.org/GNOME/evolution/issues/");
+
+ gtk_init (&argc, &argv);
+
+ e_util_init_main_thread (NULL);
+ e_passwords_init ();
+
+ test_utils_add_test ("/basic", test_basic);
+
+ res = g_test_run ();
+
+ e_misc_util_free_global_memory ();
+
+ return res;
+}
diff --git a/src/web-extensions/evolution-web-process-extension.gresource.xml
b/src/web-extensions/evolution-web-process-extension.gresource.xml
new file mode 100644
index 0000000000..0bb6335b7b
--- /dev/null
+++ b/src/web-extensions/evolution-web-process-extension.gresource.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<gresources>
+ <gresource prefix="/org/gnome/evolution-web-process-extension/js">
+ <file compressed="true">ext-utils.js</file>
+ </gresource>
+</gresources>
diff --git a/src/web-extensions/ext-utils.js b/src/web-extensions/ext-utils.js
new file mode 100644
index 0000000000..dc17545f81
--- /dev/null
+++ b/src/web-extensions/ext-utils.js
@@ -0,0 +1,76 @@
+'use strict';
+
+var Evo = {};
+
+Evo.findIFrameInDocument = function(doc, iframe_id)
+{
+ if (!doc)
+ return null;
+
+ var iframes, ii, res;
+
+ iframes = doc.getElementsByTagName("iframe");
+
+ for (ii = 0; ii < iframes.length; ii++) {
+ if (iframes[ii].id == iframe_id)
+ return iframes[ii];
+
+ res = Evo.findIFrameInDocument(iframes[ii].contentDocument, iframe_id);
+
+ if (res)
+ return res;
+ }
+
+ return null;
+}
+
+Evo.findIFrame = function(iframe_id)
+{
+ if (iframe_id == "")
+ return null;
+
+ return Evo.findIFrameInDocument(document, iframe_id);
+}
+
+Evo.findElement = function(iframe_id, element_id)
+{
+ var iframe;
+
+ if (iframe_id == "")
+ return document.getElementById(element_id);
+
+ iframe = Evo.findIFrame(iframe_id);
+
+ if (!iframe)
+ return null;
+
+ return iframe.contentDocument.getElementById(element_id);
+}
+
+Evo.SetElementHidden = function(iframe_id, element_id, value)
+{
+ var elem;
+
+ elem = Evo.findElement(iframe_id, element_id);
+
+ if (elem)
+ elem.hidden = value;
+}
+
+Evo.SetIFrameSrc = function(iframe_id, src_uri)
+{
+ var iframe;
+
+ iframe = Evo.findIFrame(iframe_id);
+ if (iframe)
+ iframe.src = src_uri;
+}
+
+Evo.SetIFrameContent = function(iframe_id, content)
+{
+ var iframe;
+
+ iframe = Evo.findIFrame(iframe_id);
+ if (iframe)
+ iframe.contentDocument.documentElement.innerHTML = content;
+}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]