Merge fx-team to central, a=merge
authorWes Kocher <wkocher@mozilla.com>
Tue, 04 Oct 2016 17:40:37 -0700
changeset 316471 ea104eeb14cc54da9a06c3766da63f73117723a0
parent 316461 9dc03b80c67223e32853b309e6c95fdd182061ba (current diff)
parent 316470 ad324639bfaeba6d7be3b732295b6a27d0c54e0a (diff)
child 316483 e912950f0968b97196a5e1a74229fb8f3d9620b6
child 316509 d3366b063f5231867f2cea79ae229ac4b4e2c63b
child 316546 35a53c44ad0d348e84da0d5041f87c8c9e72badd
push id30773
push userkwierso@gmail.com
push dateWed, 05 Oct 2016 00:40:39 +0000
treeherdermozilla-central@ea104eeb14cc [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone52.0a1
first release with
nightly linux32
ea104eeb14cc / 52.0a1 / 20161005030211 / files
nightly linux64
ea104eeb14cc / 52.0a1 / 20161005030211 / files
nightly mac
ea104eeb14cc / 52.0a1 / 20161005030211 / files
nightly win32
ea104eeb14cc / 52.0a1 / 20161005030211 / files
nightly win64
ea104eeb14cc / 52.0a1 / 20161005030211 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge fx-team to central, a=merge
--- a/devtools/client/debugger/debugger-controller.js
+++ b/devtools/client/debugger/debugger-controller.js
@@ -139,17 +139,17 @@ var services = {
 
 var Services = require("Services");
 var {TargetFactory} = require("devtools/client/framework/target");
 var {Toolbox} = require("devtools/client/framework/toolbox");
 var DevToolsUtils = require("devtools/shared/DevToolsUtils");
 var promise = require("devtools/shared/deprecated-sync-thenables");
 var Editor = require("devtools/client/sourceeditor/editor");
 var DebuggerEditor = require("devtools/client/sourceeditor/debugger");
-var {Tooltip} = require("devtools/client/shared/widgets/Tooltip");
+var Tooltip = require("devtools/client/shared/widgets/Tooltip");
 var FastListWidget = require("devtools/client/shared/widgets/FastListWidget");
 var {LocalizationHelper, ELLIPSIS} = require("devtools/shared/l10n");
 var {PrefsHelper} = require("devtools/client/shared/prefs");
 var {Task} = require("devtools/shared/task");
 
 XPCOMUtils.defineConstant(this, "EVENTS", EVENTS);
 
 XPCOMUtils.defineLazyModuleGetter(this, "Parser",
--- a/devtools/client/inspector/computed/test/browser_computed_search-filter_escape-keypress.js
+++ b/devtools/client/inspector/computed/test/browser_computed_search-filter_escape-keypress.js
@@ -1,14 +1,18 @@
 /* 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/ */
 
 "use strict";
 
+// Avoid test timeouts on Linux debug builds where the test takes just a bit too long to
+// run (see bug 1258081).
+requestLongerTimeout(2);
+
 // Tests that search filter escape keypress will clear the search field.
 
 const TEST_URI = `
   <style type="text/css">
     .matches {
       color: #F00;
     }
   </style>
--- a/devtools/client/inspector/shared/style-inspector-overlays.js
+++ b/devtools/client/inspector/shared/style-inspector-overlays.js
@@ -14,24 +14,20 @@
 
 const {getColor} = require("devtools/client/shared/theme");
 const {HTMLTooltip} = require("devtools/client/shared/widgets/HTMLTooltip");
 const {
   getImageDimensions,
   setImageTooltip,
   setBrokenImageTooltip,
 } = require("devtools/client/shared/widgets/tooltip/ImageTooltipHelper");
-const {
-  CssDocsTooltip,
-} = require("devtools/client/shared/widgets/tooltip/CssDocsTooltip");
-const {
-  SwatchColorPickerTooltip,
-  SwatchCubicBezierTooltip,
-  SwatchFilterTooltip
-} = require("devtools/client/shared/widgets/Tooltip");
+const CssDocsTooltip = require("devtools/client/shared/widgets/tooltip/CssDocsTooltip");
+const SwatchColorPickerTooltip = require("devtools/client/shared/widgets/tooltip/SwatchColorPickerTooltip");
+const SwatchCubicBezierTooltip = require("devtools/client/shared/widgets/tooltip/SwatchCubicBezierTooltip");
+const SwatchFilterTooltip = require("devtools/client/shared/widgets/tooltip/SwatchFilterTooltip");
 const EventEmitter = require("devtools/shared/event-emitter");
 const promise = require("promise");
 const {Task} = require("devtools/shared/task");
 const Services = require("Services");
 const {getCssProperties} = require("devtools/shared/fronts/css-properties");
 
 const PREF_IMAGE_TOOLTIP_SIZE = "devtools.inspector.imagePreviewTooltipSize";
 
--- a/devtools/client/preferences/devtools.js
+++ b/devtools/client/preferences/devtools.js
@@ -310,17 +310,21 @@ pref("devtools.webconsole.persistlog", f
 // any timestamps.
 pref("devtools.webconsole.timestampMessages", false);
 
 // Web Console automatic multiline mode: |true| if you want incomplete statements
 // to automatically trigger multiline editing (equivalent to shift + enter).
 pref("devtools.webconsole.autoMultiline", true);
 
 // Enable the experimental webconsole frontend (work in progress)
+#if defined(NIGHTLY_BUILD)
+pref("devtools.webconsole.new-frontend-enabled", true);
+#else
 pref("devtools.webconsole.new-frontend-enabled", false);
+#endif
 
 // Enable the experimental support for source maps in console (work in progress)
 pref("devtools.sourcemap.locations.enabled", false);
 
 // The number of lines that are displayed in the web console.
 pref("devtools.hud.loglimit", 1000);
 
 // The number of lines that are displayed in the web console for the Net,
--- a/devtools/client/responsive.html/test/browser/browser_device_change.js
+++ b/devtools/client/responsive.html/test/browser/browser_device_change.js
@@ -8,32 +8,30 @@ const TEST_URL = "data:text/html;charset
 
 const DEFAULT_DPPX = window.devicePixelRatio;
 const DEFAULT_UA = Cc["@mozilla.org/network/protocol;1?name=http"]
   .getService(Ci.nsIHttpProtocolHandler)
   .userAgent;
 
 const Types = require("devtools/client/responsive.html/types");
 
-const { addDevice, removeDevice } = require("devtools/client/shared/devices");
-
 const testDevice = {
   "name": "Fake Phone RDM Test",
   "width": 320,
   "height": 570,
   "pixelRatio": 5.5,
   "userAgent": "Mozilla/5.0 (Mobile; rv:39.0) Gecko/39.0 Firefox/39.0",
   "touch": true,
   "firefoxOS": true,
   "os": "custom",
   "featured": true,
 };
 
 // Add the new device to the list
-addDevice(testDevice);
+addDeviceForTest(testDevice);
 
 addRDMTask(TEST_URL, function* ({ ui, manager }) {
   let { store } = ui.toolWindow;
 
   // Wait until the viewport has been added and the device list has been loaded
   yield waitUntilState(store, state => state.viewports.length == 1
     && state.devices.listState == Types.deviceListState.LOADED);
 
@@ -60,19 +58,16 @@ addRDMTask(TEST_URL, function* ({ ui, ma
   testViewportSelectLabel(ui, "no device selected");
 
   // Test device with generic properties
   yield switchDevice(ui, "Laptop (1366 x 768)");
   yield waitForViewportResizeTo(ui, 1366, 768);
   yield testUserAgent(ui, DEFAULT_UA);
   yield testDevicePixelRatio(ui, 1);
   yield testTouchEventsOverride(ui, false);
-
-  ok(removeDevice(testDevice),
-    "Test Device properly removed.");
 });
 
 function testViewportDimensions(ui, w, h) {
   let viewport = ui.toolWindow.document.querySelector(".viewport-content");
 
   is(ui.toolWindow.getComputedStyle(viewport).getPropertyValue("width"),
      `${w}px`, `Viewport should have width of ${w}px`);
   is(ui.toolWindow.getComputedStyle(viewport).getPropertyValue("height"),
--- a/devtools/client/responsive.html/test/browser/browser_device_modal_submit.js
+++ b/devtools/client/responsive.html/test/browser/browser_device_modal_submit.js
@@ -1,15 +1,15 @@
 /* Any copyright is dedicated to the Public Domain.
 http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 // Test submitting display device changes on the device modal
-const { getDevices, addDevice } = require("devtools/client/shared/devices");
+const { getDevices } = require("devtools/client/shared/devices");
 
 const addedDevice = {
   "name": "Fake Phone RDM Test",
   "width": 320,
   "height": 570,
   "pixelRatio": 1.5,
   "userAgent": "Mozilla/5.0 (Mobile; rv:39.0) Gecko/39.0 Firefox/39.0",
   "touch": true,
@@ -102,17 +102,17 @@ addRDMTask(TEST_URL, function* ({ ui }) 
 
   info("Reopen device modal and check device is correctly unchecked");
   openDeviceModal(ui);
   ok([...document.querySelectorAll(".device-input-checkbox")]
     .filter(cb => !cb.checked && cb.value === checkedVal)[0],
     checkedVal + " is unchecked in the device modal.");
 
   // Let's add a dummy device to simulate featured flag changes for next test
-  addDevice(addedDevice);
+  addDeviceForTest(addedDevice);
 });
 
 addRDMTask(TEST_URL, function* ({ ui }) {
   let { store, document } = ui.toolWindow;
   let select = document.querySelector(".viewport-device-selector");
 
   // Wait until the viewport has been added
   yield waitUntilState(store, state => state.viewports.length == 1);
--- a/devtools/client/responsive.html/test/browser/head.js
+++ b/devtools/client/responsive.html/test/browser/head.js
@@ -32,16 +32,17 @@ Services.scriptloader.loadSubScript(
   this);
 
 const TEST_URI_ROOT = "http://example.com/browser/devtools/client/responsive.html/test/browser/";
 const OPEN_DEVICE_MODAL_VALUE = "OPEN_DEVICE_MODAL";
 
 const { _loadPreferredDevices } = require("devtools/client/responsive.html/actions/devices");
 const { getOwnerWindow } = require("sdk/tabs/utils");
 const asyncStorage = require("devtools/shared/async-storage");
+const { addDevice, removeDevice } = require("devtools/client/shared/devices");
 
 SimpleTest.requestCompleteLog();
 SimpleTest.waitForExplicitFinish();
 
 flags.testing = true;
 Services.prefs.clearUserPref("devtools.responsive.html.displayedDeviceList");
 Services.prefs.setCharPref("devtools.devices.url",
   TEST_URI_ROOT + "devices.json");
@@ -224,33 +225,38 @@ function openDeviceModal(ui) {
     ui.toolWindow);
   EventUtils.synthesizeMouseAtCenter(editDeviceOption, {type: "mouseup"},
     ui.toolWindow);
 
   ok(modal.classList.contains("opened") && !modal.classList.contains("closed"),
     "The device modal is displayed.");
 }
 
-function switchDevice({ toolWindow }, name) {
+function switchDevice({ toolWindow }, value) {
   return new Promise(resolve => {
-    let select = toolWindow.document.querySelector(".viewport-device-selector");
+    let selector = ".viewport-device-selector";
+    let select = toolWindow.document.querySelector(selector);
+    isnot(select, null, `selector "${selector}" should match an existing element.`);
+
+    let option = [...select.options].find(o => o.value === String(value));
+    isnot(option, undefined, `value "${value}" should match an existing option.`);
 
     let event = new toolWindow.UIEvent("change", {
       view: toolWindow,
       bubbles: true,
       cancelable: true
     });
 
-    select.addEventListener("change", function onChange() {
-      is(select.value, name, "Device should be selected");
-      select.removeEventListener("change", onChange);
+    select.addEventListener("change", () => {
+      is(select.value, value,
+        `Select's option with value "${value}" should be selected.`);
       resolve();
-    });
+    }, { once: true });
 
-    select.value = name;
+    select.value = value;
     select.dispatchEvent(event);
   });
 }
 
 function getSessionHistory(browser) {
   return ContentTask.spawn(browser, {}, function* () {
     /* eslint-disable no-undef */
     let { interfaces: Ci } = Components;
@@ -307,8 +313,18 @@ function back(browser) {
   return shown;
 }
 
 function forward(browser) {
   let shown = waitForPageShow(browser);
   browser.goForward();
   return shown;
 }
+
+function addDeviceForTest(device) {
+  info(`Adding Test Device "${device.name}" to the list.`);
+  addDevice(device);
+
+  registerCleanupFunction(() => {
+    // Note that assertions in cleanup functions are not displayed unless they failed.
+    ok(removeDevice(device), `Removed Test Device "${device.name}" from the list.`);
+  });
+}
--- a/devtools/client/shadereditor/shadereditor.js
+++ b/devtools/client/shadereditor/shadereditor.js
@@ -6,17 +6,17 @@
 var { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
 
 const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
 const {XPCOMUtils} = require("resource://gre/modules/XPCOMUtils.jsm");
 const {SideMenuWidget} = require("resource://devtools/client/shared/widgets/SideMenuWidget.jsm");
 const promise = require("promise");
 const Services = require("Services");
 const EventEmitter = require("devtools/shared/event-emitter");
-const {Tooltip} = require("devtools/client/shared/widgets/Tooltip");
+const Tooltip = require("devtools/client/shared/widgets/Tooltip");
 const Editor = require("devtools/client/sourceeditor/editor");
 const {LocalizationHelper} = require("devtools/shared/l10n");
 const {Heritage, WidgetMethods, setNamedTimeout} =
   require("devtools/client/shared/widgets/view-helpers");
 const {Task} = require("devtools/shared/task");
 
 // The panel's window global is an EventEmitter firing the following events:
 const EVENTS = {
--- a/devtools/client/shared/widgets/Tooltip.js
+++ b/devtools/client/shared/widgets/Tooltip.js
@@ -1,27 +1,18 @@
 /* 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/. */
 
 "use strict";
 
 const defer = require("devtools/shared/defer");
-const {Spectrum} = require("devtools/client/shared/widgets/Spectrum");
-const {CubicBezierWidget} =
-      require("devtools/client/shared/widgets/CubicBezierWidget");
-const {CSSFilterEditorWidget} = require("devtools/client/shared/widgets/FilterWidget");
+const EventEmitter = require("devtools/shared/event-emitter");
+const {KeyCodes} = require("devtools/client/shared/keycodes");
 const {TooltipToggle} = require("devtools/client/shared/widgets/tooltip/TooltipToggle");
-const EventEmitter = require("devtools/shared/event-emitter");
-const {colorUtils} = require("devtools/shared/css/color");
-const Heritage = require("sdk/core/heritage");
-const {HTMLTooltip} = require("devtools/client/shared/widgets/HTMLTooltip");
-const {KeyShortcuts} = require("devtools/client/shared/key-shortcuts");
-const {Task} = require("devtools/shared/task");
-const {KeyCodes} = require("devtools/client/shared/keycodes");
 
 const XHTML_NS = "http://www.w3.org/1999/xhtml";
 const ESCAPE_KEYCODE = KeyCodes.DOM_VK_ESCAPE;
 const POPUP_EVENTS = ["shown", "hidden", "showing", "hiding"];
 
 /**
  * Tooltip widget.
  *
@@ -210,18 +201,16 @@ function Tooltip(doc, options) {
       if (add in emitter) {
         emitter[add](event, this.hide, useCapture);
         break;
       }
     }
   }
 }
 
-module.exports.Tooltip = Tooltip;
-
 Tooltip.prototype = {
   defaultPosition: "before_start",
   // px
   defaultOffsetX: 0,
   // px
   defaultOffsetY: 0,
   // px
 
@@ -463,550 +452,9 @@ Tooltip.prototype = {
 
     // Put the iframe in the tooltip
     this.content = iframe;
 
     return def.promise;
   }
 };
 
-/**
- * Base class for all (color, gradient, ...)-swatch based value editors inside
- * tooltips
- *
- * @param {Toolbox} toolbox
- *        The devtools toolbox, needed to get the devtools main window.
- */
-function SwatchBasedEditorTooltip(toolbox, stylesheet) {
-  EventEmitter.decorate(this);
-  // Creating a tooltip instance
-  // This one will consume outside clicks as it makes more sense to let the user
-  // close the tooltip by clicking out
-  // It will also close on <escape> and <enter>
-  this.tooltip = new HTMLTooltip(toolbox, {
-    type: "arrow",
-    consumeOutsideClicks: true,
-    useXulWrapper: true,
-    stylesheet
-  });
-
-  // By default, swatch-based editor tooltips revert value change on <esc> and
-  // commit value change on <enter>
-  this.shortcuts = new KeyShortcuts({
-    window: this.tooltip.topWindow
-  });
-  this.shortcuts.on("Escape", (name, event) => {
-    if (!this.tooltip.isVisible()) {
-      return;
-    }
-    this.revert();
-    this.hide();
-    event.stopPropagation();
-    event.preventDefault();
-  });
-  this.shortcuts.on("Return", (name, event) => {
-    if (!this.tooltip.isVisible()) {
-      return;
-    }
-    this.commit();
-    this.hide();
-    event.stopPropagation();
-    event.preventDefault();
-  });
-
-  // All target swatches are kept in a map, indexed by swatch DOM elements
-  this.swatches = new Map();
-
-  // When a swatch is clicked, and for as long as the tooltip is shown, the
-  // activeSwatch property will hold the reference to the swatch DOM element
-  // that was clicked
-  this.activeSwatch = null;
-
-  this._onSwatchClick = this._onSwatchClick.bind(this);
-}
-
-SwatchBasedEditorTooltip.prototype = {
-  /**
-   * Show the editor tooltip for the currently active swatch.
-   *
-   * @return {Promise} a promise that resolves once the editor tooltip is displayed, or
-   *         immediately if there is no currently active swatch.
-   */
-  show: function () {
-    if (this.activeSwatch) {
-      let onShown = this.tooltip.once("shown");
-      this.tooltip.show(this.activeSwatch, "topcenter bottomleft");
-
-      // When the tooltip is closed by clicking outside the panel we want to
-      // commit any changes.
-      this.tooltip.once("hidden", () => {
-        if (!this._reverted && !this.eyedropperOpen) {
-          this.commit();
-        }
-        this._reverted = false;
-
-        // Once the tooltip is hidden we need to clean up any remaining objects.
-        if (!this.eyedropperOpen) {
-          this.activeSwatch = null;
-        }
-      });
-
-      return onShown;
-    }
-
-    return Promise.resolve();
-  },
-
-  hide: function () {
-    this.tooltip.hide();
-  },
-
-  /**
-   * Add a new swatch DOM element to the list of swatch elements this editor
-   * tooltip knows about. That means from now on, clicking on that swatch will
-   * toggle the editor.
-   *
-   * @param {node} swatchEl
-   *        The element to add
-   * @param {object} callbacks
-   *        Callbacks that will be executed when the editor wants to preview a
-   *        value change, or revert a change, or commit a change.
-   *        - onShow: will be called when one of the swatch tooltip is shown
-   *        - onPreview: will be called when one of the sub-classes calls
-   *        preview
-   *        - onRevert: will be called when the user ESCapes out of the tooltip
-   *        - onCommit: will be called when the user presses ENTER or clicks
-   *        outside the tooltip.
-   */
-  addSwatch: function (swatchEl, callbacks = {}) {
-    if (!callbacks.onShow) {
-      callbacks.onShow = function () {};
-    }
-    if (!callbacks.onPreview) {
-      callbacks.onPreview = function () {};
-    }
-    if (!callbacks.onRevert) {
-      callbacks.onRevert = function () {};
-    }
-    if (!callbacks.onCommit) {
-      callbacks.onCommit = function () {};
-    }
-
-    this.swatches.set(swatchEl, {
-      callbacks: callbacks
-    });
-    swatchEl.addEventListener("click", this._onSwatchClick, false);
-  },
-
-  removeSwatch: function (swatchEl) {
-    if (this.swatches.has(swatchEl)) {
-      if (this.activeSwatch === swatchEl) {
-        this.hide();
-        this.activeSwatch = null;
-      }
-      swatchEl.removeEventListener("click", this._onSwatchClick, false);
-      this.swatches.delete(swatchEl);
-    }
-  },
-
-  _onSwatchClick: function (event) {
-    let swatch = this.swatches.get(event.target);
-
-    if (event.shiftKey) {
-      event.stopPropagation();
-      return;
-    }
-    if (swatch) {
-      this.activeSwatch = event.target;
-      this.show();
-      swatch.callbacks.onShow();
-      event.stopPropagation();
-    }
-  },
-
-  /**
-   * Not called by this parent class, needs to be taken care of by sub-classes
-   */
-  preview: function (value) {
-    if (this.activeSwatch) {
-      let swatch = this.swatches.get(this.activeSwatch);
-      swatch.callbacks.onPreview(value);
-    }
-  },
-
-  /**
-   * This parent class only calls this on <esc> keypress
-   */
-  revert: function () {
-    if (this.activeSwatch) {
-      this._reverted = true;
-      let swatch = this.swatches.get(this.activeSwatch);
-      this.tooltip.once("hidden", () => {
-        swatch.callbacks.onRevert();
-      });
-    }
-  },
-
-  /**
-   * This parent class only calls this on <enter> keypress
-   */
-  commit: function () {
-    if (this.activeSwatch) {
-      let swatch = this.swatches.get(this.activeSwatch);
-      swatch.callbacks.onCommit();
-    }
-  },
-
-  destroy: function () {
-    this.swatches.clear();
-    this.activeSwatch = null;
-    this.tooltip.off("keypress", this._onTooltipKeypress);
-    this.tooltip.destroy();
-    this.shortcuts.destroy();
-  }
-};
-
-/**
- * The swatch color picker tooltip class is a specific class meant to be used
- * along with output-parser's generated color swatches.
- * It extends the parent SwatchBasedEditorTooltip class.
- * It just wraps a standard Tooltip and sets its content with an instance of a
- * color picker.
- *
- * @param {Toolbox} toolbox
- *        The devtools toolbox, needed to get the devtools main window.
- * @param {InspectorPanel} inspector
- *        The inspector panel, needed for the eyedropper.
- */
-function SwatchColorPickerTooltip(toolbox, inspector) {
-  let stylesheet = "chrome://devtools/content/shared/widgets/spectrum.css";
-  SwatchBasedEditorTooltip.call(this, toolbox, stylesheet);
-
-  this.inspector = inspector;
-
-  // Creating a spectrum instance. this.spectrum will always be a promise that
-  // resolves to the spectrum instance
-  this.spectrum = this.setColorPickerContent([0, 0, 0, 1]);
-  this._onSpectrumColorChange = this._onSpectrumColorChange.bind(this);
-  this._openEyeDropper = this._openEyeDropper.bind(this);
-}
-
-module.exports.SwatchColorPickerTooltip = SwatchColorPickerTooltip;
-
-SwatchColorPickerTooltip.prototype =
-Heritage.extend(SwatchBasedEditorTooltip.prototype, {
-  /**
-   * Fill the tooltip with a new instance of the spectrum color picker widget
-   * initialized with the given color, and return the instance of spectrum
-   */
-  setColorPickerContent: function (color) {
-    let { doc } = this.tooltip;
-
-    let container = doc.createElementNS(XHTML_NS, "div");
-    container.id = "spectrum-tooltip";
-    let spectrumNode = doc.createElementNS(XHTML_NS, "div");
-    spectrumNode.id = "spectrum";
-    container.appendChild(spectrumNode);
-    let eyedropper = doc.createElementNS(XHTML_NS, "button");
-    eyedropper.id = "eyedropper-button";
-    eyedropper.className = "devtools-button";
-    container.appendChild(eyedropper);
-
-    this.tooltip.setContent(container, { width: 218, height: 224 });
-
-    let spectrum = new Spectrum(spectrumNode, color);
-
-    // Wait for the tooltip to be shown before calling spectrum.show
-    // as it expect to be visible in order to compute DOM element sizes.
-    this.tooltip.once("shown", () => {
-      spectrum.show();
-    });
-
-    return spectrum;
-  },
-
-  /**
-   * Overriding the SwatchBasedEditorTooltip.show function to set spectrum's
-   * color.
-   */
-  show: Task.async(function* () {
-    // Call then parent class' show function
-    yield SwatchBasedEditorTooltip.prototype.show.call(this);
-    // Then set spectrum's color and listen to color changes to preview them
-    if (this.activeSwatch) {
-      this.currentSwatchColor = this.activeSwatch.nextSibling;
-      this._originalColor = this.currentSwatchColor.textContent;
-      let color = this.activeSwatch.style.backgroundColor;
-      this.spectrum.off("changed", this._onSpectrumColorChange);
-      this.spectrum.rgb = this._colorToRgba(color);
-      this.spectrum.on("changed", this._onSpectrumColorChange);
-      this.spectrum.updateUI();
-    }
-
-    let {target} = this.inspector;
-    target.actorHasMethod("inspector", "pickColorFromPage").then(value => {
-      let tooltipDoc = this.tooltip.doc;
-      let eyeButton = tooltipDoc.querySelector("#eyedropper-button");
-      if (value && this.inspector.selection.nodeFront.isInHTMLDocument) {
-        eyeButton.addEventListener("click", this._openEyeDropper);
-      } else {
-        eyeButton.style.display = "none";
-      }
-      this.emit("ready");
-    }, e => console.error(e));
-  }),
-
-  _onSpectrumColorChange: function (event, rgba, cssColor) {
-    this._selectColor(cssColor);
-  },
-
-  _selectColor: function (color) {
-    if (this.activeSwatch) {
-      this.activeSwatch.style.backgroundColor = color;
-      this.activeSwatch.parentNode.dataset.color = color;
-
-      color = this._toDefaultType(color);
-      this.currentSwatchColor.textContent = color;
-      this.preview(color);
-
-      if (this.eyedropperOpen) {
-        this.commit();
-      }
-    }
-  },
-
-  _openEyeDropper: function () {
-    let {inspector, toolbox, telemetry} = this.inspector;
-    telemetry.toolOpened("pickereyedropper");
-    inspector.pickColorFromPage(toolbox, {copyOnSelect: false}).then(() => {
-      this.eyedropperOpen = true;
-
-      // close the colorpicker tooltip so that only the eyedropper is open.
-      this.hide();
-
-      this.tooltip.emit("eyedropper-opened");
-    }, e => console.error(e));
-
-    inspector.once("color-picked", color => {
-      toolbox.win.focus();
-      this._selectColor(color);
-      this._onEyeDropperDone();
-    });
-
-    inspector.once("color-pick-canceled", () => {
-      this._onEyeDropperDone();
-    });
-  },
-
-  _onEyeDropperDone: function () {
-    this.eyedropperOpen = false;
-    this.activeSwatch = null;
-  },
-
-  _colorToRgba: function (color) {
-    color = new colorUtils.CssColor(color);
-    let rgba = color._getRGBATuple();
-    return [rgba.r, rgba.g, rgba.b, rgba.a];
-  },
-
-  _toDefaultType: function (color) {
-    let colorObj = new colorUtils.CssColor(color);
-    colorObj.setAuthoredUnitFromColor(this._originalColor);
-    return colorObj.toString();
-  },
-
-  destroy: function () {
-    SwatchBasedEditorTooltip.prototype.destroy.call(this);
-    this.inspector = null;
-    this.currentSwatchColor = null;
-    this.spectrum.off("changed", this._onSpectrumColorChange);
-    this.spectrum.destroy();
-  }
-});
-
-/**
- * The swatch cubic-bezier tooltip class is a specific class meant to be used
- * along with rule-view's generated cubic-bezier swatches.
- * It extends the parent SwatchBasedEditorTooltip class.
- * It just wraps a standard Tooltip and sets its content with an instance of a
- * CubicBezierWidget.
- *
- * @param {Toolbox} toolbox
- *        The devtools toolbox, needed to get the devtools main window.
- */
-function SwatchCubicBezierTooltip(toolbox) {
-  let stylesheet = "chrome://devtools/content/shared/widgets/cubic-bezier.css";
-  SwatchBasedEditorTooltip.call(this, toolbox, stylesheet);
-
-  // Creating a cubic-bezier instance.
-  // this.widget will always be a promise that resolves to the widget instance
-  this.widget = this.setCubicBezierContent([0, 0, 1, 1]);
-  this._onUpdate = this._onUpdate.bind(this);
-}
-
-module.exports.SwatchCubicBezierTooltip = SwatchCubicBezierTooltip;
-
-SwatchCubicBezierTooltip.prototype =
-Heritage.extend(SwatchBasedEditorTooltip.prototype, {
-  /**
-   * Fill the tooltip with a new instance of the cubic-bezier widget
-   * initialized with the given value, and return a promise that resolves to
-   * the instance of the widget
-   */
-  setCubicBezierContent: function (bezier) {
-    let { doc } = this.tooltip;
-
-    let container = doc.createElementNS(XHTML_NS, "div");
-    container.className = "cubic-bezier-container";
-
-    this.tooltip.setContent(container, { width: 510, height: 370 });
-
-    let def = defer();
-
-    // Wait for the tooltip to be shown before calling instanciating the widget
-    // as it expect its DOM elements to be visible.
-    this.tooltip.once("shown", () => {
-      let widget = new CubicBezierWidget(container, bezier);
-      def.resolve(widget);
-    });
-
-    return def.promise;
-  },
-
-  /**
-   * Overriding the SwatchBasedEditorTooltip.show function to set the cubic
-   * bezier curve in the widget
-   */
-  show: Task.async(function* () {
-    // Call the parent class' show function
-    yield SwatchBasedEditorTooltip.prototype.show.call(this);
-    // Then set the curve and listen to changes to preview them
-    if (this.activeSwatch) {
-      this.currentBezierValue = this.activeSwatch.nextSibling;
-      this.widget.then(widget => {
-        widget.off("updated", this._onUpdate);
-        widget.cssCubicBezierValue = this.currentBezierValue.textContent;
-        widget.on("updated", this._onUpdate);
-        this.emit("ready");
-      });
-    }
-  }),
-
-  _onUpdate: function (event, bezier) {
-    if (!this.activeSwatch) {
-      return;
-    }
-
-    this.currentBezierValue.textContent = bezier + "";
-    this.preview(bezier + "");
-  },
-
-  destroy: function () {
-    SwatchBasedEditorTooltip.prototype.destroy.call(this);
-    this.currentBezierValue = null;
-    this.widget.then(widget => {
-      widget.off("updated", this._onUpdate);
-      widget.destroy();
-    });
-  }
-});
-
-/**
- * The swatch-based css filter tooltip class is a specific class meant to be
- * used along with rule-view's generated css filter swatches.
- * It extends the parent SwatchBasedEditorTooltip class.
- * It just wraps a standard Tooltip and sets its content with an instance of a
- * CSSFilterEditorWidget.
- *
- * @param {Toolbox} toolbox
- *        The devtools toolbox, needed to get the devtools main window.
- * @param {function} cssIsValid
- *        A function to check that css declaration's name and values are valid together.
- *        This can be obtained from "shared/fronts/css-properties.js".
- */
-function SwatchFilterTooltip(toolbox, cssIsValid) {
-  let stylesheet = "chrome://devtools/content/shared/widgets/filter-widget.css";
-  SwatchBasedEditorTooltip.call(this, toolbox, stylesheet);
-  this._cssIsValid = cssIsValid;
-
-  // Creating a filter editor instance.
-  this.widget = this.setFilterContent("none");
-  this._onUpdate = this._onUpdate.bind(this);
-}
-
-exports.SwatchFilterTooltip = SwatchFilterTooltip;
-
-SwatchFilterTooltip.prototype =
-Heritage.extend(SwatchBasedEditorTooltip.prototype, {
-  /**
-   * Fill the tooltip with a new instance of the CSSFilterEditorWidget
-   * widget initialized with the given filter value, and return a promise
-   * that resolves to the instance of the widget when ready.
-   */
-  setFilterContent: function (filter) {
-    let { doc } = this.tooltip;
-
-    let container = doc.createElementNS(XHTML_NS, "div");
-    container.id = "filter-container";
-
-    this.tooltip.setContent(container, { width: 510, height: 200 });
-
-    return new CSSFilterEditorWidget(container, filter, this._cssIsValid);
-  },
-
-  show: Task.async(function* () {
-    // Call the parent class' show function
-    yield SwatchBasedEditorTooltip.prototype.show.call(this);
-    // Then set the filter value and listen to changes to preview them
-    if (this.activeSwatch) {
-      this.currentFilterValue = this.activeSwatch.nextSibling;
-      this.widget.off("updated", this._onUpdate);
-      this.widget.on("updated", this._onUpdate);
-      this.widget.setCssValue(this.currentFilterValue.textContent);
-      this.widget.render();
-      this.emit("ready");
-    }
-  }),
-
-  _onUpdate: function (event, filters) {
-    if (!this.activeSwatch) {
-      return;
-    }
-
-    // Remove the old children and reparse the property value to
-    // recompute them.
-    while (this.currentFilterValue.firstChild) {
-      this.currentFilterValue.firstChild.remove();
-    }
-    let node = this._parser.parseCssProperty("filter", filters, this._options);
-    this.currentFilterValue.appendChild(node);
-
-    this.preview();
-  },
-
-  destroy: function () {
-    SwatchBasedEditorTooltip.prototype.destroy.call(this);
-    this.currentFilterValue = null;
-    this.widget.off("updated", this._onUpdate);
-    this.widget.destroy();
-  },
-
-  /**
-   * Like SwatchBasedEditorTooltip.addSwatch, but accepts a parser object
-   * to use when previewing the updated property value.
-   *
-   * @param {node} swatchEl
-   *        @see SwatchBasedEditorTooltip.addSwatch
-   * @param {object} callbacks
-   *        @see SwatchBasedEditorTooltip.addSwatch
-   * @param {object} parser
-   *        A parser object; @see OutputParser object
-   * @param {object} options
-   *        options to pass to the output parser, with
-   *          the option |filterSwatch| set.
-   */
-  addSwatch: function (swatchEl, callbacks, parser, options) {
-    SwatchBasedEditorTooltip.prototype.addSwatch.call(this, swatchEl,
-                                                      callbacks);
-    this._parser = parser;
-    this._options = options;
-  }
-});
+module.exports = Tooltip;
--- a/devtools/client/shared/widgets/tooltip/CssDocsTooltip.js
+++ b/devtools/client/shared/widgets/tooltip/CssDocsTooltip.js
@@ -32,18 +32,16 @@ function CssDocsTooltip(toolbox) {
 
   // Initialize keyboard shortcuts
   this.shortcuts = new KeyShortcuts({ window: toolbox.win });
   this._onShortcut = this._onShortcut.bind(this);
 
   this.shortcuts.on("Escape", this._onShortcut);
 }
 
-module.exports.CssDocsTooltip = CssDocsTooltip;
-
 CssDocsTooltip.prototype = {
   /**
    * Load CSS docs for the given property,
    * then display the tooltip.
    */
   show: function (anchor, propertyName) {
     this.tooltip.once("shown", () => {
       this.widget.loadCssDocs(propertyName);
@@ -86,8 +84,10 @@ CssDocsTooltip.prototype = {
   destroy: function () {
     this.widget.off("visitlink", this._onVisitLink);
     this.widget.destroy();
 
     this.shortcuts.destroy();
     this.tooltip.destroy();
   }
 };
+
+module.exports = CssDocsTooltip;
copy from devtools/client/shared/widgets/Tooltip.js
copy to devtools/client/shared/widgets/tooltip/SwatchBasedEditorTooltip.js
--- a/devtools/client/shared/widgets/Tooltip.js
+++ b/devtools/client/shared/widgets/tooltip/SwatchBasedEditorTooltip.js
@@ -1,477 +1,17 @@
 /* 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/. */
 
 "use strict";
 
-const defer = require("devtools/shared/defer");
-const {Spectrum} = require("devtools/client/shared/widgets/Spectrum");
-const {CubicBezierWidget} =
-      require("devtools/client/shared/widgets/CubicBezierWidget");
-const {CSSFilterEditorWidget} = require("devtools/client/shared/widgets/FilterWidget");
-const {TooltipToggle} = require("devtools/client/shared/widgets/tooltip/TooltipToggle");
 const EventEmitter = require("devtools/shared/event-emitter");
-const {colorUtils} = require("devtools/shared/css/color");
-const Heritage = require("sdk/core/heritage");
-const {HTMLTooltip} = require("devtools/client/shared/widgets/HTMLTooltip");
 const {KeyShortcuts} = require("devtools/client/shared/key-shortcuts");
-const {Task} = require("devtools/shared/task");
-const {KeyCodes} = require("devtools/client/shared/keycodes");
-
-const XHTML_NS = "http://www.w3.org/1999/xhtml";
-const ESCAPE_KEYCODE = KeyCodes.DOM_VK_ESCAPE;
-const POPUP_EVENTS = ["shown", "hidden", "showing", "hiding"];
-
-/**
- * Tooltip widget.
- *
- * This widget is intended at any tool that may need to show rich content in the
- * form of floating panels.
- * A common use case is image previewing in the CSS rule view, but more complex
- * use cases may include color pickers, object inspection, etc...
- *
- * Tooltips are based on XUL (namely XUL arrow-type <panel>s), and therefore
- * need a XUL Document to live in.
- * This is pretty much the only requirement they have on their environment.
- *
- * The way to use a tooltip is simply by instantiating a tooltip yourself and
- * attaching some content in it, or using one of the ready-made content types.
- *
- * A convenient `startTogglingOnHover` method may avoid having to register event
- * handlers yourself if the tooltip has to be shown when hovering over a
- * specific element or group of elements (which is usually the most common case)
- */
-
-/**
- * Container used for dealing with optional parameters.
- *
- * @param {Object} defaults
- *        An object with all default options {p1: v1, p2: v2, ...}
- * @param {Object} options
- *        The actual values.
- */
-function OptionsStore(defaults, options) {
-  this.defaults = defaults || {};
-  this.options = options || {};
-}
-
-OptionsStore.prototype = {
-  /**
-   * Get the value for a given option name.
-   * @return {Object} Returns the value for that option, coming either for the
-   *         actual values that have been set in the constructor, or from the
-   *         defaults if that options was not specified.
-   */
-  get: function (name) {
-    if (typeof this.options[name] !== "undefined") {
-      return this.options[name];
-    }
-    return this.defaults[name];
-  }
-};
-
-/**
- * The low level structure of a tooltip is a XUL element (a <panel>).
- */
-var PanelFactory = {
-  /**
-   * Get a new XUL panel instance.
-   * @param {XULDocument} doc
-   *        The XUL document to put that panel into
-   * @param {OptionsStore} options
-   *        An options store to get some configuration from
-   */
-  get: function (doc, options) {
-    // Create the tooltip
-    let panel = doc.createElement("panel");
-    panel.setAttribute("hidden", true);
-    panel.setAttribute("ignorekeys", true);
-    panel.setAttribute("animate", false);
-
-    panel.setAttribute("consumeoutsideclicks",
-                       options.get("consumeOutsideClick"));
-    panel.setAttribute("noautofocus", options.get("noAutoFocus"));
-    panel.setAttribute("type", "arrow");
-    panel.setAttribute("level", "top");
-
-    panel.setAttribute("class", "devtools-tooltip theme-tooltip-panel");
-    doc.querySelector("window").appendChild(panel);
-
-    return panel;
-  }
-};
-
-/**
- * Tooltip class.
- *
- * Basic usage:
- *   let t = new Tooltip(xulDoc);
- *   t.content = someXulContent;
- *   t.show();
- *   t.hide();
- *   t.destroy();
- *
- * Better usage:
- *   let t = new Tooltip(xulDoc);
- *   t.startTogglingOnHover(container, target => {
- *     if (<condition based on target>) {
- *       t.content = el;
- *       return true;
- *     }
- *   });
- *   t.destroy();
- *
- * @param {XULDocument} doc
- *        The XUL document hosting this tooltip
- * @param {Object} options
- *        Optional options that give options to consumers:
- *        - consumeOutsideClick {Boolean} Wether the first click outside of the
- *        tooltip should close the tooltip and be consumed or not.
- *        Defaults to false.
- *        - closeOnKeys {Array} An array of key codes that should close the
- *        tooltip. Defaults to [27] (escape key).
- *        - closeOnEvents [{emitter: {Object}, event: {String},
- *                          useCapture: {Boolean}}]
- *        Provide an optional list of emitter objects and event names here to
- *        trigger the closing of the tooltip when these events are fired by the
- *        emitters. The emitter objects should either implement
- *        on/off(event, cb) or addEventListener/removeEventListener(event, cb).
- *        Defaults to [].
- *        For instance, the following would close the tooltip whenever the
- *        toolbox selects a new tool and when a DOM node gets scrolled:
- *        new Tooltip(doc, {
- *          closeOnEvents: [
- *            {emitter: toolbox, event: "select"},
- *            {emitter: myContainer, event: "scroll", useCapture: true}
- *          ]
- *        });
- *        - noAutoFocus {Boolean} Should the focus automatically go to the panel
- *        when it opens. Defaults to true.
- *
- * Fires these events:
- * - showing : just before the tooltip shows
- * - shown : when the tooltip is shown
- * - hiding : just before the tooltip closes
- * - hidden : when the tooltip gets hidden
- * - keypress : when any key gets pressed, with keyCode
- */
-function Tooltip(doc, options) {
-  EventEmitter.decorate(this);
-
-  this.doc = doc;
-  this.options = new OptionsStore({
-    consumeOutsideClick: false,
-    closeOnKeys: [ESCAPE_KEYCODE],
-    noAutoFocus: true,
-    closeOnEvents: []
-  }, options);
-  this.panel = PanelFactory.get(doc, this.options);
-
-  // Create tooltip toggle helper and decorate the Tooltip instance with
-  // shortcut methods.
-  this._toggle = new TooltipToggle(this);
-  this.startTogglingOnHover = this._toggle.start.bind(this._toggle);
-  this.stopTogglingOnHover = this._toggle.stop.bind(this._toggle);
-
-  // Emit show/hide events when the panel does.
-  for (let eventName of POPUP_EVENTS) {
-    this["_onPopup" + eventName] = (name => {
-      return e => {
-        if (e.target === this.panel) {
-          this.emit(name);
-        }
-      };
-    })(eventName);
-    this.panel.addEventListener("popup" + eventName,
-      this["_onPopup" + eventName], false);
-  }
-
-  // Listen to keypress events to close the tooltip if configured to do so
-  let win = this.doc.querySelector("window");
-  this._onKeyPress = event => {
-    if (this.panel.hidden) {
-      return;
-    }
-
-    this.emit("keypress", event.keyCode);
-    if (this.options.get("closeOnKeys").indexOf(event.keyCode) !== -1 &&
-        this.isShown()) {
-      event.stopPropagation();
-      this.hide();
-    }
-  };
-  win.addEventListener("keypress", this._onKeyPress, false);
-
-  // Listen to custom emitters' events to close the tooltip
-  this.hide = this.hide.bind(this);
-  let closeOnEvents = this.options.get("closeOnEvents");
-  for (let {emitter, event, useCapture} of closeOnEvents) {
-    for (let add of ["addEventListener", "on"]) {
-      if (add in emitter) {
-        emitter[add](event, this.hide, useCapture);
-        break;
-      }
-    }
-  }
-}
-
-module.exports.Tooltip = Tooltip;
-
-Tooltip.prototype = {
-  defaultPosition: "before_start",
-  // px
-  defaultOffsetX: 0,
-  // px
-  defaultOffsetY: 0,
-  // px
-
-  /**
-   * Show the tooltip. It might be wise to append some content first if you
-   * don't want the tooltip to be empty. You may access the content of the
-   * tooltip by setting a XUL node to t.content.
-   * @param {node} anchor
-   *        Which node should the tooltip be shown on
-   * @param {string} position [optional]
-   *        Optional tooltip position. Defaults to before_start
-   *        https://developer.mozilla.org/en-US/docs/XUL/PopupGuide/Positioning
-   * @param {number} x, y [optional]
-   *        The left and top offset coordinates, in pixels.
-   */
-  show: function (anchor,
-    position = this.defaultPosition,
-    x = this.defaultOffsetX,
-    y = this.defaultOffsetY) {
-    this.panel.hidden = false;
-    this.panel.openPopup(anchor, position, x, y);
-  },
-
-  /**
-   * Hide the tooltip
-   */
-  hide: function () {
-    this.panel.hidden = true;
-    this.panel.hidePopup();
-  },
-
-  isShown: function () {
-    return this.panel &&
-           this.panel.state !== "closed" &&
-           this.panel.state !== "hiding";
-  },
-
-  setSize: function (width, height) {
-    this.panel.sizeTo(width, height);
-  },
-
-  /**
-   * Empty the tooltip's content
-   */
-  empty: function () {
-    while (this.panel.hasChildNodes()) {
-      this.panel.removeChild(this.panel.firstChild);
-    }
-  },
-
-  /**
-   * Gets this panel's visibility state.
-   * @return boolean
-   */
-  isHidden: function () {
-    return this.panel.state == "closed" || this.panel.state == "hiding";
-  },
-
-  /**
-   * Gets if this panel has any child nodes.
-   * @return boolean
-   */
-  isEmpty: function () {
-    return !this.panel.hasChildNodes();
-  },
-
-  /**
-   * Get rid of references and event listeners
-   */
-  destroy: function () {
-    this.hide();
-
-    for (let eventName of POPUP_EVENTS) {
-      this.panel.removeEventListener("popup" + eventName,
-        this["_onPopup" + eventName], false);
-    }
-
-    let win = this.doc.querySelector("window");
-    win.removeEventListener("keypress", this._onKeyPress, false);
-
-    let closeOnEvents = this.options.get("closeOnEvents");
-    for (let {emitter, event, useCapture} of closeOnEvents) {
-      for (let remove of ["removeEventListener", "off"]) {
-        if (remove in emitter) {
-          emitter[remove](event, this.hide, useCapture);
-          break;
-        }
-      }
-    }
-
-    this.content = null;
-
-    this._toggle.destroy();
-
-    this.doc = null;
-
-    this.panel.remove();
-    this.panel = null;
-  },
-
-  /**
-   * Returns the outer container node (that includes the arrow etc.). Happens
-   * to be identical to this.panel here, can be different element in other
-   * Tooltip implementations.
-   */
-  get container() {
-    return this.panel;
-  },
-
-  /**
-   * Set the content of this tooltip. Will first empty the tooltip and then
-   * append the new content element.
-   * Consider using one of the set<type>Content() functions instead.
-   * @param {node} content
-   *        A node that can be appended in the tooltip XUL element
-   */
-  set content(content) {
-    if (this.content == content) {
-      return;
-    }
-
-    this.empty();
-    this.panel.removeAttribute("clamped-dimensions");
-    this.panel.removeAttribute("clamped-dimensions-no-min-height");
-    this.panel.removeAttribute("clamped-dimensions-no-max-or-min-height");
-    this.panel.removeAttribute("wide");
-
-    if (content) {
-      this.panel.appendChild(content);
-    }
-  },
-
-  get content() {
-    return this.panel.firstChild;
-  },
-
-  /**
-   * Sets some text as the content of this tooltip.
-   *
-   * @param {array} messages
-   *        A list of text messages.
-   * @param {string} messagesClass [optional]
-   *        A style class for the text messages.
-   * @param {string} containerClass [optional]
-   *        A style class for the text messages container.
-   * @param {boolean} isAlertTooltip [optional]
-   *        Pass true to add an alert image for your tooltip.
-   */
-  setTextContent: function (
-    {
-      messages,
-      messagesClass,
-      containerClass,
-      isAlertTooltip
-    },
-    extraButtons = []) {
-    messagesClass = messagesClass || "default-tooltip-simple-text-colors";
-    containerClass = containerClass || "default-tooltip-simple-text-colors";
-
-    let vbox = this.doc.createElement("vbox");
-    vbox.className = "devtools-tooltip-simple-text-container " + containerClass;
-    vbox.setAttribute("flex", "1");
-
-    for (let text of messages) {
-      let description = this.doc.createElement("description");
-      description.setAttribute("flex", "1");
-      description.className = "devtools-tooltip-simple-text " + messagesClass;
-      description.textContent = text;
-      vbox.appendChild(description);
-    }
-
-    for (let { label, className, command } of extraButtons) {
-      let button = this.doc.createElement("button");
-      button.className = className;
-      button.setAttribute("label", label);
-      button.addEventListener("command", command);
-      vbox.appendChild(button);
-    }
-
-    if (isAlertTooltip) {
-      let hbox = this.doc.createElement("hbox");
-      hbox.setAttribute("align", "start");
-
-      let alertImg = this.doc.createElement("image");
-      alertImg.className = "devtools-tooltip-alert-icon";
-      hbox.appendChild(alertImg);
-      hbox.appendChild(vbox);
-      this.content = hbox;
-    } else {
-      this.content = vbox;
-    }
-  },
-
-  /**
-   * Load a document into an iframe, and set the iframe
-   * to be the tooltip's content.
-   *
-   * Used by tooltips that want to load their interface
-   * into an iframe from a URL.
-   *
-   * @param {string} width
-   *        Width of the iframe.
-   * @param {string} height
-   *        Height of the iframe.
-   * @param {string} url
-   *        URL of the document to load into the iframe.
-   *
-   * @return {promise} A promise which is resolved with
-   * the iframe.
-   *
-   * This function creates an iframe, loads the specified document
-   * into it, sets the tooltip's content to the iframe, and returns
-   * a promise.
-   *
-   * When the document is loaded, the function gets the content window
-   * and resolves the promise with the content window.
-   */
-  setIFrameContent: function ({width, height}, url) {
-    let def = defer();
-
-    // Create an iframe
-    let iframe = this.doc.createElementNS(XHTML_NS, "iframe");
-    iframe.setAttribute("transparent", true);
-    iframe.setAttribute("width", width);
-    iframe.setAttribute("height", height);
-    iframe.setAttribute("flex", "1");
-    iframe.setAttribute("tooltip", "aHTMLTooltip");
-    iframe.setAttribute("class", "devtools-tooltip-iframe");
-
-    // Wait for the load to initialize the widget
-    function onLoad() {
-      iframe.removeEventListener("load", onLoad, true);
-      def.resolve(iframe);
-    }
-    iframe.addEventListener("load", onLoad, true);
-
-    // load the document from url into the iframe
-    iframe.setAttribute("src", url);
-
-    // Put the iframe in the tooltip
-    this.content = iframe;
-
-    return def.promise;
-  }
-};
+const {HTMLTooltip} = require("devtools/client/shared/widgets/HTMLTooltip");
 
 /**
  * Base class for all (color, gradient, ...)-swatch based value editors inside
  * tooltips
  *
  * @param {Toolbox} toolbox
  *        The devtools toolbox, needed to get the devtools main window.
  */
@@ -659,354 +199,9 @@ SwatchBasedEditorTooltip.prototype = {
     this.swatches.clear();
     this.activeSwatch = null;
     this.tooltip.off("keypress", this._onTooltipKeypress);
     this.tooltip.destroy();
     this.shortcuts.destroy();
   }
 };
 
-/**
- * The swatch color picker tooltip class is a specific class meant to be used
- * along with output-parser's generated color swatches.
- * It extends the parent SwatchBasedEditorTooltip class.
- * It just wraps a standard Tooltip and sets its content with an instance of a
- * color picker.
- *
- * @param {Toolbox} toolbox
- *        The devtools toolbox, needed to get the devtools main window.
- * @param {InspectorPanel} inspector
- *        The inspector panel, needed for the eyedropper.
- */
-function SwatchColorPickerTooltip(toolbox, inspector) {
-  let stylesheet = "chrome://devtools/content/shared/widgets/spectrum.css";
-  SwatchBasedEditorTooltip.call(this, toolbox, stylesheet);
-
-  this.inspector = inspector;
-
-  // Creating a spectrum instance. this.spectrum will always be a promise that
-  // resolves to the spectrum instance
-  this.spectrum = this.setColorPickerContent([0, 0, 0, 1]);
-  this._onSpectrumColorChange = this._onSpectrumColorChange.bind(this);
-  this._openEyeDropper = this._openEyeDropper.bind(this);
-}
-
-module.exports.SwatchColorPickerTooltip = SwatchColorPickerTooltip;
-
-SwatchColorPickerTooltip.prototype =
-Heritage.extend(SwatchBasedEditorTooltip.prototype, {
-  /**
-   * Fill the tooltip with a new instance of the spectrum color picker widget
-   * initialized with the given color, and return the instance of spectrum
-   */
-  setColorPickerContent: function (color) {
-    let { doc } = this.tooltip;
-
-    let container = doc.createElementNS(XHTML_NS, "div");
-    container.id = "spectrum-tooltip";
-    let spectrumNode = doc.createElementNS(XHTML_NS, "div");
-    spectrumNode.id = "spectrum";
-    container.appendChild(spectrumNode);
-    let eyedropper = doc.createElementNS(XHTML_NS, "button");
-    eyedropper.id = "eyedropper-button";
-    eyedropper.className = "devtools-button";
-    container.appendChild(eyedropper);
-
-    this.tooltip.setContent(container, { width: 218, height: 224 });
-
-    let spectrum = new Spectrum(spectrumNode, color);
-
-    // Wait for the tooltip to be shown before calling spectrum.show
-    // as it expect to be visible in order to compute DOM element sizes.
-    this.tooltip.once("shown", () => {
-      spectrum.show();
-    });
-
-    return spectrum;
-  },
-
-  /**
-   * Overriding the SwatchBasedEditorTooltip.show function to set spectrum's
-   * color.
-   */
-  show: Task.async(function* () {
-    // Call then parent class' show function
-    yield SwatchBasedEditorTooltip.prototype.show.call(this);
-    // Then set spectrum's color and listen to color changes to preview them
-    if (this.activeSwatch) {
-      this.currentSwatchColor = this.activeSwatch.nextSibling;
-      this._originalColor = this.currentSwatchColor.textContent;
-      let color = this.activeSwatch.style.backgroundColor;
-      this.spectrum.off("changed", this._onSpectrumColorChange);
-      this.spectrum.rgb = this._colorToRgba(color);
-      this.spectrum.on("changed", this._onSpectrumColorChange);
-      this.spectrum.updateUI();
-    }
-
-    let {target} = this.inspector;
-    target.actorHasMethod("inspector", "pickColorFromPage").then(value => {
-      let tooltipDoc = this.tooltip.doc;
-      let eyeButton = tooltipDoc.querySelector("#eyedropper-button");
-      if (value && this.inspector.selection.nodeFront.isInHTMLDocument) {
-        eyeButton.addEventListener("click", this._openEyeDropper);
-      } else {
-        eyeButton.style.display = "none";
-      }
-      this.emit("ready");
-    }, e => console.error(e));
-  }),
-
-  _onSpectrumColorChange: function (event, rgba, cssColor) {
-    this._selectColor(cssColor);
-  },
-
-  _selectColor: function (color) {
-    if (this.activeSwatch) {
-      this.activeSwatch.style.backgroundColor = color;
-      this.activeSwatch.parentNode.dataset.color = color;
-
-      color = this._toDefaultType(color);
-      this.currentSwatchColor.textContent = color;
-      this.preview(color);
-
-      if (this.eyedropperOpen) {
-        this.commit();
-      }
-    }
-  },
-
-  _openEyeDropper: function () {
-    let {inspector, toolbox, telemetry} = this.inspector;
-    telemetry.toolOpened("pickereyedropper");
-    inspector.pickColorFromPage(toolbox, {copyOnSelect: false}).then(() => {
-      this.eyedropperOpen = true;
-
-      // close the colorpicker tooltip so that only the eyedropper is open.
-      this.hide();
-
-      this.tooltip.emit("eyedropper-opened");
-    }, e => console.error(e));
-
-    inspector.once("color-picked", color => {
-      toolbox.win.focus();
-      this._selectColor(color);
-      this._onEyeDropperDone();
-    });
-
-    inspector.once("color-pick-canceled", () => {
-      this._onEyeDropperDone();
-    });
-  },
-
-  _onEyeDropperDone: function () {
-    this.eyedropperOpen = false;
-    this.activeSwatch = null;
-  },
-
-  _colorToRgba: function (color) {
-    color = new colorUtils.CssColor(color);
-    let rgba = color._getRGBATuple();
-    return [rgba.r, rgba.g, rgba.b, rgba.a];
-  },
-
-  _toDefaultType: function (color) {
-    let colorObj = new colorUtils.CssColor(color);
-    colorObj.setAuthoredUnitFromColor(this._originalColor);
-    return colorObj.toString();
-  },
-
-  destroy: function () {
-    SwatchBasedEditorTooltip.prototype.destroy.call(this);
-    this.inspector = null;
-    this.currentSwatchColor = null;
-    this.spectrum.off("changed", this._onSpectrumColorChange);
-    this.spectrum.destroy();
-  }
-});
-
-/**
- * The swatch cubic-bezier tooltip class is a specific class meant to be used
- * along with rule-view's generated cubic-bezier swatches.
- * It extends the parent SwatchBasedEditorTooltip class.
- * It just wraps a standard Tooltip and sets its content with an instance of a
- * CubicBezierWidget.
- *
- * @param {Toolbox} toolbox
- *        The devtools toolbox, needed to get the devtools main window.
- */
-function SwatchCubicBezierTooltip(toolbox) {
-  let stylesheet = "chrome://devtools/content/shared/widgets/cubic-bezier.css";
-  SwatchBasedEditorTooltip.call(this, toolbox, stylesheet);
-
-  // Creating a cubic-bezier instance.
-  // this.widget will always be a promise that resolves to the widget instance
-  this.widget = this.setCubicBezierContent([0, 0, 1, 1]);
-  this._onUpdate = this._onUpdate.bind(this);
-}
-
-module.exports.SwatchCubicBezierTooltip = SwatchCubicBezierTooltip;
-
-SwatchCubicBezierTooltip.prototype =
-Heritage.extend(SwatchBasedEditorTooltip.prototype, {
-  /**
-   * Fill the tooltip with a new instance of the cubic-bezier widget
-   * initialized with the given value, and return a promise that resolves to
-   * the instance of the widget
-   */
-  setCubicBezierContent: function (bezier) {
-    let { doc } = this.tooltip;
-
-    let container = doc.createElementNS(XHTML_NS, "div");
-    container.className = "cubic-bezier-container";
-
-    this.tooltip.setContent(container, { width: 510, height: 370 });
-
-    let def = defer();
-
-    // Wait for the tooltip to be shown before calling instanciating the widget
-    // as it expect its DOM elements to be visible.
-    this.tooltip.once("shown", () => {
-      let widget = new CubicBezierWidget(container, bezier);
-      def.resolve(widget);
-    });
-
-    return def.promise;
-  },
-
-  /**
-   * Overriding the SwatchBasedEditorTooltip.show function to set the cubic
-   * bezier curve in the widget
-   */
-  show: Task.async(function* () {
-    // Call the parent class' show function
-    yield SwatchBasedEditorTooltip.prototype.show.call(this);
-    // Then set the curve and listen to changes to preview them
-    if (this.activeSwatch) {
-      this.currentBezierValue = this.activeSwatch.nextSibling;
-      this.widget.then(widget => {
-        widget.off("updated", this._onUpdate);
-        widget.cssCubicBezierValue = this.currentBezierValue.textContent;
-        widget.on("updated", this._onUpdate);
-        this.emit("ready");
-      });
-    }
-  }),
-
-  _onUpdate: function (event, bezier) {
-    if (!this.activeSwatch) {
-      return;
-    }
-
-    this.currentBezierValue.textContent = bezier + "";
-    this.preview(bezier + "");
-  },
-
-  destroy: function () {
-    SwatchBasedEditorTooltip.prototype.destroy.call(this);
-    this.currentBezierValue = null;
-    this.widget.then(widget => {
-      widget.off("updated", this._onUpdate);
-      widget.destroy();
-    });
-  }
-});
-
-/**
- * The swatch-based css filter tooltip class is a specific class meant to be
- * used along with rule-view's generated css filter swatches.
- * It extends the parent SwatchBasedEditorTooltip class.
- * It just wraps a standard Tooltip and sets its content with an instance of a
- * CSSFilterEditorWidget.
- *
- * @param {Toolbox} toolbox
- *        The devtools toolbox, needed to get the devtools main window.
- * @param {function} cssIsValid
- *        A function to check that css declaration's name and values are valid together.
- *        This can be obtained from "shared/fronts/css-properties.js".
- */
-function SwatchFilterTooltip(toolbox, cssIsValid) {
-  let stylesheet = "chrome://devtools/content/shared/widgets/filter-widget.css";
-  SwatchBasedEditorTooltip.call(this, toolbox, stylesheet);
-  this._cssIsValid = cssIsValid;
-
-  // Creating a filter editor instance.
-  this.widget = this.setFilterContent("none");
-  this._onUpdate = this._onUpdate.bind(this);
-}
-
-exports.SwatchFilterTooltip = SwatchFilterTooltip;
-
-SwatchFilterTooltip.prototype =
-Heritage.extend(SwatchBasedEditorTooltip.prototype, {
-  /**
-   * Fill the tooltip with a new instance of the CSSFilterEditorWidget
-   * widget initialized with the given filter value, and return a promise
-   * that resolves to the instance of the widget when ready.
-   */
-  setFilterContent: function (filter) {
-    let { doc } = this.tooltip;
-
-    let container = doc.createElementNS(XHTML_NS, "div");
-    container.id = "filter-container";
-
-    this.tooltip.setContent(container, { width: 510, height: 200 });
-
-    return new CSSFilterEditorWidget(container, filter, this._cssIsValid);
-  },
-
-  show: Task.async(function* () {
-    // Call the parent class' show function
-    yield SwatchBasedEditorTooltip.prototype.show.call(this);
-    // Then set the filter value and listen to changes to preview them
-    if (this.activeSwatch) {
-      this.currentFilterValue = this.activeSwatch.nextSibling;
-      this.widget.off("updated", this._onUpdate);
-      this.widget.on("updated", this._onUpdate);
-      this.widget.setCssValue(this.currentFilterValue.textContent);
-      this.widget.render();
-      this.emit("ready");
-    }
-  }),
-
-  _onUpdate: function (event, filters) {
-    if (!this.activeSwatch) {
-      return;
-    }
-
-    // Remove the old children and reparse the property value to
-    // recompute them.
-    while (this.currentFilterValue.firstChild) {
-      this.currentFilterValue.firstChild.remove();
-    }
-    let node = this._parser.parseCssProperty("filter", filters, this._options);
-    this.currentFilterValue.appendChild(node);
-
-    this.preview();
-  },
-
-  destroy: function () {
-    SwatchBasedEditorTooltip.prototype.destroy.call(this);
-    this.currentFilterValue = null;
-    this.widget.off("updated", this._onUpdate);
-    this.widget.destroy();
-  },
-
-  /**
-   * Like SwatchBasedEditorTooltip.addSwatch, but accepts a parser object
-   * to use when previewing the updated property value.
-   *
-   * @param {node} swatchEl
-   *        @see SwatchBasedEditorTooltip.addSwatch
-   * @param {object} callbacks
-   *        @see SwatchBasedEditorTooltip.addSwatch
-   * @param {object} parser
-   *        A parser object; @see OutputParser object
-   * @param {object} options
-   *        options to pass to the output parser, with
-   *          the option |filterSwatch| set.
-   */
-  addSwatch: function (swatchEl, callbacks, parser, options) {
-    SwatchBasedEditorTooltip.prototype.addSwatch.call(this, swatchEl,
-                                                      callbacks);
-    this._parser = parser;
-    this._options = options;
-  }
-});
+module.exports = SwatchBasedEditorTooltip;
copy from devtools/client/shared/widgets/Tooltip.js
copy to devtools/client/shared/widgets/tooltip/SwatchColorPickerTooltip.js
--- a/devtools/client/shared/widgets/Tooltip.js
+++ b/devtools/client/shared/widgets/tooltip/SwatchColorPickerTooltip.js
@@ -1,673 +1,22 @@
 /* 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/. */
 
 "use strict";
 
-const defer = require("devtools/shared/defer");
+const {Task} = require("devtools/shared/task");
+const {colorUtils} = require("devtools/shared/css/color");
 const {Spectrum} = require("devtools/client/shared/widgets/Spectrum");
-const {CubicBezierWidget} =
-      require("devtools/client/shared/widgets/CubicBezierWidget");
-const {CSSFilterEditorWidget} = require("devtools/client/shared/widgets/FilterWidget");
-const {TooltipToggle} = require("devtools/client/shared/widgets/tooltip/TooltipToggle");
-const EventEmitter = require("devtools/shared/event-emitter");
-const {colorUtils} = require("devtools/shared/css/color");
+const SwatchBasedEditorTooltip = require("devtools/client/shared/widgets/tooltip/SwatchBasedEditorTooltip");
+
 const Heritage = require("sdk/core/heritage");
-const {HTMLTooltip} = require("devtools/client/shared/widgets/HTMLTooltip");
-const {KeyShortcuts} = require("devtools/client/shared/key-shortcuts");
-const {Task} = require("devtools/shared/task");
-const {KeyCodes} = require("devtools/client/shared/keycodes");
 
 const XHTML_NS = "http://www.w3.org/1999/xhtml";
-const ESCAPE_KEYCODE = KeyCodes.DOM_VK_ESCAPE;
-const POPUP_EVENTS = ["shown", "hidden", "showing", "hiding"];
-
-/**
- * Tooltip widget.
- *
- * This widget is intended at any tool that may need to show rich content in the
- * form of floating panels.
- * A common use case is image previewing in the CSS rule view, but more complex
- * use cases may include color pickers, object inspection, etc...
- *
- * Tooltips are based on XUL (namely XUL arrow-type <panel>s), and therefore
- * need a XUL Document to live in.
- * This is pretty much the only requirement they have on their environment.
- *
- * The way to use a tooltip is simply by instantiating a tooltip yourself and
- * attaching some content in it, or using one of the ready-made content types.
- *
- * A convenient `startTogglingOnHover` method may avoid having to register event
- * handlers yourself if the tooltip has to be shown when hovering over a
- * specific element or group of elements (which is usually the most common case)
- */
-
-/**
- * Container used for dealing with optional parameters.
- *
- * @param {Object} defaults
- *        An object with all default options {p1: v1, p2: v2, ...}
- * @param {Object} options
- *        The actual values.
- */
-function OptionsStore(defaults, options) {
-  this.defaults = defaults || {};
-  this.options = options || {};
-}
-
-OptionsStore.prototype = {
-  /**
-   * Get the value for a given option name.
-   * @return {Object} Returns the value for that option, coming either for the
-   *         actual values that have been set in the constructor, or from the
-   *         defaults if that options was not specified.
-   */
-  get: function (name) {
-    if (typeof this.options[name] !== "undefined") {
-      return this.options[name];
-    }
-    return this.defaults[name];
-  }
-};
-
-/**
- * The low level structure of a tooltip is a XUL element (a <panel>).
- */
-var PanelFactory = {
-  /**
-   * Get a new XUL panel instance.
-   * @param {XULDocument} doc
-   *        The XUL document to put that panel into
-   * @param {OptionsStore} options
-   *        An options store to get some configuration from
-   */
-  get: function (doc, options) {
-    // Create the tooltip
-    let panel = doc.createElement("panel");
-    panel.setAttribute("hidden", true);
-    panel.setAttribute("ignorekeys", true);
-    panel.setAttribute("animate", false);
-
-    panel.setAttribute("consumeoutsideclicks",
-                       options.get("consumeOutsideClick"));
-    panel.setAttribute("noautofocus", options.get("noAutoFocus"));
-    panel.setAttribute("type", "arrow");
-    panel.setAttribute("level", "top");
-
-    panel.setAttribute("class", "devtools-tooltip theme-tooltip-panel");
-    doc.querySelector("window").appendChild(panel);
-
-    return panel;
-  }
-};
-
-/**
- * Tooltip class.
- *
- * Basic usage:
- *   let t = new Tooltip(xulDoc);
- *   t.content = someXulContent;
- *   t.show();
- *   t.hide();
- *   t.destroy();
- *
- * Better usage:
- *   let t = new Tooltip(xulDoc);
- *   t.startTogglingOnHover(container, target => {
- *     if (<condition based on target>) {
- *       t.content = el;
- *       return true;
- *     }
- *   });
- *   t.destroy();
- *
- * @param {XULDocument} doc
- *        The XUL document hosting this tooltip
- * @param {Object} options
- *        Optional options that give options to consumers:
- *        - consumeOutsideClick {Boolean} Wether the first click outside of the
- *        tooltip should close the tooltip and be consumed or not.
- *        Defaults to false.
- *        - closeOnKeys {Array} An array of key codes that should close the
- *        tooltip. Defaults to [27] (escape key).
- *        - closeOnEvents [{emitter: {Object}, event: {String},
- *                          useCapture: {Boolean}}]
- *        Provide an optional list of emitter objects and event names here to
- *        trigger the closing of the tooltip when these events are fired by the
- *        emitters. The emitter objects should either implement
- *        on/off(event, cb) or addEventListener/removeEventListener(event, cb).
- *        Defaults to [].
- *        For instance, the following would close the tooltip whenever the
- *        toolbox selects a new tool and when a DOM node gets scrolled:
- *        new Tooltip(doc, {
- *          closeOnEvents: [
- *            {emitter: toolbox, event: "select"},
- *            {emitter: myContainer, event: "scroll", useCapture: true}
- *          ]
- *        });
- *        - noAutoFocus {Boolean} Should the focus automatically go to the panel
- *        when it opens. Defaults to true.
- *
- * Fires these events:
- * - showing : just before the tooltip shows
- * - shown : when the tooltip is shown
- * - hiding : just before the tooltip closes
- * - hidden : when the tooltip gets hidden
- * - keypress : when any key gets pressed, with keyCode
- */
-function Tooltip(doc, options) {
-  EventEmitter.decorate(this);
-
-  this.doc = doc;
-  this.options = new OptionsStore({
-    consumeOutsideClick: false,
-    closeOnKeys: [ESCAPE_KEYCODE],
-    noAutoFocus: true,
-    closeOnEvents: []
-  }, options);
-  this.panel = PanelFactory.get(doc, this.options);
-
-  // Create tooltip toggle helper and decorate the Tooltip instance with
-  // shortcut methods.
-  this._toggle = new TooltipToggle(this);
-  this.startTogglingOnHover = this._toggle.start.bind(this._toggle);
-  this.stopTogglingOnHover = this._toggle.stop.bind(this._toggle);
-
-  // Emit show/hide events when the panel does.
-  for (let eventName of POPUP_EVENTS) {
-    this["_onPopup" + eventName] = (name => {
-      return e => {
-        if (e.target === this.panel) {
-          this.emit(name);
-        }
-      };
-    })(eventName);
-    this.panel.addEventListener("popup" + eventName,
-      this["_onPopup" + eventName], false);
-  }
-
-  // Listen to keypress events to close the tooltip if configured to do so
-  let win = this.doc.querySelector("window");
-  this._onKeyPress = event => {
-    if (this.panel.hidden) {
-      return;
-    }
-
-    this.emit("keypress", event.keyCode);
-    if (this.options.get("closeOnKeys").indexOf(event.keyCode) !== -1 &&
-        this.isShown()) {
-      event.stopPropagation();
-      this.hide();
-    }
-  };
-  win.addEventListener("keypress", this._onKeyPress, false);
-
-  // Listen to custom emitters' events to close the tooltip
-  this.hide = this.hide.bind(this);
-  let closeOnEvents = this.options.get("closeOnEvents");
-  for (let {emitter, event, useCapture} of closeOnEvents) {
-    for (let add of ["addEventListener", "on"]) {
-      if (add in emitter) {
-        emitter[add](event, this.hide, useCapture);
-        break;
-      }
-    }
-  }
-}
-
-module.exports.Tooltip = Tooltip;
-
-Tooltip.prototype = {
-  defaultPosition: "before_start",
-  // px
-  defaultOffsetX: 0,
-  // px
-  defaultOffsetY: 0,
-  // px
-
-  /**
-   * Show the tooltip. It might be wise to append some content first if you
-   * don't want the tooltip to be empty. You may access the content of the
-   * tooltip by setting a XUL node to t.content.
-   * @param {node} anchor
-   *        Which node should the tooltip be shown on
-   * @param {string} position [optional]
-   *        Optional tooltip position. Defaults to before_start
-   *        https://developer.mozilla.org/en-US/docs/XUL/PopupGuide/Positioning
-   * @param {number} x, y [optional]
-   *        The left and top offset coordinates, in pixels.
-   */
-  show: function (anchor,
-    position = this.defaultPosition,
-    x = this.defaultOffsetX,
-    y = this.defaultOffsetY) {
-    this.panel.hidden = false;
-    this.panel.openPopup(anchor, position, x, y);
-  },
-
-  /**
-   * Hide the tooltip
-   */
-  hide: function () {
-    this.panel.hidden = true;
-    this.panel.hidePopup();
-  },
-
-  isShown: function () {
-    return this.panel &&
-           this.panel.state !== "closed" &&
-           this.panel.state !== "hiding";
-  },
-
-  setSize: function (width, height) {
-    this.panel.sizeTo(width, height);
-  },
-
-  /**
-   * Empty the tooltip's content
-   */
-  empty: function () {
-    while (this.panel.hasChildNodes()) {
-      this.panel.removeChild(this.panel.firstChild);
-    }
-  },
-
-  /**
-   * Gets this panel's visibility state.
-   * @return boolean
-   */
-  isHidden: function () {
-    return this.panel.state == "closed" || this.panel.state == "hiding";
-  },
-
-  /**
-   * Gets if this panel has any child nodes.
-   * @return boolean
-   */
-  isEmpty: function () {
-    return !this.panel.hasChildNodes();
-  },
-
-  /**
-   * Get rid of references and event listeners
-   */
-  destroy: function () {
-    this.hide();
-
-    for (let eventName of POPUP_EVENTS) {
-      this.panel.removeEventListener("popup" + eventName,
-        this["_onPopup" + eventName], false);
-    }
-
-    let win = this.doc.querySelector("window");
-    win.removeEventListener("keypress", this._onKeyPress, false);
-
-    let closeOnEvents = this.options.get("closeOnEvents");
-    for (let {emitter, event, useCapture} of closeOnEvents) {
-      for (let remove of ["removeEventListener", "off"]) {
-        if (remove in emitter) {
-          emitter[remove](event, this.hide, useCapture);
-          break;
-        }
-      }
-    }
-
-    this.content = null;
-
-    this._toggle.destroy();
-
-    this.doc = null;
-
-    this.panel.remove();
-    this.panel = null;
-  },
-
-  /**
-   * Returns the outer container node (that includes the arrow etc.). Happens
-   * to be identical to this.panel here, can be different element in other
-   * Tooltip implementations.
-   */
-  get container() {
-    return this.panel;
-  },
-
-  /**
-   * Set the content of this tooltip. Will first empty the tooltip and then
-   * append the new content element.
-   * Consider using one of the set<type>Content() functions instead.
-   * @param {node} content
-   *        A node that can be appended in the tooltip XUL element
-   */
-  set content(content) {
-    if (this.content == content) {
-      return;
-    }
-
-    this.empty();
-    this.panel.removeAttribute("clamped-dimensions");
-    this.panel.removeAttribute("clamped-dimensions-no-min-height");
-    this.panel.removeAttribute("clamped-dimensions-no-max-or-min-height");
-    this.panel.removeAttribute("wide");
-
-    if (content) {
-      this.panel.appendChild(content);
-    }
-  },
-
-  get content() {
-    return this.panel.firstChild;
-  },
-
-  /**
-   * Sets some text as the content of this tooltip.
-   *
-   * @param {array} messages
-   *        A list of text messages.
-   * @param {string} messagesClass [optional]
-   *        A style class for the text messages.
-   * @param {string} containerClass [optional]
-   *        A style class for the text messages container.
-   * @param {boolean} isAlertTooltip [optional]
-   *        Pass true to add an alert image for your tooltip.
-   */
-  setTextContent: function (
-    {
-      messages,
-      messagesClass,
-      containerClass,
-      isAlertTooltip
-    },
-    extraButtons = []) {
-    messagesClass = messagesClass || "default-tooltip-simple-text-colors";
-    containerClass = containerClass || "default-tooltip-simple-text-colors";
-
-    let vbox = this.doc.createElement("vbox");
-    vbox.className = "devtools-tooltip-simple-text-container " + containerClass;
-    vbox.setAttribute("flex", "1");
-
-    for (let text of messages) {
-      let description = this.doc.createElement("description");
-      description.setAttribute("flex", "1");
-      description.className = "devtools-tooltip-simple-text " + messagesClass;
-      description.textContent = text;
-      vbox.appendChild(description);
-    }
-
-    for (let { label, className, command } of extraButtons) {
-      let button = this.doc.createElement("button");
-      button.className = className;
-      button.setAttribute("label", label);
-      button.addEventListener("command", command);
-      vbox.appendChild(button);
-    }
-
-    if (isAlertTooltip) {
-      let hbox = this.doc.createElement("hbox");
-      hbox.setAttribute("align", "start");
-
-      let alertImg = this.doc.createElement("image");
-      alertImg.className = "devtools-tooltip-alert-icon";
-      hbox.appendChild(alertImg);
-      hbox.appendChild(vbox);
-      this.content = hbox;
-    } else {
-      this.content = vbox;
-    }
-  },
-
-  /**
-   * Load a document into an iframe, and set the iframe
-   * to be the tooltip's content.
-   *
-   * Used by tooltips that want to load their interface
-   * into an iframe from a URL.
-   *
-   * @param {string} width
-   *        Width of the iframe.
-   * @param {string} height
-   *        Height of the iframe.
-   * @param {string} url
-   *        URL of the document to load into the iframe.
-   *
-   * @return {promise} A promise which is resolved with
-   * the iframe.
-   *
-   * This function creates an iframe, loads the specified document
-   * into it, sets the tooltip's content to the iframe, and returns
-   * a promise.
-   *
-   * When the document is loaded, the function gets the content window
-   * and resolves the promise with the content window.
-   */
-  setIFrameContent: function ({width, height}, url) {
-    let def = defer();
-
-    // Create an iframe
-    let iframe = this.doc.createElementNS(XHTML_NS, "iframe");
-    iframe.setAttribute("transparent", true);
-    iframe.setAttribute("width", width);
-    iframe.setAttribute("height", height);
-    iframe.setAttribute("flex", "1");
-    iframe.setAttribute("tooltip", "aHTMLTooltip");
-    iframe.setAttribute("class", "devtools-tooltip-iframe");
-
-    // Wait for the load to initialize the widget
-    function onLoad() {
-      iframe.removeEventListener("load", onLoad, true);
-      def.resolve(iframe);
-    }
-    iframe.addEventListener("load", onLoad, true);
-
-    // load the document from url into the iframe
-    iframe.setAttribute("src", url);
-
-    // Put the iframe in the tooltip
-    this.content = iframe;
-
-    return def.promise;
-  }
-};
-
-/**
- * Base class for all (color, gradient, ...)-swatch based value editors inside
- * tooltips
- *
- * @param {Toolbox} toolbox
- *        The devtools toolbox, needed to get the devtools main window.
- */
-function SwatchBasedEditorTooltip(toolbox, stylesheet) {
-  EventEmitter.decorate(this);
-  // Creating a tooltip instance
-  // This one will consume outside clicks as it makes more sense to let the user
-  // close the tooltip by clicking out
-  // It will also close on <escape> and <enter>
-  this.tooltip = new HTMLTooltip(toolbox, {
-    type: "arrow",
-    consumeOutsideClicks: true,
-    useXulWrapper: true,
-    stylesheet
-  });
-
-  // By default, swatch-based editor tooltips revert value change on <esc> and
-  // commit value change on <enter>
-  this.shortcuts = new KeyShortcuts({
-    window: this.tooltip.topWindow
-  });
-  this.shortcuts.on("Escape", (name, event) => {
-    if (!this.tooltip.isVisible()) {
-      return;
-    }
-    this.revert();
-    this.hide();
-    event.stopPropagation();
-    event.preventDefault();
-  });
-  this.shortcuts.on("Return", (name, event) => {
-    if (!this.tooltip.isVisible()) {
-      return;
-    }
-    this.commit();
-    this.hide();
-    event.stopPropagation();
-    event.preventDefault();
-  });
-
-  // All target swatches are kept in a map, indexed by swatch DOM elements
-  this.swatches = new Map();
-
-  // When a swatch is clicked, and for as long as the tooltip is shown, the
-  // activeSwatch property will hold the reference to the swatch DOM element
-  // that was clicked
-  this.activeSwatch = null;
-
-  this._onSwatchClick = this._onSwatchClick.bind(this);
-}
-
-SwatchBasedEditorTooltip.prototype = {
-  /**
-   * Show the editor tooltip for the currently active swatch.
-   *
-   * @return {Promise} a promise that resolves once the editor tooltip is displayed, or
-   *         immediately if there is no currently active swatch.
-   */
-  show: function () {
-    if (this.activeSwatch) {
-      let onShown = this.tooltip.once("shown");
-      this.tooltip.show(this.activeSwatch, "topcenter bottomleft");
-
-      // When the tooltip is closed by clicking outside the panel we want to
-      // commit any changes.
-      this.tooltip.once("hidden", () => {
-        if (!this._reverted && !this.eyedropperOpen) {
-          this.commit();
-        }
-        this._reverted = false;
-
-        // Once the tooltip is hidden we need to clean up any remaining objects.
-        if (!this.eyedropperOpen) {
-          this.activeSwatch = null;
-        }
-      });
-
-      return onShown;
-    }
-
-    return Promise.resolve();
-  },
-
-  hide: function () {
-    this.tooltip.hide();
-  },
-
-  /**
-   * Add a new swatch DOM element to the list of swatch elements this editor
-   * tooltip knows about. That means from now on, clicking on that swatch will
-   * toggle the editor.
-   *
-   * @param {node} swatchEl
-   *        The element to add
-   * @param {object} callbacks
-   *        Callbacks that will be executed when the editor wants to preview a
-   *        value change, or revert a change, or commit a change.
-   *        - onShow: will be called when one of the swatch tooltip is shown
-   *        - onPreview: will be called when one of the sub-classes calls
-   *        preview
-   *        - onRevert: will be called when the user ESCapes out of the tooltip
-   *        - onCommit: will be called when the user presses ENTER or clicks
-   *        outside the tooltip.
-   */
-  addSwatch: function (swatchEl, callbacks = {}) {
-    if (!callbacks.onShow) {
-      callbacks.onShow = function () {};
-    }
-    if (!callbacks.onPreview) {
-      callbacks.onPreview = function () {};
-    }
-    if (!callbacks.onRevert) {
-      callbacks.onRevert = function () {};
-    }
-    if (!callbacks.onCommit) {
-      callbacks.onCommit = function () {};
-    }
-
-    this.swatches.set(swatchEl, {
-      callbacks: callbacks
-    });
-    swatchEl.addEventListener("click", this._onSwatchClick, false);
-  },
-
-  removeSwatch: function (swatchEl) {
-    if (this.swatches.has(swatchEl)) {
-      if (this.activeSwatch === swatchEl) {
-        this.hide();
-        this.activeSwatch = null;
-      }
-      swatchEl.removeEventListener("click", this._onSwatchClick, false);
-      this.swatches.delete(swatchEl);
-    }
-  },
-
-  _onSwatchClick: function (event) {
-    let swatch = this.swatches.get(event.target);
-
-    if (event.shiftKey) {
-      event.stopPropagation();
-      return;
-    }
-    if (swatch) {
-      this.activeSwatch = event.target;
-      this.show();
-      swatch.callbacks.onShow();
-      event.stopPropagation();
-    }
-  },
-
-  /**
-   * Not called by this parent class, needs to be taken care of by sub-classes
-   */
-  preview: function (value) {
-    if (this.activeSwatch) {
-      let swatch = this.swatches.get(this.activeSwatch);
-      swatch.callbacks.onPreview(value);
-    }
-  },
-
-  /**
-   * This parent class only calls this on <esc> keypress
-   */
-  revert: function () {
-    if (this.activeSwatch) {
-      this._reverted = true;
-      let swatch = this.swatches.get(this.activeSwatch);
-      this.tooltip.once("hidden", () => {
-        swatch.callbacks.onRevert();
-      });
-    }
-  },
-
-  /**
-   * This parent class only calls this on <enter> keypress
-   */
-  commit: function () {
-    if (this.activeSwatch) {
-      let swatch = this.swatches.get(this.activeSwatch);
-      swatch.callbacks.onCommit();
-    }
-  },
-
-  destroy: function () {
-    this.swatches.clear();
-    this.activeSwatch = null;
-    this.tooltip.off("keypress", this._onTooltipKeypress);
-    this.tooltip.destroy();
-    this.shortcuts.destroy();
-  }
-};
 
 /**
  * The swatch color picker tooltip class is a specific class meant to be used
  * along with output-parser's generated color swatches.
  * It extends the parent SwatchBasedEditorTooltip class.
  * It just wraps a standard Tooltip and sets its content with an instance of a
  * color picker.
  *
@@ -684,20 +33,17 @@ function SwatchColorPickerTooltip(toolbo
 
   // Creating a spectrum instance. this.spectrum will always be a promise that
   // resolves to the spectrum instance
   this.spectrum = this.setColorPickerContent([0, 0, 0, 1]);
   this._onSpectrumColorChange = this._onSpectrumColorChange.bind(this);
   this._openEyeDropper = this._openEyeDropper.bind(this);
 }
 
-module.exports.SwatchColorPickerTooltip = SwatchColorPickerTooltip;
-
-SwatchColorPickerTooltip.prototype =
-Heritage.extend(SwatchBasedEditorTooltip.prototype, {
+SwatchColorPickerTooltip.prototype = Heritage.extend(SwatchBasedEditorTooltip.prototype, {
   /**
    * Fill the tooltip with a new instance of the spectrum color picker widget
    * initialized with the given color, and return the instance of spectrum
    */
   setColorPickerContent: function (color) {
     let { doc } = this.tooltip;
 
     let container = doc.createElementNS(XHTML_NS, "div");
@@ -817,196 +163,9 @@ Heritage.extend(SwatchBasedEditorTooltip
     SwatchBasedEditorTooltip.prototype.destroy.call(this);
     this.inspector = null;
     this.currentSwatchColor = null;
     this.spectrum.off("changed", this._onSpectrumColorChange);
     this.spectrum.destroy();
   }
 });
 
-/**
- * The swatch cubic-bezier tooltip class is a specific class meant to be used
- * along with rule-view's generated cubic-bezier swatches.
- * It extends the parent SwatchBasedEditorTooltip class.
- * It just wraps a standard Tooltip and sets its content with an instance of a
- * CubicBezierWidget.
- *
- * @param {Toolbox} toolbox
- *        The devtools toolbox, needed to get the devtools main window.
- */
-function SwatchCubicBezierTooltip(toolbox) {
-  let stylesheet = "chrome://devtools/content/shared/widgets/cubic-bezier.css";
-  SwatchBasedEditorTooltip.call(this, toolbox, stylesheet);
-
-  // Creating a cubic-bezier instance.
-  // this.widget will always be a promise that resolves to the widget instance
-  this.widget = this.setCubicBezierContent([0, 0, 1, 1]);
-  this._onUpdate = this._onUpdate.bind(this);
-}
-
-module.exports.SwatchCubicBezierTooltip = SwatchCubicBezierTooltip;
-
-SwatchCubicBezierTooltip.prototype =
-Heritage.extend(SwatchBasedEditorTooltip.prototype, {
-  /**
-   * Fill the tooltip with a new instance of the cubic-bezier widget
-   * initialized with the given value, and return a promise that resolves to
-   * the instance of the widget
-   */
-  setCubicBezierContent: function (bezier) {
-    let { doc } = this.tooltip;
-
-    let container = doc.createElementNS(XHTML_NS, "div");
-    container.className = "cubic-bezier-container";
-
-    this.tooltip.setContent(container, { width: 510, height: 370 });
-
-    let def = defer();
-
-    // Wait for the tooltip to be shown before calling instanciating the widget
-    // as it expect its DOM elements to be visible.
-    this.tooltip.once("shown", () => {
-      let widget = new CubicBezierWidget(container, bezier);
-      def.resolve(widget);
-    });
-
-    return def.promise;
-  },
-
-  /**
-   * Overriding the SwatchBasedEditorTooltip.show function to set the cubic
-   * bezier curve in the widget
-   */
-  show: Task.async(function* () {
-    // Call the parent class' show function
-    yield SwatchBasedEditorTooltip.prototype.show.call(this);
-    // Then set the curve and listen to changes to preview them
-    if (this.activeSwatch) {
-      this.currentBezierValue = this.activeSwatch.nextSibling;
-      this.widget.then(widget => {
-        widget.off("updated", this._onUpdate);
-        widget.cssCubicBezierValue = this.currentBezierValue.textContent;
-        widget.on("updated", this._onUpdate);
-        this.emit("ready");
-      });
-    }
-  }),
-
-  _onUpdate: function (event, bezier) {
-    if (!this.activeSwatch) {
-      return;
-    }
-
-    this.currentBezierValue.textContent = bezier + "";
-    this.preview(bezier + "");
-  },
-
-  destroy: function () {
-    SwatchBasedEditorTooltip.prototype.destroy.call(this);
-    this.currentBezierValue = null;
-    this.widget.then(widget => {
-      widget.off("updated", this._onUpdate);
-      widget.destroy();
-    });
-  }
-});
-
-/**
- * The swatch-based css filter tooltip class is a specific class meant to be
- * used along with rule-view's generated css filter swatches.
- * It extends the parent SwatchBasedEditorTooltip class.
- * It just wraps a standard Tooltip and sets its content with an instance of a
- * CSSFilterEditorWidget.
- *
- * @param {Toolbox} toolbox
- *        The devtools toolbox, needed to get the devtools main window.
- * @param {function} cssIsValid
- *        A function to check that css declaration's name and values are valid together.
- *        This can be obtained from "shared/fronts/css-properties.js".
- */
-function SwatchFilterTooltip(toolbox, cssIsValid) {
-  let stylesheet = "chrome://devtools/content/shared/widgets/filter-widget.css";
-  SwatchBasedEditorTooltip.call(this, toolbox, stylesheet);
-  this._cssIsValid = cssIsValid;
-
-  // Creating a filter editor instance.
-  this.widget = this.setFilterContent("none");
-  this._onUpdate = this._onUpdate.bind(this);
-}
-
-exports.SwatchFilterTooltip = SwatchFilterTooltip;
-
-SwatchFilterTooltip.prototype =
-Heritage.extend(SwatchBasedEditorTooltip.prototype, {
-  /**
-   * Fill the tooltip with a new instance of the CSSFilterEditorWidget
-   * widget initialized with the given filter value, and return a promise
-   * that resolves to the instance of the widget when ready.
-   */
-  setFilterContent: function (filter) {
-    let { doc } = this.tooltip;
-
-    let container = doc.createElementNS(XHTML_NS, "div");
-    container.id = "filter-container";
-
-    this.tooltip.setContent(container, { width: 510, height: 200 });
-
-    return new CSSFilterEditorWidget(container, filter, this._cssIsValid);
-  },
-
-  show: Task.async(function* () {
-    // Call the parent class' show function
-    yield SwatchBasedEditorTooltip.prototype.show.call(this);
-    // Then set the filter value and listen to changes to preview them
-    if (this.activeSwatch) {
-      this.currentFilterValue = this.activeSwatch.nextSibling;
-      this.widget.off("updated", this._onUpdate);
-      this.widget.on("updated", this._onUpdate);
-      this.widget.setCssValue(this.currentFilterValue.textContent);
-      this.widget.render();
-      this.emit("ready");
-    }
-  }),
-
-  _onUpdate: function (event, filters) {
-    if (!this.activeSwatch) {
-      return;
-    }
-
-    // Remove the old children and reparse the property value to
-    // recompute them.
-    while (this.currentFilterValue.firstChild) {
-      this.currentFilterValue.firstChild.remove();
-    }
-    let node = this._parser.parseCssProperty("filter", filters, this._options);
-    this.currentFilterValue.appendChild(node);
-
-    this.preview();
-  },
-
-  destroy: function () {
-    SwatchBasedEditorTooltip.prototype.destroy.call(this);
-    this.currentFilterValue = null;
-    this.widget.off("updated", this._onUpdate);
-    this.widget.destroy();
-  },
-
-  /**
-   * Like SwatchBasedEditorTooltip.addSwatch, but accepts a parser object
-   * to use when previewing the updated property value.
-   *
-   * @param {node} swatchEl
-   *        @see SwatchBasedEditorTooltip.addSwatch
-   * @param {object} callbacks
-   *        @see SwatchBasedEditorTooltip.addSwatch
-   * @param {object} parser
-   *        A parser object; @see OutputParser object
-   * @param {object} options
-   *        options to pass to the output parser, with
-   *          the option |filterSwatch| set.
-   */
-  addSwatch: function (swatchEl, callbacks, parser, options) {
-    SwatchBasedEditorTooltip.prototype.addSwatch.call(this, swatchEl,
-                                                      callbacks);
-    this._parser = parser;
-    this._options = options;
-  }
-});
+module.exports = SwatchColorPickerTooltip;
copy from devtools/client/shared/widgets/Tooltip.js
copy to devtools/client/shared/widgets/tooltip/SwatchCubicBezierTooltip.js
--- a/devtools/client/shared/widgets/Tooltip.js
+++ b/devtools/client/shared/widgets/tooltip/SwatchCubicBezierTooltip.js
@@ -1,831 +1,22 @@
 /* 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/. */
 
 "use strict";
 
 const defer = require("devtools/shared/defer");
-const {Spectrum} = require("devtools/client/shared/widgets/Spectrum");
-const {CubicBezierWidget} =
-      require("devtools/client/shared/widgets/CubicBezierWidget");
-const {CSSFilterEditorWidget} = require("devtools/client/shared/widgets/FilterWidget");
-const {TooltipToggle} = require("devtools/client/shared/widgets/tooltip/TooltipToggle");
-const EventEmitter = require("devtools/shared/event-emitter");
-const {colorUtils} = require("devtools/shared/css/color");
+const {Task} = require("devtools/shared/task");
+const {CubicBezierWidget} = require("devtools/client/shared/widgets/CubicBezierWidget");
+const SwatchBasedEditorTooltip = require("devtools/client/shared/widgets/tooltip/SwatchBasedEditorTooltip");
+
 const Heritage = require("sdk/core/heritage");
-const {HTMLTooltip} = require("devtools/client/shared/widgets/HTMLTooltip");
-const {KeyShortcuts} = require("devtools/client/shared/key-shortcuts");
-const {Task} = require("devtools/shared/task");
-const {KeyCodes} = require("devtools/client/shared/keycodes");
 
 const XHTML_NS = "http://www.w3.org/1999/xhtml";
-const ESCAPE_KEYCODE = KeyCodes.DOM_VK_ESCAPE;
-const POPUP_EVENTS = ["shown", "hidden", "showing", "hiding"];
-
-/**
- * Tooltip widget.
- *
- * This widget is intended at any tool that may need to show rich content in the
- * form of floating panels.
- * A common use case is image previewing in the CSS rule view, but more complex
- * use cases may include color pickers, object inspection, etc...
- *
- * Tooltips are based on XUL (namely XUL arrow-type <panel>s), and therefore
- * need a XUL Document to live in.
- * This is pretty much the only requirement they have on their environment.
- *
- * The way to use a tooltip is simply by instantiating a tooltip yourself and
- * attaching some content in it, or using one of the ready-made content types.
- *
- * A convenient `startTogglingOnHover` method may avoid having to register event
- * handlers yourself if the tooltip has to be shown when hovering over a
- * specific element or group of elements (which is usually the most common case)
- */
-
-/**
- * Container used for dealing with optional parameters.
- *
- * @param {Object} defaults
- *        An object with all default options {p1: v1, p2: v2, ...}
- * @param {Object} options
- *        The actual values.
- */
-function OptionsStore(defaults, options) {
-  this.defaults = defaults || {};
-  this.options = options || {};
-}
-
-OptionsStore.prototype = {
-  /**
-   * Get the value for a given option name.
-   * @return {Object} Returns the value for that option, coming either for the
-   *         actual values that have been set in the constructor, or from the
-   *         defaults if that options was not specified.
-   */
-  get: function (name) {
-    if (typeof this.options[name] !== "undefined") {
-      return this.options[name];
-    }
-    return this.defaults[name];
-  }
-};
-
-/**
- * The low level structure of a tooltip is a XUL element (a <panel>).
- */
-var PanelFactory = {
-  /**
-   * Get a new XUL panel instance.
-   * @param {XULDocument} doc
-   *        The XUL document to put that panel into
-   * @param {OptionsStore} options
-   *        An options store to get some configuration from
-   */
-  get: function (doc, options) {
-    // Create the tooltip
-    let panel = doc.createElement("panel");
-    panel.setAttribute("hidden", true);
-    panel.setAttribute("ignorekeys", true);
-    panel.setAttribute("animate", false);
-
-    panel.setAttribute("consumeoutsideclicks",
-                       options.get("consumeOutsideClick"));
-    panel.setAttribute("noautofocus", options.get("noAutoFocus"));
-    panel.setAttribute("type", "arrow");
-    panel.setAttribute("level", "top");
-
-    panel.setAttribute("class", "devtools-tooltip theme-tooltip-panel");
-    doc.querySelector("window").appendChild(panel);
-
-    return panel;
-  }
-};
-
-/**
- * Tooltip class.
- *
- * Basic usage:
- *   let t = new Tooltip(xulDoc);
- *   t.content = someXulContent;
- *   t.show();
- *   t.hide();
- *   t.destroy();
- *
- * Better usage:
- *   let t = new Tooltip(xulDoc);
- *   t.startTogglingOnHover(container, target => {
- *     if (<condition based on target>) {
- *       t.content = el;
- *       return true;
- *     }
- *   });
- *   t.destroy();
- *
- * @param {XULDocument} doc
- *        The XUL document hosting this tooltip
- * @param {Object} options
- *        Optional options that give options to consumers:
- *        - consumeOutsideClick {Boolean} Wether the first click outside of the
- *        tooltip should close the tooltip and be consumed or not.
- *        Defaults to false.
- *        - closeOnKeys {Array} An array of key codes that should close the
- *        tooltip. Defaults to [27] (escape key).
- *        - closeOnEvents [{emitter: {Object}, event: {String},
- *                          useCapture: {Boolean}}]
- *        Provide an optional list of emitter objects and event names here to
- *        trigger the closing of the tooltip when these events are fired by the
- *        emitters. The emitter objects should either implement
- *        on/off(event, cb) or addEventListener/removeEventListener(event, cb).
- *        Defaults to [].
- *        For instance, the following would close the tooltip whenever the
- *        toolbox selects a new tool and when a DOM node gets scrolled:
- *        new Tooltip(doc, {
- *          closeOnEvents: [
- *            {emitter: toolbox, event: "select"},
- *            {emitter: myContainer, event: "scroll", useCapture: true}
- *          ]
- *        });
- *        - noAutoFocus {Boolean} Should the focus automatically go to the panel
- *        when it opens. Defaults to true.
- *
- * Fires these events:
- * - showing : just before the tooltip shows
- * - shown : when the tooltip is shown
- * - hiding : just before the tooltip closes
- * - hidden : when the tooltip gets hidden
- * - keypress : when any key gets pressed, with keyCode
- */
-function Tooltip(doc, options) {
-  EventEmitter.decorate(this);
-
-  this.doc = doc;
-  this.options = new OptionsStore({
-    consumeOutsideClick: false,
-    closeOnKeys: [ESCAPE_KEYCODE],
-    noAutoFocus: true,
-    closeOnEvents: []
-  }, options);
-  this.panel = PanelFactory.get(doc, this.options);
-
-  // Create tooltip toggle helper and decorate the Tooltip instance with
-  // shortcut methods.
-  this._toggle = new TooltipToggle(this);
-  this.startTogglingOnHover = this._toggle.start.bind(this._toggle);
-  this.stopTogglingOnHover = this._toggle.stop.bind(this._toggle);
-
-  // Emit show/hide events when the panel does.
-  for (let eventName of POPUP_EVENTS) {
-    this["_onPopup" + eventName] = (name => {
-      return e => {
-        if (e.target === this.panel) {
-          this.emit(name);
-        }
-      };
-    })(eventName);
-    this.panel.addEventListener("popup" + eventName,
-      this["_onPopup" + eventName], false);
-  }
-
-  // Listen to keypress events to close the tooltip if configured to do so
-  let win = this.doc.querySelector("window");
-  this._onKeyPress = event => {
-    if (this.panel.hidden) {
-      return;
-    }
-
-    this.emit("keypress", event.keyCode);
-    if (this.options.get("closeOnKeys").indexOf(event.keyCode) !== -1 &&
-        this.isShown()) {
-      event.stopPropagation();
-      this.hide();
-    }
-  };
-  win.addEventListener("keypress", this._onKeyPress, false);
-
-  // Listen to custom emitters' events to close the tooltip
-  this.hide = this.hide.bind(this);
-  let closeOnEvents = this.options.get("closeOnEvents");
-  for (let {emitter, event, useCapture} of closeOnEvents) {
-    for (let add of ["addEventListener", "on"]) {
-      if (add in emitter) {
-        emitter[add](event, this.hide, useCapture);
-        break;
-      }
-    }
-  }
-}
-
-module.exports.Tooltip = Tooltip;
-
-Tooltip.prototype = {
-  defaultPosition: "before_start",
-  // px
-  defaultOffsetX: 0,
-  // px
-  defaultOffsetY: 0,
-  // px
-
-  /**
-   * Show the tooltip. It might be wise to append some content first if you
-   * don't want the tooltip to be empty. You may access the content of the
-   * tooltip by setting a XUL node to t.content.
-   * @param {node} anchor
-   *        Which node should the tooltip be shown on
-   * @param {string} position [optional]
-   *        Optional tooltip position. Defaults to before_start
-   *        https://developer.mozilla.org/en-US/docs/XUL/PopupGuide/Positioning
-   * @param {number} x, y [optional]
-   *        The left and top offset coordinates, in pixels.
-   */
-  show: function (anchor,
-    position = this.defaultPosition,
-    x = this.defaultOffsetX,
-    y = this.defaultOffsetY) {
-    this.panel.hidden = false;
-    this.panel.openPopup(anchor, position, x, y);
-  },
-
-  /**
-   * Hide the tooltip
-   */
-  hide: function () {
-    this.panel.hidden = true;
-    this.panel.hidePopup();
-  },
-
-  isShown: function () {
-    return this.panel &&
-           this.panel.state !== "closed" &&
-           this.panel.state !== "hiding";
-  },
-
-  setSize: function (width, height) {
-    this.panel.sizeTo(width, height);
-  },
-
-  /**
-   * Empty the tooltip's content
-   */
-  empty: function () {
-    while (this.panel.hasChildNodes()) {
-      this.panel.removeChild(this.panel.firstChild);
-    }
-  },
-
-  /**
-   * Gets this panel's visibility state.
-   * @return boolean
-   */
-  isHidden: function () {
-    return this.panel.state == "closed" || this.panel.state == "hiding";
-  },
-
-  /**
-   * Gets if this panel has any child nodes.
-   * @return boolean
-   */
-  isEmpty: function () {
-    return !this.panel.hasChildNodes();
-  },
-
-  /**
-   * Get rid of references and event listeners
-   */
-  destroy: function () {
-    this.hide();
-
-    for (let eventName of POPUP_EVENTS) {
-      this.panel.removeEventListener("popup" + eventName,
-        this["_onPopup" + eventName], false);
-    }
-
-    let win = this.doc.querySelector("window");
-    win.removeEventListener("keypress", this._onKeyPress, false);
-
-    let closeOnEvents = this.options.get("closeOnEvents");
-    for (let {emitter, event, useCapture} of closeOnEvents) {
-      for (let remove of ["removeEventListener", "off"]) {
-        if (remove in emitter) {
-          emitter[remove](event, this.hide, useCapture);
-          break;
-        }
-      }
-    }
-
-    this.content = null;
-
-    this._toggle.destroy();
-
-    this.doc = null;
-
-    this.panel.remove();
-    this.panel = null;
-  },
-
-  /**
-   * Returns the outer container node (that includes the arrow etc.). Happens
-   * to be identical to this.panel here, can be different element in other
-   * Tooltip implementations.
-   */
-  get container() {
-    return this.panel;
-  },
-
-  /**
-   * Set the content of this tooltip. Will first empty the tooltip and then
-   * append the new content element.
-   * Consider using one of the set<type>Content() functions instead.
-   * @param {node} content
-   *        A node that can be appended in the tooltip XUL element
-   */
-  set content(content) {
-    if (this.content == content) {
-      return;
-    }
-
-    this.empty();
-    this.panel.removeAttribute("clamped-dimensions");
-    this.panel.removeAttribute("clamped-dimensions-no-min-height");
-    this.panel.removeAttribute("clamped-dimensions-no-max-or-min-height");
-    this.panel.removeAttribute("wide");
-
-    if (content) {
-      this.panel.appendChild(content);
-    }
-  },
-
-  get content() {
-    return this.panel.firstChild;
-  },
-
-  /**
-   * Sets some text as the content of this tooltip.
-   *
-   * @param {array} messages
-   *        A list of text messages.
-   * @param {string} messagesClass [optional]
-   *        A style class for the text messages.
-   * @param {string} containerClass [optional]
-   *        A style class for the text messages container.
-   * @param {boolean} isAlertTooltip [optional]
-   *        Pass true to add an alert image for your tooltip.
-   */
-  setTextContent: function (
-    {
-      messages,
-      messagesClass,
-      containerClass,
-      isAlertTooltip
-    },
-    extraButtons = []) {
-    messagesClass = messagesClass || "default-tooltip-simple-text-colors";
-    containerClass = containerClass || "default-tooltip-simple-text-colors";
-
-    let vbox = this.doc.createElement("vbox");
-    vbox.className = "devtools-tooltip-simple-text-container " + containerClass;
-    vbox.setAttribute("flex", "1");
-
-    for (let text of messages) {
-      let description = this.doc.createElement("description");
-      description.setAttribute("flex", "1");
-      description.className = "devtools-tooltip-simple-text " + messagesClass;
-      description.textContent = text;
-      vbox.appendChild(description);
-    }
-
-    for (let { label, className, command } of extraButtons) {
-      let button = this.doc.createElement("button");
-      button.className = className;
-      button.setAttribute("label", label);
-      button.addEventListener("command", command);
-      vbox.appendChild(button);
-    }
-
-    if (isAlertTooltip) {
-      let hbox = this.doc.createElement("hbox");
-      hbox.setAttribute("align", "start");
-
-      let alertImg = this.doc.createElement("image");
-      alertImg.className = "devtools-tooltip-alert-icon";
-      hbox.appendChild(alertImg);
-      hbox.appendChild(vbox);
-      this.content = hbox;
-    } else {
-      this.content = vbox;
-    }
-  },
-
-  /**
-   * Load a document into an iframe, and set the iframe
-   * to be the tooltip's content.
-   *
-   * Used by tooltips that want to load their interface
-   * into an iframe from a URL.
-   *
-   * @param {string} width
-   *        Width of the iframe.
-   * @param {string} height
-   *        Height of the iframe.
-   * @param {string} url
-   *        URL of the document to load into the iframe.
-   *
-   * @return {promise} A promise which is resolved with
-   * the iframe.
-   *
-   * This function creates an iframe, loads the specified document
-   * into it, sets the tooltip's content to the iframe, and returns
-   * a promise.
-   *
-   * When the document is loaded, the function gets the content window
-   * and resolves the promise with the content window.
-   */
-  setIFrameContent: function ({width, height}, url) {
-    let def = defer();
-
-    // Create an iframe
-    let iframe = this.doc.createElementNS(XHTML_NS, "iframe");
-    iframe.setAttribute("transparent", true);
-    iframe.setAttribute("width", width);
-    iframe.setAttribute("height", height);
-    iframe.setAttribute("flex", "1");
-    iframe.setAttribute("tooltip", "aHTMLTooltip");
-    iframe.setAttribute("class", "devtools-tooltip-iframe");
-
-    // Wait for the load to initialize the widget
-    function onLoad() {
-      iframe.removeEventListener("load", onLoad, true);
-      def.resolve(iframe);
-    }
-    iframe.addEventListener("load", onLoad, true);
-
-    // load the document from url into the iframe
-    iframe.setAttribute("src", url);
-
-    // Put the iframe in the tooltip
-    this.content = iframe;
-
-    return def.promise;
-  }
-};
-
-/**
- * Base class for all (color, gradient, ...)-swatch based value editors inside
- * tooltips
- *
- * @param {Toolbox} toolbox
- *        The devtools toolbox, needed to get the devtools main window.
- */
-function SwatchBasedEditorTooltip(toolbox, stylesheet) {
-  EventEmitter.decorate(this);
-  // Creating a tooltip instance
-  // This one will consume outside clicks as it makes more sense to let the user
-  // close the tooltip by clicking out
-  // It will also close on <escape> and <enter>
-  this.tooltip = new HTMLTooltip(toolbox, {
-    type: "arrow",
-    consumeOutsideClicks: true,
-    useXulWrapper: true,
-    stylesheet
-  });
-
-  // By default, swatch-based editor tooltips revert value change on <esc> and
-  // commit value change on <enter>
-  this.shortcuts = new KeyShortcuts({
-    window: this.tooltip.topWindow
-  });
-  this.shortcuts.on("Escape", (name, event) => {
-    if (!this.tooltip.isVisible()) {
-      return;
-    }
-    this.revert();
-    this.hide();
-    event.stopPropagation();
-    event.preventDefault();
-  });
-  this.shortcuts.on("Return", (name, event) => {
-    if (!this.tooltip.isVisible()) {
-      return;
-    }
-    this.commit();
-    this.hide();
-    event.stopPropagation();
-    event.preventDefault();
-  });
-
-  // All target swatches are kept in a map, indexed by swatch DOM elements
-  this.swatches = new Map();
-
-  // When a swatch is clicked, and for as long as the tooltip is shown, the
-  // activeSwatch property will hold the reference to the swatch DOM element
-  // that was clicked
-  this.activeSwatch = null;
-
-  this._onSwatchClick = this._onSwatchClick.bind(this);
-}
-
-SwatchBasedEditorTooltip.prototype = {
-  /**
-   * Show the editor tooltip for the currently active swatch.
-   *
-   * @return {Promise} a promise that resolves once the editor tooltip is displayed, or
-   *         immediately if there is no currently active swatch.
-   */
-  show: function () {
-    if (this.activeSwatch) {
-      let onShown = this.tooltip.once("shown");
-      this.tooltip.show(this.activeSwatch, "topcenter bottomleft");
-
-      // When the tooltip is closed by clicking outside the panel we want to
-      // commit any changes.
-      this.tooltip.once("hidden", () => {
-        if (!this._reverted && !this.eyedropperOpen) {
-          this.commit();
-        }
-        this._reverted = false;
-
-        // Once the tooltip is hidden we need to clean up any remaining objects.
-        if (!this.eyedropperOpen) {
-          this.activeSwatch = null;
-        }
-      });
-
-      return onShown;
-    }
-
-    return Promise.resolve();
-  },
-
-  hide: function () {
-    this.tooltip.hide();
-  },
-
-  /**
-   * Add a new swatch DOM element to the list of swatch elements this editor
-   * tooltip knows about. That means from now on, clicking on that swatch will
-   * toggle the editor.
-   *
-   * @param {node} swatchEl
-   *        The element to add
-   * @param {object} callbacks
-   *        Callbacks that will be executed when the editor wants to preview a
-   *        value change, or revert a change, or commit a change.
-   *        - onShow: will be called when one of the swatch tooltip is shown
-   *        - onPreview: will be called when one of the sub-classes calls
-   *        preview
-   *        - onRevert: will be called when the user ESCapes out of the tooltip
-   *        - onCommit: will be called when the user presses ENTER or clicks
-   *        outside the tooltip.
-   */
-  addSwatch: function (swatchEl, callbacks = {}) {
-    if (!callbacks.onShow) {
-      callbacks.onShow = function () {};
-    }
-    if (!callbacks.onPreview) {
-      callbacks.onPreview = function () {};
-    }
-    if (!callbacks.onRevert) {
-      callbacks.onRevert = function () {};
-    }
-    if (!callbacks.onCommit) {
-      callbacks.onCommit = function () {};
-    }
-
-    this.swatches.set(swatchEl, {
-      callbacks: callbacks
-    });
-    swatchEl.addEventListener("click", this._onSwatchClick, false);
-  },
-
-  removeSwatch: function (swatchEl) {
-    if (this.swatches.has(swatchEl)) {
-      if (this.activeSwatch === swatchEl) {
-        this.hide();
-        this.activeSwatch = null;
-      }
-      swatchEl.removeEventListener("click", this._onSwatchClick, false);
-      this.swatches.delete(swatchEl);
-    }
-  },
-
-  _onSwatchClick: function (event) {
-    let swatch = this.swatches.get(event.target);
-
-    if (event.shiftKey) {
-      event.stopPropagation();
-      return;
-    }
-    if (swatch) {
-      this.activeSwatch = event.target;
-      this.show();
-      swatch.callbacks.onShow();
-      event.stopPropagation();
-    }
-  },
-
-  /**
-   * Not called by this parent class, needs to be taken care of by sub-classes
-   */
-  preview: function (value) {
-    if (this.activeSwatch) {
-      let swatch = this.swatches.get(this.activeSwatch);
-      swatch.callbacks.onPreview(value);
-    }
-  },
-
-  /**
-   * This parent class only calls this on <esc> keypress
-   */
-  revert: function () {
-    if (this.activeSwatch) {
-      this._reverted = true;
-      let swatch = this.swatches.get(this.activeSwatch);
-      this.tooltip.once("hidden", () => {
-        swatch.callbacks.onRevert();
-      });
-    }
-  },
-
-  /**
-   * This parent class only calls this on <enter> keypress
-   */
-  commit: function () {
-    if (this.activeSwatch) {
-      let swatch = this.swatches.get(this.activeSwatch);
-      swatch.callbacks.onCommit();
-    }
-  },
-
-  destroy: function () {
-    this.swatches.clear();
-    this.activeSwatch = null;
-    this.tooltip.off("keypress", this._onTooltipKeypress);
-    this.tooltip.destroy();
-    this.shortcuts.destroy();
-  }
-};
-
-/**
- * The swatch color picker tooltip class is a specific class meant to be used
- * along with output-parser's generated color swatches.
- * It extends the parent SwatchBasedEditorTooltip class.
- * It just wraps a standard Tooltip and sets its content with an instance of a
- * color picker.
- *
- * @param {Toolbox} toolbox
- *        The devtools toolbox, needed to get the devtools main window.
- * @param {InspectorPanel} inspector
- *        The inspector panel, needed for the eyedropper.
- */
-function SwatchColorPickerTooltip(toolbox, inspector) {
-  let stylesheet = "chrome://devtools/content/shared/widgets/spectrum.css";
-  SwatchBasedEditorTooltip.call(this, toolbox, stylesheet);
-
-  this.inspector = inspector;
-
-  // Creating a spectrum instance. this.spectrum will always be a promise that
-  // resolves to the spectrum instance
-  this.spectrum = this.setColorPickerContent([0, 0, 0, 1]);
-  this._onSpectrumColorChange = this._onSpectrumColorChange.bind(this);
-  this._openEyeDropper = this._openEyeDropper.bind(this);
-}
-
-module.exports.SwatchColorPickerTooltip = SwatchColorPickerTooltip;
-
-SwatchColorPickerTooltip.prototype =
-Heritage.extend(SwatchBasedEditorTooltip.prototype, {
-  /**
-   * Fill the tooltip with a new instance of the spectrum color picker widget
-   * initialized with the given color, and return the instance of spectrum
-   */
-  setColorPickerContent: function (color) {
-    let { doc } = this.tooltip;
-
-    let container = doc.createElementNS(XHTML_NS, "div");
-    container.id = "spectrum-tooltip";
-    let spectrumNode = doc.createElementNS(XHTML_NS, "div");
-    spectrumNode.id = "spectrum";
-    container.appendChild(spectrumNode);
-    let eyedropper = doc.createElementNS(XHTML_NS, "button");
-    eyedropper.id = "eyedropper-button";
-    eyedropper.className = "devtools-button";
-    container.appendChild(eyedropper);
-
-    this.tooltip.setContent(container, { width: 218, height: 224 });
-
-    let spectrum = new Spectrum(spectrumNode, color);
-
-    // Wait for the tooltip to be shown before calling spectrum.show
-    // as it expect to be visible in order to compute DOM element sizes.
-    this.tooltip.once("shown", () => {
-      spectrum.show();
-    });
-
-    return spectrum;
-  },
-
-  /**
-   * Overriding the SwatchBasedEditorTooltip.show function to set spectrum's
-   * color.
-   */
-  show: Task.async(function* () {
-    // Call then parent class' show function
-    yield SwatchBasedEditorTooltip.prototype.show.call(this);
-    // Then set spectrum's color and listen to color changes to preview them
-    if (this.activeSwatch) {
-      this.currentSwatchColor = this.activeSwatch.nextSibling;
-      this._originalColor = this.currentSwatchColor.textContent;
-      let color = this.activeSwatch.style.backgroundColor;
-      this.spectrum.off("changed", this._onSpectrumColorChange);
-      this.spectrum.rgb = this._colorToRgba(color);
-      this.spectrum.on("changed", this._onSpectrumColorChange);
-      this.spectrum.updateUI();
-    }
-
-    let {target} = this.inspector;
-    target.actorHasMethod("inspector", "pickColorFromPage").then(value => {
-      let tooltipDoc = this.tooltip.doc;
-      let eyeButton = tooltipDoc.querySelector("#eyedropper-button");
-      if (value && this.inspector.selection.nodeFront.isInHTMLDocument) {
-        eyeButton.addEventListener("click", this._openEyeDropper);
-      } else {
-        eyeButton.style.display = "none";
-      }
-      this.emit("ready");
-    }, e => console.error(e));
-  }),
-
-  _onSpectrumColorChange: function (event, rgba, cssColor) {
-    this._selectColor(cssColor);
-  },
-
-  _selectColor: function (color) {
-    if (this.activeSwatch) {
-      this.activeSwatch.style.backgroundColor = color;
-      this.activeSwatch.parentNode.dataset.color = color;
-
-      color = this._toDefaultType(color);
-      this.currentSwatchColor.textContent = color;
-      this.preview(color);
-
-      if (this.eyedropperOpen) {
-        this.commit();
-      }
-    }
-  },
-
-  _openEyeDropper: function () {
-    let {inspector, toolbox, telemetry} = this.inspector;
-    telemetry.toolOpened("pickereyedropper");
-    inspector.pickColorFromPage(toolbox, {copyOnSelect: false}).then(() => {
-      this.eyedropperOpen = true;
-
-      // close the colorpicker tooltip so that only the eyedropper is open.
-      this.hide();
-
-      this.tooltip.emit("eyedropper-opened");
-    }, e => console.error(e));
-
-    inspector.once("color-picked", color => {
-      toolbox.win.focus();
-      this._selectColor(color);
-      this._onEyeDropperDone();
-    });
-
-    inspector.once("color-pick-canceled", () => {
-      this._onEyeDropperDone();
-    });
-  },
-
-  _onEyeDropperDone: function () {
-    this.eyedropperOpen = false;
-    this.activeSwatch = null;
-  },
-
-  _colorToRgba: function (color) {
-    color = new colorUtils.CssColor(color);
-    let rgba = color._getRGBATuple();
-    return [rgba.r, rgba.g, rgba.b, rgba.a];
-  },
-
-  _toDefaultType: function (color) {
-    let colorObj = new colorUtils.CssColor(color);
-    colorObj.setAuthoredUnitFromColor(this._originalColor);
-    return colorObj.toString();
-  },
-
-  destroy: function () {
-    SwatchBasedEditorTooltip.prototype.destroy.call(this);
-    this.inspector = null;
-    this.currentSwatchColor = null;
-    this.spectrum.off("changed", this._onSpectrumColorChange);
-    this.spectrum.destroy();
-  }
-});
 
 /**
  * The swatch cubic-bezier tooltip class is a specific class meant to be used
  * along with rule-view's generated cubic-bezier swatches.
  * It extends the parent SwatchBasedEditorTooltip class.
  * It just wraps a standard Tooltip and sets its content with an instance of a
  * CubicBezierWidget.
  *
@@ -837,20 +28,17 @@ function SwatchCubicBezierTooltip(toolbo
   SwatchBasedEditorTooltip.call(this, toolbox, stylesheet);
 
   // Creating a cubic-bezier instance.
   // this.widget will always be a promise that resolves to the widget instance
   this.widget = this.setCubicBezierContent([0, 0, 1, 1]);
   this._onUpdate = this._onUpdate.bind(this);
 }
 
-module.exports.SwatchCubicBezierTooltip = SwatchCubicBezierTooltip;
-
-SwatchCubicBezierTooltip.prototype =
-Heritage.extend(SwatchBasedEditorTooltip.prototype, {
+SwatchCubicBezierTooltip.prototype = Heritage.extend(SwatchBasedEditorTooltip.prototype, {
   /**
    * Fill the tooltip with a new instance of the cubic-bezier widget
    * initialized with the given value, and return a promise that resolves to
    * the instance of the widget
    */
   setCubicBezierContent: function (bezier) {
     let { doc } = this.tooltip;
 
@@ -904,109 +92,9 @@ Heritage.extend(SwatchBasedEditorTooltip
     this.currentBezierValue = null;
     this.widget.then(widget => {
       widget.off("updated", this._onUpdate);
       widget.destroy();
     });
   }
 });
 
-/**
- * The swatch-based css filter tooltip class is a specific class meant to be
- * used along with rule-view's generated css filter swatches.
- * It extends the parent SwatchBasedEditorTooltip class.
- * It just wraps a standard Tooltip and sets its content with an instance of a
- * CSSFilterEditorWidget.
- *
- * @param {Toolbox} toolbox
- *        The devtools toolbox, needed to get the devtools main window.
- * @param {function} cssIsValid
- *        A function to check that css declaration's name and values are valid together.
- *        This can be obtained from "shared/fronts/css-properties.js".
- */
-function SwatchFilterTooltip(toolbox, cssIsValid) {
-  let stylesheet = "chrome://devtools/content/shared/widgets/filter-widget.css";
-  SwatchBasedEditorTooltip.call(this, toolbox, stylesheet);
-  this._cssIsValid = cssIsValid;
-
-  // Creating a filter editor instance.
-  this.widget = this.setFilterContent("none");
-  this._onUpdate = this._onUpdate.bind(this);
-}
-
-exports.SwatchFilterTooltip = SwatchFilterTooltip;
-
-SwatchFilterTooltip.prototype =
-Heritage.extend(SwatchBasedEditorTooltip.prototype, {
-  /**
-   * Fill the tooltip with a new instance of the CSSFilterEditorWidget
-   * widget initialized with the given filter value, and return a promise
-   * that resolves to the instance of the widget when ready.
-   */
-  setFilterContent: function (filter) {
-    let { doc } = this.tooltip;
-
-    let container = doc.createElementNS(XHTML_NS, "div");
-    container.id = "filter-container";
-
-    this.tooltip.setContent(container, { width: 510, height: 200 });
-
-    return new CSSFilterEditorWidget(container, filter, this._cssIsValid);
-  },
-
-  show: Task.async(function* () {
-    // Call the parent class' show function
-    yield SwatchBasedEditorTooltip.prototype.show.call(this);
-    // Then set the filter value and listen to changes to preview them
-    if (this.activeSwatch) {
-      this.currentFilterValue = this.activeSwatch.nextSibling;
-      this.widget.off("updated", this._onUpdate);
-      this.widget.on("updated", this._onUpdate);
-      this.widget.setCssValue(this.currentFilterValue.textContent);
-      this.widget.render();
-      this.emit("ready");
-    }
-  }),
-
-  _onUpdate: function (event, filters) {
-    if (!this.activeSwatch) {
-      return;
-    }
-
-    // Remove the old children and reparse the property value to
-    // recompute them.
-    while (this.currentFilterValue.firstChild) {
-      this.currentFilterValue.firstChild.remove();
-    }
-    let node = this._parser.parseCssProperty("filter", filters, this._options);
-    this.currentFilterValue.appendChild(node);
-
-    this.preview();
-  },
-
-  destroy: function () {
-    SwatchBasedEditorTooltip.prototype.destroy.call(this);
-    this.currentFilterValue = null;
-    this.widget.off("updated", this._onUpdate);
-    this.widget.destroy();
-  },
-
-  /**
-   * Like SwatchBasedEditorTooltip.addSwatch, but accepts a parser object
-   * to use when previewing the updated property value.
-   *
-   * @param {node} swatchEl
-   *        @see SwatchBasedEditorTooltip.addSwatch
-   * @param {object} callbacks
-   *        @see SwatchBasedEditorTooltip.addSwatch
-   * @param {object} parser
-   *        A parser object; @see OutputParser object
-   * @param {object} options
-   *        options to pass to the output parser, with
-   *          the option |filterSwatch| set.
-   */
-  addSwatch: function (swatchEl, callbacks, parser, options) {
-    SwatchBasedEditorTooltip.prototype.addSwatch.call(this, swatchEl,
-                                                      callbacks);
-    this._parser = parser;
-    this._options = options;
-  }
-});
+module.exports = SwatchCubicBezierTooltip;
copy from devtools/client/shared/widgets/Tooltip.js
copy to devtools/client/shared/widgets/tooltip/SwatchFilterTooltip.js
--- a/devtools/client/shared/widgets/Tooltip.js
+++ b/devtools/client/shared/widgets/tooltip/SwatchFilterTooltip.js
@@ -1,918 +1,21 @@
 /* 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/. */
 
 "use strict";
 
-const defer = require("devtools/shared/defer");
-const {Spectrum} = require("devtools/client/shared/widgets/Spectrum");
-const {CubicBezierWidget} =
-      require("devtools/client/shared/widgets/CubicBezierWidget");
+const {Task} = require("devtools/shared/task");
 const {CSSFilterEditorWidget} = require("devtools/client/shared/widgets/FilterWidget");
-const {TooltipToggle} = require("devtools/client/shared/widgets/tooltip/TooltipToggle");
-const EventEmitter = require("devtools/shared/event-emitter");
-const {colorUtils} = require("devtools/shared/css/color");
+const SwatchBasedEditorTooltip = require("devtools/client/shared/widgets/tooltip/SwatchBasedEditorTooltip");
+
 const Heritage = require("sdk/core/heritage");
-const {HTMLTooltip} = require("devtools/client/shared/widgets/HTMLTooltip");
-const {KeyShortcuts} = require("devtools/client/shared/key-shortcuts");
-const {Task} = require("devtools/shared/task");
-const {KeyCodes} = require("devtools/client/shared/keycodes");
 
 const XHTML_NS = "http://www.w3.org/1999/xhtml";
-const ESCAPE_KEYCODE = KeyCodes.DOM_VK_ESCAPE;
-const POPUP_EVENTS = ["shown", "hidden", "showing", "hiding"];
-
-/**
- * Tooltip widget.
- *
- * This widget is intended at any tool that may need to show rich content in the
- * form of floating panels.
- * A common use case is image previewing in the CSS rule view, but more complex
- * use cases may include color pickers, object inspection, etc...
- *
- * Tooltips are based on XUL (namely XUL arrow-type <panel>s), and therefore
- * need a XUL Document to live in.
- * This is pretty much the only requirement they have on their environment.
- *
- * The way to use a tooltip is simply by instantiating a tooltip yourself and
- * attaching some content in it, or using one of the ready-made content types.
- *
- * A convenient `startTogglingOnHover` method may avoid having to register event
- * handlers yourself if the tooltip has to be shown when hovering over a
- * specific element or group of elements (which is usually the most common case)
- */
-
-/**
- * Container used for dealing with optional parameters.
- *
- * @param {Object} defaults
- *        An object with all default options {p1: v1, p2: v2, ...}
- * @param {Object} options
- *        The actual values.
- */
-function OptionsStore(defaults, options) {
-  this.defaults = defaults || {};
-  this.options = options || {};
-}
-
-OptionsStore.prototype = {
-  /**
-   * Get the value for a given option name.
-   * @return {Object} Returns the value for that option, coming either for the
-   *         actual values that have been set in the constructor, or from the
-   *         defaults if that options was not specified.
-   */
-  get: function (name) {
-    if (typeof this.options[name] !== "undefined") {
-      return this.options[name];
-    }
-    return this.defaults[name];
-  }
-};
-
-/**
- * The low level structure of a tooltip is a XUL element (a <panel>).
- */
-var PanelFactory = {
-  /**
-   * Get a new XUL panel instance.
-   * @param {XULDocument} doc
-   *        The XUL document to put that panel into
-   * @param {OptionsStore} options
-   *        An options store to get some configuration from
-   */
-  get: function (doc, options) {
-    // Create the tooltip
-    let panel = doc.createElement("panel");
-    panel.setAttribute("hidden", true);
-    panel.setAttribute("ignorekeys", true);
-    panel.setAttribute("animate", false);
-
-    panel.setAttribute("consumeoutsideclicks",
-                       options.get("consumeOutsideClick"));
-    panel.setAttribute("noautofocus", options.get("noAutoFocus"));
-    panel.setAttribute("type", "arrow");
-    panel.setAttribute("level", "top");
-
-    panel.setAttribute("class", "devtools-tooltip theme-tooltip-panel");
-    doc.querySelector("window").appendChild(panel);
-
-    return panel;
-  }
-};
-
-/**
- * Tooltip class.
- *
- * Basic usage:
- *   let t = new Tooltip(xulDoc);
- *   t.content = someXulContent;
- *   t.show();
- *   t.hide();
- *   t.destroy();
- *
- * Better usage:
- *   let t = new Tooltip(xulDoc);
- *   t.startTogglingOnHover(container, target => {
- *     if (<condition based on target>) {
- *       t.content = el;
- *       return true;
- *     }
- *   });
- *   t.destroy();
- *
- * @param {XULDocument} doc
- *        The XUL document hosting this tooltip
- * @param {Object} options
- *        Optional options that give options to consumers:
- *        - consumeOutsideClick {Boolean} Wether the first click outside of the
- *        tooltip should close the tooltip and be consumed or not.
- *        Defaults to false.
- *        - closeOnKeys {Array} An array of key codes that should close the
- *        tooltip. Defaults to [27] (escape key).
- *        - closeOnEvents [{emitter: {Object}, event: {String},
- *                          useCapture: {Boolean}}]
- *        Provide an optional list of emitter objects and event names here to
- *        trigger the closing of the tooltip when these events are fired by the
- *        emitters. The emitter objects should either implement
- *        on/off(event, cb) or addEventListener/removeEventListener(event, cb).
- *        Defaults to [].
- *        For instance, the following would close the tooltip whenever the
- *        toolbox selects a new tool and when a DOM node gets scrolled:
- *        new Tooltip(doc, {
- *          closeOnEvents: [
- *            {emitter: toolbox, event: "select"},
- *            {emitter: myContainer, event: "scroll", useCapture: true}
- *          ]
- *        });
- *        - noAutoFocus {Boolean} Should the focus automatically go to the panel
- *        when it opens. Defaults to true.
- *
- * Fires these events:
- * - showing : just before the tooltip shows
- * - shown : when the tooltip is shown
- * - hiding : just before the tooltip closes
- * - hidden : when the tooltip gets hidden
- * - keypress : when any key gets pressed, with keyCode
- */
-function Tooltip(doc, options) {
-  EventEmitter.decorate(this);
-
-  this.doc = doc;
-  this.options = new OptionsStore({
-    consumeOutsideClick: false,
-    closeOnKeys: [ESCAPE_KEYCODE],
-    noAutoFocus: true,
-    closeOnEvents: []
-  }, options);
-  this.panel = PanelFactory.get(doc, this.options);
-
-  // Create tooltip toggle helper and decorate the Tooltip instance with
-  // shortcut methods.
-  this._toggle = new TooltipToggle(this);
-  this.startTogglingOnHover = this._toggle.start.bind(this._toggle);
-  this.stopTogglingOnHover = this._toggle.stop.bind(this._toggle);
-
-  // Emit show/hide events when the panel does.
-  for (let eventName of POPUP_EVENTS) {
-    this["_onPopup" + eventName] = (name => {
-      return e => {
-        if (e.target === this.panel) {
-          this.emit(name);
-        }
-      };
-    })(eventName);
-    this.panel.addEventListener("popup" + eventName,
-      this["_onPopup" + eventName], false);
-  }
-
-  // Listen to keypress events to close the tooltip if configured to do so
-  let win = this.doc.querySelector("window");
-  this._onKeyPress = event => {
-    if (this.panel.hidden) {
-      return;
-    }
-
-    this.emit("keypress", event.keyCode);
-    if (this.options.get("closeOnKeys").indexOf(event.keyCode) !== -1 &&
-        this.isShown()) {
-      event.stopPropagation();
-      this.hide();
-    }
-  };
-  win.addEventListener("keypress", this._onKeyPress, false);
-
-  // Listen to custom emitters' events to close the tooltip
-  this.hide = this.hide.bind(this);
-  let closeOnEvents = this.options.get("closeOnEvents");
-  for (let {emitter, event, useCapture} of closeOnEvents) {
-    for (let add of ["addEventListener", "on"]) {
-      if (add in emitter) {
-        emitter[add](event, this.hide, useCapture);
-        break;
-      }
-    }
-  }
-}
-
-module.exports.Tooltip = Tooltip;
-
-Tooltip.prototype = {
-  defaultPosition: "before_start",
-  // px
-  defaultOffsetX: 0,
-  // px
-  defaultOffsetY: 0,
-  // px
-
-  /**
-   * Show the tooltip. It might be wise to append some content first if you
-   * don't want the tooltip to be empty. You may access the content of the
-   * tooltip by setting a XUL node to t.content.
-   * @param {node} anchor
-   *        Which node should the tooltip be shown on
-   * @param {string} position [optional]
-   *        Optional tooltip position. Defaults to before_start
-   *        https://developer.mozilla.org/en-US/docs/XUL/PopupGuide/Positioning
-   * @param {number} x, y [optional]
-   *        The left and top offset coordinates, in pixels.
-   */
-  show: function (anchor,
-    position = this.defaultPosition,
-    x = this.defaultOffsetX,
-    y = this.defaultOffsetY) {
-    this.panel.hidden = false;
-    this.panel.openPopup(anchor, position, x, y);
-  },
-
-  /**
-   * Hide the tooltip
-   */
-  hide: function () {
-    this.panel.hidden = true;
-    this.panel.hidePopup();
-  },
-
-  isShown: function () {
-    return this.panel &&
-           this.panel.state !== "closed" &&
-           this.panel.state !== "hiding";
-  },
-
-  setSize: function (width, height) {
-    this.panel.sizeTo(width, height);
-  },
-
-  /**
-   * Empty the tooltip's content
-   */
-  empty: function () {
-    while (this.panel.hasChildNodes()) {
-      this.panel.removeChild(this.panel.firstChild);
-    }
-  },
-
-  /**
-   * Gets this panel's visibility state.
-   * @return boolean
-   */
-  isHidden: function () {
-    return this.panel.state == "closed" || this.panel.state == "hiding";
-  },
-
-  /**
-   * Gets if this panel has any child nodes.
-   * @return boolean
-   */
-  isEmpty: function () {
-    return !this.panel.hasChildNodes();
-  },
-
-  /**
-   * Get rid of references and event listeners
-   */
-  destroy: function () {
-    this.hide();
-
-    for (let eventName of POPUP_EVENTS) {
-      this.panel.removeEventListener("popup" + eventName,
-        this["_onPopup" + eventName], false);
-    }
-
-    let win = this.doc.querySelector("window");
-    win.removeEventListener("keypress", this._onKeyPress, false);
-
-    let closeOnEvents = this.options.get("closeOnEvents");
-    for (let {emitter, event, useCapture} of closeOnEvents) {
-      for (let remove of ["removeEventListener", "off"]) {
-        if (remove in emitter) {
-          emitter[remove](event, this.hide, useCapture);
-          break;
-        }
-      }
-    }
-
-    this.content = null;
-
-    this._toggle.destroy();
-
-    this.doc = null;
-
-    this.panel.remove();
-    this.panel = null;
-  },
-
-  /**
-   * Returns the outer container node (that includes the arrow etc.). Happens
-   * to be identical to this.panel here, can be different element in other
-   * Tooltip implementations.
-   */
-  get container() {
-    return this.panel;
-  },
-
-  /**
-   * Set the content of this tooltip. Will first empty the tooltip and then
-   * append the new content element.
-   * Consider using one of the set<type>Content() functions instead.
-   * @param {node} content
-   *        A node that can be appended in the tooltip XUL element
-   */
-  set content(content) {
-    if (this.content == content) {
-      return;
-    }
-
-    this.empty();
-    this.panel.removeAttribute("clamped-dimensions");
-    this.panel.removeAttribute("clamped-dimensions-no-min-height");
-    this.panel.removeAttribute("clamped-dimensions-no-max-or-min-height");
-    this.panel.removeAttribute("wide");
-
-    if (content) {
-      this.panel.appendChild(content);
-    }
-  },
-
-  get content() {
-    return this.panel.firstChild;
-  },
-
-  /**
-   * Sets some text as the content of this tooltip.
-   *
-   * @param {array} messages
-   *        A list of text messages.
-   * @param {string} messagesClass [optional]
-   *        A style class for the text messages.
-   * @param {string} containerClass [optional]
-   *        A style class for the text messages container.
-   * @param {boolean} isAlertTooltip [optional]
-   *        Pass true to add an alert image for your tooltip.
-   */
-  setTextContent: function (
-    {
-      messages,
-      messagesClass,
-      containerClass,
-      isAlertTooltip
-    },
-    extraButtons = []) {
-    messagesClass = messagesClass || "default-tooltip-simple-text-colors";
-    containerClass = containerClass || "default-tooltip-simple-text-colors";
-
-    let vbox = this.doc.createElement("vbox");
-    vbox.className = "devtools-tooltip-simple-text-container " + containerClass;
-    vbox.setAttribute("flex", "1");
-
-    for (let text of messages) {
-      let description = this.doc.createElement("description");
-      description.setAttribute("flex", "1");
-      description.className = "devtools-tooltip-simple-text " + messagesClass;
-      description.textContent = text;
-      vbox.appendChild(description);
-    }
-
-    for (let { label, className, command } of extraButtons) {
-      let button = this.doc.createElement("button");
-      button.className = className;
-      button.setAttribute("label", label);
-      button.addEventListener("command", command);
-      vbox.appendChild(button);
-    }
-
-    if (isAlertTooltip) {
-      let hbox = this.doc.createElement("hbox");
-      hbox.setAttribute("align", "start");
-
-      let alertImg = this.doc.createElement("image");
-      alertImg.className = "devtools-tooltip-alert-icon";
-      hbox.appendChild(alertImg);
-      hbox.appendChild(vbox);
-      this.content = hbox;
-    } else {
-      this.content = vbox;
-    }
-  },
-
-  /**
-   * Load a document into an iframe, and set the iframe
-   * to be the tooltip's content.
-   *
-   * Used by tooltips that want to load their interface
-   * into an iframe from a URL.
-   *
-   * @param {string} width
-   *        Width of the iframe.
-   * @param {string} height
-   *        Height of the iframe.
-   * @param {string} url
-   *        URL of the document to load into the iframe.
-   *
-   * @return {promise} A promise which is resolved with
-   * the iframe.
-   *
-   * This function creates an iframe, loads the specified document
-   * into it, sets the tooltip's content to the iframe, and returns
-   * a promise.
-   *
-   * When the document is loaded, the function gets the content window
-   * and resolves the promise with the content window.
-   */
-  setIFrameContent: function ({width, height}, url) {
-    let def = defer();
-
-    // Create an iframe
-    let iframe = this.doc.createElementNS(XHTML_NS, "iframe");
-    iframe.setAttribute("transparent", true);
-    iframe.setAttribute("width", width);
-    iframe.setAttribute("height", height);
-    iframe.setAttribute("flex", "1");
-    iframe.setAttribute("tooltip", "aHTMLTooltip");
-    iframe.setAttribute("class", "devtools-tooltip-iframe");
-
-    // Wait for the load to initialize the widget
-    function onLoad() {
-      iframe.removeEventListener("load", onLoad, true);
-      def.resolve(iframe);
-    }
-    iframe.addEventListener("load", onLoad, true);
-
-    // load the document from url into the iframe
-    iframe.setAttribute("src", url);
-
-    // Put the iframe in the tooltip
-    this.content = iframe;
-
-    return def.promise;
-  }
-};
-
-/**
- * Base class for all (color, gradient, ...)-swatch based value editors inside
- * tooltips
- *
- * @param {Toolbox} toolbox
- *        The devtools toolbox, needed to get the devtools main window.
- */
-function SwatchBasedEditorTooltip(toolbox, stylesheet) {
-  EventEmitter.decorate(this);
-  // Creating a tooltip instance
-  // This one will consume outside clicks as it makes more sense to let the user
-  // close the tooltip by clicking out
-  // It will also close on <escape> and <enter>
-  this.tooltip = new HTMLTooltip(toolbox, {
-    type: "arrow",
-    consumeOutsideClicks: true,
-    useXulWrapper: true,
-    stylesheet
-  });
-
-  // By default, swatch-based editor tooltips revert value change on <esc> and
-  // commit value change on <enter>
-  this.shortcuts = new KeyShortcuts({
-    window: this.tooltip.topWindow
-  });
-  this.shortcuts.on("Escape", (name, event) => {
-    if (!this.tooltip.isVisible()) {
-      return;
-    }
-    this.revert();
-    this.hide();
-    event.stopPropagation();
-    event.preventDefault();
-  });
-  this.shortcuts.on("Return", (name, event) => {
-    if (!this.tooltip.isVisible()) {
-      return;
-    }
-    this.commit();
-    this.hide();
-    event.stopPropagation();
-    event.preventDefault();
-  });
-
-  // All target swatches are kept in a map, indexed by swatch DOM elements
-  this.swatches = new Map();
-
-  // When a swatch is clicked, and for as long as the tooltip is shown, the
-  // activeSwatch property will hold the reference to the swatch DOM element
-  // that was clicked
-  this.activeSwatch = null;
-
-  this._onSwatchClick = this._onSwatchClick.bind(this);
-}
-
-SwatchBasedEditorTooltip.prototype = {
-  /**
-   * Show the editor tooltip for the currently active swatch.
-   *
-   * @return {Promise} a promise that resolves once the editor tooltip is displayed, or
-   *         immediately if there is no currently active swatch.
-   */
-  show: function () {
-    if (this.activeSwatch) {
-      let onShown = this.tooltip.once("shown");
-      this.tooltip.show(this.activeSwatch, "topcenter bottomleft");
-
-      // When the tooltip is closed by clicking outside the panel we want to
-      // commit any changes.
-      this.tooltip.once("hidden", () => {
-        if (!this._reverted && !this.eyedropperOpen) {
-          this.commit();
-        }
-        this._reverted = false;
-
-        // Once the tooltip is hidden we need to clean up any remaining objects.
-        if (!this.eyedropperOpen) {
-          this.activeSwatch = null;
-        }
-      });
-
-      return onShown;
-    }
-
-    return Promise.resolve();
-  },
-
-  hide: function () {
-    this.tooltip.hide();
-  },
-
-  /**
-   * Add a new swatch DOM element to the list of swatch elements this editor
-   * tooltip knows about. That means from now on, clicking on that swatch will
-   * toggle the editor.
-   *
-   * @param {node} swatchEl
-   *        The element to add
-   * @param {object} callbacks
-   *        Callbacks that will be executed when the editor wants to preview a
-   *        value change, or revert a change, or commit a change.
-   *        - onShow: will be called when one of the swatch tooltip is shown
-   *        - onPreview: will be called when one of the sub-classes calls
-   *        preview
-   *        - onRevert: will be called when the user ESCapes out of the tooltip
-   *        - onCommit: will be called when the user presses ENTER or clicks
-   *        outside the tooltip.
-   */
-  addSwatch: function (swatchEl, callbacks = {}) {
-    if (!callbacks.onShow) {
-      callbacks.onShow = function () {};
-    }
-    if (!callbacks.onPreview) {
-      callbacks.onPreview = function () {};
-    }
-    if (!callbacks.onRevert) {
-      callbacks.onRevert = function () {};
-    }
-    if (!callbacks.onCommit) {
-      callbacks.onCommit = function () {};
-    }
-
-    this.swatches.set(swatchEl, {
-      callbacks: callbacks
-    });
-    swatchEl.addEventListener("click", this._onSwatchClick, false);
-  },
-
-  removeSwatch: function (swatchEl) {
-    if (this.swatches.has(swatchEl)) {
-      if (this.activeSwatch === swatchEl) {
-        this.hide();
-        this.activeSwatch = null;
-      }
-      swatchEl.removeEventListener("click", this._onSwatchClick, false);
-      this.swatches.delete(swatchEl);
-    }
-  },
-
-  _onSwatchClick: function (event) {
-    let swatch = this.swatches.get(event.target);
-
-    if (event.shiftKey) {
-      event.stopPropagation();
-      return;
-    }
-    if (swatch) {
-      this.activeSwatch = event.target;
-      this.show();
-      swatch.callbacks.onShow();
-      event.stopPropagation();
-    }
-  },
-
-  /**
-   * Not called by this parent class, needs to be taken care of by sub-classes
-   */
-  preview: function (value) {
-    if (this.activeSwatch) {
-      let swatch = this.swatches.get(this.activeSwatch);
-      swatch.callbacks.onPreview(value);
-    }
-  },
-
-  /**
-   * This parent class only calls this on <esc> keypress
-   */
-  revert: function () {
-    if (this.activeSwatch) {
-      this._reverted = true;
-      let swatch = this.swatches.get(this.activeSwatch);
-      this.tooltip.once("hidden", () => {
-        swatch.callbacks.onRevert();
-      });
-    }
-  },
-
-  /**
-   * This parent class only calls this on <enter> keypress
-   */
-  commit: function () {
-    if (this.activeSwatch) {
-      let swatch = this.swatches.get(this.activeSwatch);
-      swatch.callbacks.onCommit();
-    }
-  },
-
-  destroy: function () {
-    this.swatches.clear();
-    this.activeSwatch = null;
-    this.tooltip.off("keypress", this._onTooltipKeypress);
-    this.tooltip.destroy();
-    this.shortcuts.destroy();
-  }
-};
-
-/**
- * The swatch color picker tooltip class is a specific class meant to be used
- * along with output-parser's generated color swatches.
- * It extends the parent SwatchBasedEditorTooltip class.
- * It just wraps a standard Tooltip and sets its content with an instance of a
- * color picker.
- *
- * @param {Toolbox} toolbox
- *        The devtools toolbox, needed to get the devtools main window.
- * @param {InspectorPanel} inspector
- *        The inspector panel, needed for the eyedropper.
- */
-function SwatchColorPickerTooltip(toolbox, inspector) {
-  let stylesheet = "chrome://devtools/content/shared/widgets/spectrum.css";
-  SwatchBasedEditorTooltip.call(this, toolbox, stylesheet);
-
-  this.inspector = inspector;
-
-  // Creating a spectrum instance. this.spectrum will always be a promise that
-  // resolves to the spectrum instance
-  this.spectrum = this.setColorPickerContent([0, 0, 0, 1]);
-  this._onSpectrumColorChange = this._onSpectrumColorChange.bind(this);
-  this._openEyeDropper = this._openEyeDropper.bind(this);
-}
-
-module.exports.SwatchColorPickerTooltip = SwatchColorPickerTooltip;
-
-SwatchColorPickerTooltip.prototype =
-Heritage.extend(SwatchBasedEditorTooltip.prototype, {
-  /**
-   * Fill the tooltip with a new instance of the spectrum color picker widget
-   * initialized with the given color, and return the instance of spectrum
-   */
-  setColorPickerContent: function (color) {
-    let { doc } = this.tooltip;
-
-    let container = doc.createElementNS(XHTML_NS, "div");
-    container.id = "spectrum-tooltip";
-    let spectrumNode = doc.createElementNS(XHTML_NS, "div");
-    spectrumNode.id = "spectrum";
-    container.appendChild(spectrumNode);
-    let eyedropper = doc.createElementNS(XHTML_NS, "button");
-    eyedropper.id = "eyedropper-button";
-    eyedropper.className = "devtools-button";
-    container.appendChild(eyedropper);
-
-    this.tooltip.setContent(container, { width: 218, height: 224 });
-
-    let spectrum = new Spectrum(spectrumNode, color);
-
-    // Wait for the tooltip to be shown before calling spectrum.show
-    // as it expect to be visible in order to compute DOM element sizes.
-    this.tooltip.once("shown", () => {
-      spectrum.show();
-    });
-
-    return spectrum;
-  },
-
-  /**
-   * Overriding the SwatchBasedEditorTooltip.show function to set spectrum's
-   * color.
-   */
-  show: Task.async(function* () {
-    // Call then parent class' show function
-    yield SwatchBasedEditorTooltip.prototype.show.call(this);
-    // Then set spectrum's color and listen to color changes to preview them
-    if (this.activeSwatch) {
-      this.currentSwatchColor = this.activeSwatch.nextSibling;
-      this._originalColor = this.currentSwatchColor.textContent;
-      let color = this.activeSwatch.style.backgroundColor;
-      this.spectrum.off("changed", this._onSpectrumColorChange);
-      this.spectrum.rgb = this._colorToRgba(color);
-      this.spectrum.on("changed", this._onSpectrumColorChange);
-      this.spectrum.updateUI();
-    }
-
-    let {target} = this.inspector;
-    target.actorHasMethod("inspector", "pickColorFromPage").then(value => {
-      let tooltipDoc = this.tooltip.doc;
-      let eyeButton = tooltipDoc.querySelector("#eyedropper-button");
-      if (value && this.inspector.selection.nodeFront.isInHTMLDocument) {
-        eyeButton.addEventListener("click", this._openEyeDropper);
-      } else {
-        eyeButton.style.display = "none";
-      }
-      this.emit("ready");
-    }, e => console.error(e));
-  }),
-
-  _onSpectrumColorChange: function (event, rgba, cssColor) {
-    this._selectColor(cssColor);
-  },
-
-  _selectColor: function (color) {
-    if (this.activeSwatch) {
-      this.activeSwatch.style.backgroundColor = color;
-      this.activeSwatch.parentNode.dataset.color = color;
-
-      color = this._toDefaultType(color);
-      this.currentSwatchColor.textContent = color;
-      this.preview(color);
-
-      if (this.eyedropperOpen) {
-        this.commit();
-      }
-    }
-  },
-
-  _openEyeDropper: function () {
-    let {inspector, toolbox, telemetry} = this.inspector;
-    telemetry.toolOpened("pickereyedropper");
-    inspector.pickColorFromPage(toolbox, {copyOnSelect: false}).then(() => {
-      this.eyedropperOpen = true;
-
-      // close the colorpicker tooltip so that only the eyedropper is open.
-      this.hide();
-
-      this.tooltip.emit("eyedropper-opened");
-    }, e => console.error(e));
-
-    inspector.once("color-picked", color => {
-      toolbox.win.focus();
-      this._selectColor(color);
-      this._onEyeDropperDone();
-    });
-
-    inspector.once("color-pick-canceled", () => {
-      this._onEyeDropperDone();
-    });
-  },
-
-  _onEyeDropperDone: function () {
-    this.eyedropperOpen = false;
-    this.activeSwatch = null;
-  },
-
-  _colorToRgba: function (color) {
-    color = new colorUtils.CssColor(color);
-    let rgba = color._getRGBATuple();
-    return [rgba.r, rgba.g, rgba.b, rgba.a];
-  },
-
-  _toDefaultType: function (color) {
-    let colorObj = new colorUtils.CssColor(color);
-    colorObj.setAuthoredUnitFromColor(this._originalColor);
-    return colorObj.toString();
-  },
-
-  destroy: function () {
-    SwatchBasedEditorTooltip.prototype.destroy.call(this);
-    this.inspector = null;
-    this.currentSwatchColor = null;
-    this.spectrum.off("changed", this._onSpectrumColorChange);
-    this.spectrum.destroy();
-  }
-});
-
-/**
- * The swatch cubic-bezier tooltip class is a specific class meant to be used
- * along with rule-view's generated cubic-bezier swatches.
- * It extends the parent SwatchBasedEditorTooltip class.
- * It just wraps a standard Tooltip and sets its content with an instance of a
- * CubicBezierWidget.
- *
- * @param {Toolbox} toolbox
- *        The devtools toolbox, needed to get the devtools main window.
- */
-function SwatchCubicBezierTooltip(toolbox) {
-  let stylesheet = "chrome://devtools/content/shared/widgets/cubic-bezier.css";
-  SwatchBasedEditorTooltip.call(this, toolbox, stylesheet);
-
-  // Creating a cubic-bezier instance.
-  // this.widget will always be a promise that resolves to the widget instance
-  this.widget = this.setCubicBezierContent([0, 0, 1, 1]);
-  this._onUpdate = this._onUpdate.bind(this);
-}
-
-module.exports.SwatchCubicBezierTooltip = SwatchCubicBezierTooltip;
-
-SwatchCubicBezierTooltip.prototype =
-Heritage.extend(SwatchBasedEditorTooltip.prototype, {
-  /**
-   * Fill the tooltip with a new instance of the cubic-bezier widget
-   * initialized with the given value, and return a promise that resolves to
-   * the instance of the widget
-   */
-  setCubicBezierContent: function (bezier) {
-    let { doc } = this.tooltip;
-
-    let container = doc.createElementNS(XHTML_NS, "div");
-    container.className = "cubic-bezier-container";
-
-    this.tooltip.setContent(container, { width: 510, height: 370 });
-
-    let def = defer();
-
-    // Wait for the tooltip to be shown before calling instanciating the widget
-    // as it expect its DOM elements to be visible.
-    this.tooltip.once("shown", () => {
-      let widget = new CubicBezierWidget(container, bezier);
-      def.resolve(widget);
-    });
-
-    return def.promise;
-  },
-
-  /**
-   * Overriding the SwatchBasedEditorTooltip.show function to set the cubic
-   * bezier curve in the widget
-   */
-  show: Task.async(function* () {
-    // Call the parent class' show function
-    yield SwatchBasedEditorTooltip.prototype.show.call(this);
-    // Then set the curve and listen to changes to preview them
-    if (this.activeSwatch) {
-      this.currentBezierValue = this.activeSwatch.nextSibling;
-      this.widget.then(widget => {
-        widget.off("updated", this._onUpdate);
-        widget.cssCubicBezierValue = this.currentBezierValue.textContent;
-        widget.on("updated", this._onUpdate);
-        this.emit("ready");
-      });
-    }
-  }),
-
-  _onUpdate: function (event, bezier) {
-    if (!this.activeSwatch) {
-      return;
-    }
-
-    this.currentBezierValue.textContent = bezier + "";
-    this.preview(bezier + "");
-  },
-
-  destroy: function () {
-    SwatchBasedEditorTooltip.prototype.destroy.call(this);
-    this.currentBezierValue = null;
-    this.widget.then(widget => {
-      widget.off("updated", this._onUpdate);
-      widget.destroy();
-    });
-  }
-});
 
 /**
  * The swatch-based css filter tooltip class is a specific class meant to be
  * used along with rule-view's generated css filter swatches.
  * It extends the parent SwatchBasedEditorTooltip class.
  * It just wraps a standard Tooltip and sets its content with an instance of a
  * CSSFilterEditorWidget.
  *
@@ -927,20 +30,17 @@ function SwatchFilterTooltip(toolbox, cs
   SwatchBasedEditorTooltip.call(this, toolbox, stylesheet);
   this._cssIsValid = cssIsValid;
 
   // Creating a filter editor instance.
   this.widget = this.setFilterContent("none");
   this._onUpdate = this._onUpdate.bind(this);
 }
 
-exports.SwatchFilterTooltip = SwatchFilterTooltip;
-
-SwatchFilterTooltip.prototype =
-Heritage.extend(SwatchBasedEditorTooltip.prototype, {
+SwatchFilterTooltip.prototype = Heritage.extend(SwatchBasedEditorTooltip.prototype, {
   /**
    * Fill the tooltip with a new instance of the CSSFilterEditorWidget
    * widget initialized with the given filter value, and return a promise
    * that resolves to the instance of the widget when ready.
    */
   setFilterContent: function (filter) {
     let { doc } = this.tooltip;
 
@@ -1005,8 +105,10 @@ Heritage.extend(SwatchBasedEditorTooltip
    */
   addSwatch: function (swatchEl, callbacks, parser, options) {
     SwatchBasedEditorTooltip.prototype.addSwatch.call(this, swatchEl,
                                                       callbacks);
     this._parser = parser;
     this._options = options;
   }
 });
+
+module.exports = SwatchFilterTooltip;
--- a/devtools/client/shared/widgets/tooltip/moz.build
+++ b/devtools/client/shared/widgets/tooltip/moz.build
@@ -3,11 +3,15 @@
 # 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/.
 
 DevToolsModules(
     'CssDocsTooltip.js',
     'EventTooltipHelper.js',
     'ImageTooltipHelper.js',
+    'SwatchBasedEditorTooltip.js',
+    'SwatchColorPickerTooltip.js',
+    'SwatchCubicBezierTooltip.js',
+    'SwatchFilterTooltip.js',
     'TooltipToggle.js',
     'VariableContentHelper.js',
 )
--- a/devtools/client/webconsole/jsterm.js
+++ b/devtools/client/webconsole/jsterm.js
@@ -371,18 +371,17 @@ JSTerm.prototype = {
     if (!errorMessage && result && typeof result == "object" &&
         result.type == "undefined" &&
         helperResult && !helperHasRawOutput) {
       callback && callback();
       return;
     }
 
     if (this.hud.NEW_CONSOLE_OUTPUT_ENABLED) {
-      this.hud.newConsoleOutput.dispatchMessageAdd(response);
-      callback && callback();
+      this.hud.newConsoleOutput.dispatchMessageAdd(response, true).then(callback);
       return;
     }
     let msg = new Messages.JavaScriptEvalOutput(response,
                                                 errorMessage, errorDocLink);
     this.hud.output.addMessage(msg);
 
     if (callback) {
       let oldFlushCallback = this.hud._flushCallback;
--- a/devtools/client/webconsole/new-console-output/components/console-output.js
+++ b/devtools/client/webconsole/new-console-output/components/console-output.js
@@ -16,29 +16,27 @@ const { getAllMessages, getAllMessagesUi
 const { getScrollSetting } = require("devtools/client/webconsole/new-console-output/selectors/ui");
 const MessageContainer = createFactory(require("devtools/client/webconsole/new-console-output/components/message-container").MessageContainer);
 
 const ConsoleOutput = createClass({
 
   displayName: "ConsoleOutput",
 
   propTypes: {
-    hudProxyClient: PropTypes.object.isRequired,
     messages: PropTypes.object.isRequired,
     messagesUi: PropTypes.object.isRequired,
-    sourceMapService: PropTypes.object,
-    onViewSourceInDebugger: PropTypes.func.isRequired,
-    openNetworkPanel: PropTypes.func.isRequired,
-    openLink: PropTypes.func.isRequired,
-    emitNewMessage: PropTypes.func.isRequired,
+    serviceContainer: PropTypes.shape({
+      attachRefToHud: PropTypes.func.isRequired,
+    }),
     autoscroll: PropTypes.bool.isRequired,
   },
 
   componentDidMount() {
     scrollToBottom(this.outputNode);
+    this.props.serviceContainer.attachRefToHud("outputWrapper", this.outputNode);
   },
 
   componentWillUpdate(nextProps, nextState) {
     if (!this.outputNode) {
       return;
     }
 
     const outputNode = this.outputNode;
@@ -55,39 +53,29 @@ const ConsoleOutput = createClass({
       scrollToBottom(this.outputNode);
     }
   },
 
   render() {
     let {
       dispatch,
       autoscroll,
-      hudProxyClient,
       messages,
       messagesUi,
       messagesTableData,
-      sourceMapService,
-      onViewSourceInDebugger,
-      openNetworkPanel,
-      openLink,
-      emitNewMessage,
+      serviceContainer,
     } = this.props;
 
     let messageNodes = messages.map((message) => {
       return (
         MessageContainer({
           dispatch,
-          hudProxyClient,
           message,
           key: message.id,
-          sourceMapService,
-          onViewSourceInDebugger,
-          openNetworkPanel,
-          openLink,
-          emitNewMessage,
+          serviceContainer,
           open: messagesUi.includes(message.id),
           tableData: messagesTableData.get(message.id),
           autoscroll,
         })
       );
     });
     return (
       dom.div({
--- a/devtools/client/webconsole/new-console-output/components/console-table.js
+++ b/devtools/client/webconsole/new-console-output/components/console-table.js
@@ -19,28 +19,30 @@ const TABLE_COLUMN_MAX_ITEMS = 10;
 
 const ConsoleTable = createClass({
 
   displayName: "ConsoleTable",
 
   propTypes: {
     dispatch: PropTypes.func.isRequired,
     parameters: PropTypes.array.isRequired,
-    hudProxyClient: PropTypes.object.isRequired,
+    serviceContainer: PropTypes.shape({
+      hudProxyClient: PropTypes.object.isRequired,
+    }),
     id: PropTypes.string.isRequired,
   },
 
   componentWillMount: function () {
-    const {id, dispatch, hudProxyClient, parameters} = this.props;
+    const {id, dispatch, serviceContainer, parameters} = this.props;
 
     if (!Array.isArray(parameters) || parameters.length === 0) {
       return;
     }
 
-    const client = new ObjectClient(hudProxyClient, parameters[0]);
+    const client = new ObjectClient(serviceContainer.hudProxyClient, parameters[0]);
     let dataType = getParametersDataType(parameters);
 
     // Get all the object properties.
     dispatch(actions.messageTableDataGet(id, client, dataType));
   },
 
   getHeaders: function (columns) {
     let headerItems = [];
--- a/devtools/client/webconsole/new-console-output/components/filter-bar.js
+++ b/devtools/client/webconsole/new-console-output/components/filter-bar.js
@@ -21,19 +21,27 @@ const {
 const FilterButton = createFactory(require("devtools/client/webconsole/new-console-output/components/filter-button"));
 
 const FilterBar = createClass({
 
   displayName: "FilterBar",
 
   propTypes: {
     filter: PropTypes.object.isRequired,
+    serviceContainer: PropTypes.shape({
+      attachRefToHud: PropTypes.func.isRequired,
+    }).isRequired,
     ui: PropTypes.object.isRequired
   },
 
+  componentDidMount() {
+    this.props.serviceContainer.attachRefToHud("filterBox",
+      this.wrapperNode.querySelector(".text-filter"));
+  },
+
   onClickMessagesClear: function () {
     this.props.dispatch(messagesClear());
   },
 
   onClickFilterBarToggle: function () {
     this.props.dispatch(uiActions.filterBarToggle());
   },
 
@@ -58,17 +66,17 @@ const FilterBar = createClass({
       }),
       dom.button({
         className: "devtools-button devtools-filter-icon" + (
           filterBarVisible ? " checked" : ""),
         title: "Toggle filter bar",
         onClick: this.onClickFilterBarToggle
       }),
       dom.input({
-        className: "devtools-plaininput",
+        className: "devtools-plaininput text-filter",
         type: "search",
         value: filter.text,
         placeholder: "Filter output",
         onInput: this.onSearchInput
       })
     ));
 
     if (filterBarVisible) {
@@ -136,18 +144,22 @@ const FilterBar = createClass({
             className: "menu-filter-button",
             onClick: this.onClickFiltersClear
           }, "Remove filters")
         )
       );
     }
 
     return (
-      dom.div({className: "webconsole-filteringbar-wrapper"},
-        ...children
+      dom.div({
+        className: "webconsole-filteringbar-wrapper",
+        ref: node => {
+          this.wrapperNode = node;
+        }
+      }, ...children
       )
     );
   }
 });
 
 function mapStateToProps(state) {
   return {
     filter: getAllFilters(state),
--- a/devtools/client/webconsole/new-console-output/components/message-container.js
+++ b/devtools/client/webconsole/new-console-output/components/message-container.js
@@ -27,22 +27,18 @@ const componentMap = new Map([
   ["PageError", require("./message-types/page-error")]
 ]);
 
 const MessageContainer = createClass({
   displayName: "MessageContainer",
 
   propTypes: {
     message: PropTypes.object.isRequired,
-    sourceMapService: PropTypes.object,
-    onViewSourceInDebugger: PropTypes.func.isRequired,
-    openNetworkPanel: PropTypes.func.isRequired,
-    openLink: PropTypes.func.isRequired,
     open: PropTypes.bool.isRequired,
-    hudProxyClient: PropTypes.object.isRequired,
+    serviceContainer: PropTypes.object.isRequired,
     autoscroll: PropTypes.bool.isRequired,
   },
 
   getDefaultProps: function () {
     return {
       open: false
     };
   },
--- a/devtools/client/webconsole/new-console-output/components/message-types/console-api-call.js
+++ b/devtools/client/webconsole/new-console-output/components/message-types/console-api-call.js
@@ -16,36 +16,31 @@ const GripMessageBody = createFactory(re
 const ConsoleTable = createFactory(require("devtools/client/webconsole/new-console-output/components/console-table"));
 
 const Message = createFactory(require("devtools/client/webconsole/new-console-output/components/message"));
 
 ConsoleApiCall.displayName = "ConsoleApiCall";
 
 ConsoleApiCall.propTypes = {
   message: PropTypes.object.isRequired,
-  sourceMapService: PropTypes.object,
-  onViewSourceInDebugger: PropTypes.func.isRequired,
   open: PropTypes.bool,
-  hudProxyClient: PropTypes.object.isRequired,
+  serviceContainer: PropTypes.object.isRequired,
 };
 
 ConsoleApiCall.defaultProps = {
   open: false
 };
 
 function ConsoleApiCall(props) {
   const {
     dispatch,
     message,
-    sourceMapService,
-    onViewSourceInDebugger,
     open,
-    hudProxyClient,
     tableData,
-    emitNewMessage,
+    serviceContainer,
   } = props;
   const {
     id: messageId,
     source, type,
     level,
     repeat,
     stacktrace,
     frame,
@@ -67,17 +62,17 @@ function ConsoleApiCall(props) {
     messageBody = message.messageText;
   }
 
   let attachment = null;
   if (type === "table") {
     attachment = ConsoleTable({
       dispatch,
       id: message.id,
-      hudProxyClient,
+      serviceContainer,
       parameters: message.parameters,
       tableData
     });
   }
 
   const topLevelClasses = ["cm-s-mozilla"];
 
   return Message({
@@ -87,19 +82,17 @@ function ConsoleApiCall(props) {
     type,
     level,
     topLevelClasses,
     messageBody,
     repeat,
     frame,
     stacktrace,
     attachment,
-    onViewSourceInDebugger,
-    sourceMapService,
-    emitNewMessage,
+    serviceContainer,
     dispatch,
   });
 }
 
 function formatReps(parameters) {
   return (
     parameters
       // Get all the grips.
--- a/devtools/client/webconsole/new-console-output/components/message-types/console-command.js
+++ b/devtools/client/webconsole/new-console-output/components/message-types/console-command.js
@@ -26,20 +26,25 @@ ConsoleCommand.propTypes = {
 function ConsoleCommand(props) {
   const {
     source,
     type,
     level,
     messageText: messageBody,
   } = props.message;
 
+  const {
+    serviceContainer,
+  } = props;
+
   const childProps = {
     source,
     type,
     level,
     topLevelClasses: [],
     messageBody,
     scrollToMessage: props.autoscroll,
+    serviceContainer,
   };
   return Message(childProps);
 }
 
 module.exports = ConsoleCommand;
--- a/devtools/client/webconsole/new-console-output/components/message-types/evaluation-result.js
+++ b/devtools/client/webconsole/new-console-output/components/message-types/evaluation-result.js
@@ -16,22 +16,22 @@ const GripMessageBody = createFactory(re
 
 EvaluationResult.displayName = "EvaluationResult";
 
 EvaluationResult.propTypes = {
   message: PropTypes.object.isRequired,
 };
 
 function EvaluationResult(props) {
-  const { message } = props;
+  const { message, serviceContainer } = props;
   const {
     source,
     type,
     level,
-    emitNewMessage,
+    id: messageId,
   } = message;
 
   let messageBody;
   if (message.messageText) {
     messageBody = message.messageText;
   } else {
     messageBody = GripMessageBody({grip: message.parameters});
   }
@@ -39,15 +39,16 @@ function EvaluationResult(props) {
   const topLevelClasses = ["cm-s-mozilla"];
 
   const childProps = {
     source,
     type,
     level,
     topLevelClasses,
     messageBody,
+    messageId,
     scrollToMessage: props.autoscroll,
-    emitNewMessage,
+    serviceContainer,
   };
   return Message(childProps);
 }
 
 module.exports = EvaluationResult;
--- a/devtools/client/webconsole/new-console-output/components/message-types/network-event-message.js
+++ b/devtools/client/webconsole/new-console-output/components/message-types/network-event-message.js
@@ -14,27 +14,29 @@ const {
 } = require("devtools/client/shared/vendor/react");
 const Message = createFactory(require("devtools/client/webconsole/new-console-output/components/message"));
 const { l10n } = require("devtools/client/webconsole/new-console-output/utils/messages");
 
 NetworkEventMessage.displayName = "NetworkEventMessage";
 
 NetworkEventMessage.propTypes = {
   message: PropTypes.object.isRequired,
-  openNetworkPanel: PropTypes.func.isRequired,
+  serviceContainer: PropTypes.shape({
+    openNetworkPanel: PropTypes.func.isRequired,
+  }),
 };
 
 function NetworkEventMessage(props) {
-  const { message, openNetworkPanel, emitNewMessage } = props;
+  const { message, serviceContainer } = props;
   const { actor, source, type, level, request, isXHR } = message;
 
   const topLevelClasses = [ "cm-s-mozilla" ];
 
   function onUrlClick() {
-    openNetworkPanel(actor);
+    serviceContainer.openNetworkPanel(actor);
   }
 
   const method = dom.span({className: "method" }, request.method);
   const xhr = isXHR
     ? dom.span({ className: "xhr" }, l10n.getStr("webConsoleXhrIndicator"))
     : null;
   const url = dom.a({ className: "url", title: request.url, onClick: onUrlClick },
         request.url.replace(/\?.+/, ""));
@@ -42,14 +44,14 @@ function NetworkEventMessage(props) {
   const messageBody = dom.span({}, method, xhr, url);
 
   const childProps = {
     source,
     type,
     level,
     topLevelClasses,
     messageBody,
-    emitNewMessage,
+    serviceContainer,
   };
   return Message(childProps);
 }
 
 module.exports = NetworkEventMessage;
--- a/devtools/client/webconsole/new-console-output/components/message-types/page-error.js
+++ b/devtools/client/webconsole/new-console-output/components/message-types/page-error.js
@@ -23,19 +23,17 @@ PageError.propTypes = {
 PageError.defaultProps = {
   open: false
 };
 
 function PageError(props) {
   const {
     message,
     open,
-    sourceMapService,
-    onViewSourceInDebugger,
-    emitNewMessage,
+    serviceContainer,
   } = props;
   const {
     id: messageId,
     source,
     type,
     level,
     messageText: messageBody,
     repeat,
@@ -49,16 +47,14 @@ function PageError(props) {
     source,
     type,
     level,
     topLevelClasses: [],
     messageBody,
     repeat,
     frame,
     stacktrace,
-    onViewSourceInDebugger,
-    sourceMapService,
-    emitNewMessage,
+    serviceContainer,
   };
   return Message(childProps);
 }
 
 module.exports = PageError;
--- a/devtools/client/webconsole/new-console-output/components/message.js
+++ b/devtools/client/webconsole/new-console-output/components/message.js
@@ -31,42 +31,48 @@ const Message = createClass({
     topLevelClasses: PropTypes.array.isRequired,
     messageBody: PropTypes.any.isRequired,
     repeat: PropTypes.any,
     frame: PropTypes.any,
     attachment: PropTypes.any,
     stacktrace: PropTypes.any,
     messageId: PropTypes.string,
     scrollToMessage: PropTypes.bool,
-    onViewSourceInDebugger: PropTypes.func,
-    sourceMapService: PropTypes.any,
+    serviceContainer: PropTypes.shape({
+      emitNewMessage: PropTypes.func.isRequired,
+      onViewSourceInDebugger: PropTypes.func.isRequired,
+      sourceMapService: PropTypes.any,
+    }),
   },
 
   componentDidMount() {
-    if (this.messageNode && this.props.emitNewMessage) {
-      this.props.emitNewMessage(this.messageNode);
+    if (this.messageNode) {
       if (this.props.scrollToMessage) {
         this.messageNode.scrollIntoView();
       }
+      // Event used in tests. Some message types don't pass it in because existing tests
+      // did not emit for them.
+      if (this.props.serviceContainer) {
+        this.props.serviceContainer.emitNewMessage(this.messageNode, this.props.messageId);
+      }
     }
   },
 
   render() {
     const {
       messageId,
       open,
       source,
       type,
       level,
       topLevelClasses,
       messageBody,
       frame,
       stacktrace,
-      onViewSourceInDebugger,
-      sourceMapService,
+      serviceContainer,
       dispatch,
     } = this.props;
 
     topLevelClasses.push("message", source, type, level);
     if (open) {
       topLevelClasses.push("open");
     }
 
@@ -74,17 +80,17 @@ const Message = createClass({
 
     // Figure out if there is an expandable part to the message.
     let attachment = null;
     if (this.props.attachment) {
       attachment = this.props.attachment;
     } else if (stacktrace) {
       const child = open ? StackTrace({
         stacktrace: stacktrace,
-        onViewSourceInDebugger: onViewSourceInDebugger
+        onViewSourceInDebugger: serviceContainer.onViewSourceInDebugger
       }) : null;
       attachment = dom.div({ className: "stacktrace devtools-monospace" }, child);
     }
 
     // If there is an expandable part, make it collapsible.
     let collapse = null;
     if (attachment) {
       collapse = CollapseButton({
@@ -101,19 +107,19 @@ const Message = createClass({
 
     const repeat = this.props.repeat ? MessageRepeat({repeat: this.props.repeat}) : null;
 
     // Configure the location.
     const shouldRenderFrame = frame && frame.source !== "debugger eval code";
     const location = dom.span({ className: "message-location devtools-monospace" },
       shouldRenderFrame ? FrameView({
         frame,
-        onClick: onViewSourceInDebugger,
+        onClick: serviceContainer.onViewSourceInDebugger,
         showEmptyPathAsHost: true,
-        sourceMapService
+        sourceMapService: serviceContainer.sourceMapService
       }) : null
     );
 
     return dom.div({
       className: topLevelClasses.join(" "),
       ref: node => {
         this.messageNode = node;
       }
--- a/devtools/client/webconsole/new-console-output/main.js
+++ b/devtools/client/webconsole/new-console-output/main.js
@@ -13,12 +13,12 @@ const { BrowserLoader } = Cu.import("res
 
 // Initialize module loader and load all modules of the new inline
 // preview feature. The entire code-base doesn't need any extra
 // privileges and runs entirely in content scope.
 const NewConsoleOutputWrapper = BrowserLoader({
   baseURI: "resource://devtools/client/webconsole/new-console-output/",
   window}).require("./new-console-output-wrapper");
 
-this.NewConsoleOutput = function (parentNode, jsterm, toolbox, owner, emitNewMessage) {
+this.NewConsoleOutput = function (parentNode, jsterm, toolbox, owner, serviceContainer) {
   console.log("Creating NewConsoleOutput", parentNode, NewConsoleOutputWrapper);
-  return new NewConsoleOutputWrapper(parentNode, jsterm, toolbox, owner, emitNewMessage);
+  return new NewConsoleOutputWrapper(parentNode, jsterm, toolbox, owner, serviceContainer);
 };
--- a/devtools/client/webconsole/new-console-output/new-console-output-wrapper.js
+++ b/devtools/client/webconsole/new-console-output/new-console-output-wrapper.js
@@ -24,60 +24,87 @@ function NewConsoleOutputWrapper(parentN
   this.toolbox = toolbox;
   this.owner = owner;
 
   this.init = this.init.bind(this);
 }
 
 NewConsoleOutputWrapper.prototype = {
   init: function () {
-    const sourceMapService = this.toolbox ? this.toolbox._sourceMapService : null;
+    const attachRefToHud = (id, node) => {
+      this.jsterm.hud[id] = node;
+    };
 
     let childComponent = ConsoleOutput({
-      hudProxyClient: this.jsterm.hud.proxy.client,
-      sourceMapService,
-      onViewSourceInDebugger: frame => this.toolbox.viewSourceInDebugger.call(
-        this.toolbox,
-        frame.url,
-        frame.line
-      ),
-      openNetworkPanel: (requestId) => {
-        return this.toolbox.selectTool("netmonitor").then(panel => {
-          return panel.panelWin.NetMonitorController.inspectRequest(requestId);
-        });
-      },
-      openLink: (url) => {
-        this.owner.openLink(url);
-      },
-      emitNewMessage: (node) => {
-        this.jsterm.hud.emit("new-messages", new Set([{
-          node
-        }]));
-      },
+      serviceContainer: {
+        attachRefToHud,
+        emitNewMessage: (node, messageId) => {
+          this.jsterm.hud.emit("new-messages", new Set([{
+            node,
+            messageId,
+          }]));
+        },
+        hudProxyClient: this.jsterm.hud.proxy.client,
+        onViewSourceInDebugger: frame => this.toolbox.viewSourceInDebugger.call(
+          this.toolbox,
+          frame.url,
+          frame.line
+        ),
+        openNetworkPanel: (requestId) => {
+          return this.toolbox.selectTool("netmonitor").then(panel => {
+            return panel.panelWin.NetMonitorController.inspectRequest(requestId);
+          });
+        },
+        sourceMapService: this.toolbox ? this.toolbox._sourceMapService : null,
+      }
     });
-    let filterBar = FilterBar({});
+    let filterBar = FilterBar({
+      serviceContainer: {
+        attachRefToHud
+      }
+    });
     let provider = React.createElement(
       Provider,
       { store },
       React.DOM.div(
         {className: "webconsole-output-wrapper"},
         filterBar,
         childComponent
     ));
 
     this.body = ReactDOM.render(provider, this.parentNode);
   },
-  dispatchMessageAdd: (message) => {
-    batchedMessageAdd(actions.messageAdd(message));
+  dispatchMessageAdd: function(message, waitForResponse) {
+      let action = actions.messageAdd(message);
+      let messageId = action.message.get("id");
+      batchedMessageAdd(action);
+
+      // Wait for the message to render to resolve with the DOM node.
+      // This is just for backwards compatibility with old tests, and should
+      // be removed once it's not needed anymore.
+      if (waitForResponse) {
+        return new Promise(resolve => {
+          let jsterm = this.jsterm;
+          jsterm.hud.on("new-messages", function onThisMessage(e, messages) {
+            for (let m of messages) {
+              if (m.messageId == messageId) {
+                resolve(m.node);
+                jsterm.hud.off("new-messages", onThisMessage);
+                return;
+              }
+            }
+          });
+        });
+      }
   },
-  dispatchMessagesAdd: (messages) => {
+  dispatchMessagesAdd: function(messages) {
     const batchedActions = messages.map(message => actions.messageAdd(message));
     store.dispatch(actions.batchActions(batchedActions));
   },
-  dispatchMessagesClear: () => {
+  dispatchMessagesClear: function() {
     store.dispatch(actions.messagesClear());
   },
 };
 
 function batchedMessageAdd(action) {
   queuedActions.push(action);
   if (!throttledDispatchTimeout) {
     throttledDispatchTimeout = setTimeout(() => {
--- a/devtools/client/webconsole/new-console-output/test/components/console-api-call.test.js
+++ b/devtools/client/webconsole/new-console-output/test/components/console-api-call.test.js
@@ -9,90 +9,90 @@ const { render } = require("enzyme");
 // React
 const { createFactory } = require("devtools/client/shared/vendor/react");
 
 // Components under test.
 const ConsoleApiCall = createFactory(require("devtools/client/webconsole/new-console-output/components/message-types/console-api-call"));
 
 // Test fakes.
 const { stubPreparedMessages } = require("devtools/client/webconsole/new-console-output/test/fixtures/stubs/index");
-const onViewSourceInDebugger = () => {};
+const serviceContainer = require("devtools/client/webconsole/new-console-output/test/fixtures/serviceContainer");
 
 const tempfilePath = "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js";
 
 describe("ConsoleAPICall component:", () => {
   describe("console.log", () => {
     it("renders string grips", () => {
       const message = stubPreparedMessages.get("console.log('foobar', 'test')");
-      const wrapper = render(ConsoleApiCall({ message, onViewSourceInDebugger }));
+      const wrapper = render(ConsoleApiCall({ message, serviceContainer }));
 
       expect(wrapper.find(".message-body").text()).toBe("foobar test");
       expect(wrapper.find(".objectBox-string").length).toBe(2);
       expect(wrapper.find("div.message.cm-s-mozilla span span.message-flex-body span.message-body.devtools-monospace").length).toBe(1);
 
       // There should be the location
       const locationLink = wrapper.find(`.message-location`);
       expect(locationLink.length).toBe(1);
       expect(locationLink.text()).toBe("test-tempfile.js:1:27");
     });
 
     it("renders repeat node", () => {
       const message =
         stubPreparedMessages.get("console.log('foobar', 'test')")
         .set("repeat", 107);
-      const wrapper = render(ConsoleApiCall({ message, onViewSourceInDebugger }));
+      const wrapper = render(ConsoleApiCall({ message, serviceContainer }));
 
       expect(wrapper.find(".message-repeats").text()).toBe("107");
       expect(wrapper.find(".message-repeats").prop("title")).toBe("107 repeats");
 
       expect(wrapper.find("span > span.message-flex-body > span.message-body.devtools-monospace + span.message-repeats").length).toBe(1);
     });
   });
 
   describe("console.count", () => {
     it("renders", () => {
       const message = stubPreparedMessages.get("console.count('bar')");
-      const wrapper = render(ConsoleApiCall({ message, onViewSourceInDebugger }));
+      const wrapper = render(ConsoleApiCall({ message, serviceContainer }));
 
       expect(wrapper.find(".message-body").text()).toBe("bar: 1");
     });
   });
 
   describe("console.assert", () => {
     it("renders", () => {
       const message = stubPreparedMessages.get("console.assert(false, {message: 'foobar'})");
-      const wrapper = render(ConsoleApiCall({ message, onViewSourceInDebugger }));
+      const wrapper = render(ConsoleApiCall({ message, serviceContainer }));
 
       expect(wrapper.find(".message-body").text()).toBe("Assertion failed: Object { message: \"foobar\" }");
     });
   });
 
   describe("console.time", () => {
     it("does not show anything", () => {
       const message = stubPreparedMessages.get("console.time('bar')");
-      const wrapper = render(ConsoleApiCall({ message, onViewSourceInDebugger }));
+      const wrapper = render(ConsoleApiCall({ message, serviceContainer }));
 
       expect(wrapper.find(".message-body").text()).toBe("");
     });
   });
 
   describe("console.timeEnd", () => {
     it("renders as expected", () => {
       const message = stubPreparedMessages.get("console.timeEnd('bar')");
-      const wrapper = render(ConsoleApiCall({ message, onViewSourceInDebugger }));
+      const wrapper = render(ConsoleApiCall({ message, serviceContainer }));
 
       expect(wrapper.find(".message-body").text()).toBe(message.messageText);
       expect(wrapper.find(".message-body").text()).toMatch(/^bar: \d+(\.\d+)?ms$/);
     });
   });
 
   describe("console.trace", () => {
     it("renders", () => {
       const message = stubPreparedMessages.get("console.trace()");
-      const wrapper = render(ConsoleApiCall({ message, onViewSourceInDebugger, open: true }));
+      const wrapper = render(ConsoleApiCall({ message, serviceContainer, open: true }));
       const filepath = `${tempfilePath}`;
 
       expect(wrapper.find(".message-body").text()).toBe("console.trace()");
 
       const frameLinks = wrapper.find(`.stack-trace span.frame-link[data-url='${filepath}']`);
       expect(frameLinks.length).toBe(3);
 
       expect(frameLinks.eq(0).find(".frame-link-function-display-name").text()).toBe("testStacktraceFiltering");
--- a/devtools/client/webconsole/new-console-output/test/components/filter-bar.test.js
+++ b/devtools/client/webconsole/new-console-output/test/components/filter-bar.test.js
@@ -13,49 +13,50 @@ const FilterButton = createFactory(requi
 const FilterBar = createFactory(require("devtools/client/webconsole/new-console-output/components/filter-bar"));
 const { getAllUi } = require("devtools/client/webconsole/new-console-output/selectors/ui");
 const {
   MESSAGES_CLEAR,
   MESSAGE_LEVEL
 } = require("devtools/client/webconsole/new-console-output/constants");
 
 const { setupStore } = require("devtools/client/webconsole/new-console-output/test/helpers");
+const serviceContainer = require("devtools/client/webconsole/new-console-output/test/fixtures/serviceContainer");
 
 describe("FilterBar component:", () => {
   it("initial render", () => {
     const store = setupStore([]);
 
-    const wrapper = render(Provider({store}, FilterBar({})));
+    const wrapper = render(Provider({store}, FilterBar({ serviceContainer })));
     const toolbar = wrapper.find(
       ".devtools-toolbar.webconsole-filterbar-primary"
     );
 
     // Clear button
     expect(toolbar.children().eq(0).attr("class"))
       .toBe("devtools-button devtools-clear-icon");
     expect(toolbar.children().eq(0).attr("title")).toBe("Clear output");
 
     // Filter bar toggle
     expect(toolbar.children().eq(1).attr("class"))
       .toBe("devtools-button devtools-filter-icon");
     expect(toolbar.children().eq(1).attr("title")).toBe("Toggle filter bar");
 
     // Text filter
-    expect(toolbar.children().eq(2).attr("class")).toBe("devtools-plaininput");
+    expect(toolbar.children().eq(2).attr("class")).toBe("devtools-plaininput text-filter");
     expect(toolbar.children().eq(2).attr("placeholder")).toBe("Filter output");
     expect(toolbar.children().eq(2).attr("type")).toBe("search");
     expect(toolbar.children().eq(2).attr("value")).toBe("");
   });
 
   it("displays filter bar when button is clicked", () => {
     const store = setupStore([]);
 
     expect(getAllUi(store.getState()).filterBarVisible).toBe(false);
 
-    const wrapper = mount(Provider({store}, FilterBar({})));
+    const wrapper = mount(Provider({store}, FilterBar({ serviceContainer })));
     wrapper.find(".devtools-filter-icon").simulate("click");
 
     expect(getAllUi(store.getState()).filterBarVisible).toBe(true);
 
     // Buttons are displayed
     const buttonProps = {
       active: true,
       dispatch: store.dispatch
@@ -72,24 +73,24 @@ describe("FilterBar component:", () => {
       { label: "Errors", filterKey: MESSAGE_LEVEL.ERROR }));
     expect(wrapper.contains([errorButton, warnButton, logButton, infoButton, debugButton])).toBe(true);
   });
 
   it("fires MESSAGES_CLEAR action when clear button is clicked", () => {
     const store = setupStore([]);
     store.dispatch = sinon.spy();
 
-    const wrapper = mount(Provider({store}, FilterBar({})));
+    const wrapper = mount(Provider({store}, FilterBar({ serviceContainer })));
     wrapper.find(".devtools-clear-icon").simulate("click");
     const call = store.dispatch.getCall(0);
     expect(call.args[0]).toEqual({
       type: MESSAGES_CLEAR
     });
   });
 
   it("sets filter text when text is typed", () => {
     const store = setupStore([]);
 
-    const wrapper = mount(Provider({store}, FilterBar({})));
+    const wrapper = mount(Provider({store}, FilterBar({ serviceContainer })));
     wrapper.find(".devtools-plaininput").simulate("input", { target: { value: "a" } });
     expect(store.getState().filters.text).toBe("a");
   });
 });
--- a/devtools/client/webconsole/new-console-output/test/components/message-container.test.js
+++ b/devtools/client/webconsole/new-console-output/test/components/message-container.test.js
@@ -12,22 +12,22 @@ const {
 // Components under test.
 const { MessageContainer } = require("devtools/client/webconsole/new-console-output/components/message-container");
 const ConsoleApiCall = require("devtools/client/webconsole/new-console-output/components/message-types/console-api-call");
 const EvaluationResult = require("devtools/client/webconsole/new-console-output/components/message-types/evaluation-result");
 const PageError = require("devtools/client/webconsole/new-console-output/components/message-types/page-error");
 
 // Test fakes.
 const { stubPreparedMessages } = require("devtools/client/webconsole/new-console-output/test/fixtures/stubs/index");
-const onViewSourceInDebugger = () => {};
+const serviceContainer = require("devtools/client/webconsole/new-console-output/test/fixtures/serviceContainer");
 
 describe("MessageContainer component:", () => {
   it("pipes data to children as expected", () => {
     const message = stubPreparedMessages.get("console.log('foobar', 'test')");
-    const rendered = renderComponent(MessageContainer, {message, onViewSourceInDebugger});
+    const rendered = renderComponent(MessageContainer, {message, serviceContainer});
 
     expect(rendered.textContent.includes("foobar")).toBe(true);
   });
   it("picks correct child component", () => {
     const messageTypes = [
       {
         component: ConsoleApiCall,
         message: stubPreparedMessages.get("console.log('foobar', 'test')")
@@ -41,14 +41,14 @@ describe("MessageContainer component:", 
         message: stubPreparedMessages.get("ReferenceError: asdf is not defined")
       }
     ];
 
     messageTypes.forEach(info => {
       const { component, message } = info;
       const rendered = shallowRenderComponent(MessageContainer, {
         message,
-        onViewSourceInDebugger,
+        serviceContainer,
       });
       expect(rendered.type).toBe(component);
     });
   });
 });
--- a/devtools/client/webconsole/new-console-output/test/components/network-event-message.test.js
+++ b/devtools/client/webconsole/new-console-output/test/components/network-event-message.test.js
@@ -9,52 +9,51 @@ const { render } = require("enzyme");
 // React
 const { createFactory } = require("devtools/client/shared/vendor/react");
 
 // Components under test.
 const NetworkEventMessage = createFactory(require("devtools/client/webconsole/new-console-output/components/message-types/network-event-message"));
 
 // Test fakes.
 const { stubPreparedMessages } = require("devtools/client/webconsole/new-console-output/test/fixtures/stubs/index");
-const onViewSourceInDebugger = () => {};
-const openNetworkPanel = () => {};
-const openLink = () => {};
+const serviceContainer = require("devtools/client/webconsole/new-console-output/test/fixtures/serviceContainer");
+
 const EXPECTED_URL = "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/inexistent.html";
 
 describe("NetworkEventMessage component:", () => {
   describe("GET request", () => {
     it("renders as expected", () => {
       const message = stubPreparedMessages.get("GET request");
-      const wrapper = render(NetworkEventMessage({ message, onViewSourceInDebugger, openNetworkPanel, openLink }));
+      const wrapper = render(NetworkEventMessage({ message, serviceContainer }));
 
       expect(wrapper.find(".message-body .method").text()).toBe("GET");
       expect(wrapper.find(".message-body .xhr").length).toBe(0);
       expect(wrapper.find(".message-body .url").length).toBe(1);
       expect(wrapper.find(".message-body .url").text()).toBe(EXPECTED_URL);
       expect(wrapper.find("div.message.cm-s-mozilla span.message-body.devtools-monospace").length).toBe(1);
     });
   });
 
   describe("XHR GET request", () => {
     it("renders as expected", () => {
       const message = stubPreparedMessages.get("XHR GET request");
-      const wrapper = render(NetworkEventMessage({ message, onViewSourceInDebugger, openNetworkPanel, openLink }));
+      const wrapper = render(NetworkEventMessage({ message, serviceContainer }));
 
       expect(wrapper.find(".message-body .method").text()).toBe("GET");
       expect(wrapper.find(".message-body .xhr").length).toBe(1);
       expect(wrapper.find(".message-body .xhr").text()).toBe("XHR");
       expect(wrapper.find(".message-body .url").text()).toBe(EXPECTED_URL);
       expect(wrapper.find("div.message.cm-s-mozilla span.message-body.devtools-monospace").length).toBe(1);
     });
   });
 
   describe("XHR POST request", () => {
     it("renders as expected", () => {
       const message = stubPreparedMessages.get("XHR POST request");
-      const wrapper = render(NetworkEventMessage({ message, onViewSourceInDebugger, openNetworkPanel, openLink }));
+      const wrapper = render(NetworkEventMessage({ message, serviceContainer }));
 
       expect(wrapper.find(".message-body .method").text()).toBe("POST");
       expect(wrapper.find(".message-body .xhr").length).toBe(1);
       expect(wrapper.find(".message-body .xhr").text()).toBe("XHR");
       expect(wrapper.find(".message-body .url").length).toBe(1);
       expect(wrapper.find(".message-body .url").text()).toBe(EXPECTED_URL);
       expect(wrapper.find("div.message.cm-s-mozilla span.message-body.devtools-monospace").length).toBe(1);
     });
--- a/devtools/client/webconsole/new-console-output/test/components/page-error.test.js
+++ b/devtools/client/webconsole/new-console-output/test/components/page-error.test.js
@@ -6,21 +6,22 @@
 const expect = require("expect");
 const { render } = require("enzyme");
 
 // Components under test.
 const PageError = require("devtools/client/webconsole/new-console-output/components/message-types/page-error");
 
 // Test fakes.
 const { stubPreparedMessages } = require("devtools/client/webconsole/new-console-output/test/fixtures/stubs/index");
+const serviceContainer = require("devtools/client/webconsole/new-console-output/test/fixtures/serviceContainer");
 
 describe("PageError component:", () => {
   it("renders", () => {
     const message = stubPreparedMessages.get("ReferenceError: asdf is not defined");
-    const wrapper = render(PageError({ message }));
+    const wrapper = render(PageError({ message, serviceContainer }));
 
     expect(wrapper.find(".message-body").text())
       .toBe("ReferenceError: asdf is not defined");
 
     // The stacktrace should be closed by default.
     const frameLinks = wrapper.find(`.stack-trace`);
     expect(frameLinks.length).toBe(0);
 
@@ -28,15 +29,15 @@ describe("PageError component:", () => {
     const locationLink = wrapper.find(`.message-location`);
     expect(locationLink.length).toBe(1);
     // @TODO Will likely change. See https://github.com/devtools-html/gecko-dev/issues/285
     expect(locationLink.text()).toBe("test-tempfile.js:3:5");
   });
 
   it("has a stacktrace which can be openned", () => {
     const message = stubPreparedMessages.get("ReferenceError: asdf is not defined");
-    const wrapper = render(PageError({ message, open: true }));
+    const wrapper = render(PageError({ message, serviceContainer, open: true }));
 
     // There should be three stacktrace items.
     const frameLinks = wrapper.find(`.stack-trace span.frame-link`);
     expect(frameLinks.length).toBe(3);
   });
 });
new file mode 100644
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/fixtures/serviceContainer.js
@@ -0,0 +1,15 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+module.exports = {
+  attachRefToHud: () => {},
+  emitNewMessage: () => {},
+  hudProxyClient: {},
+  onViewSourceInDebugger: () => {},
+  openNetworkPanel: () => {},
+  sourceMapService: {
+    subscribe: () => {},
+  },
+};
\ No newline at end of file
--- a/devtools/client/webconsole/new-console-output/test/mochitest/browser.ini
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser.ini
@@ -7,11 +7,12 @@ support-files =
   test-console-table.html
   test-console.html
   test-console-filters.html
 
 [browser_webconsole_console_table.js]
 [browser_webconsole_filters.js]
 [browser_webconsole_init.js]
 [browser_webconsole_input_focus.js]
+[browser_webconsole_keyboard_accessibility.js]
 [browser_webconsole_observer_notifications.js]
 [browser_webconsole_vview_close_on_esc_key.js]
 
new file mode 100644
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_keyboard_accessibility.js
@@ -0,0 +1,71 @@
+/* -*- 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/ */
+
+// Check that basic keyboard shortcuts work in the web console.
+
+"use strict";
+
+const TEST_URI =
+  `data:text/html;charset=utf-8,<p>Test keyboard accessibility</p>
+  <script>
+    for (let i = 1; i <= 100; i++) {
+      console.log("console message " + i);
+    }
+  </script>
+  `;
+
+add_task(function* () {
+  let hud = yield openNewTabAndConsole(TEST_URI);
+  info("Web Console opened");
+
+  const outputWrapper = hud.ui.outputWrapper;
+
+  yield waitFor(() => findMessages(hud, "").length == 100);
+
+  let currentPosition = outputWrapper.scrollTop;
+  const bottom = currentPosition;
+
+  EventUtils.sendMouseEvent({type: "click"}, hud.jsterm.inputNode);
+
+  // Page up.
+  EventUtils.synthesizeKey("VK_PAGE_UP", {});
+  isnot(outputWrapper.scrollTop, currentPosition,
+    "scroll position changed after page up");
+
+  // Page down.
+  currentPosition = outputWrapper.scrollTop;
+  EventUtils.synthesizeKey("VK_PAGE_DOWN", {});
+  ok(outputWrapper.scrollTop > currentPosition,
+     "scroll position now at bottom");
+
+  // Home
+  EventUtils.synthesizeKey("VK_HOME", {});
+  is(outputWrapper.scrollTop, 0, "scroll position now at top");
+
+  // End
+  EventUtils.synthesizeKey("VK_END", {});
+  let scrollTop = outputWrapper.scrollTop;
+  ok(scrollTop > 0 && Math.abs(scrollTop - bottom) <= 5,
+     "scroll position now at bottom");
+
+  // Clear output
+  info("try ctrl-l to clear output");
+  let clearShortcut;
+  if (Services.appinfo.OS === "Darwin") {
+    clearShortcut = WCUL10n.getStr("webconsole.clear.keyOSX");
+  } else {
+    clearShortcut = WCUL10n.getStr("webconsole.clear.key");
+  }
+  synthesizeKeyShortcut(clearShortcut);
+  yield waitFor(() => findMessages(hud, "").length == 0);
+  is(hud.jsterm.inputNode.getAttribute("focused"), "true", "jsterm input is focused");
+
+  // Focus filter
+  info("try ctrl-f to focus filter");
+  synthesizeKeyShortcut(WCUL10n.getStr("webconsole.find.key"));
+  ok(!hud.jsterm.inputNode.getAttribute("focused"), "jsterm input is not focused");
+  is(hud.ui.filterBox, outputWrapper.ownerDocument.activeElement,
+    "filter input is focused");
+});
--- a/devtools/client/webconsole/new-console-output/test/mochitest/head.js
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/head.js
@@ -7,16 +7,20 @@
 "use strict";
 
 // shared-head.js handles imports, constants, and utility functions
 // Load the shared-head file first.
 Services.scriptloader.loadSubScript(
   "chrome://mochitests/content/browser/devtools/client/framework/test/shared-head.js",
   this);
 
+var {Utils: WebConsoleUtils} = require("devtools/client/webconsole/utils");
+const WEBCONSOLE_STRINGS_URI = "devtools/locale/webconsole.properties";
+var WCUL10n = new WebConsoleUtils.L10n(WEBCONSOLE_STRINGS_URI);
+
 Services.prefs.setBoolPref("devtools.webconsole.new-frontend-enabled", true);
 registerCleanupFunction(function* () {
   Services.prefs.clearUserPref("devtools.webconsole.new-frontend-enabled");
 
   let browserConsole = HUDService.getBrowserConsole();
   if (browserConsole) {
     if (browserConsole.jsterm) {
       browserConsole.jsterm.clearOutput(true);
--- a/devtools/client/webconsole/webconsole.js
+++ b/devtools/client/webconsole/webconsole.js
@@ -576,17 +576,19 @@ WebConsoleFrame.prototype = {
     this.jsterm = new JSTerm(this);
     this.jsterm.init();
 
     let toolbox = gDevTools.getToolbox(this.owner.target);
 
     if (this.NEW_CONSOLE_OUTPUT_ENABLED) {
       // @TODO Remove this once JSTerm is handled with React/Redux.
       this.window.jsterm = this.jsterm;
-      console.log("Entering experimental mode for console frontend");
+
+      // Remove context menu for now (see Bug 1307239).
+      this.outputWrapper.removeAttribute("context");
 
       // XXX: We should actually stop output from happening on old output
       // panel, but for now let's just hide it.
       this.experimentalOutputNode = this.outputNode.cloneNode();
       this.experimentalOutputNode.removeAttribute("tabindex");
       this.outputNode.hidden = true;
       this.outputNode.parentNode.appendChild(this.experimentalOutputNode);
       // @TODO Once the toolbox has been converted to React, see if passing