Bug 1020495 - Add registration of requestAutocomplete UI handlers on initialization. r=MattN
authorPaolo Amadini <paolo.mozmail@amadzone.org>
Thu, 26 Jun 2014 14:32:24 +0100
changeset 190990 95ade88228e86e63bf5597bd3359344f27eebac1
parent 190989 e709613cc827aedf3f4bfb3f998d10d4d4b9541e
child 190991 b4fcdf70278fe8baabb05a862e008020c0c8483a
push id8417
push userryanvm@gmail.com
push dateThu, 26 Jun 2014 21:15:47 +0000
treeherderb2g-inbound@3bef42144aab [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersMattN
bugs1020495
milestone33.0a1
Bug 1020495 - Add registration of requestAutocomplete UI handlers on initialization. r=MattN
toolkit/components/formautofill/FormAutofill.jsm
toolkit/components/formautofill/FormAutofillIntegration.jsm
toolkit/components/formautofill/moz.build
toolkit/components/formautofill/test/head_common.js
toolkit/components/formautofill/test/xpcshell/test_integration.js
toolkit/components/formautofill/test/xpcshell/xpcshell.ini
new file mode 100644
--- /dev/null
+++ b/toolkit/components/formautofill/FormAutofill.jsm
@@ -0,0 +1,115 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Main module handling references to objects living in the main process.
+ */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = [
+  "FormAutofill",
+];
+
+const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "FormAutofillIntegration",
+                                  "resource://gre/modules/FormAutofillIntegration.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Promise",
+                                  "resource://gre/modules/Promise.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+                                  "resource://gre/modules/Task.jsm");
+
+/**
+ * Main module handling references to objects living in the main process.
+ */
+this.FormAutofill = {
+  /**
+   * Dynamically generated object implementing the FormAutofillIntegration
+   * methods.  Platform-specific code and add-ons can override methods of this
+   * object using the registerIntegration method.
+   */
+  get integration() {
+    // This lazy getter is only called if registerIntegration was never called.
+    this._refreshIntegrations();
+    return this.integration;
+  },
+
+  /**
+   * Registers new overrides for the FormAutofillIntegration methods.  Example:
+   *
+   *   FormAutofill.registerIntegration(base => ({
+   *     createRequestAutocompleteUI: Task.async(function* () {
+   *       yield base.createRequestAutocompleteUI.apply(this, arguments);
+   *     }),
+   *   }));
+   *
+   * @param aIntegrationFn
+   *        Function returning an object defining the methods that should be
+   *        overridden.  Its only parameter is an object that contains the base
+   *        implementation of all the available methods.
+   *
+   * @note The integration function is called every time the list of registered
+   *       integration functions changes.  Thus, it should not have any side
+   *       effects or do any other initialization.
+   */
+  registerIntegration: function (aIntegrationFn) {
+    this._integrationFns.add(aIntegrationFn);
+    this._refreshIntegrations();
+  },
+
+  /**
+   * Removes a previously registered FormAutofillIntegration override.
+   *
+   * Overrides don't usually need to be unregistered, unless they are added by a
+   * restartless add-on, in which case they should be unregistered when the
+   * add-on is disabled or uninstalled.
+   *
+   * @param aIntegrationFn
+   *        This must be the same function object passed to registerIntegration.
+   */
+  unregisterIntegration: function (aIntegrationFn) {
+    this._integrationFns.delete(aIntegrationFn);
+    this._refreshIntegrations();
+  },
+
+  /**
+   * Ordered list of registered functions defining integration overrides.
+   */
+  _integrationFns: new Set(),
+
+  /**
+   * Updates the "integration" getter with the object resulting from combining
+   * all the registered integration overrides with the default implementation.
+   */
+  _refreshIntegrations: function () {
+    delete this.integration;
+
+    let combined = FormAutofillIntegration;
+    for (let integrationFn of this._integrationFns) {
+      try {
+        // Obtain a new set of methods from the next integration function in the
+        // list, specifying the current combined object as the base argument.
+        let integration = integrationFn.call(null, combined);
+
+        // Retrieve a list of property descriptors from the returned object, and
+        // use them to build a new combined object whose prototype points to the
+        // previous combined object.
+        let descriptors = {};
+        for (let name of Object.getOwnPropertyNames(integration)) {
+          descriptors[name] = Object.getOwnPropertyDescriptor(integration, name);
+        }
+        combined = Object.create(combined, descriptors);
+      } catch (ex) {
+        // Any error will result in the current integration being skipped.
+        Cu.reportError(ex);
+      }
+    }
+
+    this.integration = combined;
+  },
+};
new file mode 100644
--- /dev/null
+++ b/toolkit/components/formautofill/FormAutofillIntegration.jsm
@@ -0,0 +1,48 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * This module defines the default implementation of platform-specific functions
+ * that can be overridden by the host application and by add-ons.
+ *
+ * This module should not be imported directly, but the "integration" getter of
+ * the FormAutofill module should be used to get a reference to the currently
+ * defined implementations of the methods.
+ */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = [
+  "FormAutofillIntegration",
+];
+
+const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "Promise",
+                                  "resource://gre/modules/Promise.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+                                  "resource://gre/modules/Task.jsm");
+
+/**
+ * This module defines the default implementation of platform-specific functions
+ * that can be overridden by the host application and by add-ons.
+ */
+this.FormAutofillIntegration = {
+  /**
+   * Creates a new RequestAutocompleteUI object.
+   *
+   * @param aProperties
+   *        Provides the initial properties for the newly created object.
+   *
+   * @return {Promise}
+   * @resolves The newly created RequestAutocompleteUI object.
+   * @rejects JavaScript exception.
+   */
+  createRequestAutocompleteUI: Task.async(function* (aProperties) {
+    return {};
+  }),
+};
--- a/toolkit/components/formautofill/moz.build
+++ b/toolkit/components/formautofill/moz.build
@@ -22,8 +22,13 @@ XPIDL_SOURCES += [
 ]
 
 XPIDL_MODULE = 'toolkit_formautofill'
 
 EXTRA_COMPONENTS += [
     'formautofill.manifest',
     'FormAutofillContentService.js',
 ]
+
+EXTRA_JS_MODULES += [
+    'FormAutofill.jsm',
+    'FormAutofillIntegration.jsm',
+]
--- a/toolkit/components/formautofill/test/head_common.js
+++ b/toolkit/components/formautofill/test/head_common.js
@@ -6,16 +6,18 @@
  */
 
 "use strict";
 
 XPCOMUtils.defineLazyModuleGetter(this, "DownloadPaths",
                                   "resource://gre/modules/DownloadPaths.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
                                   "resource://gre/modules/FileUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "FormAutofill",
+                                  "resource://gre/modules/FormAutofill.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
                                   "resource://gre/modules/NetUtil.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Promise",
                                   "resource://gre/modules/Promise.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Task",
                                   "resource://gre/modules/Task.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "OS",
                                   "resource://gre/modules/osfile.jsm");
new file mode 100644
--- /dev/null
+++ b/toolkit/components/formautofill/test/xpcshell/test_integration.js
@@ -0,0 +1,61 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * Tests overriding the FormAutofillIntegration module functions.
+ */
+
+"use strict";
+
+/**
+ * Registers and unregisters an integration override function.
+ */
+add_task(function* test_integration_override() {
+  let overrideCalled = false;
+
+  let newIntegrationFn = base => ({
+    createRequestAutocompleteUI: Task.async(function* () {
+      yield base.createRequestAutocompleteUI.apply(this, arguments);
+      overrideCalled = true;
+    }),
+  });
+
+  FormAutofill.registerIntegration(newIntegrationFn);
+  try {
+    yield FormAutofill.integration.createRequestAutocompleteUI({});
+  } finally {
+    FormAutofill.unregisterIntegration(newIntegrationFn);
+  }
+
+  Assert.ok(overrideCalled);
+});
+
+/**
+ * Registers an integration override function that throws an exception, and
+ * ensures that this does not block other functions from being registered.
+ */
+add_task(function* test_integration_override_error() {
+  let overrideCalled = false;
+
+  let errorIntegrationFn = base => { throw "Expected error." };
+
+  let newIntegrationFn = base => ({
+    createRequestAutocompleteUI: Task.async(function* () {
+      yield base.createRequestAutocompleteUI.apply(this, arguments);
+      overrideCalled = true;
+    }),
+  });
+
+  FormAutofill.registerIntegration(errorIntegrationFn);
+  FormAutofill.registerIntegration(newIntegrationFn);
+  try {
+    yield FormAutofill.integration.createRequestAutocompleteUI({});
+  } finally {
+    FormAutofill.unregisterIntegration(errorIntegrationFn);
+    FormAutofill.unregisterIntegration(newIntegrationFn);
+  }
+
+  Assert.ok(overrideCalled);
+});
+
+add_task(terminationTaskFn);
--- a/toolkit/components/formautofill/test/xpcshell/xpcshell.ini
+++ b/toolkit/components/formautofill/test/xpcshell/xpcshell.ini
@@ -1,5 +1,6 @@
 [DEFAULT]
 head = head.js ../head_common.js
 tail =
 
 [test_infrastructure.js]
+[test_integration.js]