diff --git a/data/webkit/e-editor.js b/data/webkit/e-editor.js index 5a3701519f..bf363edf7e 100644 --- a/data/webkit/e-editor.js +++ b/data/webkit/e-editor.js @@ -87,6 +87,7 @@ var EvoEditor = { MODE_PLAIN_TEXT : 0, MODE_HTML : 1, + plugins : null, mode : 1, // one of the MODE constants storedSelection : null, propertiesSelection : null, // dedicated to Properties dialogs @@ -117,6 +118,45 @@ var EvoEditor = { } }; +EvoEditor.RegisterPlugin = function(plugin) +{ + if (plugin == null) + return; + + if (plugin.name === undefined) { + console.error("Evo.RegisterPlugin: Plugin '" + plugin + "' has missing 'name' member"); + return; + } + + if (plugin.setup === undefined) { + console.error("Evo.RegisterPlugin: Plugin '" + plugin.name + "' has missing 'setup' function"); + return; + } + + if (EvoEditor.plugins == null) + EvoEditor.plugins = []; + + EvoEditor.plugins.push(plugin); +} + +EvoEditor.setupPlugins = function(doc) +{ + if (EvoEditor.plugins == null) + return; + + var ii; + + for (ii = 0; ii < EvoEditor.plugins.length; ii++) { + try { + if (EvoEditor.plugins[ii] != null) + EvoEditor.plugins[ii].setup(doc); + } catch (err) { + console.error("Failed to setup plugin '" + EvoEditor.plugins[ii].name + "': " + err.name + ": " + err.message); + EvoEditor.plugins[ii] = null; + } + } +} + EvoEditor.maybeUpdateFormattingState = function(force) { var anchorElem = null; @@ -1778,6 +1818,8 @@ EvoEditor.initializeContent = function() document.getSelection().setPosition(document.body.firstChild ? document.body.firstChild : document.body, 0); } } + + EvoEditor.setupPlugins(document); } EvoEditor.getNextNodeInHierarchy = function(node, upToNode) diff --git a/data/webkit/e-web-view.js b/data/webkit/e-web-view.js index 2e87eaed1d..73fce72680 100644 --- a/data/webkit/e-web-view.js +++ b/data/webkit/e-web-view.js @@ -24,9 +24,49 @@ var Evo = { hasSelection : false, blockquoteStyle : "margin:0 0 0 .8ex; border-left:2px #729fcf solid;padding-left:1ex", magicSpacebarState: -1, - markCitationColor : null + markCitationColor : null, + plugins : null }; +Evo.RegisterPlugin = function(plugin) +{ + if (plugin == null) + return; + + if (plugin.name === undefined) { + console.error("Evo.RegisterPlugin: Plugin '" + plugin + "' has missing 'name' member"); + return; + } + + if (plugin.setup === undefined) { + console.error("Evo.RegisterPlugin: Plugin '" + plugin.name + "' has missing 'setup' function"); + return; + } + + if (Evo.plugins == null) + Evo.plugins = []; + + Evo.plugins.push(plugin); +} + +Evo.setupPlugins = function(doc) +{ + if (Evo.plugins == null) + return; + + var ii; + + for (ii = 0; ii < Evo.plugins.length; ii++) { + try { + if (Evo.plugins[ii] != null) + Evo.plugins[ii].setup(doc); + } catch (err) { + console.error("Failed to setup plugin '" + Evo.plugins[ii].name + "': " + err.name + ": " + err.message); + Evo.plugins[ii] = null; + } + } +} + /* The 'traversar_obj' is an object, which implements a callback function: boolean exec(doc, iframe_id, level); and it returns whether continue the traversar */ @@ -619,6 +659,8 @@ Evo.initialize = function(elem) } else doc = document; + Evo.setupPlugins(doc); + elems = doc.getElementsByTagName("iframe"); for (ii = 0; ii < elems.length; ii++) { diff --git a/src/modules/webkit-editor/web-extension/e-editor-web-extension.c b/src/modules/webkit-editor/web-extension/e-editor-web-extension.c index 5b6cfb9bc5..cf8fdf90cf 100644 --- a/src/modules/webkit-editor/web-extension/e-editor-web-extension.c +++ b/src/modules/webkit-editor/web-extension/e-editor-web-extension.c @@ -31,6 +31,7 @@ struct _EEditorWebExtensionPrivate { WebKitWebExtension *wk_extension; ESpellChecker *spell_checker; + GSList *known_plugins; /* gchar * - full filename to known plugins */ }; G_DEFINE_TYPE_WITH_PRIVATE (EEditorWebExtension, e_editor_web_extension, G_TYPE_OBJECT) @@ -43,6 +44,9 @@ e_editor_web_extension_dispose (GObject *object) g_clear_object (&extension->priv->wk_extension); g_clear_object (&extension->priv->spell_checker); + g_slist_free_full (extension->priv->known_plugins, g_free); + extension->priv->known_plugins = NULL; + /* Chain up to parent's dispose() method. */ G_OBJECT_CLASS (e_editor_web_extension_parent_class)->dispose (object); } @@ -86,15 +90,100 @@ use_sources_js_file (void) return res; } -static void +static gboolean load_javascript_file (JSCContext *jsc_context, - const gchar *js_filename) + const gchar *js_filename, + const gchar *filename) { JSCValue *result; JSCException *exception; - gchar *content, *filename = NULL, *resource_uri; + gchar *content, *resource_uri; gsize length = 0; GError *error = NULL; + gboolean success = TRUE; + + g_return_val_if_fail (jsc_context != NULL, FALSE); + + if (!g_file_get_contents (filename, &content, &length, &error)) { + g_warning ("Failed to load '%s': %s", filename, error ? error->message : "Unknown error"); + + g_clear_error (&error); + + return FALSE; + } + + resource_uri = g_strconcat ("resource:///", js_filename, NULL); + + result = jsc_context_evaluate_with_source_uri (jsc_context, content, length, resource_uri, 1); + + g_free (resource_uri); + + exception = jsc_context_get_exception (jsc_context); + + if (exception) { + g_warning ("Failed to call script '%s': %d:%d: %s", + filename, + jsc_exception_get_line_number (exception), + jsc_exception_get_column_number (exception), + jsc_exception_get_message (exception)); + + jsc_context_clear_exception (jsc_context); + success = FALSE; + } + + g_clear_object (&result); + g_free (content); + + return success; +} + +static void +load_javascript_plugins (JSCContext *jsc_context, + const gchar *top_path, + GSList **out_loaded_plugins) +{ + const gchar *dirfile; + gchar *path; + GDir *dir; + + g_return_if_fail (jsc_context != NULL); + + /* Do not load plugins during unit tests */ + if (use_sources_js_file ()) + return; + + path = g_build_filename (top_path, "webkit-editor-plugins", NULL); + + dir = g_dir_open (path, 0, NULL); + if (!dir) { + g_free (path); + return; + } + + while (dirfile = g_dir_read_name (dir), dirfile) { + if (g_str_has_suffix (dirfile, ".js") || + g_str_has_suffix (dirfile, ".Js") || + g_str_has_suffix (dirfile, ".jS") || + g_str_has_suffix (dirfile, ".JS")) { + gchar *filename; + + filename = g_build_filename (path, dirfile, NULL); + if (load_javascript_file (jsc_context, filename, filename)) + *out_loaded_plugins = g_slist_prepend (*out_loaded_plugins, filename); + else + g_free (filename); + } + } + + g_dir_close (dir); + g_free (path); +} + +static void +load_javascript_builtin_file (JSCContext *jsc_context, + const gchar *js_filename) +{ + gchar *filename = NULL; g_return_if_fail (jsc_context != NULL); @@ -119,36 +208,9 @@ load_javascript_file (JSCContext *jsc_context, if (!filename) filename = g_build_filename (EVOLUTION_WEBKITDATADIR, js_filename, NULL); - if (!g_file_get_contents (filename, &content, &length, &error)) { - g_warning ("Failed to load '%s': %s", filename, error ? error->message : "Unknown error"); + load_javascript_file (jsc_context, js_filename, filename); - g_clear_error (&error); - g_free (filename); - - return; - } - - resource_uri = g_strconcat ("resource:///", js_filename, NULL); - - result = jsc_context_evaluate_with_source_uri (jsc_context, content, length, resource_uri, 1); - - g_free (resource_uri); - - exception = jsc_context_get_exception (jsc_context); - - if (exception) { - g_warning ("Failed to call script '%s': %d:%d: %s", - filename, - jsc_exception_get_line_number (exception), - jsc_exception_get_column_number (exception), - jsc_exception_get_message (exception)); - - jsc_context_clear_exception (jsc_context); - } - - g_clear_object (&result); g_free (filename); - g_free (content); } static void @@ -472,10 +534,10 @@ window_object_cleared_cb (WebKitScriptWorld *world, jsc_context = webkit_frame_get_js_context (frame); /* Read in order approximately as each other uses the previous */ - load_javascript_file (jsc_context, "e-convert.js"); - load_javascript_file (jsc_context, "e-selection.js"); - load_javascript_file (jsc_context, "e-undo-redo.js"); - load_javascript_file (jsc_context, "e-editor.js"); + load_javascript_builtin_file (jsc_context, "e-convert.js"); + load_javascript_builtin_file (jsc_context, "e-selection.js"); + load_javascript_builtin_file (jsc_context, "e-undo-redo.js"); + load_javascript_builtin_file (jsc_context, "e-editor.js"); jsc_editor = jsc_context_get_value (jsc_context, "EvoEditor"); @@ -525,6 +587,25 @@ window_object_cleared_cb (WebKitScriptWorld *world, g_clear_object (&jsc_editor); } + if (extension->priv->known_plugins) { + GSList *link; + + for (link = extension->priv->known_plugins; link; link = g_slist_next (link)) { + const gchar *filename = link->data; + + if (filename) + load_javascript_file (jsc_context, filename, filename); + } + } else { + load_javascript_plugins (jsc_context, EVOLUTION_WEBKITDATADIR, &extension->priv->known_plugins); + load_javascript_plugins (jsc_context, e_get_user_data_dir (), &extension->priv->known_plugins); + + if (!extension->priv->known_plugins) + extension->priv->known_plugins = g_slist_prepend (extension->priv->known_plugins, NULL); + else + extension->priv->known_plugins = g_slist_reverse (extension->priv->known_plugins); + } + g_clear_object (&jsc_context); } diff --git a/src/web-extensions/e-web-extension.c b/src/web-extensions/e-web-extension.c index fe0123b789..57ac47042e 100644 --- a/src/web-extensions/e-web-extension.c +++ b/src/web-extensions/e-web-extension.c @@ -34,6 +34,7 @@ struct _EWebExtensionPrivate { WebKitWebExtension *wk_extension; + GSList *known_plugins; /* gchar * - full filename to known plugins */ gboolean initialized; }; @@ -56,6 +57,9 @@ e_web_extension_dispose (GObject *object) g_clear_object (&extension->priv->wk_extension); + g_slist_free_full (extension->priv->known_plugins, g_free); + extension->priv->known_plugins = NULL; + G_OBJECT_CLASS (e_web_extension_parent_class)->dispose (object); } @@ -159,15 +163,100 @@ use_sources_js_file (void) return res; } -static void +static gboolean load_javascript_file (JSCContext *jsc_context, - const gchar *js_filename) + const gchar *js_filename, + const gchar *filename) { JSCValue *result; JSCException *exception; - gchar *content, *filename = NULL, *resource_uri; + gchar *content, *resource_uri; gsize length = 0; GError *error = NULL; + gboolean success = TRUE; + + g_return_val_if_fail (jsc_context != NULL, FALSE); + + if (!g_file_get_contents (filename, &content, &length, &error)) { + g_warning ("Failed to load '%s': %s", filename, error ? error->message : "Unknown error"); + + g_clear_error (&error); + + return FALSE; + } + + resource_uri = g_strconcat ("resource:///", js_filename, NULL); + + result = jsc_context_evaluate_with_source_uri (jsc_context, content, length, resource_uri, 1); + + g_free (resource_uri); + + exception = jsc_context_get_exception (jsc_context); + + if (exception) { + g_warning ("Failed to call script '%s': %d:%d: %s", + filename, + jsc_exception_get_line_number (exception), + jsc_exception_get_column_number (exception), + jsc_exception_get_message (exception)); + + jsc_context_clear_exception (jsc_context); + success = FALSE; + } + + g_clear_object (&result); + g_free (content); + + return success; +} + +static void +load_javascript_plugins (JSCContext *jsc_context, + const gchar *top_path, + GSList **out_loaded_plugins) +{ + const gchar *dirfile; + gchar *path; + GDir *dir; + + g_return_if_fail (jsc_context != NULL); + + /* Do not load plugins during unit tests */ + if (use_sources_js_file ()) + return; + + path = g_build_filename (top_path, "preview-plugins", NULL); + + dir = g_dir_open (path, 0, NULL); + if (!dir) { + g_free (path); + return; + } + + while (dirfile = g_dir_read_name (dir), dirfile) { + if (g_str_has_suffix (dirfile, ".js") || + g_str_has_suffix (dirfile, ".Js") || + g_str_has_suffix (dirfile, ".jS") || + g_str_has_suffix (dirfile, ".JS")) { + gchar *filename; + + filename = g_build_filename (path, dirfile, NULL); + if (load_javascript_file (jsc_context, filename, filename)) + *out_loaded_plugins = g_slist_prepend (*out_loaded_plugins, filename); + else + g_free (filename); + } + } + + g_dir_close (dir); + g_free (path); +} + +static void +load_javascript_builtin_file (JSCContext *jsc_context, + const gchar *js_filename) +{ + gchar *filename = NULL; g_return_if_fail (jsc_context != NULL); @@ -192,36 +281,9 @@ load_javascript_file (JSCContext *jsc_context, if (!filename) filename = g_build_filename (EVOLUTION_WEBKITDATADIR, js_filename, NULL); - if (!g_file_get_contents (filename, &content, &length, &error)) { - g_warning ("Failed to load '%s': %s", filename, error ? error->message : "Unknown error"); + load_javascript_file (jsc_context, js_filename, filename); - g_clear_error (&error); - g_free (filename); - - return; - } - - resource_uri = g_strconcat ("resource:///", js_filename, NULL); - - result = jsc_context_evaluate_with_source_uri (jsc_context, content, length, resource_uri, 1); - - g_free (resource_uri); - - exception = jsc_context_get_exception (jsc_context); - - if (exception) { - g_warning ("Failed to call script '%s': %d:%d: %s", - filename, - jsc_exception_get_line_number (exception), - jsc_exception_get_column_number (exception), - jsc_exception_get_message (exception)); - - jsc_context_clear_exception (jsc_context); - } - - g_clear_object (&result); g_free (filename); - g_free (content); } static void @@ -230,6 +292,7 @@ window_object_cleared_cb (WebKitScriptWorld *world, WebKitFrame *frame, gpointer user_data) { + EWebExtension *extension = user_data; JSCContext *jsc_context; JSCValue *jsc_evo_object; @@ -240,8 +303,8 @@ window_object_cleared_cb (WebKitScriptWorld *world, jsc_context = webkit_frame_get_js_context (frame); /* Read e-convert.js first, because e-web-view.js uses it */ - load_javascript_file (jsc_context, "e-convert.js"); - load_javascript_file (jsc_context, "e-web-view.js"); + load_javascript_builtin_file (jsc_context, "e-convert.js"); + load_javascript_builtin_file (jsc_context, "e-web-view.js"); jsc_evo_object = jsc_context_get_value (jsc_context, "Evo"); @@ -262,6 +325,26 @@ window_object_cleared_cb (WebKitScriptWorld *world, } g_clear_object (&jsc_evo_object); + + if (extension->priv->known_plugins) { + GSList *link; + + for (link = extension->priv->known_plugins; link; link = g_slist_next (link)) { + const gchar *filename = link->data; + + if (filename) + load_javascript_file (jsc_context, filename, filename); + } + } else { + load_javascript_plugins (jsc_context, EVOLUTION_WEBKITDATADIR, &extension->priv->known_plugins); + load_javascript_plugins (jsc_context, e_get_user_data_dir (), &extension->priv->known_plugins); + + if (!extension->priv->known_plugins) + extension->priv->known_plugins = g_slist_prepend (extension->priv->known_plugins, NULL); + else + extension->priv->known_plugins = g_slist_reverse (extension->priv->known_plugins); + } + g_clear_object (&jsc_context); } @@ -287,7 +370,7 @@ e_web_extension_initialize (EWebExtension *extension, script_world = webkit_script_world_get_default (); g_signal_connect (script_world, "window-object-cleared", - G_CALLBACK (window_object_cleared_cb), NULL); + G_CALLBACK (window_object_cleared_cb), extension); } WebKitWebExtension *