Backed out 2 changesets (bug 1598259) for dt perma fails. CLOSED TREE
authorRazvan Maries <rmaries@mozilla.com>
Fri, 22 Nov 2019 19:36:38 +0200
changeset 503395 3bd8841fab31690f88f9e1475a3a42e5f9780202
parent 503394 3079e0857fda4a4411bb5ff862ad635f2300ac8d
child 503396 ee7bdadd80bc03b8174aaf29f6916c48cc8cf89c
push id36833
push userbtara@mozilla.com
push dateFri, 22 Nov 2019 21:40:53 +0000
treeherdermozilla-central@2c912e46295e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1598259
milestone72.0a1
backs out38140fd01a52aa9e9f42d98200304eec6fcb6018
70b22c90ea2e2a3294d46a41cb4cb42be065537c
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Backed out 2 changesets (bug 1598259) for dt perma fails. CLOSED TREE Backed out changeset 38140fd01a52 (bug 1598259) Backed out changeset 70b22c90ea2e (bug 1598259)
devtools/client/aboutdebugging/test/browser/helper-addons.js
devtools/client/framework/moz.build
devtools/client/framework/test/browser-rtl.ini
devtools/client/framework/test/browser.ini
devtools/client/framework/test/browser_browser_toolbox.js
devtools/client/framework/test/browser_browser_toolbox_debugger.js
devtools/client/framework/test/browser_browser_toolbox_fission_inspector.js
devtools/client/framework/test/browser_browser_toolbox_rtl.js
devtools/client/framework/test/head.js
devtools/client/framework/test/helpers.js
devtools/client/framework/test/test_browser_toolbox_debugger.js
devtools/client/framework/toolbox-process-window.js
--- a/devtools/client/aboutdebugging/test/browser/helper-addons.js
+++ b/devtools/client/aboutdebugging/test/browser/helper-addons.js
@@ -12,16 +12,18 @@ function _getSupportsFile(path) {
   const uri = Services.io.newURI(CHROME_URL_ROOT + path);
   const fileurl = cr.convertChromeURL(uri);
   return fileurl.QueryInterface(Ci.nsIFileURL);
 }
 
 async function enableExtensionDebugging() {
   // Disable security prompt
   await pushPref("devtools.debugger.prompt-connection", false);
+  // Enable Browser toolbox test script execution via env variable
+  await pushPref("devtools.browser-toolbox.allow-unsafe-script", true);
 }
 /* exported enableExtensionDebugging */
 
 /**
  * Install an extension using the AddonManager so it does not show up as temporary.
  */
 async function installRegularExtension(pathOrFile) {
   const isFile = typeof pathOrFile.isFile === "function" && pathOrFile.isFile();
--- a/devtools/client/framework/moz.build
+++ b/devtools/client/framework/moz.build
@@ -1,16 +1,17 @@
 # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # 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/.
 
 BROWSER_CHROME_MANIFESTS += [
     'test/allocations/browser_allocations_target.ini',
+    'test/browser-rtl.ini',
     'test/browser-telemetry-startup.ini',
     'test/browser.ini',
     'test/metrics/browser_metrics_debugger.ini',
     'test/metrics/browser_metrics_inspector.ini',
     'test/metrics/browser_metrics_netmonitor.ini',
     'test/metrics/browser_metrics_webconsole.ini',
 ]
 XPCSHELL_TESTS_MANIFESTS += ['test/unit/xpcshell.ini']
new file mode 100644
--- /dev/null
+++ b/devtools/client/framework/test/browser-rtl.ini
@@ -0,0 +1,14 @@
+[DEFAULT]
+tags = devtools
+subsuite = devtools
+prefs =
+  # This test suite is dedicated to tests that need to run with the browser in RTL mode.
+  # This mode cannot be dynamically changed between tests reusing the browser instance and
+  # window.
+  intl.uidirection=1
+support-files =
+  head.js
+  !/devtools/client/shared/test/shared-head.js
+  !/devtools/client/shared/test/telemetry-test-helpers.js
+
+[browser_browser_toolbox_rtl.js]
--- a/devtools/client/framework/test/browser.ini
+++ b/devtools/client/framework/test/browser.ini
@@ -37,16 +37,17 @@ support-files =
   helper_disable_cache.js
   doc_theme.css
   doc_viewsource.html
   browser_toolbox_options_enable_serviceworkers_testing_frame_script.js
   browser_toolbox_options_enable_serviceworkers_testing.html
   serviceworker.js
   sjs_code_reload.sjs
   sjs_code_bundle_reload_map.sjs
+  test_browser_toolbox_debugger.js
   test_chrome_page.html
   !/devtools/client/debugger/test/mochitest/head.js
   !/devtools/client/debugger/test/mochitest/helpers.js
   !/devtools/client/debugger/test/mochitest/helpers/context.js
   !/devtools/client/shared/test/frame-script-utils.js
   !/devtools/client/shared/test/shared-head.js
   !/devtools/client/shared/test/shared-redux-head.js
   !/devtools/client/shared/test/telemetry-test-helpers.js
@@ -58,17 +59,16 @@ prefs =
 [browser_about-devtools-toolbox_load.js]
 [browser_about-devtools-toolbox_reload.js]
 [browser_browser_toolbox.js]
 skip-if = coverage # Bug 1387827
 [browser_browser_toolbox_debugger.js]
 skip-if = os == 'win' || debug || (bits == 64 && !debug && (os == 'mac' || os == 'linux')) # Bug 1282269, 1448084, Bug 1270731
 [browser_browser_toolbox_fission_inspector.js]
 skip-if = coverage # Bug 1387827
-[browser_browser_toolbox_rtl.js]
 [browser_devtools_api_destroy.js]
 [browser_dynamic_tool_enabling.js]
 [browser_front_parentFront.js]
 [browser_ignore_toolbox_network_requests.js]
 [browser_keybindings_01.js]
 [browser_keybindings_02.js]
 [browser_keybindings_03.js]
 [browser_menu_api.js]
--- a/devtools/client/framework/test/browser_browser_toolbox.js
+++ b/devtools/client/framework/test/browser_browser_toolbox.js
@@ -1,31 +1,95 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
-/* import-globals-from helpers.js */
-Services.scriptloader.loadSubScript(
-  "chrome://mochitests/content/browser/devtools/client/framework/test/helpers.js",
-  this
-);
-
 // There are shutdown issues for which multiple rejections are left uncaught.
 // See bug 1018184 for resolving these issues.
 const { PromiseTestUtils } = ChromeUtils.import(
   "resource://testing-common/PromiseTestUtils.jsm"
 );
 PromiseTestUtils.whitelistRejectionsGlobally(/File closed/);
 
 // On debug test slave, it takes about 50s to run the test.
 requestLongerTimeout(4);
 
 add_task(async function() {
-  const ToolboxTask = await initBrowserToolboxTask();
-  await ToolboxTask.importFunctions({});
+  await setupPreferencesForBrowserToolbox();
+
+  // Wait for a notification sent by a script evaluated in the webconsole
+  // of the browser toolbox.
+  const onCustomMessage = new Promise(done => {
+    Services.obs.addObserver(function listener(target, aTop, data) {
+      Services.obs.removeObserver(listener, "browser-toolbox-console-works");
+      done(data === "true");
+    }, "browser-toolbox-console-works");
+  });
 
-  const hasCloseButton = await ToolboxTask.spawn(null, async () => {
-    /* global gToolbox */
-    return !!gToolbox.doc.getElementById("toolbox-close");
+  // 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 env = Cc["@mozilla.org/process/environment;1"].getService(
+    Ci.nsIEnvironment
+  );
+  /* global toolbox */
+  const testScript = function() {
+    toolbox
+      .selectTool("webconsole")
+      .then(console => {
+        // This is for checking Browser Toolbox doesn't have a close button.
+        const hasCloseButton = !!toolbox.doc.getElementById("toolbox-close");
+        const { wrapper } = console.hud.ui;
+        const js = `Services.obs.notifyObservers(null, 'browser-toolbox-console-works', ${hasCloseButton} )`;
+        const onResult = new Promise(resolve => {
+          const onNewMessages = messages => {
+            for (const message of messages) {
+              if (message.node.classList.contains("result")) {
+                console.hud.ui.off("new-messages", onNewMessages);
+                resolve();
+              }
+            }
+          };
+          console.hud.ui.on("new-messages", onNewMessages);
+        });
+        wrapper.dispatchEvaluateExpression(js);
+        return onResult;
+      })
+      .then(() => toolbox.destroy());
+  };
+  env.set("MOZ_TOOLBOX_TEST_SCRIPT", "new " + testScript);
+  registerCleanupFunction(() => {
+    env.set("MOZ_TOOLBOX_TEST_SCRIPT", "");
   });
+
+  const { BrowserToolboxProcess } = ChromeUtils.import(
+    "resource://devtools/client/framework/ToolboxProcess.jsm"
+  );
+  is(
+    BrowserToolboxProcess.getBrowserToolboxSessionState(),
+    false,
+    "No session state initially"
+  );
+
+  let closePromise;
+  await new Promise(onRun => {
+    closePromise = new Promise(onClose => {
+      info("Opening the browser toolbox\n");
+      BrowserToolboxProcess.init(onClose, onRun);
+    });
+  });
+  ok(true, "Browser toolbox started\n");
+  is(
+    BrowserToolboxProcess.getBrowserToolboxSessionState(),
+    true,
+    "Has session state"
+  );
+
+  const hasCloseButton = await onCustomMessage;
+  ok(true, "Received the custom message");
   ok(!hasCloseButton, "Browser toolbox doesn't have a close button");
 
-  await ToolboxTask.destroy();
+  await closePromise;
+  ok(true, "Browser toolbox process just closed");
+  is(
+    BrowserToolboxProcess.getBrowserToolboxSessionState(),
+    false,
+    "No session state after closing"
+  );
 });
--- a/devtools/client/framework/test/browser_browser_toolbox_debugger.js
+++ b/devtools/client/framework/test/browser_browser_toolbox_debugger.js
@@ -1,18 +1,15 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // This test asserts that the new debugger works from the browser toolbox process
-
-/* import-globals-from helpers.js */
-Services.scriptloader.loadSubScript(
-  "chrome://mochitests/content/browser/devtools/client/framework/test/helpers.js",
-  this
-);
+// Its pass a big piece of Javascript string to the browser toolbox process via
+// MOZ_TOOLBOX_TEST_SCRIPT env variable. It does that as test resources fetched from
+// chrome://mochitests/ package isn't available from browser toolbox process.
 
 // There are shutdown issues for which multiple rejections are left uncaught.
 // See bug 1018184 for resolving these issues.
 const { PromiseTestUtils } = ChromeUtils.import(
   "resource://testing-common/PromiseTestUtils.jsm"
 );
 PromiseTestUtils.whitelistRejectionsGlobally(/File closed/);
 
@@ -21,18 +18,21 @@ requestLongerTimeout(4);
 
 const { fetch } = require("devtools/shared/DevToolsUtils");
 
 const debuggerHeadURL =
   CHROME_URL_ROOT + "../../debugger/test/mochitest/head.js";
 const helpersURL = CHROME_URL_ROOT + "../../debugger/test/mochitest/helpers.js";
 const helpersContextURL =
   CHROME_URL_ROOT + "../../debugger/test/mochitest/helpers/context.js";
+const testScriptURL = CHROME_URL_ROOT + "test_browser_toolbox_debugger.js";
 
 add_task(async function runTest() {
+  await setupPreferencesForBrowserToolbox();
+
   const s = Cu.Sandbox("http://mozilla.org");
 
   // Use a unique id for the fake script name in order to be able to run
   // this test more than once. That's because the Sandbox is not immediately
   // destroyed and so the debugger would display only one file but not necessarily
   // connected to the latest sandbox.
   const id = new Date().getTime();
 
@@ -52,118 +52,135 @@ add_task(async function runTest() {
     "1.8",
     testUrl,
     0
   );
 
   // Execute the function every second in order to trigger the breakpoint
   const interval = setInterval(s.plop, 1000);
 
+  // Be careful, this JS function is going to be executed in the browser toolbox,
+  // which lives in another process. So do not try to use any scope variable!
+  const env = Cc["@mozilla.org/process/environment;1"].getService(
+    Ci.nsIEnvironment
+  );
+  // First inject a very minimal head, with simplest assertion methods
+  // and very common globals
+  /* eslint-disable no-unused-vars */
+  const testHead = function() {
+    const info = msg => dump(msg + "\n");
+    const is = (a, b, description) => {
+      let msg =
+        "'" + JSON.stringify(a) + "' is equal to '" + JSON.stringify(b) + "'";
+      if (description) {
+        msg += " - " + description;
+      }
+      if (a !== b) {
+        msg = "FAILURE: " + msg;
+        dump(msg + "\n");
+        throw new Error(msg);
+      } else {
+        msg = "SUCCESS: " + msg;
+        dump(msg + "\n");
+      }
+    };
+    const ok = (a, description) => {
+      let msg = "'" + JSON.stringify(a) + "' is true";
+      if (description) {
+        msg += " - " + description;
+      }
+      if (!a) {
+        msg = "FAILURE: " + msg;
+        dump(msg + "\n");
+        throw new Error(msg);
+      } else {
+        msg = "SUCCESS: " + msg;
+        dump(msg + "\n");
+      }
+    };
+
+    const registerCleanupFunction = () => {};
+
+    const { require } = ChromeUtils.import(
+      "resource://devtools/shared/Loader.jsm"
+    );
+    const { Services } = ChromeUtils.import(
+      "resource://gre/modules/Services.jsm"
+    );
+
+    // Copied from shared-head.js:
+    // test_browser_toolbox_debugger.js uses waitForPaused, which relies on waitUntil
+    // which is normally provided by shared-head.js
+    const { setTimeout } = ChromeUtils.import(
+      "resource://gre/modules/Timer.jsm"
+    );
+    function waitUntil(predicate, interval = 10) {
+      if (predicate()) {
+        return Promise.resolve(true);
+      }
+      return new Promise(resolve => {
+        // TODO: fixme.
+        // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+        setTimeout(function() {
+          waitUntil(predicate, interval).then(() => resolve(true));
+        }, interval);
+      });
+    }
+  }
+    .toSource()
+    .replace(/^\(function\(\) \{|\}\)$/g, "");
+  /* eslint-enable no-unused-vars */
+  // Stringify testHead's function and remove `(function {` prefix and `})` suffix
+  // to ensure inner symbols gets exposed to next pieces of code
+  // Then inject new debugger head file
   let { content: debuggerHead } = await fetch(debuggerHeadURL);
 
   // Also include the debugger helpers which are separated from debugger's head to be
   // reused in other modules.
   const { content: debuggerHelpers } = await fetch(helpersURL);
   const { content: debuggerContextHelpers } = await fetch(helpersContextURL);
   debuggerHead = debuggerHead + debuggerContextHelpers + debuggerHelpers;
 
   // We remove its import of shared-head, which isn't available in browser toolbox process
   // And isn't needed thanks to testHead's symbols
   debuggerHead = debuggerHead.replace(
     /Services.scriptloader.loadSubScript[^\)]*\);/g,
     ""
   );
-  const ToolboxTask = await initBrowserToolboxTask();
-  await ToolboxTask.importScript(debuggerHead);
-  await ToolboxTask.importFunctions({
-    info: msg => dump(msg + "\n"),
-    is: (a, b, description) => {
-      let msg =
-        "'" + JSON.stringify(a) + "' is equal to '" + JSON.stringify(b) + "'";
-      if (description) {
-        msg += " - " + description;
-      }
-      if (a !== b) {
-        msg = "FAILURE: " + msg;
-        dump(msg + "\n");
-        throw new Error(msg);
-      } else {
-        msg = "SUCCESS: " + msg;
-        dump(msg + "\n");
-      }
-    },
-    ok: (a, description) => {
-      let msg = "'" + JSON.stringify(a) + "' is true";
-      if (description) {
-        msg += " - " + description;
-      }
-      if (!a) {
-        msg = "FAILURE: " + msg;
-        dump(msg + "\n");
-        throw new Error(msg);
-      } else {
-        msg = "SUCCESS: " + msg;
-        dump(msg + "\n");
-      }
-    },
-    waitUntil,
+
+  // Finally, fetch the debugger test script that is going to be execute in the browser
+  // toolbox process
+  const testScript = (await fetch(testScriptURL)).content;
+  const source =
+    'try { let testUrl = "' +
+    testUrl +
+    '";' +
+    testHead +
+    debuggerHead +
+    testScript +
+    "} catch (e) {" +
+    "  dump('Exception: '+ e + ' at ' + e.fileName + ':' + " +
+    "       e.lineNumber + '\\nStack: ' + e.stack + '\\n');" +
+    "}";
+  env.set("MOZ_TOOLBOX_TEST_SCRIPT", source);
+  registerCleanupFunction(() => {
+    env.set("MOZ_TOOLBOX_TEST_SCRIPT", "");
   });
 
-  await ToolboxTask.spawn(`"${testUrl}"`, async _testUrl => {
-    /* global createDebuggerContext, waitForSources,
-          waitForPaused, addBreakpoint, assertPausedLocation, stepIn,
-          findSource, removeBreakpoint, resume, selectSource */
-    const { Services } = ChromeUtils.import(
-      "resource://gre/modules/Services.jsm"
-    );
-
-    Services.prefs.clearUserPref("devtools.debugger.tabs");
-    Services.prefs.clearUserPref("devtools.debugger.pending-selected-location");
-
-    info("Waiting for debugger load");
-    /* global gToolbox */
-    await gToolbox.selectTool("jsdebugger");
-    const dbg = createDebuggerContext(gToolbox);
-    const window = dbg.win;
-    const document = window.document;
-
-    await waitForSources(dbg, _testUrl);
-
-    info("Loaded, selecting the test script to debug");
-    // First expand the domain
-    const domain = [...document.querySelectorAll(".tree-node")].find(node => {
-      return node.querySelector(".label").textContent.trim() == "mozilla.org";
+  const { BrowserToolboxProcess } = ChromeUtils.import(
+    "resource://devtools/client/framework/ToolboxProcess.jsm"
+  );
+  // Use two promises, one for each BrowserToolboxProcess.init callback
+  // arguments, to ensure that we wait for toolbox run and close events.
+  let closePromise;
+  await new Promise(onRun => {
+    closePromise = new Promise(onClose => {
+      info("Opening the browser toolbox\n");
+      BrowserToolboxProcess.init(onClose, onRun);
     });
-    const arrow = domain.querySelector(".arrow");
-    arrow.click();
+  });
+  ok(true, "Browser toolbox started\n");
 
-    const fileName = _testUrl.match(/browser-toolbox-test.*\.js/)[0];
-
-    let script = [...document.querySelectorAll(".tree-node")].find(node => {
-      return node.textContent.includes(fileName);
-    });
-    script = script.querySelector(".node");
-    script.click();
-
-    const onPaused = waitForPaused(dbg);
-    await selectSource(dbg, fileName);
-    await addBreakpoint(dbg, fileName, 2);
-
-    await onPaused;
-
-    assertPausedLocation(dbg, fileName, 2);
-
-    await stepIn(dbg);
-
-    assertPausedLocation(dbg, fileName, 3);
-
-    // Remove the breakpoint before resuming in order to prevent hitting the breakpoint
-    // again during test closing.
-    const source = findSource(dbg, fileName);
-    await removeBreakpoint(dbg, source.id, 2);
-
-    await resume(dbg);
-  });
+  await closePromise;
+  ok(true, "Browser toolbox process just closed");
 
   clearInterval(interval);
-
-  await ToolboxTask.destroy();
 });
--- a/devtools/client/framework/test/browser_browser_toolbox_fission_inspector.js
+++ b/devtools/client/framework/test/browser_browser_toolbox_fission_inspector.js
@@ -1,79 +1,126 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
-/* import-globals-from helpers.js */
-Services.scriptloader.loadSubScript(
-  "chrome://mochitests/content/browser/devtools/client/framework/test/helpers.js",
-  this
-);
-
 // There are shutdown issues for which multiple rejections are left uncaught.
 // See bug 1018184 for resolving these issues.
 const { PromiseTestUtils } = ChromeUtils.import(
   "resource://testing-common/PromiseTestUtils.jsm"
 );
 PromiseTestUtils.whitelistRejectionsGlobally(/File closed/);
 
 // On debug test slave, it takes about 50s to run the test.
 requestLongerTimeout(4);
 
 // This test is used to test fission-like features via the Browser Toolbox:
 // - computed view is correct when selecting an element in a remote frame
 
 add_task(async function() {
-  const ToolboxTask = await initBrowserToolboxTask({
-    enableBrowserToolboxFission: true,
-  });
-  await ToolboxTask.importFunctions({});
+  await setupPreferencesForBrowserToolbox();
 
   const tab = await addTab(
     `data:text/html,<div id="my-div" style="color: red">Foo</div>`
   );
 
   // Set a custom attribute on the tab's browser, in order to easily select it in the markup view
   tab.linkedBrowser.setAttribute("test-tab", "true");
 
-  const color = await ToolboxTask.spawn(null, async () => {
-    /* global gToolbox */
-    const inspector = await gToolbox.selectTool("inspector");
-    const onSidebarSelect = inspector.sidebar.once("select");
-    inspector.sidebar.select("computedview");
-    await onSidebarSelect;
+  // Be careful, this JS function is going to be executed in the browser toolbox,
+  // which lives in another process. So do not try to use any scope variable!
+  const env = Cc["@mozilla.org/process/environment;1"].getService(
+    Ci.nsIEnvironment
+  );
+  /* global toolbox */
+  const testScript = function() {
+    // Force the fission pref in order to be able to pierce through the remote browser element
+    // Set the pref from the toolbox process as previous test may already have created
+    // the browser toolbox profile folder. Then setting the pref in Firefox process
+    // won't be taken into account for the browser toolbox.
+    const { Services } = ChromeUtils.import(
+      "resource://gre/modules/Services.jsm"
+    );
+    Services.prefs.setBoolPref("devtools.browsertoolbox.fission", true);
+
+    toolbox
+      .selectTool("inspector")
+      .then(async inspector => {
+        const onSidebarSelect = inspector.sidebar.once("select");
+        inspector.sidebar.select("computedview");
+        await onSidebarSelect;
 
-    async function select(walker, selector) {
-      const nodeFront = await walker.querySelector(walker.rootNode, selector);
-      const updated = inspector.once("inspector-updated");
-      inspector.selection.setNodeFront(nodeFront);
-      await updated;
-      return nodeFront;
-    }
-    const browser = await select(
-      inspector.walker,
-      'browser[remote="true"][test-tab]'
-    );
-    const browserTarget = await browser.connectToRemoteFrame();
-    const walker = (await browserTarget.getFront("inspector")).walker;
-    await select(walker, "#my-div");
+        async function select(walker, selector) {
+          const nodeFront = await walker.querySelector(
+            walker.rootNode,
+            selector
+          );
+          const updated = inspector.once("inspector-updated");
+          inspector.selection.setNodeFront(nodeFront);
+          await updated;
+          return nodeFront;
+        }
+        const browser = await select(
+          inspector.walker,
+          'browser[remote="true"][test-tab]'
+        );
+        const browserTarget = await browser.connectToRemoteFrame();
+        const walker = (await browserTarget.getFront("inspector")).walker;
+        await select(walker, "#my-div");
 
-    const view = inspector.getPanel("computedview").computedView;
-    function getProperty(name) {
-      const propertyViews = view.propertyViews;
-      for (const propView of propertyViews) {
-        if (propView.name == name) {
-          return propView;
+        const view = inspector.getPanel("computedview").computedView;
+        function getProperty(name) {
+          const propertyViews = view.propertyViews;
+          for (const propView of propertyViews) {
+            if (propView.name == name) {
+              return propView;
+            }
+          }
+          return null;
         }
-      }
-      return null;
-    }
-    const prop = getProperty("color");
-    return prop.valueNode.textContent;
+        const prop = getProperty("color");
+        const color = prop.valueNode.textContent;
+        if (color != "rgb(255, 0, 0)") {
+          throw new Error(
+            "The color property of the <div> within a tab isn't red, got: " +
+              color
+          );
+        }
+
+        Services.prefs.setBoolPref("devtools.browsertoolbox.fission", false);
+      })
+      .then(() => toolbox.destroy());
+  };
+  env.set("MOZ_TOOLBOX_TEST_SCRIPT", "new " + testScript);
+  registerCleanupFunction(() => {
+    env.set("MOZ_TOOLBOX_TEST_SCRIPT", "");
   });
 
+  const { BrowserToolboxProcess } = ChromeUtils.import(
+    "resource://devtools/client/framework/ToolboxProcess.jsm"
+  );
   is(
-    color,
-    "rgb(255, 0, 0)",
-    "The color property of the <div> within a tab isn't red"
+    BrowserToolboxProcess.getBrowserToolboxSessionState(),
+    false,
+    "No session state initially"
   );
 
-  await ToolboxTask.destroy();
+  let closePromise;
+  await new Promise(onRun => {
+    closePromise = new Promise(onClose => {
+      info("Opening the browser toolbox\n");
+      BrowserToolboxProcess.init(onClose, onRun);
+    });
+  });
+  ok(true, "Browser toolbox started\n");
+  is(
+    BrowserToolboxProcess.getBrowserToolboxSessionState(),
+    true,
+    "Has session state"
+  );
+
+  await closePromise;
+  ok(true, "Browser toolbox process just closed");
+  is(
+    BrowserToolboxProcess.getBrowserToolboxSessionState(),
+    false,
+    "No session state after closing"
+  );
 });
--- a/devtools/client/framework/test/browser_browser_toolbox_rtl.js
+++ b/devtools/client/framework/test/browser_browser_toolbox_rtl.js
@@ -1,35 +1,94 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
-/* import-globals-from helpers.js */
-Services.scriptloader.loadSubScript(
-  "chrome://mochitests/content/browser/devtools/client/framework/test/helpers.js",
-  this
-);
-
 // There are shutdown issues for which multiple rejections are left uncaught.
 // See bug 1018184 for resolving these issues.
 const { PromiseTestUtils } = ChromeUtils.import(
   "resource://testing-common/PromiseTestUtils.jsm"
 );
 PromiseTestUtils.whitelistRejectionsGlobally(/File closed/);
 
 // On debug test slave, it takes about 50s to run the test.
 requestLongerTimeout(4);
 
 // Test that DevTools panels are rendered in "rtl" (right-to-left) in the Browser Toolbox.
 add_task(async function() {
-  await pushPref("intl.uidirection", 1);
+  await setupPreferencesForBrowserToolbox();
+
+  // Wait for a notification sent by a script evaluated in the webconsole
+  // of the browser toolbox.
+  const onCustomMessage = new Promise(resolve => {
+    Services.obs.addObserver(function listener(target, aTop, data) {
+      Services.obs.removeObserver(listener, "browser-toolbox-inspector-dir");
+      resolve(data);
+    }, "browser-toolbox-inspector-dir");
+  });
 
-  const ToolboxTask = await initBrowserToolboxTask();
-  await ToolboxTask.importFunctions({});
+  const env = Cc["@mozilla.org/process/environment;1"].getService(
+    Ci.nsIEnvironment
+  );
+  env.set(
+    "MOZ_TOOLBOX_TEST_SCRIPT",
+    "new function() {(" + testScript + ")();}"
+  );
+  registerCleanupFunction(() => env.set("MOZ_TOOLBOX_TEST_SCRIPT", ""));
+
+  const { BrowserToolboxProcess } = ChromeUtils.import(
+    "resource://devtools/client/framework/ToolboxProcess.jsm"
+  );
+
+  let closePromise;
+  await new Promise(onRun => {
+    closePromise = new Promise(onClose => {
+      info("Opening the browser toolbox");
+      BrowserToolboxProcess.init(onClose, onRun);
+    });
+  });
+  info("Browser toolbox started");
 
-  const dir = await ToolboxTask.spawn(null, async () => {
-    /* global gToolbox */
-    const inspector = await gToolbox.selectTool("inspector");
-    return inspector.panelDoc.dir;
+  const inspectorPanelDirection = await onCustomMessage;
+  info("Received the custom message");
+  is(
+    inspectorPanelDirection,
+    "rtl",
+    "Inspector panel has the expected direction"
+  );
+
+  await closePromise;
+  info("Browser toolbox process just closed");
+  is(
+    BrowserToolboxProcess.getBrowserToolboxSessionState(),
+    false,
+    "No session state after closing"
+  );
+});
+
+// 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!
+async function testScript() {
+  /* global toolbox */
+  // Get the current direction of the inspector panel.
+  const inspector = await toolbox.selectTool("inspector");
+  const dir = inspector.panelDoc.dir;
+
+  // Switch to the webconsole to send the result to the main test.
+  const webconsole = await toolbox.selectTool("webconsole");
+  const js = `Services.obs.notifyObservers(null, "browser-toolbox-inspector-dir", "${dir}");`;
+
+  const onResult = new Promise(resolve => {
+    const onNewMessages = messages => {
+      for (const message of messages) {
+        if (message.node.classList.contains("result")) {
+          webconsole.hud.ui.off("new-messages", onNewMessages);
+          resolve();
+        }
+      }
+    };
+    webconsole.hud.ui.on("new-messages", onNewMessages);
   });
-  is(dir, "rtl", "Inspector panel has the expected direction");
+  webconsole.hud.ui.wrapper.dispatchEvaluateExpression(js);
+  await onResult;
 
-  await ToolboxTask.destroy();
-});
+  // Destroy the toolbox.
+  await toolbox.destroy();
+}
--- a/devtools/client/framework/test/head.js
+++ b/devtools/client/framework/test/head.js
@@ -435,16 +435,38 @@ async function openAboutToolbox(params) 
 
   return {
     tab,
     document: browser.contentDocument,
   };
 }
 
 /**
+ * Enable temporary preferences useful to run browser toolbox process tests.
+ * Returns a promise that will resolve when the preferences are set.
+ */
+function setupPreferencesForBrowserToolbox() {
+  const options = {
+    set: [
+      ["devtools.debugger.prompt-connection", false],
+      ["devtools.debugger.remote-enabled", true],
+      ["devtools.chrome.enabled", true],
+      // Test-only pref to allow passing `testScript` argument to the browser
+      // toolbox
+      ["devtools.browser-toolbox.allow-unsafe-script", true],
+      // On debug test runner, it takes more than the default time (20s)
+      // to get a initialized console
+      ["devtools.debugger.remote-timeout", 120000],
+    ],
+  };
+
+  return SpecialPowers.pushPrefEnv(options);
+}
+
+/**
  * Load FTL.
  *
  * @param {Toolbox} toolbox
  *        Toolbox instance.
  * @param {String} path
  *        Path to the FTL file.
  */
 function loadFTL(toolbox, path) {
--- a/devtools/client/framework/test/helpers.js
+++ b/devtools/client/framework/test/helpers.js
@@ -4,43 +4,38 @@
 
 "use strict";
 
 const { BrowserToolboxProcess } = ChromeUtils.import(
   "resource://devtools/client/framework/ToolboxProcess.jsm"
 );
 const { DebuggerClient } = require("devtools/shared/client/debugger-client");
 
-/**
- * Open up a browser toolbox and return a ToolboxTask object for interacting
- * with it. ToolboxTask has the following methods:
- *
- * importFunctions(object)
- *
- *   The object contains functions from this process which should be defined in
- *   the global evaluation scope of the toolbox. The toolbox cannot load testing
- *   files directly.
- *
- * spawn(arg, function)
- *
- *   Invoke the given function and argument within the global evaluation scope
- *   of the toolbox. The evaluation scope predefines the name "gToolbox" for the
- *   toolbox itself.
- *
- * destroy()
- *
- *   Destroy the browser toolbox and make sure it exits cleanly.
- *
- * @param {Object}:
- *        - {Boolean} enableBrowserToolboxFission: pass true to enable the OBT.
- */
-async function initBrowserToolboxTask({ enableBrowserToolboxFission } = {}) {
+// Open up a browser toolbox and return a ToolboxTask object for interacting
+// with it. ToolboxTask has the following methods:
+//
+// importFunctions(object)
+//
+//   The object contains functions from this process which should be defined in
+//   the global evaluation scope of the toolbox. The toolbox cannot load testing
+//   files directly.
+//
+// spawn(arg, function)
+//
+//   Invoke the given function and argument within the global evaluation scope
+//   of the toolbox. The evaluation scope predefines the name "gToolbox" for the
+//   toolbox itself.
+//
+// destroy()
+//
+//   Destroy the browser toolbox and make sure it exits cleanly.
+async function initBrowserToolboxTask() {
   await pushPref("devtools.chrome.enabled", true);
   await pushPref("devtools.debugger.remote-enabled", true);
-  await pushPref("devtools.browsertoolbox.enable-test-server", true);
+  await pushPref("devtools.browser-toolbox.allow-unsafe-script", true);
   await pushPref("devtools.debugger.prompt-connection", false);
 
   // This rejection seems to affect all tests using the browser toolbox.
   ChromeUtils.import(
     "resource://testing-common/PromiseTestUtils.jsm"
   ).PromiseTestUtils.whitelistRejectionsGlobally(/File closed/);
 
   const process = await new Promise(onRun => {
@@ -71,48 +66,39 @@ async function initBrowserToolboxTask({ 
   ok(true, "Got transport");
 
   const client = new DebuggerClient(transport);
   await client.connect();
 
   ok(true, "Connected");
 
   const target = await client.mainRoot.getMainProcess();
-  const consoleFront = await target.getFront("console");
-  const preferenceFront = await client.mainRoot.getFront("preference");
-
-  if (enableBrowserToolboxFission) {
-    await preferenceFront.setBoolPref("devtools.browsertoolbox.fission", true);
-  }
+  const console = await target.getFront("console");
 
   async function spawn(arg, fn) {
-    const rv = await consoleFront.evaluateJSAsync(`(${fn})(${arg})`, {
+    const rv = await console.evaluateJSAsync(`(${fn})(${arg})`, {
       mapped: { await: true },
     });
     if (rv.exception) {
       throw new Error(`ToolboxTask.spawn failure: ${rv.exception.message}`);
     } else if (rv.topLevelAwaitRejected) {
       throw new Error(`ToolboxTask.spawn await rejected`);
     }
     return rv.result;
   }
 
   async function importFunctions(functions) {
     for (const [key, fn] of Object.entries(functions)) {
-      await consoleFront.evaluateJSAsync(`this.${key} = ${fn}`);
+      await console.evaluateJSAsync(`this.${key} = ${fn}`);
     }
   }
 
-  async function importScript(script) {
-    await consoleFront.evaluateJSAsync(script);
-  }
-
   async function destroy() {
     const closePromise = process._dbgProcess.wait();
-    consoleFront.evaluateJSAsync("gToolbox.destroy()");
+    console.evaluateJSAsync("gToolbox.destroy()");
 
     const { exitCode } = await closePromise;
     ok(true, "Browser toolbox process closed");
 
     is(exitCode, 0, "The remote debugger process died cleanly");
 
     is(
       BrowserToolboxProcess.getBrowserToolboxSessionState(),
@@ -120,13 +106,12 @@ async function initBrowserToolboxTask({ 
       "No session state after closing"
     );
 
     await client.close();
   }
 
   return {
     importFunctions,
-    importScript,
     spawn,
     destroy,
   };
 }
new file mode 100644
--- /dev/null
+++ b/devtools/client/framework/test/test_browser_toolbox_debugger.js
@@ -0,0 +1,56 @@
+/* global toolbox, createDebuggerContext, waitForSources, testUrl,
+          waitForPaused, addBreakpoint, assertPausedLocation, stepIn,
+          findSource, removeBreakpoint, resume, selectSource */
+
+info(`START: ${new Error().lineNumber}`);
+
+(async function() {
+  Services.prefs.clearUserPref("devtools.debugger.tabs");
+  Services.prefs.clearUserPref("devtools.debugger.pending-selected-location");
+
+  info("Waiting for debugger load");
+  await toolbox.selectTool("jsdebugger");
+  const dbg = createDebuggerContext(toolbox);
+  const window = dbg.win;
+  const document = window.document;
+
+  await waitForSources(dbg, testUrl);
+
+  info("Loaded, selecting the test script to debug");
+  // First expand the domain
+  const domain = [...document.querySelectorAll(".tree-node")].find(node => {
+    return node.querySelector(".label").textContent.trim() == "mozilla.org";
+  });
+  const arrow = domain.querySelector(".arrow");
+  arrow.click();
+
+  const fileName = testUrl.match(/browser-toolbox-test.*\.js/)[0];
+
+  let script = [...document.querySelectorAll(".tree-node")].find(node => {
+    return node.textContent.includes(fileName);
+  });
+  script = script.querySelector(".node");
+  script.click();
+
+  const onPaused = waitForPaused(dbg);
+  await selectSource(dbg, fileName);
+  await addBreakpoint(dbg, fileName, 2);
+
+  await onPaused;
+
+  assertPausedLocation(dbg, fileName, 2);
+
+  await stepIn(dbg);
+
+  assertPausedLocation(dbg, fileName, 3);
+
+  // Remove the breakpoint before resuming in order to prevent hitting the breakpoint
+  // again during test closing.
+  const source = findSource(dbg, fileName);
+  await removeBreakpoint(dbg, source.id, 2);
+
+  await resume(dbg);
+
+  info("Close the browser toolbox");
+  toolbox.destroy();
+})();
--- a/devtools/client/framework/toolbox-process-window.js
+++ b/devtools/client/framework/toolbox-process-window.js
@@ -201,28 +201,45 @@ async function openToolbox(target) {
   await onNewToolbox(toolbox);
 }
 
 async function onNewToolbox(toolbox) {
   gToolbox = toolbox;
   bindToolboxHandlers();
   raise();
 
-  // Enable some testing features if the browser toolbox test pref is set.
+  // Enable some testing features if a testing-only pref is set.
+  const prefName = "devtools.browser-toolbox.allow-unsafe-script";
   if (
-    Services.prefs.getBoolPref(
-      "devtools.browsertoolbox.enable-test-server",
-      false
-    )
+    Services.prefs.getPrefType(prefName) == Services.prefs.PREF_BOOL &&
+    Services.prefs.getBoolPref(prefName) === true
   ) {
-    // setup a server so that the test can evaluate messages in this process.
-    installTestingServer(toolbox);
+    // Execute any test script provided by an environment variable.
+    const env = Cc["@mozilla.org/process/environment;1"].getService(
+      Ci.nsIEnvironment
+    );
+    const testScript = env.get("MOZ_TOOLBOX_TEST_SCRIPT");
+    if (testScript) {
+      evaluateTestScript(testScript, toolbox);
+    } else {
+      // If there is no testing script, setup a server so that the test can
+      // evaluate messages in this process.
+      installTestingServer(toolbox);
+    }
   }
 }
 
+function evaluateTestScript(script, toolbox) {
+  const sandbox = Cu.Sandbox(window);
+  sandbox.window = window;
+  sandbox.toolbox = toolbox;
+  sandbox.ChromeUtils = ChromeUtils;
+  Cu.evalInSandbox(script, sandbox);
+}
+
 function installTestingServer(toolbox) {
   // Install a DebuggerServer in this process and inform the server of its
   // location. Tests operating on the browser toolbox run in the server
   // (the firefox parent process) and can connect to this new server using
   // initBrowserToolboxTask(), allowing them to evaluate scripts here.
 
   const testLoader = new DevToolsLoader({
     invisibleToDebugger: true,