Backed out 4 changesets (bug 1367077) for android mochitest failures in passwordmgr/ a=backout CLOSED TREE
authorWes Kocher <wkocher@mozilla.com>
Tue, 12 Sep 2017 16:41:19 -0700
changeset 429923 04bf10e9a1f7640a282a7bd254c0fd7000863960
parent 429922 23bf7009f21d5745e82761488d835e5aa1faf1a8
child 429924 08a75460a0d8911bf6a321e789994f23cc32f48a
push id7761
push userjlund@mozilla.com
push dateFri, 15 Sep 2017 00:19:52 +0000
treeherdermozilla-beta@c38455951db4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbackout
bugs1367077
milestone57.0a1
backs out0509b09c11fa0d9b72173404eecfcbe1752d89df
8df5e093dd92890fa36fde7c648b097501603e44
c6300312d42adeed70b34434f03d682dfe58ad6a
83cf26e442840c8024233057efb3f55c09364d0a
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 4 changesets (bug 1367077) for android mochitest failures in passwordmgr/ a=backout CLOSED TREE Backed out changeset 0509b09c11fa (bug 1367077) Backed out changeset 8df5e093dd92 (bug 1367077) Backed out changeset c6300312d42a (bug 1367077) Backed out changeset 83cf26e44284 (bug 1367077) MozReview-Commit-ID: JFwOEdTq7bL
browser/components/nsBrowserGlue.js
mobile/android/chrome/content/browser.js
mobile/android/components/BrowserCLH.js
mobile/android/components/MobileComponents.manifest
mobile/android/components/PromptService.js
mobile/android/components/geckoview/GeckoViewStartup.js
mobile/android/modules/DelayedInit.jsm
mobile/android/modules/DownloadNotifications.jsm
mobile/android/modules/geckoview/GeckoViewUtils.jsm
mobile/android/modules/geckoview/moz.build
toolkit/components/passwordmgr/LoginManagerParent.jsm
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -142,24 +142,24 @@ const listeners = {
     "Content:Click": ["ContentClick"],
     "ContentSearch": ["ContentSearch"],
     "FormValidation:ShowPopup": ["FormValidationHandler"],
     "FormValidation:HidePopup": ["FormValidationHandler"],
     "Prompt:Open": ["RemotePrompt"],
     "Reader:ArticleGet": ["ReaderParent"],
     "Reader:FaviconRequest": ["ReaderParent"],
     "Reader:UpdateReaderButton": ["ReaderParent"],
-    // PLEASE KEEP THIS LIST IN SYNC WITH THE MOBILE LISTENERS IN BrowserCLH.js
+    // PLEASE KEEP THIS LIST IN SYNC WITH THE LISTENERS ADDED IN LoginManagerParent.init
     "RemoteLogins:findLogins": ["LoginManagerParent"],
     "RemoteLogins:findRecipes": ["LoginManagerParent"],
     "RemoteLogins:onFormSubmit": ["LoginManagerParent"],
     "RemoteLogins:autoCompleteLogins": ["LoginManagerParent"],
     "RemoteLogins:removeLogin": ["LoginManagerParent"],
     "RemoteLogins:insecureLoginFormPresent": ["LoginManagerParent"],
-    // PLEASE KEEP THIS LIST IN SYNC WITH THE MOBILE LISTENERS IN BrowserCLH.js
+    // PLEASE KEEP THIS LIST IN SYNC WITH THE LISTENERS ADDED IN LoginManagerParent.init
     "WCCR:registerProtocolHandler": ["Feeds"],
     "WCCR:registerContentHandler": ["Feeds"],
     "rtcpeer:CancelRequest": ["webrtcUI"],
     "rtcpeer:Request": ["webrtcUI"],
     "webrtc:CancelRequest": ["webrtcUI"],
     "webrtc:Request": ["webrtcUI"],
     "webrtc:StopRecording": ["webrtcUI"],
     "webrtc:UpdateBrowserIndicators": ["webrtcUI"],
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -41,16 +41,22 @@ XPCOMUtils.defineLazyModuleGetter(this, 
                                   "resource://gre/modules/PluralForm.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "Downloads",
                                   "resource://gre/modules/Downloads.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "UserAgentOverrides",
                                   "resource://gre/modules/UserAgentOverrides.jsm");
 
+XPCOMUtils.defineLazyModuleGetter(this, "LoginManagerContent",
+                                  "resource://gre/modules/LoginManagerContent.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "LoginManagerParent",
+                                  "resource://gre/modules/LoginManagerParent.jsm");
+
 XPCOMUtils.defineLazyModuleGetter(this, "Task", "resource://gre/modules/Task.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "SafeBrowsing",
                                   "resource://gre/modules/SafeBrowsing.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "BrowserUtils",
                                   "resource://gre/modules/BrowserUtils.jsm");
@@ -545,16 +551,19 @@ var BrowserApp = {
 
       InitLater(() => LightWeightThemeWebInstaller.init());
       InitLater(() => CastingApps.init(), window, "CastingApps");
       InitLater(() => Services.search.init(), Services, "search");
 
       // Bug 778855 - Perf regression if we do this here. To be addressed in bug 779008.
       InitLater(() => SafeBrowsing.init(), window, "SafeBrowsing");
 
+      InitLater(() => Cc["@mozilla.org/login-manager;1"].getService(Ci.nsILoginManager));
+      InitLater(() => LoginManagerParent.init(), window, "LoginManagerParent");
+
     }, {once: true});
 
     // Pass caret StateChanged events to ActionBarHandler.
     window.addEventListener("mozcaretstatechanged", e => {
       ActionBarHandler.caretStateChangedHandler(e);
     }, /* useCapture = */ true, /* wantsUntrusted = */ false);
   },
 
@@ -3760,24 +3769,27 @@ Tab.prototype = {
 
     this.browser.removeProgressListener(this.filter);
     this.filter.removeProgressListener(this);
     this.filter = null;
     this.browser.sessionHistory.removeSHistoryListener(this);
 
     this.browser.removeEventListener("DOMContentLoaded", this, true);
     this.browser.removeEventListener("DOMFormHasPassword", this, true);
+    this.browser.removeEventListener("DOMInputPasswordAdded", this, true);
     this.browser.removeEventListener("DOMLinkAdded", this, true);
     this.browser.removeEventListener("DOMLinkChanged", this, true);
     this.browser.removeEventListener("DOMMetaAdded", this);
     this.browser.removeEventListener("DOMTitleChanged", this, true);
     this.browser.removeEventListener("DOMAudioPlaybackStarted", this, true);
     this.browser.removeEventListener("DOMAudioPlaybackStopped", this, true);
     this.browser.removeEventListener("DOMWindowClose", this, true);
     this.browser.removeEventListener("DOMWillOpenModalDialog", this, true);
+    this.browser.removeEventListener("DOMAutoComplete", this, true);
+    this.browser.removeEventListener("blur", this, true);
     this.browser.removeEventListener("pageshow", this, true);
     this.browser.removeEventListener("MozApplicationManifest", this, true);
     this.browser.removeEventListener("TabPreZombify", this, true);
     this.browser.removeEventListener("DOMWindowFocus", this, true);
 
     this.browser.removeEventListener("VideoBindingAttached", this, true, true);
     this.browser.removeEventListener("VideoBindingCast", this, true, true);
 
@@ -4127,31 +4139,39 @@ Tab.prototype = {
         if (!docURI.startsWith("about:")) {
           WebsiteMetadata.parseAsynchronously(this.browser.contentDocument);
         }
 
         break;
       }
 
       case "DOMFormHasPassword": {
+        LoginManagerContent.onDOMFormHasPassword(aEvent,
+                                                 this.browser.contentWindow);
+
         // Send logins for this hostname to Java.
         let hostname = aEvent.target.baseURIObject.prePath;
         let foundLogins = Services.logins.findLogins({}, hostname, "", "");
         if (foundLogins.length > 0) {
           let displayHost = IdentityHandler.getEffectiveHost();
           let title = { text: displayHost, resource: hostname };
           let selectObj = { title: title, logins: foundLogins };
           GlobalEventDispatcher.sendRequest({
             type: "Doorhanger:Logins",
             data: selectObj
           });
         }
         break;
       }
 
+      case "DOMInputPasswordAdded": {
+        LoginManagerContent.onDOMInputPasswordAdded(aEvent,
+                                                    this.browser.contentWindow);
+      }
+
       case "DOMMetaAdded":
         let target = aEvent.originalTarget;
         let browser = BrowserApp.getBrowserForDocument(target.ownerDocument);
 
         switch (target.name) {
           case "msapplication-TileImage":
             this.addMetadata("tileImage", browser.currentURI.resolve(target.content), this.METADATA_GOOD_MATCH);
             break;
@@ -4271,16 +4291,22 @@ Tab.prototype = {
 
         // We're about to open a modal dialog, make sure the opening
         // tab is brought to the front.
         let tab = BrowserApp.getTabForWindow(aEvent.target.top);
         BrowserApp.selectTab(tab);
         break;
       }
 
+      case "DOMAutoComplete":
+      case "blur": {
+        LoginManagerContent.onUsernameInput(aEvent);
+        break;
+      }
+
       case "VideoBindingAttached": {
         CastingApps.handleVideoBindingAttached(this, aEvent);
         break;
       }
 
       case "VideoBindingCast": {
         CastingApps.handleVideoBindingCast(this, aEvent);
         break;
@@ -4296,16 +4322,18 @@ Tab.prototype = {
           type: "Tab:Select",
           tabID: this.id,
           foreground: true,
         });
         break;
       }
 
       case "pageshow": {
+        LoginManagerContent.onPageShow(aEvent, this.browser.contentWindow);
+
         // The rest of this only handles pageshow for the top-level document.
         if (aEvent.originalTarget.defaultView != this.browser.contentWindow)
           return;
 
         let target = aEvent.originalTarget;
         let docURI = target.documentURI;
         if (!docURI.startsWith("about:neterror") && !this.isSearch) {
           // If this wasn't an error page and the user isn't search, don't retain the typed entry
--- a/mobile/android/components/BrowserCLH.js
+++ b/mobile/android/components/BrowserCLH.js
@@ -1,22 +1,21 @@
 /* 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/. */
 
-const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
 
-XPCOMUtils.defineLazyModuleGetters(this, {
-  AppConstants: "resource://gre/modules/AppConstants.jsm",
-  DelayedInit: "resource://gre/modules/DelayedInit.jsm",
-  GeckoViewUtils: "resource://gre/modules/GeckoViewUtils.jsm",
-  Services: "resource://gre/modules/Services.jsm",
-});
+XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
+                                  "resource://gre/modules/AppConstants.jsm");
 
 var Strings = {};
 
 XPCOMUtils.defineLazyGetter(Strings, "brand", _ =>
         Services.strings.createBundle("chrome://branding/locale/brand.properties"));
 XPCOMUtils.defineLazyGetter(Strings, "browser", _ =>
         Services.strings.createBundle("chrome://browser/locale/browser.properties"));
 XPCOMUtils.defineLazyGetter(Strings, "reader", _ =>
@@ -36,137 +35,71 @@ BrowserCLH.prototype = {
     let url = registry.convertChromeURL(Services.io.newURI("chrome://browser/content/aboutHome.xhtml")).spec;
     // Like jar:file:///data/app/org.mozilla.fennec-2.apk!/
     url = url.substring(4, url.indexOf("!/") + 2);
 
     let protocolHandler = Services.io.getProtocolHandler("resource").QueryInterface(Ci.nsIResProtocolHandler);
     protocolHandler.setSubstitution("android", Services.io.newURI(url));
   },
 
+  addObserverScripts: function(aScripts) {
+    aScripts.forEach(item => {
+      let {name, topics, script} = item;
+      XPCOMUtils.defineLazyGetter(this, name, _ => {
+        let sandbox = {};
+        if (script.endsWith(".jsm")) {
+          Cu.import(script, sandbox);
+        } else {
+          Services.scriptloader.loadSubScript(script, sandbox);
+        }
+        return sandbox[name];
+      });
+      let observer = (subject, topic, data) => {
+        Services.obs.removeObserver(observer, topic);
+        if (!item.once) {
+          Services.obs.addObserver(this[name], topic);
+        }
+        this[name].observe(subject, topic, data); // Explicitly notify new observer
+      };
+      topics.forEach(topic => {
+        Services.obs.addObserver(observer, topic);
+      });
+    });
+  },
+
   observe: function(subject, topic, data) {
     switch (topic) {
-      case "app-startup": {
+      case "app-startup":
         this.setResourceSubstitutions();
 
-        Services.obs.addObserver(this, "chrome-document-global-created");
-        Services.obs.addObserver(this, "content-document-global-created");
-
-        GeckoViewUtils.addLazyGetter(this, "DownloadNotifications", {
-          module: "resource://gre/modules/DownloadNotifications.jsm",
-          observers: ["chrome-document-loaded"],
+        let observerScripts = [{
+          name: "DownloadNotifications",
+          script: "resource://gre/modules/DownloadNotifications.jsm",
+          topics: ["chrome-document-interactive"],
           once: true,
-        });
-
+        }];
         if (AppConstants.MOZ_WEBRTC) {
-          GeckoViewUtils.addLazyGetter(this, "WebrtcUI", {
+          observerScripts.push({
+            name: "WebrtcUI",
             script: "chrome://browser/content/WebrtcUI.js",
-            observers: [
+            topics: [
               "getUserMedia:ask-device-permission",
               "getUserMedia:request",
               "PeerConnection:request",
               "recording-device-events",
               "VideoCapture:Paused",
               "VideoCapture:Resumed",
             ],
           });
         }
-
-        GeckoViewUtils.addLazyGetter(this, "SelectHelper", {
-          script: "chrome://browser/content/SelectHelper.js",
-        });
-        GeckoViewUtils.addLazyGetter(this, "InputWidgetHelper", {
-          script: "chrome://browser/content/InputWidgetHelper.js",
-        });
-
-        GeckoViewUtils.addLazyGetter(this, "LoginManagerParent", {
-          module: "resource://gre/modules/LoginManagerParent.jsm",
-          mm: [
-            // PLEASE KEEP THIS LIST IN SYNC WITH THE DESKTOP LIST IN nsBrowserGlue.js
-            "RemoteLogins:findLogins",
-            "RemoteLogins:findRecipes",
-            "RemoteLogins:onFormSubmit",
-            "RemoteLogins:autoCompleteLogins",
-            "RemoteLogins:removeLogin",
-            "RemoteLogins:insecureLoginFormPresent",
-            // PLEASE KEEP THIS LIST IN SYNC WITH THE DESKTOP LIST IN nsBrowserGlue.js
-          ],
-        });
-        GeckoViewUtils.addLazyGetter(this, "LoginManagerContent", {
-          module: "resource://gre/modules/LoginManagerContent.jsm",
-        });
-
-        // Once the first chrome window is loaded, schedule a list of startup
-        // tasks to be performed on idle.
-        GeckoViewUtils.addLazyGetter(this, "DelayedStartup", {
-          observers: ["chrome-document-loaded"],
-          once: true,
-          handler: _ => DelayedInit.scheduleList([
-            _ => Services.logins,
-          ], 10000 /* 10 seconds maximum wait. */),
-        });
+        this.addObserverScripts(observerScripts);
         break;
-      }
-
-      case "chrome-document-global-created":
-      case "content-document-global-created": {
-        let win = GeckoViewUtils.getChromeWindow(subject);
-        if (win !== subject) {
-          // Only attach to top-level windows.
-          return;
-        }
-
-        GeckoViewUtils.addLazyEventListener(win, "click", {
-          handler: _ => [this.SelectHelper, this.InputWidgetHelper],
-          options: {
-            capture: true,
-            mozSystemGroup: true,
-          },
-        });
-
-        this._initLoginManagerEvents(aWindow);
-        break;
-      }
     }
   },
 
-  _initLoginManagerEvents: function(aWindow) {
-    let options = {
-      capture: true,
-      mozSystemGroup: true,
-    };
-
-    aWindow.addEventListener("DOMFormHasPassword", event => {
-      this.LoginManagerContent.onDOMFormHasPassword(event, event.target.ownerGlobal.top);
-    }, options);
-
-    aWindow.addEventListener("DOMInputPasswordAdded", event => {
-      this.LoginManagerContent.onDOMInputPasswordAdded(event, event.target.ownerGlobal.top);
-    }, options);
-
-    aWindow.addEventListener("DOMAutoComplete", event => {
-      this.LoginManagerContent.onUsernameInput(event);
-    }, options);
-
-    aWindow.addEventListener("blur", event => {
-      let win = event.target && event.target.ownerGlobal;
-      if (win && win.HTMLDocument &&
-          event.target instanceof win.HTMLInputElement) {
-        this.LoginManagerContent.onUsernameInput(event);
-      }
-    }, options);
-
-    aWindow.addEventListener("pageshow", event => {
-      let win = event.target && event.target.defaultView;
-      if (win && win.HTMLDocument &&
-          event.target instanceof win.HTMLDocument) {
-        this.LoginManagerContent.onPageShow(event, win.top);
-      }
-    }, options);
-  },
-
   // QI
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),
 
   // XPCOMUtils factory
   classID: Components.ID("{be623d20-d305-11de-8a39-0800200c9a66}")
 };
 
 var components = [ BrowserCLH ];
--- a/mobile/android/components/MobileComponents.manifest
+++ b/mobile/android/components/MobileComponents.manifest
@@ -36,16 +36,17 @@ component {C6E8C44D-9F39-4AF7-BCC0-76E38
 contract @mozilla.org/content-permission/prompt;1 {C6E8C44D-9F39-4AF7-BCC0-76E38A8310F5}
 
 # PromptService.js
 component {9a61149b-2276-4a0a-b79c-be994ad106cf} PromptService.js
 contract @mozilla.org/prompter;1 {9a61149b-2276-4a0a-b79c-be994ad106cf}
 contract @mozilla.org/embedcomp/prompt-service;1 {9a61149b-2276-4a0a-b79c-be994ad106cf}
 component {80dae1e9-e0d2-4974-915f-f97050fa8068} PromptService.js
 contract @mozilla.org/network/authprompt-adapter-factory;1 {80dae1e9-e0d2-4974-915f-f97050fa8068}
+category app-startup PromptService service,@mozilla.org/prompter;1
 
 # PresentationDevicePrompt.js
 component {388bd149-c919-4a43-b646-d7ec57877689} PresentationDevicePrompt.js
 contract @mozilla.org/presentation-device/prompt;1 {388bd149-c919-4a43-b646-d7ec57877689}
 
 # PresentationRequestUIGlue.js
 component {9c550ef7-3ff6-4bd1-9ad1-5a3735b90d21} PresentationRequestUIGlue.js
 contract @mozilla.org/presentation/requestuiglue;1 {9c550ef7-3ff6-4bd1-9ad1-5a3735b90d21}
--- a/mobile/android/components/PromptService.js
+++ b/mobile/android/components/PromptService.js
@@ -17,17 +17,56 @@ var gPromptService = null;
 function PromptService() {
   gPromptService = this;
 }
 
 PromptService.prototype = {
   classID: Components.ID("{9a61149b-2276-4a0a-b79c-be994ad106cf}"),
 
   QueryInterface: XPCOMUtils.generateQI([
-      Ci.nsIPromptFactory, Ci.nsIPromptService, Ci.nsIPromptService2]),
+      Ci.nsIObserver, Ci.nsIPromptFactory, Ci.nsIPromptService, Ci.nsIPromptService2]),
+
+  loadSubscript: function(aName, aScript) {
+    let sandbox = {};
+    Services.scriptloader.loadSubScript(aScript, sandbox);
+    return sandbox[aName];
+  },
+
+  /* ----------  nsIObserver  ---------- */
+  observe: function(aSubject, aTopic, aData) {
+    switch (aTopic) {
+      case "app-startup": {
+        Services.obs.addObserver(this, "chrome-document-global-created");
+        Services.obs.addObserver(this, "content-document-global-created");
+        break;
+      }
+      case "chrome-document-global-created":
+      case "content-document-global-created": {
+        let win = aSubject.QueryInterface(Ci.nsIInterfaceRequestor)
+                          .getInterface(Ci.nsIDocShell).QueryInterface(Ci.nsIDocShellTreeItem)
+                          .rootTreeItem.QueryInterface(Ci.nsIInterfaceRequestor)
+                          .getInterface(Ci.nsIDOMWindow);
+        if (win !== aSubject) {
+          // Only attach to top-level windows.
+          return;
+        }
+        if (!this.selectHelper) {
+          this.selectHelper = this.loadSubscript(
+              "SelectHelper", "chrome://browser/content/SelectHelper.js");
+        }
+        if (!this.inputWidgetHelper) {
+          this.inputWidgetHelper = this.loadSubscript(
+              "InputWidgetHelper", "chrome://browser/content/InputWidgetHelper.js");
+        }
+        win.addEventListener("click", this.selectHelper, /* capture */ true);
+        win.addEventListener("click", this.inputWidgetHelper, /* capture */ true);
+        break;
+      }
+    }
+  },
 
   /* ----------  nsIPromptFactory  ---------- */
   // XXX Copied from nsPrompter.js.
   getPrompt: function getPrompt(domWin, iid) {
     // This is still kind of dumb; the C++ code delegated to login manager
     // here, which in turn calls back into us via nsIPromptService2.
     if (iid.equals(Ci.nsIAuthPrompt2) || iid.equals(Ci.nsIAuthPrompt)) {
       try {
--- a/mobile/android/components/geckoview/GeckoViewStartup.js
+++ b/mobile/android/components/geckoview/GeckoViewStartup.js
@@ -1,87 +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/. */
 
 const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
-XPCOMUtils.defineLazyModuleGetters(this, {
-  GeckoViewUtils: "resource://gre/modules/GeckoViewUtils.jsm",
-  Services: "resource://gre/modules/Services.jsm",
-});
+XPCOMUtils.defineLazyModuleGetter(this, "Services",
+                                  "resource://gre/modules/Services.jsm");
 
 function GeckoViewStartup() {
 }
 
 GeckoViewStartup.prototype = {
   classID: Components.ID("{8e993c34-fdd6-432c-967e-f995d888777f}"),
 
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),
 
+  addLazyGetter: function({name, script, service, module,
+                           observers, ppmm, mm, init, once}) {
+    if (script) {
+      XPCOMUtils.defineLazyScriptGetter(this, name, script);
+    } else if (module) {
+      XPCOMUtils.defineLazyGetter(this, name, _ => {
+        let sandbox = {};
+        Cu.import(module, sandbox);
+        if (init) {
+          init.call(this, sandbox[name]);
+        }
+        return sandbox[name];
+      });
+    } else if (service) {
+      XPCOMUtils.defineLazyGetter(this, name, _ =>
+        Cc[service].getService(Ci.nsISupports).wrappedJSObject);
+    }
+
+    if (observers) {
+      let observer = (subject, topic, data) => {
+        Services.obs.removeObserver(observer, topic);
+        if (!once) {
+          Services.obs.addObserver(this[name], topic);
+        }
+        this[name].observe(subject, topic, data); // Explicitly notify new observer
+      };
+      observers.forEach(topic => Services.obs.addObserver(observer, topic));
+    }
+
+    if (ppmm || mm) {
+      let target = ppmm ? Services.ppmm : Services.mm;
+      let listener = msg => {
+        target.removeMessageListener(msg.name, listener);
+        if (!once) {
+          target.addMessageListener(msg.name, this[name]);
+        }
+        this[name].receiveMessage(msg);
+      };
+      (ppmm || mm).forEach(msg => target.addMessageListener(msg, listener));
+    }
+  },
+
+  addLazyEventListener: function({name, target, events, options}) {
+    let listener = event => {
+      if (!options || !options.once) {
+        target.removeEventListener(event.type, listener, options);
+        target.addEventListener(event.type, this[name], options);
+      }
+      this[name].handleEvent(event);
+    };
+    events.forEach(event => target.addEventListener(event, listener, options));
+  },
+
   /* ----------  nsIObserver  ---------- */
   observe: function(aSubject, aTopic, aData) {
     switch (aTopic) {
       case "app-startup": {
         // Parent and content process.
         Services.obs.addObserver(this, "chrome-document-global-created");
         Services.obs.addObserver(this, "content-document-global-created");
 
-        GeckoViewUtils.addLazyGetter(this, "GeckoViewPermission", {
+        this.addLazyGetter({
+          name: "GeckoViewPermission",
           service: "@mozilla.org/content-permission/prompt;1",
           observers: [
             "getUserMedia:ask-device-permission",
             "getUserMedia:request",
             "PeerConnection:request",
           ],
         });
 
         if (Services.appinfo.processType != Services.appinfo.PROCESS_TYPE_DEFAULT) {
           // Content process only.
-          GeckoViewUtils.addLazyGetter(this, "GeckoViewPrompt", {
+          this.addLazyGetter({
+            name: "GeckoViewPrompt",
             service: "@mozilla.org/prompter;1",
           });
         }
         break;
       }
 
       case "profile-after-change": {
         // Parent process only.
         // ContentPrefServiceParent is needed for e10s file picker.
-        GeckoViewUtils.addLazyGetter(this, "ContentPrefServiceParent", {
+        this.addLazyGetter({
+          name: "ContentPrefServiceParent",
           module: "resource://gre/modules/ContentPrefServiceParent.jsm",
           init: cpsp => cpsp.alwaysInit(),
           ppmm: [
             "ContentPrefs:FunctionCall",
             "ContentPrefs:AddObserverForName",
             "ContentPrefs:RemoveObserverForName",
           ],
         });
 
-        GeckoViewUtils.addLazyGetter(this, "GeckoViewPrompt", {
+        this.addLazyGetter({
+          name: "GeckoViewPrompt",
           service: "@mozilla.org/prompter;1",
           mm: [
             "GeckoView:Prompt",
           ],
         });
         break;
       }
 
       case "chrome-document-global-created":
       case "content-document-global-created": {
-        let win = GeckoViewUtils.getChromeWindow(aSubject);
+        let win = aSubject.QueryInterface(Ci.nsIInterfaceRequestor)
+                          .getInterface(Ci.nsIDocShell).QueryInterface(Ci.nsIDocShellTreeItem)
+                          .rootTreeItem.QueryInterface(Ci.nsIInterfaceRequestor)
+                          .getInterface(Ci.nsIDOMWindow);
         if (win !== aSubject) {
           // Only attach to top-level windows.
           return;
         }
 
-        GeckoViewUtils.addLazyEventListener(win, ["click", "contextmenu"], {
-          handler: _ => this.GeckoViewPrompt,
+        this.addLazyEventListener({
+          name: "GeckoViewPrompt",
+          target: win,
+          events: [
+            "click",
+            "contextmenu",
+          ],
           options: {
             capture: false,
             mozSystemGroup: true,
           },
         });
         break;
       }
     }
--- a/mobile/android/modules/DelayedInit.jsm
+++ b/mobile/android/modules/DelayedInit.jsm
@@ -47,22 +47,16 @@ XPCOMUtils.defineLazyServiceGetter(this,
  *   }
  *   InitLater(() => Foo.init());
  *   InitLater(() => Bar.init(), this, "Bar");
  */
 var DelayedInit = {
   schedule: function(fn, object, name, maxWait) {
     return Impl.scheduleInit(fn, object, name, maxWait);
   },
-
-  scheduleList: function(fns, maxWait) {
-    for (let fn of fns) {
-      Impl.scheduleInit(fn, null, null, maxWait);
-    }
-  },
 };
 
 // Maximum duration for each idling period. Pending inits are run until this
 // duration is exceeded; then we wait for next idling period.
 const MAX_IDLE_RUN_MS = 50;
 
 var Impl = {
   pendingInits: [],
--- a/mobile/android/modules/DownloadNotifications.jsm
+++ b/mobile/android/modules/DownloadNotifications.jsm
@@ -42,17 +42,17 @@ const kButtons = {
 };
 
 var notifications = new Map();
 
 var DownloadNotifications = {
   _notificationKey: "downloads",
 
   observe: function(subject, topic, data) {
-    if (topic === "chrome-document-loaded") {
+    if (topic === "chrome-document-interactive") {
       this.init();
     }
   },
 
   init: function() {
     Downloads.getList(Downloads.ALL)
              .then(list => list.addView(this))
              .then(() => this._viewAdded = true, Cu.reportError);
deleted file mode 100644
--- a/mobile/android/modules/geckoview/GeckoViewUtils.jsm
+++ /dev/null
@@ -1,162 +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/. */
-"use strict"
-
-const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
-
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-
-XPCOMUtils.defineLazyModuleGetters(this, {
-  Services: "resource://gre/modules/Services.jsm",
-  EventDispatcher: "resource://gre/modules/Messaging.jsm",
-});
-
-this.EXPORTED_SYMBOLS = ["GeckoViewUtils"];
-
-var GeckoViewUtils = {
-  /**
-   * Define a lazy getter that loads an object from external code, and
-   * optionally handles observer and/or message manager notifications for the
-   * object, so the object only loads when a notification is received.
-   *
-   * @param scope     Scope for holding the loaded object.
-   * @param name      Name of the object to load.
-   * @param script    If specified, load the object from a JS subscript.
-   * @param service   If specified, load the object from a JS component; the
-   *                  component must include the line
-   *                  "this.wrappedJSObject = this;" in its constructor.
-   * @param module    If specified, load the object from a JS module.
-   * @param init      For non-scripts, optional post-load initialization function.
-   * @param observers If specified, listen to specified observer notifications.
-   * @param ppmm      If specified, listen to specified process messages.
-   * @param mm        If specified, listen to specified frame messages.
-   * @param ged       If specified, listen to specified global EventDispatcher events.
-   * @param once      If specified, only listen to the specified
-   *                  notifications/messages once.
-   */
-  addLazyGetter: function(scope, name, {script, service, module, handler,
-                                        observers, ppmm, mm, ged, init, once}) {
-    if (script) {
-      XPCOMUtils.defineLazyScriptGetter(scope, name, script);
-    } else {
-      XPCOMUtils.defineLazyGetter(scope, name, _ => {
-        let ret = undefined;
-        if (module) {
-          ret = Cu.import(module, {})[name];
-        } else if (service) {
-          ret = Cc[service].getService(Ci.nsISupports).wrappedJSObject;
-        } else if (typeof handler === "function") {
-          ret = {
-            handleEvent: handler,
-            observe: handler,
-            onEvent: handler,
-            receiveMessage: handler,
-          };
-        } else if (handler) {
-          ret = handler;
-        }
-        if (ret && init) {
-          init.call(scope, ret);
-        }
-        return ret;
-      });
-    }
-
-    if (observers) {
-      let observer = (subject, topic, data) => {
-        Services.obs.removeObserver(observer, topic);
-        if (!once) {
-          Services.obs.addObserver(scope[name], topic);
-        }
-        scope[name].observe(subject, topic, data); // Explicitly notify new observer
-      };
-      observers.forEach(topic => Services.obs.addObserver(observer, topic));
-    }
-
-    if (ppmm || mm) {
-      let target = ppmm ? Services.ppmm : Services.mm;
-      let listener = msg => {
-        target.removeMessageListener(msg.name, listener);
-        if (!once) {
-          target.addMessageListener(msg.name, scope[name]);
-        }
-        scope[name].receiveMessage(msg);
-      };
-      (ppmm || mm).forEach(msg => target.addMessageListener(msg, listener));
-    }
-
-    if (ged) {
-      let listener = (event, data, callback) => {
-        EventDispatcher.instance.unregisterListener(listener, event);
-        if (!once) {
-          EventDispatcher.instance.registerListener(scope[name], event);
-        }
-        scope[name].onEvent(event, data, callback);
-      };
-      EventDispatcher.instance.registerListener(listener, ged);
-    }
-  },
-
-  /**
-   * Add lazy event listeners that only load the actual handler when an event
-   * is being handled.
-   *
-   * @param target  Event target for the event listeners.
-   * @param events  Event name as a string or array.
-   * @param handler If specified, function that, for a given event, returns the
-   *                actual event handler as an object or an array of objects.
-   *                If handler is not specified, the actual event handler is
-   *                specified using the scope and name pair.
-   * @param scope   See handler.
-   * @param name    See handler.
-   * @param options Options for addEventListener.
-   */
-  addLazyEventListener: function(target, events, {handler, scope, name, options}) {
-    if (!handler) {
-      handler = (_ => Array.isArray(name) ? name.map(n => scope[n]) : scope[name]);
-    }
-    let listener = event => {
-      let handlers = handler(event);
-      if (!handlers) {
-          return;
-      }
-      if (!Array.isArray(handlers)) {
-        handlers = [handlers];
-      }
-      if (!options || !options.once) {
-        target.removeEventListener(event.type, listener, options);
-        handlers.forEach(handler => target.addEventListener(event.type, handler, options));
-      }
-      handlers.forEach(handler => handler.handleEvent(event));
-    };
-    if (Array.isArray(events)) {
-      events.forEach(event => target.addEventListener(event, listener, options));
-    } else {
-      target.addEventListener(events, listener, options);
-    }
-  },
-
-  /**
-   * Return the outermost chrome DOM window (the XUL window) for a given DOM
-   * window.
-   *
-   * @param aWin a DOM window.
-   */
-  getChromeWindow: function(aWin) {
-    return aWin.QueryInterface(Ci.nsIInterfaceRequestor)
-               .getInterface(Ci.nsIDocShell).QueryInterface(Ci.nsIDocShellTreeItem)
-               .rootTreeItem.QueryInterface(Ci.nsIInterfaceRequestor)
-               .getInterface(Ci.nsIDOMWindow);
-  },
-
-  /**
-   * Return the per-nsWindow EventDispatcher for a given DOM window.
-   *
-   * @param aWin a DOM window.
-   */
-  getDispatcherForWindow: function(aWin) {
-    let win = this.getChromeWindow(aWin.top);
-    return win.WindowEventDispatcher || EventDispatcher.for(win);
-  },
-};
--- a/mobile/android/modules/geckoview/moz.build
+++ b/mobile/android/modules/geckoview/moz.build
@@ -8,11 +8,10 @@ EXTRA_JS_MODULES += [
     'AndroidLog.jsm',
     'GeckoViewContent.jsm',
     'GeckoViewContentModule.jsm',
     'GeckoViewModule.jsm',
     'GeckoViewNavigation.jsm',
     'GeckoViewProgress.jsm',
     'GeckoViewScroll.jsm',
     'GeckoViewSettings.jsm',
-    'GeckoViewUtils.jsm',
     'Messaging.jsm',
 ]
--- a/toolkit/components/passwordmgr/LoginManagerParent.jsm
+++ b/toolkit/components/passwordmgr/LoginManagerParent.jsm
@@ -61,18 +61,34 @@ var LoginManagerParent = {
     // Dedupe so the length checks below still make sense with scheme upgrades.
     let resolveBy = [
       "scheme",
       "timePasswordChanged",
     ];
     return LoginHelper.dedupeLogins(logins, ["username"], resolveBy, formOrigin);
   },
 
-  // Listeners are added in nsBrowserGlue.js on desktop
-  // and in BrowserCLH.js on mobile.
+  // This should only be called on Android. Listeners are added in
+  // nsBrowserGlue.js on desktop. Please make sure that the list of
+  // listeners added here stays in sync with the listeners added in
+  // nsBrowserGlue when you change either.
+  init() {
+    let mm = Cc["@mozilla.org/globalmessagemanager;1"]
+               .getService(Ci.nsIMessageListenerManager);
+    // PLEASE KEEP THIS LIST IN SYNC WITH THE LISTENERS ADDED IN nsBrowserGlue
+    mm.addMessageListener("RemoteLogins:findLogins", this);
+    mm.addMessageListener("RemoteLogins:findRecipes", this);
+    mm.addMessageListener("RemoteLogins:onFormSubmit", this);
+    mm.addMessageListener("RemoteLogins:autoCompleteLogins", this);
+    mm.addMessageListener("RemoteLogins:removeLogin", this);
+    mm.addMessageListener("RemoteLogins:insecureLoginFormPresent", this);
+    // PLEASE KEEP THIS LIST IN SYNC WITH THE LISTENERS ADDED IN nsBrowserGlue
+  },
+
+  // Listeners are added in nsBrowserGlue.js
   receiveMessage(msg) {
     let data = msg.data;
     switch (msg.name) {
       case "RemoteLogins:findLogins": {
         // TODO Verify msg.target's principals against the formOrigin?
         this.sendLoginDataToChild(data.options.showMasterPassword,
                                   data.formOrigin,
                                   data.actionOrigin,