Backed out 10 changesets (bug 1474143) for perma failing browser_markup_events_01.js CLOSED TREE
authorCiure Andrei <aciure@mozilla.com>
Fri, 22 Feb 2019 21:16:49 +0200
changeset 460634 9634959f052667455f4fb4521b2def4403d2af97
parent 460633 06dc7a3404fc5198077c8d44108034d0b47bbd15
child 460635 74006e58ea467b3fd78d405ceb30df68a02acea8
push id35596
push userrmaries@mozilla.com
push dateSat, 23 Feb 2019 04:13:22 +0000
treeherdermozilla-central@fdd04819e350 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1474143
milestone67.0a1
backs out1b5d0486658571aed2d5ea68281f13cc627cb5d5
78bfb4dd1f6a68cc4cd4a13a2dea2445b6aafae3
85ec4f1f5f60e5527756edb7ad0b3ac11b57e449
5c112b77e4892b38180a17fe67433e2b72747ada
5d35599598bb557f83f0d0399ba35b75bb80326d
9fd0d7a7946f5a0619b96b325429dc6fd3fd8441
1a83be7a75ca841c06805c812e9206b1790a05b9
2fc9b13171d0cfd4fb1608a57ef2ea1cbd96dbe3
3983d7b6d9ad3bf9e14fc93bf37b6d0d4eb4cfdc
9fe55dd58cd89cc5617d8b9f20ad44127beb09f8
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
Backed out 10 changesets (bug 1474143) for perma failing browser_markup_events_01.js CLOSED TREE Backed out changeset 1b5d04866585 (bug 1474143) Backed out changeset 78bfb4dd1f6a (bug 1474143) Backed out changeset 85ec4f1f5f60 (bug 1474143) Backed out changeset 5c112b77e489 (bug 1474143) Backed out changeset 5d35599598bb (bug 1474143) Backed out changeset 9fd0d7a7946f (bug 1474143) Backed out changeset 1a83be7a75ca (bug 1474143) Backed out changeset 2fc9b13171d0 (bug 1474143) Backed out changeset 3983d7b6d9ad (bug 1474143) Backed out changeset 9fe55dd58cd8 (bug 1474143)
browser/actors/FormSubmitChild.jsm
browser/actors/FormValidationChild.jsm
browser/actors/moz.build
browser/base/content/content.js
browser/base/content/test/performance/browser_startup_content.js
browser/components/BrowserGlue.jsm
browser/extensions/formautofill/FormAutofillContent.jsm
browser/extensions/formautofill/FormAutofillParent.jsm
browser/extensions/formautofill/api.js
browser/extensions/formautofill/content/FormAutofillFrameScript.js
browser/extensions/formautofill/test/unit/test_activeStatus.js
browser/extensions/formautofill/test/unit/test_onFormSubmitted.js
browser/extensions/formautofill/test/unit/test_savedFieldNames.js
dom/html/HTMLFormElement.cpp
dom/html/HTMLFormElement.h
dom/html/nsIFormSubmitObserver.idl
mobile/android/components/BrowserCLH.js
toolkit/components/passwordmgr/LoginManagerContent.jsm
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
toolkit/modules/addons/WebNavigationContent.js
rename from browser/actors/FormValidationChild.jsm
rename to browser/actors/FormSubmitChild.jsm
--- a/browser/actors/FormValidationChild.jsm
+++ b/browser/actors/FormSubmitChild.jsm
@@ -1,25 +1,25 @@
 /* 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/. */
 "use strict";
 
-/**
+/*
  * Handles the validation callback from nsIFormFillController and
  * the display of the help panel on invalid elements.
  */
 
-var EXPORTED_SYMBOLS = ["FormValidationChild"];
+var EXPORTED_SYMBOLS = ["FormSubmitChild"];
 
 const {ActorChild} = ChromeUtils.import("resource://gre/modules/ActorChild.jsm");
 const {BrowserUtils} = ChromeUtils.import("resource://gre/modules/BrowserUtils.jsm");
 const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
 
-class FormValidationChild extends ActorChild {
+class FormSubmitChild extends ActorChild {
   constructor(dispatcher) {
     super(dispatcher);
 
     this._validationMessage = "";
     this._element = null;
 
     this.mm.addEventListener("pageshow", this);
   }
--- a/browser/actors/moz.build
+++ b/browser/actors/moz.build
@@ -28,17 +28,17 @@ with Files("WebRTCChild.jsm"):
 FINAL_TARGET_FILES.actors += [
     'AboutReaderChild.jsm',
     'BlockedSiteChild.jsm',
     'BrowserTabChild.jsm',
     'ClickHandlerChild.jsm',
     'ContentSearchChild.jsm',
     'ContextMenuChild.jsm',
     'DOMFullscreenChild.jsm',
-    'FormValidationChild.jsm',
+    'FormSubmitChild.jsm',
     'LightweightThemeChild.jsm',
     'LightWeightThemeInstallChild.jsm',
     'LinkHandlerChild.jsm',
     'NetErrorChild.jsm',
     'OfflineAppsChild.jsm',
     'PageInfoChild.jsm',
     'PageStyleChild.jsm',
     'PluginChild.jsm',
--- a/browser/base/content/content.js
+++ b/browser/base/content/content.js
@@ -36,22 +36,16 @@ addMessageListener("RemoteLogins:fillFor
 });
 
 function shouldIgnoreLoginManagerEvent(event) {
   // If we have a null principal then prevent any more password manager code from running and
   // incorrectly using the document `location`.
   return event.target.nodePrincipal.isNullPrincipal;
 }
 
-addEventListener("DOMFormBeforeSubmit", function(event) {
-  if (shouldIgnoreLoginManagerEvent(event)) {
-    return;
-  }
-  LoginManagerContent.onDOMFormBeforeSubmit(event);
-});
 addEventListener("DOMFormHasPassword", function(event) {
   if (shouldIgnoreLoginManagerEvent(event)) {
     return;
   }
   LoginManagerContent.onDOMFormHasPassword(event);
   let formLike = LoginFormFactory.createFromForm(event.originalTarget);
   InsecurePasswordUtils.reportInsecurePasswords(formLike);
 });
--- a/browser/base/content/test/performance/browser_startup_content.js
+++ b/browser/base/content/test/performance/browser_startup_content.js
@@ -36,16 +36,20 @@ const whitelist = {
 
     // Logging related
     "resource://gre/modules/Log.jsm",
 
     // Session store
     "resource:///modules/sessionstore/ContentSessionStore.jsm",
     "resource://gre/modules/sessionstore/SessionHistory.jsm",
 
+    // Forms and passwords
+    "resource://formautofill/FormAutofill.jsm",
+    "resource://formautofill/FormAutofillContent.jsm",
+
     // Browser front-end
     "resource:///actors/AboutReaderChild.jsm",
     "resource:///actors/BrowserTabChild.jsm",
     "resource:///modules/ContentMetaHandler.jsm",
     "resource:///actors/LinkHandlerChild.jsm",
     "resource:///actors/PageStyleChild.jsm",
     "resource:///actors/SearchTelemetryChild.jsm",
     "resource://gre/modules/ActorChild.jsm",
@@ -79,18 +83,20 @@ 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,
 // which are all required.
 const intermittently_loaded_whitelist = {
   modules: new Set([
--- a/browser/components/BrowserGlue.jsm
+++ b/browser/components/BrowserGlue.jsm
@@ -118,19 +118,19 @@ let ACTORS = {
       },
       messages: [
         "DOMFullscreen:Entered",
         "DOMFullscreen:CleanUp",
       ],
     },
   },
 
-  FormValidation: {
+  FormSubmit: {
     child: {
-      module: "resource:///actors/FormValidationChild.jsm",
+      module: "resource:///actors/FormSubmitChild.jsm",
       events: {
         "MozInvalidForm": {},
       },
     },
   },
 
   LightWeightThemeInstall: {
     child: {
--- a/browser/extensions/formautofill/FormAutofillContent.jsm
+++ b/browser/extensions/formautofill/FormAutofillContent.jsm
@@ -238,18 +238,16 @@ let ProfileAutocomplete = {
 
     FormAutofill.defineLazyLogGetter(this, "ProfileAutocomplete");
     this.debug("ensureRegistered");
     this._factory = new AutocompleteFactory();
     this._factory.register(AutofillProfileAutoCompleteSearch);
     this._registered = true;
 
     Services.obs.addObserver(this, "autocomplete-will-enter-text");
-
-    this.debug("ensureRegistered. Finished with _registered:", this._registered);
   },
 
   ensureUnregistered() {
     if (!this._registered) {
       return;
     }
 
     this.debug("ensureUnregistered");
@@ -333,116 +331,123 @@ let ProfileAutocomplete = {
 };
 
 /**
  * Handles content's interactions for the process.
  *
  * NOTE: Declares it by "var" to make it accessible in unit tests.
  */
 var FormAutofillContent = {
+  QueryInterface: ChromeUtils.generateQI([Ci.nsIFormSubmitObserver]),
   /**
    * @type {WeakMap} mapping FormLike root HTML elements to FormAutofillHandler objects.
    */
   _formsDetails: new WeakMap(),
 
   /**
    * @type {Set} Set of the fields with usable values in any saved profile.
    */
-  get savedFieldNames() {
-    return Services.cpmm.sharedData.get("FormAutofill:savedFieldNames");
-  },
+  savedFieldNames: null,
 
   /**
    * @type {Object} The object where to store the active items, e.g. element,
    * handler, section, and field detail.
    */
   _activeItems: {},
 
   init() {
     FormAutofill.defineLazyLogGetter(this, "FormAutofillContent");
-    this.debug("init");
 
-    // eslint-disable-next-line mozilla/balanced-listeners
-    Services.cpmm.sharedData.addEventListener("change", this);
+    Services.cpmm.addMessageListener("FormAutofill:enabledStatus", this);
+    Services.cpmm.addMessageListener("FormAutofill:savedFieldNames", this);
+    Services.obs.addObserver(this, "earlyformsubmit");
 
-    let autofillEnabled = Services.cpmm.sharedData.get("FormAutofill:enabled");
+    let autofillEnabled = Services.cpmm.initialProcessData.autofillEnabled;
     // If storage hasn't be initialized yet autofillEnabled is undefined but we need to ensure
     // autocomplete is registered before the focusin so register it in this case as long as the
     // pref is true.
     let shouldEnableAutofill = autofillEnabled === undefined &&
                                (FormAutofill.isAutofillAddressesEnabled ||
                                FormAutofill.isAutofillCreditCardsEnabled);
     if (autofillEnabled || shouldEnableAutofill) {
       ProfileAutocomplete.ensureRegistered();
     }
+
+    this.savedFieldNames =
+      Services.cpmm.initialProcessData.autofillSavedFieldNames;
   },
 
   /**
    * Send the profile to parent for doorhanger and storage saving/updating.
    *
    * @param {Object} profile Submitted form's address/creditcard guid and record.
    * @param {Object} domWin Current content window.
    * @param {int} timeStartedFillingMS Time of form filling started.
    */
   _onFormSubmit(profile, domWin, timeStartedFillingMS) {
     let mm = this._messageManagerFromWindow(domWin);
     mm.sendAsyncMessage("FormAutofill:OnFormSubmit",
                         {profile, timeStartedFillingMS});
   },
 
   /**
-   * Handle a form submission and early return when:
+   * Handle earlyformsubmit event and early return when:
    * 1. In private browsing mode.
    * 2. Could not map any autofill handler by form element.
    * 3. Number of filled fields is less than autofill threshold
    *
-   * @param {HTMLElement} formElement Root element which receives submit event.
-   * @param {Window} domWin Content window only passed for unit tests
+   * @param {HTMLElement} formElement Root element which receives earlyformsubmit event.
+   * @param {Object} domWin Content window
+   * @returns {boolean} Should always return true so form submission isn't canceled.
    */
-  formSubmitted(formElement, domWin = formElement.ownerGlobal) {
-    this.debug("Handling form submission");
+  notify(formElement, domWin) {
+    try {
+      this.debug("Notifying form early submission");
 
-    if (!FormAutofill.isAutofillEnabled) {
-      this.debug("Form Autofill is disabled");
-      return;
-    }
+      if (!FormAutofill.isAutofillEnabled) {
+        this.debug("Form Autofill is disabled");
+        return true;
+      }
 
-    // The `domWin` truthiness test is used by unit tests to bypass this check.
-    if (domWin && PrivateBrowsingUtils.isContentWindowPrivate(domWin)) {
-      this.debug("Ignoring submission in a private window");
-      return;
-    }
+      if (domWin && PrivateBrowsingUtils.isContentWindowPrivate(domWin)) {
+        this.debug("Ignoring submission in a private window");
+        return true;
+      }
+
+      let handler = this._formsDetails.get(formElement);
+      if (!handler) {
+        this.debug("Form element could not map to an existing handler");
+        return true;
+      }
 
-    let handler = this._formsDetails.get(formElement);
-    if (!handler) {
-      this.debug("Form element could not map to an existing handler");
-      return;
-    }
+      let records = handler.createRecords();
+      if (!Object.values(records).some(typeRecords => typeRecords.length)) {
+        return true;
+      }
 
-    let records = handler.createRecords();
-    if (!Object.values(records).some(typeRecords => typeRecords.length)) {
-      return;
+      this._onFormSubmit(records, domWin, handler.timeStartedFillingMS);
+    } catch (ex) {
+      Cu.reportError(ex);
     }
-
-    this._onFormSubmit(records, domWin, handler.timeStartedFillingMS);
+    return true;
   },
 
-  handleEvent(evt) {
-    switch (evt.type) {
-      case "change": {
-        if (!evt.changedKeys.includes("FormAutofill:enabled")) {
-          return;
-        }
-        if (Services.cpmm.sharedData.get("FormAutofill:enabled")) {
+  receiveMessage({name, data}) {
+    switch (name) {
+      case "FormAutofill:enabledStatus": {
+        if (data) {
           ProfileAutocomplete.ensureRegistered();
         } else {
           ProfileAutocomplete.ensureUnregistered();
         }
         break;
       }
+      case "FormAutofill:savedFieldNames": {
+        this.savedFieldNames = data;
+      }
     }
   },
 
   /**
    * Get the form's handler from cache which is created after page identified.
    *
    * @param {HTMLInputElement} element Focused input which triggered profile searching
    * @returns {Array<Object>|null}
--- a/browser/extensions/formautofill/FormAutofillParent.jsm
+++ b/browser/extensions/formautofill/FormAutofillParent.jsm
@@ -182,30 +182,30 @@ FormAutofillParent.prototype = {
     }
   },
 
   /**
    * Broadcast the status to frames when the form autofill status changes.
    */
   _onStatusChanged() {
     log.debug("_onStatusChanged: Status changed to", this._active);
-    Services.ppmm.sharedData.set("FormAutofill:enabled", this._active);
-    // Sync autofill enabled to make sure the value is up-to-date
+    Services.ppmm.broadcastAsyncMessage("FormAutofill:enabledStatus", this._active);
+    // Sync process data autofillEnabled to make sure the value up to date
     // no matter when the new content process is initialized.
-    Services.ppmm.sharedData.flush();
+    Services.ppmm.initialProcessData.autofillEnabled = this._active;
   },
 
   /**
    * Query preference and storage status to determine the overall status of the
    * form autofill feature.
    *
    * @returns {boolean} whether form autofill is active (enabled and has data)
    */
   _computeStatus() {
-    const savedFieldNames = Services.ppmm.sharedData.get("FormAutofill:savedFieldNames");
+    const savedFieldNames = Services.ppmm.initialProcessData.autofillSavedFieldNames;
 
     return (Services.prefs.getBoolPref(ENABLED_AUTOFILL_ADDRESSES_PREF) ||
            Services.prefs.getBoolPref(ENABLED_AUTOFILL_CREDITCARDS_PREF)) &&
            savedFieldNames &&
            savedFieldNames.size > 0;
   },
 
   /**
@@ -370,30 +370,28 @@ FormAutofillParent.prototype = {
     }
 
     target.sendAsyncMessage("FormAutofill:Records", records);
   },
 
   _updateSavedFieldNames() {
     log.debug("_updateSavedFieldNames");
 
-    let savedFieldNames;
     // Don't access the credit cards store unless it is enabled.
     if (FormAutofill.isAutofillCreditCardsAvailable) {
-      savedFieldNames = new Set([
-        ...this.formAutofillStorage.addresses.getSavedFieldNames(),
-        ...this.formAutofillStorage.creditCards.getSavedFieldNames(),
-      ]);
+      Services.ppmm.initialProcessData.autofillSavedFieldNames =
+        new Set([...this.formAutofillStorage.addresses.getSavedFieldNames(),
+          ...this.formAutofillStorage.creditCards.getSavedFieldNames()]);
     } else {
-      savedFieldNames = this.formAutofillStorage.addresses.getSavedFieldNames();
+      Services.ppmm.initialProcessData.autofillSavedFieldNames =
+        this.formAutofillStorage.addresses.getSavedFieldNames();
     }
 
-    Services.ppmm.sharedData.set("FormAutofill:savedFieldNames", savedFieldNames);
-    Services.ppmm.sharedData.flush();
-
+    Services.ppmm.broadcastAsyncMessage("FormAutofill:savedFieldNames",
+                                        Services.ppmm.initialProcessData.autofillSavedFieldNames);
     this._updateStatus();
   },
 
   async _onAddressSubmit(address, target, timeStartedFillingMS) {
     let showDoorhanger = null;
     if (address.guid) {
       // Avoid updating the fields that users don't modify.
       let originalAddress = await this.formAutofillStorage.addresses.get(address.guid);
--- a/browser/extensions/formautofill/api.js
+++ b/browser/extensions/formautofill/api.js
@@ -122,16 +122,21 @@ this.formautofill = class extends Extens
     } else {
       Services.prefs.clearUserPref("services.sync.engine.creditcards.available");
     }
 
     // Listen for the autocomplete popup message to lazily append our stylesheet related to the popup.
     Services.mm.addMessageListener("FormAutoComplete:MaybeOpenPopup", onMaybeOpenPopup);
 
     formAutofillParent.init().catch(Cu.reportError);
+    /* eslint-disable no-unused-vars */
+    Services.ppmm.loadProcessScript("data:,new " + function() {
+      ChromeUtils.import("resource://formautofill/FormAutofillContent.jsm");
+    }, true);
+    /* eslint-enable no-unused-vars */
     Services.mm.loadFrameScript("chrome://formautofill/content/FormAutofillFrameScript.js", true, true);
   }
 
   onShutdown() {
     resProto.setSubstitution(RESOURCE_HOST, null);
 
     this.chromeHandle.destruct();
     this.chromeHandle = null;
--- a/browser/extensions/formautofill/content/FormAutofillFrameScript.js
+++ b/browser/extensions/formautofill/content/FormAutofillFrameScript.js
@@ -1,32 +1,33 @@
 /* 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/. */
 
-/**
+/*
  * Form Autofill frame script.
  */
 
 "use strict";
 
 /* eslint-env mozilla/frame-script */
 
 var {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
+var {FormAutofillContent} = ChromeUtils.import("resource://formautofill/FormAutofillContent.jsm");
 ChromeUtils.defineModuleGetter(this, "setTimeout",
                                "resource://gre/modules/Timer.jsm");
 ChromeUtils.defineModuleGetter(this, "FormAutofill",
                                "resource://formautofill/FormAutofill.jsm");
-ChromeUtils.defineModuleGetter(this, "FormAutofillContent",
-                               "resource://formautofill/FormAutofillContent.jsm");
 ChromeUtils.defineModuleGetter(this, "FormAutofillUtils",
                                "resource://formautofill/FormAutofillUtils.jsm");
 
 /**
  * Handles content's interactions for the frame.
+ *
+ * NOTE: Declares it by "var" to make it accessible in unit tests.
  */
 var FormAutofillFrameScript = {
   _nextHandleElement: null,
   _alreadyDOMContentLoaded: false,
   _hasDOMContentLoadedHandler: false,
   _hasPendingTask: false,
 
   _doIdentifyAutofillFields() {
@@ -43,44 +44,26 @@ var FormAutofillFrameScript = {
       // form has been identified, and ready to open popup.
       sendAsyncMessage("FormAutofill:FieldsIdentified");
       FormAutofillContent.updateActiveInput();
     });
   },
 
   init() {
     addEventListener("focusin", this);
-    addEventListener("DOMFormBeforeSubmit", this);
     addMessageListener("FormAutofill:PreviewProfile", this);
     addMessageListener("FormAutofill:ClearForm", this);
     addMessageListener("FormAutoComplete:PopupClosed", this);
     addMessageListener("FormAutoComplete:PopupOpened", this);
   },
 
   handleEvent(evt) {
     if (!evt.isTrusted || !FormAutofill.isAutofillEnabled) {
       return;
     }
-
-    switch (evt.type) {
-      case "focusin": {
-        this.onFocusIn(evt);
-        break;
-      }
-      case "DOMFormBeforeSubmit": {
-        this.onDOMFormBeforeSubmit(evt);
-        break;
-      }
-      default: {
-        throw new Error("Unexpected event type");
-      }
-    }
-  },
-
-  onFocusIn(evt) {
     FormAutofillContent.updateActiveInput();
 
     let element = evt.target;
     if (!FormAutofillUtils.isFieldEligibleForAutofill(element)) {
       return;
     }
     this._nextHandleElement = element;
 
@@ -94,30 +77,16 @@ var FormAutofillFrameScript = {
         return;
       }
       this._alreadyDOMContentLoaded = true;
     }
 
     this._doIdentifyAutofillFields();
   },
 
-  /**
-   * Handle the DOMFormBeforeSubmit event.
-   * @param {Event} evt
-   */
-  onDOMFormBeforeSubmit(evt) {
-    let formElement = evt.target;
-
-    if (!FormAutofill.isAutofillEnabled) {
-      return;
-    }
-
-    FormAutofillContent.formSubmitted(formElement);
-  },
-
   receiveMessage(message) {
     if (!FormAutofill.isAutofillEnabled) {
       return;
     }
 
     const doc = content.document;
     const {chromeEventHandler} = doc.ownerGlobal.docShell;
 
--- a/browser/extensions/formautofill/test/unit/test_activeStatus.js
+++ b/browser/extensions/formautofill/test/unit/test_activeStatus.js
@@ -11,29 +11,29 @@ add_task(async function setup() {
 });
 
 add_task(async function test_activeStatus_init() {
   let formAutofillParent = new FormAutofillParent();
   sinon.spy(formAutofillParent, "_updateStatus");
 
   // Default status is null before initialization
   Assert.equal(formAutofillParent._active, null);
-  Assert.equal(Services.ppmm.sharedData.get("FormAutofill:enabled"), undefined);
+  Assert.equal(Services.ppmm.initialProcessData.autofillEnabled, undefined);
 
   await formAutofillParent.init();
   // init shouldn't call updateStatus since that requires storage which will
   // lead to startup time regressions.
   Assert.equal(formAutofillParent._updateStatus.called, false);
-  Assert.equal(Services.ppmm.sharedData.get("FormAutofill:enabled"), undefined);
+  Assert.equal(Services.ppmm.initialProcessData.autofillEnabled, undefined);
 
   // Initialize profile storage
   await formAutofillParent.formAutofillStorage.initialize();
   // Upon first initializing profile storage, status should be computed.
   Assert.equal(formAutofillParent._updateStatus.called, true);
-  Assert.equal(Services.ppmm.sharedData.get("FormAutofill:enabled"), false);
+  Assert.equal(Services.ppmm.initialProcessData.autofillEnabled, false);
 
   formAutofillParent._uninit();
 });
 
 add_task(async function test_activeStatus_observe() {
   let formAutofillParent = new FormAutofillParent();
   sinon.stub(formAutofillParent, "_computeStatus");
   sinon.spy(formAutofillParent, "_onStatusChanged");
--- a/browser/extensions/formautofill/test/unit/test_onFormSubmitted.js
+++ b/browser/extensions/formautofill/test/unit/test_onFormSubmitted.js
@@ -486,22 +486,22 @@ const TESTCASES = [
           untouchedFields: [],
         }],
         creditCard: [],
       },
     },
   },
 ];
 
-add_task(async function handle_invalid_form() {
+add_task(async function handle_earlyformsubmit_event() {
   info("Starting testcase: Test an invalid form element");
   let fakeForm = MOCK_DOC.createElement("form");
   sinon.spy(FormAutofillContent, "_onFormSubmit");
 
-  FormAutofillContent.formSubmitted(fakeForm, null);
+  Assert.equal(FormAutofillContent.notify(fakeForm), true);
   Assert.equal(FormAutofillContent._onFormSubmit.called, false);
   FormAutofillContent._onFormSubmit.restore();
 });
 
 add_task(async function autofill_disabled() {
   let form = MOCK_DOC.getElementById("form1");
   form.reset();
 
@@ -520,41 +520,41 @@ add_task(async function autofill_disable
   FormAutofillContent.identifyAutofillFields(element);
 
   sinon.stub(FormAutofillContent, "_onFormSubmit");
 
   // "_onFormSubmit" shouldn't be called if both "addresses" and "creditCards"
   // are disabled.
   Services.prefs.setBoolPref("extensions.formautofill.addresses.enabled", false);
   Services.prefs.setBoolPref("extensions.formautofill.creditCards.enabled", false);
-  FormAutofillContent.formSubmitted(form, null);
+  FormAutofillContent.notify(form);
   Assert.equal(FormAutofillContent._onFormSubmit.called, false);
   FormAutofillContent._onFormSubmit.reset();
 
   // "_onFormSubmit" should be called as usual.
   Services.prefs.clearUserPref("extensions.formautofill.addresses.enabled");
   Services.prefs.clearUserPref("extensions.formautofill.creditCards.enabled");
-  FormAutofillContent.formSubmitted(form, null);
+  FormAutofillContent.notify(form);
   Assert.equal(FormAutofillContent._onFormSubmit.called, true);
   Assert.notDeepEqual(FormAutofillContent._onFormSubmit.args[0][0].address, []);
   Assert.notDeepEqual(FormAutofillContent._onFormSubmit.args[0][0].creditCard, []);
   FormAutofillContent._onFormSubmit.reset();
 
   // "address" should be empty if "addresses" pref is disabled.
   Services.prefs.setBoolPref("extensions.formautofill.addresses.enabled", false);
-  FormAutofillContent.formSubmitted(form, null);
+  FormAutofillContent.notify(form);
   Assert.equal(FormAutofillContent._onFormSubmit.called, true);
   Assert.deepEqual(FormAutofillContent._onFormSubmit.args[0][0].address, []);
   Assert.notDeepEqual(FormAutofillContent._onFormSubmit.args[0][0].creditCard, []);
   FormAutofillContent._onFormSubmit.reset();
   Services.prefs.clearUserPref("extensions.formautofill.addresses.enabled");
 
   // "creditCard" should be empty if "creditCards" pref is disabled.
   Services.prefs.setBoolPref("extensions.formautofill.creditCards.enabled", false);
-  FormAutofillContent.formSubmitted(form, null);
+  FormAutofillContent.notify(form);
   Assert.deepEqual(FormAutofillContent._onFormSubmit.called, true);
   Assert.notDeepEqual(FormAutofillContent._onFormSubmit.args[0][0].address, []);
   Assert.deepEqual(FormAutofillContent._onFormSubmit.args[0][0].creditCard, []);
   FormAutofillContent._onFormSubmit.reset();
   Services.prefs.clearUserPref("extensions.formautofill.creditCards.enabled");
 
   FormAutofillContent._onFormSubmit.restore();
 });
@@ -577,17 +577,17 @@ TESTCASES.forEach(testcase => {
       } else {
         input.value = testcase.formValue[key];
       }
     }
     sinon.stub(FormAutofillContent, "_onFormSubmit");
 
     let element = MOCK_DOC.getElementById(TARGET_ELEMENT_ID);
     FormAutofillContent.identifyAutofillFields(element);
-    FormAutofillContent.formSubmitted(form, null);
+    FormAutofillContent.notify(form);
 
     Assert.equal(FormAutofillContent._onFormSubmit.called,
                  testcase.expectedResult.formSubmission,
                  "Check expected onFormSubmit.called");
     if (FormAutofillContent._onFormSubmit.called) {
       Assert.deepEqual(FormAutofillContent._onFormSubmit.args[0][0],
                        testcase.expectedResult.records);
     }
--- a/browser/extensions/formautofill/test/unit/test_savedFieldNames.js
+++ b/browser/extensions/formautofill/test/unit/test_savedFieldNames.js
@@ -1,10 +1,10 @@
 /*
- * Test for keeping the valid fields information in sharedData.
+ * Test for keeping the valid fields information in initialProcessData.
  */
 
 "use strict";
 
 let FormAutofillParent;
 
 add_task(async function setup() {
   ({FormAutofillParent} = ChromeUtils.import("resource://formautofill/FormAutofillParent.jsm", null));
@@ -49,17 +49,17 @@ add_task(async function test_profileSave
   Object.defineProperty(
     formAutofillParent.formAutofillStorage.addresses,
     "_data", {writable: true});
 
   formAutofillParent.formAutofillStorage.addresses._data = [];
 
   // The set is empty if there's no profile in the store.
   formAutofillParent._updateSavedFieldNames();
-  Assert.equal(Services.ppmm.sharedData.get("FormAutofill:savedFieldNames").size, 0);
+  Assert.equal(Services.ppmm.initialProcessData.autofillSavedFieldNames.size, 0);
 
   // 2 profiles with 4 valid fields.
   formAutofillParent.formAutofillStorage.addresses._data = [{
     guid: "test-guid-1",
     organization: "Sesame Street",
     "street-address": "123 Sesame Street.",
     tel: "1-345-345-3456",
     email: "",
@@ -76,17 +76,17 @@ add_task(async function test_profileSave
     timeCreated: 0,
     timeLastUsed: 0,
     timeLastModified: 0,
     timesUsed: 0,
   }];
 
   formAutofillParent._updateSavedFieldNames();
 
-  let autofillSavedFieldNames = Services.ppmm.sharedData.get("FormAutofill:savedFieldNames");
+  let autofillSavedFieldNames = Services.ppmm.initialProcessData.autofillSavedFieldNames;
   Assert.equal(autofillSavedFieldNames.size, 4);
   Assert.equal(autofillSavedFieldNames.has("organization"), true);
   Assert.equal(autofillSavedFieldNames.has("street-address"), true);
   Assert.equal(autofillSavedFieldNames.has("tel"), true);
   Assert.equal(autofillSavedFieldNames.has("email"), false);
   Assert.equal(autofillSavedFieldNames.has("guid"), false);
   Assert.equal(autofillSavedFieldNames.has("timeCreated"), false);
   Assert.equal(autofillSavedFieldNames.has("timeLastUsed"), false);
--- a/dom/html/HTMLFormElement.cpp
+++ b/dom/html/HTMLFormElement.cpp
@@ -87,16 +87,19 @@ static const uint8_t NS_FORM_AUTOCOMPLET
 static const nsAttrValue::EnumTable kFormAutocompleteTable[] = {
     {"on", NS_FORM_AUTOCOMPLETE_ON},
     {"off", NS_FORM_AUTOCOMPLETE_OFF},
     {nullptr, 0}};
 // Default autocomplete value is 'on'.
 static const nsAttrValue::EnumTable* kFormDefaultAutocomplete =
     &kFormAutocompleteTable[0];
 
+bool HTMLFormElement::gFirstFormSubmitted = false;
+bool HTMLFormElement::gPasswordManagerInitialized = false;
+
 HTMLFormElement::HTMLFormElement(
     already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)
     : nsGenericHTMLElement(std::move(aNodeInfo)),
       mControls(new HTMLFormControlsCollection(this)),
       mSelectedRadioButtons(2),
       mRequiredRadioButtonCounts(2),
       mValueMissingRadioGroups(2),
       mPendingSubmission(nullptr),
@@ -845,36 +848,72 @@ nsresult HTMLFormElement::DoSecureToInse
                                    telemetryBucket + 1);
   }
   return NS_OK;
 }
 
 nsresult HTMLFormElement::NotifySubmitObservers(nsIURI* aActionURL,
                                                 bool* aCancelSubmit,
                                                 bool aEarlyNotify) {
+  // If this is the first form, bring alive the first form submit
+  // category observers
+  if (!gFirstFormSubmitted) {
+    gFirstFormSubmitted = true;
+    NS_CreateServicesFromCategory(NS_FIRST_FORMSUBMIT_CATEGORY, nullptr,
+                                  NS_FIRST_FORMSUBMIT_CATEGORY);
+  }
+
   if (!aEarlyNotify) {
     nsresult rv = DoSecureToInsecureSubmitCheck(aActionURL, aCancelSubmit);
     if (NS_FAILED(rv)) {
       return rv;
     }
     if (*aCancelSubmit) {
       return NS_OK;
     }
   }
 
-  bool defaultAction = true;
-  nsresult rv = nsContentUtils::DispatchEventOnlyToChrome(
-      OwnerDoc(), static_cast<nsINode*>(this),
-      aEarlyNotify ? NS_LITERAL_STRING("DOMFormBeforeSubmit")
-                   : NS_LITERAL_STRING("DOMFormSubmit"),
-      CanBubble::eYes, Cancelable::eYes, &defaultAction);
-  *aCancelSubmit = !defaultAction;
-  if (*aCancelSubmit) {
-    return NS_OK;
+  // Notify observers that the form is being submitted.
+  nsCOMPtr<nsIObserverService> service =
+      mozilla::services::GetObserverService();
+  if (!service) return NS_ERROR_FAILURE;
+
+  nsCOMPtr<nsISimpleEnumerator> theEnum;
+  nsresult rv = service->EnumerateObservers(
+      aEarlyNotify ? NS_EARLYFORMSUBMIT_SUBJECT : NS_FORMSUBMIT_SUBJECT,
+      getter_AddRefs(theEnum));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  if (theEnum) {
+    nsCOMPtr<nsISupports> inst;
+    *aCancelSubmit = false;
+
+    // XXXbz what do the submit observers actually want?  The window
+    // of the document this is shown in?  Or something else?
+    // sXBL/XBL2 issue
+    nsCOMPtr<nsPIDOMWindowOuter> window = OwnerDoc()->GetWindow();
+
+    bool loop = true;
+    while (NS_SUCCEEDED(theEnum->HasMoreElements(&loop)) && loop) {
+      theEnum->GetNext(getter_AddRefs(inst));
+
+      nsCOMPtr<nsIFormSubmitObserver> formSubmitObserver(
+          do_QueryInterface(inst));
+      if (formSubmitObserver) {
+        rv = formSubmitObserver->Notify(
+            this, window ? window->GetCurrentInnerWindow() : nullptr,
+            aActionURL, aCancelSubmit);
+        NS_ENSURE_SUCCESS(rv, rv);
+      }
+      if (*aCancelSubmit) {
+        return NS_OK;
+      }
+    }
   }
+
   return rv;
 }
 
 nsresult HTMLFormElement::WalkFormElements(
     HTMLFormSubmission* aFormSubmission) {
   // This shouldn't be called recursively, so use a rather large value
   // for the preallocated buffer.
   AutoTArray<RefPtr<nsGenericHTMLFormElement>, 100> sortedControls;
@@ -1080,18 +1119,26 @@ nsresult HTMLFormElement::AddElement(nsG
   bool lastElement = AddElementToList(controlList, aChild, this);
 
 #ifdef DEBUG
   AssertDocumentOrder(controlList, this);
 #endif
 
   int32_t type = aChild->ControlType();
 
-  // If it is a password control, inform the password manager.
+  //
+  // If it is a password control, and the password manager has not yet been
+  // initialized, initialize the password manager
+  //
   if (type == NS_FORM_INPUT_PASSWORD) {
+    if (!gPasswordManagerInitialized) {
+      gPasswordManagerInitialized = true;
+      NS_CreateServicesFromCategory(NS_PASSWORDMANAGER_CATEGORY, nullptr,
+                                    NS_PASSWORDMANAGER_CATEGORY);
+    }
     PostPasswordEvent();
   }
 
   // Default submit element handling
   if (aChild->IsSubmitControl()) {
     // Update mDefaultSubmitElement, mFirstSubmitInElements,
     // mFirstSubmitNotInElements.
 
--- a/dom/html/HTMLFormElement.h
+++ b/dom/html/HTMLFormElement.h
@@ -597,16 +597,22 @@ class HTMLFormElement final : public nsG
   /** Keep track of whether a submission was user-initiated or not */
   bool mSubmitInitiatedFromUserInput;
   /**
    * Whether the submission of this form has been ever prevented because of
    * being invalid.
    */
   bool mEverTriedInvalidSubmit;
 
+ protected:
+  /** Detection of first form to notify observers */
+  static bool gFirstFormSubmitted;
+  /** Detection of first password input to initialize the password manager */
+  static bool gPasswordManagerInitialized;
+
  private:
   ~HTMLFormElement();
 };
 
 }  // namespace dom
 
 }  // namespace mozilla
 
--- a/dom/html/nsIFormSubmitObserver.idl
+++ b/dom/html/nsIFormSubmitObserver.idl
@@ -11,15 +11,21 @@ interface nsIURI;
 interface nsIArray;
 
 webidl HTMLFormElement;
 webidl Element;
 
 [scriptable, uuid(867cb7e7-835d-408b-9788-d2834d284e03)]
 interface nsIFormSubmitObserver: nsISupports
 {
+  void notify(in HTMLFormElement formNode, in mozIDOMWindow window, in nsIURI actionURL, out boolean cancelSubmit);
+
   void notifyInvalidSubmit(in HTMLFormElement formNode,
                            in Array<Element> invalidElements);
 };
 
 %{C++
+#define NS_FORMSUBMIT_SUBJECT "formsubmit"
+#define NS_EARLYFORMSUBMIT_SUBJECT "earlyformsubmit"
+#define NS_FIRST_FORMSUBMIT_CATEGORY "firstformsubmit"
+#define NS_PASSWORDMANAGER_CATEGORY "passwordmanager"
 #define NS_INVALIDFORMSUBMIT_SUBJECT "invalidformsubmit"
 %}
--- a/mobile/android/components/BrowserCLH.js
+++ b/mobile/android/components/BrowserCLH.js
@@ -221,22 +221,16 @@ BrowserCLH.prototype = {
 
     let options = {
       capture: true,
       mozSystemGroup: true,
     };
 
     // NOTE: Much of this logic is duplicated in browser/base/content/content.js
     // for desktop.
-    aWindow.addEventListener("DOMFormBeforeSubmit", event => {
-      if (shouldIgnoreLoginManagerEvent(event)) {
-        return;
-      }
-      this.LoginManagerContent.onDOMFormBeforeSubmit(event);
-    });
     aWindow.addEventListener("DOMFormHasPassword", event => {
       if (shouldIgnoreLoginManagerEvent(event)) {
         return;
       }
       this.LoginManagerContent.onDOMFormHasPassword(event);
     }, options);
 
     aWindow.addEventListener("DOMInputPasswordAdded", event => {
--- a/toolkit/components/passwordmgr/LoginManagerContent.jsm
+++ b/toolkit/components/passwordmgr/LoginManagerContent.jsm
@@ -46,19 +46,37 @@ XPCOMUtils.defineLazyGetter(this, "log",
 Services.cpmm.addMessageListener("clearRecipeCache", () => {
   LoginRecipesContent._clearRecipeCache();
 });
 
 var gLastRightClickTimeStamp = Number.NEGATIVE_INFINITY;
 
 var observer = {
   QueryInterface: ChromeUtils.generateQI([Ci.nsIObserver,
+                                          Ci.nsIFormSubmitObserver,
                                           Ci.nsIWebProgressListener,
                                           Ci.nsISupportsWeakReference]),
 
+  // nsIFormSubmitObserver
+  notify(formElement, aWindow, actionURI) {
+    log("observer notified for form submission.");
+
+    // We're invoked before the content's |onsubmit| handlers, so we
+    // can grab form data before it might be modified (see bug 257781).
+
+    try {
+      let formLike = LoginFormFactory.createFromForm(formElement);
+      LoginManagerContent._onFormSubmit(formLike);
+    } catch (e) {
+      log("Caught error in onFormSubmit(", e.lineNumber, "):", e.message);
+      Cu.reportError(e);
+    }
+
+    return true; // Always return true, or form submit will be canceled.
+  },
 
   // nsIWebProgressListener
   onLocationChange(aWebProgress, aRequest, aLocation, aFlags) {
     // Only handle pushState/replaceState here.
     if (!(aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT) ||
         !(aWebProgress.loadType & Ci.nsIDocShell.LOAD_CMD_PUSHSTATE)) {
       return;
     }
@@ -122,16 +140,17 @@ var observer = {
 
       default: {
         throw new Error("Unexpected event");
       }
     }
   },
 };
 
+Services.obs.addObserver(observer, "earlyformsubmit");
 
 // This object maps to the "child" process (even in the single-process case).
 var LoginManagerContent = {
   __formFillService: null, // FormFillController, for username autocompleting
   get _formFillService() {
     if (!this.__formFillService) {
       this.__formFillService =
                       Cc["@mozilla.org/satchel/form-fill-controller;1"].
@@ -336,28 +355,16 @@ var LoginManagerContent = {
       webProgress.addProgressListener(observer,
                                       Ci.nsIWebProgress.NOTIFY_STATE_DOCUMENT |
                                       Ci.nsIWebProgress.NOTIFY_LOCATION);
     } catch (ex) {
       // Ignore NS_ERROR_FAILURE if the progress listener was already added
     }
   },
 
-  onDOMFormBeforeSubmit(event) {
-    if (!event.isTrusted) {
-      return;
-    }
-
-    // We're invoked before the content's |submit| event handlers, so we
-    // can grab form data before it might be modified (see bug 257781).
-    log("notified before form submission");
-    let formLike = LoginFormFactory.createFromForm(event.target);
-    LoginManagerContent._onFormSubmit(formLike);
-  },
-
   onDOMFormHasPassword(event) {
     if (!event.isTrusted) {
       return;
     }
 
     let form = event.target;
     let formLike = LoginFormFactory.createFromForm(form);
     log("onDOMFormHasPassword:", form, formLike);
@@ -891,17 +898,17 @@ var LoginManagerContent = {
 
     for (let formRoot of weakLoginFormRootElements) {
       if (!formRoot.isConnected) {
         continue;
       }
 
       if (ChromeUtils.getClassName(formRoot) === "HTMLFormElement") {
         // For now only perform capture upon navigation for FormLike's without
-        // a <form> to avoid capture from both a DOMFormBeforeSubmit event and
+        // a <form> to avoid capture from both an earlyformsubmit and
         // navigation for the same "form".
         log("Ignoring navigation for the form root to avoid multiple prompts " +
             "since it was for a real <form>");
         continue;
       }
       let formLike = this._formLikeByRootElement.get(formRoot);
       this._onFormSubmit(formLike);
     }
--- a/toolkit/components/satchel/FormHistoryStartup.jsm
+++ b/toolkit/components/satchel/FormHistoryStartup.jsm
@@ -42,17 +42,18 @@ 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.mm.addMessageListener("FormHistory:FormSubmitEntries", this);
+    Services.ppmm.loadProcessScript("chrome://satchel/content/formSubmitListener.js", true);
+    Services.ppmm.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/FormSubmitChild.jsm
rename to toolkit/components/satchel/formSubmitListener.js
--- a/toolkit/components/satchel/FormSubmitChild.jsm
+++ b/toolkit/components/satchel/formSubmitListener.js
@@ -1,152 +1,150 @@
 /* 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/. */
 
-"use strict";
-
-var EXPORTED_SYMBOLS = ["FormSubmitChild"];
-
-const {ActorChild} = ChromeUtils.import("resource://gre/modules/ActorChild.jsm");
-const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
+/* eslint-env mozilla/frame-script */
 
 ChromeUtils.defineModuleGetter(this, "CreditCard",
                                "resource://gre/modules/CreditCard.jsm");
 ChromeUtils.defineModuleGetter(this, "PrivateBrowsingUtils",
                                "resource://gre/modules/PrivateBrowsingUtils.jsm");
 
-class FormSubmitChild extends ActorChild {
-  constructor(dispatcher) {
-    super(dispatcher);
+(function() {
+const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
 
-    this.QueryInterface = ChromeUtils.generateQI([
-      Ci.nsIObserver,
-      Ci.nsISupportsWeakReference,
-    ]);
+let satchelFormListener = {
+  QueryInterface: ChromeUtils.generateQI([
+    Ci.nsIFormSubmitObserver,
+    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);
     }
-  }
+  },
 
-  handleEvent(event) {
-    switch (event.type) {
-      case "DOMFormBeforeSubmit": {
-        this.onDOMFormBeforeSubmit(event);
-        break;
-      }
-      default: {
-        throw new Error("Unexpected event");
-      }
-    }
-  }
+  /* ---- nsIFormSubmitObserver interfaces ---- */
 
-  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;
+  notify(form, domWin, actionURI, cancelSubmit) {
+    try {
+      if (!this.enabled || PrivateBrowsingUtils.isContentWindowPrivate(domWin)) {
+        return;
       }
 
-      // Only use inputs that hold text values (not including type="password")
-      if (!input.mozIsTextField(true)) {
-        continue;
-      }
+      this.log("Form submit observer notified.");
 
-      // 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;
+      if (form.hasAttribute("autocomplete") &&
+        form.getAttribute("autocomplete").toLowerCase() == "off") {
+        return;
       }
 
-      // Bug 394612: If Login Manager marked this input, don't save it.
-      // The login manager will deal with remembering it.
+      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;
+        }
 
-      // Don't save values when @autocomplete is "off" or has a sensitive field name.
-      let autocompleteInfo = input.getAutocompleteInfo();
-      if (autocompleteInfo && !autocompleteInfo.canAutomaticallyPersist) {
-        continue;
-      }
+        // 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 value = input.value.trim();
+        // 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();
 
-      // Don't save empty or unchanged values.
-      if (!value || value == input.defaultValue.trim()) {
-        continue;
-      }
+        // 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 credit card numbers.
-      if (CreditCard.isValidNumber(value)) {
-        this.log("skipping saving a credit card number");
-        continue;
-      }
+        if (name == "searchbar-history") {
+          this.log('addEntry for input name "' + name + '" is denied');
+          continue;
+        }
 
-      let name = input.name || input.id;
-      if (!name) {
-        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 });
       }
 
-      if (name == "searchbar-history") {
-        this.log('addEntry for input name "' + name + '" is denied');
-        continue;
+      if (entries.length) {
+        this.log("sending entries to parent process for form " + form.id);
+        sendAsyncMessage("FormHistory:FormSubmitEntries", entries);
       }
-
-      // 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 });
-    }
-
-    if (entries.length) {
-      this.log("sending entries to parent process for form " + form.id);
-      this.sendAsyncMessage("FormHistory:FormSubmitEntries", entries);
-    }
-  }
-}
+satchelFormListener.init();
+})();
new file mode 100644
--- /dev/null
+++ b/toolkit/components/satchel/jar.mn
@@ -0,0 +1,7 @@
+# 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,13 +35,11 @@ EXTRA_JS_MODULES += [
     'InputListAutoComplete.jsm',
     'nsFormAutoCompleteResult.jsm',
 ]
 
 XPCOM_MANIFESTS += [
     'components.conf',
 ]
 
-FINAL_TARGET_FILES.actors += [
-    'FormSubmitChild.jsm',
-]
+FINAL_LIBRARY = 'xul'
 
-FINAL_LIBRARY = 'xul'
+JAR_MANIFESTS += ['jar.mn']
--- 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,22 +26,20 @@ 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,31 +18,26 @@
 </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,26 +181,16 @@ 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},
       },
     },
   },
--- a/toolkit/modules/addons/WebNavigationContent.js
+++ b/toolkit/modules/addons/WebNavigationContent.js
@@ -77,28 +77,35 @@ var CreatedNavigationTargetListener = {
       sourceFrameId,
       createdOuterWindowId,
       isSourceTab,
     });
   },
 };
 
 var FormSubmitListener = {
+  QueryInterface: ChromeUtils.generateQI([Ci.nsIObserver,
+                                          Ci.nsIFormSubmitObserver,
+                                          Ci.nsISupportsWeakReference]),
   init() {
     this.formSubmitWindows = new WeakSet();
-    addEventListener("DOMFormBeforeSubmit", this);
+    Services.obs.addObserver(FormSubmitListener, "earlyformsubmit");
   },
 
   uninit() {
-    removeEventListener("DOMFormBeforeSubmit", this);
+    Services.obs.removeObserver(FormSubmitListener, "earlyformsubmit");
     this.formSubmitWindows = new WeakSet();
   },
 
-  handleEvent({target: form}) {
-    this.formSubmitWindows.add(form.ownerGlobal);
+  notify: function(form, window, actionURI) {
+    try {
+      this.formSubmitWindows.add(window);
+    } catch (e) {
+      Cu.reportError("Error in FormSubmitListener.notify");
+    }
   },
 
   hasAndForget: function(window) {
     let has = this.formSubmitWindows.has(window);
     this.formSubmitWindows.delete(window);
     return has;
   },
 };