diff options
author | Philip Chimento <philip.chimento@gmail.com> | 2015-08-07 15:59:54 -0700 |
---|---|---|
committer | Philip Chimento <philip.chimento@gmail.com> | 2015-08-07 17:42:25 -0700 |
commit | 8c837e660853abd03c97a98815f73146eb57c010 (patch) | |
tree | e84e1da4c347a7b32884eddb706b008ab9ba6278 /webhelper | |
parent | f2ae518dd264861746558970199def6d34325a4c (diff) |
Create a parallel version of WebHelper2 for WebKit2-3.0
We cannot switch eos-virtual-school to WebKit2-4.0 because of regressions,
but we already committed to using WebHelper2 to localize it. This adds a
version of WebHelper2 that is compatible with WebKit2-3.0.
[endlessm/eos-sdk#3430]
Diffstat (limited to 'webhelper')
-rw-r--r-- | webhelper/webhelper2.js | 531 | ||||
-rw-r--r-- | webhelper/webhelper2_old.js | 11 | ||||
-rw-r--r-- | webhelper/webhelper_private/common.js | 530 |
3 files changed, 548 insertions, 524 deletions
diff --git a/webhelper/webhelper2.js b/webhelper/webhelper2.js index e781330..6b28b10 100644 --- a/webhelper/webhelper2.js +++ b/webhelper/webhelper2.js @@ -1,528 +1,11 @@ // Copyright 2015 Endless Mobile, Inc. -imports.gi.versions.WebKit2 = '4.0'; - -const Format = imports.format; -const Gio = imports.gi.Gio; -const GLib = imports.gi.GLib; -const GObject = imports.gi.GObject; -const Lang = imports.lang; -const WebHelper2Private = imports.gi.WebHelper2Private; -const WebKit2 = imports.gi.WebKit2; - -const Config = imports.webhelper_private.config; - -String.prototype.format = Format.format; - -const WH2_URI_SCHEME = 'webhelper'; -const WH2_LOCAL_FILE_SCHEME = 'local'; -const DBUS_WEBVIEW_EXPORT_PATH = '/com/endlessm/webview/'; -const WH2_DBUS_EXTENSION_INTERFACE = '\ - <node> \ - <interface name="com.endlessm.WebHelper2.Translation"> \ - <method name="Translate"/> \ - </interface> \ - </node>'; -const WH2_DBUS_MAIN_PROGRAM_INTERFACE = '\ - <node> \ - <interface name="com.endlessm.WebHelper2.Gettext"> \ - <method name="Gettext"> \ - <arg name="message" type="s" direction="in"/> \ - <arg name="translation" type="s" direction="out"/> \ - </method> \ - <method name="NGettext"> \ - <arg name="message_singular" type="s" direction="in"/> \ - <arg name="message_plural" type="s" direction="in"/> \ - <arg name="number" type="t" direction="in"/> \ - <arg name="translation" type="s" direction="out"/> \ - </method> \ - </interface> \ - </node>'; - -/** - * Namespace: WebHelper2 - * Convenience library for running web applications - * - * WebHelper is a convenience library for displaying web applications inside a - * GTK container running WebKitGTK. - * WebHelper2 is the WebKit2 version. - * - * Although WebKit provides an easy way of communicating from GTK code to - * the in-browser Javascript, through the execute_script() method, it is not so - * easy to communicate the other way around. - * - * WebHelper solves that problem by detecting when the web page navigates to a - * custom action URI. - * The custom URI corresponds to a function that you define using - * <WebHelper.define_web_action()>, and you can pass parameters to the - * function. - * - * Another often-encountered problem is localizing text through the same API as - * your main GTK program. - * WebHelper solves this problem by allowing you to mark strings in your HTML - * page and translating them through a function of your choice when you run - * <WebHelper.translate_html()>. - * It also exposes a *gettext()* function in the client-side Javascript. - * - * For cases where you need to load local files for your web applications, - * WebHelper also provides the local:// URI scheme. - * For this to work, you must also load your main page via the local:// URI - * scheme. - */ - -/** - * Class: WebHelper - * Helper object for a WebKit2 web application - * - * Constructor parameters: - * props - a dictionary of construction properties and values (default {}) - * - * The application class for your web application should create <WebHelper> in - * its *vfunc_dbus_register()* implementation, with appropriate - * <well-known-name> and <connection> parameters. - * After that, you can do further setup on it, such as defining web actions, in - * your *vfunc_startup()* implementation. - * - * There is no need to set up specially any web views that you create, unlike - * WebKit1 where you must set up <Application.web_actions_handler()>. - * - * Example: - * > const TestApplication = new Lang.Class({ - * > Name: 'TestApplication', - * > Extends: Gtk.Application, - * > - * > vfunc_dbus_register: function (connection, object_path) { - * > this._webhelper = new WebHelper2.WebHelper({ - * > well_known_name: this.application_id, - * > connection: connection, - * > }); - * > return this.parent(connection, object_path); - * > }, - * > - * > vfunc_startup: function () { - * > this.parent(); - * > - * > this._webhelper.set_gettext(Gettext.dgettext.bind(null, - * > GETTEXT_DOMAIN)); - * > - * > let window = new Gtk.Window(); - * > let webview = new WebKit2.WebView(); - * > webview.connect('load-changed', (webview, event) => { - * > if (event === WebKit2.LoadEvent.FINISHED) - * > this._webhelper.translate_html(webview, null, (obj, res) => { - * > this._webhelper.translate_html_finish(res); - * > window.show_all(); - * > }); - * > }); - * > window.add(webview); - * > webview.load_uri('file:///path/to/my/page.html'); - * > }, - * > - * > vfunc_dbus_unregister: function (connection, object_path) { - * > this.parent(connection, object_path); - * > this._webhelper.unregister(); - * > }, - * >}); - * > - * >let app = new TestApplication({ - * > application_id: 'com.example.SmokeGrinder', - * >}); - * >app.run(ARGV); - */ -const WebHelper = new Lang.Class({ - Name: 'WebHelper', - GTypeName: 'Wh2WebHelper', - Extends: GObject.Object, - Properties: { - /** - * Property: well-known-name - * Well-known bus name owned by the calling program - * - * Type: - * string - * - * This property is required at construction time. - * It must conform to <the rules for well-known bus names at - * http://dbus.freedesktop.org/doc/dbus-specification.html#message-protocol-names>. - * - * This must be a well-known bus name that your program owns. - * The easiest way to ensure that is to use your application's ID, since - * every application class registers its ID as a bus name. - */ - 'well-known-name': GObject.ParamSpec.string('well-known-name', - 'Well-known name', - 'Well-known bus name owned by the calling program', - GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY, - ''), - /** - * Property: connection - * DBus connection owned by the calling program - * - * Type: - * *Gio.DBusConnection* - * - * This property is required at construction time. - * - * This must be a DBus connection object that your program owns. - * The easiest way to ensure that is to use the connection object passed - * in to your application's *vfunc_dbus_register()* function. - */ - 'connection': GObject.ParamSpec.object('connection', 'Connection', - 'DBus connection owned by the calling program', - GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY, - Gio.DBusConnection.$gtype), - }, - - _init: function (props={}) { - this._web_actions = {}; - this._gettext = null; - this._ngettext = null; - this._ProxyConstructor = - Gio.DBusProxy.makeProxyWrapper(WH2_DBUS_EXTENSION_INTERFACE); - this.parent(props); - - if (this.well_known_name === '') - throw new Error('The "well-known-name" parameter is required.'); - this._extension_name = this.well_known_name + '.webhelper'; - - // Set up Webkit to load our web extension - let context = WebKit2.WebContext.get_default(); - context.connect('initialize-web-extensions', () => { - let libexec = Gio.File.new_for_path(Config.LIBEXECDIR); - let path = libexec.get_child('webhelper2').get_path(); - - let localpath = GLib.getenv('WEBHELPER_UNINSTALLED_EXTENSION_DIR'); - if (localpath) - path = localpath; - - context.set_web_extensions_directory(path); - context.set_web_extensions_initialization_user_data(new GLib.Variant('s', - this.well_known_name)); - }); - - // Export our own DBus interface - this._dbus_impl = - Gio.DBusExportedObject.wrapJSObject(WH2_DBUS_MAIN_PROGRAM_INTERFACE, - this); - this._dbus_impl.export(this.connection, '/com/endlessm/gettext'); - - // Set up handling for custom URIs - WebHelper2Private.register_uri_scheme(WH2_URI_SCHEME, - this._on_endless_uri_request.bind(this)); - WebHelper2Private.register_uri_scheme(WH2_LOCAL_FILE_SCHEME, - this._on_local_file_request.bind(this)); - }, - - _on_endless_uri_request: function (request) { - let uri = request.get_uri(); - - // get the name and parameters for the desired function - let f_call = uri.substr((WH2_URI_SCHEME + '://').length).split('?'); - let function_name = decodeURI(f_call[0]); - - if (!this._web_actions.hasOwnProperty(function_name)) - throw new Error(('Undefined WebHelper action "%s". Did you define it with ' + - 'WebHelper.Application.define_web_action()?').format(function_name)); - - let parameters = {}; - if (f_call[1]) { - // there are parameters - let params = f_call[1].split('&'); - params.forEach(function (entry) { - let param = entry.split('='); +/* exported WebHelper */ - if (param.length == 2) { - param[0] = decodeURIComponent(param[0]); - param[1] = decodeURIComponent(param[1]); - // and now we add it... - parameters[param[0]] = param[1]; - } - }); - } - - (this._web_actions[function_name].bind(this))(parameters); - - // Don't call request.finish(), because we don't want to finish the - // action, which would involve loading a new page. The request dies - // if we return from this function without calling ref() or finish() - // on it. - }, - - _on_local_file_request: function (request) { - let path = request.get_path(); - let file = Gio.File.new_for_path(path); - let [content_type, certain] = Gio.content_type_guess(path, null); - try { - let stream = file.read(null); - request.finish(stream, -1, content_type); - } catch (error) { - request.finish_error(error); - } - }, - - // DBus implementations - - Gettext: function (string) { - return this._gettext(string); - }, - - NGettext: function (singular, plural, number) { - return this._ngettext(singular, plural, number); - }, - - // Public API - - /** - * Method: set_gettext - * Define function which translates text - * - * Parameters: - * gettext_func - a function, or null - * - * When you plan to translate text in your web application, set this - * property to the translation function. - * The function must take one parameter, a string, and also return a - * string. - * The canonical example is gettext(). - * - * This function will be called with each string to translate when you call - * <translate_html()>. - * The function is also made available directly to the browser-side - * Javascript as *gettext()*, a property of the global object. - * - * Pass null for _gettext_func_ to unset the translation function. - * - * If this function has not been called, or has most recently been called - * with null, then it is illegal to call <translate_html()>. - * - * Example: - * > const Gettext = imports.gettext; - * > const GETTEXT_DOMAIN = 'smoke-grinder'; - * > - * > webhelper.set_gettext(Gettext.dgettext.bind(null, GETTEXT_DOMAIN)); - */ - set_gettext: function (gettext_func) { - if (gettext_func !== null && typeof gettext_func !== 'function') - throw new Error('The translation function must be a function, or ' + - 'null.'); - this._gettext = gettext_func; - }, - - /** - * Method: get_gettext - * Retrieve the currently set translation function - * - * Returns: - * the translation function previously set with <set_gettext()>, or null - * if none is currently set. - */ - get_gettext: function () { - return this._gettext; - }, - - /** - * Method: set_ngettext - * Define function which translates singular/plural text - * - * Parameters: - * ngettext_func - a function, or null - * - * When you plan to translate text in your web application, set this - * property to the translation function. - * The function must take three parameters: a string singular message, a - * string plural message, and a number for which to generate the message. - * The function must return a string. - * The canonical example is ngettext(). - * - * This function is made available directly to the browser-side Javascript - * as *ngettext()*, a property of the global object. - * - * Pass null for _ngettext_func_ to unset the translation function. - * - * If this function has not been called, or has most recently been called - * with null, then it is illegal to call *ngettext()* in the client-side - * Javascript. - * - * Example: - * > const WebHelper2 = imports.webhelper2; - * > const Gettext = imports.gettext; - * > const GETTEXT_DOMAIN = 'smoke-grinder'; - * > - * > let webhelper = new WebHelper2.WebHelper('com.example.SmokeGrinder'); - * > webhelper.set_gettext(Gettext.dngettext.bind(null, GETTEXT_DOMAIN)); - */ - set_ngettext: function (ngettext_func) { - if (ngettext_func !== null && typeof ngettext_func !== 'function') - throw new Error('The translation function must be a function, or ' + - 'null.'); - this._ngettext = ngettext_func; - }, - - /** - * Method: get_ngettext - * Retrieve the currently set singular/plural translation function - * - * Returns: - * the translation function previously set with <set_ngettext()>, or null - * if none is currently set. - */ - get_ngettext: function () { - return this._ngettext; - }, - - /** - * Method: translate_html - * Translate the HTML page in a webview asynchronously - * - * Parameters: - * webview - a *WebKit2.WebView* with HTML loaded - * cancellable - a *Gio.Cancellable*, or null - * callback - a function that takes two parameters: this <WebHelper> - * object, and a result object; or null if you don't want a callback - * - * Perform translation on all HTML elements marked with name="translatable" - * in the HTML document displaying in _webview_. - * The translation will be performed using the function you have set using - * <set_gettext()>. - * - * When the translation is finished, _callback_ will be called. - * You can get the result of the operation by calling - * <translate_html_finish()> with the _result_ object passed to _callback_. - * - * You can optionally pass _cancellable_ if you want to be able to cancel - * the operation. - * - * Example: - * > webview.connect('load-changed', (webview, event) => { - * > if (event === WebKit2.LoadEvent.FINISHED) - * > webhelper.translate_html(webview, null, (obj, res) => { - * > webhelper.translate_html_finish(res); - * > // Translation done, show the page - * > webview.show_all(); - * > }); - * > }); - */ - translate_html: function (webview, cancellable, callback) { - let task = { callback: callback }; - // Wait for the DBus interface to appear on the bus - task.watch_id = Gio.DBus.watch_name(Gio.BusType.SESSION, - this._extension_name, Gio.BusNameWatcherFlags.NONE, - (connection, name, owner) => { - // name appeared - let webview_object_path = DBUS_WEBVIEW_EXPORT_PATH + - webview.get_page_id(); - // Warning: this._ProxyConstructor will do a synchronous - // operation unless you pass in a callback - new this._ProxyConstructor(connection, this._extension_name, - webview_object_path, (proxy, error) => - { - if (error) { - this._translate_callback(task, null, error); - return; - } - if (cancellable) - proxy.TranslateRemote(cancellable, - this._translate_callback.bind(this, task)); - else - proxy.TranslateRemote(this._translate_callback.bind(this, - task)); - }, cancellable); - }, - null); // do nothing when name vanishes - return task; - }, - - _translate_callback: function (task, result, error) { - Gio.DBus.unwatch_name(task.watch_id); - if (error) - task.error = error; - if (task.callback) - task.callback(this, task); - }, - - /** - * Method: translate_html_finish - * Get the result of <translate_html()> - * - * Parameters: - * res - result object passed to your callback - * - * Finishes an operation started by <translate_html()>. - * If the operation ended in an error, this function will throw that error. - */ - translate_html_finish: function (res) { - if (res.error) - throw res.error; - }, - - /** - * Method: define_web_action - * Define an action that may be invoked from a WebView - * - * Parameters: - * name - a string, which must be a valid URI location. - * implementation - a function (see Callback Parameters below.) - * - * Callback Parameters: - * dict - object containing properties corresponding to the query - * parameters that the web action was called with - * - * Sets up an action that may be invoked from an HTML document inside a - * WebView, or from the in-browser Javascript environment inside a WebView. - * If you set up an action "setVolume" as follows: - * > app.define_web_action('setVolume', function(dict) { ... }); - * Then you can invoke the action inside the HTML document, e.g. as the - * target of a link, as follows: - * > <a href="endless://setVolume?volume=11">This one goes to 11</a> - * Or from the in-browser Javascript, by navigating to the action URI, as - * follows: - * > window.location.href = 'endless://setVolume?volume=11'; - * - * In both cases, the function would then be called with the _dict_ - * parameter equal to - * > { "volume": "11" } - * - * If an action called _name_ is already defined, the new _implementation_ - * replaces the old one. - */ - define_web_action: function (name, implementation) { - if (typeof implementation !== 'function') { - throw new Error('The implementation of a web action must be a ' + - 'function.'); - } - this._web_actions[name] = implementation; - }, - - /** - * Method: define_web_actions - * Define several web actions at once - * - * Parameters: - * dict - an object, with web action names as property names, and their - * implementations as values - * - * Convenience method to define more than one web action at once. - * Calls <define_web_action()> on each property of _dict_. - * - * *Note* This API is Javascript-only. It will not be implemented in C. - */ - define_web_actions: function (dict) { - Object.keys(dict).forEach((key) => - this.define_web_action(key, dict[key])); - }, +imports.gi.versions.WebKit2 = '4.0'; +window.WebHelper2Private = imports.gi.WebHelper2Private; +window.WebKit2 = imports.gi.WebKit2; +window.LIBEXEC_SUBDIR = 'webhelper2'; - /** - * Method: unregister - * Break the connection to WebKit - * - * Breaks the connection to all webviews and removes all DBus objects. - * You should call this in your application's *vfunc_dbus_unregister()* - * implementation. - * - * After this function has been called, no WebHelper functionality will - * work. - */ - unregister: function () { - this._dbus_impl.unexport_from_connection(this.connection); - }, -}); +const Common = imports.webhelper_private.common; +const WebHelper = Common.WebHelper; diff --git a/webhelper/webhelper2_old.js b/webhelper/webhelper2_old.js new file mode 100644 index 0000000..809504e --- /dev/null +++ b/webhelper/webhelper2_old.js @@ -0,0 +1,11 @@ +// Copyright 2015 Endless Mobile, Inc. + +/* exported WebHelper */ + +imports.gi.versions.WebKit2 = '3.0'; +window.WebHelper2Private = imports.gi.WebHelper2OldPrivate; +window.WebKit2 = imports.gi.WebKit2; +window.LIBEXEC_SUBDIR = 'webhelper2old'; + +const Common = imports.webhelper_private.common; +const WebHelper = Common.WebHelper; diff --git a/webhelper/webhelper_private/common.js b/webhelper/webhelper_private/common.js new file mode 100644 index 0000000..4a93d53 --- /dev/null +++ b/webhelper/webhelper_private/common.js @@ -0,0 +1,530 @@ +// Copyright 2015 Endless Mobile, Inc. + +// The following constant and two modules must be in the global namespace before +// importing this file: LIBEXEC_SUBDIR, WebKit2, WebHelper2Private + +/* exported WebHelper */ + +const Format = imports.format; +const Gio = imports.gi.Gio; +const GLib = imports.gi.GLib; +const GObject = imports.gi.GObject; +const Lang = imports.lang; + +String.prototype.format = Format.format; + +const Config = imports.webhelper_private.config; + +const WH2_URI_SCHEME = 'webhelper'; +const WH2_LOCAL_FILE_SCHEME = 'local'; +const DBUS_WEBVIEW_EXPORT_PATH = '/com/endlessm/webview/'; +const WH2_DBUS_EXTENSION_INTERFACE = '\ + <node> \ + <interface name="com.endlessm.WebHelper2.Translation"> \ + <method name="Translate"/> \ + </interface> \ + </node>'; +const WH2_DBUS_MAIN_PROGRAM_INTERFACE = '\ + <node> \ + <interface name="com.endlessm.WebHelper2.Gettext"> \ + <method name="Gettext"> \ + <arg name="message" type="s" direction="in"/> \ + <arg name="translation" type="s" direction="out"/> \ + </method> \ + <method name="NGettext"> \ + <arg name="message_singular" type="s" direction="in"/> \ + <arg name="message_plural" type="s" direction="in"/> \ + <arg name="number" type="t" direction="in"/> \ + <arg name="translation" type="s" direction="out"/> \ + </method> \ + </interface> \ + </node>'; + + +/** + * Namespace: WebHelper2 + * Convenience library for running web applications + * + * WebHelper is a convenience library for displaying web applications inside a + * GTK container running WebKitGTK. + * WebHelper2 is the WebKit2 version. + * + * Although WebKit provides an easy way of communicating from GTK code to + * the in-browser Javascript, through the execute_script() method, it is not so + * easy to communicate the other way around. + * + * WebHelper solves that problem by detecting when the web page navigates to a + * custom action URI. + * The custom URI corresponds to a function that you define using + * <WebHelper.define_web_action()>, and you can pass parameters to the + * function. + * + * Another often-encountered problem is localizing text through the same API as + * your main GTK program. + * WebHelper solves this problem by allowing you to mark strings in your HTML + * page and translating them through a function of your choice when you run + * <WebHelper.translate_html()>. + * It also exposes a *gettext()* function in the client-side Javascript. + * + * For cases where you need to load local files for your web applications, + * WebHelper also provides the local:// URI scheme. + * For this to work, you must also load your main page via the local:// URI + * scheme. + */ + +/** + * Class: WebHelper + * Helper object for a WebKit2 web application + * + * Constructor parameters: + * props - a dictionary of construction properties and values (default {}) + * + * The application class for your web application should create <WebHelper> in + * its *vfunc_dbus_register()* implementation, with appropriate + * <well-known-name> and <connection> parameters. + * After that, you can do further setup on it, such as defining web actions, in + * your *vfunc_startup()* implementation. + * + * There is no need to set up specially any web views that you create, unlike + * WebKit1 where you must set up <Application.web_actions_handler()>. + * + * Example: + * > const TestApplication = new Lang.Class({ + * > Name: 'TestApplication', + * > Extends: Gtk.Application, + * > + * > vfunc_dbus_register: function (connection, object_path) { + * > this._webhelper = new WebHelper2.WebHelper({ + * > well_known_name: this.application_id, + * > connection: connection, + * > }); + * > return this.parent(connection, object_path); + * > }, + * > + * > vfunc_startup: function () { + * > this.parent(); + * > + * > this._webhelper.set_gettext(Gettext.dgettext.bind(null, + * > GETTEXT_DOMAIN)); + * > + * > let window = new Gtk.Window(); + * > let webview = new WebKit2.WebView(); + * > webview.connect('load-changed', (webview, event) => { + * > if (event === WebKit2.LoadEvent.FINISHED) + * > this._webhelper.translate_html(webview, null, (obj, res) => { + * > this._webhelper.translate_html_finish(res); + * > window.show_all(); + * > }); + * > }); + * > window.add(webview); + * > webview.load_uri('file:///path/to/my/page.html'); + * > }, + * > + * > vfunc_dbus_unregister: function (connection, object_path) { + * > this.parent(connection, object_path); + * > this._webhelper.unregister(); + * > }, + * >}); + * > + * >let app = new TestApplication({ + * > application_id: 'com.example.SmokeGrinder', + * >}); + * >app.run(ARGV); + */ +const WebHelper = new Lang.Class({ + Name: 'WebHelper', + GTypeName: 'Wh2WebHelper', + Extends: GObject.Object, + Properties: { + /** + * Property: well-known-name + * Well-known bus name owned by the calling program + * + * Type: + * string + * + * This property is required at construction time. + * It must conform to <the rules for well-known bus names at + * http://dbus.freedesktop.org/doc/dbus-specification.html#message-protocol-names>. + * + * This must be a well-known bus name that your program owns. + * The easiest way to ensure that is to use your application's ID, since + * every application class registers its ID as a bus name. + */ + 'well-known-name': GObject.ParamSpec.string('well-known-name', + 'Well-known name', + 'Well-known bus name owned by the calling program', + GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY, + ''), + /** + * Property: connection + * DBus connection owned by the calling program + * + * Type: + * *Gio.DBusConnection* + * + * This property is required at construction time. + * + * This must be a DBus connection object that your program owns. + * The easiest way to ensure that is to use the connection object passed + * in to your application's *vfunc_dbus_register()* function. + */ + 'connection': GObject.ParamSpec.object('connection', 'Connection', + 'DBus connection owned by the calling program', + GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY, + Gio.DBusConnection.$gtype), + }, + + _init: function (props={}) { + this._web_actions = {}; + this._gettext = null; + this._ngettext = null; + this._ProxyConstructor = + Gio.DBusProxy.makeProxyWrapper(WH2_DBUS_EXTENSION_INTERFACE); + this.parent(props); + + if (this.well_known_name === '') + throw new Error('The "well-known-name" parameter is required.'); + this._extension_name = this.well_known_name + '.webhelper'; + + // Set up Webkit to load our web extension + let context = window.WebKit2.WebContext.get_default(); + context.connect('initialize-web-extensions', () => { + let libexec = Gio.File.new_for_path(Config.LIBEXECDIR); + let path = libexec.get_child(window.LIBEXEC_SUBDIR).get_path(); + + let localpath = GLib.getenv('WEBHELPER_UNINSTALLED_EXTENSION_DIR'); + if (localpath) + path = localpath; + + context.set_web_extensions_directory(path); + context.set_web_extensions_initialization_user_data(new GLib.Variant('s', + this.well_known_name)); + }); + + // Export our own DBus interface + this._dbus_impl = + Gio.DBusExportedObject.wrapJSObject(WH2_DBUS_MAIN_PROGRAM_INTERFACE, + this); + this._dbus_impl.export(this.connection, '/com/endlessm/gettext'); + + // Set up handling for custom URIs + window.WebHelper2Private.register_uri_scheme(WH2_URI_SCHEME, + this._on_endless_uri_request.bind(this)); + window.WebHelper2Private.register_uri_scheme(WH2_LOCAL_FILE_SCHEME, + this._on_local_file_request.bind(this)); + }, + + _on_endless_uri_request: function (request) { + let uri = request.get_uri(); + + // get the name and parameters for the desired function + let f_call = uri.substr((WH2_URI_SCHEME + '://').length).split('?'); + let function_name = decodeURI(f_call[0]); + + if (!this._web_actions.hasOwnProperty(function_name)) + throw new Error(('Undefined WebHelper action "%s". Did you define it with ' + + 'WebHelper.Application.define_web_action()?').format(function_name)); + + let parameters = {}; + if (f_call[1]) { + // there are parameters + let params = f_call[1].split('&'); + params.forEach(function (entry) { + let param = entry.split('='); + + if (param.length == 2) { + param[0] = decodeURIComponent(param[0]); + param[1] = decodeURIComponent(param[1]); + // and now we add it... + parameters[param[0]] = param[1]; + } + }); + } + + (this._web_actions[function_name].bind(this))(parameters); + + // Don't call request.finish(), because we don't want to finish the + // action, which would involve loading a new page. The request dies + // if we return from this function without calling ref() or finish() + // on it. + }, + + _on_local_file_request: function (request) { + let path = request.get_path(); + let file = Gio.File.new_for_path(path); + let [content_type, certain] = Gio.content_type_guess(path, null); + try { + let stream = file.read(null); + request.finish(stream, -1, content_type); + } catch (error) { + request.finish_error(error); + } + }, + + // DBus implementations + + Gettext: function (string) { + return this._gettext(string); + }, + + NGettext: function (singular, plural, number) { + return this._ngettext(singular, plural, number); + }, + + // Public API + + /** + * Method: set_gettext + * Define function which translates text + * + * Parameters: + * gettext_func - a function, or null + * + * When you plan to translate text in your web application, set this + * property to the translation function. + * The function must take one parameter, a string, and also return a + * string. + * The canonical example is gettext(). + * + * This function will be called with each string to translate when you call + * <translate_html()>. + * The function is also made available directly to the browser-side + * Javascript as *gettext()*, a property of the global object. + * + * Pass null for _gettext_func_ to unset the translation function. + * + * If this function has not been called, or has most recently been called + * with null, then it is illegal to call <translate_html()>. + * + * Example: + * > const Gettext = imports.gettext; + * > const GETTEXT_DOMAIN = 'smoke-grinder'; + * > + * > webhelper.set_gettext(Gettext.dgettext.bind(null, GETTEXT_DOMAIN)); + */ + set_gettext: function (gettext_func) { + if (gettext_func !== null && typeof gettext_func !== 'function') + throw new Error('The translation function must be a function, or ' + + 'null.'); + this._gettext = gettext_func; + }, + + /** + * Method: get_gettext + * Retrieve the currently set translation function + * + * Returns: + * the translation function previously set with <set_gettext()>, or null + * if none is currently set. + */ + get_gettext: function () { + return this._gettext; + }, + + /** + * Method: set_ngettext + * Define function which translates singular/plural text + * + * Parameters: + * ngettext_func - a function, or null + * + * When you plan to translate text in your web application, set this + * property to the translation function. + * The function must take three parameters: a string singular message, a + * string plural message, and a number for which to generate the message. + * The function must return a string. + * The canonical example is ngettext(). + * + * This function is made available directly to the browser-side Javascript + * as *ngettext()*, a property of the global object. + * + * Pass null for _ngettext_func_ to unset the translation function. + * + * If this function has not been called, or has most recently been called + * with null, then it is illegal to call *ngettext()* in the client-side + * Javascript. + * + * Example: + * > const WebHelper2 = imports.webhelper2; + * > const Gettext = imports.gettext; + * > const GETTEXT_DOMAIN = 'smoke-grinder'; + * > + * > let webhelper = new WebHelper2.WebHelper('com.example.SmokeGrinder'); + * > webhelper.set_gettext(Gettext.dngettext.bind(null, GETTEXT_DOMAIN)); + */ + set_ngettext: function (ngettext_func) { + if (ngettext_func !== null && typeof ngettext_func !== 'function') + throw new Error('The translation function must be a function, or ' + + 'null.'); + this._ngettext = ngettext_func; + }, + + /** + * Method: get_ngettext + * Retrieve the currently set singular/plural translation function + * + * Returns: + * the translation function previously set with <set_ngettext()>, or null + * if none is currently set. + */ + get_ngettext: function () { + return this._ngettext; + }, + + /** + * Method: translate_html + * Translate the HTML page in a webview asynchronously + * + * Parameters: + * webview - a *WebKit2.WebView* with HTML loaded + * cancellable - a *Gio.Cancellable*, or null + * callback - a function that takes two parameters: this <WebHelper> + * object, and a result object; or null if you don't want a callback + * + * Perform translation on all HTML elements marked with name="translatable" + * in the HTML document displaying in _webview_. + * The translation will be performed using the function you have set using + * <set_gettext()>. + * + * When the translation is finished, _callback_ will be called. + * You can get the result of the operation by calling + * <translate_html_finish()> with the _result_ object passed to _callback_. + * + * You can optionally pass _cancellable_ if you want to be able to cancel + * the operation. + * + * Example: + * > webview.connect('load-changed', (webview, event) => { + * > if (event === WebKit2.LoadEvent.FINISHED) + * > webhelper.translate_html(webview, null, (obj, res) => { + * > webhelper.translate_html_finish(res); + * > // Translation done, show the page + * > webview.show_all(); + * > }); + * > }); + */ + translate_html: function (webview, cancellable, callback) { + let task = { callback: callback }; + // Wait for the DBus interface to appear on the bus + task.watch_id = Gio.DBus.watch_name(Gio.BusType.SESSION, + this._extension_name, Gio.BusNameWatcherFlags.NONE, + (connection) => { + // name appeared + let webview_object_path = DBUS_WEBVIEW_EXPORT_PATH + + webview.get_page_id(); + // Warning: this._ProxyConstructor will do a synchronous + // operation unless you pass in a callback + new this._ProxyConstructor(connection, this._extension_name, + webview_object_path, (proxy, error) => + { + if (error) { + this._translate_callback(task, null, error); + return; + } + if (cancellable) + proxy.TranslateRemote(cancellable, + this._translate_callback.bind(this, task)); + else + proxy.TranslateRemote(this._translate_callback.bind(this, + task)); + }, cancellable); + }, + null); // do nothing when name vanishes + return task; + }, + + _translate_callback: function (task, result, error) { + Gio.DBus.unwatch_name(task.watch_id); + if (error) + task.error = error; + if (task.callback) + task.callback(this, task); + }, + + /** + * Method: translate_html_finish + * Get the result of <translate_html()> + * + * Parameters: + * res - result object passed to your callback + * + * Finishes an operation started by <translate_html()>. + * If the operation ended in an error, this function will throw that error. + */ + translate_html_finish: function (res) { + if (res.error) + throw res.error; + }, + + /** + * Method: define_web_action + * Define an action that may be invoked from a WebView + * + * Parameters: + * name - a string, which must be a valid URI location. + * implementation - a function (see Callback Parameters below.) + * + * Callback Parameters: + * dict - object containing properties corresponding to the query + * parameters that the web action was called with + * + * Sets up an action that may be invoked from an HTML document inside a + * WebView, or from the in-browser Javascript environment inside a WebView. + * If you set up an action "setVolume" as follows: + * > app.define_web_action('setVolume', function(dict) { ... }); + * Then you can invoke the action inside the HTML document, e.g. as the + * target of a link, as follows: + * > <a href="endless://setVolume?volume=11">This one goes to 11</a> + * Or from the in-browser Javascript, by navigating to the action URI, as + * follows: + * > window.location.href = 'endless://setVolume?volume=11'; + * + * In both cases, the function would then be called with the _dict_ + * parameter equal to + * > { "volume": "11" } + * + * If an action called _name_ is already defined, the new _implementation_ + * replaces the old one. + */ + define_web_action: function (name, implementation) { + if (typeof implementation !== 'function') { + throw new Error('The implementation of a web action must be a ' + + 'function.'); + } + this._web_actions[name] = implementation; + }, + + /** + * Method: define_web_actions + * Define several web actions at once + * + * Parameters: + * dict - an object, with web action names as property names, and their + * implementations as values + * + * Convenience method to define more than one web action at once. + * Calls <define_web_action()> on each property of _dict_. + * + * *Note* This API is Javascript-only. It will not be implemented in C. + */ + define_web_actions: function (dict) { + Object.keys(dict).forEach((key) => + this.define_web_action(key, dict[key])); + }, + + /** + * Method: unregister + * Break the connection to WebKit + * + * Breaks the connection to all webviews and removes all DBus objects. + * You should call this in your application's *vfunc_dbus_unregister()* + * implementation. + * + * After this function has been called, no WebHelper functionality will + * work. + */ + unregister: function () { + this._dbus_impl.unexport_from_connection(this.connection); + }, +}); |