Bug 1460047 - 2. Add frame script support to ModuleManager; r?esawin draft
authorJim Chen <nchen@mozilla.com>
Fri, 11 May 2018 14:59:50 -0400
changeset 794354 68084e111f7f1651a44b3914995bf8124985c5e8
parent 794353 f4a79a066cfea022dc5138c7cda2bec361bbad4f
child 794355 57de5be73350c063aaf0f049a63bdb03916842b1
push id109650
push userbmo:nchen@mozilla.com
push dateFri, 11 May 2018 19:02:41 +0000
reviewersesawin
bugs1460047
milestone62.0a1
Bug 1460047 - 2. Add frame script support to ModuleManager; r?esawin Make ModuleManager capable of loading frame scripts. This consolidates more logic from GeckoViewModule into ModuleManager. MozReview-Commit-ID: 6LoRJeTKzep
mobile/android/chrome/geckoview/geckoview.js
mobile/android/modules/geckoview/GeckoViewContent.jsm
mobile/android/modules/geckoview/GeckoViewContentModule.jsm
mobile/android/modules/geckoview/GeckoViewModule.jsm
mobile/android/modules/geckoview/GeckoViewNavigation.jsm
mobile/android/modules/geckoview/GeckoViewScroll.jsm
mobile/android/modules/geckoview/GeckoViewSelectionAction.jsm
mobile/android/modules/geckoview/GeckoViewSettings.jsm
--- a/mobile/android/chrome/geckoview/geckoview.js
+++ b/mobile/android/chrome/geckoview/geckoview.js
@@ -44,21 +44,30 @@ var ModuleManager = {
             enabled: !!initData.modules[module.name],
             manager: self,
             ...module,
           }),
         ];
       }
     })());
 
+    window.document.documentElement.appendChild(aBrowser);
+
     WindowEventDispatcher.registerListener(this, [
       "GeckoView:UpdateModuleState",
       "GeckoView:UpdateInitData",
       "GeckoView:UpdateSettings",
     ]);
+
+    this.messageManager.addMessageListener("GeckoView:ContentModuleLoaded",
+                                           this);
+
+    this.forEach(module => {
+      module.onInit();
+    });
   },
 
   get window() {
     return window;
   },
 
   get browser() {
     return this._browser;
@@ -90,16 +99,17 @@ var ModuleManager = {
       }
     });
 
     this._browser.messageManager.sendAsyncMessage("GeckoView:UpdateSettings",
                                                   aSettings);
   },
 
   onEvent(aEvent, aData, aCallback) {
+    debug `onEvent ${aEvent} ${aData}`;
     switch (aEvent) {
       case "GeckoView:UpdateModuleState": {
         const module = this._modules.get(aData.module);
         if (module) {
           module.enabled = aData.enabled;
         }
         break;
       }
@@ -120,16 +130,29 @@ var ModuleManager = {
       }
 
       case "GeckoView:UpdateSettings": {
         this._updateSettings(aData);
         break;
       }
     }
   },
+
+  receiveMessage(aMsg) {
+    debug `receiveMessage ${aMsg.name} ${aMsg.data}`;
+    switch (aMsg.name) {
+      case "GeckoView:ContentModuleLoaded": {
+        const module = this._modules.get(aMsg.data.module);
+        if (module) {
+          module.onContentModuleLoaded();
+        }
+        break;
+      }
+    }
+  },
 };
 
 /**
  * ModuleInfo is the structure used by ModuleManager to represent individual
  * modules. It is responsible for loading the module JSM file if necessary,
  * and it acts as the intermediary between ModuleManager and the module
  * object that extends GeckoViewModule.
  */
@@ -144,46 +167,65 @@ class ModuleInfo {
    * @param onEnable Phase object for the enable phase, when the module is first
    *                 enabled by setting a delegate in Java.
    */
   constructor({manager, name, enabled, onInit, onEnable}) {
     this._manager = manager;
     this._name = name;
 
     this._impl = null;
+    this._contentModuleLoaded = false;
     this._enabled = false;
     // Only enable once we performed initialization.
     this._enabledOnInit = enabled;
 
-    this._loadPhase(onInit);
+    // For init, load resource _before_ initializing browser to support the
+    // onInitBrowser() override. However, load content module after initializing
+    // browser, because we don't have a message manager before then.
+    this._loadPhase({
+      resource: onInit && onInit.resource,
+    });
+    this._onInitPhase = {
+      frameScript: onInit && onInit.frameScript,
+    };
     this._onEnablePhase = onEnable;
   }
 
   onInit() {
     this._impl.onInit();
+    this._loadPhase(this._onInitPhase);
+    this._onInitPhase = null;
+
     this.enabled = this._enabledOnInit;
   }
 
   /**
    * Load resources according to a phase object that contains possible keys,
    *
    * "resource": specify the JSM resource to load for this module.
+   * "frameScript": specify a content JS frame script to load for this module.
    */
   _loadPhase(aPhase) {
     if (!aPhase) {
       return;
     }
 
     if (aPhase.resource && !this._impl) {
       const scope = {};
       const global = ChromeUtils.import(aPhase.resource, scope);
       const tag = this._name.replace("GeckoView", "GeckoView.");
       GeckoViewUtils.initLogging(tag, global);
       this._impl = new scope[this._name](this);
     }
+
+    if (aPhase.frameScript && !this._contentModuleLoaded) {
+      this._impl.onLoadContentModule();
+      this._manager.messageManager.loadFrameScript(aPhase.frameScript, true);
+      this._contentModuleLoaded = true;
+    }
   }
 
   get manager() {
     return this._manager;
   }
 
   get name() {
     return this._name;
@@ -210,20 +252,29 @@ class ModuleInfo {
 
     if (aEnabled) {
       this._loadPhase(this._onEnablePhase);
       this._onEnablePhase = null;
       this._impl.onEnable();
       this._impl.onSettingsUpdate();
     }
 
+    this._updateContentModuleState(/* includeSettings */ aEnabled);
+  }
+
+  onContentModuleLoaded() {
+    this._updateContentModuleState(/* includeSettings */ true);
+    this._impl.onContentModuleLoaded();
+  }
+
+  _updateContentModuleState(aIncludeSettings) {
     this._manager.messageManager.sendAsyncMessage("GeckoView:UpdateModuleState", {
       module: this._name,
-      enabled: aEnabled,
-      settings: aEnabled ? this._manager.settings : null,
+      enabled: this.enabled,
+      settings: aIncludeSettings ? this._manager.settings : null,
     });
   }
 }
 
 function createBrowser() {
   const browser = window.browser = document.createElement("browser");
   browser.setAttribute("type", "content");
   browser.setAttribute("primary", "true");
@@ -239,56 +290,57 @@ function startup() {
     name: "GeckoViewAccessibility",
     onInit: {
       resource: "resource://gre/modules/GeckoViewAccessibility.jsm",
     },
   }, {
     name: "GeckoViewContent",
     onInit: {
       resource: "resource://gre/modules/GeckoViewContent.jsm",
+      frameScript: "chrome://geckoview/content/GeckoViewContent.js",
     },
   }, {
     name: "GeckoViewNavigation",
     onInit: {
       resource: "resource://gre/modules/GeckoViewNavigation.jsm",
     },
+    onEnable: {
+      frameScript: "chrome://geckoview/content/GeckoViewNavigationContent.js",
+    },
   }, {
     name: "GeckoViewProgress",
     onEnable: {
       resource: "resource://gre/modules/GeckoViewProgress.jsm",
     },
   }, {
     name: "GeckoViewScroll",
     onEnable: {
       resource: "resource://gre/modules/GeckoViewScroll.jsm",
+      frameScript: "chrome://geckoview/content/GeckoViewScrollContent.js",
     },
   }, {
     name: "GeckoViewSelectionAction",
     onEnable: {
       resource: "resource://gre/modules/GeckoViewSelectionAction.jsm",
+      frameScript: "chrome://geckoview/content/GeckoViewSelectionActionContent.js",
     },
   }, {
     name: "GeckoViewSettings",
     onInit: {
       resource: "resource://gre/modules/GeckoViewSettings.jsm",
+      frameScript: "chrome://geckoview/content/GeckoViewContentSettings.js",
     },
   }, {
     name: "GeckoViewTab",
     onInit: {
       resource: "resource://gre/modules/GeckoViewTab.jsm",
     },
   }, {
     name: "GeckoViewTrackingProtection",
     onEnable: {
       resource: "resource://gre/modules/GeckoViewTrackingProtection.jsm",
     },
   }]);
 
-  window.document.documentElement.appendChild(browser);
-
-  ModuleManager.forEach(module => {
-    module.onInit();
-  });
-
   // Move focus to the content window at the end of startup,
   // so things like text selection can work properly.
   browser.focus();
 }
--- a/mobile/android/modules/geckoview/GeckoViewContent.jsm
+++ b/mobile/android/modules/geckoview/GeckoViewContent.jsm
@@ -15,18 +15,16 @@ class GeckoViewContent extends GeckoView
         "GeckoViewContent:ExitFullScreen",
         "GeckoView:RestoreState",
         "GeckoView:SaveState",
         "GeckoView:SetActive",
         "GeckoView:ZoomToInput",
     ]);
 
     this.messageManager.addMessageListener("GeckoView:SaveStateFinish", this);
-
-    this.registerContent("chrome://geckoview/content/GeckoViewContent.js");
   }
 
   onEnable() {
     this.window.addEventListener("MozDOMFullScreen:Entered", this,
                                  /* capture */ true, /* untrusted */ false);
     this.window.addEventListener("MozDOMFullScreen:Exited", this,
                                  /* capture */ true, /* untrusted */ false);
 
--- a/mobile/android/modules/geckoview/GeckoViewContentModule.jsm
+++ b/mobile/android/modules/geckoview/GeckoViewContentModule.jsm
@@ -79,18 +79,19 @@ class GeckoViewContentModule {
         if (settings && enabled) {
           this.onSettingsUpdate();
         }
       }
     );
 
     this.onInit();
 
-    this.messageManager.sendAsyncMessage(
-      "GeckoView:ContentRegistered", { module: this.moduleName });
+    this.messageManager.sendAsyncMessage("GeckoView:ContentModuleLoaded", {
+      module: this.moduleName,
+    });
   }
 
   // Override to initialize module.
   onInit() {}
 
   // Override to detect settings change. Access settings via this.settings.
   onSettingsUpdate() {}
 
--- a/mobile/android/modules/geckoview/GeckoViewModule.jsm
+++ b/mobile/android/modules/geckoview/GeckoViewModule.jsm
@@ -59,37 +59,27 @@ class GeckoViewModule {
   onSettingsUpdate() {}
 
   // Override to enable module after setting a Java delegate.
   onEnable() {}
 
   // Override to disable module after clearing the Java delegate.
   onDisable() {}
 
-  registerContent(aUri) {
-    if (this._isContentLoaded) {
-      return;
-    }
-    this._isContentLoaded = true;
+  // Override to perform actions when content module has started loading;
+  // by default, pause events so events that depend on content modules can work.
+  onLoadContentModule() {
     this._eventProxy.enableQueuing(true);
+  }
 
-    let self = this;
-    this.messageManager.addMessageListener("GeckoView:ContentRegistered",
-      function listener(aMsg) {
-        if (aMsg.data.module !== self.name) {
-          return;
-        }
-        self.messageManager.removeMessageListener("GeckoView:ContentRegistered",
-                                                  listener);
-        self.messageManager.sendAsyncMessage("GeckoView:UpdateSettings",
-                                             self.settings);
-        self._eventProxy.enableQueuing(false);
-        self._eventProxy.dispatchQueuedEvents();
-    });
-    this.messageManager.loadFrameScript(aUri, true);
+  // Override to perform actions when content module has finished loading;
+  // by default, un-pause events and flush queued events.
+  onContentModuleLoaded() {
+    this._eventProxy.enableQueuing(false);
+    this._eventProxy.dispatchQueuedEvents();
   }
 
   registerListener(aEventList) {
     this._eventProxy.registerListener(aEventList);
   }
 
   unregisterListener() {
     this._eventProxy.unregisterListener();
--- a/mobile/android/modules/geckoview/GeckoViewNavigation.jsm
+++ b/mobile/android/modules/geckoview/GeckoViewNavigation.jsm
@@ -245,19 +245,16 @@ class GeckoViewNavigation extends GeckoV
   canClose() {
     debug `canClose`;
     return true;
   }
 
   onEnable() {
     debug `onEnable`;
 
-    this.registerContent(
-      "chrome://geckoview/content/GeckoViewNavigationContent.js");
-
     let flags = Ci.nsIWebProgress.NOTIFY_LOCATION;
     this.progressFilter =
       Cc["@mozilla.org/appshell/component/browser-status-filter;1"]
       .createInstance(Ci.nsIWebProgress);
     this.progressFilter.addProgressListener(this, flags);
     this.browser.addProgressListener(this.progressFilter, flags);
   }
 
--- a/mobile/android/modules/geckoview/GeckoViewScroll.jsm
+++ b/mobile/android/modules/geckoview/GeckoViewScroll.jsm
@@ -7,11 +7,10 @@
 var EXPORTED_SYMBOLS = ["GeckoViewScroll"];
 
 ChromeUtils.import("resource://gre/modules/GeckoViewModule.jsm");
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 
 class GeckoViewScroll extends GeckoViewModule {
   onEnable() {
     debug `onEnable`;
-    this.registerContent("chrome://geckoview/content/GeckoViewScrollContent.js");
   }
 }
--- a/mobile/android/modules/geckoview/GeckoViewSelectionAction.jsm
+++ b/mobile/android/modules/geckoview/GeckoViewSelectionAction.jsm
@@ -8,11 +8,10 @@ var EXPORTED_SYMBOLS = ["GeckoViewSelect
 
 ChromeUtils.import("resource://gre/modules/GeckoViewModule.jsm");
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 
 // Handles inter-op between accessible carets and GeckoSession.
 class GeckoViewSelectionAction extends GeckoViewModule {
   onEnable() {
     debug `onEnable`;
-    this.registerContent("chrome://geckoview/content/GeckoViewSelectionActionContent.js");
   }
 }
--- a/mobile/android/modules/geckoview/GeckoViewSettings.jsm
+++ b/mobile/android/modules/geckoview/GeckoViewSettings.jsm
@@ -37,19 +37,16 @@ class GeckoViewSettings extends GeckoVie
     if (this.settings.useMultiprocess) {
       this.browser.setAttribute("remote", "true");
     }
   }
 
   onInit() {
     this._useTrackingProtection = false;
     this._useDesktopMode = false;
-
-    this.registerContent(
-        "chrome://geckoview/content/GeckoViewContentSettings.js");
   }
 
   onSettingsUpdate() {
     const settings = this.settings;
     debug `onSettingsUpdate: ${settings}`;
 
     this.displayMode = settings.displayMode;
     this.useTrackingProtection = !!settings.useTrackingProtection;