Bug 1435959 - Fix missing network requests in netmonitor panel for oop extensions. draft
authorLuca Greco <lgreco@mozilla.com>
Mon, 19 Feb 2018 18:00:35 +0100
changeset 765349 6763c48e4faeb1c7effdb97d8bbfc4dca5ab76ab
parent 765246 f1965cf7425fe422c9e9c78018f11b97e0a0f229
push id102038
push userluca.greco@alcacoop.it
push dateFri, 09 Mar 2018 16:27:56 +0000
bugs1435959
milestone60.0a1
Bug 1435959 - Fix missing network requests in netmonitor panel for oop extensions. MozReview-Commit-ID: F8jzwBveACm
browser/components/extensions/test/browser/browser-common.ini
browser/components/extensions/test/browser/browser_ext_addon_debugging_netmonitor.js
devtools/client/framework/ToolboxProcess.jsm
devtools/server/actors/webconsole.js
devtools/server/actors/webextension.js
--- a/browser/components/extensions/test/browser/browser-common.ini
+++ b/browser/components/extensions/test/browser/browser-common.ini
@@ -33,16 +33,17 @@ support-files =
   serviceWorker.js
   searchSuggestionEngine.xml
   searchSuggestionEngine.sjs
   ../../../../../toolkit/components/extensions/test/mochitest/head_webrequest.js
   ../../../../../toolkit/components/extensions/test/mochitest/redirection.sjs
   ../../../../../toolkit/components/reader/test/readerModeNonArticle.html
   ../../../../../toolkit/components/reader/test/readerModeArticle.html
 
+[browser_ext_addon_debugging_netmonitor.js]
 [browser_ext_browserAction_area.js]
 [browser_ext_browserAction_experiment.js]
 [browser_ext_browserAction_context.js]
 skip-if = os == 'win' || os == 'mac' # Bug 1405453
 [browser_ext_browserAction_contextMenu.js]
 # bug 1369197
 skip-if = os == 'linux'
 [browser_ext_browserAction_disabled.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_addon_debugging_netmonitor.js
@@ -0,0 +1,132 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+ChromeUtils.defineModuleGetter(this, "BrowserToolboxProcess",
+                               "resource://devtools/client/framework/ToolboxProcess.jsm");
+
+async function setupToolboxProcessTest(toolboxProcessScript) {
+  // Enable addon debugging.
+  await SpecialPowers.pushPrefEnv({
+    "set": [
+      // Force enabling of addons debugging
+      ["devtools.chrome.enabled", true],
+      ["devtools.debugger.remote-enabled", true],
+      // Disable security prompt
+      ["devtools.debugger.prompt-connection", false],
+      // Enable Browser toolbox test script execution via env variable
+      ["devtools.browser-toolbox.allow-unsafe-script", true],
+    ],
+  });
+
+  let env = Cc["@mozilla.org/process/environment;1"]
+              .getService(Ci.nsIEnvironment);
+  env.set("MOZ_TOOLBOX_TEST_SCRIPT", `(${toolboxProcessScript})();`);
+  registerCleanupFunction(() => {
+    env.set("MOZ_TOOLBOX_TEST_SCRIPT", "");
+  });
+}
+
+add_task(async function test_addon_debugging_netmonitor_panel() {
+  const EXTENSION_ID = "test-monitor-panel@mozilla";
+
+  function background() {
+    let expectedURL;
+    window.doFetchHTTPRequest = async function(urlToFetch) {
+      expectedURL = urlToFetch;
+      await fetch(urlToFetch);
+    };
+    window.testNetworkRequestReceived = async function(requests) {
+      browser.test.log("Addon Debugging Netmonitor panel collected requests: " +
+                       JSON.stringify(requests));
+      browser.test.assertEq(1, requests.length, "Got one request logged");
+      browser.test.assertEq("GET", requests[0].method, "Got a GET request");
+      browser.test.assertEq(expectedURL, requests[0].url, "Got the expected request url");
+
+      browser.test.notifyPass("netmonitor_request_logged");
+    };
+    browser.test.sendMessage("ready");
+  }
+
+  let extension = ExtensionTestUtils.loadExtension({
+    background,
+    useAddonManager: "temporary",
+    manifest: {
+      permissions: ["http://mochi.test/"],
+      applications: {
+        gecko: {id: EXTENSION_ID},
+      },
+    },
+  });
+
+  await extension.startup();
+  await extension.awaitMessage("ready");
+
+  // Be careful, this JS function is going to be executed in the addon toolbox,
+  // which lives in another process. So do not try to use any scope variable!
+  const toolboxProcessScript = async function() {
+    /* eslint-disable no-undef */
+    async function waitFor(condition) {
+      while (!condition()) {
+        // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+        await new Promise(done => window.setTimeout(done, 1000));
+      }
+    }
+
+    const console = await toolbox.selectTool("webconsole");
+    const {hud} = console;
+    const {jsterm} = hud;
+
+    const netmonitor = await toolbox.selectTool("netmonitor");
+
+    const expectedURL = "http://mochi.test:8888/?test_netmonitor=1";
+
+    // Call a function defined in the target extension to make it
+    // fetch from an expected http url.
+    await jsterm.execute(`doFetchHTTPRequest("${expectedURL}");`);
+
+    await waitFor(() => {
+      return !netmonitor.panelWin.document.querySelector(".request-list-empty-notice");
+    });
+
+    let {store} = netmonitor.panelWin;
+
+    // NOTE: we need to filter the requests to the ones that we expect until
+    // the network monitor is not yet filtering out the requests that are not
+    // coming from an extension window or a descendent of an extension window,
+    // in both oop and non-oop extension mode (filed as Bug 1442621).
+    function filterRequest(request) {
+      return request.url === expectedURL;
+    }
+
+    let requests;
+
+    await waitFor(() => {
+      requests = Array.from(store.getState().requests.requests.values())
+                      .filter(filterRequest);
+
+      return requests.length > 0;
+    });
+
+    // Call a function defined in the target extension to make assertions
+    // on the network requests collected by the netmonitor panel.
+    await jsterm.execute(`testNetworkRequestReceived(${JSON.stringify(requests)});`);
+    /* eslint-enable no-undef */
+  };
+
+  await setupToolboxProcessTest(toolboxProcessScript);
+  const browserToolboxProcess = new BrowserToolboxProcess({
+    addonID: EXTENSION_ID,
+  });
+
+  await extension.awaitFinish("netmonitor_request_logged");
+
+  let onToolboxClose = browserToolboxProcess.once("close");
+  await browserToolboxProcess.close();
+
+  await onToolboxClose;
+
+  info("Addon Toolbox closed");
+
+  await extension.unload();
+});
--- a/devtools/client/framework/ToolboxProcess.jsm
+++ b/devtools/client/framework/ToolboxProcess.jsm
@@ -341,31 +341,33 @@ BrowserToolboxProcess.prototype = {
   /**
    * Closes the remote debugging server and kills the toolbox process.
    */
   close: async function () {
     if (this.closed) {
       return;
     }
 
+    this.closed = true;
+
     dumpn("Cleaning up the chrome debugging process.");
+
     Services.obs.removeObserver(this.close, "quit-application");
 
     this._dbgProcess.stdout.close();
     await this._dbgProcess.kill();
 
     this._telemetry.toolClosed("jsbrowserdebugger");
     if (this.debuggerServer) {
       this.debuggerServer.off("connectionchange", this._onConnectionChange);
       this.debuggerServer.destroy();
       this.debuggerServer = null;
     }
 
     dumpn("Chrome toolbox is now closed...");
-    this.closed = true;
     this.emit("close", this);
     processes.delete(this);
 
     this._dbgProcess = null;
     this._options = null;
     if (this.loader) {
       this.loader.destroy();
     }
--- a/devtools/server/actors/webconsole.js
+++ b/devtools/server/actors/webconsole.js
@@ -81,17 +81,16 @@ function WebConsoleActor(connection, par
             this._onChangedToplevelDocument);
   this._onObserverNotification = this._onObserverNotification.bind(this);
   if (this.parentActor.isRootActor) {
     Services.obs.addObserver(this._onObserverNotification,
                              "last-pb-context-exited");
   }
 
   this.traits = {
-    customNetworkRequest: !this._parentIsContentActor,
     evaluateJSAsync: true,
     transferredResponseSize: true,
     selectedObjectActor: true, // 44+
   };
 }
 
 WebConsoleActor.prototype =
 {
@@ -159,26 +158,16 @@ WebConsoleActor.prototype =
 
   /**
    * List of supported features by the console actor.
    * @type object
    */
   traits: null,
 
   /**
-   * Boolean getter that tells if the parent actor is a ContentActor.
-   *
-   * @private
-   * @type boolean
-   */
-  get _parentIsContentActor() {
-    return this.parentActor.constructor.name == "ContentActor";
-  },
-
-  /**
    * The window or sandbox we work with.
    * Note that even if it is named `window` it refers to the current
    * global we are debugging, which can be a Sandbox for addons
    * or browser content toolbox.
    *
    * @type nsIDOMWindow or Sandbox
    */
   get window() {
@@ -582,17 +571,26 @@ WebConsoleActor.prototype =
    * @return object
    *         The response object which holds the startedListeners array.
    */
   onStartListeners: function (request) {
     let startedListeners = [];
     let window = !this.parentActor.isRootActor ? this.window : null;
     let messageManager = null;
 
-    if (this._parentIsContentActor) {
+    // Check if the actor is running in a child process (but only if
+    // Services.appinfo exists, to prevent onStartListeners to fail
+    // when the target is a Worker).
+    let processBoundary = Services.appinfo && (
+      Services.appinfo.processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT
+    );
+
+    // Retrieve a message manager from the parent actor if this actor is
+    // not currently running in the main process.
+    if (processBoundary) {
       messageManager = this.parentActor.messageManager;
     }
 
     while (request.listeners.length > 0) {
       let listener = request.listeners.shift();
       switch (listener) {
         case "PageError":
           // Workers don't support this message type yet
@@ -624,18 +622,16 @@ WebConsoleActor.prototype =
           if (!this.networkMonitor) {
             // Create a StackTraceCollector that's going to be shared both by
             // the NetworkMonitorChild (getting messages about requests from
             // parent) and by the NetworkMonitor that directly watches service
             // workers requests.
             this.stackTraceCollector = new StackTraceCollector({ window });
             this.stackTraceCollector.init();
 
-            let processBoundary = Services.appinfo.processType !=
-                                  Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
             if (messageManager && processBoundary) {
               // Start a network monitor in the parent process to listen to
               // most requests than happen in parent
               this.networkMonitor =
                 new NetworkMonitorChild(this.parentActor.outerWindowID,
                                         messageManager, this.conn, this);
               this.networkMonitor.init();
               // Spawn also one in the child to listen to service workers
--- a/devtools/server/actors/webextension.js
+++ b/devtools/server/actors/webextension.js
@@ -55,16 +55,28 @@ const FALLBACK_DOC_MESSAGE = "Your addon
  */
 function WebExtensionChildActor(conn, chromeGlobal, prefix, addonId) {
   ChromeActor.call(this, conn);
 
   this._chromeGlobal = chromeGlobal;
   this._prefix = prefix;
   this.id = addonId;
 
+  // Redefine the messageManager getter to return the chromeGlobal
+  // as the messageManager for this actor (which is the browser XUL
+  // element used by the parent actor running in the main process to
+  // connect to the extension process).
+  Object.defineProperty(this, "messageManager", {
+    enumerable: true,
+    configurable: true,
+    get: () => {
+      return this._chromeGlobal;
+    }
+  });
+
   // Bind the _allowSource helper to this, it is used in the
   // TabActor to lazily create the TabSources instance.
   this._allowSource = this._allowSource.bind(this);
   this._onParentExit = this._onParentExit.bind(this);
 
   this._chromeGlobal.addMessageListener("debug:webext_parent_exit", this._onParentExit);
 
   // Set the consoleAPIListener filtering options