summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--test/webhelper/smoke-tests/webview.js90
-rw-r--r--test/webhelper/testTranslate.js39
-rw-r--r--test/webhelper/testWebActions.js87
-rw-r--r--webhelper/webhelper.js126
4 files changed, 228 insertions, 114 deletions
diff --git a/test/webhelper/smoke-tests/webview.js b/test/webhelper/smoke-tests/webview.js
index 133d4ce..a3b91e5 100644
--- a/test/webhelper/smoke-tests/webview.js
+++ b/test/webhelper/smoke-tests/webview.js
@@ -60,52 +60,46 @@ const TestApplication = new Lang.Class({
Name: 'TestApplication',
Extends: WebHelper.Application,
- _translationFunction: function(string) {
- return string.italics();
+ /* *** ACTIONS AVAILABLE FROM THE WEB VIEW *** */
+
+ /* dict['name'] is the name of the page to move to */
+ moveToPage: function(dict) {
+ this._pm.visible_page_name = dict['name'];
},
- /* *** ACTIONS AVAILABLE FROM THE WEB VIEW *** */
+ /* 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();
- _webActions: {
- /* dict['name'] is the name of the page to move to */
- moveToPage: function(dict) {
- this._pm.visible_page_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 += '★ ';
- }
+ 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 += '★ ';
},
/* *************************** */
@@ -113,6 +107,16 @@ const TestApplication = new Lang.Class({
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',
diff --git a/test/webhelper/testTranslate.js b/test/webhelper/testTranslate.js
index 10e389c..651ddf5 100644
--- a/test/webhelper/testTranslate.js
+++ b/test/webhelper/testTranslate.js
@@ -49,24 +49,47 @@ function setUp() {
function testStringIsTranslated() {
let translationFunctionWasCalled = false;
let translationFunctionCalledWithString;
- app._translationFunction = function(s) {
+ app.set_translation_function(function(s) {
translationFunctionWasCalled = true;
translationFunctionCalledWithString = s;
return s;
- };
+ });
app.run([]);
assertTrue(translationFunctionWasCalled);
assertEquals('Translate Me', translationFunctionCalledWithString);
}
-// The following two tests are commented out because GJS cannot catch exceptions
+// The following test is commented out because GJS cannot catch exceptions
// across FFI interfaces (e.g. in GObject callbacks.)
// function testMissingTranslationFunctionIsHandled() {
-// assertRaises(app.run([]));
+// assertRaises(function() {
+// app.run([]);
+// });
// }
-// function testBadTranslationFunctionIsHandled() {
-// app._translationFunction = "I am not a function";
-// assertRaises(app.run([]));
-// } \ No newline at end of file
+function testSetBadTranslationFunction() {
+ assertRaises(function() {
+ app.set_translation_function("I am not a function");
+ });
+}
+
+function testGetSetTranslationFunction() {
+ let translationFunction = function(string) {
+ return string;
+ };
+ app.set_translation_function(translationFunction);
+ let actualTranslationFunction = app.get_translation_function();
+ assertEquals(translationFunction, actualTranslationFunction);
+}
+
+function testTranslationFunctionIsNullByDefault() {
+ assertNull(app.get_translation_function());
+}
+
+function testGetSetNullTranslationFunction() {
+ app.set_translation_function(function (s) { return s; });
+ assertNotNull(app.get_translation_function());
+ app.set_translation_function(null);
+ assertNull(app.get_translation_function());
+}
diff --git a/test/webhelper/testWebActions.js b/test/webhelper/testWebActions.js
index 585ab00..72ea6ba 100644
--- a/test/webhelper/testWebActions.js
+++ b/test/webhelper/testWebActions.js
@@ -45,12 +45,10 @@ function setUp() {
function testWebActionIsCalled() {
let actionWasCalled = false;
- app._webActions = {
- quitApplication: function() {
- actionWasCalled = true;
- app.quit();
- }
- };
+ app.define_web_action('quitApplication', function() {
+ actionWasCalled = true;
+ app.quit();
+ });
app.webActionToTest = 'endless://quitApplication';
app.run([]);
assertTrue(actionWasCalled);
@@ -58,12 +56,10 @@ function testWebActionIsCalled() {
function testWebActionIsCalledWithParameter() {
let actionParameter;
- app._webActions = {
- getParameterAndQuit: function(dict) {
- actionParameter = dict['param'];
- app.quit();
- }
- };
+ app.define_web_action('getParameterAndQuit', function(dict) {
+ actionParameter = dict['param'];
+ app.quit();
+ });
app.webActionToTest = 'endless://getParameterAndQuit?param=value';
app.run([]);
assertEquals('value', actionParameter);
@@ -71,14 +67,12 @@ function testWebActionIsCalledWithParameter() {
function testWebActionIsCalledWithManyParameters() {
let firstParameter, secondParameter, thirdParameter;
- app._webActions = {
- getParametersAndQuit: function(dict) {
- firstParameter = dict['first'];
- secondParameter = dict['second'];
- thirdParameter = dict['third'];
- app.quit();
- }
- };
+ app.define_web_action('getParametersAndQuit', function(dict) {
+ firstParameter = dict['first'];
+ secondParameter = dict['second'];
+ thirdParameter = dict['third'];
+ app.quit();
+ });
app.webActionToTest = 'endless://getParametersAndQuit?first=thefirst&second=thesecond&third=thethird';
app.run([]);
assertEquals('thefirst', firstParameter);
@@ -89,12 +83,10 @@ function testWebActionIsCalledWithManyParameters() {
function testParameterNameIsUriDecoded() {
let expectedParameter = 'päräm💩';
let parameterWasFound = false;
- app._webActions = {
- getUriDecodedParameterAndQuit: function(dict) {
- parameterWasFound = (expectedParameter in dict);
- app.quit();
- }
- };
+ app.define_web_action('getUriDecodedParameterAndQuit', function(dict) {
+ parameterWasFound = (expectedParameter in dict);
+ app.quit();
+ });
app.webActionToTest = 'endless://getUriDecodedParameterAndQuit?p%C3%A4r%C3%A4m%F0%9F%92%A9=value';
app.run([]);
assertTrue(parameterWasFound);
@@ -103,12 +95,10 @@ function testParameterNameIsUriDecoded() {
function testParameterValueIsUriDecoded() {
let expectedValue = 'válué💩';
let actualValue;
- app._webActions = {
- getUriDecodedValueAndQuit: function(dict) {
- actualValue = dict['param'];
- app.quit();
- }
- };
+ app.define_web_action('getUriDecodedValueAndQuit', function(dict) {
+ actualValue = dict['param'];
+ app.quit();
+ });
app.webActionToTest = 'endless://getUriDecodedValueAndQuit?param=v%C3%A1lu%C3%A9%F0%9F%92%A9';
app.run([]);
assertEquals(expectedValue, actualValue);
@@ -124,17 +114,34 @@ function testParameterValueIsUriDecoded() {
function testWebActionIsCalledWithBlankParameter() {
let parameterWasFound = false;
let parameterValue;
- app._webActions = {
- getBlankValueAndQuit: function(dict) {
- parameterWasFound = ('param' in dict);
- if(parameterWasFound)
- parameterValue = dict['param'];
- app.quit();
- }
- };
+ app.define_web_action('getBlankValueAndQuit', function(dict) {
+ parameterWasFound = ('param' in dict);
+ if(parameterWasFound)
+ parameterValue = dict['param'];
+ app.quit();
+ });
app.webActionToTest = 'endless://getBlankValueAndQuit?param=';
app.run([]);
assertTrue(parameterWasFound);
assertNotUndefined(parameterValue);
assertEquals('', parameterValue);
}
+
+function testDefineMultipleActionsOverride() {
+ let actionWasCalled = false;
+ app.define_web_actions({
+ quitApplication: function() {
+ actionWasCalled = true;
+ app.quit();
+ }
+ });
+ app.webActionToTest = 'endless://quitApplication';
+ app.run([]);
+ assertTrue(actionWasCalled);
+}
+
+function testDefineBadAction() {
+ assertRaises(function() {
+ app.define_web_action('badAction', 'not a function');
+ });
+}
diff --git a/webhelper/webhelper.js b/webhelper/webhelper.js
index 1992973..dc1f842 100644
--- a/webhelper/webhelper.js
+++ b/webhelper/webhelper.js
@@ -1,6 +1,7 @@
const Endless = imports.gi.Endless;
const GLib = imports.gi.GLib;
const Gtk = imports.gi.Gtk;
+const Lang = imports.lang;
const WebKit = imports.gi.WebKit;
const EOS_URI_SCHEME = 'endless://';
@@ -17,8 +18,8 @@ const EOS_URI_SCHEME = 'endless://';
*
* 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 in
- * <Application._webActions>, and you can pass parameters to the
+ * The custom URI corresponds to a function that you define using
+ * <Application.define_web_action()>, and you can pass parameters to the
* function.
*
* Another often-encountered problem is localizing text through the same API as
@@ -42,18 +43,70 @@ const Application = new Lang.Class({
Name: 'WebApplication',
Extends: Endless.Application,
+ _init: function(props) {
+ this._webActions = {};
+ this._translationFunction = null;
+ this.parent(props);
+ },
+
/**
- * Property: _webActions
- * Set of actions that may be invoked from a WebView
+ * 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.)
*
- * Declare them as a function that takes a dict as a parameter, and use
- * links with the format "endless://actionName?parameter=value"
+ * Callback Parameters:
+ * - dict: object containing properties corresponding to the query
+ * parameters that the web action was called with
*
- * *Note* This API will likely disappear and be replaced by a method
- * add_web_action().
- * That is the reason for the underscore starting the name.
+ * Sets up an action that may be invoked from an HTML document inside a
+ * WebView, or from the in-browser Javascript environment inside a WebView.
+ * If you set up an action "setVolume" as follows:
+ * > app.define_web_action('setVolume', function(dict) { ... });
+ * Then you can invoke the action inside the HTML document, e.g. as the
+ * target of a link, as follows:
+ * > <a href="endless://setVolume?volume=11">This one goes to 11</a>
+ * Or from the in-browser Javascript, by navigating to the action URI, as
+ * follows:
+ * > window.location = '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.
*/
- _webActions: { },
+ 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 <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) {
+ for(let key in dict) {
+ if(dict.hasOwnProperty(key)) {
+ this.define_web_action(key, dict[key]);
+ }
+ }
+ },
/**
* Callback: web_actions_handler
@@ -101,8 +154,9 @@ const Application = new Lang.Class({
if(this._webActions[function_name])
Lang.bind(this, this._webActions[function_name])(parameters);
else
- throw new Error("Undefined WebHelper action '%s'. Did you add it " +
- "to your app's _webActions object?".format(function_name));
+ 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;
@@ -119,18 +173,43 @@ const Application = new Lang.Class({
},
/**
- * Property: _translationFunction
- * Function which transforms all translatable text
+ * Method: set_translation_function
+ * Define function which transforms all translatable text
+ *
+ * Parameters:
+ * translation_function - a function, or null
*
* When you plan to use the <translate_html()> 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 -- for example, gettext().
+ * string.
+ * The canonical example is gettext().
+ *
+ * Pass null for _translation_function_ to unset the translation function.
*
- * *Note* This API will likely disappear and be replaced by a read-write
- * translation_function property.
- * That is the reason for the underscore starting the name.
+ * If this function has not been called, or has most recently been called
+ * with null, then it is illegal to call <translate_html()>.
*/
+ 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
+ * <set_translation_function()>, or null if none is currently set.
+ */
+ get_translation_function: function() {
+ return this._translationFunction;
+ },
/**
* Method: translate_html
@@ -152,12 +231,12 @@ const Application = new Lang.Class({
*
* Then after the web view has finished loading, call <translate_html()> on
* it, and each of the marked strings will be passed to the function that
- * you specify using the <_translationFunction> property.
- * The return value from <_translationFunction> will be substituted into the
- * HTML instead of the original string.
+ * you specify using <set_translation_function()> property.
+ * The return value from the translation function will be substituted into
+ * the HTML instead of the original string.
*
* Example:
- * > app._translationFunction = _;
+ * > app.set_translation_function(_);
* > webview.connect('notify::load-status',
* > Lang.bind(app, function(webview) {
* > if(webview.load_status == WebKit.LoadStatus.FINISHED)
@@ -177,7 +256,8 @@ const Application = new Lang.Class({
// Translate the text
if(typeof this._translationFunction !== 'function')
throw new Error("No suitable translation function was found. " +
- "Did you forget to set '_translationFunction' on your app?");
+ "Did you forget to call 'set_translation_function()' on " +
+ "your app?");
element.inner_html = this._translationFunction(element.inner_text);
}
}