Bug 1474143 - Switch earlyformsubmit satchel observer to DOMFormBeforeSubmit listener. r=Felipe
authorMatthew Noorenberghe <mozilla@noorenberghe.ca>
Sat, 23 Feb 2019 00:24:52 +0000
changeset 460714 25f5c0d8531da828351138e8db57287cc696a28e
parent 460713 bb1ecf178495365b6260d599bc10dde20134a3c1
child 460715 1eea6e958cca92263842de0fbbe112ea09e9ba91
push id78821
push usermozilla@noorenberghe.ca
push dateSat, 23 Feb 2019 00:27:30 +0000
treeherderautoland@5a471d8ee829 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersFelipe
bugs1474143
milestone67.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 1474143 - Switch earlyformsubmit satchel observer to DOMFormBeforeSubmit listener. r=Felipe Extend ActorChild for satchel's formSubmitListener in order to listen to the event. Differential Revision: https://phabricator.services.mozilla.com/D16655
browser/base/content/test/performance/browser_startup_content.js
toolkit/components/satchel/FormHistoryStartup.jsm
toolkit/components/satchel/FormSubmitChild.jsm
toolkit/components/satchel/formSubmitListener.js
toolkit/components/satchel/jar.mn
toolkit/components/satchel/moz.build
toolkit/content/tests/chrome/test_autocomplete_with_composition_on_input.html
toolkit/content/tests/chrome/test_editor_for_input_with_autocomplete.html
toolkit/modules/ActorManagerParent.jsm
--- a/browser/base/content/test/performance/browser_startup_content.js
+++ b/browser/base/content/test/performance/browser_startup_content.js
@@ -83,17 +83,16 @@ const whitelist = {
 
     // Extensions
     "resource://gre/modules/addons/Content.js",
   ]),
   processScripts: new Set([
     "chrome://global/content/process-content.js",
     "resource:///modules/ContentObservers.js",
     "data:,ChromeUtils.import('resource://gre/modules/ExtensionProcessScript.jsm')",
-    "chrome://satchel/content/formSubmitListener.js",
     "resource://devtools/client/jsonview/converter-observer.js",
     "resource://gre/modules/WebRequestContent.js",
     "data:,new function() {\n      ChromeUtils.import(\"resource://formautofill/FormAutofillContent.jsm\");\n    }",
   ]),
 };
 
 // Items on this list are allowed to be loaded but not
 // required, as opposed to items in the main whitelist,
--- a/toolkit/components/satchel/FormHistoryStartup.jsm
+++ b/toolkit/components/satchel/FormHistoryStartup.jsm
@@ -42,18 +42,17 @@ FormHistoryStartup.prototype = {
     this.inited = true;
 
     Services.prefs.addObserver("browser.formfill.", this, true);
 
     // triggers needed service cleanup and db shutdown
     Services.obs.addObserver(this, "idle-daily", true);
     Services.obs.addObserver(this, "formhistory-expire-now", true);
 
-    Services.ppmm.loadProcessScript("chrome://satchel/content/formSubmitListener.js", true);
-    Services.ppmm.addMessageListener("FormHistory:FormSubmitEntries", this);
+    Services.mm.addMessageListener("FormHistory:FormSubmitEntries", this);
 
     // For each of these messages, we could receive them from content,
     // or we might receive them from the ppmm if the searchbar is
     // having its history queried.
     for (let manager of [Services.mm, Services.ppmm]) {
       manager.addMessageListener("FormHistory:AutoCompleteSearchAsync", this);
       manager.addMessageListener("FormHistory:RemoveEntry", this);
     }
rename from toolkit/components/satchel/formSubmitListener.js
rename to toolkit/components/satchel/FormSubmitChild.jsm
--- a/toolkit/components/satchel/formSubmitListener.js
+++ b/toolkit/components/satchel/FormSubmitChild.jsm
@@ -1,150 +1,152 @@
 /* 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/. */
 
-/* eslint-env mozilla/frame-script */
+"use strict";
+
+var EXPORTED_SYMBOLS = ["FormSubmitChild"];
+
+const {ActorChild} = ChromeUtils.import("resource://gre/modules/ActorChild.jsm");
+const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
 
 ChromeUtils.defineModuleGetter(this, "CreditCard",
                                "resource://gre/modules/CreditCard.jsm");
 ChromeUtils.defineModuleGetter(this, "PrivateBrowsingUtils",
                                "resource://gre/modules/PrivateBrowsingUtils.jsm");
 
-(function() {
-const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
+class FormSubmitChild extends ActorChild {
+  constructor(dispatcher) {
+    super(dispatcher);
 
-let satchelFormListener = {
-  QueryInterface: ChromeUtils.generateQI([
-    Ci.nsIFormSubmitObserver,
-    Ci.nsIObserver,
-    Ci.nsISupportsWeakReference,
-  ]),
+    this.QueryInterface = ChromeUtils.generateQI([
+      Ci.nsIObserver,
+      Ci.nsISupportsWeakReference,
+    ]);
 
-  debug: true,
-  enabled: true,
-
-  init() {
-    Services.obs.addObserver(this, "earlyformsubmit");
-    Services.obs.addObserver(this, "xpcom-shutdown");
     Services.prefs.addObserver("browser.formfill.", this);
     this.updatePrefs();
-  },
+  }
+
+  cleanup() {
+    super.cleanup();
+    Services.prefs.removeObserver("browser.formfill.", this);
+  }
 
   updatePrefs() {
     this.debug          = Services.prefs.getBoolPref("browser.formfill.debug");
     this.enabled        = Services.prefs.getBoolPref("browser.formfill.enable");
-  },
+  }
 
   log(message) {
     if (!this.debug) {
       return;
     }
     dump("satchelFormListener: " + message + "\n");
     Services.console.logStringMessage("satchelFormListener: " + message);
-  },
+  }
 
   /* ---- nsIObserver interface ---- */
 
   observe(subject, topic, data) {
     if (topic == "nsPref:changed") {
       this.updatePrefs();
-    } else if (topic == "xpcom-shutdown") {
-      Services.obs.removeObserver(this, "earlyformsubmit");
-      Services.obs.removeObserver(this, "xpcom-shutdown");
-      Services.prefs.removeObserver("browser.formfill.", this);
     } else {
       this.log("Oops! Unexpected notification: " + topic);
     }
-  },
+  }
 
-  /* ---- nsIFormSubmitObserver interfaces ---- */
+  handleEvent(event) {
+    switch (event.type) {
+      case "DOMFormBeforeSubmit": {
+        this.onDOMFormBeforeSubmit(event);
+        break;
+      }
+      default: {
+        throw new Error("Unexpected event");
+      }
+    }
+  }
 
-  notify(form, domWin, actionURI, cancelSubmit) {
-    try {
-      if (!this.enabled || PrivateBrowsingUtils.isContentWindowPrivate(domWin)) {
-        return;
+  onDOMFormBeforeSubmit(event) {
+    let form = event.target;
+    if (!this.enabled || PrivateBrowsingUtils.isContentWindowPrivate(form.ownerGlobal)) {
+      return;
+    }
+
+    this.log("Form submit observer notified.");
+
+    if (form.hasAttribute("autocomplete") &&
+        form.getAttribute("autocomplete").toLowerCase() == "off") {
+      return;
+    }
+
+    let entries = [];
+    for (let input of form.elements) {
+      if (ChromeUtils.getClassName(input) !== "HTMLInputElement") {
+        continue;
       }
 
-      this.log("Form submit observer notified.");
+      // Only use inputs that hold text values (not including type="password")
+      if (!input.mozIsTextField(true)) {
+        continue;
+      }
 
-      if (form.hasAttribute("autocomplete") &&
-        form.getAttribute("autocomplete").toLowerCase() == "off") {
-        return;
+      // Don't save fields that were previously type=password such as on sites
+      // that allow the user to toggle password visibility.
+      if (input.hasBeenTypePassword) {
+        continue;
       }
 
-      let entries = [];
-      for (let i = 0; i < form.elements.length; i++) {
-        let input = form.elements[i];
-        if (ChromeUtils.getClassName(input) !== "HTMLInputElement") {
-          continue;
-        }
-
-        // Only use inputs that hold text values (not including type="password")
-        if (!input.mozIsTextField(true)) {
-          continue;
-        }
+      // Bug 394612: If Login Manager marked this input, don't save it.
+      // The login manager will deal with remembering it.
 
-        // Don't save fields that were previously type=password such as on sites
-        // that allow the user to toggle password visibility.
-        if (input.hasBeenTypePassword) {
-          continue;
-        }
+      // Don't save values when @autocomplete is "off" or has a sensitive field name.
+      let autocompleteInfo = input.getAutocompleteInfo();
+      if (autocompleteInfo && !autocompleteInfo.canAutomaticallyPersist) {
+        continue;
+      }
 
-        // Bug 394612: If Login Manager marked this input, don't save it.
-        // The login manager will deal with remembering it.
-
-        // Don't save values when @autocomplete is "off" or has a sensitive field name.
-        let autocompleteInfo = input.getAutocompleteInfo();
-        if (autocompleteInfo && !autocompleteInfo.canAutomaticallyPersist) {
-          continue;
-        }
-
-        let value = input.value.trim();
+      let value = input.value.trim();
 
-        // Don't save empty or unchanged values.
-        if (!value || value == input.defaultValue.trim()) {
-          continue;
-        }
-
-        // Don't save credit card numbers.
-        if (CreditCard.isValidNumber(value)) {
-          this.log("skipping saving a credit card number");
-          continue;
-        }
-
-        let name = input.name || input.id;
-        if (!name) {
-          continue;
-        }
+      // Don't save empty or unchanged values.
+      if (!value || value == input.defaultValue.trim()) {
+        continue;
+      }
 
-        if (name == "searchbar-history") {
-          this.log('addEntry for input name "' + name + '" is denied');
-          continue;
-        }
+      // Don't save credit card numbers.
+      if (CreditCard.isValidNumber(value)) {
+        this.log("skipping saving a credit card number");
+        continue;
+      }
 
-        // Limit stored data to 200 characters.
-        if (name.length > 200 || value.length > 200) {
-          this.log("skipping input that has a name/value too large");
-          continue;
-        }
-
-        // Limit number of fields stored per form.
-        if (entries.length >= 100) {
-          this.log("not saving any more entries for this form.");
-          break;
-        }
-
-        entries.push({ name, value });
+      let name = input.name || input.id;
+      if (!name) {
+        continue;
       }
 
-      if (entries.length) {
-        this.log("sending entries to parent process for form " + form.id);
-        sendAsyncMessage("FormHistory:FormSubmitEntries", entries);
+      if (name == "searchbar-history") {
+        this.log('addEntry for input name "' + name + '" is denied');
+        continue;
+      }
+
+      // Limit stored data to 200 characters.
+      if (name.length > 200 || value.length > 200) {
+        this.log("skipping input that has a name/value too large");
+        continue;
       }
-    } catch (e) {
-      this.log("notify failed: " + e);
+
+      // Limit number of fields stored per form.
+      if (entries.length >= 100) {
+        this.log("not saving any more entries for this form.");
+        break;
+      }
+
+      entries.push({ name, value });
     }
-  },
-};
 
-satchelFormListener.init();
-})();
+    if (entries.length) {
+      this.log("sending entries to parent process for form " + form.id);
+      this.sendAsyncMessage("FormHistory:FormSubmitEntries", entries);
+    }
+  }
+}
deleted file mode 100644
--- a/toolkit/components/satchel/jar.mn
+++ /dev/null
@@ -1,7 +0,0 @@
-# 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/.
-
-toolkit.jar:
-%   content satchel %content/satchel/
-    content/satchel/formSubmitListener.js
--- a/toolkit/components/satchel/moz.build
+++ b/toolkit/components/satchel/moz.build
@@ -35,11 +35,13 @@ EXTRA_JS_MODULES += [
     'InputListAutoComplete.jsm',
     'nsFormAutoCompleteResult.jsm',
 ]
 
 XPCOM_MANIFESTS += [
     'components.conf',
 ]
 
-FINAL_LIBRARY = 'xul'
+FINAL_TARGET_FILES.actors += [
+    'FormSubmitChild.jsm',
+]
 
-JAR_MANIFESTS += ['jar.mn']
+FINAL_LIBRARY = 'xul'
--- a/toolkit/content/tests/chrome/test_autocomplete_with_composition_on_input.html
+++ b/toolkit/content/tests/chrome/test_autocomplete_with_composition_on_input.html
@@ -26,20 +26,22 @@ function runTests() {
     SpecialPowers.getFormFillController()
                  .QueryInterface(Ci.nsIAutoCompleteInput);
   var originalFormFillTimeout = formFillController.timeout;
 
   SpecialPowers.attachFormFillControllerTo(window);
   var target = document.getElementById("input");
 
   // Register a word to the form history.
+  let chromeScript = SpecialPowers.loadChromeScript(function addEntry() {
+    let {FormHistory} = ChromeUtils.import("resource://gre/modules/FormHistory.jsm");
+    FormHistory.update({ op: "add", fieldname: "test", value: "Mozilla" });
+  });
+  chromeScript.destroy();
   target.focus();
-  target.value = "Mozilla";
-  synthesizeKey("KEY_Enter");
-  target.value = "";
 
   new nsDoTestsForAutoCompleteWithComposition(
     "Testing on HTML input (asynchronously search)",
     window, target, formFillController.controller, is,
     function() { return target.value; },
     function() {
       target.setAttribute("timeout", 0);
       new nsDoTestsForAutoCompleteWithComposition(
--- a/toolkit/content/tests/chrome/test_editor_for_input_with_autocomplete.html
+++ b/toolkit/content/tests/chrome/test_editor_for_input_with_autocomplete.html
@@ -18,26 +18,31 @@
 </div>
 
 <pre id="test">
 <script class="testbody" type="text/javascript">
 SimpleTest.waitForExplicitFinish();
 
 async function registerWord(aTarget, aAutoCompleteController) {
   // Register a word to the form history.
+  let chromeScript = SpecialPowers.loadChromeScript(function addEntry() {
+    let {FormHistory} = ChromeUtils.import("resource://gre/modules/FormHistory.jsm");
+    FormHistory.update({ op: "add", fieldname: "test", value: "Mozilla" });
+  });
   aTarget.focus();
   aTarget.value = "Mozilla";
-  synthesizeKey("KEY_Enter");
+
   await waitForCondition(() => {
     if (aAutoCompleteController.searchStatus == aAutoCompleteController.STATUS_NONE ||
         aAutoCompleteController.searchStatus == aAutoCompleteController.STATUS_COMPLETE_NO_MATCH) {
       aAutoCompleteController.startSearch("Mozilla");
     }
     return aAutoCompleteController.matchCount > 0;
   });
+  chromeScript.destroy();
   aTarget.value = "";
   synthesizeKey("KEY_Escape");
 }
 
 async function runTests() {
   var formFillController =
     SpecialPowers.getFormFillController()
                  .QueryInterface(Ci.nsIAutoCompleteInput);
--- a/toolkit/modules/ActorManagerParent.jsm
+++ b/toolkit/modules/ActorManagerParent.jsm
@@ -181,16 +181,26 @@ let ACTORS = {
     child: {
       module: "resource://gre/actors/FinderChild.jsm",
       messages: [
         "Finder:Initialize",
       ],
     },
   },
 
+  FormSubmit: {
+    child: {
+      module: "resource://gre/actors/FormSubmitChild.jsm",
+      allFrames: true,
+      events: {
+        "DOMFormBeforeSubmit": {},
+      },
+    },
+  },
+
   KeyPressEventModelChecker: {
     child: {
       module: "resource://gre/actors/KeyPressEventModelCheckerChild.jsm",
       events: {
         "CheckKeyPressEventModel": {capture: true, mozSystemGroup: true},
       },
     },
   },