--- /dev/null
+/*
+ Copyright (C) 2009 Christian Dywan <christian@twotoasts.de>
+
+ 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; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ See the file COPYING for the full license text.
+*/
+
+/*
+midori.tabs
+midori.windows
+midori.currentWindow
+midori.createAction({name:'Example',label:'_Example',icon:'gtk-info',tooltip:'Examples',activate:function()})
+action = midori.currentWindow.getAction(name)
+foreach (window as midori.windows) window.getAction('myaction').activate()
+midori.window[i].tabs
+midori.addWindow
+midori.addTab
+midori.removeWindow
+midori.removeTab
+*/
+
+#if HAVE_CONFIG_H
+ #include <config.h>
+#endif
+
+#include <midori/midori.h>
+#include <midori/sokoke.h>
+#include <JavaScriptCore/JavaScript.h>
+
+gchar*
+sokoke_js_string_utf8 (JSStringRef js_string)
+{
+ size_t size_utf8;
+ gchar* string_utf8;
+
+ g_return_val_if_fail (js_string, NULL);
+
+ size_utf8 = JSStringGetMaximumUTF8CStringSize (js_string);
+ string_utf8 = g_new (gchar, size_utf8);
+ JSStringGetUTF8CString (js_string, string_utf8, size_utf8);
+ return string_utf8;
+}
+
+static void
+javascript_deactivate_cb (MidoriExtension* extension,
+ MidoriBrowser* browser)
+{
+ /* FIXME: Unload all javascript extensions */
+}
+
+static JSValueRef
+midori_javascript_midori_action_cb (JSContextRef js_context,
+ JSObjectRef js_function,
+ JSObjectRef js_this,
+ size_t n_arguments,
+ const JSValueRef js_arguments[],
+ JSValueRef* js_exception)
+{
+ GtkAction* action;
+ JSObjectRef js_meta;
+ JSPropertyNameArrayRef js_properties;
+ size_t n_properties;
+ guint i;
+ MidoriApp* app;
+
+ if (n_arguments != 1)
+ {
+ *js_exception = JSValueMakeString (js_context,
+ JSStringCreateWithUTF8CString (
+ "MidoriError: Wrong number of arguments; 'midori.createAction ({property:value, ...})'"));
+ return JSValueMakeNull (js_context);
+ }
+
+ if (!JSValueIsObject (js_context, js_arguments[0]))
+ {
+ *js_exception = JSValueMakeString (js_context,
+ JSStringCreateWithUTF8CString (
+ "MidoriError: Argument is not an object; 'midori.createAction ({property:value, ...})'"));
+ return JSValueMakeNull (js_context);
+ }
+
+ action = g_object_new (GTK_TYPE_ACTION, NULL);
+ js_meta = JSValueToObject (js_context, js_arguments[0], NULL);
+ js_properties = JSObjectCopyPropertyNames (js_context, js_meta);
+ n_properties = JSPropertyNameArrayGetCount (js_properties);
+ for (i = 0; i < n_properties; i++)
+ {
+ JSStringRef js_name;
+ gchar* name;
+ JSValueRef js_property;
+ JSStringRef js_string;
+ gchar* string;
+
+ js_name = JSPropertyNameArrayGetNameAtIndex (js_properties, i);
+ name = sokoke_js_string_utf8 (js_name);
+ if (g_str_equal (name, "label"))
+ {
+ js_property = JSObjectGetProperty (js_context, js_meta, js_name, NULL);
+ js_string = JSValueToStringCopy (js_context, js_property, NULL);
+ string = sokoke_js_string_utf8 (js_string);
+ JSStringRelease (js_string);
+ g_object_set (action, "label", string, NULL);
+ g_free (string);
+ }
+ else if (g_str_equal (name, "icon"))
+ {
+ js_property = JSObjectGetProperty (js_context, js_meta, js_name, NULL);
+ js_string = JSValueToStringCopy (js_context, js_property, NULL);
+ string = sokoke_js_string_utf8 (js_string);
+ JSStringRelease (js_string);
+ /* FIXME: stock-id, or icon-name, or URI */
+ g_object_set (action, "stock-id", string, NULL);
+ g_free (string);
+ }
+ else if (g_str_equal (name, "tooltip"))
+ {
+ js_property = JSObjectGetProperty (js_context, js_meta, js_name, NULL);
+ js_string = JSValueToStringCopy (js_context, js_property, NULL);
+ string = sokoke_js_string_utf8 (js_string);
+ JSStringRelease (js_string);
+ g_object_set (action, "tooltip", string, NULL);
+ g_free (string);
+ }
+ else if (g_str_equal (name, "activate"))
+ {
+ /* FIXME */
+ }
+ else
+ {
+ *js_exception = JSValueMakeString (js_context,
+ JSStringCreateWithUTF8CString (
+ "MidoriError: Unknown property; 'midori.createAction ({property:value, ...})'"));
+ return JSValueMakeNull (js_context);
+ }
+ }
+
+ if ((app = JSObjectGetPrivate (js_this)))
+ {
+ /* TODO: Offer the user to add a toolbar button */
+ }
+ /* TODO: add action to all existing and future browsers */
+ /* gtk_action_connect_accelerator */
+ return JSValueMakeNull (js_context);
+}
+
+static JSContextRef
+midori_javascript_context (MidoriApp* app)
+{
+ JSContextRef js_context = JSGlobalContextCreateInGroup (NULL, NULL);
+ JSClassDefinition js_class_def = kJSClassDefinitionEmpty;
+ JSClassRef js_class;
+ JSObjectRef js_object;
+ JSStringRef js_name;
+ JSStaticFunction functions[] = {
+ { "createAction", midori_javascript_midori_action_cb, kJSPropertyAttributeNone },
+ { NULL, NULL, 0 }
+ };
+
+ js_class_def.className = "midori";
+ js_class_def.staticFunctions = functions;
+ js_class = JSClassCreate (&js_class_def);
+ js_object = JSObjectMake (js_context, js_class, app);
+
+ js_name = JSStringCreateWithUTF8CString ("midori");
+ JSObjectSetProperty (js_context, JSContextGetGlobalObject (js_context),
+ js_name, js_object, kJSPropertyAttributeNone, NULL);
+ JSStringRelease (js_name);
+
+ return js_context;
+}
+
+static void
+midori_javascript_extension_activate_cb (MidoriExtension* extension,
+ MidoriApp* app)
+{
+ gchar* filename = g_object_get_data (G_OBJECT (extension), "filename");
+ gchar* fullname = g_build_path (G_DIR_SEPARATOR_S, g_get_user_data_dir (),
+ PACKAGE_NAME, "extensions", filename, NULL);
+ gchar* script;
+ GError* error = NULL;
+ if (g_file_get_contents (fullname, &script, NULL, &error))
+ {
+ JSContextRef js_context = midori_javascript_context (app);
+ gchar* exception = NULL;
+ g_free (sokoke_js_script_eval (js_context, script, &exception));
+ if (exception)
+ {
+ g_object_set (extension, "description",
+ exception, "version", NULL, NULL);
+ g_warning ("%s", exception);
+ g_free (exception);
+ }
+ }
+ else
+ {
+ g_object_set (extension, "description",
+ error->message, "version", NULL, NULL);
+ g_warning ("%s", error->message);
+ g_error_free (error);
+ }
+ g_free (fullname);
+ g_free (script);
+}
+
+static void
+javascript_load_extensions (gchar** active,
+ MidoriApp* app,
+ const gchar* path)
+{
+ GDir* dir;
+
+ /* TODO: Monitor folder for new files or modifications at runtime */
+ if ((dir = g_dir_open (path, 0, NULL)))
+ {
+ KatzeArray* extensions = katze_object_get_object (app, "extensions");
+ JSContextRef js_context = midori_javascript_context (app);
+ const gchar* filename;
+
+ while ((filename = g_dir_read_name (dir)))
+ {
+ gchar* fullname;
+ GError* error;
+ gchar* script;
+ MidoriExtension* extension;
+
+ /* Ignore files which don't have the correct suffix */
+ if (!g_str_has_suffix (filename, ".js"))
+ continue;
+
+ fullname = g_build_filename (path, filename, NULL);
+ error = NULL;
+ extension = g_object_new (MIDORI_TYPE_EXTENSION, "name", filename, NULL);
+ if (g_file_get_contents (fullname, &script, NULL, &error))
+ {
+ JSStringRef js_script;
+ JSValueRef js_exception;
+
+ js_script = JSStringCreateWithUTF8CString (script);
+ if (JSCheckScriptSyntax (js_context, js_script, NULL,
+ 0, &js_exception))
+ {
+ /* FIXME: Read meta data from .js file */
+ g_object_set (extension, "description", "",
+ "version", "0.1", "authors", "", NULL);
+ /* Signal that we want the extension to load and save */
+ g_object_set_data_full (G_OBJECT (extension), "filename",
+ g_strdup (filename), g_free);
+ if (midori_extension_is_prepared (extension))
+ midori_extension_get_config_dir (extension);
+ g_signal_connect (extension, "activate",
+ G_CALLBACK (midori_javascript_extension_activate_cb), NULL);
+ }
+ else
+ {
+ JSStringRef js_string = JSValueToStringCopy (js_context,
+ js_exception, NULL);
+ gchar* string = sokoke_js_string_utf8 (js_string);
+ JSStringRelease (js_string);
+ g_object_set (extension, "description", string, NULL);
+ g_warning ("%s", string);
+ g_free (string);
+ }
+ }
+ else
+ {
+ g_object_set (extension, "description", error->message, NULL);
+ g_warning ("%s", error->message);
+ g_error_free (error);
+ }
+ g_free (fullname);
+ katze_array_add_item (extensions, extension);
+ if (active)
+ {
+ guint i = 0;
+ gchar* name;
+ while ((name = active[i++]))
+ if (!g_strcmp0 (filename, name))
+ g_signal_emit_by_name (extension, "activate", app);
+ }
+ /* FIXME main.c needs to monitor extensions
+ g_signal_connect_after (extension, "activate",
+ G_CALLBACK (extension_activate_cb), app);
+ g_signal_connect_after (extension, "deactivate",
+ G_CALLBACK (extension_activate_cb), app); */
+ g_object_unref (extension);
+ }
+ g_object_unref (extensions);
+ g_dir_close (dir);
+ }
+}
+
+static void
+javascript_activate_cb (MidoriExtension* extension,
+ MidoriApp* app)
+{
+ gchar** active = midori_extension_get_string_list (extension, "extensions", NULL);
+ /* FIXME Scan system data dirs */
+ gchar* path = g_build_path (G_DIR_SEPARATOR_S, g_get_user_data_dir (),
+ PACKAGE_NAME, "extensions", NULL);
+ javascript_load_extensions (active, app, path);
+ g_free (path);
+ g_strfreev (active);
+
+ g_signal_connect (extension, "deactivate",
+ G_CALLBACK (javascript_deactivate_cb), NULL);
+}
+
+MidoriExtension*
+extension_init (void)
+{
+ MidoriExtension* extension = g_object_new (MIDORI_TYPE_EXTENSION,
+ "name", _("Javascript extensions"),
+ "description", _("Enable extensions written in Javascript"),
+ "version", "0.1",
+ "authors", "Christian Dywan <christian@twotoasts.de>",
+ NULL);
+ midori_extension_install_string_list (extension, "extensions", NULL, G_MAXSIZE);
+
+ g_signal_connect (extension, "activate",
+ G_CALLBACK (javascript_activate_cb), NULL);
+
+ return extension;
+}