[evolution/wip/mcrha/webkit-jsc-api] Initial commit for use of WebKit's JavaScriptCore API



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]