diff options
author | Philip Chimento <philip@endlessm.com> | 2015-05-19 11:31:25 -0700 |
---|---|---|
committer | Philip Chimento <philip@endlessm.com> | 2015-05-21 13:22:23 -0700 |
commit | a58f5454c8b046ba4f17534a88db1c19ac22b822 (patch) | |
tree | b25eca159bc2d6f54882e9f29cf0818ad15f9c7b /webhelper/webextensions | |
parent | e2729f045a9459c9a320c617ac6e599814744e56 (diff) |
Expose gettext() to client-side JS
This exposes the function set by webhelper.set_gettext() to the client-
side Javascript as a gettext() function, defined on the global window
object. This allows apps to translate messages that are generated
at runtime, not just messages in static HTML.
Some often-used JavaScriptCore operations can be turned into separate
functions, which we can put in a separate source file. This is in
anticipation of the next commit where we will define another function
property of the global object.
[endlessm/eos-sdk#291]
Diffstat (limited to 'webhelper/webextensions')
-rw-r--r-- | webhelper/webextensions/wh2extension.c | 89 | ||||
-rw-r--r-- | webhelper/webextensions/wh2jscutil.c | 65 | ||||
-rw-r--r-- | webhelper/webextensions/wh2jscutil.h | 25 |
3 files changed, 179 insertions, 0 deletions
diff --git a/webhelper/webextensions/wh2extension.c b/webhelper/webextensions/wh2extension.c index c51171e..48ea9aa 100644 --- a/webhelper/webextensions/wh2extension.c +++ b/webhelper/webextensions/wh2extension.c @@ -5,9 +5,13 @@ #include <string.h> #include <glib.h> +#include <JavaScriptCore/JavaScript.h> #include <webkit2/webkit-web-extension.h> #include <webkitdom/webkitdom.h> +#include "wh2jscutil.h" + +#define PRIVATE_NAME "_webhelper_private" #define WH2_DBUS_INTERFACE_NAME "com.endlessm.WebHelper2.Translation" #define MAIN_PROGRAM_OBJECT_PATH "/com/endlessm/gettext" #define MAIN_PROGRAM_INTERFACE_NAME "com.endlessm.WebHelper2.Gettext" @@ -74,6 +78,51 @@ translation_function (const gchar *message, return retval; } +static JSValueRef +gettext_shim (JSContextRef js, + JSObjectRef function, + JSObjectRef this_object, + size_t n_args, + const JSValueRef args[], + JSValueRef *exception) +{ + if (n_args != 1) + { + gchar *errmsg = g_strdup_printf ("Expected one argument to gettext()," + "but got %d.", n_args); + *exception = throw_exception (js, errmsg); + g_free (errmsg); + return NULL; + } + if (!JSValueIsString (js, args[0])) + { + *exception = throw_exception (js, + "Expected a string argument to gettext()."); + return NULL; + } + + JSObjectRef window = JSContextGetGlobalObject (js); + JSStringRef private_name = JSStringCreateWithUTF8CString (PRIVATE_NAME); + JSValueRef private_data = JSObjectGetProperty (js, window, private_name, + exception); + if (JSValueIsUndefined (js, private_data)) + return NULL; /* propagate exception */ + Context *ctxt = (Context *) JSObjectGetPrivate ((JSObjectRef) private_data); + + JSStringRef message_ref = JSValueToStringCopy (js, args[0], exception); + if (message_ref == NULL) + return NULL; /* propagate exception */ + gchar *message = string_ref_to_string (message_ref); + JSStringRelease (message_ref); + + gchar *translation = translation_function (message, ctxt); + g_free (message); + + JSValueRef retval = string_to_value_ref (js, translation); + g_free (translation); + return retval; +} + static void translate_html (WebKitDOMDocument *dom, Context *ctxt) @@ -216,6 +265,41 @@ on_page_created (WebKitWebExtension *extension, pctxt, NULL); } +/* window-object-cleared is the best time to define properties on the page's +window object, according to the documentation. */ +static void +on_window_object_cleared (WebKitScriptWorld *script_world, + WebKitWebPage *page, + WebKitFrame *frame, + Context *ctxt) +{ + JSGlobalContextRef js = + webkit_frame_get_javascript_context_for_script_world (frame, script_world); + JSObjectRef window = JSContextGetGlobalObject (js); + + /* First we need to create a custom class for a private data object to store + our context in, because you can't pass callback data to JavaScriptCore + callbacks. You also can't set private data on a Javascript object if it's not + of a custom class, because the built-in classes don't allocate space for a + private pointer. */ + JSClassDefinition class_def = { + .className = "PrivateContextObject" + }; + JSClassRef klass = JSClassCreate (&class_def); + JSObjectRef private_data = JSObjectMake (js, klass, ctxt); + JSClassRelease (klass); + + if (!set_object_property (js, window, PRIVATE_NAME, (JSValueRef) private_data, + kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete)) + return; + + JSObjectRef gettext_func = + JSObjectMakeFunctionWithCallback (js, NULL, gettext_shim); + if (!set_object_property (js, window, "gettext", (JSValueRef) gettext_func, + kJSPropertyAttributeNone)) + return; +} + static void on_bus_acquired (GDBusConnection *connection, const gchar *name, @@ -225,6 +309,11 @@ on_bus_acquired (GDBusConnection *connection, ctxt->connection = connection; + /* Get a notification when Javascript is ready */ + WebKitScriptWorld *script_world = webkit_script_world_get_default (); + g_signal_connect (script_world, "window-object-cleared", + G_CALLBACK (on_window_object_cleared), ctxt); + /* Export our interface on the bus */ ctxt->node = g_dbus_node_info_new_for_xml (introspection_xml, &error); if (ctxt->node == NULL) diff --git a/webhelper/webextensions/wh2jscutil.c b/webhelper/webextensions/wh2jscutil.c new file mode 100644 index 0000000..34f325f --- /dev/null +++ b/webhelper/webextensions/wh2jscutil.c @@ -0,0 +1,65 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/* Copyright 2015 Endless Mobile, Inc. */ + +#include <glib.h> +#include <JavaScriptCore/JavaScript.h> + +#include "wh2jscutil.h" + +G_GNUC_INTERNAL +gboolean +set_object_property (JSContextRef js, + JSObjectRef object, + const gchar *property_name, + JSValueRef property_value, + JSPropertyAttributes flags) +{ + JSValueRef exception = NULL; + JSStringRef property_name_ref = JSStringCreateWithUTF8CString (property_name); + JSObjectSetProperty (js, object, property_name_ref, property_value, flags, + &exception); + JSStringRelease (property_name_ref); + if (exception != NULL) + { + g_critical ("There was a problem setting the property '%s'.", + property_name); + return FALSE; + } + return TRUE; +} + +/* Returns a newly allocated string. */ +G_GNUC_INTERNAL +gchar * +string_ref_to_string (JSStringRef string_ref) +{ + size_t bufsize = JSStringGetMaximumUTF8CStringSize (string_ref); + gchar *string = g_new0 (gchar, bufsize); + JSStringGetUTF8CString (string_ref, string, bufsize); + return string; +} + +G_GNUC_INTERNAL +JSValueRef +string_to_value_ref (JSContextRef js, + const gchar *string) +{ + JSStringRef string_ref = JSStringCreateWithUTF8CString (string); + JSValueRef value_ref = JSValueMakeString (js, string_ref); + /* value_ref owns string_ref now */ + return value_ref; +} + +G_GNUC_INTERNAL +JSValueRef +throw_exception (JSContextRef js, + const gchar *message) +{ + JSValueRef msgval = string_to_value_ref (js, message); + JSValueRef inner_error = NULL; + JSObjectRef exception = JSObjectMakeError (js, 1, &msgval, &inner_error); + if (inner_error != NULL) + return inner_error; + return (JSValueRef) exception; +} diff --git a/webhelper/webextensions/wh2jscutil.h b/webhelper/webextensions/wh2jscutil.h new file mode 100644 index 0000000..af800e5 --- /dev/null +++ b/webhelper/webextensions/wh2jscutil.h @@ -0,0 +1,25 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/* Copyright 2015 Endless Mobile, Inc. */ + +#ifndef WH2_JSC_UTIL_H +#define WH2_JSC_UTIL_H + +#include <glib.h> +#include <JavaScriptCore/JavaScript.h> + +gboolean set_object_property (JSContextRef js, + JSObjectRef object, + const gchar *property_name, + JSValueRef property_value, + JSPropertyAttributes flags); + +gchar *string_ref_to_string (JSStringRef string_ref); + +JSValueRef string_to_value_ref (JSContextRef js, + const gchar *string); + +JSValueRef throw_exception (JSContextRef js, + const gchar *message); + +#endif /* WH2_JSC_UTIL_H */ |