Bug 1436187 - Centralize frame-script-utils loading via shared-head. r=bgrins
☠☠ backed out by 670b7ca7a72f ☠ ☠
authorJ. Ryan Stinnett <jryans@gmail.com>
Mon, 05 Mar 2018 16:31:49 -0600
changeset 462010 b2d808aa8c2efed8d962b1359624b6bc9eb9c45b
parent 462009 e747480f3cd255841195cfe28be4957d1e0f5567
child 462011 9875771c9967e68acf3695ee0796cfdfbe577041
push id1683
push usersfraser@mozilla.com
push dateThu, 26 Apr 2018 16:43:40 +0000
treeherdermozilla-release@5af6cb21869d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbgrins
bugs1436187
milestone60.0a1
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
Bug 1436187 - Centralize frame-script-utils loading via shared-head. r=bgrins Use `loadFrameScriptUtils` from shared-head as a central utility for loading the frame script utils helper. This means less stray references to the utils file's path across our tests. As part of this, I went ahead and converted Canvas Debugger, Shader Editor, and Web Audio Editor to shared-head, as that seemed like the best path to reduce duplication. (I left one extra path reference in profiler-mm-utils.js as-is, since it's a module, so shared-head is not easily usable there.) MozReview-Commit-ID: AKbZt8Jo0GM
devtools/client/animationinspector/test/browser_animation_refresh_when_active_after_mutations.js
devtools/client/animationinspector/test/head.js
devtools/client/canvasdebugger/test/browser.ini
devtools/client/canvasdebugger/test/browser_canvas-actor-test-10.js
devtools/client/canvasdebugger/test/browser_canvas-actor-test-12.js
devtools/client/canvasdebugger/test/browser_canvas-frontend-record-04.js
devtools/client/canvasdebugger/test/head.js
devtools/client/debugger/test/mochitest/head.js
devtools/client/dom/test/head.js
devtools/client/framework/test/browser_target_events.js
devtools/client/framework/test/browser_toolbox_options_enable_serviceworkers_testing.js
devtools/client/framework/test/browser_toolbox_window_reload_target.js
devtools/client/framework/test/head.js
devtools/client/framework/test/helper_disable_cache.js
devtools/client/inspector/animation/test/head.js
devtools/client/jsonview/test/head.js
devtools/client/netmonitor/test/browser_net_filter-01.js
devtools/client/netmonitor/test/browser_net_filter-02.js
devtools/client/netmonitor/test/browser_net_filter-03.js
devtools/client/netmonitor/test/browser_net_filter-04.js
devtools/client/netmonitor/test/browser_net_filter-autocomplete.js
devtools/client/netmonitor/test/browser_net_filter-flags.js
devtools/client/netmonitor/test/browser_net_sort-01.js
devtools/client/netmonitor/test/browser_net_sort-02.js
devtools/client/netmonitor/test/head.js
devtools/client/shadereditor/test/browser.ini
devtools/client/shadereditor/test/head.js
devtools/client/shared/test/shared-head.js
devtools/client/shared/webgl-utils.js
devtools/client/webaudioeditor/test/browser.ini
devtools/client/webaudioeditor/test/browser_audionode-actor-get-params-01.js
devtools/client/webaudioeditor/test/browser_audionode-actor-get-params-02.js
devtools/client/webaudioeditor/test/browser_callwatcher-02.js
devtools/client/webaudioeditor/test/browser_wa_properties-view-media-nodes.js
devtools/client/webaudioeditor/test/browser_wa_properties-view-params.js
devtools/client/webaudioeditor/test/head.js
devtools/client/webconsole/net/test/mochitest/head.js
--- a/devtools/client/animationinspector/test/browser_animation_refresh_when_active_after_mutations.js
+++ b/devtools/client/animationinspector/test/browser_animation_refresh_when_active_after_mutations.js
@@ -24,18 +24,17 @@ add_task(function* () {
 
   // Count players-updated event in controller.
   let updatedEventCount = 0;
   controller.on("players-updated", () => {
     updatedEventCount += 1;
   });
 
   info("Make animation by eval in content");
-  yield evalInDebuggee(gBrowser.selectedBrowser.messageManager,
-                       `document.querySelector('#target').animate(
+  yield evalInDebuggee(`document.querySelector('#target').animate(
                         { transform: 'translate(100px)' },
                         { duration: 100000, easing: 'steps(2)' });`);
   info("Wait for animation mutations event");
   yield controller.animationsFront.once("mutations");
   info("Check players-updated events count");
   is(updatedEventCount, 0, "players-updated event shoud not be fired");
 
   info("Re-select animation inspector and check the UI");
--- a/devtools/client/animationinspector/test/head.js
+++ b/devtools/client/animationinspector/test/head.js
@@ -7,17 +7,16 @@
 
 /* import-globals-from ../../inspector/test/head.js */
 // Import the inspector's head.js first (which itself imports shared-head.js).
 Services.scriptloader.loadSubScript(
   "chrome://mochitests/content/browser/devtools/client/inspector/test/head.js",
   this);
 
 const FRAME_SCRIPT_URL = CHROME_URL_ROOT + "doc_frame_script.js";
-const COMMON_FRAME_SCRIPT_URL = "chrome://devtools/content/shared/frame-script-utils.js";
 const TAB_NAME = "animationinspector";
 const ANIMATION_L10N =
   new LocalizationHelper("devtools/client/locales/animationinspector.properties");
 
 // Auto clean-up when a test ends
 registerCleanupFunction(function* () {
   yield closeAnimationInspector();
 
@@ -51,18 +50,17 @@ function enableAnimationFeatures() {
  * @return a promise that resolves to the tab object when the url is loaded
  */
 var _addTab = addTab;
 addTab = function (url) {
   return enableAnimationFeatures().then(() => _addTab(url)).then(tab => {
     let browser = tab.linkedBrowser;
     info("Loading the helper frame script " + FRAME_SCRIPT_URL);
     browser.messageManager.loadFrameScript(FRAME_SCRIPT_URL, false);
-    info("Loading the helper frame script " + COMMON_FRAME_SCRIPT_URL);
-    browser.messageManager.loadFrameScript(COMMON_FRAME_SCRIPT_URL, false);
+    loadFrameScriptUtils(browser);
     return tab;
   });
 };
 
 /**
  * Reload the current tab location.
  * @param {InspectorPanel} inspector The instance of InspectorPanel currently
  * loaded in the toolbox
--- a/devtools/client/canvasdebugger/test/browser.ini
+++ b/devtools/client/canvasdebugger/test/browser.ini
@@ -10,16 +10,17 @@ support-files =
   doc_simple-canvas-bitmasks.html
   doc_simple-canvas-deep-stack.html
   doc_simple-canvas-transparent.html
   doc_webgl-bindings.html
   doc_webgl-enum.html
   doc_webgl-drawArrays.html
   doc_webgl-drawElements.html
   head.js
+  !/devtools/client/shared/test/shared-head.js
 
 [browser_canvas-actor-test-01.js]
 [browser_canvas-actor-test-02.js]
 [browser_canvas-actor-test-03.js]
 [browser_canvas-actor-test-04.js]
 [browser_canvas-actor-test-05.js]
 [browser_canvas-actor-test-06.js]
 [browser_canvas-actor-test-07.js]
--- a/devtools/client/canvasdebugger/test/browser_canvas-actor-test-10.js
+++ b/devtools/client/canvasdebugger/test/browser_canvas-actor-test-10.js
@@ -3,17 +3,17 @@
 
 /**
  * Tests that the correct framebuffer, renderbuffer and textures are re-bound
  * after generating screenshots using the actor.
  */
 
 function* ifTestingSupported() {
   let { target, front } = yield initCanvasDebuggerBackend(WEBGL_BINDINGS_URL);
-  loadFrameScripts();
+  loadFrameScriptUtils();
 
   let navigated = once(target, "navigate");
 
   yield front.setup({ reload: true });
   ok(true, "The front was setup up successfully.");
 
   yield navigated;
   ok(true, "Target automatically navigated when the front was set up.");
--- a/devtools/client/canvasdebugger/test/browser_canvas-actor-test-12.js
+++ b/devtools/client/canvasdebugger/test/browser_canvas-actor-test-12.js
@@ -3,17 +3,17 @@
 
 /**
  * Tests that the recording can be disabled via stopRecordingAnimationFrame
  * in the event no rAF loop is found.
  */
 
 function* ifTestingSupported() {
   let { target, front } = yield initCanvasDebuggerBackend(NO_CANVAS_URL);
-  loadFrameScripts();
+  loadFrameScriptUtils();
 
   let navigated = once(target, "navigate");
 
   yield front.setup({ reload: true });
   ok(true, "The front was setup up successfully.");
 
   yield navigated;
   ok(true, "Target automatically navigated when the front was set up.");
--- a/devtools/client/canvasdebugger/test/browser_canvas-frontend-record-04.js
+++ b/devtools/client/canvasdebugger/test/browser_canvas-frontend-record-04.js
@@ -6,17 +6,17 @@
  * Tests that the canvas actor correctly returns from recordAnimationFrame
  * in the scenario where a loop starts with rAF and has rAF in the beginning
  * of its loop, when the recording starts before the rAFs start.
  */
 
 function* ifTestingSupported() {
   let { target, panel } = yield initCanvasDebuggerFrontend(RAF_BEGIN_URL);
   let { window, EVENTS, gFront, SnapshotsListView } = panel.panelWin;
-  loadFrameScripts();
+  loadFrameScriptUtils();
 
   yield reload(target);
 
   let recordingFinished = once(window, EVENTS.SNAPSHOT_RECORDING_FINISHED);
   SnapshotsListView._onRecordButtonClick();
 
   // Wait until after the recording started to trigger the content.
   // Use the gFront method rather than the SNAPSHOT_RECORDING_STARTED event
--- a/devtools/client/canvasdebugger/test/head.js
+++ b/devtools/client/canvasdebugger/test/head.js
@@ -1,31 +1,30 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* eslint no-unused-vars: [2, {"vars": "local"}] */
+/* import-globals-from ../../shared/test/shared-head.js */
+
 "use strict";
 
+// Load the shared-head file first.
+Services.scriptloader.loadSubScript(
+  "chrome://mochitests/content/browser/devtools/client/shared/test/shared-head.js",
+  this);
+
 var { generateUUID } = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator);
-var { require } = ChromeUtils.import("resource://devtools/shared/Loader.jsm", {});
 
-var Services = require("Services");
-var promise = require("promise");
-const defer = require("devtools/shared/defer");
-var { gDevTools } = require("devtools/client/framework/devtools");
 var { DebuggerClient } = require("devtools/shared/client/debugger-client");
 var { DebuggerServer } = require("devtools/server/main");
 var { CallWatcherFront } = require("devtools/shared/fronts/call-watcher");
 var { CanvasFront } = require("devtools/shared/fronts/canvas");
-var DevToolsUtils = require("devtools/shared/DevToolsUtils");
-var flags = require("devtools/shared/flags");
-var { TargetFactory } = require("devtools/client/framework/target");
 var { Toolbox } = require("devtools/client/framework/toolbox");
 var { isWebGLSupported } = require("devtools/client/shared/webgl-utils");
-var mm = null;
 
-const FRAME_SCRIPT_UTILS_URL = "chrome://devtools/content/shared/frame-script-utils.js";
 const EXAMPLE_URL = "http://example.com/browser/devtools/client/canvasdebugger/test/";
 const SET_TIMEOUT_URL = EXAMPLE_URL + "doc_settimeout.html";
 const NO_CANVAS_URL = EXAMPLE_URL + "doc_no-canvas.html";
 const RAF_NO_CANVAS_URL = EXAMPLE_URL + "doc_raf-no-canvas.html";
 const SIMPLE_CANVAS_URL = EXAMPLE_URL + "doc_simple-canvas.html";
 const SIMPLE_BITMASKS_URL = EXAMPLE_URL + "doc_simple-canvas-bitmasks.html";
 const SIMPLE_CANVAS_TRANSPARENT_URL = EXAMPLE_URL + "doc_simple-canvas-transparent.html";
 const SIMPLE_CANVAS_DEEP_STACK_URL = EXAMPLE_URL + "doc_simple-canvas-deep-stack.html";
@@ -35,81 +34,28 @@ const WEBGL_DRAW_ARRAYS = EXAMPLE_URL + 
 const WEBGL_DRAW_ELEMENTS = EXAMPLE_URL + "doc_webgl-drawElements.html";
 const RAF_BEGIN_URL = EXAMPLE_URL + "doc_raf-begin.html";
 
 // Disable logging for all the tests. Both the debugger server and frontend will
 // be affected by this pref.
 var gEnableLogging = Services.prefs.getBoolPref("devtools.debugger.log");
 Services.prefs.setBoolPref("devtools.debugger.log", false);
 
-// All tests are asynchronous.
-waitForExplicitFinish();
-
 var gToolEnabled = Services.prefs.getBoolPref("devtools.canvasdebugger.enabled");
 
-flags.testing = true;
-
 registerCleanupFunction(() => {
-  info("finish() was called, cleaning up...");
-  flags.testing = false;
   Services.prefs.setBoolPref("devtools.debugger.log", gEnableLogging);
   Services.prefs.setBoolPref("devtools.canvasdebugger.enabled", gToolEnabled);
 
   // Some of yhese tests use a lot of memory due to GL contexts, so force a GC
   // to help fragmentation.
   info("Forcing GC after canvas debugger test.");
   Cu.forceGC();
 });
 
-/**
- * Call manually in tests that use frame script utils after initializing
- * the shader editor. Call after init but before navigating to different pages.
- */
-function loadFrameScripts() {
-  mm = gBrowser.selectedBrowser.messageManager;
-  mm.loadFrameScript(FRAME_SCRIPT_UTILS_URL, false);
-}
-
-function addTab(aUrl, aWindow) {
-  info("Adding tab: " + aUrl);
-
-  let deferred = defer();
-  let targetWindow = aWindow || window;
-  let targetBrowser = targetWindow.gBrowser;
-
-  targetWindow.focus();
-  let tab = targetBrowser.selectedTab = targetBrowser.addTab(aUrl);
-  let linkedBrowser = tab.linkedBrowser;
-
-  BrowserTestUtils.browserLoaded(linkedBrowser)
-    .then(function () {
-      info("Tab added and finished loading: " + aUrl);
-      deferred.resolve(tab);
-    });
-
-  return deferred.promise;
-}
-
-function removeTab(aTab, aWindow) {
-  info("Removing tab.");
-
-  let deferred = defer();
-  let targetWindow = aWindow || window;
-  let targetBrowser = targetWindow.gBrowser;
-  let tabContainer = targetBrowser.tabContainer;
-
-  tabContainer.addEventListener("TabClose", function (aEvent) {
-    info("Tab removed and finished closing.");
-    deferred.resolve();
-  }, {once: true});
-
-  targetBrowser.removeTab(aTab);
-  return deferred.promise;
-}
-
 function handleError(aError) {
   ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
   finish();
 }
 
 var gRequiresWebGL = false;
 
 function ifTestingSupported() {
@@ -139,45 +85,16 @@ function isTestingSupported() {
 
   let supported = isWebGLSupported(document);
 
   info("This test requires WebGL support.");
   info("Apparently, WebGL is" + (supported ? "" : " not") + " supported.");
   return supported;
 }
 
-function once(aTarget, aEventName, aUseCapture = false) {
-  info("Waiting for event: '" + aEventName + "' on " + aTarget + ".");
-
-  let deferred = defer();
-
-  for (let [add, remove] of [
-    ["on", "off"], // Use event emitter before DOM events for consistency
-    ["addEventListener", "removeEventListener"],
-    ["addListener", "removeListener"]
-  ]) {
-    if ((add in aTarget) && (remove in aTarget)) {
-      aTarget[add](aEventName, function onEvent(...aArgs) {
-        info("Got event: '" + aEventName + "' on " + aTarget + ".");
-        aTarget[remove](aEventName, onEvent, aUseCapture);
-        deferred.resolve(...aArgs);
-      }, aUseCapture);
-      break;
-    }
-  }
-
-  return deferred.promise;
-}
-
-function waitForTick() {
-  let deferred = defer();
-  executeSoon(deferred.resolve);
-  return deferred.promise;
-}
-
 function navigateInHistory(aTarget, aDirection, aWaitForTargetEvent = "navigate") {
   executeSoon(() => content.history[aDirection]());
   return once(aTarget, aWaitForTargetEvent);
 }
 
 function navigate(aTarget, aUrl, aWaitForTargetEvent = "navigate") {
   executeSoon(() => aTarget.activeTab.navigateTo(aUrl));
   return once(aTarget, aWaitForTargetEvent);
@@ -243,58 +160,12 @@ function teardown({target}) {
   info("Destroying the specified canvas debugger.");
 
   let {tab} = target;
   return gDevTools.closeToolbox(target).then(() => {
     removeTab(tab);
   });
 }
 
-/**
- * Takes a string `script` and evaluates it directly in the content
- * in potentially a different process.
- */
-function evalInDebuggee(script) {
-  let deferred = defer();
-
-  if (!mm) {
-    throw new Error("`loadFrameScripts()` must be called when using MessageManager.");
-  }
-
-  let id = generateUUID().toString();
-  mm.sendAsyncMessage("devtools:test:eval", { script: script, id: id });
-  mm.addMessageListener("devtools:test:eval:response", handler);
-
-  function handler({ data }) {
-    if (id !== data.id) {
-      return;
-    }
-
-    mm.removeMessageListener("devtools:test:eval:response", handler);
-    deferred.resolve(data.value);
-  }
-
-  return deferred.promise;
-}
-
 function getSourceActor(aSources, aURL) {
   let item = aSources.getItemForAttachment(a => a.source.url === aURL);
   return item ? item.value : null;
 }
-
-/**
- * Waits until a predicate returns true.
- *
- * @param function predicate
- *        Invoked once in a while until it returns true.
- * @param number interval [optional]
- *        How often the predicate is invoked, in milliseconds.
- */
-function* waitUntil(predicate, interval = 10) {
-  if (yield predicate()) {
-    return Promise.resolve(true);
-  }
-  let deferred = defer();
-  setTimeout(function () {
-    waitUntil(predicate).then(() => deferred.resolve(true));
-  }, interval);
-  return deferred.promise;
-}
--- a/devtools/client/debugger/test/mochitest/head.js
+++ b/devtools/client/debugger/test/mochitest/head.js
@@ -203,17 +203,20 @@ function attachThreadActorForUrl(aClient
         deferred.resolve(aThreadClient);
       });
     });
   });
 
   return deferred.promise;
 }
 
-function once(aTarget, aEventName, aUseCapture = false) {
+// Override once from shared-head, as some tests depend on trying native DOM listeners
+// before EventEmitter.  Since this directory is deprecated, there's little value in
+// resolving the descrepency here.
+this.once = function (aTarget, aEventName, aUseCapture = false) {
   info("Waiting for event: '" + aEventName + "' on " + aTarget + ".");
 
   let deferred = promise.defer();
 
   for (let [add, remove] of [
     ["addEventListener", "removeEventListener"],
     ["addListener", "removeListener"],
     ["on", "off"]
@@ -223,17 +226,17 @@ function once(aTarget, aEventName, aUseC
         aTarget[remove](aEventName, onEvent, aUseCapture);
         deferred.resolve.apply(deferred, aArgs);
       }, aUseCapture);
       break;
     }
   }
 
   return deferred.promise;
-}
+};
 
 function waitForTick() {
   let deferred = promise.defer();
   executeSoon(deferred.resolve);
   return deferred.promise;
 }
 
 function waitForTime(aDelay) {
--- a/devtools/client/dom/test/head.js
+++ b/devtools/client/dom/test/head.js
@@ -1,19 +1,16 @@
 /* vim: set ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 /* eslint no-unused-vars: [2, {"vars": "local", "args": "none"}] */
 /* import-globals-from ../../shared/test/shared-head.js */
 
 "use strict";
 
-const FRAME_SCRIPT_UTILS_URL =
-  "chrome://devtools/content/shared/frame-script-utils.js";
-
 // shared-head.js handles imports, constants, and utility functions
 Services.scriptloader.loadSubScript(
   "chrome://mochitests/content/browser/devtools/client/shared/test/shared-head.js", this);
 
 // DOM panel actions.
 const constants = require("devtools/client/dom/content/constants");
 
 // Uncomment this pref to dump all devtools emitted events to the console.
@@ -36,17 +33,17 @@ registerCleanupFunction(() => {
  *        the url is loaded
  */
 function addTestTab(url) {
   info("Adding a new test tab with URL: '" + url + "'");
 
   return new Promise(resolve => {
     addTab(url).then(tab => {
       // Load devtools/shared/frame-script-utils.js
-      getFrameScript();
+      loadFrameScriptUtils();
 
       // Select the DOM panel and wait till it's initialized.
       initDOMPanel(tab).then(panel => {
         waitForDispatch(panel, "FETCH_PROPERTIES").then(() => {
           resolve({
             tab: tab,
             browser: tab.linkedBrowser,
             panel: panel
--- a/devtools/client/framework/test/browser_target_events.js
+++ b/devtools/client/framework/test/browser_target_events.js
@@ -26,17 +26,17 @@ function onHidden() {
   ok(true, "Hidden event received");
   target.once("visible", onVisible);
   gBrowser.removeCurrentTab();
 }
 
 function onVisible() {
   ok(true, "Visible event received");
   target.once("will-navigate", onWillNavigate);
-  let mm = getFrameScript();
+  let mm = loadFrameScriptUtils();
   mm.sendAsyncMessage("devtools:test:navigate", { location: "data:text/html,<meta charset='utf8'/>test navigation" });
 }
 
 function onWillNavigate(event, request) {
   ok(true, "will-navigate event received");
   // Wait for navigation handling to complete before removing the tab, in order
   // to avoid triggering assertions.
   target.once("navigate", executeSoon.bind(null, onNavigate));
--- a/devtools/client/framework/test/browser_toolbox_options_enable_serviceworkers_testing.js
+++ b/devtools/client/framework/test/browser_toolbox_options_enable_serviceworkers_testing.js
@@ -1,18 +1,16 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // Test that enabling Service Workers testing option enables the
 // mServiceWorkersTestingEnabled attribute added to nsPIDOMWindow.
 
-const COMMON_FRAME_SCRIPT_URL =
-  "chrome://devtools/content/shared/frame-script-utils.js";
 const ROOT_TEST_DIR =
   getRootDirectory(gTestPath);
 const FRAME_SCRIPT_URL =
   ROOT_TEST_DIR +
   "browser_toolbox_options_enable_serviceworkers_testing_frame_script.js";
 const TEST_URI = URL_ROOT +
                  "browser_toolbox_options_enable_serviceworkers_testing.html";
 
@@ -30,17 +28,17 @@ function test() {
   ]}, init);
 }
 
 function init() {
   addTab(TEST_URI).then(tab => {
     let target = TargetFactory.forTab(tab);
     let linkedBrowser = tab.linkedBrowser;
 
-    linkedBrowser.messageManager.loadFrameScript(COMMON_FRAME_SCRIPT_URL, false);
+    loadFrameScriptUtils(linkedBrowser);
     linkedBrowser.messageManager.loadFrameScript(FRAME_SCRIPT_URL, false);
 
     gDevTools.showToolbox(target).then(testSelectTool);
   });
 }
 
 function testSelectTool(aToolbox) {
   toolbox = aToolbox;
--- a/devtools/client/framework/test/browser_toolbox_window_reload_target.js
+++ b/devtools/client/framework/test/browser_toolbox_window_reload_target.js
@@ -26,17 +26,17 @@ function test() {
                   .map(def => def.id);
       gDevTools.showToolbox(target, toolIDs[0], Toolbox.HostType.BOTTOM)
                .then(startReloadTest);
     });
   });
 }
 
 function startReloadTest(aToolbox) {
-  getFrameScript(); // causes frame-script-utils to be loaded into the child.
+  loadFrameScriptUtils(); // causes frame-script-utils to be loaded into the child.
   toolbox = aToolbox;
 
   reloadsSent = 0;
   let reloads = 0;
   let reloadCounter = (msg) => {
     reloads++;
     info("Detected reload #" + reloads);
     is(reloads, reloadsSent, "Reloaded from devtools window once and only for " + description + "");
--- a/devtools/client/framework/test/head.js
+++ b/devtools/client/framework/test/head.js
@@ -151,24 +151,26 @@ function checkHostType(toolbox, hostType
  * Create a new <script> referencing URL.  Return a promise that
  * resolves when this has happened
  * @param {String} url
  *        the url
  * @return {Promise} a promise that resolves when the element has been created
  */
 function createScript(url) {
   info(`Creating script: ${url}`);
-  let mm = getFrameScript();
+  // This is not ideal if called multiple times, as it loads the frame script
+  // separately each time.  See bug 1443680.
+  loadFrameScriptUtils();
   let command = `
     let script = document.createElement("script");
     script.setAttribute("src", "${url}");
     document.body.appendChild(script);
     null;
   `;
-  return evalInDebuggee(mm, command);
+  return evalInDebuggee(command);
 }
 
 /**
  * Wait for the toolbox to notice that a given source is loaded
  * @param {Toolbox} toolbox
  * @param {String} url
  *        the url to wait for
  * @return {Promise} a promise that is resolved when the source is loaded
--- a/devtools/client/framework/test/helper_disable_cache.js
+++ b/devtools/client/framework/test/helper_disable_cache.js
@@ -92,17 +92,17 @@ function reloadTab(tabX) {
   let browser = gBrowser.selectedBrowser;
 
   BrowserTestUtils.browserLoaded(browser).then(function () {
     info("Reloaded tab " + tabX.title);
     def.resolve();
   });
 
   info("Reloading tab " + tabX.title);
-  let mm = getFrameScript();
+  let mm = loadFrameScriptUtils();
   mm.sendAsyncMessage("devtools:test:reload");
 
   return def.promise;
 }
 
 function* destroyTab(tabX) {
   let toolbox = gDevTools.getToolbox(tabX.target);
 
--- a/devtools/client/inspector/animation/test/head.js
+++ b/devtools/client/inspector/animation/test/head.js
@@ -4,17 +4,16 @@
 
 "use strict";
 
 /* import-globals-from ../../test/head.js */
 // Import the inspector's head.js first (which itself imports shared-head.js).
 Services.scriptloader.loadSubScript(
   "chrome://mochitests/content/browser/devtools/client/inspector/test/head.js", this);
 
-const COMMON_FRAME_SCRIPT_URL = "chrome://devtools/content/shared/frame-script-utils.js";
 const FRAME_SCRIPT_URL = CHROME_URL_ROOT + "doc_frame_script.js";
 const TAB_NAME = "newanimationinspector";
 
 const ANIMATION_L10N =
   new LocalizationHelper("devtools/client/locales/animationinspector.properties");
 
 // Enable new animation inspector.
 Services.prefs.setBoolPref("devtools.new-animationinspector.enabled", true);
@@ -76,18 +75,17 @@ const enableAnimationFeatures = function
  */
 const _addTab = addTab;
 addTab = async function (url) {
   await enableAnimationFeatures();
   const tab = await _addTab(url);
   const browser = tab.linkedBrowser;
   info("Loading the helper frame script " + FRAME_SCRIPT_URL);
   browser.messageManager.loadFrameScript(FRAME_SCRIPT_URL, false);
-  info("Loading the helper frame script " + COMMON_FRAME_SCRIPT_URL);
-  browser.messageManager.loadFrameScript(COMMON_FRAME_SCRIPT_URL, false);
+  loadFrameScriptUtils(browser);
   return tab;
 };
 
 /**
  * Click on an animation in the timeline to select it.
  *
  * @param {AnimationInspector} animationInspector.
  * @param {AnimationsPanel} panel
--- a/devtools/client/jsonview/test/head.js
+++ b/devtools/client/jsonview/test/head.js
@@ -61,17 +61,17 @@ async function addJsonViewTab(url, {
         // Fires when the tab is ready but before completely loaded.
         webProgress.removeProgressListener(this);
         resolve();
       },
     }, Ci.nsIWebProgress.NOTIFY_LOCATION);
   })]);
 
   // Load devtools/shared/frame-script-utils.js
-  getFrameScript();
+  loadFrameScriptUtils();
   let rootDir = getRootDirectory(gTestPath);
 
   // Catch RequireJS errors (usually timeouts)
   let error = tabLoaded.then(() => new Promise((resolve, reject) => {
     let {requirejs} = content.wrappedJSObject;
     if (requirejs) {
       requirejs.onError = err => {
         info(err);
--- a/devtools/client/netmonitor/test/browser_net_filter-01.js
+++ b/devtools/client/netmonitor/test/browser_net_filter-01.js
@@ -142,17 +142,17 @@ add_task(function* () {
 
   function setFreetextFilter(value) {
     store.dispatch(Actions.setRequestFilterText(value));
   }
 
   info("Starting test... ");
 
   let wait = waitForNetworkEvents(monitor, 9);
-  loadCommonFrameScript();
+  loadFrameScriptUtils();
   yield performRequestsInContent(REQUESTS_WITH_MEDIA_AND_FLASH_AND_WS);
   yield wait;
 
   EventUtils.sendMouseEvent({ type: "mousedown" },
     document.querySelectorAll(".request-list-item")[0]);
 
   isnot(getSelectedRequest(store.getState()), null,
     "There should be a selected item in the requests menu.");
--- a/devtools/client/netmonitor/test/browser_net_filter-02.js
+++ b/devtools/client/netmonitor/test/browser_net_filter-02.js
@@ -142,17 +142,17 @@ add_task(function* () {
     getDisplayedRequests,
     getSelectedRequest,
     getSortedRequests,
   } = windowRequire("devtools/client/netmonitor/src/selectors/index");
 
   store.dispatch(Actions.batchEnable(false));
 
   let wait = waitForNetworkEvents(monitor, 9);
-  loadCommonFrameScript();
+  loadFrameScriptUtils();
   yield performRequestsInContent(REQUESTS_WITH_MEDIA_AND_FLASH_AND_WS);
   yield wait;
 
   EventUtils.sendMouseEvent({ type: "mousedown" },
     document.querySelectorAll(".request-list-item")[0]);
 
   isnot(getSelectedRequest(store.getState()), null,
     "There should be a selected item in the requests menu.");
--- a/devtools/client/netmonitor/test/browser_net_filter-03.js
+++ b/devtools/client/netmonitor/test/browser_net_filter-03.js
@@ -38,17 +38,17 @@ add_task(function* () {
   store.dispatch(Actions.batchEnable(false));
 
   // The test assumes that the first HTML request here has a longer response
   // body than the other HTML requests performed later during the test.
   let requests = Cu.cloneInto(REQUESTS_WITH_MEDIA, {});
   let newres = "res=<p>" + new Array(10).join(Math.random(10)) + "</p>";
   requests[0].url = requests[0].url.replace("res=undefined", newres);
 
-  loadCommonFrameScript();
+  loadFrameScriptUtils();
 
   let wait = waitForNetworkEvents(monitor, 7);
   yield performRequestsInContent(requests);
   yield wait;
 
   EventUtils.sendMouseEvent({ type: "mousedown" },
     document.querySelectorAll(".request-list-item")[0]);
 
--- a/devtools/client/netmonitor/test/browser_net_filter-04.js
+++ b/devtools/client/netmonitor/test/browser_net_filter-04.js
@@ -43,17 +43,17 @@ add_task(function* () {
   store.dispatch(Actions.batchEnable(false));
 
   is(Prefs.filters.length, 3,
     "All the filter types should be loaded.");
   is(Prefs.filters[0], "bogus",
     "The first filter type is invalid, but loaded anyway.");
 
   let wait = waitForNetworkEvents(monitor, 9);
-  loadCommonFrameScript();
+  loadFrameScriptUtils();
   yield performRequestsInContent(REQUESTS_WITH_MEDIA_AND_FLASH_AND_WS);
   yield wait;
 
   testFilterButtons(monitor, "js");
   ok(true, "Only the correct filter type was taken into consideration.");
 
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector(".requests-list-filter-html-button"));
--- a/devtools/client/netmonitor/test/browser_net_filter-autocomplete.js
+++ b/devtools/client/netmonitor/test/browser_net_filter-autocomplete.js
@@ -41,17 +41,17 @@ add_task(async function () {
 
   store.dispatch(Actions.batchEnable(false));
 
   info("Starting test... ");
 
   // Let the requests load completely before the autocomplete tests begin
   // as autocomplete values also rely on the network requests.
   let waitNetwork = waitForNetworkEvents(monitor, REQUESTS.length);
-  loadCommonFrameScript();
+  loadFrameScriptUtils();
   await performRequestsInContent(REQUESTS);
   await waitNetwork;
 
   EventUtils.synthesizeMouseAtCenter(
     document.querySelector(".devtools-filterinput"), {}, window);
   // Empty Mouse click should keep autocomplete hidden
   ok(!document.querySelector(".devtools-autocomplete-popup"),
     "Autocomplete Popup still hidden");
--- a/devtools/client/netmonitor/test/browser_net_filter-flags.js
+++ b/devtools/client/netmonitor/test/browser_net_filter-flags.js
@@ -161,17 +161,17 @@ add_task(function* () {
     filterBox.focus();
     filterBox.value = "";
     type(value);
   }
 
   info("Starting test... ");
 
   let waitNetwork = waitForNetworkEvents(monitor, REQUESTS.length);
-  loadCommonFrameScript();
+  loadFrameScriptUtils();
   yield performRequestsInContent(REQUESTS);
   yield waitNetwork;
 
   // Test running flag once requests finish running
   setFreetextFilter("is:running");
   yield testContents([0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
 
   // Test cached flag
--- a/devtools/client/netmonitor/test/browser_net_sort-01.js
+++ b/devtools/client/netmonitor/test/browser_net_sort-01.js
@@ -24,17 +24,17 @@ add_task(function* () {
     getSelectedRequest,
     getSortedRequests,
   } = windowRequire("devtools/client/netmonitor/src/selectors/index");
 
   store.dispatch(Actions.batchEnable(false));
 
   // Loading the frame script and preparing the xhr request URLs so we can
   // generate some requests later.
-  loadCommonFrameScript();
+  loadFrameScriptUtils();
   let requests = [{
     url: "sjs_sorting-test-server.sjs?index=1&" + Math.random(),
     method: "GET1"
   }, {
     url: "sjs_sorting-test-server.sjs?index=5&" + Math.random(),
     method: "GET5"
   }, {
     url: "sjs_sorting-test-server.sjs?index=2&" + Math.random(),
--- a/devtools/client/netmonitor/test/browser_net_sort-02.js
+++ b/devtools/client/netmonitor/test/browser_net_sort-02.js
@@ -24,17 +24,17 @@ add_task(function* () {
     getSelectedRequest,
     getSortedRequests,
   } = windowRequire("devtools/client/netmonitor/src/selectors/index");
 
   store.dispatch(Actions.batchEnable(false));
 
   // Loading the frame script and preparing the xhr request URLs so we can
   // generate some requests later.
-  loadCommonFrameScript();
+  loadFrameScriptUtils();
   let requests = [{
     url: "sjs_sorting-test-server.sjs?index=1&" + Math.random(),
     method: "GET1"
   }, {
     url: "sjs_sorting-test-server.sjs?index=5&" + Math.random(),
     method: "GET5"
   }, {
     url: "sjs_sorting-test-server.sjs?index=2&" + Math.random(),
--- a/devtools/client/netmonitor/test/head.js
+++ b/devtools/client/netmonitor/test/head.js
@@ -1,14 +1,14 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /* import-globals-from ../../shared/test/shared-head.js */
 /* exported Toolbox, restartNetMonitor, teardown, waitForExplicitFinish,
-   verifyRequestItemTarget, waitFor, testFilterButtons, loadCommonFrameScript,
+   verifyRequestItemTarget, waitFor, testFilterButtons,
    performRequestsInContent, waitForNetworkEvents, selectIndexAndWaitForSourceEditor,
    testColumnsAlignment, hideColumn, showColumn */
 
 "use strict";
 
 // shared-head.js handles imports, constants, and utility functions
 Services.scriptloader.loadSubScript(
   "chrome://mochitests/content/browser/devtools/client/shared/test/shared-head.js",
@@ -74,17 +74,16 @@ const CORS_SJS_PATH = "/browser/devtools
 const HSTS_SJS = EXAMPLE_URL + "sjs_hsts-test-server.sjs";
 
 const HSTS_BASE_URL = EXAMPLE_URL;
 const HSTS_PAGE_URL = CUSTOM_GET_URL;
 
 const TEST_IMAGE = EXAMPLE_URL + "test-image.png";
 const TEST_IMAGE_DATA_URI = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAHWSURBVHjaYvz//z8DJQAggJiQOe/fv2fv7Oz8rays/N+VkfG/iYnJfyD/1+rVq7ffu3dPFpsBAAHEAHIBCJ85c8bN2Nj4vwsDw/8zQLwKiO8CcRoQu0DxqlWrdsHUwzBAAIGJmTNnPgYa9j8UqhFElwPxf2MIDeIrKSn9FwSJoRkAEEAM0DD4DzMAyPi/G+QKY4hh5WAXGf8PDQ0FGwJ22d27CjADAAIIrLmjo+MXA9R2kAHvGBA2wwx6B8W7od6CeQcggKCmCEL8bgwxYCbUIGTDVkHDBia+CuotgACCueD3TDQN75D4xmAvCoK9ARMHBzAw0AECiBHkAlC0Mdy7x9ABNA3obAZXIAa6iKEcGlMVQHwWyjYuL2d4v2cPg8vZswx7gHyAAAK7AOif7SAbOqCmn4Ha3AHFsIDtgPq/vLz8P4MSkJ2W9h8ggBjevXvHDo4FQUQg/kdypqCg4H8lUIACnQ/SOBMYI8bAsAJFPcj1AAEEjwVQqLpAbXmH5BJjqI0gi9DTAAgDBBCcAVLkgmQ7yKCZxpCQxqUZhAECCJ4XgMl493ug21ZD+aDAXH0WLM4A9MZPXJkJIIAwTAR5pQMalaCABQUULttBGCCAGCnNzgABBgAMJ5THwGvJLAAAAABJRU5ErkJggg==";
 
-const FRAME_SCRIPT_UTILS_URL = "chrome://devtools/content/shared/frame-script-utils.js";
 /* eslint-enable no-unused-vars, max-len */
 
 // All tests are asynchronous.
 waitForExplicitFinish();
 
 const gEnableLogging = Services.prefs.getBoolPref("devtools.debugger.log");
 // To enable logging for try runs, just set the pref to true.
 Services.prefs.setBoolPref("devtools.debugger.log", false);
@@ -613,28 +612,16 @@ function testFilterButtonsCustom(monitor
         "The " + button.id + " button should not have a 'checked' class.");
       is(button.getAttribute("aria-pressed"), "false",
         "The " + button.id + " button should set 'aria-pressed' = false.");
     }
   }
 }
 
 /**
- * Loads shared/frame-script-utils.js in the specified tab.
- *
- * @param tab
- *        Optional tab to load the frame script in. Defaults to the current tab.
- */
-function loadCommonFrameScript(tab) {
-  let browser = tab ? tab.linkedBrowser : gBrowser.selectedBrowser;
-
-  browser.messageManager.loadFrameScript(FRAME_SCRIPT_UTILS_URL, false);
-}
-
-/**
  * Perform the specified requests in the context of the page content.
  *
  * @param Array requests
  *        An array of objects specifying the requests to perform. See
  *        shared/frame-script-utils.js for more information.
  *
  * @return A promise that resolves once the requests complete.
  */
--- a/devtools/client/shadereditor/test/browser.ini
+++ b/devtools/client/shadereditor/test/browser.ini
@@ -3,16 +3,17 @@ tags = devtools
 subsuite = devtools
 support-files =
   doc_blended-geometry.html
   doc_multiple-contexts.html
   doc_overlapping-geometry.html
   doc_shader-order.html
   doc_simple-canvas.html
   head.js
+  !/devtools/client/shared/test/shared-head.js
 
 [browser_se_aaa_run_first_leaktest.js]
 [browser_se_bfcache.js]
 skip-if = true # Bug 942473, caused by Bug 940541
 [browser_se_editors-contents.js]
 [browser_se_editors-error-gutter.js]
 [browser_se_editors-error-tooltip.js]
 [browser_se_editors-lazy-init.js]
--- a/devtools/client/shadereditor/test/head.js
+++ b/devtools/client/shadereditor/test/head.js
@@ -1,51 +1,41 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* eslint no-unused-vars: [2, {"vars": "local"}] */
+/* import-globals-from ../../shared/test/shared-head.js */
+
 "use strict";
 
-var { require } = ChromeUtils.import("resource://devtools/shared/Loader.jsm", {});
-var { Task } = require("devtools/shared/task");
+// Load the shared-head file first.
+Services.scriptloader.loadSubScript(
+  "chrome://mochitests/content/browser/devtools/client/shared/test/shared-head.js",
+  this);
 
-var Services = require("Services");
-var promise = require("promise");
-const defer = require("devtools/shared/defer");
-var { gDevTools } = require("devtools/client/framework/devtools");
 var { DebuggerClient } = require("devtools/shared/client/debugger-client");
 var { DebuggerServer } = require("devtools/server/main");
 var { WebGLFront } = require("devtools/shared/fronts/webgl");
-var DevToolsUtils = require("devtools/shared/DevToolsUtils");
-var flags = require("devtools/shared/flags");
-var { TargetFactory } = require("devtools/client/framework/target");
 var { Toolbox } = require("devtools/client/framework/toolbox");
 var { isWebGLSupported } = require("devtools/client/shared/webgl-utils");
-var mm = null;
 
-const FRAME_SCRIPT_UTILS_URL = "chrome://devtools/content/shared/frame-script-utils.js";
 const EXAMPLE_URL = "http://example.com/browser/devtools/client/shadereditor/test/";
 const SIMPLE_CANVAS_URL = EXAMPLE_URL + "doc_simple-canvas.html";
 const SHADER_ORDER_URL = EXAMPLE_URL + "doc_shader-order.html";
 const MULTIPLE_CONTEXTS_URL = EXAMPLE_URL + "doc_multiple-contexts.html";
 const OVERLAPPING_GEOMETRY_CANVAS_URL = EXAMPLE_URL + "doc_overlapping-geometry.html";
 const BLENDED_GEOMETRY_CANVAS_URL = EXAMPLE_URL + "doc_blended-geometry.html";
 
 var gEnableLogging = Services.prefs.getBoolPref("devtools.debugger.log");
 // To enable logging for try runs, just set the pref to true.
 Services.prefs.setBoolPref("devtools.debugger.log", false);
 
-// All tests are asynchronous.
-waitForExplicitFinish();
-
 var gToolEnabled = Services.prefs.getBoolPref("devtools.shadereditor.enabled");
 
-flags.testing = true;
-
 registerCleanupFunction(() => {
-  info("finish() was called, cleaning up...");
-  flags.testing = false;
   Services.prefs.setBoolPref("devtools.debugger.log", gEnableLogging);
   Services.prefs.setBoolPref("devtools.shadereditor.enabled", gToolEnabled);
 
   // These tests use a lot of memory due to GL contexts, so force a GC to help
   // fragmentation.
   info("Forcing GC after shadereditor test.");
   Cu.forceGC();
 });
@@ -54,57 +44,20 @@ registerCleanupFunction(() => {
  * Call manually in tests that use frame script utils after initializing
  * the shader editor. Must be called after initializing so we can detect
  * whether or not `content` is a CPOW or not. Call after init but before navigating
  * to different pages, as bfcache and thus shader caching gets really strange if
  * frame script attached in the middle of the test.
  */
 function loadFrameScripts() {
   if (Cu.isCrossProcessWrapper(content)) {
-    mm = gBrowser.selectedBrowser.messageManager;
-    mm.loadFrameScript(FRAME_SCRIPT_UTILS_URL, false);
+    loadFrameScriptUtils();
   }
 }
 
-function addTab(aUrl, aWindow) {
-  info("Adding tab: " + aUrl);
-
-  let deferred = defer();
-  let targetWindow = aWindow || window;
-  let targetBrowser = targetWindow.gBrowser;
-
-  targetWindow.focus();
-  let tab = targetBrowser.selectedTab = targetBrowser.addTab(aUrl);
-  let linkedBrowser = tab.linkedBrowser;
-
-  BrowserTestUtils.browserLoaded(linkedBrowser).then(function () {
-    info("Tab added and finished loading: " + aUrl);
-    deferred.resolve(tab);
-  });
-
-  return deferred.promise;
-}
-
-function removeTab(aTab, aWindow) {
-  info("Removing tab.");
-
-  let deferred = defer();
-  let targetWindow = aWindow || window;
-  let targetBrowser = targetWindow.gBrowser;
-  let tabContainer = targetBrowser.tabContainer;
-
-  tabContainer.addEventListener("TabClose", function (aEvent) {
-    info("Tab removed and finished closing.");
-    deferred.resolve();
-  }, {once: true});
-
-  targetBrowser.removeTab(aTab);
-  return deferred.promise;
-}
-
 function handleError(aError) {
   ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
   finish();
 }
 
 function ifWebGLSupported() {
   ok(false, "You need to define a 'ifWebGLSupported' function.");
   finish();
@@ -119,38 +72,16 @@ function test() {
   let generator = isWebGLSupported(document) ? ifWebGLSupported : ifWebGLUnsupported;
   Task.spawn(generator).catch(handleError);
 }
 
 function createCanvas() {
   return document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
 }
 
-function once(aTarget, aEventName, aUseCapture = false) {
-  info("Waiting for event: '" + aEventName + "' on " + aTarget + ".");
-
-  let deferred = defer();
-
-  for (let [add, remove] of [
-    ["on", "off"], // Use event emitter before DOM events for consistency
-    ["addEventListener", "removeEventListener"],
-    ["addListener", "removeListener"]
-  ]) {
-    if ((add in aTarget) && (remove in aTarget)) {
-      aTarget[add](aEventName, function onEvent(...aArgs) {
-        aTarget[remove](aEventName, onEvent, aUseCapture);
-        deferred.resolve(...aArgs);
-      }, aUseCapture);
-      break;
-    }
-  }
-
-  return deferred.promise;
-}
-
 // Hack around `once`, as that only resolves to a single (first) argument
 // and discards the rest. `onceSpread` is similar, except resolves to an
 // array of all of the arguments in the handler. These should be consolidated
 // into the same function, but many tests will need to be changed.
 function onceSpread(aTarget, aEvent) {
   let deferred = defer();
   aTarget.once(aEvent, (...args) => deferred.resolve(args));
   return deferred.promise;
@@ -195,22 +126,19 @@ function ensurePixelIs(aFront, aPosition
 
     ok(false, "Expected pixel was not already shown at: " + aPosition.toSource());
     throw new Error("Expected pixel was not already shown at: " + aPosition.toSource());
   });
 }
 
 function navigateInHistory(aTarget, aDirection, aWaitForTargetEvent = "navigate") {
   if (Cu.isCrossProcessWrapper(content)) {
-    if (!mm) {
-      throw new Error("`loadFrameScripts()` must be called before attempting to navigate in e10s.");
-    }
+    let mm = gBrowser.selectedBrowser.messageManager;
     mm.sendAsyncMessage("devtools:test:history", { direction: aDirection });
-  }
-  else {
+  } else {
     executeSoon(() => content.history[aDirection]());
   }
   return once(aTarget, aWaitForTargetEvent);
 }
 
 function navigate(aTarget, aUrl, aWaitForTargetEvent = "navigate") {
   executeSoon(() => aTarget.activeTab.navigateTo(aUrl));
   return once(aTarget, aWaitForTargetEvent);
--- a/devtools/client/shared/test/shared-head.js
+++ b/devtools/client/shared/test/shared-head.js
@@ -112,19 +112,20 @@ if (DEBUG_ALLOCATIONS) {
       tracker.logAllocationSites();
     }
     tracker.stop();
   });
 }
 
 var waitForTime = DevToolsUtils.waitForTime;
 
-function getFrameScript() {
-  let mm = gBrowser.selectedBrowser.messageManager;
+function loadFrameScriptUtils(browser = gBrowser.selectedBrowser) {
+  let mm = browser.messageManager;
   let frameURL = "chrome://devtools/content/shared/frame-script-utils.js";
+  info("Loading the helper frame script " + frameURL);
   mm.loadFrameScript(frameURL, false);
   SimpleTest.registerCleanupFunction(() => {
     mm = null;
   });
   return mm;
 }
 
 flags.testing = true;
@@ -273,19 +274,19 @@ function synthesizeKeyShortcut(key, targ
  */
 function waitForNEvents(target, eventName, numTimes, useCapture = false) {
   info("Waiting for event: '" + eventName + "' on " + target + ".");
 
   let deferred = defer();
   let count = 0;
 
   for (let [add, remove] of [
+    ["on", "off"],
     ["addEventListener", "removeEventListener"],
     ["addListener", "removeListener"],
-    ["on", "off"]
   ]) {
     if ((add in target) && (remove in target)) {
       target[add](eventName, function onEvent(...aArgs) {
         info("Got event: '" + eventName + "' on " + target + ".");
         if (++count == numTimes) {
           target[remove](eventName, onEvent, useCapture);
           deferred.resolve.apply(deferred, aArgs);
         }
@@ -377,17 +378,20 @@ function waitForTick() {
  * This shouldn't be used in the tests, but is useful when writing new tests or
  * debugging existing tests in order to introduce delays in the test steps
  *
  * @param {Number} ms
  *        The time to wait
  * @return A promise that resolves when the time is passed
  */
 function wait(ms) {
-  return new promise(resolve => setTimeout(resolve, ms));
+  return new Promise(resolve => {
+    setTimeout(resolve, ms);
+    info("Waiting " + ms / 1000 + " seconds.");
+  });
 }
 
 /**
  * Open the toolbox in a given tab.
  * @param {XULNode} tab The tab the toolbox should be opened in.
  * @param {String} toolId Optional. The ID of the tool to be selected.
  * @param {String} hostType Optional. The type of toolbox host to be used.
  * @return {Promise} Resolves with the toolbox, when it has been opened.
@@ -477,19 +481,20 @@ function waitUntil(predicate, interval =
   });
 }
 
 /**
  * Takes a string `script` and evaluates it directly in the content
  * in potentially a different process.
  */
 let MM_INC_ID = 0;
-function evalInDebuggee(mm, script) {
-  return new Promise(function (resolve, reject) {
+function evalInDebuggee(script, browser = gBrowser.selectedBrowser) {
+  return new Promise(resolve => {
     let id = MM_INC_ID++;
+    let mm = browser.messageManager;
     mm.sendAsyncMessage("devtools:test:eval", { script, id });
     mm.addMessageListener("devtools:test:eval:response", handler);
 
     function handler({ data }) {
       if (id !== data.id) {
         return;
       }
 
--- a/devtools/client/shared/webgl-utils.js
+++ b/devtools/client/shared/webgl-utils.js
@@ -28,17 +28,17 @@ function isWebGLSupportedByGFX() {
   }
   return supported;
 }
 
 function create3DContext(canvas) {
   // try to get a valid context from an existing canvas
   let context = null;
   try {
-    context = canvas.getContext(WEBGL_CONTEXT_NAME, aFlags);
+    context = canvas.getContext(WEBGL_CONTEXT_NAME);
   } catch (e) {
     return null;
   }
   return context;
 }
 
 function createCanvas(doc) {
   return doc.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
--- a/devtools/client/webaudioeditor/test/browser.ini
+++ b/devtools/client/webaudioeditor/test/browser.ini
@@ -13,16 +13,17 @@ support-files =
   doc_iframe-context.html
   doc_automation.html
   doc_bug_1112378.html
   doc_bug_1125817.html
   doc_bug_1130901.html
   doc_bug_1141261.html
   440hz_sine.ogg
   head.js
+  !/devtools/client/shared/test/shared-head.js
 
 [browser_audionode-actor-get-param-flags.js]
 [browser_audionode-actor-get-params-01.js]
 [browser_audionode-actor-get-params-02.js]
 [browser_audionode-actor-get-set-param.js]
 [browser_audionode-actor-type.js]
 [browser_audionode-actor-source.js]
 [browser_audionode-actor-bypass.js]
--- a/devtools/client/webaudioeditor/test/browser_audionode-actor-get-params-01.js
+++ b/devtools/client/webaudioeditor/test/browser_audionode-actor-get-params-01.js
@@ -7,17 +7,17 @@
 
 add_task(function* () {
   let { target, front } = yield initBackend(SIMPLE_NODES_URL);
   let [_, nodes] = yield Promise.all([
     front.setup({ reload: true }),
     getN(front, "create-node", 15)
   ]);
 
-  yield loadFrameScripts();
+  yield loadFrameScriptUtils();
 
   let allNodeParams = yield Promise.all(nodes.map(node => node.getParams()));
   let nodeTypes = [
     "AudioDestinationNode",
     "AudioBufferSourceNode", "ScriptProcessorNode", "AnalyserNode", "GainNode",
     "DelayNode", "BiquadFilterNode", "WaveShaperNode", "PannerNode", "ConvolverNode",
     "ChannelSplitterNode", "ChannelMergerNode", "DynamicsCompressorNode", "OscillatorNode",
     "StereoPannerNode"
--- a/devtools/client/webaudioeditor/test/browser_audionode-actor-get-params-02.js
+++ b/devtools/client/webaudioeditor/test/browser_audionode-actor-get-params-02.js
@@ -8,17 +8,17 @@
 
 add_task(function* () {
   let { target, front } = yield initBackend(SIMPLE_NODES_URL);
   let [_, nodes] = yield Promise.all([
     front.setup({ reload: true }),
     getN(front, "create-node", 15)
   ]);
 
-  yield loadFrameScripts();
+  yield loadFrameScriptUtils();
 
   let allParams = yield Promise.all(nodes.map(node => node.getParams()));
   let types = [
     "AudioDestinationNode", "AudioBufferSourceNode", "ScriptProcessorNode",
     "AnalyserNode", "GainNode", "DelayNode", "BiquadFilterNode", "WaveShaperNode",
     "PannerNode", "ConvolverNode", "ChannelSplitterNode", "ChannelMergerNode",
     "DynamicsCompressorNode", "OscillatorNode", "StereoPannerNode"
   ];
--- a/devtools/client/webaudioeditor/test/browser_callwatcher-02.js
+++ b/devtools/client/webaudioeditor/test/browser_callwatcher-02.js
@@ -9,17 +9,17 @@
 
 const BUG_1112378_URL = EXAMPLE_URL + "doc_bug_1112378.html";
 
 add_task(function* () {
   let { target, panel } = yield initWebAudioEditor(BUG_1112378_URL);
   let { panelWin } = panel;
   let { gFront, $, $$, EVENTS, gAudioNodes } = panelWin;
 
-  loadFrameScripts();
+  loadFrameScriptUtils();
 
   let rendered = waitForGraphRendered(panelWin, 2, 0);
   reload(target);
   yield rendered;
 
   let error = yield evalInDebuggee("throwError()");
   is(error.lineNumber, 21, "error has correct lineNumber");
   is(error.columnNumber, 11, "error has correct columnNumber");
--- a/devtools/client/webaudioeditor/test/browser_wa_properties-view-media-nodes.js
+++ b/devtools/client/webaudioeditor/test/browser_wa_properties-view-media-nodes.js
@@ -37,17 +37,17 @@ add_task(function* () {
   let { panelWin } = panel;
   let { gFront, $, $$, EVENTS, PropertiesView } = panelWin;
   let gVars = PropertiesView._propsView;
 
   // Auto enable getUserMedia
   let mediaPermissionPref = Services.prefs.getBoolPref(MEDIA_PERMISSION);
   Services.prefs.setBoolPref(MEDIA_PERMISSION, true);
 
-  yield loadFrameScripts();
+  yield loadFrameScriptUtils();
 
   let events = Promise.all([
     getN(gFront, "create-node", 4),
     waitForGraphRendered(panelWin, 4, 0)
   ]);
   reload(target);
   let [actors] = yield events;
   let nodeIds = actors.map(actor => actor.actorID);
--- a/devtools/client/webaudioeditor/test/browser_wa_properties-view-params.js
+++ b/devtools/client/webaudioeditor/test/browser_wa_properties-view-params.js
@@ -9,17 +9,17 @@
 add_task(function* () {
   let { target, panel } = yield initWebAudioEditor(SIMPLE_NODES_URL);
   let { panelWin } = panel;
   let { gFront, $, $$, EVENTS, PropertiesView } = panelWin;
   let gVars = PropertiesView._propsView;
 
   let started = once(gFront, "start-context");
 
-  yield loadFrameScripts();
+  yield loadFrameScriptUtils();
 
   let events = Promise.all([
     getN(gFront, "create-node", 15),
     waitForGraphRendered(panelWin, 15, 0)
   ]);
   reload(target);
   let [actors] = yield events;
   let nodeIds = actors.map(actor => actor.actorID);
--- a/devtools/client/webaudioeditor/test/head.js
+++ b/devtools/client/webaudioeditor/test/head.js
@@ -1,46 +1,27 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* eslint no-unused-vars: [2, {"vars": "local"}] */
+/* import-globals-from ../../shared/test/shared-head.js */
+
 "use strict";
 
-// There are shutdown issues for which multiple rejections are left uncaught.
-// This bug should be fixed, but for the moment devtools are whitelisted.
-//
-// NOTE: Entire directory whitelisting should be kept to a minimum. Normally you
-//       should use "expectUncaughtRejection" to flag individual failures.
-const { PromiseTestUtils } = ChromeUtils.import("resource://testing-common/PromiseTestUtils.jsm", {});
-PromiseTestUtils.whitelistRejectionsGlobally(/Component not initialized/);
-PromiseTestUtils.whitelistRejectionsGlobally(/Connection closed/);
-PromiseTestUtils.whitelistRejectionsGlobally(/destroy/);
-PromiseTestUtils.whitelistRejectionsGlobally(/File closed/);
-PromiseTestUtils.whitelistRejectionsGlobally(/is no longer, usable/);
-PromiseTestUtils.whitelistRejectionsGlobally(/NS_ERROR_FAILURE/);
-PromiseTestUtils.whitelistRejectionsGlobally(/this\._urls is null/);
-PromiseTestUtils.whitelistRejectionsGlobally(/this\.tabTarget is null/);
-PromiseTestUtils.whitelistRejectionsGlobally(/this\.toolbox is null/);
-PromiseTestUtils.whitelistRejectionsGlobally(/this\.webConsoleClient is null/);
-PromiseTestUtils.whitelistRejectionsGlobally(/this\.worker is null/);
+// Load the shared-head file first.
+Services.scriptloader.loadSubScript(
+  "chrome://mochitests/content/browser/devtools/client/shared/test/shared-head.js",
+  this);
 
-var { require } = ChromeUtils.import("resource://devtools/shared/Loader.jsm", {});
-var { Task } = require("devtools/shared/task");
-var Services = require("Services");
-var { gDevTools } = require("devtools/client/framework/devtools");
-var { TargetFactory } = require("devtools/client/framework/target");
 var { DebuggerServer } = require("devtools/server/main");
 var { generateUUID } = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator);
 
-var Services = require("Services");
 var { WebAudioFront } = require("devtools/shared/fronts/webaudio");
-var DevToolsUtils = require("devtools/shared/DevToolsUtils");
-var flags = require("devtools/shared/flags");
 var audioNodes = require("devtools/server/actors/utils/audionodes.json");
-var mm = null;
 
-const FRAME_SCRIPT_UTILS_URL = "chrome://devtools/content/shared/frame-script-utils.js";
 const EXAMPLE_URL = "http://example.com/browser/devtools/client/webaudioeditor/test/";
 const SIMPLE_CONTEXT_URL = EXAMPLE_URL + "doc_simple-context.html";
 const COMPLEX_CONTEXT_URL = EXAMPLE_URL + "doc_complex-context.html";
 const SIMPLE_NODES_URL = EXAMPLE_URL + "doc_simple-node-creation.html";
 const MEDIA_NODES_URL = EXAMPLE_URL + "doc_media-node-creation.html";
 const BUFFER_AND_ARRAY_URL = EXAMPLE_URL + "doc_buffer-and-array.html";
 const DESTROY_NODES_URL = EXAMPLE_URL + "doc_destroy-nodes.html";
 const CONNECT_PARAM_URL = EXAMPLE_URL + "doc_connect-param.html";
@@ -48,116 +29,35 @@ const CONNECT_MULTI_PARAM_URL = EXAMPLE_
 const IFRAME_CONTEXT_URL = EXAMPLE_URL + "doc_iframe-context.html";
 const AUTOMATION_URL = EXAMPLE_URL + "doc_automation.html";
 
 // Enable logging for all the tests. Both the debugger server and frontend will
 // be affected by this pref.
 var gEnableLogging = Services.prefs.getBoolPref("devtools.debugger.log");
 Services.prefs.setBoolPref("devtools.debugger.log", false);
 
-// All tests are asynchronous.
-waitForExplicitFinish();
-
 var gToolEnabled = Services.prefs.getBoolPref("devtools.webaudioeditor.enabled");
 
-flags.testing = true;
-
 registerCleanupFunction(() => {
-  flags.testing = false;
-  info("finish() was called, cleaning up...");
   Services.prefs.setBoolPref("devtools.debugger.log", gEnableLogging);
   Services.prefs.setBoolPref("devtools.webaudioeditor.enabled", gToolEnabled);
   Cu.forceGC();
 });
 
-/**
- * Call manually in tests that use frame script utils after initializing
- * the web audio editor. Call after init but before navigating to a different page.
- */
-function loadFrameScripts() {
-  mm = gBrowser.selectedBrowser.messageManager;
-  mm.loadFrameScript(FRAME_SCRIPT_UTILS_URL, false);
-}
-
-function addTab(aUrl, aWindow) {
-  info("Adding tab: " + aUrl);
-
-  let targetWindow = aWindow || window;
-  let targetBrowser = targetWindow.gBrowser;
-
-  targetWindow.focus();
-  let tab = targetBrowser.selectedTab = targetBrowser.addTab(aUrl);
-  let linkedBrowser = tab.linkedBrowser;
-
-  return new Promise((resolve, reject) => {
-    BrowserTestUtils.browserLoaded(linkedBrowser).then(function () {
-      info("Tab added and finished loading: " + aUrl);
-      resolve(tab);
-    });
-  });
-}
-
-function removeTab(aTab, aWindow) {
-  info("Removing tab.");
-
-  let targetWindow = aWindow || window;
-  let targetBrowser = targetWindow.gBrowser;
-  let tabContainer = targetBrowser.tabContainer;
-
-  return new Promise((resolve, reject) => {
-    tabContainer.addEventListener("TabClose", function (aEvent) {
-      info("Tab removed and finished closing.");
-      resolve();
-    }, {once: true});
-
-    targetBrowser.removeTab(aTab);
-  });
-}
-
-function once(aTarget, aEventName, aUseCapture = false) {
-  info("Waiting for event: '" + aEventName + "' on " + aTarget + ".");
-
-  return new Promise((resolve, reject) => {
-    for (let [add, remove] of [
-      ["on", "off"], // Use event emitter before DOM events for consistency
-      ["addEventListener", "removeEventListener"],
-      ["addListener", "removeListener"]
-    ]) {
-      if ((add in aTarget) && (remove in aTarget)) {
-        aTarget[add](aEventName, function onEvent(...aArgs) {
-          aTarget[remove](aEventName, onEvent, aUseCapture);
-          info("Got event: '" + aEventName + "' on " + aTarget + ".");
-          resolve(...aArgs);
-        }, aUseCapture);
-        break;
-      }
-    }
-  });
-}
-
 function reload(aTarget, aWaitForTargetEvent = "navigate") {
   aTarget.activeTab.reload();
   return once(aTarget, aWaitForTargetEvent);
 }
 
 function navigate(aTarget, aUrl, aWaitForTargetEvent = "navigate") {
   executeSoon(() => aTarget.activeTab.navigateTo(aUrl));
   return once(aTarget, aWaitForTargetEvent);
 }
 
 /**
- * Call manually in tests that use frame script utils after initializing
- * the shader editor. Call after init but before navigating to different pages.
- */
-function loadFrameScripts() {
-  mm = gBrowser.selectedBrowser.messageManager;
-  mm.loadFrameScript(FRAME_SCRIPT_UTILS_URL, false);
-}
-
-/**
  * Adds a new tab, and instantiate a WebAudiFront object.
  * This requires calling removeTab before the test ends.
  */
 function initBackend(aUrl) {
   info("Initializing a web audio editor front.");
 
   DebuggerServer.init();
   DebuggerServer.registerAllActors();
@@ -363,26 +263,16 @@ function command(button) {
   button.dispatchEvent(ev);
 }
 
 function isVisible(element) {
   return !element.getAttribute("hidden");
 }
 
 /**
- * Used in debugging, returns a promise that resolves in `n` milliseconds.
- */
-function wait(n) {
-  return new Promise((resolve, reject) => {
-    setTimeout(resolve, n);
-    info("Waiting " + n / 1000 + " seconds.");
-  });
-}
-
-/**
  * Clicks a graph node based on actorID or passing in an element.
  * Returns a promise that resolves once UI_INSPECTOR_NODE_SET is fired and
  * the tabs have rendered, completing all RDP requests for the node.
  */
 function clickGraphNode(panelWin, el, waitForToggle = false) {
   let promises = [
     once(panelWin, panelWin.EVENTS.UI_INSPECTOR_NODE_SET),
     once(panelWin, panelWin.EVENTS.UI_PROPERTIES_TAB_RENDERED),
@@ -483,41 +373,16 @@ function checkAutomationValue(values, ti
 function waitForInspectorRender(panelWin, EVENTS) {
   return Promise.all([
     once(panelWin, EVENTS.UI_PROPERTIES_TAB_RENDERED),
     once(panelWin, EVENTS.UI_AUTOMATION_TAB_RENDERED)
   ]);
 }
 
 /**
- * Takes a string `script` and evaluates it directly in the content
- * in potentially a different process.
- */
-function evalInDebuggee(script) {
-  if (!mm) {
-    throw new Error("`loadFrameScripts()` must be called when using MessageManager.");
-  }
-
-  return new Promise((resolve, reject) => {
-    let id = generateUUID().toString();
-    mm.sendAsyncMessage("devtools:test:eval", { script: script, id: id });
-    mm.addMessageListener("devtools:test:eval:response", handler);
-
-    function handler({ data }) {
-      if (id !== data.id) {
-        return;
-      }
-
-      mm.removeMessageListener("devtools:test:eval:response", handler);
-      resolve(data.value);
-    }
-  });
-}
-
-/**
  * Takes an AudioNode type and returns it's properties (from audionode.json)
  * as keys and their default values as keys
  */
 function nodeDefaultValues(nodeName) {
   let fn = NODE_CONSTRUCTORS[nodeName];
 
   if (typeof fn === "undefined") return {};
 
--- a/devtools/client/webconsole/net/test/mochitest/head.js
+++ b/devtools/client/webconsole/net/test/mochitest/head.js
@@ -5,19 +5,16 @@
 /* import-globals-from ../../../test/head.js */
 
 "use strict";
 
 // Load Web Console head.js, it implements helper console test API
 Services.scriptloader.loadSubScript(
   "chrome://mochitests/content/browser/devtools/client/webconsole/test/head.js", this);
 
-const FRAME_SCRIPT_UTILS_URL =
-  "chrome://devtools/content/shared/frame-script-utils.js";
-
 const NET_INFO_PREF = "devtools.webconsole.filter.networkinfo";
 const NET_XHR_PREF = "devtools.webconsole.filter.netxhr";
 
 // Enable XHR logging for the test
 Services.prefs.setBoolPref(NET_INFO_PREF, true);
 Services.prefs.setBoolPref(NET_XHR_PREF, true);
 
 registerCleanupFunction(() => {
@@ -39,17 +36,17 @@ registerCleanupFunction(function* () {
  */
 function addTestTab(url) {
   info("Adding a new JSON tab with URL: '" + url + "'");
 
   return Task.spawn(function* () {
     let tab = yield addTab(url);
 
     // Load devtools/shared/frame-script-utils.js
-    loadCommonFrameScript(tab);
+    loadFrameScriptUtils(tab.linkedBrowser);
 
     // Open the Console panel
     let hud = yield openConsole();
 
     return {
       tab: tab,
       browser: tab.linkedBrowser,
       hud: hud
@@ -197,13 +194,8 @@ function waitForContentMessage(name) {
 
   return new Promise((resolve) => {
     mm.addMessageListener(name, function onMessage(msg) {
       mm.removeMessageListener(name, onMessage);
       resolve(msg.data);
     });
   });
 }
-
-function loadCommonFrameScript(tab) {
-  let browser = tab ? tab.linkedBrowser : gBrowser.selectedBrowser;
-  browser.messageManager.loadFrameScript(FRAME_SCRIPT_UTILS_URL, false);
-}