diff options
-rw-r--r-- | .gitignore | 2 | ||||
-rw-r--r-- | Makefile.am | 35 | ||||
-rw-r--r-- | configure.ac | 9 | ||||
-rw-r--r-- | jasmine.json | 4 | ||||
-rw-r--r-- | test/Makefile.am.inc | 2 | ||||
-rw-r--r-- | test/webhelper/testTranslate2Old.js | 213 | ||||
-rw-r--r-- | test/webhelper/testWebActions2Old.js | 120 | ||||
-rw-r--r-- | webhelper/webhelper2.js | 532 | ||||
-rw-r--r-- | webhelper/webhelper2_old.js | 11 | ||||
-rw-r--r-- | webhelper/webhelper_private/common.js | 530 |
10 files changed, 526 insertions, 932 deletions
@@ -8,8 +8,6 @@ Endless-0.gir Endless-0.typelib WebHelper2Private-1.0.gir WebHelper2Private-1.0.typelib -WebHelper2OldPrivate-1.0.gir -WebHelper2OldPrivate-1.0.typelib endless/eosresource.c endless/eosresource-private.h endless/eosversion.h diff --git a/Makefile.am b/Makefile.am index 8638a1f..52f610d 100644 --- a/Makefile.am +++ b/Makefile.am @@ -91,7 +91,6 @@ DISTCLEANFILES += @EOS_SDK_API_NAME@.pc webhelper_sources = \ webhelper/webhelper.js \ webhelper/webhelper2.js \ - webhelper/webhelper2_old.js \ $(NULL) gjsmodulesdir = $(datadir)/gjs-1.0 @@ -101,7 +100,6 @@ dist_webhelper_DATA = \ $(webhelper_sources) \ $(NULL) dist_webhelper_private_DATA = \ - webhelper/webhelper_private/common.js \ webhelper/webhelper_private/config.js \ $(NULL) @@ -130,28 +128,6 @@ wh2extension_la_CPPFLAGS = @WEBHELPER2_EXTENSION_CFLAGS@ wh2extension_la_LIBADD = @WEBHELPER2_EXTENSION_LIBS@ wh2extension_la_LDFLAGS = -module -avoid-version -no-undefined -# WebKit2-3.0 version - -lib_LTLIBRARIES += libwebhelper2oldprivate.la -libwebhelper2oldprivate_la_SOURCES = \ - webhelper/lib/wh2private.c \ - webhelper/lib/wh2private.h \ - $(NULL) -libwebhelper2oldprivate_la_CPPFLAGS = @WEBHELPER2_OLD_PRIVATE_CFLAGS@ -libwebhelper2oldprivate_la_LIBADD = @WEBHELPER2_OLD_PRIVATE_LIBS@ -libwebhelper2oldprivate_la_LDFLAGS = -avoid-version - -webhelper2oldextensionsdir = $(libexecdir)/webhelper2old -webhelper2oldextensions_LTLIBRARIES = wh2oldextension.la -wh2oldextension_la_SOURCES = \ - webhelper/webextensions/wh2extension.c \ - webhelper/webextensions/wh2jscutil.c \ - webhelper/webextensions/wh2jscutil.h \ - $(NULL) -wh2oldextension_la_CPPFLAGS = @WEBHELPER2_OLD_EXTENSION_CFLAGS@ -wh2oldextension_la_LIBADD = @WEBHELPER2_OLD_EXTENSION_LIBS@ -wh2oldextension_la_LDFLAGS = -module -avoid-version -no-undefined - # # # INTROSPECTION FILES # # # -include $(INTROSPECTION_MAKEFILE) @@ -187,16 +163,6 @@ WebHelper2Private_1_0_gir_LIBS = libwebhelper2private.la WebHelper2Private_1_0_gir_FILES = $(libwebhelper2private_la_SOURCES) INTROSPECTION_GIRS += WebHelper2Private-1.0.gir -WebHelper2OldPrivate-1.0.gir: libwebhelper2oldprivate.la -WebHelper2OldPrivate_1_0_gir_INCLUDES = GObject-2.0 GLib-2.0 WebKit2-3.0 -WebHelper2OldPrivate_1_0_gir_SCANNERFLAGS = \ - --identifier-prefix=Wh2 \ - --symbol-prefix=wh2 \ - $(NULL) -WebHelper2OldPrivate_1_0_gir_LIBS = libwebhelper2oldprivate.la -WebHelper2OldPrivate_1_0_gir_FILES = $(libwebhelper2oldprivate_la_SOURCES) -INTROSPECTION_GIRS += WebHelper2OldPrivate-1.0.gir - girdir = $(datadir)/gir-1.0 gir_DATA = $(INTROSPECTION_GIRS) @@ -412,7 +378,6 @@ AM_JS_LOG_FLAGS += @EOS_JS_COVERAGE_LOG_FLAGS@ AM_CFLAGS += @EOS_C_COVERAGE_CFLAGS@ AM_LDFLAGS = @EOS_C_COVERAGE_LDFLAGS@ libwebhelper2private_la_LDFLAGS += @EOS_C_COVERAGE_LDFLAGS@ -libwebhelper2oldprivate_la_LDFLAGS += @EOS_C_COVERAGE_LDFLAGS@ wh2extension_la_LDFLAGS += @EOS_C_COVERAGE_LDFLAGS@ clean-local:: clean-coverage diff --git a/configure.ac b/configure.ac index 8aa55f1..3e17d00 100644 --- a/configure.ac +++ b/configure.ac @@ -82,7 +82,6 @@ GIO_REQUIREMENT="gio-2.0" GTK_REQUIREMENT="gtk+-3.0 >= 3.16" JSON_GLIB_REQUIREMENT="json-glib-1.0 >= 0.12" WEBKIT2_REQUIREMENT="webkit2gtk-4.0" -WEBKIT2_OLD_REQUIREMENT="webkit2gtk-3.0" # These go into the pkg-config file as Requires: and Requires.private: # (Generally, use Requires.private: instead of Requires:) EOS_REQUIRED_MODULES= @@ -223,18 +222,10 @@ PKG_CHECK_MODULES([WEBHELPER2_EXTENSION], [ PKG_CHECK_MODULES([WEBHELPER2_PRIVATE], [ $GLIB_REQUIREMENT $WEBKIT2_REQUIREMENT]) -PKG_CHECK_MODULES([WEBHELPER2_OLD_EXTENSION], [ - $GLIB_REQUIREMENT - $GOBJECT_REQUIREMENT - $WEBKIT2_OLD_REQUIREMENT]) -PKG_CHECK_MODULES([WEBHELPER2_OLD_PRIVATE], [ - $GLIB_REQUIREMENT - $WEBKIT2_OLD_REQUIREMENT]) # Check installed GIRs for webhelper JS module EOS_CHECK_GJS_GIR([GLib], [2.0]) EOS_CHECK_GJS_GIR([WebKit], [3.0]) -EOS_CHECK_GJS_GIR([WebKit2], [3.0]) EOS_CHECK_GJS_GIR([WebKit2], [4.0]) # Code coverage reports support diff --git a/jasmine.json b/jasmine.json index 7e2e39f..440dca1 100644 --- a/jasmine.json +++ b/jasmine.json @@ -10,10 +10,8 @@ ], "exclude": [ "test/webhelper/testTranslate.js", - "test/webhelper/testTranslate2Old.js", "test/webhelper/testUpdateFontSize.js", - "test/webhelper/testWebActions.js", - "test/webhelper/testWebActions2Old.js" + "test/webhelper/testWebActions.js" ], "environment": { "GI_TYPELIB_PATH": ".", diff --git a/test/Makefile.am.inc b/test/Makefile.am.inc index 0fad7a2..727a5df 100644 --- a/test/Makefile.am.inc +++ b/test/Makefile.am.inc @@ -47,10 +47,8 @@ javascript_tests = \ test/webhelper/testLocal.js \ test/webhelper/testTranslate.js \ test/webhelper/testTranslate2.js \ - test/webhelper/testTranslate2Old.js \ test/webhelper/testWebActions.js \ test/webhelper/testWebActions2.js \ - test/webhelper/testWebActions2Old.js \ test/webhelper/testUpdateFontSize.js \ test/endless/testCustomContainer.js \ test/endless/testTopbarHomeButton.js \ diff --git a/test/webhelper/testTranslate2Old.js b/test/webhelper/testTranslate2Old.js deleted file mode 100644 index 66d54ce..0000000 --- a/test/webhelper/testTranslate2Old.js +++ /dev/null @@ -1,213 +0,0 @@ -// Copyright 2015 Endless Mobile, Inc. - -const Gio = imports.gi.Gio; -const Gtk = imports.gi.Gtk; -const Mainloop = imports.mainloop; -const WebHelper2 = imports.webhelper2_old; -const WebKit2 = imports.gi.WebKit2; - -const WELL_KNOWN_NAME = 'com.endlessm.WebHelper.testTranslate2'; - -/* CAUTION: - * All tests trying to use the translation functionality of WebHelper2 must be - * run in this file, and this file must be run before any other WebHelper2 - * tests in the same process. - * That is because we can only tell the default web context to load web - * extensions with user data once per process. WebHelper doesn't support web - * contexts other than the default one. - */ - -Gtk.init(null); - -describe('WebHelper2 WebKit2-3.0 translator', function () { - let webhelper, owner_id, connection; - - beforeAll(function (done) { - owner_id = Gio.DBus.own_name(Gio.BusType.SESSION, WELL_KNOWN_NAME, - Gio.BusNameOwnerFlags.NONE, - null, // bus acquired - (con) => { // name acquired - connection = con; - done(); - }, - null); // name lost - }); - - afterAll(function () { - Gio.DBus.unown_name(owner_id); - }); - - beforeEach(function () { - webhelper = new WebHelper2.WebHelper({ - well_known_name: WELL_KNOWN_NAME, - connection: connection, - }); - }); - - afterEach(function () { - webhelper.unregister(); - }); - - it('complains about a bad gettext function', function () { - expect(function () { - webhelper.set_gettext('I am not a function'); - }).toThrow(); - }); - - it('gets and sets the gettext function', function () { - let translation_function = (s) => s; - webhelper.set_gettext(translation_function); - expect(webhelper.get_gettext()).toBe(translation_function); - }); - - it('has a null gettext function by default', function () { - expect(webhelper.get_gettext()).toBeNull(); - }); - - it('can remove the gettext function by setting null', function () { - webhelper.set_gettext((s) => s); - expect(webhelper.get_gettext()).not.toBeNull(); - webhelper.set_gettext(null); - expect(webhelper.get_gettext()).toBeNull(); - }); - - it('complains about a bad ngettext function', function () { - expect(function () { - webhelper.set_ngettext('I am not a function'); - }).toThrow(); - }); - - it('gets and sets the ngettext function', function () { - let translation_function = (s, p, n) => n == 1 ? s : p; - webhelper.set_ngettext(translation_function); - expect(webhelper.get_ngettext()).toBe(translation_function); - }); - - it('has a null ngettext function by default', function () { - expect(webhelper.get_ngettext()).toBeNull(); - }); - - it('can remove the ngettext function by setting null', function () { - webhelper.set_ngettext((s, p, n) => n == 1 ? s : p); - expect(webhelper.get_ngettext()).not.toBeNull(); - webhelper.set_ngettext(null); - expect(webhelper.get_ngettext()).toBeNull(); - }); - - describe('translating a page', function () { - let webview, gettext_spy; - const MINIMAL_HTML = '<p name="translatable">Translate Me</p>'; - - function run_loop(html=MINIMAL_HTML) { - let error_spy = jasmine.createSpy('error_spy'); - webview.connect('load-failed', error_spy); - let id = webview.connect('load-changed', (webview, event) => { - if (event === WebKit2.LoadEvent.FINISHED) { - webhelper.translate_html(webview, null, (obj, res) => { - expect(function () { - webhelper.translate_html_finish(res); - }).not.toThrow(); - webview.disconnect(id); - expect(error_spy).not.toHaveBeenCalled(); - Mainloop.quit('webhelper2'); - }); - } - }); - webview.load_html('<html><body>' + html + '</body></html>', null); - Mainloop.run('webhelper2'); - } - - beforeEach(function () { - webview = new WebKit2.WebView(); - gettext_spy = jasmine.createSpy('gettext_spy').and.callFake((s) => s); - webhelper.set_gettext(gettext_spy); - }); - - it('translates a string', function () { - run_loop(); - expect(gettext_spy).toHaveBeenCalledWith('Translate Me'); - }); - - // The following test is disabled because GJS cannot catch exceptions - // across FFI interfaces (e.g. in GObject callbacks.) - xit('complains about a gettext function not being set', function () { - expect(function () { - run_loop(); - }).toThrow(); - }); - - it('can cancel the translation operation', function (done) { - webhelper.set_gettext((s) => s); - let error_spy = jasmine.createSpy('error_spy'); - webview.connect('load-failed', error_spy); - let id = webview.connect('load-changed', (webview, event) => { - if (event === WebKit2.LoadEvent.FINISHED) { - let cancellable = new Gio.Cancellable(); - cancellable.cancel(); - webhelper.translate_html(webview, cancellable, (obj, res) => { - expect(function () { - webhelper.translate_html_finish(res); - }).toThrow(); - webview.disconnect(id); - expect(error_spy).not.toHaveBeenCalled(); - done(); - }); - } - }); - webview.load_html('<html><body></body></html>', null); - }); - - it('normalizes a string before translating it', function () { - run_loop('<p name="translatable">\n\ - Translate Me\n\ - </p>'); - expect(gettext_spy).toHaveBeenCalledWith('Translate Me'); - }); - - it('handles quotes correctly', function () { - run_loop('<p name="translatable">String with "quotes"</p>'); - expect(gettext_spy).toHaveBeenCalledWith('String with "quotes"'); - }); - - it('handles embedded tags correctly', function () { - run_loop('<p name="translatable">Embedded<br><b>tags</b></p>'); - expect(gettext_spy).toHaveBeenCalledWith('Embedded<br><b>tags</b>'); - }); - }); - - describe('used from client-side Javascript', function () { - let webview; - - beforeEach(function () { - webview = new WebKit2.WebView(); - }); - - function load_script(view, script) { - view.load_html('<html><body><script type="text/javascript">' + - script + '</script></body></html>', null); - Mainloop.run('webhelper2'); - } - - it('translates a string with gettext()', function (done) { - let gettext_spy = jasmine.createSpy('gettext_spy').and.callFake((s) => { - Mainloop.quit('webhelper2'); - return s; - }); - webhelper.set_gettext(gettext_spy); - load_script(webview, 'gettext("Translate Me");'); - expect(gettext_spy).toHaveBeenCalledWith('Translate Me'); - done(); - }); - - it('translates a string with ngettext()', function (done) { - let ngettext_spy = jasmine.createSpy('ngettext_spy').and.callFake((s, p, n) => { - Mainloop.quit('webhelper2'); - return n == 1 ? s : p; - }); - webhelper.set_ngettext(ngettext_spy); - load_script(webview, 'ngettext("File", "Files", 3);'); - expect(ngettext_spy).toHaveBeenCalledWith('File', 'Files', 3); - done(); - }); - }); -}); diff --git a/test/webhelper/testWebActions2Old.js b/test/webhelper/testWebActions2Old.js deleted file mode 100644 index a602dc1..0000000 --- a/test/webhelper/testWebActions2Old.js +++ /dev/null @@ -1,120 +0,0 @@ -const Gio = imports.gi.Gio; -const Gtk = imports.gi.Gtk; -const Mainloop = imports.mainloop; -const WebHelper2 = imports.webhelper2_old; -const WebKit2 = imports.gi.WebKit2; - -const WELL_KNOWN_NAME = 'com.endlessm.WebHelper.testWebActions2'; - -Gtk.init(null); - -describe('WebKit2-3.0 actions bindings', function () { - let owner_id, connection, webview, webhelper, web_action_spy; - - beforeAll(function (done) { - owner_id = Gio.DBus.own_name(Gio.BusType.SESSION, WELL_KNOWN_NAME, - Gio.BusNameOwnerFlags.NONE, - null, // bus acquired - (con) => { // name acquired - connection = con; - done(); - }, - null); // name lost - }); - - afterAll(function () { - Gio.DBus.unown_name(owner_id); - }); - - function run_loop(action_to_test) { - let string = '<html><head><meta http-equiv="refresh" content="0;url=' + - action_to_test + '"></head><body></body></html>'; - webview.load_html(string, null); - Mainloop.run('webhelper2'); - } - - beforeEach(function () { - webhelper = new WebHelper2.WebHelper({ - well_known_name: WELL_KNOWN_NAME, - connection: connection, - }); - webview = new WebKit2.WebView(); - web_action_spy = jasmine.createSpy('web_action_spy').and.callFake(() => - Mainloop.quit('webhelper2')); - webhelper.define_web_action('action', web_action_spy); - }); - - afterEach(function () { - webhelper.unregister(); - }); - - it('calls a web action', function () { - run_loop('webhelper://action'); - expect(web_action_spy).toHaveBeenCalled(); - }); - - it('calls a web action with a parameter', function () { - run_loop('webhelper://action?param=value'); - expect(web_action_spy).toHaveBeenCalledWith(jasmine.objectContaining({ - param: 'value', - })); - }); - - it('calls a web action with many parameters', function () { - run_loop('webhelper://action?first=thefirst&second=thesecond&third=thethird'); - expect(web_action_spy).toHaveBeenCalledWith(jasmine.objectContaining({ - first: 'thefirst', - second: 'thesecond', - third: 'thethird', - })); - }); - - it('uri-decodes parameter names', function () { - run_loop('webhelper://action?p%C3%A4r%C3%A4m%F0%9F%92%A9=value'); - expect(web_action_spy).toHaveBeenCalledWith(jasmine.objectContaining({ - 'päräm💩': 'value', - })); - }); - - it('uri-decodes parameter values', function () { - run_loop('webhelper://action?param=v%C3%A1lu%C3%A9%F0%9F%92%A9'); - expect(web_action_spy).toHaveBeenCalledWith(jasmine.objectContaining({ - param: 'válué💩', - })); - }); - - // This is commented out because GJS cannot catch exceptions across FFI - // interfaces (e.g. in GObject callbacks.) - xit('raises an exception on a nonexistent action instead of calling it', function () { - expect(function () { - run_loop('webhelper://nonexistentAction?param=value'); - }).toThrow(); - }); - - it('calls a web action with a blank parameter', function () { - run_loop('webhelper://action?param='); - expect(web_action_spy).toHaveBeenCalledWith(jasmine.objectContaining({ - param: '', - })); - }); - - it('uri-decodes web action names', function () { - webhelper.define_web_action('äction💩Quit', web_action_spy); - run_loop('webhelper://%C3%A4ction%F0%9F%92%A9Quit'); - expect(web_action_spy).toHaveBeenCalled(); - }); - - it('can define more than one action with define_web_actions()', function () { - webhelper.define_web_actions({ - action2: web_action_spy, - }); - run_loop('webhelper://action2'); - expect(web_action_spy).toBeTruthy(); - }); - - it('complains when defining an action that is not a function', function () { - expect(function () { - webhelper.define_web_action('badAction', 'not a function'); - }).toThrow(); - }); -}); diff --git a/webhelper/webhelper2.js b/webhelper/webhelper2.js index 6b28b10..ea25bc1 100644 --- a/webhelper/webhelper2.js +++ b/webhelper/webhelper2.js @@ -1,11 +1,529 @@ // Copyright 2015 Endless Mobile, Inc. -/* exported WebHelper */ - imports.gi.versions.WebKit2 = '4.0'; -window.WebHelper2Private = imports.gi.WebHelper2Private; -window.WebKit2 = imports.gi.WebKit2; -window.LIBEXEC_SUBDIR = 'webhelper2'; -const Common = imports.webhelper_private.common; -const WebHelper = Common.WebHelper; +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 LIBEXEC_SUBDIR = 'webhelper2'; + +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> after + * registering itself in the session bus (i.e. not inside *vfunc_dbus_register()*), + * with appropriate <well-known-name> and <connection> parameters. + * A good place to do this would be in your *vfunc_startup()* implementation, where + * you can also do further setup on it, such as defining web actions. + * + * 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(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 + 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('='); + + 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: + * > webhelper.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="webhelper://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 = 'webhelper://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); + }, +}); diff --git a/webhelper/webhelper2_old.js b/webhelper/webhelper2_old.js deleted file mode 100644 index 809504e..0000000 --- a/webhelper/webhelper2_old.js +++ /dev/null @@ -1,11 +0,0 @@ -// 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 deleted file mode 100644 index 33aa2a0..0000000 --- a/webhelper/webhelper_private/common.js +++ /dev/null @@ -1,530 +0,0 @@ -// 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> after - * registering itself in the session bus (i.e. not inside *vfunc_dbus_register()*), - * with appropriate <well-known-name> and <connection> parameters. - * A good place to do this would be in your *vfunc_startup()* implementation, where - * you can also do further setup on it, such as defining web actions. - * - * 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: - * > webhelper.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="webhelper://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 = 'webhelper://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); - }, -}); |