Load JavaScript plugins for message preview and WebKit editor
This allows to save JavaScript plugins, files with .js extension, into `$PREFIX/share/evolution/webkit/preview-plugins/` or into `~/.local/share/evolution/preview-plugins/` for the message preview and into `$PREFIX/share/evolution/webkit/webkit-editor-plugins/` or into `~/.local/share/evolution/webkit-editor-plugins/` for the WebKit composer. The plugin is an object, which contains two members: * `name` - a string with a name of the plugin * `setup(doc)` - a setup function The argument of the `setup` function is a `document`, which can be either the main document or an iframe document. The function can be called multiple times for the same document. The preview plugin registers itself by calling `Evo.RegisterPlugin(plugin_instance)`. The WebKit editor plugin registers itself by calling `EvoEditor.RegisterPlugin(plugin_instance)`.
This commit is contained in:
@ -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)
|
||||
|
@ -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++) {
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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 *
|
||||
|
Reference in New Issue
Block a user