Bug 1394580 - 2. Add GeckoViewStartup to consolidate startup tasks; r=snorp draft
authorJim Chen <nchen@mozilla.com>
Tue, 05 Sep 2017 21:26:30 -0400
changeset 659404 0bda1af1e97b9bafea3aa72195f3ea937cf8a273
parent 659403 c71edca4a13f3de785e06f2e0a249ff80fd8c1d4
child 659405 f6902e25a445d29001f93e024e7cc82fddbb58f2
push id78126
push userbmo:nchen@mozilla.com
push dateWed, 06 Sep 2017 01:26:43 +0000
reviewerssnorp
bugs1394580
milestone57.0a1
Bug 1394580 - 2. Add GeckoViewStartup to consolidate startup tasks; r=snorp Add a GeckoViewStartup component to consolidate tasks performed during GeckoView startup, such as adding lazy observers and event listeners. This enables us to not load individual GeckoView modules such as GeckoViewPermission and GeckoViewPrompt until they are actually needed. MozReview-Commit-ID: IsaUGwBHKbs
mobile/android/components/geckoview/GeckoView.manifest
mobile/android/components/geckoview/GeckoViewPermission.js
mobile/android/components/geckoview/GeckoViewPrompt.js
mobile/android/components/geckoview/GeckoViewStartup.js
mobile/android/components/geckoview/moz.build
mobile/android/installer/package-manifest.in
--- a/mobile/android/components/geckoview/GeckoView.manifest
+++ b/mobile/android/components/geckoview/GeckoView.manifest
@@ -1,18 +1,21 @@
 # Stylesheets
 category agent-style-sheets browser-content-stylesheet chrome://geckoview/skin/content.css
 
+# GeckoViewStartup.js
+component {8e993c34-fdd6-432c-967e-f995d888777f} GeckoViewStartup.js
+contract @mozilla.org/geckoview/startup;1 {8e993c34-fdd6-432c-967e-f995d888777f}
+category app-startup GeckoViewStartup service,@mozilla.org/geckoview/startup;1
+category profile-after-change GeckoViewStartup @mozilla.org/geckoview/startup;1 process=main
+
 # GeckoViewPermission.js
 component {42f3c238-e8e8-4015-9ca2-148723a8afcf} GeckoViewPermission.js
 contract @mozilla.org/content-permission/prompt;1 {42f3c238-e8e8-4015-9ca2-148723a8afcf}
-category app-startup GeckoViewPermission service,@mozilla.org/content-permission/prompt;1
 
 # GeckoViewPrompt.js
 component {076ac188-23c1-4390-aa08-7ef1f78ca5d9} GeckoViewPrompt.js
 contract @mozilla.org/embedcomp/prompt-service;1 {076ac188-23c1-4390-aa08-7ef1f78ca5d9}
 contract @mozilla.org/prompter;1 {076ac188-23c1-4390-aa08-7ef1f78ca5d9}
-category app-startup GeckoViewPrompt service,@mozilla.org/prompter;1
-category profile-after-change GeckoViewPrompt @mozilla.org/prompter;1 process=main
 component {aa0dd6fc-73dd-4621-8385-c0b377e02cee} GeckoViewPrompt.js process=main
 contract @mozilla.org/colorpicker;1 {aa0dd6fc-73dd-4621-8385-c0b377e02cee} process=main
 component {e4565e36-f101-4bf5-950b-4be0887785a9} GeckoViewPrompt.js process=main
 contract @mozilla.org/filepicker;1 {e4565e36-f101-4bf5-950b-4be0887785a9} process=main
--- a/mobile/android/components/geckoview/GeckoViewPermission.js
+++ b/mobile/android/components/geckoview/GeckoViewPermission.js
@@ -1,45 +1,41 @@
 /* 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, results: Cr} = Components;
 
-Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
-XPCOMUtils.defineLazyModuleGetter(this, "EventDispatcher",
-                                  "resource://gre/modules/Messaging.jsm");
+XPCOMUtils.defineLazyModuleGetters(this, {
+  EventDispatcher: "resource://gre/modules/Messaging.jsm",
+  Services: "resource://gre/modules/Services.jsm",
+});
 
 // See: http://developer.android.com/reference/android/Manifest.permission.html
 const PERM_ACCESS_FINE_LOCATION = "android.permission.ACCESS_FINE_LOCATION";
 const PERM_CAMERA = "android.permission.CAMERA";
 const PERM_RECORD_AUDIO = "android.permission.RECORD_AUDIO";
 
 function GeckoViewPermission() {
+  this.wrappedJSObject = this;
 }
 
 GeckoViewPermission.prototype = {
   classID: Components.ID("{42f3c238-e8e8-4015-9ca2-148723a8afcf}"),
 
   QueryInterface: XPCOMUtils.generateQI([
       Ci.nsIObserver, Ci.nsIContentPermissionPrompt]),
 
   _appPermissions: {},
 
   /* ----------  nsIObserver  ---------- */
   observe: function(aSubject, aTopic, aData) {
     switch (aTopic) {
-      case "app-startup": {
-        Services.obs.addObserver(this, "getUserMedia:ask-device-permission");
-        Services.obs.addObserver(this, "getUserMedia:request");
-        Services.obs.addObserver(this, "PeerConnection:request");
-        break;
-      }
       case "getUserMedia:ask-device-permission": {
         this.handleMediaAskDevicePermission(aData, aSubject);
         break;
       }
       case "getUserMedia:request": {
         this.handleMediaRequest(aSubject);
         break;
       }
--- a/mobile/android/components/geckoview/GeckoViewPrompt.js
+++ b/mobile/android/components/geckoview/GeckoViewPrompt.js
@@ -1,68 +1,34 @@
 /* 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, results: Cr} = Components;
 
-Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
-XPCOMUtils.defineLazyModuleGetter(this, "ContentPrefServiceParent",
-                                  "resource://gre/modules/ContentPrefServiceParent.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "EventDispatcher",
-                                  "resource://gre/modules/Messaging.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
-                                  "resource://gre/modules/FileUtils.jsm");
+XPCOMUtils.defineLazyModuleGetters(this, {
+  EventDispatcher: "resource://gre/modules/Messaging.jsm",
+  FileUtils: "resource://gre/modules/FileUtils.jsm",
+  Services: "resource://gre/modules/Services.jsm",
+});
 
 XPCOMUtils.defineLazyServiceGetter(this, "UUIDGen",
                                    "@mozilla.org/uuid-generator;1", "nsIUUIDGenerator");
 
 function PromptFactory() {
+  this.wrappedJSObject = this;
 }
 
 PromptFactory.prototype = {
   classID: Components.ID("{076ac188-23c1-4390-aa08-7ef1f78ca5d9}"),
 
   QueryInterface: XPCOMUtils.generateQI([
-    Ci.nsIObserver, Ci.nsIPromptFactory, Ci.nsIPromptService, Ci.nsIPromptService2]),
-
-  /* ----------  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 "profile-after-change": {
-        // ContentPrefServiceParent is needed for e10s file picker.
-        ContentPrefServiceParent.init();
-        Services.mm.addMessageListener("GeckoView:Prompt", this);
-        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;
-        }
-        win.addEventListener("click", this); // non-capture
-        win.addEventListener("contextmenu", this); // non-capture
-        break;
-      }
-    }
-  },
+    Ci.nsIPromptFactory, Ci.nsIPromptService, Ci.nsIPromptService2]),
 
   handleEvent: function(aEvent) {
     switch (aEvent.type) {
       case "click":
         this._handleClick(aEvent);
         break;
       case "contextmenu":
         this._handleContextMenu(aEvent);
new file mode 100644
--- /dev/null
+++ b/mobile/android/components/geckoview/GeckoViewStartup.js
@@ -0,0 +1,155 @@
+/* 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.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(aOptions) {
+    let {name, script, service, module, observers, ppmm, mm} = aOptions;
+    if (script) {
+      XPCOMUtils.defineLazyScriptGetter(this, name, script);
+    } else if (module) {
+      XPCOMUtils.defineLazyGetter(this, name, _ => {
+        let sandbox = {};
+        Cu.import(module, sandbox);
+        if (aOptions.init) {
+          aOptions.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 (!aOptions.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 (!aOptions.once) {
+          target.addMessageListener(msg.name, this[name]);
+        }
+        this[name].receiveMessage(msg);
+      };
+      (ppmm || mm).forEach(msg => target.addMessageListener(msg, listener));
+    }
+  },
+
+  addLazyEventListener: function(aOptions) {
+    let {name, target, events, options} = aOptions;
+    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");
+
+        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.
+          this.addLazyGetter({
+            name: "GeckoViewPrompt",
+            service: "@mozilla.org/prompter;1",
+          });
+        }
+        break;
+      }
+
+      case "profile-after-change": {
+        // Parent process only.
+        // ContentPrefServiceParent is needed for e10s file picker.
+        this.addLazyGetter({
+          name: "ContentPrefServiceParent",
+          module: "resource://gre/modules/ContentPrefServiceParent.jsm",
+          init: cpsp => cpsp.alwaysInit(),
+          ppmm: [
+            "ContentPrefs:FunctionCall",
+            "ContentPrefs:AddObserverForName",
+            "ContentPrefs:RemoveObserverForName",
+          ],
+        });
+
+        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 = 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;
+        }
+
+        this.addLazyEventListener({
+          name: "GeckoViewPrompt",
+          target: win,
+          events: [
+            "click",
+            "contextmenu",
+          ],
+          options: {
+            capture: false,
+            mozSystemGroup: true,
+          },
+        });
+        break;
+      }
+    }
+  },
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([GeckoViewStartup]);
--- a/mobile/android/components/geckoview/moz.build
+++ b/mobile/android/components/geckoview/moz.build
@@ -3,9 +3,10 @@
 # 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/.
 
 EXTRA_COMPONENTS += [
     'GeckoView.manifest',
     'GeckoViewPermission.js',
     'GeckoViewPrompt.js',
+    'GeckoViewStartup.js',
 ]
--- a/mobile/android/installer/package-manifest.in
+++ b/mobile/android/installer/package-manifest.in
@@ -521,16 +521,17 @@
 [mobile]
 @BINPATH@/chrome/geckoview@JAREXT@
 @BINPATH@/chrome/geckoview.manifest
 
 #ifdef MOZ_GECKOVIEW_JAR
 @BINPATH@/components/GeckoView.manifest
 @BINPATH@/components/GeckoViewPrompt.js
 @BINPATH@/components/GeckoViewPermission.js
+@BINPATH@/components/GeckoViewStartup.js
 #else
 @BINPATH@/chrome/chrome@JAREXT@
 @BINPATH@/chrome/chrome.manifest
 @BINPATH@/components/AboutRedirector.js
 @BINPATH@/components/AddonUpdateService.js
 @BINPATH@/components/BlocklistPrompt.js
 @BINPATH@/components/BrowserCLH.js
 @BINPATH@/components/ColorPicker.js