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 190942 95ade88228e86e63bf5597bd3359344f27eebac1
parent 190941 e709613cc827aedf3f4bfb3f998d10d4d4b9541e
child 190943 b4fcdf70278fe8baabb05a862e008020c0c8483a
push id27022
push userryanvm@gmail.com
push dateThu, 26 Jun 2014 20:33:29 +0000
treeherdermozilla-central@1b16fb9ff352 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersMattN
bugs1020495
milestone33.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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]