Bug 1189901: part 3: talos-powers: add generic ParentExec. r=mconley
authorAvi Halachmi <avihpit@yahoo.com>
Thu, 29 Sep 2016 21:00:22 +0300
changeset 315863 a72a4afe756ea5664d2391bfa6ec0882e91ffc89
parent 315862 f8719709d98a121fbd36242098026aa0031b303f
child 315864 2b747bcd819e4379a7f4036d271a398b9236802e
push id20634
push usercbook@mozilla.com
push dateFri, 30 Sep 2016 10:10:13 +0000
treeherderfx-team@afe79b010d13 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmconley
bugs1189901
milestone52.0a1
Bug 1189901: part 3: talos-powers: add generic ParentExec. r=mconley
testing/talos/talos/talos-powers/chrome/talos-powers-content.js
testing/talos/talos/talos-powers/components/TalosPowersService.js
testing/talos/talos/talos-powers/content/TalosPowersContent.js
--- a/testing/talos/talos/talos-powers/chrome/talos-powers-content.js
+++ b/testing/talos/talos/talos-powers/chrome/talos-powers-content.js
@@ -1,12 +1,14 @@
 /* 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/. */
 
+// This file is loaded as a framescript
+
 var { interfaces: Ci, utils: Cu } = Components;
 
 /**
  * Content that wants to quit the whole session should
  * fire the TalosQuitApplication custom event. This will
  * attempt to force-quit the browser.
  */
 addEventListener("TalosQuitApplication", event => {
@@ -75,8 +77,47 @@ addEventListener("TalosPowersContentGetS
       detail: msg.data,
     }, content);
 
     content.dispatchEvent(
       new content.CustomEvent("TalosPowersContentGetStartupInfoResult",
                               event));
   });
 });
+
+/* *
+ * Mediator for the generic ParentExec mechanism.
+ * Listens for a query event from the content, forwards it as a query message
+ * to the parent, listens to a parent reply message, and forwards it as a reply
+ * event for the content to capture.
+ * The consumer API for this mechanism is at content/TalosPowersContent.js
+ * and the callees are at ParentExecServices at components/TalosPowersService.js
+ */
+addEventListener("TalosPowers:ParentExec:QueryEvent", function (e) {
+  if (content.location.protocol != "file:" &&
+      content.location.hostname != "localhost" &&
+      content.location.hostname != "127.0.0.1") {
+    throw new Error("TalosPowers:ParentExec may only be used with local content");
+  }
+  let uniqueMessageId = "TalosPowers:ParentExec:"
+                      + content.document.documentURI + Date.now() + Math.random();
+
+  // Listener for the reply from the parent process
+  addMessageListener("TalosPowers:ParentExec:ReplyMsg", function done(reply) {
+    if (reply.data.id != uniqueMessageId)
+      return;
+
+    removeMessageListener("TalosPowers:ParentExec:ReplyMsg", done);
+
+    // reply to content via an event
+    let contentEvent = Cu.cloneInto({
+      bubbles: true,
+      detail: reply.data.result
+    }, content);
+    content.dispatchEvent(new content.CustomEvent(e.detail.listeningTo, contentEvent));
+  });
+
+  // Send the query to the parent process
+  sendAsyncMessage("TalosPowers:ParentExec:QueryMsg", {
+    command: e.detail.command,
+    id: uniqueMessageId
+  });
+}, false, true);  // wantsUntrusted since we're exposing to unprivileged
--- a/testing/talos/talos/talos-powers/components/TalosPowersService.js
+++ b/testing/talos/talos/talos-powers/components/TalosPowersService.js
@@ -36,16 +36,17 @@ TalosPowersService.prototype = {
   },
 
   init() {
     Services.mm.loadFrameScript(FRAME_SCRIPT, true);
     Services.mm.addMessageListener("Talos:ForceQuit", this);
     Services.mm.addMessageListener("TalosContentProfiler:Command", this);
     Services.mm.addMessageListener("TalosPowersContent:ForceCCAndGC", this);
     Services.mm.addMessageListener("TalosPowersContent:GetStartupInfo", this);
+    Services.mm.addMessageListener("TalosPowers:ParentExec:QueryMsg", this);
     Services.obs.addObserver(this, "xpcom-shutdown", false);
   },
 
   uninit() {
     Services.obs.removeObserver(this, "xpcom-shutdown", false);
   },
 
   receiveMessage(message) {
@@ -61,16 +62,21 @@ TalosPowersService.prototype = {
       case "TalosPowersContent:ForceCCAndGC": {
         Cu.forceGC();
         Cu.forceCC();
         Cu.forceShrinkingGC();
         break;
       }
       case "TalosPowersContent:GetStartupInfo": {
         this.receiveGetStartupInfo(message);
+        break;
+      }
+      case "TalosPowers:ParentExec:QueryMsg": {
+        this.RecieveParentExecCommand(message);
+        break;
       }
     }
   },
 
   /**
    * Enable the SPS profiler with some settings and then pause
    * immediately.
    *
@@ -251,11 +257,48 @@ TalosPowersService.prototype = {
                             startupInfo);
       };
       Services.obs.addObserver(obs, "widget-first-paint", false);
     } else {
       mm.sendAsyncMessage("TalosPowersContent:GetStartupInfo:Result",
                           startupInfo);
     }
   },
+
+  // These services are exposed to local unprivileged content.
+  // Each service is a function which accepts an argument, a callback for sending
+  // the reply (possibly async), and the parent window as a utility.
+  // arg/reply semantice are service-specific.
+  // To add a service: add a method at ParentExecServices here, then at the content:
+  // <script src="chrome://talos-powers-content/content/TalosPowersContent.js"></script>
+  // and then e.g. TalosPowersParent.exec("sampleParentService", myArg, myCallback)
+  // Sample service:
+  /*
+    // arg: anything. return: sample reply
+    sampleParentService: function(arg, callback, win) {
+      win.setTimeout(function() {
+        callback("sample reply for: " + arg);
+      }, 500);
+    },
+
+  */
+  ParentExecServices: {
+  },
+
+  RecieveParentExecCommand(msg) {
+    function sendResult(result) {
+      let mm = msg.target.messageManager;
+      mm.sendAsyncMessage("TalosPowers:ParentExec:ReplyMsg", {
+        id: msg.data.id,
+        result: result
+      });
+    }
+
+    let command = msg.data.command;
+    if (!this.ParentExecServices.hasOwnProperty(command.name))
+      throw new Error("TalosPowers:ParentExec: Invalid service '" + command.name + "'");
+
+    this.ParentExecServices[command.name](command.data, sendResult, msg.target.ownerGlobal);
+  },
+
 };
 
 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([TalosPowersService]);
--- a/testing/talos/talos/talos-powers/content/TalosPowersContent.js
+++ b/testing/talos/talos/talos-powers/content/TalosPowersContent.js
@@ -1,13 +1,20 @@
 /* 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/. */
 
+// This file should be executed by the [possibly unprivileged] consumer, e.g.:
+// <script src="chrome://talos-powers-content/content/TalosPowersContent.js"></script>
+// and then e.g. TalosPowersParent.exec("sampleParentService", myArg, myCallback)
+// It marely sends a query event and possibly listens to a reply event, and does not
+// depend on any special privileges.
+
 var TalosPowersContent;
+var TalosPowersParent;
 
 (function() {
   TalosPowersContent = {
     /**
      * Synchronously force CC and GC in this process, as well as in the
      * parent process.
      */
     forceCCAndGC() {
@@ -40,9 +47,45 @@ var TalosPowersContent;
                          function onResult(e) {
           removeEventListener("TalosPowersContentGetStartupInfoResult",
                               onResult);
           resolve(e.detail);
         });
       });
     },
   };
+
+  /**
+   * Generic interface to service functions which run at the parent process.
+   */
+  // If including this script proves too much touble, you may embed the following
+  // code verbatim instead, and keep the copy up to date with its source here:
+  TalosPowersParent = {
+    replyId: 1,
+
+    // dispatch an event to the framescript and register the result/callback event
+    exec: function(commandName, arg, callback, opt_custom_window) {
+      let win = opt_custom_window || window;
+      let replyEvent = "TalosPowers:ParentExec:ReplyEvent:" + this.replyId++;
+      if (callback) {
+        win.addEventListener(replyEvent, function rvhandler(e) {
+          win.removeEventListener(replyEvent, rvhandler);
+          callback(e.detail);
+        });
+      }
+      win.dispatchEvent(
+        new win.CustomEvent("TalosPowers:ParentExec:QueryEvent", {
+          bubbles: true,
+          detail: {
+            command: {
+              name: commandName,
+              data: arg,
+            },
+            listeningTo: replyEvent,
+          }
+        })
+      );
+    },
+
+  };
+  // End of possibly embedded code
+
 })();