Bug 1169290 - Make Marionette component safe to load in child process. r?maja_zf draft
authorAndreas Tolfsen <ato@sny.no>
Sat, 27 Jan 2018 19:34:32 +0000
changeset 753896 6f37570a152e70287a00f07a1b216412ca7e1ff5
parent 753895 e37d3479cd09c0f3128183fc1c0a71fb547caca3
child 753897 c2ce4da729c2f9acc429b9d840d49aa97cfa519b
push id98715
push userbmo:ato@sny.no
push dateMon, 12 Feb 2018 16:37:16 +0000
reviewersmaja_zf
bugs1169290
milestone60.0a1
Bug 1169290 - Make Marionette component safe to load in child process. r?maja_zf The Marionette XPCOM component is loaded once per process, but only ever initialised in the main process. A subprocess that calls nsIMarionette.running will always see it return false because loading the MarionetteComponent class resets the MarionetteComponent#server property to null, causing MarionetteComponent#running to return false. To report the correct value in child processes they need to query the main process for the running state. This patch introduces a synchronous IPC message call to the main process using the child process message manager (CPMM). Because nsIMarionette is currently never used in a subprocess it is considered acceptable to use sync IPC in this case, especially given the circumstances that Marionette instruments the browser and is not tied to any frontend Firefox UX. MozReview-Commit-ID: 93xtZN4MvWq
testing/marionette/components/marionette.js
--- a/testing/marionette/components/marionette.js
+++ b/testing/marionette/components/marionette.js
@@ -41,16 +41,19 @@ const ENV_ENABLED = "MOZ_MARIONETTE";
 // a different profile in order to test things like Firefox refresh.
 // The environment variable itself, if present, is interpreted as a
 // JSON structure, with the keys mapping to preference names in the
 // "marionette." branch, and the values to the values of those prefs. So
 // something like {"port": 4444} would result in the marionette.port
 // pref being set to 4444.
 const ENV_PRESERVE_PREFS = "MOZ_MARIONETTE_PREF_STATE_ACROSS_RESTARTS";
 
+const isRemote = Services.appinfo.processType ==
+    Services.appinfo.PROCESS_TYPE_CONTENT;
+
 const LogLevel = {
   get(level) {
     let levels = new Map([
       ["fatal", Log.Level.Fatal],
       ["error", Log.Level.Error],
       ["warn", Log.Level.Warn],
       ["info", Log.Level.Info],
       ["config", Log.Level.Config],
@@ -132,47 +135,59 @@ const prefs = {
         for (let prefName of Object.keys(prefs)) {
           Preferences.set(prefName, prefs[prefName]);
         }
       }
     }
   },
 };
 
-class MarionetteComponent {
+class MarionetteMainProcess {
   constructor() {
     this.server = null;
 
     // holds reference to ChromeWindow
     // used to run GFX sanity tests on Windows
     this.gfxWindow = null;
 
     // indicates that all pending window checks have been completed
     // and that we are ready to start the Marionette server
     this.finalUIStartup = false;
 
     log.level = prefs.logLevel;
 
     this.enabled = env.exists(ENV_ENABLED);
 
     Services.prefs.addObserver(PREF_ENABLED, this);
+    Services.ppmm.addMessageListener("Marionette:IsRunning", this);
   }
 
   get running() {
     return this.server && this.server.alive;
   }
 
   set enabled(value) {
     Services.prefs.setBoolPref(PREF_ENABLED, value);
   }
 
   get enabled() {
     return Services.prefs.getBoolPref(PREF_ENABLED);
   }
 
+  receiveMessage({name}) {
+    switch (name) {
+      case "Marionette:IsRunning":
+        return this.running;
+
+      default:
+        log.warn("Unknown IPC message to main process: " + name);
+        return null;
+    }
+  }
+
   observe(subject, topic) {
     log.debug(`Received observer notification ${topic}`);
 
     switch (topic) {
       case "nsPref:changed":
         if (Services.prefs.getBoolPref(PREF_ENABLED)) {
           this.init();
         } else {
@@ -306,24 +321,48 @@ class MarionetteComponent {
     return XPCOMUtils.generateQI([
       Ci.nsICommandLineHandler,
       Ci.nsIMarionette,
       Ci.nsIObserver,
     ]);
   }
 }
 
+class MarionetteContentProcess {
+  get running() {
+    let reply = Services.cpmm.sendSyncMessage("Marionette:IsRunning");
+    if (reply.length == 0) {
+      log.warn("No reply from main process");
+      return false;
+    }
+    return reply[0];
+  }
+
+  get QueryInterface() {
+    return XPCOMUtils.generateQI([Ci.nsIMarionette]);
+  }
+}
+
 const MarionetteFactory = {
+  instance_: null,
+
   createInstance(outer, iid) {
     if (outer) {
       throw Cr.NS_ERROR_NO_AGGREGATION;
     }
 
-    let marionette = new MarionetteComponent();
-    return marionette.QueryInterface(iid);
+    if (!this.instance_) {
+      if (isRemote) {
+        this.instance_ = new MarionetteContentProcess();
+      } else {
+        this.instance_ = new MarionetteMainProcess();
+      }
+    }
+
+    return this.instance_.QueryInterface(iid);
   },
 };
 
 function Marionette() {}
 
 Marionette.prototype = {
   classDescription: "Marionette component",
   classID: Components.ID("{786a1369-dca5-4adc-8486-33d23c88010a}"),