Bug 1435959 - Fix missing network requests in netmonitor panel for oop extensions.
MozReview-Commit-ID: F8jzwBveACm
--- 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