From e54bec3feb08b09de64945f72885c8c37258de60 Mon Sep 17 00:00:00 2001 From: Philip Chimento Date: Wed, 12 Apr 2017 15:24:48 -0700 Subject: build: Remove Webhelper Webhelper is now in its own repository: https://github.com/endlessm/webhelper https://phabricator.endlessm.com/T16203 --- .gitignore | 10 - Makefile.am | 101 +----- configure.ac | 41 +-- docs/reference/webhelper/Makefile.am.inc | 31 -- docs/reference/webhelper/eos.css | 46 --- jasmine.json | 10 +- test/Makefile.am.inc | 18 +- test/smoke-tests/webhelper/webview.js | 160 --------- test/smoke-tests/webhelper/webview2.js | 147 -------- test/webhelper/testLocal.js | 59 ---- test/webhelper/testTranslate.js | 102 ------ test/webhelper/testTranslate2.js | 214 ----------- test/webhelper/testUpdateFontSize.js | 97 ----- test/webhelper/testWebActions.js | 141 -------- test/webhelper/testWebActions2.js | 121 ------- webhelper/lib/wh2private.c | 36 -- webhelper/lib/wh2private.h | 20 -- webhelper/webextensions/wh2extension.c | 588 ------------------------------- webhelper/webextensions/wh2jscutil.c | 65 ---- webhelper/webextensions/wh2jscutil.h | 25 -- webhelper/webhelper.js | 290 --------------- webhelper/webhelper2.js | 529 --------------------------- webhelper/webhelper_private/config.js.in | 1 - 23 files changed, 6 insertions(+), 2846 deletions(-) delete mode 100644 docs/reference/webhelper/Makefile.am.inc delete mode 100644 docs/reference/webhelper/eos.css delete mode 100644 test/smoke-tests/webhelper/webview.js delete mode 100644 test/smoke-tests/webhelper/webview2.js delete mode 100644 test/webhelper/testLocal.js delete mode 100644 test/webhelper/testTranslate.js delete mode 100644 test/webhelper/testTranslate2.js delete mode 100644 test/webhelper/testUpdateFontSize.js delete mode 100644 test/webhelper/testWebActions.js delete mode 100644 test/webhelper/testWebActions2.js delete mode 100644 webhelper/lib/wh2private.c delete mode 100644 webhelper/lib/wh2private.h delete mode 100644 webhelper/webextensions/wh2extension.c delete mode 100644 webhelper/webextensions/wh2jscutil.c delete mode 100644 webhelper/webextensions/wh2jscutil.h delete mode 100644 webhelper/webhelper.js delete mode 100644 webhelper/webhelper2.js delete mode 100644 webhelper/webhelper_private/config.js.in diff --git a/.gitignore b/.gitignore index 274e0ae..6847fc3 100644 --- a/.gitignore +++ b/.gitignore @@ -6,14 +6,11 @@ test/smoke-tests/hello test/smoke-tests/images/credits.gresource Endless-0.gir Endless-0.typelib -WebHelper2Private-1.0.gir -WebHelper2Private-1.0.typelib endless/eosresource.c endless/eosresource-private.h endless/eosversion.h tools/eos-application-manifest/eos-application-manifest tools/eos-json-extractor/eos-json-extractor -webhelper/webhelper_private/config.js *.py[cod] @@ -129,10 +126,3 @@ run_coverage.coverage # Editor stuff *~ *.swp - -# NaturalDocs project files -/docs/reference/webhelper/Data/ -/docs/reference/webhelper/Languages.txt -/docs/reference/webhelper/Menu.txt -/docs/reference/webhelper/Topics.txt -/docs/reference/webhelper/html/ diff --git a/Makefile.am b/Makefile.am index 1491997..9e30ce8 100644 --- a/Makefile.am +++ b/Makefile.am @@ -29,8 +29,6 @@ AM_CFLAGS = @STRICT_CFLAGS@ AM_DISTCHECK_CONFIGURE_FLAGS = \ --enable-gtk-doc \ --enable-gir-doc \ - --enable-js-doc \ - --enable-webhelper \ $(NULL) if EOS_ENABLE_COVERAGE AM_DISTCHECK_CONFIGURE_FLAGS += --enable-coverage --with-coverage-dir=@EOS_COVERAGE_DIR@ @@ -51,36 +49,12 @@ dist-hook:: else dist-hook:: @echo "***" - @echo "*** You must configure with --enable-gtk-doc, --enable-gir-doc, and" - @echo "*** --enable-js-doc to run make dist or make distcheck." + @echo "*** You must configure with --enable-gtk-doc and --enable-gir-doc" + @echo "*** to run make dist or make distcheck." @echo "***" @false endif -# # # SUBSTITUTED FILES # # # -# These files need to be filled in with make variables - -subst = $(SED) \ - -e 's,%libexecdir%,$(libexecdir),g' \ - $(NULL) - -subst_files = -if ENABLE_WEBHELPER -subst_files += \ - webhelper/webhelper_private/config.js \ - $(NULL) -endif ENABLE_WEBHELPER - -$(subst_files): %: %.in Makefile - $(AM_V_GEN)$(MKDIR_P) $(@D) && \ - rm -f $@ $@.tmp && \ - $(subst) $< > $@.tmp && \ - chmod a-w $@.tmp && \ - mv $@.tmp $@ - -CLEANFILES += $(subst_files) -EXTRA_DIST += $(patsubst %,%.in,$(subst_files)) - # # # LIBRARY # # # # Main Open Endless SDK library @@ -97,52 +71,6 @@ pkgconfigdir = $(libdir)/pkgconfig pkgconfig_DATA = @EOS_SDK_API_NAME@.pc DISTCLEANFILES += @EOS_SDK_API_NAME@.pc -# # # WEBHELPER LIBRARY # # # - -if ENABLE_WEBHELPER - -webhelper_sources = \ - webhelper/webhelper.js \ - webhelper/webhelper2.js \ - $(NULL) - -gjsmodulesdir = $(datadir)/gjs-1.0 -webhelperdir = $(gjsmodulesdir) -webhelper_privatedir = $(webhelperdir)/webhelper_private -dist_webhelper_DATA = \ - $(webhelper_sources) \ - $(NULL) -dist_webhelper_private_DATA = \ - webhelper/webhelper_private/config.js \ - $(NULL) - -EOS_JS_COVERAGE_FILES += $(webhelper_sources) - -## Workaround for https://bugs.webkit.org/show_bug.cgi?id=116672 -## When that is solved, we can eliminate this private library and go back to -## using pure Javascript in WebHelper. -lib_LTLIBRARIES += libwebhelper2private.la -libwebhelper2private_la_SOURCES = \ - webhelper/lib/wh2private.c \ - webhelper/lib/wh2private.h \ - $(NULL) -libwebhelper2private_la_CPPFLAGS = @WEBHELPER2_PRIVATE_CFLAGS@ -libwebhelper2private_la_LIBADD = @WEBHELPER2_PRIVATE_LIBS@ -libwebhelper2private_la_LDFLAGS = -avoid-version - -webhelper2extensionsdir = $(libexecdir)/webhelper2 -webhelper2extensions_LTLIBRARIES = wh2extension.la -wh2extension_la_SOURCES = \ - webhelper/webextensions/wh2extension.c \ - webhelper/webextensions/wh2jscutil.c \ - webhelper/webextensions/wh2jscutil.h \ - $(NULL) -wh2extension_la_CPPFLAGS = @WEBHELPER2_EXTENSION_CFLAGS@ -wh2extension_la_LIBADD = @WEBHELPER2_EXTENSION_LIBS@ -wh2extension_la_LDFLAGS = -module -avoid-version -no-undefined - -endif ENABLE_WEBHELPER - # # # INTROSPECTION FILES # # # -include $(INTROSPECTION_MAKEFILE) @@ -169,20 +97,6 @@ Endless_@EOS_SDK_API_VERSION@_gir_FILES = $(introspection_sources) Endless_@EOS_SDK_API_VERSION@_gir_EXPORT_PACKAGES = @EOS_SDK_API_NAME@ INTROSPECTION_GIRS += Endless-@EOS_SDK_API_VERSION@.gir -if ENABLE_WEBHELPER - -WebHelper2Private-1.0.gir: libwebhelper2private.la -WebHelper2Private_1_0_gir_INCLUDES = GObject-2.0 GLib-2.0 WebKit2-4.0 -WebHelper2Private_1_0_gir_SCANNERFLAGS = \ - --identifier-prefix=Wh2 \ - --symbol-prefix=wh2 \ - $(NULL) -WebHelper2Private_1_0_gir_LIBS = libwebhelper2private.la -WebHelper2Private_1_0_gir_FILES = $(libwebhelper2private_la_SOURCES) -INTROSPECTION_GIRS += WebHelper2Private-1.0.gir - -endif ENABLE_WEBHELPER - girdir = $(datadir)/gir-1.0 gir_DATA = $(INTROSPECTION_GIRS) @@ -365,12 +279,6 @@ nobase_dist_licenses_DATA = licenses/creativecommons/C/CC-BY-2.0.html \ licenses/includes/zero.css \ $(NULL) -# # # PURE JAVASCRIPT MODULE DOCUMENTATION # # # - -if ENABLE_WEBHELPER -include $(top_srcdir)/docs/reference/webhelper/Makefile.am.inc -endif ENABLE_WEBHELPER - # # # GJS OVERRIDES # # # include $(top_srcdir)/overrides/Makefile.am.inc @@ -400,9 +308,4 @@ AM_JS_LOG_FLAGS += @EOS_JS_COVERAGE_LOG_FLAGS@ AM_CFLAGS += @EOS_C_COVERAGE_CFLAGS@ AM_LDFLAGS = @EOS_C_COVERAGE_LDFLAGS@ -if ENABLE_WEBHELPER -libwebhelper2private_la_LDFLAGS += @EOS_C_COVERAGE_LDFLAGS@ -wh2extension_la_LDFLAGS += @EOS_C_COVERAGE_LDFLAGS@ -endif ENABLE_WEBHELPER - clean-local:: clean-coverage diff --git a/configure.ac b/configure.ac index 77389a9..7001a30 100644 --- a/configure.ac +++ b/configure.ac @@ -81,7 +81,6 @@ GOBJECT_REQUIREMENT="gobject-2.0" GIO_REQUIREMENT="gio-2.0" GTK_REQUIREMENT="gtk+-3.0 >= 3.20" JSON_GLIB_REQUIREMENT="json-glib-1.0 >= 0.12" -WEBKIT2_REQUIREMENT="webkit2gtk-4.0" EOSMETRICS_REQUIREMENT="eosmetrics-0" # These go into the pkg-config file as Requires: and Requires.private: # (Generally, use Requires.private: instead of Requires:) @@ -122,8 +121,6 @@ AC_PATH_PROG([GIRDOCTOOL], [g-ir-doc-tool], [notfound]) AC_ARG_VAR([GIRDOCTOOL], [Path to g-ir-doc-tool]) AC_PATH_PROG([YELPBUILD], [yelp-build], [notfound]) AC_ARG_VAR([YELPBUILD], [Path to yelp-build]) -AC_PATH_PROG([NATURALDOCS], [naturaldocs], [notfound]) -AC_ARG_VAR([NATURALDOCS], [Path to naturaldocs]) GLIB_COMPILE_RESOURCES=`$PKG_CONFIG --variable glib_compile_resources gio-2.0` AC_SUBST(GLIB_COMPILE_RESOURCES) @@ -180,12 +177,6 @@ AS_IF([test "x$enable_metrics" != "xno"], [ AC_DEFINE([USE_METRICS]) EOS_REQUIRED_MODULES_PRIVATE="$EOS_REQUIRED_MODULES_PRIVATE $EOSMETRICS_REQUIREMENT"]) -# --disable-webhelper: Don't build WebHelper. Use this if you just want a quick -# Endless SDK without the webkit dependency. -AC_ARG_ENABLE([webhelper], [AS_HELP_STRING([--disable-webhelper], - [WebHelper requires WebKit dependency @<:@default=yes@:>@])]) -AM_CONDITIONAL([ENABLE_WEBHELPER], [test "x$enable_webhelper" != "xno"]) - # --enable-gir-doc: Build GIR documentation for Javascript. Done automatically # during 'make distcheck'. AC_ARG_ENABLE([gir-doc], @@ -198,22 +189,12 @@ AS_IF([test "x$enable_gir_doc" = "xyes"], [ [AC_MSG_ERROR([yelp-build must be installed for --enable-gir-doc])])]) AM_CONDITIONAL([ENABLE_GIR_DOC], [test "x$enable_gir_doc" = "xyes"]) -# --enable-js-doc: Build pure Javascript module documentation. -AC_ARG_ENABLE([js-doc], - [AS_HELP_STRING([--enable-js-doc], - [Build documentation for pure Javascript modules @<:@default=no@:>@])]) -AS_IF([test "x$enable_js_doc" = "xyes"], [ - AS_IF([test "x$NATURALDOCS" = "xnotfound"], - [AC_MSG_ERROR([NaturalDocs must be installed for --enable-js-doc])])]) -AM_CONDITIONAL([ENABLE_JS_DOC], [test "x$enable_js_doc" = "xyes"]) - -# For 'make dist' or 'make distcheck', all three of --enable-gtk-doc, -# --enable-gir-doc, and --enable-js-doc are required +# For 'make dist' or 'make distcheck', both of --enable-gtk-doc and +# --enable-gir-doc are required AC_MSG_CHECKING([whether this configuration allows building distributions]) can_make_dist=yes AM_COND_IF([ENABLE_GTK_DOC], [], [can_make_dist=no]) AM_COND_IF([ENABLE_GIR_DOC], [], [can_make_dist=no]) -AM_COND_IF([ENABLE_JS_DOC], [], [can_make_dist=no]) AM_CONDITIONAL([CAN_MAKE_DIST], [test "x$can_make_dist" = "xyes"]) AC_MSG_RESULT([$can_make_dist]) @@ -233,21 +214,6 @@ PKG_CHECK_MODULES([EOS_SDK], [ $EOS_REQUIRED_MODULES $EOS_REQUIRED_MODULES_PRIVATE]) -AS_IF([test "x$enable_webhelper" != "xno"], [ - PKG_CHECK_MODULES([WEBHELPER2_EXTENSION], [ - $GLIB_REQUIREMENT - $GOBJECT_REQUIREMENT - $WEBKIT2_REQUIREMENT]) - PKG_CHECK_MODULES([WEBHELPER2_PRIVATE], [ - $GLIB_REQUIREMENT - $WEBKIT2_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], [4.0]) -]) - # Code coverage reports support EOS_COVERAGE_REPORT([c js]) @@ -266,9 +232,6 @@ AC_CONFIG_FILES([ tools/eos-json-extractor/eos-json-extractor ]) AC_CONFIG_HEADERS([config.h]) dnl Header with system-dependent #defines -# Make docs/reference/webhelper/eos.css available in the build tree as well as -# the source tree -AC_CONFIG_LINKS([docs/reference/webhelper/eos.css:docs/reference/webhelper/eos.css]) # Do the output AC_OUTPUT diff --git a/docs/reference/webhelper/Makefile.am.inc b/docs/reference/webhelper/Makefile.am.inc deleted file mode 100644 index 55caccf..0000000 --- a/docs/reference/webhelper/Makefile.am.inc +++ /dev/null @@ -1,31 +0,0 @@ -if ENABLE_JS_DOC - -build_naturaldocs_verbose = $(build_naturaldocs_verbose_@AM_V@) -build_naturaldocs_verbose_ = $(build_naturaldocs_verbose_@AM_DEFAULT_V@) -build_naturaldocs_verbose_0 = @echo ' DOC Building WebHelper docs'; - -docs/reference/webhelper/doc-build.stamp: webhelper/webhelper.js docs/reference/webhelper/eos.css - $(build_naturaldocs_verbose)$(MKDIR_P) $(@D)/html && \ - naturaldocs -i $( \ -

\ -\ -

Regular link to a Web site

\ -\ -

I want \ -stars!

\ -\ -

This is text that will be italicized: Hello, \ -world!

\ -\ - \ -'; - -const TestApplication = new Lang.Class({ - Name: 'TestApplication', - Extends: WebHelper.Application, - - /* *** ACTIONS AVAILABLE FROM THE WEB VIEW *** */ - - /* dict['name'] is the name of the page to move to */ - moveToPage: function(dict) { - this._pm.visible_child_name = dict['name']; - }, - - /* dict['msg'] is the message to display */ - showMessageFromParameter: function(dict) { - let dialog = new Gtk.MessageDialog({ - buttons: Gtk.ButtonsType.CLOSE, - message_type: Gtk.MessageType.INFO, - text: dict['msg'] - }); - dialog.set_transient_for(this._window); - dialog.run(); - dialog.destroy(); - }, - - /* dict['id'] is the ID of the input field to use */ - showMessageFromInputField: function(dict) { - let input = this._getElementById(this._webview, dict['id']); - - // WebKitDOMHTMLInputElement - let msg = input.get_value(); - - let dialog = new Gtk.MessageDialog({ - buttons: Gtk.ButtonsType.CLOSE, - message_type: Gtk.MessageType.INFO, - text: msg - }); - dialog.set_transient_for(this._window); - dialog.run(); - dialog.destroy(); - }, - - /* dict['id'] is the ID of the element to use */ - addStars: function(dict) { - let e = this._getElementById(this._webview, dict['id']); - e.inner_text += '★ '; - }, - - /* *************************** */ - - vfunc_startup: function() { - this.parent(); - - this.set_translation_function(function(string) { - return string.italics(); - }); - this.define_web_actions({ - moveToPage: this.moveToPage, - showMessageFromParameter: this.showMessageFromParameter, - showMessageFromInputField: this.showMessageFromInputField, - addStars: this.addStars - }); - - this._webview = new WebKit.WebView(); - this._webview.load_string(TEST_HTML, 'text/html', 'UTF-8', 'file://'); - this._webview.connect('notify::load-status', - Lang.bind(this, function (webview) { - if (webview.load_status == WebKit.LoadStatus.FINISHED) - this.translate_html(webview); - })); - - this._webview.connect('navigation-policy-decision-requested', - Lang.bind(this, this.web_actions_handler)); - - this._page1 = new Gtk.ScrolledWindow(); - this._page1.add(this._webview); - - this._page2 = new Gtk.Grid(); - let back_button = new Gtk.Button({ label:"Go back to page 1" }); - back_button.connect('clicked', Lang.bind(this, function() { - this._pm.visible_child_name = 'page1'; - })); - this._page2.add(back_button); - - this._window = new Endless.Window({ - application: this, - border_width: 16 - }); - - this._pm = this._window.page_manager; - this._pm.set_transition_type(Gtk.StackTransitionType.CROSSFADE); - this._pm.add(this._page1, { name: 'page1' }); - this._pm.add(this._page2, { name: 'page2' }); - this._pm.visible_child = this._page1; - - this._window.show_all(); - } -}); - -let app = new TestApplication({ - application_id: TEST_APPLICATION_ID, - flags: 0 -}); -app.run(ARGV); diff --git a/test/smoke-tests/webhelper/webview2.js b/test/smoke-tests/webhelper/webview2.js deleted file mode 100644 index f50c42d..0000000 --- a/test/smoke-tests/webhelper/webview2.js +++ /dev/null @@ -1,147 +0,0 @@ -// Copyright 2015 Endless Mobile, Inc. - -const Endless = imports.gi.Endless; -const Gettext = imports.gettext; -const Gtk = imports.gi.Gtk; -const Lang = imports.lang; -const WebHelper2 = imports.webhelper2; -const WebKit2 = imports.gi.WebKit2; - -const TEST_APPLICATION_ID = 'com.endlessm.example.test-webview2'; -const TEST_HTML = '\ - \ - \ -First page \ - \ - \ -\ - \ - \ -

First page

\ -\ -

Move to page 2

\ -\ -

Show \ -message from parameter in this URL

\ -\ -
\ - \ - \ -
\ -\ -

Regular link to a Web site

\ -\ -

This is text that will be italicized: Hello, \ -world!

\ -\ -

\ - \ - \ -

\ -\ - \ -'; - -const TestApplication = new Lang.Class({ - Name: 'TestApplication', - Extends: Endless.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((s) => s.italics()); - this._webhelper.set_ngettext((s, p, n) => - '** ' + (n == 1 ? s : p) + ' **'); - this._webhelper.define_web_actions({ - moveToPage: this.moveToPage.bind(this), - showMessageFromParameter: this.showMessageFromParameter.bind(this), - }); - - this._webview = new WebKit2.WebView(); - this._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); - }); - }); - this._webview.load_html(TEST_HTML, null); - - this._page2 = new Gtk.Grid(); - let back_button = new Gtk.Button({ label: 'Go back to page 1' }); - back_button.connect('clicked', () => { - this._pm.visible_child_name = 'page1'; - }); - this._page2.add(back_button); - - this._window = new Endless.Window({ - application: this, - border_width: 16, - }); - - this._pm = this._window.page_manager; - this._pm.set_transition_type(Gtk.StackTransitionType.CROSSFADE); - this._pm.add(this._webview, { name: 'page1' }); - this._pm.add(this._page2, { name: 'page2' }); - this._pm.visible_child_name = 'page1'; - - this._window.show_all(); - }, - - vfunc_dbus_unregister: function (connection, object_path) { - this.parent(connection, object_path); - this._webhelper.unregister(); - }, - - // WEB ACTIONS - - // dict['name'] is the name of the page to move to - moveToPage: function (dict) { - this._pm.visible_child_name = dict['name']; - }, - - // dict['msg'] is the message to display - showMessageFromParameter: function (dict) { - let dialog = new Gtk.MessageDialog({ - buttons: Gtk.ButtonsType.CLOSE, - message_type: Gtk.MessageType.INFO, - text: dict['msg'], - transient_for: this._window, - }); - dialog.run(); - dialog.destroy(); - }, -}); - -let app = new TestApplication({ - application_id: TEST_APPLICATION_ID, -}); -app.run(ARGV); diff --git a/test/webhelper/testLocal.js b/test/webhelper/testLocal.js deleted file mode 100644 index d65e6c7..0000000 --- a/test/webhelper/testLocal.js +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright 2015 Endless Mobile, Inc. - -const Gio = imports.gi.Gio; -const GLib = imports.gi.GLib; -const Gtk = imports.gi.Gtk; -const WebHelper2 = imports.webhelper2; -const WebKit2 = imports.gi.WebKit2; - -const WELL_KNOWN_NAME = 'com.endlessm.WebHelper.testLocal'; - -Gtk.init(null); - -describe('Local URI scheme', function () { - let owner_id, connection, webview, webhelper; - - beforeAll(function (done) { - owner_id = Gio.DBus.own_name(Gio.BusType.SESSION, WELL_KNOWN_NAME, - Gio.BusNameOwnerFlags.NONE, - null, // bus acquired - (con, name) => { // 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, - }); - webview = new WebKit2.WebView(); - }); - - afterEach(function () { - webhelper.unregister(); - }); - - it('loads a local file', function (done) { - let path = GLib.build_filenamev([GLib.getenv('TOP_SRCDIR'), 'test', - 'tools', 'test.html']); - let test_file = Gio.File.new_for_path(path); - - 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) { - webview.disconnect(id); - expect(error_spy).not.toHaveBeenCalled(); - done(); - } - }); - webview.load_uri(test_file.get_uri().replace(/^file:/, 'local:')); - }); -}); diff --git a/test/webhelper/testTranslate.js b/test/webhelper/testTranslate.js deleted file mode 100644 index da8c77a..0000000 --- a/test/webhelper/testTranslate.js +++ /dev/null @@ -1,102 +0,0 @@ -const Endless = imports.gi.Endless; -const GLib = imports.gi.GLib; -const Gtk = imports.gi.Gtk; -const Lang = imports.lang; -const WebHelper = imports.webhelper; -const WebKit = imports.gi.WebKit; - -const WebHelperApplicationWithTranslatableText = new Lang.Class({ - Name: 'WebHelperApplicationWithTranslatableText', - Extends: WebHelper.Application, - - get_translation_string: function() { - return 'Translate Me'; - }, - - vfunc_startup: function() { - this.parent(); - this.webview = new WebKit.WebView(); - let string = '

' + - this.get_translation_string() + - '

'; - this.webview.load_string(string, 'text/html', 'UTF-8', 'file://'); - this.win = new Endless.Window({ - application: this - }); - this.scrolled = new Gtk.ScrolledWindow(); - this.scrolled.add(this.webview); - this.win.page_manager.add(this.scrolled); - - this.webview.connect('notify::load-status', Lang.bind(this, function() { - if(this.webview.load_status === WebKit.LoadStatus.FINISHED) { - this.translate_html(this.webview); - this.quit(); - } - })); - } -}); - -describe("Translation strategy", function() { - let app; - - beforeEach(function() { - // FIXME In this version of GJS there is no Posix module, so fake the PID - let id_string = 'com.endlessm.webhelper.test' + GLib.get_real_time() + fake_pid; - // Generate a unique ID for each app instance that we test - let fake_pid = GLib.random_int(); - app = new WebHelperApplicationWithTranslatableText({ - application_id: id_string - }); - }); - - describe("translation function", function() { - let translationFunctionSpy; - beforeEach(function() { - translationFunctionSpy = jasmine.createSpy('translate').and.returnValue('Translated'); - }); - it("gets called with string to translate on run", function() { - app.set_translation_function(translationFunctionSpy); - app.run([]); - expect(translationFunctionSpy).toHaveBeenCalledWith(app.get_translation_string()); - }); - }); - - it("throws when an incompatible type is set as the translation function", function() { - expect(function() { - app.set_translation_function({}); - }).toThrow(); - }); - - // Can't test this right now as there is no support for propagating exceptions across - // GI interfaces - xit("throws when there isn't a translation function set", function() { - expect(function() { - app.run([]); - }).toThrow(); - }); - - it("has a null translation function by default", function() { - expect(app.get_translation_function()).toBe(null); - }); - - it("stores the expected translation function", function() { - let translation = function(str) { - return str; - }; - - app.set_translation_function(translation); - expect(app.get_translation_function()).toBe(translation); - }); - - it("allows us to store a null translation function", function() { - let nonNullTranslation = function(str) { - return str; - } - - // set a non-null translation function first so that we get - // the non-default behaviour for get_translation_function - app.set_translation_function(nonNullTranslation); - app.set_translation_function(null); - expect(app.get_translation_function()).toBe(null); - }); -}); diff --git a/test/webhelper/testTranslate2.js b/test/webhelper/testTranslate2.js deleted file mode 100644 index 6ce4776..0000000 --- a/test/webhelper/testTranslate2.js +++ /dev/null @@ -1,214 +0,0 @@ -// Copyright 2015 Endless Mobile, Inc. - -const Gio = imports.gi.Gio; -const Gtk = imports.gi.Gtk; -const Lang = imports.lang; -const Mainloop = imports.mainloop; -const WebHelper2 = imports.webhelper2; -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 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) => { // 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 = '

Translate Me

'; - - 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 + '', 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('', null); - }); - - it('normalizes a string before translating it', function () { - run_loop('

\n\ - Translate Me\n\ -

'); - expect(gettext_spy).toHaveBeenCalledWith('Translate Me'); - }); - - it('handles quotes correctly', function () { - run_loop('

String with "quotes"

'); - expect(gettext_spy).toHaveBeenCalledWith('String with "quotes"'); - }); - - it('handles embedded tags correctly', function () { - run_loop('

Embedded
tags

'); - expect(gettext_spy).toHaveBeenCalledWith('Embedded
tags'); - }); - }); - - describe('used from client-side Javascript', function () { - let webview; - - beforeEach(function () { - webview = new WebKit2.WebView(); - }); - - function load_script(view, script) { - view.load_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/testUpdateFontSize.js b/test/webhelper/testUpdateFontSize.js deleted file mode 100644 index dd45d6f..0000000 --- a/test/webhelper/testUpdateFontSize.js +++ /dev/null @@ -1,97 +0,0 @@ -const Endless = imports.gi.Endless; -const Gio = imports.gi.Gio; -const GLib = imports.gi.GLib; -const Gtk = imports.gi.Gtk; -const Lang = imports.lang; -const WebHelper = imports.webhelper; -const WebKit = imports.gi.WebKit; - -const WebUpdateFontSizeTestApplication = new Lang.Class({ - Name: 'WebUpdateFontSizeTestApplication', - Extends: WebHelper.Application, - - vfunc_startup: function () { - this.parent(); - - this.webview = new WebKit.WebView(); - this.websettings = this.webview.get_settings(); - this.webview.set_settings(this.websettings); - this.win = new Endless.Window({ - application: this, - font_scaling_active: true - }); - - /* Typically logic in tests is bad. In this case, we need to inject these - * callbacks to test specific cases that only occur during the runtime main - * loop of the test application. */ - if (this.set_font_resizable_callback !== null) { - this.set_font_resizable_callback(this.win, this.websettings); - this.websettings.connect('notify::default-font-size', Lang.bind(this, function () { - this.default_font_size_changed_callback(this.win, this.websettings); - })); - } - - if (this.accept_callback !== null) - this.accept_callback(this.win, this.websettings.default_font_size); - }, - - accept_callback: null, - - set_font_resizable_callback: null, - - default_font_size_changed_callback: null -}); - -// TODO: These tests depend on a running X Server and Window Manager. That means -// that they are not runnable in a continuous-integration server -describe("Web Helper Font Sizes", function () { - let app; - - beforeEach(function () { - // Generate a unique ID for each app instance that we test - let fake_pid = GLib.random_int(); - // FIXME In this version of GJS there is no Posix module, so fake the PID - let id_string = 'com.endlessm.webhelper.test' + GLib.get_real_time() + fake_pid; - - app = new WebUpdateFontSizeTestApplication({ - application_id: id_string - }); - }); - - it("does not have the default font scale for a tiny window", function () { - let test_initial_font_size = function (my_win, default_font_size) { - my_win.font_scaling_default_window_size = 200; // Force a ridiculous value - // Window's font scaling is a double, web settings' is an integer - expect(my_win.font_scaling_calculated_font_size).not.toBeCloseTo(default_font_size, 0); - - /* This does not immediately quit the application. It sets a flag for termination - * which will cause the application to be disposed on the next main loop iteration. */ - app.quit(); - }; - - app.accept_callback = test_initial_font_size; - - spyOn(app, 'accept_callback').and.callThrough(); - app.run([]); - expect(app.accept_callback).toHaveBeenCalled(); - }); - - it("takes the provided settings default font size on setting resizable", function () { - app.set_font_resizable_callback = app.set_web_settings_font_resizable; - - let test_font_sizing = function (my_win, my_websettings) { - // Window's font scaling is a double, web settings' is an integer - expect(my_win.font_scaling_calculated_font_size).toBeCloseTo(my_websettings.default_font_size, 0); - - /* This does not immediately quit the application. It sets a flag for termination - * which will cause the application to be disposed on the next main loop iteration. */ - app.quit(); - }; - - app.default_font_size_changed_callback = test_font_sizing; - - spyOn(app, 'default_font_size_changed_callback').and.callThrough(); - app.run([]); - expect(app.default_font_size_changed_callback).toHaveBeenCalled(); - }); -}); diff --git a/test/webhelper/testWebActions.js b/test/webhelper/testWebActions.js deleted file mode 100644 index e937fb6..0000000 --- a/test/webhelper/testWebActions.js +++ /dev/null @@ -1,141 +0,0 @@ -const Endless = imports.gi.Endless; -const Gio = imports.gi.Gio; -const GLib = imports.gi.GLib; -const Gtk = imports.gi.Gtk; -const Lang = imports.lang; -const WebHelper = imports.webhelper; -const WebKit = imports.gi.WebKit; - -const WebActionTestApplication = new Lang.Class({ - Name: 'WebActionTestApplication', - Extends: WebHelper.Application, - - vfunc_startup: function() { - this.parent(); - this.webview = new WebKit.WebView(); - this.webview.connect('navigation-policy-decision-requested', - Lang.bind(this, this.web_actions_handler)); - let string = (''); - this.webview.load_string(string, 'text/html', 'UTF-8', 'file://'); - this.win = new Endless.Window({ - application: this - }); - this.scrolled = new Gtk.ScrolledWindow(); - this.scrolled.add(this.webview); - this.win.page_manager.add(this.scrolled); - } -}); - -// TODO: These tests depend on a running X Server and Window Manager. That means -// that they are not runnable in a continuous-integration server -describe("Web Actions Bindings", function() { - let app; - let webActionSpy; - - beforeEach(function() { - // Generate a unique ID for each app instance that we test - let fake_pid = GLib.random_int(); - // FIXME In this version of GJS there is no Posix module, so fake the PID - let id_string = 'com.endlessm.webhelper.test' + GLib.get_real_time() + fake_pid; - app = new WebActionTestApplication({ - application_id: id_string - }); - webActionSpy = jasmine.createSpy('quitAction').and.callFake(function() { - // Calls destroy on the applications window, which decrements the hold count on the - // application and implicitly causes the application to close. - app.win.destroy(); - }); - }); - - let RunApplicationWithWebAction = function(app, action) { - app.webActionToTest = action; - app.run([]); - } - it("has a working quitApplication uri upon defining quitApplication as a string", function() { - app.define_web_action('quitApplication', webActionSpy); - RunApplicationWithWebAction(app, 'endless://quitApplication'); - - expect(webActionSpy).toHaveBeenCalled(); - }); - - it("is called with a parameter", function() { - app.define_web_action('getParameterAndQuit', webActionSpy); - RunApplicationWithWebAction(app, 'endless://getParameterAndQuit?param=value'); - - expect(webActionSpy).toHaveBeenCalledWith(new jasmine.ObjectContaining({ param: 'value' })); - }); - - it("can be called with many parameters", function() { - app.define_web_action('getParametersAndQuit', webActionSpy); - RunApplicationWithWebAction(app, 'endless://getParametersAndQuit?first=thefirst&second=thesecond&third=thethird'); - - expect(webActionSpy).toHaveBeenCalledWith(new jasmine.ObjectContaining({ - first: 'thefirst', - second: 'thesecond', - third: 'thethird' - })); - }); - - it("decodes parameter URI names", function() { - app.define_web_action('getUriDecodedParameterAndQuit', webActionSpy); - RunApplicationWithWebAction(app, 'endless://getUriDecodedParameterAndQuit?p%C3%A4r%C3%A4m%F0%9F%92%A9=value'); - - expect(webActionSpy).toHaveBeenCalledWith(new jasmine.ObjectContaining({ - 'päräm💩' : 'value' - })); - }); - - it("decodes parameter URI values", function() { - app.define_web_action('getUriDecodedParameterValueAndQuit', webActionSpy); - RunApplicationWithWebAction(app, 'endless://getUriDecodedParameterValueAndQuit?param=v%C3%A1lu%C3%A9%F0%9F%92%A9'); - - expect(webActionSpy).toHaveBeenCalledWith(new jasmine.ObjectContaining({ - param : 'válué💩' - })); - }); - - // We currently can't catch exceptions across GObject-Introspection callbacks - xit('bad action is not called', function() { - expect(function() { RunApplicationWithWebAction(app, 'endless://nonexistentWebAction') }).toThrow(); - }); - - describe("with blank parameters", function() { - beforeEach(function() { - app.define_web_action('getBlankValueAndQuit', webActionSpy); - RunApplicationWithWebAction(app, 'endless://getBlankValueAndQuit?param='); - }); - - it("can be called", function() { - expect(webActionSpy).toHaveBeenCalled(); - }); - - it("is called with a paramater that is an empty string", function() { - expect(webActionSpy).toHaveBeenCalledWith(new jasmine.ObjectContaining({ - 'param' : '' - })); - }); - }); - - it("URI decodes the action", function() { - app.define_web_action('äction💩Quit', webActionSpy); - RunApplicationWithWebAction(app, 'endless://%C3%A4ction%F0%9F%92%A9Quit'); - expect(webActionSpy).toHaveBeenCalled(); - }); - - it("allows web actions to be defined as object properties", function() { - app.define_web_actions({ - quitApplication: webActionSpy - }); - - RunApplicationWithWebAction(app, 'endless://quitApplication'); - - expect(webActionSpy).toHaveBeenCalled(); - }); - - it("throws an error when trying to define an action that is not a function", function() { - expect(function() { - app.define_web_action('action', {}); - }).toThrow(); - }); -}); diff --git a/test/webhelper/testWebActions2.js b/test/webhelper/testWebActions2.js deleted file mode 100644 index 5af5e2b..0000000 --- a/test/webhelper/testWebActions2.js +++ /dev/null @@ -1,121 +0,0 @@ -const Gio = imports.gi.Gio; -const Gtk = imports.gi.Gtk; -const Lang = imports.lang; -const Mainloop = imports.mainloop; -const WebHelper2 = imports.webhelper2; -const WebKit2 = imports.gi.WebKit2; - -const WELL_KNOWN_NAME = 'com.endlessm.WebHelper.testWebActions2'; - -Gtk.init(null); - -describe('WebKit2 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) => { // name acquired - connection = con; - done(); - }, - null); // name lost - }); - - afterAll(function () { - Gio.DBus.unown_name(owner_id); - }); - - function run_loop(action_to_test) { - let string = ''; - 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/lib/wh2private.c b/webhelper/lib/wh2private.c deleted file mode 100644 index 04a7710..0000000 --- a/webhelper/lib/wh2private.c +++ /dev/null @@ -1,36 +0,0 @@ -/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ - -/* Copyright 2015 Endless Mobile, Inc. */ - -#include -#include - -#include "wh2private.h" - -/** - * wh2_private_register_global_uri_scheme: - * @scheme: the network scheme to register - * @callback: a #WebKitURISchemeRequestCallback. - * @user_data: (closure): user data for the @callback - * @notify: destroy notify function for the @callback - * - * Workaround for https://bugzilla.gnome.org/show_bug.cgi?id=729611 - * - * Registers a URI scheme handler with the default WebContext. Does not pass the - * GDestroyNotifyFunc, which GJS uses to shim a destructor for @callback, along - * to the the web context. - * - * The default web context is a global object which does not get destroyed - * until a atexit handler after the javascript runtime has been torn down. - * Calling into the GJS function destructor at that point would be a - * mistake. - */ -void -wh2_register_uri_scheme (const gchar *scheme, - WebKitURISchemeRequestCallback callback, - gpointer user_data, - GDestroyNotify notify) -{ - WebKitWebContext *context = webkit_web_context_get_default (); - webkit_web_context_register_uri_scheme (context, scheme, callback, NULL, NULL); -} diff --git a/webhelper/lib/wh2private.h b/webhelper/lib/wh2private.h deleted file mode 100644 index a20fe87..0000000 --- a/webhelper/lib/wh2private.h +++ /dev/null @@ -1,20 +0,0 @@ -/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ - -/* Copyright 2015 Endless Mobile, Inc. */ - -#ifndef WH2_PRIVATE_H -#define WH2_PRIVATE_H - -#include -#include - -G_BEGIN_DECLS - -void wh2_register_uri_scheme (const gchar *scheme, - WebKitURISchemeRequestCallback callback, - gpointer user_data, - GDestroyNotify notify); - -G_END_DECLS - -#endif /* WH2_PRIVATE_H */ diff --git a/webhelper/webextensions/wh2extension.c b/webhelper/webextensions/wh2extension.c deleted file mode 100644 index 8dd738b..0000000 --- a/webhelper/webextensions/wh2extension.c +++ /dev/null @@ -1,588 +0,0 @@ -/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ - -/* Copyright 2015 Endless Mobile, Inc. */ - -#include -#include - -#include -#include -#include -#include - -#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" - -/* Declaration of externally visible symbol */ -void webkit_web_extension_initialize_with_user_data (WebKitWebExtension *, const GVariant *); - -typedef struct { - WebKitWebExtension *extension; /* unowned */ - GDBusConnection *connection; /* unowned */ - GDBusNodeInfo *node; /* owned */ - GDBusInterfaceInfo *interface; /* owned by node */ - GSList *bus_ids; /* GSList; owned */ - GArray *unregistered_pages; /* GArray; owned */ - gchar *main_program_name; /* owned; well-known-name of main program */ -} Context; - -typedef struct { - Context *ctxt; /* unowned */ - guint64 page_id; -} PageContext; - -static const gchar introspection_xml[] = - "" - "" - "" - "" - ""; - -static void -context_free (Context *ctxt) -{ - g_clear_pointer (&ctxt->node, g_dbus_node_info_unref); - g_clear_pointer (&ctxt->bus_ids, g_slist_free); - if (ctxt->unregistered_pages != NULL) - g_array_free (ctxt->unregistered_pages, TRUE); - ctxt->unregistered_pages = NULL; - g_clear_pointer (&ctxt->main_program_name, g_free); - g_free (ctxt); -} - -/* Spins the main loop until the DBus connection comes up. We need this since we -define functions in JS, that JS code can potentially call before there is a -connection. */ -static void -wait_for_connection_sync (Context *ctxt) -{ - g_return_if_fail (ctxt->connection == NULL); - - GMainContext *main = g_main_context_get_thread_default (); - while (ctxt->connection == NULL) - g_main_context_iteration (main, TRUE /* may block */); - - g_assert (ctxt->connection); -} - -static gchar * -translation_function (const gchar *message, - Context *ctxt) -{ - GError *error = NULL; - - if (ctxt->connection == NULL) - wait_for_connection_sync (ctxt); - - GVariant *result = - g_dbus_connection_call_sync (ctxt->connection, ctxt->main_program_name, - MAIN_PROGRAM_OBJECT_PATH, - MAIN_PROGRAM_INTERFACE_NAME, "Gettext", - g_variant_new ("(s)", message), - (GVariantType *) "(s)", - G_DBUS_CALL_FLAGS_NO_AUTO_START, - -1 /* timeout */, NULL /* cancellable */, - &error); - if (result == NULL) - { - g_warning ("No return value from gettext: %s", error->message); - g_clear_error (&error); - return g_strdup (message); - } - - gchar *retval; - g_variant_get (result, "(s)", &retval); - g_variant_unref (result); - return retval; -} - -static gchar * -ngettext_translation_function (const gchar *singular, - const gchar *plural, - guint64 number, - Context *ctxt) -{ - GError *error = NULL; - - if (ctxt->connection == NULL) - wait_for_connection_sync (ctxt); - - GVariant *result = - g_dbus_connection_call_sync (ctxt->connection, ctxt->main_program_name, - MAIN_PROGRAM_OBJECT_PATH, - MAIN_PROGRAM_INTERFACE_NAME, "NGettext", - g_variant_new ("(sst)", singular, plural, - number), - (GVariantType *) "(s)", - G_DBUS_CALL_FLAGS_NO_AUTO_START, - -1 /* timeout */, NULL /* cancellable */, - &error); - if (result == NULL) - { - g_warning ("No return value from ngettext: %s", error->message); - g_clear_error (&error); - return g_strdup (number == 1 ? singular : plural); - } - - gchar *retval; - g_variant_get (result, "(s)", &retval); - g_variant_unref (result); - 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 %"G_GSIZE_FORMAT".", 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 JSValueRef -ngettext_shim (JSContextRef js, - JSObjectRef function, - JSObjectRef this_object, - size_t n_args, - const JSValueRef args[], - JSValueRef *exception) -{ - if (n_args != 3) - { - gchar *errmsg = g_strdup_printf ("Expected three arguments to ngettext()," - "but got %"G_GSIZE_FORMAT".", n_args); - *exception = throw_exception (js, errmsg); - g_free (errmsg); - return NULL; - } - if (!JSValueIsString (js, args[0])) - { - *exception = throw_exception (js, "The first argument to ngettext() " - "must be a string."); - return NULL; - } - if (!JSValueIsString (js, args[1])) - { - *exception = throw_exception (js, "The second argument to ngettext() " - "must be a string."); - return NULL; - } - if (!JSValueIsNumber (js, args[2])) - { - *exception = throw_exception (js, "The third argument to ngettext() " - "must be a number."); - 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 singular_ref = JSValueToStringCopy (js, args[0], exception); - if (singular_ref == NULL) - return NULL; /* propagate exception */ - gchar *singular_msg = string_ref_to_string (singular_ref); - JSStringRelease (singular_ref); - - JSStringRef plural_ref = JSValueToStringCopy (js, args[1], exception); - if (plural_ref == NULL) - { - g_free (singular_msg); - return NULL; /* propagate exception */ - } - gchar *plural_msg = string_ref_to_string (plural_ref); - JSStringRelease (plural_ref); - - double number = JSValueToNumber (js, args[2], exception); - if (isnan (number)) - { - g_free (singular_msg); - g_free (plural_msg); - return NULL; /* propagate exception */ - } - - gchar *translation = ngettext_translation_function (singular_msg, plural_msg, - (guint64) number, ctxt); - g_free (singular_msg); - g_free (plural_msg); - - JSValueRef retval = string_to_value_ref (js, translation); - g_free (translation); - return retval; -} - -static gchar * -normalize_string (const gchar *string) -{ - static GRegex *whitespace = NULL; - - if (g_once_init_enter (&whitespace)) - { - GError *regex_error = NULL; - GRegex *new_regex = g_regex_new ("\\s+", G_REGEX_OPTIMIZE, 0, - ®ex_error); - // Don't free; will persist until exit - if (new_regex == NULL) - { - g_critical ("Trouble creating regex: %s\n", regex_error->message); - g_clear_error (®ex_error); - } - - g_once_init_leave (&whitespace, new_regex); - } - - GError *error = NULL; - gchar *copy = g_strstrip (g_strdup (string)); - gchar *retval = g_regex_replace_literal (whitespace, copy, -1, 0, " ", 0, &error); - if (retval == NULL) - { - g_critical ("Trouble normalizing string: %s\n", error->message); - g_clear_error (&error); - return copy; - } - g_free (copy); - return retval; -} - -static void -translate_html (WebKitDOMDocument *dom, - Context *ctxt) -{ - WebKitDOMNodeList *translatable; - GError *error = NULL; - - translatable = webkit_dom_document_get_elements_by_name (dom, "translatable"); - - gulong index, length = webkit_dom_node_list_get_length (translatable); - for (index = 0; index < length; index++) - { - WebKitDOMNode *element = webkit_dom_node_list_item (translatable, index); - - /* Translate the text */ - if (WEBKIT_DOM_IS_HTML_ELEMENT (element)) - { - WebKitDOMHTMLElement *el_html = WEBKIT_DOM_HTML_ELEMENT (element); - gchar *inner_html = webkit_dom_html_element_get_inner_html (el_html); - gchar *normalized = normalize_string (inner_html); - gchar *translated_html = translation_function (normalized, ctxt); - webkit_dom_html_element_set_inner_html (el_html, translated_html, - &error); - if (error != NULL) - { - g_warning ("There was a problem translating '%s' to '%s': %s", - inner_html, translated_html, error->message); - g_clear_error (&error); - } - - g_free (translated_html); - g_free (inner_html); - g_free (normalized); - } - else - { - gchar *text = webkit_dom_node_get_text_content (element); - gchar *normalized = normalize_string (text); - gchar *translated_text = translation_function (normalized, ctxt); - webkit_dom_node_set_text_content (element, translated_text, &error); - if (error != NULL) - { - g_warning ("There was a problem translating '%s' to '%s': %s", - text, translated_text, error->message); - g_clear_error (&error); - } - - g_free (translated_text); - g_free (text); - g_free (normalized); - } - } - - g_object_unref (translatable); -} - -static void -on_wh2_method_call (GDBusConnection *connection, - const gchar *sender, - const gchar *object_path, - const gchar *interface_name, - const gchar *method_name, - GVariant *parameters, - GDBusMethodInvocation *invocation, - PageContext *pctxt) -{ - if (strcmp (method_name, "Translate") != 0) - { - g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, - G_DBUS_ERROR_UNKNOWN_METHOD, - "Unknown method %s invoked on interface %s", - method_name, interface_name); - return; - } - - WebKitWebPage *page = webkit_web_extension_get_page (pctxt->ctxt->extension, - pctxt->page_id); - if (page == NULL) - return; - /* The page may have been destroyed, but WebKit doesn't let us find out. */ - - WebKitDOMDocument *document = webkit_web_page_get_dom_document (page); - if (document == NULL) - { - g_dbus_method_invocation_return_error_literal (invocation, G_IO_ERROR, - G_IO_ERROR_NOT_INITIALIZED, - "The web page has not loaded a document yet"); - return; - } - - translate_html (document, pctxt->ctxt); - - g_dbus_method_invocation_return_value (invocation, NULL); -} - -static GDBusInterfaceVTable dbus_impl_vtable = { - (GDBusInterfaceMethodCallFunc) on_wh2_method_call, - NULL, /* get property */ - NULL /* set property */ -}; - -static void -register_object (guint64 page_id, - Context *ctxt) -{ - GError *error = NULL; - - g_assert (ctxt->connection != NULL); - - gchar *object_path = g_strdup_printf("/com/endlessm/webview/%" - G_GUINT64_FORMAT, page_id); - - /* This struct is owned by the registered DBus object */ - PageContext *pctxt = g_new0 (PageContext, 1); - pctxt->ctxt = ctxt; - pctxt->page_id = page_id; - - guint bus_id = - g_dbus_connection_register_object (ctxt->connection, object_path, - ctxt->interface, &dbus_impl_vtable, - pctxt, (GDestroyNotify) g_free, &error); - g_free (object_path); - if (bus_id == 0) - { - g_critical ("Failed to export webview object on bus: %s", error->message); - g_clear_error (&error); - goto fail; - } - - ctxt->bus_ids = g_slist_prepend (ctxt->bus_ids, GUINT_TO_POINTER (bus_id)); - return; - -fail: - g_free (pctxt); -} - -static void -on_page_created (WebKitWebExtension *extension, - WebKitWebPage *page, - Context *ctxt) -{ - /* The ID is known to the main process and the web process. So we can address - a specific web page over DBus. */ - guint64 id = webkit_web_page_get_id (page); - - if (ctxt->connection == NULL) - { - /* The connection is not ready yet. Save the page ID in a list of pages - for which we need to register objects later. */ - g_array_append_val (ctxt->unregistered_pages, id); - return; - } - - register_object (id, ctxt); -} - -/* 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; - - JSObjectRef ngettext_func = - JSObjectMakeFunctionWithCallback (js, NULL, ngettext_shim); - if (!set_object_property (js, window, "ngettext", (JSValueRef) ngettext_func, - kJSPropertyAttributeNone)) - return; -} - -static void -on_bus_acquired (GDBusConnection *connection, - const gchar *name, - Context *ctxt) -{ - GError *error = NULL; - - ctxt->connection = connection; - - /* Export our interface on the bus */ - ctxt->node = g_dbus_node_info_new_for_xml (introspection_xml, &error); - if (ctxt->node == NULL) - goto fail; - ctxt->interface = g_dbus_node_info_lookup_interface (ctxt->node, - WH2_DBUS_INTERFACE_NAME); - if (ctxt->interface == NULL) - goto fail; - - /* Register DBus objects for any pages that were created before we got here */ - guint ix; - for (ix = 0; ix < ctxt->unregistered_pages->len; ix++) - { - guint64 id = g_array_index (ctxt->unregistered_pages, guint64, ix); - register_object (id, ctxt); - } - g_array_remove_range (ctxt->unregistered_pages, 0, - ctxt->unregistered_pages->len); - - return; - -fail: - if (error != NULL) - { - g_critical ("Error hooking up web extension DBus interface: %s", - error->message); - g_clear_error (&error); - } - else - { - g_critical ("Unknown error hooking up web extension DBus interface"); - } -} - -static void -unregister_object (gpointer data, - GDBusConnection *connection) -{ - guint bus_id = GPOINTER_TO_UINT (data); - if (!g_dbus_connection_unregister_object (connection, bus_id)) - g_critical ("Trouble unregistering object"); -} - -static void -on_name_lost (GDBusConnection *connection, - const gchar *name, - Context *ctxt) -{ - if (connection == NULL) - { - g_warning ("Could not initialize DBus interface for WebHelper2 " - "extension; the name %s was lost.", name); - return; - } - - g_slist_foreach (ctxt->bus_ids, (GFunc) unregister_object, connection); -} - -/* Receives the main program's unique DBus name as user data. */ -G_MODULE_EXPORT void -webkit_web_extension_initialize_with_user_data (WebKitWebExtension *extension, - const GVariant *data_from_app) -{ - const gchar *name = g_variant_get_string ((GVariant *) data_from_app, NULL); - - Context *ctxt = g_new0 (Context, 1); - ctxt->extension = extension; - ctxt->unregistered_pages = g_array_new (FALSE, FALSE, sizeof (guint64)); - ctxt->main_program_name = g_strdup (name); - gchar *well_known_name = g_strconcat (name, ".webhelper", NULL); - - g_signal_connect (extension, "page-created", - G_CALLBACK (on_page_created), ctxt); - - g_bus_own_name (G_BUS_TYPE_SESSION, well_known_name, - G_BUS_NAME_OWNER_FLAGS_NONE, - (GBusAcquiredCallback) on_bus_acquired, - NULL, /* name acquired callback */ - (GBusNameLostCallback) on_name_lost, - ctxt, (GDestroyNotify) context_free); - - g_free (well_known_name); - - /* Get a notification when Javascript is ready. In this callback it's possible - that the DBus connection has not been acquired yet, so we have sync waits - later if JS tries to call DBus. However, connecting to this signal later - doesn't work because it will often already have been fired before the DBus - connection is acquired. */ - WebKitScriptWorld *script_world = webkit_script_world_get_default (); - g_signal_connect (script_world, "window-object-cleared", - G_CALLBACK (on_window_object_cleared), ctxt); -} diff --git a/webhelper/webextensions/wh2jscutil.c b/webhelper/webextensions/wh2jscutil.c deleted file mode 100644 index 34f325f..0000000 --- a/webhelper/webextensions/wh2jscutil.c +++ /dev/null @@ -1,65 +0,0 @@ -/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ - -/* Copyright 2015 Endless Mobile, Inc. */ - -#include -#include - -#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 deleted file mode 100644 index af800e5..0000000 --- a/webhelper/webextensions/wh2jscutil.h +++ /dev/null @@ -1,25 +0,0 @@ -/* -*- 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 -#include - -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 */ diff --git a/webhelper/webhelper.js b/webhelper/webhelper.js deleted file mode 100644 index d4f6811..0000000 --- a/webhelper/webhelper.js +++ /dev/null @@ -1,290 +0,0 @@ -const Endless = imports.gi.Endless; -const Format = imports.format; -const GLib = imports.gi.GLib; -const Gtk = imports.gi.Gtk; -const Lang = imports.lang; -const WebKit = imports.gi.WebKit; - -String.prototype.format = Format.format; - -const EOS_URI_SCHEME = 'endless://'; - -/** - * Namespace: WebHelper - * Convenience library for running web applications - * - * WebHelper is a convenience library for displaying web applications inside a - * GTK container running WebKitGTK (currently WebKit 1, not 2.) - * 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 - * , 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 - * . - */ - -/** - * Class: Application - * Main application class for a WebHelper application - * - * The application class in GJS for your web application should extend - * WebHelper.Application. - * - * You should set up any WebViews that you create, by connecting - * , so that they can handle custom action URIs. - */ -const Application = new Lang.Class({ - Name: 'WebApplication', - Extends: Endless.Application, - - _init: function(props) { - this._webActions = {}; - this._translationFunction = null; - this.parent(props); - }, - - /** - * 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: - * > This one goes to 11 - * 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._webActions[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 on each property of _dict_. - * - * *Note* This API is Javascript-only. It will not be implemented in C. - */ - define_web_actions: function(dict) { - for(let key in dict) { - if(dict.hasOwnProperty(key)) { - this.define_web_action(key, dict[key]); - } - } - }, - - /** - * Callback: web_actions_handler - * Navigation callback which routes the custom URIs to actions - * - * When you create a web view in which you want to run a web application - * that uses custom action URIs, remember to set it up by connecting this - * callback to its "navigation-policy-decision-requested" signal. The - * following code will do this: - * - * > webview.connect('navigation-policy-decision-requested', - * > Lang.bind(app, app.web_actions_handler)); - * - * where 'webview' is the web view, and 'app' is the WebHelper.Application - * instance. - */ - web_actions_handler: function(webview, frame, request, action, policy_decision) { - let uri = request.get_uri(); - - if(uri.indexOf(EOS_URI_SCHEME) !== 0) { - // this is a regular URL, just navigate there - return false; - } - - // get the name and parameters for the desired function - let f_call = uri.substring(EOS_URI_SCHEME.length, uri.length).split('?'); - var function_name = decodeURI(f_call[0]); - var 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]; - } - }); - } - - if(this._webActions[function_name]) - Lang.bind(this, this._webActions[function_name])(parameters); - else - throw new Error("Undefined WebHelper action '%s'. Did you define " + - "it with WebHelper.Application.define_web_action()?".format( - function_name)); - - policy_decision.ignore(); - return true; - }, - - // convenience function - - _getElementById: function(webview, id) { - // WebKit.DOMDocument - let dom = webview.get_dom_document(); - - // WebKit.DOMElement - return dom.get_element_by_id(id); - }, - - /** - * Method: set_translation_function - * Define function which transforms all translatable text - * - * Parameters: - * translation_function - a function, or null - * - * When you plan to use the function 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(). - * - * Pass null for _translation_function_ 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 . - */ - set_translation_function: function(translation_function) { - if(translation_function !== null - && typeof translation_function !== 'function') { - throw new Error('The translation function must be a function, or ' + - 'null.'); - } - this._translationFunction = translation_function; - }, - - /** - * Method: get_translation_function - * Retrieve the currently set translation function - * - * Returns: - * the translation function previously set with - * , or null if none is currently set. - */ - get_translation_function: function() { - return this._translationFunction; - }, - - /** - * Method: translate_html - * Translate all translatable text currently showing in a web view - * - * Parameters: - * webview - the WebView containing the text to translate. - * - * Another problem with running web applications inside a GTK container is - * keeping all the localization data in the same place. - * For example, the most obvious way to do localization in a GTK application - * is to use gettext(), but that does not work so easily inside a web view. - * - * In a , you can mark strings in your HTML for - * translation by enclosing them in - * > String to be translated - * or just putting the "translatable" name directly on the element - * containing the string, e.g.

or

. - * - * Then after the web view has finished loading, call on - * it, and each of the marked strings will be passed to the function that - * you specify using property. - * The return value from the translation function will be substituted into - * the HTML instead of the original string. - * - * Example: - * > app.set_translation_function(_); - * > webview.connect('notify::load-status', - * > Lang.bind(app, function(webview) { - * > if(webview.load_status == WebKit.LoadStatus.FINISHED) - * > this.translate_html(webview); - * > })); - */ - translate_html: function(webview) { - let dom = webview.get_dom_document(); - - // WebKit.DOMNodeList - let translatable = dom.get_elements_by_name('translatable'); - - for (var i = 0 ; i < translatable.get_length() ; i++) { - // WebKit.DOMNode - let element = translatable.item(i); - - // Translate the text - if(typeof this._translationFunction !== 'function') - throw new Error("No suitable translation function was found. " + - "Did you forget to call 'set_translation_function()' on " + - "your app?"); - element.inner_html = this._translationFunction(element.inner_text); - } - }, - - /** - * Method: set_web_settings_font_resizable - * Set an eos_window to update font size of web_settings - * - * Parameters: - * eos_windw - an - * web_settings - a - * - * The will be connected on its "size-allocate" signal - * to the given . The will update the - * "default-font-size" property of the calculated font size - * to the 's calculated font size. - */ - set_web_settings_font_resizable: function (eos_window, web_settings) { - eos_window.connect('size-allocate', - Lang.bind(this, function (widget, allocation) { - if (eos_window.font_scaling_active) { - web_settings.default_font_size = eos_window.font_scaling_calculated_font_size; - } - } - )); - } -}); diff --git a/webhelper/webhelper2.js b/webhelper/webhelper2.js deleted file mode 100644 index ea25bc1..0000000 --- a/webhelper/webhelper2.js +++ /dev/null @@ -1,529 +0,0 @@ -// 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 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 = '\ - \ - \ - \ - \ - '; -const WH2_DBUS_MAIN_PROGRAM_INTERFACE = '\ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - '; - -/** - * 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 - * , 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 - * . - * 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 after - * registering itself in the session bus (i.e. not inside *vfunc_dbus_register()*), - * with appropriate and 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 . - * - * 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 . - * - * 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 - * . - * 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 . - * - * 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 , 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 , 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 - * 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 - * . - * - * When the translation is finished, _callback_ will be called. - * You can get the result of the operation by calling - * 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 - * - * Parameters: - * res - result object passed to your callback - * - * Finishes an operation started by . - * 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: - * > This one goes to 11 - * 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 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/webhelper_private/config.js.in b/webhelper/webhelper_private/config.js.in deleted file mode 100644 index f9d87cb..0000000 --- a/webhelper/webhelper_private/config.js.in +++ /dev/null @@ -1 +0,0 @@ -const LIBEXECDIR = '%libexecdir%'; -- cgit v1.2.3