Merge fx-team to m-c. a=merge
authorRyan VanderMeulen <ryanvm@gmail.com>
Wed, 18 Jun 2014 16:15:32 -0400
changeset 189409 f78e532e8a105c597392f43212b8bfe9792b3193
parent 189397 00d973a0bdbf63a1a5bd0fc7e1fc9927f7bae8fc (current diff)
parent 189408 9f10f484bb2643122d377d8a4917258b50edbc3d (diff)
child 189410 ed9d62bef0df94f75f27a8d98fb4089b2f188cd4
child 189511 673a55b794f38dd0272e9cd4d071f5982d9db1ab
child 189569 815c4fab188b032d2a4ddccff0b7f2a7d4a4d248
push id45051
push userryanvm@gmail.com
push dateWed, 18 Jun 2014 20:16:38 +0000
treeherdermozilla-inbound@ed9d62bef0df [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone33.0a1
first release with
nightly linux32
f78e532e8a10 / 33.0a1 / 20140619030203 / files
nightly linux64
f78e532e8a10 / 33.0a1 / 20140619030203 / files
nightly mac
f78e532e8a10 / 33.0a1 / 20140619030203 / files
nightly win32
f78e532e8a10 / 33.0a1 / 20140619030203 / files
nightly win64
f78e532e8a10 / 33.0a1 / 20140619030203 / 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 m-c. a=merge
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -6897,43 +6897,58 @@ let gRemoteTabsUI = {
  *        URI to search for
  * @param aOpenNew
  *        True to open a new tab and switch to it, if no existing tab is found.
  *        If no suitable window is found, a new one will be opened.
  * @param aOpenParams
  *        If switching to this URI results in us opening a tab, aOpenParams
  *        will be the parameter object that gets passed to openUILinkIn. Please
  *        see the documentation for openUILinkIn to see what parameters can be
- *        passed via this object.
+ *        passed via this object. This object also allows the 'ignoreFragment'
+ *        property to be set to true to exclude fragment-portion matching when
+ *        comparing URIs.
  * @return True if an existing tab was found, false otherwise
  */
-function switchToTabHavingURI(aURI, aOpenNew, aOpenParams) {
+function switchToTabHavingURI(aURI, aOpenNew, aOpenParams={}) {
   // Certain URLs can be switched to irrespective of the source or destination
   // window being in private browsing mode:
   const kPrivateBrowsingWhitelist = new Set([
     "about:customizing",
   ]);
+
+  let ignoreFragment = aOpenParams.ignoreFragment;
+  // This property is only used by switchToTabHavingURI and should
+  // not be used as a parameter for the new load.
+  delete aOpenParams.ignoreFragment;
+
   // This will switch to the tab in aWindow having aURI, if present.
   function switchIfURIInWindow(aWindow) {
     // Only switch to the tab if neither the source nor the destination window
     // are private and they are not in permanent private browsing mode
     if (!kPrivateBrowsingWhitelist.has(aURI.spec) &&
         (PrivateBrowsingUtils.isWindowPrivate(window) ||
          PrivateBrowsingUtils.isWindowPrivate(aWindow)) &&
         !PrivateBrowsingUtils.permanentPrivateBrowsing) {
       return false;
     }
 
     let browsers = aWindow.gBrowser.browsers;
     for (let i = 0; i < browsers.length; i++) {
       let browser = browsers[i];
-      if (browser.currentURI.equals(aURI)) {
+      if (ignoreFragment ? browser.currentURI.equalsExceptRef(aURI) :
+                           browser.currentURI.equals(aURI)) {
         // Focus the matching window & tab
         aWindow.focus();
         aWindow.gBrowser.tabContainer.selectedIndex = i;
+        if (ignoreFragment) {
+          let spec = aURI.spec;
+          if (!aURI.ref)
+            spec += "#";
+          browser.loadURI(spec);
+        }
         return true;
       }
     }
     return false;
   }
 
   // This can be passed either nsIURI or a string.
   if (!(aURI instanceof Ci.nsIURI))
--- a/browser/base/content/test/general/browser.ini
+++ b/browser/base/content/test/general/browser.ini
@@ -423,8 +423,9 @@ skip-if = (os == "win" && !debug) || e10
 skip-if = e10s # Bug ?????? - test directly manipulates content (content.document.getElementById)
 [browser_zbug569342.js]
 skip-if = e10s # Bug 516755 - SessionStore disabled for e10s
 [browser_registerProtocolHandler_notification.js]
 skip-if = e10s # Bug 940206 - nsIWebContentHandlerRegistrar::registerProtocolHandler doesn't work in e10s
 [browser_no_mcb_on_http_site.js]
 skip-if = e10s # Bug 516755 - SessionStore disabled for e10s
 [browser_bug1003461-switchtab-override.js]
+[browser_bug1025195_switchToTabHavingURI_ignoreFragment.js]
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug1025195_switchToTabHavingURI_ignoreFragment.js
@@ -0,0 +1,35 @@
+/* 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/. */
+
+add_task(function() {
+  registerCleanupFunction(function() {
+    while (gBrowser.tabs.length > 1)
+      gBrowser.removeCurrentTab();
+  });
+  let tabRefAboutHome = gBrowser.addTab("about:home#1");
+  yield promiseTabLoaded(tabRefAboutHome);
+  let tabRefAboutMozilla = gBrowser.addTab("about:mozilla");
+  yield promiseTabLoaded(tabRefAboutMozilla);
+
+  gBrowser.selectedTab = tabRefAboutMozilla;
+  let numTabsAtStart = gBrowser.tabs.length;
+
+  switchTab("about:home#1", false, true);
+  switchTab("about:mozilla", false, true);
+  switchTab("about:home#2", true, true);
+  is(tabRefAboutHome, gBrowser.selectedTab, "The same about:home tab should be switched to");
+  is(gBrowser.currentURI.ref, "2", "The ref should be updated to the new ref");
+  switchTab("about:mozilla", false, true);
+  switchTab("about:home#1", false, false);
+  isnot(tabRefAboutHome, gBrowser.selectedTab, "Selected tab should not be initial about:blank tab");
+  is(gBrowser.tabs.length, numTabsAtStart + 1, "Should have one new tab opened");
+  switchTab("about:about", true, false);
+});
+
+function switchTab(aURI, aIgnoreFragment, aShouldFindExistingTab) {
+  let tabFound = switchToTabHavingURI(aURI, true, {ignoreFragment: aIgnoreFragment});
+  is(tabFound, aShouldFindExistingTab,
+     "Should switch to existing " + aURI + " tab if one existed, " +
+     (aIgnoreFragment ? "ignoring" : "including") + " fragment portion");
+}
--- a/browser/base/content/test/general/head.js
+++ b/browser/base/content/test/general/head.js
@@ -466,8 +466,9 @@ let FullZoomHelper = {
 
   failAndContinue: function failAndContinue(func) {
     return function (err) {
       ok(false, err);
       func();
     };
   },
 };
+
--- a/browser/devtools/scratchpad/scratchpad.js
+++ b/browser/devtools/scratchpad/scratchpad.js
@@ -1953,33 +1953,35 @@ let scratchpadTargets = new WeakMap();
  * @return Promise
  *         The promise for the connection information.
  */
 ScratchpadTab.consoleFor = function consoleFor(aSubject)
 {
   if (!scratchpadTargets.has(aSubject)) {
     scratchpadTargets.set(aSubject, new this(aSubject));
   }
-  return scratchpadTargets.get(aSubject).connect();
+  return scratchpadTargets.get(aSubject).connect(aSubject);
 };
 
 
 ScratchpadTab.prototype = {
   /**
    * The promise for the connection.
    */
   _connector: null,
 
   /**
    * Initialize a debugger client and connect it to the debugger server.
    *
+ * @param object aSubject
+ *        The tab or window to obtain the connection for.
    * @return Promise
    *         The promise for the result of connecting to this tab or window.
    */
-  connect: function ST_connect()
+  connect: function ST_connect(aSubject)
   {
     if (this._connector) {
       return this._connector;
     }
 
     let deferred = promise.defer();
     this._connector = deferred.promise;
 
@@ -1987,17 +1989,17 @@ ScratchpadTab.prototype = {
       deferred.reject({
         error: "timeout",
         message: Scratchpad.strings.GetStringFromName("connectionTimeout"),
       });
     }, REMOTE_TIMEOUT);
 
     deferred.promise.then(() => clearTimeout(connectTimer));
 
-    this._attach().then(aTarget => {
+    this._attach(aSubject).then(aTarget => {
       let consoleActor = aTarget.form.consoleActor;
       let client = aTarget.client;
       client.attachConsole(consoleActor, [], (aResponse, aWebConsoleClient) => {
         if (aResponse.error) {
           reportError("attachConsole", aResponse);
           deferred.reject(aResponse);
         }
         else {
@@ -2010,22 +2012,29 @@ ScratchpadTab.prototype = {
     });
 
     return deferred.promise;
   },
 
   /**
    * Attach to this tab.
    *
+ * @param object aSubject
+ *        The tab or window to obtain the connection for.
    * @return Promise
    *         The promise for the TabTarget for this tab.
    */
-  _attach: function ST__attach()
+  _attach: function ST__attach(aSubject)
   {
     let target = TargetFactory.forTab(this._tab);
+    target.once("close", () => {
+      if (scratchpadTargets) {
+        scratchpadTargets.delete(aSubject);
+      }
+    });
     return target.makeRemote().then(() => target);
   },
 };
 
 
 /**
  * Represents the DebuggerClient connection to a specific window as used by the
  * Scratchpad.
--- a/browser/devtools/scratchpad/test/browser.ini
+++ b/browser/devtools/scratchpad/test/browser.ini
@@ -34,8 +34,9 @@ support-files = head.js
 [browser_scratchpad_open_error_console.js]
 [browser_scratchpad_throw_output.js]
 [browser_scratchpad_pprint-02.js]
 [browser_scratchpad_pprint.js]
 [browser_scratchpad_pprint_error_goto_line.js]
 [browser_scratchpad_restore.js]
 [browser_scratchpad_tab_switch.js]
 [browser_scratchpad_ui.js]
+[browser_scratchpad_close_toolbox.js]
new file mode 100644
--- /dev/null
+++ b/browser/devtools/scratchpad/test/browser_scratchpad_close_toolbox.js
@@ -0,0 +1,38 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that closing the toolbox after having opened a scratchpad leaves the
+// latter in a functioning state.
+
+let {Task} = Cu.import("resource://gre/modules/Task.jsm", {});
+let {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
+
+function test() {
+  const options = {
+    tabContent: "test closing toolbox and then reusing scratchpad"
+  };
+  openTabAndScratchpad(options)
+    .then(Task.async(runTests))
+    .then(finish, console.error);
+}
+
+function* runTests([win, sp]) {
+  // Use the scratchpad before opening the toolbox.
+  const source = "window.foobar = 7;";
+  sp.setText(source);
+  let [,,result] = yield sp.display();
+  is(result, 7, "Display produced the expected output.");
+
+  // Now open the toolbox and close it again.
+  let target = devtools.TargetFactory.forTab(gBrowser.selectedTab);
+  let toolbox = yield gDevTools.showToolbox(target, "webconsole");
+  ok(toolbox, "Toolbox was opened.");
+  let closed = yield gDevTools.closeToolbox(target);
+  is(closed, true, "Toolbox was closed.");
+
+  // Now see if using the scratcphad works as expected.
+  sp.setText(source);
+  let [,,result2] = yield sp.display();
+  is(result2, 7,
+     "Display produced the expected output after the toolbox was gone.");
+}
--- a/browser/devtools/styleeditor/StyleEditorUI.jsm
+++ b/browser/devtools/styleeditor/StyleEditorUI.jsm
@@ -755,17 +755,19 @@ StyleEditorUI.prototype = {
         cond.className = "media-rule-condition"
         if (!rule.matches) {
           cond.classList.add("media-condition-unmatched");
         }
         div.appendChild(cond);
 
         let link = this._panelDoc.createElement("div");
         link.className = "media-rule-line theme-link";
-        link.textContent = ":" + location.line;
+        if (location.line != -1) {
+          link.textContent = ":" + location.line;
+        }
         div.appendChild(link);
 
         list.appendChild(div);
       }
 
       sidebar.hidden = !showSidebar || !inSource;
 
       this.emit("media-list-changed", editor);
--- a/browser/devtools/styleeditor/test/browser_styleeditor_media_sidebar.js
+++ b/browser/devtools/styleeditor/test/browser_styleeditor_media_sidebar.js
@@ -2,18 +2,19 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // https rather than chrome to improve coverage
 const TESTCASE_URI = TEST_BASE_HTTPS + "media-rules.html";
 const MEDIA_PREF = "devtools.styleeditor.showMediaSidebar";
 
 const RESIZE = 300;
-const LABELS = ["not all", "all", "(max-width: 400px)"];
-const LINE_NOS = [2, 8, 20];
+const LABELS = ["not all", "all", "(max-width: 400px)", "(max-width: 600px)"];
+const LINE_NOS = [2, 8, 20, 25];
+const NEW_RULE = "\n@media (max-width: 600px) { div { color: blue; } }";
 
 waitForExplicitFinish();
 
 let test = asyncTest(function*() {
   let {UI} = yield addTabAndOpenStyleEditors(2, null, TESTCASE_URI);
 
   is(UI.editors.length, 2, "correct number of editors");
 
@@ -23,46 +24,34 @@ let test = asyncTest(function*() {
   testPlainEditor(plainEditor);
 
   // Test editor with @media rules
   let mediaEditor = UI.editors[1];
   yield openEditor(mediaEditor);
   testMediaEditor(mediaEditor);
 
   // Test that sidebar hides when flipping pref
-  testShowHide(mediaEditor);
+  yield testShowHide(UI, mediaEditor);
+
+  // Test adding a rule updates the list
+  yield testMediaRuleAdded(UI, mediaEditor);
 
   // Test resizing and seeing @media matching state change
   let originalWidth = window.outerWidth;
   let originalHeight = window.outerHeight;
 
   let onMatchesChange = listenForMediaChange(UI);
   window.resizeTo(RESIZE, RESIZE);
   yield onMatchesChange;
 
   testMediaMatchChanged(mediaEditor);
 
   window.resizeTo(originalWidth, originalHeight);
 });
 
-function* testShowHide(editor) {
-  let sidebarChange = listenForMediaChange(UI);
-  Services.prefs.setBoolPref(MEDIA_PREF, false);
-  yield sidebarChange;
-
-  let sidebar = editor.details.querySelector(".stylesheet-sidebar");
-  is(sidebar.hidden, true, "sidebar is hidden after flipping pref");
-
-  sidebarChange = listenForMediaChange(UI);
-  Services.prefs.clearUserPref(MEDIA_PREF);
-  yield sidebarChange;
-
-  is(sidebar.hidden, false, "sidebar is showing after flipping pref back");
-}
-
 function testPlainEditor(editor) {
   let sidebar = editor.details.querySelector(".stylesheet-sidebar");
   is(sidebar.hidden, true, "sidebar is hidden on editor without @media");
 }
 
 function testMediaEditor(editor) {
   let sidebar = editor.details.querySelector(".stylesheet-sidebar");
   is(sidebar.hidden, false, "sidebar is showing on editor with @media");
@@ -79,16 +68,47 @@ function testMediaMatchChanged(editor) {
   let sidebar = editor.details.querySelector(".stylesheet-sidebar");
 
   let cond = sidebar.querySelectorAll(".media-rule-condition")[2];
   is(cond.textContent, "(max-width: 400px)", "third rule condition text is correct");
   ok(!cond.classList.contains("media-condition-unmatched"),
      "media rule is now matched after resizing");
 }
 
+function* testShowHide(UI, editor) {
+  let sidebarChange = listenForMediaChange(UI);
+  Services.prefs.setBoolPref(MEDIA_PREF, false);
+  yield sidebarChange;
+
+  let sidebar = editor.details.querySelector(".stylesheet-sidebar");
+  is(sidebar.hidden, true, "sidebar is hidden after flipping pref");
+
+  sidebarChange = listenForMediaChange(UI);
+  Services.prefs.clearUserPref(MEDIA_PREF);
+  yield sidebarChange;
+
+  is(sidebar.hidden, false, "sidebar is showing after flipping pref back");
+}
+
+function* testMediaRuleAdded(UI, editor) {
+  yield editor.getSourceEditor();
+  let text = editor.sourceEditor.getText();
+  text += NEW_RULE;
+
+  let listChange = listenForMediaChange(UI);
+  editor.sourceEditor.setText(text);
+  yield listChange;
+
+  let sidebar = editor.details.querySelector(".stylesheet-sidebar");
+  let entries = [...sidebar.querySelectorAll(".media-rule-label")];
+  is(entries.length, 4, "four @media rules after changing text");
+
+  testRule(entries[3], LABELS[3], false, LINE_NOS[3]);
+}
+
 function testRule(rule, text, matches, lineno) {
   let cond = rule.querySelector(".media-rule-condition");
   is(cond.textContent, text, "media label is correct for " + text);
 
   let matched = !cond.classList.contains("media-condition-unmatched");
   ok(matches ? matched : !matched,
      "media rule is " + (matches ? "matched" : "unmatched"));
 
--- a/browser/devtools/styleinspector/computed-view.js
+++ b/browser/devtools/styleinspector/computed-view.js
@@ -7,31 +7,30 @@
 const {Cc, Ci, Cu} = require("chrome");
 
 const ToolDefinitions = require("main").Tools;
 const {CssLogic} = require("devtools/styleinspector/css-logic");
 const {ELEMENT_STYLE} = require("devtools/server/actors/styles");
 const {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {});
 const {EventEmitter} = require("devtools/toolkit/event-emitter");
 const {OutputParser} = require("devtools/output-parser");
-const {Tooltip} = require("devtools/shared/widgets/Tooltip");
 const {PrefObserver, PREF_ORIG_SOURCES} = require("devtools/styleeditor/utils");
 const {gDevTools} = Cu.import("resource:///modules/devtools/gDevTools.jsm", {});
+const overlays = require("devtools/styleinspector/style-inspector-overlays");
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/devtools/Templater.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
                                   "resource://gre/modules/PluralForm.jsm");
 
 const FILTER_CHANGED_TIMEOUT = 300;
 const HTML_NS = "http://www.w3.org/1999/xhtml";
 const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
-const TRANSFORM_HIGHLIGHTER_TYPE = "CssTransformHighlighter";
 
 /**
  * Helper for long-running processes that should yield occasionally to
  * the mainloop.
  *
  * @param {Window} aWin
  *        Timeouts will be set on this window when appropriate.
  * @param {Generator} aGenerator
@@ -129,16 +128,17 @@ UpdateProcess.prototype = {
  *
  * @constructor
  */
 function CssHtmlTree(aStyleInspector, aPageStyle)
 {
   this.styleWindow = aStyleInspector.window;
   this.styleDocument = aStyleInspector.window.document;
   this.styleInspector = aStyleInspector;
+  this.inspector = this.styleInspector.inspector;
   this.pageStyle = aPageStyle;
   this.propertyViews = [];
 
   this._outputParser = new OutputParser();
 
   let chromeReg = Cc["@mozilla.org/chrome/chrome-registry;1"].
     getService(Ci.nsIXULChromeRegistry);
   this.getRTLAttr = chromeReg.isLocaleRTL("global") ? "rtl" : "ltr";
@@ -154,20 +154,20 @@ function CssHtmlTree(aStyleInspector, aP
 
   this.styleDocument.addEventListener("copy", this._onCopy);
   this.styleDocument.addEventListener("mousedown", this.focusWindow);
   this.styleDocument.addEventListener("contextmenu", this._onContextMenu);
 
   // Nodes used in templating
   this.root = this.styleDocument.getElementById("root");
   this.templateRoot = this.styleDocument.getElementById("templateRoot");
-  this.propertyContainer = this.styleDocument.getElementById("propertyContainer");
+  this.element = this.styleDocument.getElementById("propertyContainer");
 
   // Listen for click events
-  this.propertyContainer.addEventListener("click", this._onClick, false);
+  this.element.addEventListener("click", this._onClick, false);
 
   // No results text.
   this.noResults = this.styleDocument.getElementById("noResults");
 
   // Refresh panel when color unit changed.
   this._handlePrefChange = this._handlePrefChange.bind(this);
   gDevTools.on("pref-changed", this._handlePrefChange);
 
@@ -176,29 +176,24 @@ function CssHtmlTree(aStyleInspector, aP
   this._prefObserver = new PrefObserver("devtools.");
   this._prefObserver.on(PREF_ORIG_SOURCES, this._updateSourceLinks);
 
   CssHtmlTree.processTemplate(this.templateRoot, this.root, this);
 
   // The element that we're inspecting, and the document that it comes from.
   this.viewedElement = null;
 
-  // Properties preview tooltip
-  this.tooltip = new Tooltip(this.styleInspector.inspector.panelDoc);
-  this.tooltip.startTogglingOnHover(this.propertyContainer,
-    this._onTooltipTargetHover.bind(this));
-
   this._buildContextMenu();
   this.createStyleViews();
 
-  // Initialize the css transform highlighter if the target supports it
-  let hUtils = this.styleInspector.inspector.toolbox.highlighterUtils;
-  if (hUtils.hasCustomHighlighter(TRANSFORM_HIGHLIGHTER_TYPE)) {
-    this._initTransformHighlighter();
-  }
+  // Add the tooltips and highlightersoverlay
+  this.tooltips = new overlays.TooltipsOverlay(this);
+  this.tooltips.addToView();
+  this.highlighters = new overlays.HighlightersOverlay(this);
+  this.highlighters.addToView();
 }
 
 /**
  * Memoized lookup of a l10n string from a string bundle.
  * @param {string} aName The key to lookup.
  * @returns A localized version of the given key.
  */
 CssHtmlTree.l10n = function CssHtmlTree_l10n(aName)
@@ -305,28 +300,70 @@ CssHtmlTree.prototype = {
       }
       // Hiding all properties
       for (let propView of this.propertyViews) {
         propView.refresh();
       }
       return promise.resolve(undefined);
     }
 
-    this.tooltip.hide();
-
     if (aElement === this.viewedElement) {
       return promise.resolve(undefined);
     }
 
     this.viewedElement = aElement;
     this.refreshSourceFilter();
 
     return this.refreshPanel();
   },
 
+  /**
+   * Get the type of a given node in the computed-view
+   * @param {DOMNode} node The node which we want information about
+   * @return {Object} The type information object contains the following props:
+   * - type {String} One of the VIEW_NODE_XXX_TYPE const in
+   *   style-inspector-overlays
+   * - value {Object} Depends on the type of the node
+   * returns null of the node isn't anything we care about
+   */
+  getNodeInfo: function(node) {
+    let type, value;
+    let classes = node.classList;
+
+    if (classes.contains("property-name") ||
+        classes.contains("property-value") ||
+        (classes.contains("theme-link") && !classes.contains("link"))) {
+      // Go up to the common parent to find the property and value
+      let parent = node.parentNode;
+      while (!parent.classList.contains("property-view")) {
+        parent = parent.parentNode;
+      }
+      value = {
+        property: parent.querySelector(".property-name").textContent,
+        value: parent.querySelector(".property-value").textContent
+      };
+    }
+
+    if (classes.contains("property-name")) {
+      type = overlays.VIEW_NODE_PROPERTY_TYPE;
+    } else if (classes.contains("property-value")) {
+      type = overlays.VIEW_NODE_VALUE_TYPE;
+    } else if (classes.contains("theme-link")) {
+      type = overlays.VIEW_NODE_IMAGE_URL_TYPE;
+      value.url = node.textContent;
+    } else {
+      return null;
+    }
+
+    return {
+      type: type,
+      value: value
+    };
+  },
+
   _createPropertyViews: function()
   {
     if (this._createViewsPromise) {
       return this._createViewsPromise;
     }
 
     let deferred = promise.defer();
     this._createViewsPromise = deferred.promise;
@@ -347,17 +384,17 @@ CssHtmlTree.prototype = {
         }
         this.propertyViews.push(propView);
       },
       onCancel: () => {
         deferred.reject("_createPropertyViews cancelled");
       },
       onDone: () => {
         // Completed callback.
-        this.propertyContainer.appendChild(fragment);
+        this.element.appendChild(fragment);
         this.noResults.hidden = this.numVisibleProperties > 0;
         deferred.resolve(undefined);
       }
     });
 
     this._createViewsProcess.schedule();
     return deferred.promise;
   },
@@ -402,17 +439,17 @@ CssHtmlTree.prototype = {
       let deferred = promise.defer();
       this._refreshProcess = new UpdateProcess(this.styleWindow, this.propertyViews, {
         onItem: (aPropView) => {
           aPropView.refresh();
         },
         onDone: () => {
           this._refreshProcess = null;
           this.noResults.hidden = this.numVisibleProperties > 0;
-          this.styleInspector.inspector.emit("computed-view-refreshed");
+          this.inspector.emit("computed-view-refreshed");
           deferred.resolve(undefined);
         }
       });
       this._refreshProcess.schedule();
       return deferred.promise;
     }).then(null, (err) => console.error(err));
   },
 
@@ -516,116 +553,16 @@ CssHtmlTree.prototype = {
    */
   focusWindow: function(aEvent)
   {
     let win = this.styleDocument.defaultView;
     win.focus();
   },
 
   /**
-   * Get the css transform highlighter front, initializing it if needed
-   * @param a promise that resolves to the highlighter
-   */
-  getTransformHighlighter: function() {
-    if (this.transformHighlighterPromise) {
-      return this.transformHighlighterPromise;
-    }
-
-    let utils = this.styleInspector.inspector.toolbox.highlighterUtils;
-    this.transformHighlighterPromise =
-      utils.getHighlighterByType(TRANSFORM_HIGHLIGHTER_TYPE).then(highlighter => {
-        this.transformHighlighter = highlighter;
-        return this.transformHighlighter;
-      });
-
-    return this.transformHighlighterPromise;
-  },
-
-  _initTransformHighlighter: function() {
-    this.isTransformHighlighterShown = false;
-
-    this._onMouseMove = this._onMouseMove.bind(this);
-    this._onMouseLeave = this._onMouseLeave.bind(this);
-
-    this.propertyContainer.addEventListener("mousemove", this._onMouseMove, false);
-    this.propertyContainer.addEventListener("mouseleave", this._onMouseLeave, false);
-  },
-
-  _onMouseMove: function(event) {
-    if (event.target === this._lastHovered) {
-      return;
-    }
-
-    if (this.isTransformHighlighterShown) {
-      this.isTransformHighlighterShown = false;
-      this.getTransformHighlighter().then(highlighter => highlighter.hide());
-    }
-
-    this._lastHovered = event.target;
-    if (this._lastHovered.classList.contains("property-value")) {
-      let propName = this._lastHovered.parentNode.querySelector(".property-name");
-
-      if (propName.textContent === "transform") {
-        this.isTransformHighlighterShown = true;
-        let node = this.styleInspector.inspector.selection.nodeFront;
-        this.getTransformHighlighter().then(highlighter => highlighter.show(node));
-      }
-    }
-  },
-
-  _onMouseLeave: function(event) {
-    this._lastHovered = null;
-    if (this.isTransformHighlighterShown) {
-      this.isTransformHighlighterShown = false;
-      this.getTransformHighlighter().then(highlighter => highlighter.hide());
-    }
-  },
-
-  /**
-   * Executed by the tooltip when the pointer hovers over an element of the view.
-   * Used to decide whether the tooltip should be shown or not and to actually
-   * put content in it.
-   * Checks if the hovered target is a css value we support tooltips for.
-   */
-  _onTooltipTargetHover: function(target)
-  {
-    let inspector = this.styleInspector.inspector;
-
-    // Test for image url
-    if (target.classList.contains("theme-link") && inspector.hasUrlToImageDataResolver) {
-      let propValue = target.parentNode;
-      let propName = propValue.parentNode.querySelector(".property-name");
-      if (propName.textContent === "background-image") {
-        let maxDim = Services.prefs.getIntPref("devtools.inspector.imagePreviewTooltipSize");
-        let uri = CssLogic.getBackgroundImageUriFromProperty(propValue.textContent);
-        return this.tooltip.setRelativeImageContent(uri, inspector.inspector, maxDim);
-      }
-    }
-
-    if (target.classList.contains("property-value")) {
-      let propValue = target;
-      let propName = target.parentNode.querySelector(".property-name");
-
-      // Test for font family
-      if (propName.textContent === "font-family") {
-        let prop = propValue.textContent.toLowerCase();
-
-        if (prop !== "inherit" && prop !== "unset" && prop !== "initial") {
-          return this.tooltip.setFontFamilyContent(propValue.textContent,
-            inspector.selection.nodeFront);
-        }
-      }
-    }
-
-    // If the target isn't one that should receive a tooltip, signal it by rejecting
-    // a promise
-    return promise.reject();
-  },
-
-  /**
    * Create a context menu.
    */
   _buildContextMenu: function()
   {
     let doc = this.styleDocument.defaultView.parent.document;
 
     this._contextmenu = this.styleDocument.createElementNS(XUL_NS, "menupopup");
     this._contextmenu.addEventListener("popupshowing", this._contextMenuUpdate);
@@ -751,18 +688,17 @@ CssHtmlTree.prototype = {
   },
 
   _onClick: function(event) {
     let target = event.target;
 
     if (target.nodeName === "a") {
       event.stopPropagation();
       event.preventDefault();
-      let browserWin = this.styleInspector.inspector.target
-                           .tab.ownerDocument.defaultView;
+      let browserWin = this.inspector.target.tab.ownerDocument.defaultView;
       browserWin.openUILinkIn(target.href, "tab");
     }
   },
 
   _onCopyColor: function() {
     clipboardHelper.copyString(this._colorToCopy, this.styleDocument);
   },
 
@@ -820,18 +756,18 @@ CssHtmlTree.prototype = {
     Services.prefs.setBoolPref(PREF_ORIG_SOURCES, !isEnabled);
   },
 
   /**
    * Destructor for CssHtmlTree.
    */
   destroy: function CssHtmlTree_destroy()
   {
-    delete this.viewedElement;
-    delete this._outputParser;
+    this.viewedElement = null;
+    this._outputParser = null;
 
     // Remove event listeners
     this.includeBrowserStylesCheckbox.removeEventListener("command",
       this.includeBrowserStylesChanged);
     this.searchField.removeEventListener("command", this.filterChanged);
     gDevTools.off("pref-changed", this._handlePrefChange);
 
     this._prefObserver.off(PREF_ORIG_SOURCES, this._updateSourceLinks);
@@ -840,17 +776,17 @@ CssHtmlTree.prototype = {
     // Cancel tree construction
     if (this._createViewsProcess) {
       this._createViewsProcess.cancel();
     }
     if (this._refreshProcess) {
       this._refreshProcess.cancel();
     }
 
-    this.propertyContainer.removeEventListener("click", this._onClick, false);
+    this.element.removeEventListener("click", this._onClick, false);
 
     // Remove context menu
     if (this._contextmenu) {
       // Destroy the Select All menuitem.
       this.menuitemCopy.removeEventListener("command", this._onCopy);
       this.menuitemCopy = null;
 
       // Destroy the Copy menuitem.
@@ -864,51 +800,41 @@ CssHtmlTree.prototype = {
       // Destroy the context menu.
       this._contextmenu.removeEventListener("popupshowing", this._contextMenuUpdate);
       this._contextmenu.parentNode.removeChild(this._contextmenu);
       this._contextmenu = null;
     }
 
     this.popupNode = null;
 
-    this.tooltip.stopTogglingOnHover(this.propertyContainer);
-    this.tooltip.destroy();
-
-    if (this.transformHighlighter) {
-      this.transformHighlighter.finalize();
-      this.transformHighlighter = null;
-
-      this.propertyContainer.removeEventListener("mousemove", this._onMouseMove, false);
-      this.propertyContainer.removeEventListener("mouseleave", this._onMouseLeave, false);
-
-      this._lastHovered = null;
-    }
+    this.tooltips.destroy();
+    this.highlighters.destroy();
 
     // Remove bound listeners
     this.styleDocument.removeEventListener("contextmenu", this._onContextMenu);
     this.styleDocument.removeEventListener("copy", this._onCopy);
     this.styleDocument.removeEventListener("mousedown", this.focusWindow);
 
     // Nodes used in templating
-    delete this.root;
-    delete this.propertyContainer;
-    delete this.panel;
+    this.root = null;
+    this.element = null;
+    this.panel = null;
 
     // The document in which we display the results (csshtmltree.xul).
-    delete this.styleDocument;
+    this.styleDocument = null;
 
     for (let propView of this.propertyViews)  {
       propView.destroy();
     }
 
     // The element that we're inspecting, and the document that it comes from.
-    delete this.propertyViews;
-    delete this.styleWindow;
-    delete this.styleDocument;
-    delete this.styleInspector;
+    this.propertyViews = null;
+    this.styleWindow = null;
+    this.styleDocument = null;
+    this.styleInspector = null;
   }
 };
 
 function PropertyInfo(aTree, aName) {
   this.tree = aTree;
   this.name = aName;
 }
 PropertyInfo.prototype = {
@@ -1187,22 +1113,22 @@ PropertyView.prototype = {
         if (!this.matchedExpanded) {
           return;
         }
 
         this._matchedSelectorResponse = matched;
         CssHtmlTree.processTemplate(this.templateMatchedSelectors,
           this.matchedSelectorsContainer, this);
         this.matchedExpander.setAttribute("open", "");
-        this.tree.styleInspector.inspector.emit("computed-view-property-expanded");
+        this.tree.inspector.emit("computed-view-property-expanded");
       }).then(null, console.error);
     } else {
       this.matchedSelectorsContainer.innerHTML = "";
       this.matchedExpander.removeAttribute("open");
-      this.tree.styleInspector.inspector.emit("computed-view-property-collapsed");
+      this.tree.inspector.emit("computed-view-property-collapsed");
       return promise.resolve(undefined);
     }
   },
 
   get matchedSelectors()
   {
     return this._matchedSelectorResponse;
   },
@@ -1251,17 +1177,17 @@ PropertyView.prototype = {
     aEvent.preventDefault();
   },
 
   /**
    * The action when a user clicks on the MDN help link for a property.
    */
   mdnLinkClick: function PropertyView_mdnLinkClick(aEvent)
   {
-    let inspector = this.tree.styleInspector.inspector;
+    let inspector = this.tree.inspector;
 
     if (inspector.target.tab) {
       let browserWin = inspector.target.tab.ownerDocument.defaultView;
       browserWin.openUILinkIn(this.link, "tab");
     }
     aEvent.preventDefault();
   },
 
@@ -1393,19 +1319,19 @@ SelectorView.prototype = {
 
   /**
    * Update the text of the source link to reflect whether we're showing
    * original sources or not.
    */
   updateSourceLink: function()
   {
     this.updateSource().then((oldSource) => {
-      if (oldSource != this.source && this.tree.propertyContainer) {
+      if (oldSource != this.source && this.tree.element) {
         let selector = '[sourcelocation="' + oldSource + '"]';
-        let link = this.tree.propertyContainer.querySelector(selector);
+        let link = this.tree.element.querySelector(selector);
         if (link) {
           link.textContent = this.source;
           link.setAttribute("sourcelocation", this.source);
         }
       }
     });
   },
 
@@ -1464,17 +1390,17 @@ SelectorView.prototype = {
    *
    *   We can only view stylesheets contained in document.styleSheets inside the
    *   style editor.
    *
    * @param aEvent The click event
    */
   openStyleEditor: function(aEvent)
   {
-    let inspector = this.tree.styleInspector.inspector;
+    let inspector = this.tree.inspector;
     let rule = this.selectorInfo.rule;
 
     // The style editor can only display stylesheets coming from content because
     // chrome stylesheets are not listed in the editor's stylesheet selector.
     //
     // If the stylesheet is a content stylesheet we send it to the style
     // editor else we display it in the view source window.
     let sheet = rule.parentStyleSheet;
--- a/browser/devtools/styleinspector/rule-view.js
+++ b/browser/devtools/styleinspector/rule-view.js
@@ -7,29 +7,28 @@
 "use strict";
 
 const {Cc, Ci, Cu} = require("chrome");
 const {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {});
 const {CssLogic} = require("devtools/styleinspector/css-logic");
 const {InplaceEditor, editableField, editableItem} = require("devtools/shared/inplace-editor");
 const {ELEMENT_STYLE, PSEUDO_ELEMENTS} = require("devtools/server/actors/styles");
 const {gDevTools} = Cu.import("resource:///modules/devtools/gDevTools.jsm", {});
-const {Tooltip, SwatchColorPickerTooltip} = require("devtools/shared/widgets/Tooltip");
 const {OutputParser} = require("devtools/output-parser");
 const {PrefObserver, PREF_ORIG_SOURCES} = require("devtools/styleeditor/utils");
 const {parseSingleValue, parseDeclarations} = require("devtools/styleinspector/css-parsing-utils");
+const overlays = require("devtools/styleinspector/style-inspector-overlays");
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 const HTML_NS = "http://www.w3.org/1999/xhtml";
 const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 const PREF_UA_STYLES = "devtools.inspector.showUserAgentStyles";
 const PREF_DEFAULT_COLOR_UNIT = "devtools.defaultColorUnit";
-const TRANSFORM_HIGHLIGHTER_TYPE = "CssTransformHighlighter";
 
 /**
  * These regular expressions are adapted from firebug's css.js, and are
  * used to parse CSSStyleDeclaration's cssText attribute.
  */
 
 // Used to split on css line separators
 const CSS_LINE_RE = /(?:[^;\(]*(?:\([^\)]*?\))?[^;\(]*)*;?/g;
@@ -1095,33 +1094,24 @@ function CssRuleView(aInspector, aDoc, a
   this.showUserAgentStyles = Services.prefs.getBoolPref(PREF_UA_STYLES);
 
   let options = {
     autoSelect: true,
     theme: "auto"
   };
   this.popup = new AutocompletePopup(aDoc.defaultView.parent.document, options);
 
-  // Create a tooltip for previewing things in the rule view (images for now)
-  this.previewTooltip = new Tooltip(this.inspector.panelDoc);
-  this.previewTooltip.startTogglingOnHover(this.element,
-    this._onTooltipTargetHover.bind(this));
-
-  // Also create a more complex tooltip for editing colors with the spectrum
-  // color picker
-  this.colorPicker = new SwatchColorPickerTooltip(this.inspector.panelDoc);
-
   this._buildContextMenu();
   this._showEmpty();
 
-  // Initialize the css transform highlighter if the target supports it
-  let hUtils = this.inspector.toolbox.highlighterUtils;
-  if (hUtils.hasCustomHighlighter(TRANSFORM_HIGHLIGHTER_TYPE)) {
-    this._initTransformHighlighter();
-  }
+  // Add the tooltips and highlighters to the view
+  this.tooltips = new overlays.TooltipsOverlay(this);
+  this.tooltips.addToView();
+  this.highlighters = new overlays.HighlightersOverlay(this);
+  this.highlighters.addToView();
 }
 
 exports.CssRuleView = CssRuleView;
 
 CssRuleView.prototype = {
   // The element that we're inspecting.
   _viewedElement: null,
 
@@ -1161,147 +1151,16 @@ CssRuleView.prototype = {
       popupset = doc.createElementNS(XUL_NS, "popupset");
       doc.documentElement.appendChild(popupset);
     }
 
     popupset.appendChild(this._contextmenu);
   },
 
   /**
-   * Get the css transform highlighter front, initializing it if needed
-   * @param a promise that resolves to the highlighter
-   */
-  getTransformHighlighter: function() {
-    if (this.transformHighlighterPromise) {
-      return this.transformHighlighterPromise;
-    }
-
-    let utils = this.inspector.toolbox.highlighterUtils;
-    this.transformHighlighterPromise =
-    utils.getHighlighterByType(TRANSFORM_HIGHLIGHTER_TYPE).then(highlighter => {
-      this.transformHighlighter = highlighter;
-      return this.transformHighlighter;
-    });
-
-    return this.transformHighlighterPromise;
-  },
-
-  _initTransformHighlighter: function() {
-    this.isTransformHighlighterShown = false;
-
-    this._onMouseMove = this._onMouseMove.bind(this);
-    this._onMouseLeave = this._onMouseLeave.bind(this);
-
-    this.element.addEventListener("mousemove", this._onMouseMove, false);
-    this.element.addEventListener("mouseleave", this._onMouseLeave, false);
-  },
-
-  _onMouseMove: function(event) {
-    if (event.target === this._lastHovered) {
-      return;
-    }
-
-    if (this.isTransformHighlighterShown) {
-      this.isTransformHighlighterShown = false;
-      this.getTransformHighlighter().then(highlighter => highlighter.hide());
-    }
-
-    this._lastHovered = event.target;
-    let prop = event.target.textProperty;
-    let isHighlightable = prop && prop.name === "transform" &&
-                          prop.enabled && !prop.overridden &&
-                          !prop.rule.pseudoElement;
-
-    if (isHighlightable) {
-      this.isTransformHighlighterShown = true;
-      let node = this.inspector.selection.nodeFront;
-      this.getTransformHighlighter().then(highlighter => highlighter.show(node));
-    }
-  },
-
-  _onMouseLeave: function(event) {
-    this._lastHovered = null;
-    if (this.isTransformHighlighterShown) {
-      this.isTransformHighlighterShown = false;
-      this.getTransformHighlighter().then(highlighter => highlighter.hide());
-    }
-  },
-
-  /**
-   * Which type of hover-tooltip should be shown for the given element?
-   * This depends on the element: does it contain a URL, a font-family, ...
-   * @param {DOMNode} el The element to test
-   * @return {String} The type of hover-tooltip
-   */
-  _getHoverTooltipTypeForTarget: function(el) {
-    let prop = el.textProperty;
-
-    // Test for image
-    let isUrl = el.classList.contains("theme-link") &&
-                el.parentNode.classList.contains("ruleview-propertyvalue");
-    if (this.inspector.hasUrlToImageDataResolver && isUrl) {
-      return "image";
-    }
-
-    // Test for font-family
-    let propertyRoot = el.parentNode;
-    let propertyNameNode = propertyRoot.querySelector(".ruleview-propertyname");
-    if (!propertyNameNode) {
-      propertyRoot = propertyRoot.parentNode;
-      propertyNameNode = propertyRoot.querySelector(".ruleview-propertyname");
-    }
-    let propertyName;
-    if (propertyNameNode) {
-      propertyName = propertyNameNode.textContent;
-    }
-    if (propertyName === "font-family" && el.classList.contains("ruleview-propertyvalue")) {
-      return "font";
-    }
-  },
-
-  /**
-   * Executed by the tooltip when the pointer hovers over an element of the view.
-   * Used to decide whether the tooltip should be shown or not and to actually
-   * put content in it.
-   * Checks if the hovered target is a css value we support tooltips for.
-   * @param {DOMNode} target
-   * @return {Boolean|Promise} Either a boolean or a promise, used by the
-   * Tooltip class to wait for the content to be put in the tooltip and finally
-   * decide whether or not the tooltip should be shown.
-   */
-  _onTooltipTargetHover: function(target) {
-    let tooltipType = this._getHoverTooltipTypeForTarget(target);
-    if (!tooltipType) {
-      return false;
-    }
-
-    if (this.colorPicker.tooltip.isShown()) {
-      this.colorPicker.revert();
-      this.colorPicker.hide();
-    }
-
-    if (tooltipType === "image") {
-      let prop = target.parentNode.textProperty;
-      let dim = Services.prefs.getIntPref("devtools.inspector.imagePreviewTooltipSize");
-      let uri = CssLogic.getBackgroundImageUriFromProperty(prop.value, prop.rule.domRule.href);
-      return this.previewTooltip.setRelativeImageContent(uri, this.inspector.inspector, dim);
-    }
-    if (tooltipType === "font") {
-      let prop = target.textContent.toLowerCase();
-
-      if (prop !== "inherit" && prop !== "unset" && prop !== "initial") {
-        return this.previewTooltip.setFontFamilyContent(target.textContent,
-          this.inspector.selection.nodeFront);
-      }
-    }
-
-    return false;
-  },
-
-  /**
    * Update the context menu. This means enabling or disabling menuitems as
    * appropriate.
    */
   _contextMenuUpdate: function() {
     let win = this.doc.defaultView;
 
     // Copy selection.
     let selection = win.getSelection();
@@ -1334,16 +1193,75 @@ CssRuleView.prototype = {
                                       _strings.GetStringFromName(label));
 
     let accessKey = label + ".accessKey";
     this.menuitemSources.setAttribute("accesskey",
                                       _strings.GetStringFromName(accessKey));
   },
 
   /**
+   * Get the type of a given node in the rule-view
+   * @param {DOMNode} node The node which we want information about
+   * @return {Object} The type information object contains the following props:
+   * - type {String} One of the VIEW_NODE_XXX_TYPE const in
+   *   style-inspector-overlays
+   * - value {Object} Depends on the type of the node
+   * returns null of the node isn't anything we care about
+   */
+  getNodeInfo: function(node) {
+    let type, value;
+    let classes = node.classList;
+    let prop = getParentTextProperty(node);
+
+    if (classes.contains("ruleview-propertyname") && prop) {
+      type = overlays.VIEW_NODE_PROPERTY_TYPE;
+      value = {
+        property: node.textContent,
+        value: getPropertyNameAndValue(node).value,
+        enabled: prop.enabled,
+        overridden: prop.overridden,
+        pseudoElement: prop.rule.pseudoElement,
+        sheetHref: prop.rule.domRule.href
+      };
+    } else if (classes.contains("ruleview-propertyvalue") && prop) {
+      type = overlays.VIEW_NODE_VALUE_TYPE;
+      value = {
+        property: getPropertyNameAndValue(node).name,
+        value: node.textContent,
+        enabled: prop.enabled,
+        overridden: prop.overridden,
+        pseudoElement: prop.rule.pseudoElement,
+        sheetHref: prop.rule.domRule.href
+      };
+    } else if (classes.contains("theme-link") && prop) {
+      type = overlays.VIEW_NODE_IMAGE_URL_TYPE;
+      value = {
+        property: getPropertyNameAndValue(node).name,
+        value: node.parentNode.textContent,
+        url: node.textContent,
+        enabled: prop.enabled,
+        overridden: prop.overridden,
+        pseudoElement: prop.rule.pseudoElement,
+        sheetHref: prop.rule.domRule.href
+      };
+    } else if (classes.contains("ruleview-selector-unmatched") ||
+               classes.contains("ruleview-selector-matched")) {
+      type = overlays.VIEW_NODE_SELECTOR_TYPE;
+      value = node.textContent;
+    } else {
+      return null;
+    }
+
+    return {
+      type: type,
+      value: value
+    };
+  },
+
+  /**
    * A helper that determines if the popup was opened with a click to a color
    * value and saves the color to this._colorToCopy.
    *
    * @return {Boolean}
    *         true if click on color opened the popup, false otherwise.
    */
   _isColorPopup: function () {
     this._colorToCopy = "";
@@ -1440,17 +1358,17 @@ CssRuleView.prototype = {
     this.pageStyle = aPageStyle;
   },
 
   /**
    * Return {bool} true if the rule view currently has an input editor visible.
    */
   get isEditing() {
     return this.element.querySelectorAll(".styleinspector-propertyeditor").length > 0
-      || this.colorPicker.tooltip.isShown();
+      || this.tooltips.colorPicker.tooltip.isShown();
   },
 
   _handlePrefChange: function(pref) {
     if (pref === PREF_UA_STYLES) {
       this.showUserAgentStyles = Services.prefs.getBoolPref(pref);
     }
 
     // Reselect the currently selected element
@@ -1512,29 +1430,18 @@ CssRuleView.prototype = {
       this._contextmenu.removeEventListener("popupshowing", this._contextMenuUpdate);
       this._contextmenu.parentNode.removeChild(this._contextmenu);
       this._contextmenu = null;
     }
 
     // We manage the popupNode ourselves so we also need to destroy it.
     this.doc.popupNode = null;
 
-    this.previewTooltip.stopTogglingOnHover(this.element);
-    this.previewTooltip.destroy();
-    this.colorPicker.destroy();
-
-    if (this.transformHighlighter) {
-      this.transformHighlighter.finalize();
-      this.transformHighlighter = null;
-
-      this.element.removeEventListener("mousemove", this._onMouseMove, false);
-      this.element.removeEventListener("mouseleave", this._onMouseLeave, false);
-
-      this._lastHovered = null;
-    }
+    this.tooltips.destroy();
+    this.highlighters.destroy();
 
     if (this.element.parentNode) {
       this.element.parentNode.removeChild(this.element);
     }
 
     if (this.elementStyle) {
       this.elementStyle.destroy();
     }
@@ -1635,19 +1542,16 @@ CssRuleView.prototype = {
 
   /**
    * Clear the rule view.
    */
   clear: function() {
     this._clearRules();
     this._viewedElement = null;
     this._elementStyle = null;
-
-    this.previewTooltip.hide();
-    this.colorPicker.hide();
   },
 
   /**
    * Called when the user has made changes to the ElementStyle.
    * Emits an event that clients can listen to.
    */
   _changed: function() {
     var evt = this.doc.createEvent("Events");
@@ -2149,18 +2053,18 @@ function TextPropertyEditor(aRuleEditor,
 }
 
 TextPropertyEditor.prototype = {
   /**
    * Boolean indicating if the name or value is being currently edited.
    */
   get editing() {
     return !!(this.nameSpan.inplaceEditor || this.valueSpan.inplaceEditor ||
-      this.ruleEditor.ruleView.colorPicker.tooltip.isShown() ||
-      this.ruleEditor.ruleView.colorPicker.eyedropperOpen) ||
+      this.ruleEditor.ruleView.tooltips.colorPicker.tooltip.isShown() ||
+      this.ruleEditor.ruleView.tooltips.colorPicker.eyedropperOpen) ||
       this.popup.isOpen;
   },
 
   /**
    * Create the property editor's DOM.
    */
   _create: function() {
     this.element = this.doc.createElementNS(HTML_NS, "li");
@@ -2202,19 +2106,20 @@ TextPropertyEditor.prototype = {
     // Property value, editable when focused.  Changes to the
     // property value are applied as they are typed, and reverted
     // if the user presses escape.
     this.valueSpan = createChild(propertyContainer, "span", {
       class: "ruleview-propertyvalue theme-fg-color1",
       tabindex: this.ruleEditor.isEditable ? "0" : "-1",
     });
 
-    // Storing the TextProperty on the valuespan for easy access
+    // Storing the TextProperty on the elements for easy access
     // (for instance by the tooltip)
     this.valueSpan.textProperty = this.prop;
+    this.nameSpan.textProperty = this.prop;
 
     // Save the initial value as the last committed value,
     // for restoring after pressing escape.
     this.committed = { name: this.prop.name,
                        value: this.prop.value,
                        priority: this.prop.priority };
 
     appendText(propertyContainer, ";");
@@ -2400,17 +2305,17 @@ TextPropertyEditor.prototype = {
 
     // Attach the color picker tooltip to the color swatches
     this._swatchSpans = this.valueSpan.querySelectorAll("." + swatchClass);
     if (this.ruleEditor.isEditable) {
       for (let span of this._swatchSpans) {
         // Capture the original declaration value to be able to revert later
         let originalValue = this.valueSpan.textContent;
         // Adding this swatch to the list of swatches our colorpicker knows about
-        this.ruleEditor.ruleView.colorPicker.addSwatch(span, {
+        this.ruleEditor.ruleView.tooltips.colorPicker.addSwatch(span, {
           onPreview: () => this._previewValue(this.valueSpan.textContent),
           onCommit: () => this._applyNewValue(this.valueSpan.textContent),
           onRevert: () => this._applyNewValue(originalValue)
         });
       }
     }
 
     // Populate the computed styles.
@@ -2541,22 +2446,23 @@ TextPropertyEditor.prototype = {
 
   /**
    * Remove property from style and the editors from DOM.
    * Begin editing next available property.
    */
   remove: function() {
     if (this._swatchSpans && this._swatchSpans.length) {
       for (let span of this._swatchSpans) {
-        this.ruleEditor.ruleView.colorPicker.removeSwatch(span);
+        this.ruleEditor.ruleView.tooltips.colorPicker.removeSwatch(span);
       }
     }
 
     this.element.parentNode.removeChild(this.element);
     this.ruleEditor.rule.editClosestTextProperty(this.prop);
+    this.nameSpan.textProperty = null;
     this.valueSpan.textProperty = null;
     this.prop.remove();
   },
 
   /**
    * Called when a value editor closes.  If the user pressed escape,
    * revert to the value this property had before editing.
    *
@@ -2865,16 +2771,73 @@ function blurOnMultipleProperties(e) {
 
 /**
  * Append a text node to an element.
  */
 function appendText(aParent, aText) {
   aParent.appendChild(aParent.ownerDocument.createTextNode(aText));
 }
 
+/**
+ * Walk up the DOM from a given node until a parent property holder is found.
+ * For elements inside the computed property list, the non-computed parent
+ * property holder will be returned
+ * @param {DOMNode} node The node to start from
+ * @return {DOMNode} The parent property holder node, or null if not found
+ */
+function getParentTextPropertyHolder(node) {
+  while (true) {
+    if (!node || !node.classList) {
+      return null;
+    }
+    if (node.classList.contains("ruleview-property")) {
+      return node;
+    }
+    node = node.parentNode;
+  }
+}
+
+/**
+ * For any given node, find the TextProperty it is in if any
+ * @param {DOMNode} node The node to start from
+ * @return {TextProperty}
+ */
+function getParentTextProperty(node) {
+  let parent = getParentTextPropertyHolder(node);
+  if (!parent) {
+    return null;
+  }
+  return parent.querySelector(".ruleview-propertyvalue").textProperty;
+}
+
+/**
+ * Walker up the DOM from a given node until a parent property holder is found,
+ * and return the textContent for the name and value nodes.
+ * Stops at the first property found, so if node is inside the computed property
+ * list, the computed property will be returned
+ * @param {DOMNode} node The node to start from
+ * @return {Object} {name, value}
+ */
+function getPropertyNameAndValue(node) {
+  while (true) {
+    if (!node || !node.classList) {
+      return null;
+    }
+    // Check first for ruleview-computed since it's the deepest
+    if (node.classList.contains("ruleview-computed") ||
+        node.classList.contains("ruleview-property")) {
+      return {
+        name: node.querySelector(".ruleview-propertyname").textContent,
+        value: node.querySelector(".ruleview-propertyvalue").textContent
+      };
+    }
+    node = node.parentNode;
+  }
+}
+
 XPCOMUtils.defineLazyGetter(this, "clipboardHelper", function() {
   return Cc["@mozilla.org/widget/clipboardhelper;1"].
     getService(Ci.nsIClipboardHelper);
 });
 
 XPCOMUtils.defineLazyGetter(this, "_strings", function() {
   return Services.strings.createBundle(
     "chrome://global/locale/devtools/styleinspector.properties");
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleinspector/style-inspector-overlays.js
@@ -0,0 +1,375 @@
+/* -*- Mode: Javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* 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";
+
+// The style-inspector overlays are:
+// - tooltips that appear when hovering over property values
+// - editor tooltips that appear when clicking color swatches, etc.
+// - in-content highlighters that appear when hovering over property values
+// - etc.
+
+const {Cc, Ci, Cu} = require("chrome");
+const {
+  Tooltip,
+  SwatchColorPickerTooltip
+} = require("devtools/shared/widgets/Tooltip");
+const {CssLogic} = require("devtools/styleinspector/css-logic");
+const {Promise:promise} = Cu.import("resource://gre/modules/Promise.jsm", {});
+Cu.import("resource://gre/modules/Task.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+const PREF_IMAGE_TOOLTIP_SIZE = "devtools.inspector.imagePreviewTooltipSize";
+
+// Types of existing tooltips
+const TOOLTIP_IMAGE_TYPE = "image";
+const TOOLTIP_FONTFAMILY_TYPE = "font-family";
+
+// Types of existing highlighters
+const HIGHLIGHTER_TRANSFORM_TYPE = "CssTransformHighlighter";
+const HIGHLIGHTER_TYPES = [
+  HIGHLIGHTER_TRANSFORM_TYPE
+];
+
+// Types of nodes in the rule/computed-view
+const VIEW_NODE_SELECTOR_TYPE = exports.VIEW_NODE_SELECTOR_TYPE = 1;
+const VIEW_NODE_PROPERTY_TYPE = exports.VIEW_NODE_PROPERTY_TYPE = 2;
+const VIEW_NODE_VALUE_TYPE = exports.VIEW_NODE_VALUE_TYPE = 3;
+const VIEW_NODE_IMAGE_URL_TYPE = exports.VIEW_NODE_IMAGE_URL_TYPE = 4;
+
+/**
+ * Manages all highlighters in the style-inspector.
+ * @param {CssRuleView|CssHtmlTree} view Either the rule-view or computed-view
+ * panel
+ */
+function HighlightersOverlay(view) {
+  this.view = view;
+
+  let {CssRuleView} = require("devtools/styleinspector/rule-view");
+  this.isRuleView = view instanceof CssRuleView;
+
+  this.highlighterUtils = this.view.inspector.toolbox.highlighterUtils;
+
+  this._onMouseMove = this._onMouseMove.bind(this);
+  this._onMouseLeave = this._onMouseLeave.bind(this);
+
+  this.promises = {};
+  this.highlighters = {};
+
+  // Only initialize the overlay if at least one of the highlighter types is
+  // supported
+  this.supportsHighlighters = HIGHLIGHTER_TYPES.some(type => {
+    return this.highlighterUtils.hasCustomHighlighter(type);
+  });
+}
+
+exports.HighlightersOverlay = HighlightersOverlay;
+
+HighlightersOverlay.prototype = {
+  /**
+   * Add the highlighters overlay to the view. This will start tracking mouse
+   * movements and display highlighters when needed
+   */
+  addToView: function() {
+    if (!this.supportsHighlighters || this._isStarted || this._isDestroyed) {
+      return;
+    }
+
+    let el = this.view.element;
+    el.addEventListener("mousemove", this._onMouseMove, false);
+    el.addEventListener("mouseleave", this._onMouseLeave, false);
+
+    this._isStarted = true;
+  },
+
+  /**
+   * Remove the overlay from the current view. This will stop tracking mouse
+   * movement and showing highlighters
+   */
+  removeFromView: function() {
+    if (!this.supportsHighlighters || !this._isStarted || this._isDestroyed) {
+      return;
+    }
+
+    this._hideCurrent();
+
+    let el = this.view.element;
+    el.removeEventListener("mousemove", this._onMouseMove, false);
+    el.removeEventListener("mouseleave", this._onMouseLeave, false);
+
+    this._isStarted = false;
+  },
+
+  _onMouseMove: function(event) {
+    // Bail out if the target is the same as for the last mousemove
+    if (event.target === this._lastHovered) {
+      return;
+    }
+
+    // Only one highlighter can be displayed at a time, hide the currently shown
+    this._hideCurrent();
+
+    this._lastHovered = event.target;
+
+    let nodeInfo = this.view.getNodeInfo(event.target);
+    if (!nodeInfo) {
+      return;
+    }
+
+    // Choose the type of highlighter required for the hovered node
+    let type;
+    if (this._isRuleViewTransform(nodeInfo) ||
+        this._isComputedViewTransform(nodeInfo)) {
+      type = HIGHLIGHTER_TRANSFORM_TYPE;
+    }
+
+    if (type) {
+      this.highlighterShown = type;
+      let node = this.view.inspector.selection.nodeFront;
+      this._getHighlighter(type).then(highlighter => highlighter.show(node));
+    }
+  },
+
+  _onMouseLeave: function(event) {
+    this._lastHovered = null;
+    this._hideCurrent();
+  },
+
+  /**
+   * Is the current hovered node a css transform property value in the rule-view
+   * @param {Object} nodeInfo
+   * @return {Boolean}
+   */
+  _isRuleViewTransform: function(nodeInfo) {
+    let isTransform = nodeInfo.type === VIEW_NODE_VALUE_TYPE &&
+                      nodeInfo.value.property === "transform";
+    let isEnabled = nodeInfo.value.enabled &&
+                    !nodeInfo.value.overridden &&
+                    !nodeInfo.value.pseudoElement;
+    return this.isRuleView && isTransform && isEnabled;
+  },
+
+  /**
+   * Is the current hovered node a css transform property value in the
+   * computed-view
+   * @param {Object} nodeInfo
+   * @return {Boolean}
+   */
+  _isComputedViewTransform: function(nodeInfo) {
+    let isTransform = nodeInfo.type === VIEW_NODE_VALUE_TYPE &&
+                      nodeInfo.value.property === "transform";
+    return !this.isRuleView && isTransform;
+  },
+
+  /**
+   * Hide the currently shown highlighter
+   */
+  _hideCurrent: function() {
+    if (this.highlighterShown) {
+      this._getHighlighter(this.highlighterShown).then(highlighter => {
+        highlighter.hide();
+        this.highlighterShown = null;
+      });
+    }
+  },
+
+  /**
+   * Get a highlighter front given a type. It will only be initialized once
+   * @param {String} type The highlighter type. One of this.highlighters
+   * @return a promise that resolves to the highlighter
+   */
+  _getHighlighter: function(type) {
+    let utils = this.highlighterUtils;
+    if (!utils.hasCustomHighlighter(type)) {
+      return promise.reject();
+    }
+
+    if (this.promises[type]) {
+      return this.promises[type];
+    }
+
+    return this.promises[type] = utils.getHighlighterByType(type).then(highlighter => {
+      this.highlighters[type] = highlighter;
+      return highlighter;
+    });
+  },
+
+  /**
+   * Destroy this overlay instance, removing it from the view and destroying
+   * all initialized highlighters
+   */
+  destroy: function() {
+    this.removeFromView();
+
+    for (let type in this.highlighters) {
+      if (this.highlighters[type]) {
+        this.highlighters[type].finalize();
+        this.highlighters[type] = null;
+      }
+    }
+
+    this.promises = null;
+    this.view = null;
+    this.highlighterUtils = null;
+
+    this._isDestroyed = true;
+  }
+};
+
+/**
+ * Manages all tooltips in the style-inspector.
+ * @param {CssRuleView|CssHtmlTree} view Either the rule-view or computed-view
+ * panel
+ */
+function TooltipsOverlay(view) {
+  this.view = view;
+
+  let {CssRuleView} = require("devtools/styleinspector/rule-view");
+  this.isRuleView = view instanceof CssRuleView;
+
+  this._onNewSelection = this._onNewSelection.bind(this);
+  this.view.inspector.selection.on("new-node-front", this._onNewSelection);
+}
+
+exports.TooltipsOverlay = TooltipsOverlay;
+
+TooltipsOverlay.prototype = {
+  /**
+   * Add the tooltips overlay to the view. This will start tracking mouse
+   * movements and display tooltips when needed
+   */
+  addToView: function() {
+    if (this._isStarted || this._isDestroyed) {
+      return;
+    }
+
+    // Image, fonts, ... preview tooltip
+    this.previewTooltip = new Tooltip(this.view.inspector.panelDoc);
+    this.previewTooltip.startTogglingOnHover(this.view.element,
+      this._onPreviewTooltipTargetHover.bind(this));
+
+    // Color picker tooltip
+    if (this.isRuleView) {
+      this.colorPicker = new SwatchColorPickerTooltip(this.view.inspector.panelDoc);
+    }
+
+    this._isStarted = true;
+  },
+
+  /**
+   * Remove the tooltips overlay from the view. This will stop tracking mouse
+   * movements and displaying tooltips
+   */
+  removeFromView: function() {
+    if (!this._isStarted || this._isDestroyed) {
+      return;
+    }
+
+    this.previewTooltip.stopTogglingOnHover(this.view.element);
+    this.previewTooltip.destroy();
+
+    if (this.colorPicker) {
+      this.colorPicker.destroy();
+    }
+
+    this._isStarted = false;
+  },
+
+  /**
+   * Given a hovered node info, find out which type of tooltip should be shown,
+   * if any
+   * @param {Object} nodeInfo
+   * @return {String} The tooltip type to be shown, or null
+   */
+  _getTooltipType: function({type, value:prop}) {
+    let tooltipType = null;
+    let inspector = this.view.inspector;
+
+    // Image preview tooltip
+    if (type === VIEW_NODE_IMAGE_URL_TYPE && inspector.hasUrlToImageDataResolver) {
+      let dim = Services.prefs.getIntPref(PREF_IMAGE_TOOLTIP_SIZE);
+      let uri = CssLogic.getBackgroundImageUriFromProperty(prop.value,
+        prop.sheetHref); // sheetHref is undefined for computed-view properties,
+                         // but we don't care as URIs are absolute
+      tooltipType = TOOLTIP_IMAGE_TYPE;
+    }
+
+    // Font preview tooltip
+    if (type === VIEW_NODE_VALUE_TYPE && prop.property === "font-family") {
+      let value = prop.value.toLowerCase();
+      if (value !== "inherit" && value !== "unset" && value !== "initial") {
+        tooltipType = TOOLTIP_FONTFAMILY_TYPE;
+      }
+    }
+
+    return tooltipType;
+  },
+
+  /**
+   * Executed by the tooltip when the pointer hovers over an element of the view.
+   * Used to decide whether the tooltip should be shown or not and to actually
+   * put content in it.
+   * Checks if the hovered target is a css value we support tooltips for.
+   * @param {DOMNode} target The currently hovered node
+   */
+  _onPreviewTooltipTargetHover: function(target) {
+    let nodeInfo = this.view.getNodeInfo(target);
+    if (!nodeInfo) {
+      // The hovered node isn't something we care about
+      return promise.reject();
+    }
+
+    let type = this._getTooltipType(nodeInfo);
+    if (!type) {
+      // There is no tooltip type defined for the hovered node
+      return promise.reject();
+    }
+
+    if (this.isRuleView && this.colorPicker.tooltip.isShown()) {
+      this.colorPicker.revert();
+      this.colorPicker.hide();
+    }
+
+    let inspector = this.view.inspector;
+
+    if (type === TOOLTIP_IMAGE_TYPE) {
+      let dim = Services.prefs.getIntPref(PREF_IMAGE_TOOLTIP_SIZE);
+      let uri = CssLogic.getBackgroundImageUriFromProperty(nodeInfo.value.value,
+        nodeInfo.value.sheetHref); // sheetHref is undefined for computed-view
+                                   // properties, but we don't care as uris are
+                                   // absolute
+      return this.previewTooltip.setRelativeImageContent(uri,
+        inspector.inspector, dim);
+    }
+
+    if (type === TOOLTIP_FONTFAMILY_TYPE) {
+      return this.previewTooltip.setFontFamilyContent(nodeInfo.value.value,
+        inspector.selection.nodeFront);
+    }
+  },
+
+  _onNewSelection: function() {
+    if (this.previewTooltip) {
+      this.previewTooltip.hide();
+    }
+
+    if (this.colorPicker) {
+      this.colorPicker.hide();
+    }
+  },
+
+  /**
+   * Destroy this overlay instance, removing it from the view
+   */
+  destroy: function() {
+    this.removeFromView();
+
+    this.view.inspector.selection.off("new-node-front", this._onNewSelection);
+    this.view = null;
+
+    this._isDestroyed = true;
+  }
+};
--- a/browser/devtools/styleinspector/test/browser_ruleview_colorpicker-and-image-tooltip_01.js
+++ b/browser/devtools/styleinspector/test/browser_ruleview_colorpicker-and-image-tooltip_01.js
@@ -25,22 +25,22 @@ let test = asyncTest(function*() {
   let value = getRuleViewProperty(view, "body", "background").valueSpan;
   let swatch = value.querySelector(".ruleview-colorswatch");
   let url = value.querySelector(".theme-link");
   yield testImageTooltipAfterColorChange(swatch, url, view);
 });
 
 function* testImageTooltipAfterColorChange(swatch, url, ruleView) {
   info("First, verify that the image preview tooltip works");
-  let anchor = yield isHoverTooltipTarget(ruleView.previewTooltip, url);
+  let anchor = yield isHoverTooltipTarget(ruleView.tooltips.previewTooltip, url);
   ok(anchor, "The image preview tooltip is shown on the url span");
   is(anchor, url, "The anchor returned by the showOnHover callback is correct");
 
   info("Open the color picker tooltip and change the color");
-  let picker = ruleView.colorPicker;
+  let picker = ruleView.tooltips.colorPicker;
   let onShown = picker.tooltip.once("shown");
   swatch.click();
   yield onShown;
   yield simulateColorPickerChange(picker, [0, 0, 0, 1], {
     element: content.document.body,
     name: "backgroundImage",
     value: 'url("chrome://global/skin/icons/warning-64.png"), linear-gradient(rgb(0, 0, 0), rgb(255, 0, 102) 400px)'
   });
@@ -49,12 +49,12 @@ function* testImageTooltipAfterColorChan
   let onHidden = picker.tooltip.once("hidden");
   EventUtils.sendKey("RETURN", spectrum.element.ownerDocument.defaultView);
   yield onHidden;
 
   info("Verify again that the image preview tooltip works");
   // After a color change, the property is re-populated, we need to get the new
   // dom node
   url = getRuleViewProperty(ruleView, "body", "background").valueSpan.querySelector(".theme-link");
-  let anchor = yield isHoverTooltipTarget(ruleView.previewTooltip, url);
+  let anchor = yield isHoverTooltipTarget(ruleView.tooltips.previewTooltip, url);
   ok(anchor, "The image preview tooltip is shown on the url span");
   is(anchor, url, "The anchor returned by the showOnHover callback is correct");
 }
--- a/browser/devtools/styleinspector/test/browser_ruleview_colorpicker-and-image-tooltip_02.js
+++ b/browser/devtools/styleinspector/test/browser_ruleview_colorpicker-and-image-tooltip_02.js
@@ -26,17 +26,17 @@ let test = asyncTest(function*() {
   yield testColorChangeIsntRevertedWhenOtherTooltipIsShown(view);
 });
 
 function* testColorChangeIsntRevertedWhenOtherTooltipIsShown(ruleView) {
   let swatch = getRuleViewProperty(ruleView, "body", "background").valueSpan
     .querySelector(".ruleview-colorswatch");
 
   info("Open the color picker tooltip and change the color");
-  let picker = ruleView.colorPicker;
+  let picker = ruleView.tooltips.colorPicker;
   let onShown = picker.tooltip.once("shown");
   swatch.click();
   yield onShown;
 
   yield simulateColorPickerChange(picker, [0, 0, 0, 1], {
     element: content.document.body,
     name: "backgroundColor",
     value: "rgb(0, 0, 0)"
@@ -44,18 +44,18 @@ function* testColorChangeIsntRevertedWhe
   let spectrum = yield picker.spectrum;
   let onHidden = picker.tooltip.once("hidden");
   EventUtils.sendKey("RETURN", spectrum.element.ownerDocument.defaultView);
   yield onHidden;
 
   info("Open the image preview tooltip");
   let value = getRuleViewProperty(ruleView, "body", "background").valueSpan;
   let url = value.querySelector(".theme-link");
-  let onShown = ruleView.previewTooltip.once("shown");
-  let anchor = yield isHoverTooltipTarget(ruleView.previewTooltip, url);
-  ruleView.previewTooltip.show(anchor);
+  let onShown = ruleView.tooltips.previewTooltip.once("shown");
+  let anchor = yield isHoverTooltipTarget(ruleView.tooltips.previewTooltip, url);
+  ruleView.tooltips.previewTooltip.show(anchor);
   yield onShown;
 
   info("Image tooltip is shown, verify that the swatch is still correct");
   let swatch = value.querySelector(".ruleview-colorswatch");
   is(swatch.style.backgroundColor, "rgb(0, 0, 0)", "The swatch's color is correct");
   is(swatch.nextSibling.textContent, "#000", "The color name is correct");
 }
--- a/browser/devtools/styleinspector/test/browser_ruleview_colorpicker-appears-on-swatch-click.js
+++ b/browser/devtools/styleinspector/test/browser_ruleview_colorpicker-appears-on-swatch-click.js
@@ -32,17 +32,17 @@ let test = asyncTest(function*() {
 
   for (let swatch of [cSwatch, bgSwatch, bSwatch]) {
     info("Testing that the colorpicker appears colorswatch click");
     yield testColorPickerAppearsOnColorSwatchClick(view, swatch);
   }
 });
 
 function* testColorPickerAppearsOnColorSwatchClick(view, swatch) {
-  let cPicker = view.colorPicker;
+  let cPicker = view.tooltips.colorPicker;
   ok(cPicker, "The rule-view has the expected colorPicker property");
 
   let cPickerPanel = cPicker.tooltip.panel;
   ok(cPickerPanel, "The XUL panel for the color picker exists");
 
   let onShown = cPicker.tooltip.once("shown");
   swatch.click();
   yield onShown;
--- a/browser/devtools/styleinspector/test/browser_ruleview_colorpicker-commit-on-ENTER.js
+++ b/browser/devtools/styleinspector/test/browser_ruleview_colorpicker-commit-on-ENTER.js
@@ -21,17 +21,17 @@ let test = asyncTest(function*() {
   let {toolbox, inspector, view} = yield openRuleView();
 
   let swatch = getRuleViewProperty(view, "body" , "border").valueSpan
     .querySelector(".ruleview-colorswatch");
   yield testPressingEnterCommitsChanges(swatch, view);
 });
 
 function* testPressingEnterCommitsChanges(swatch, ruleView) {
-  let cPicker = ruleView.colorPicker;
+  let cPicker = ruleView.tooltips.colorPicker;
 
   let onShown = cPicker.tooltip.once("shown");
   swatch.click();
   yield onShown;
 
   yield simulateColorPickerChange(cPicker, [0, 255, 0, .5], {
     element: content.document.body,
     name: "borderLeftColor",
--- a/browser/devtools/styleinspector/test/browser_ruleview_colorpicker-edit-gradient.js
+++ b/browser/devtools/styleinspector/test/browser_ruleview_colorpicker-edit-gradient.js
@@ -52,17 +52,17 @@ function testColorParsing(view) {
 
 function* testPickingNewColor(view) {
   // Grab the first color swatch and color in the gradient
   let ruleEl = getRuleViewProperty(view, "body", "background-image");
   let swatchEl = ruleEl.valueSpan.querySelector(".ruleview-colorswatch");
   let colorEl = ruleEl.valueSpan.querySelector(".ruleview-color");
 
   info("Getting the color picker tooltip and clicking on the swatch to show it");
-  let cPicker = view.colorPicker;
+  let cPicker = view.tooltips.colorPicker;
   let onShown = cPicker.tooltip.once("shown");
   swatchEl.click();
   yield onShown;
 
   yield simulateColorPickerChange(cPicker, [1, 1, 1, 1]);
 
   is(swatchEl.style.backgroundColor, "rgb(1, 1, 1)",
     "The color swatch's background was updated");
--- a/browser/devtools/styleinspector/test/browser_ruleview_colorpicker-hides-on-tooltip.js
+++ b/browser/devtools/styleinspector/test/browser_ruleview_colorpicker-hides-on-tooltip.js
@@ -27,22 +27,22 @@ let test = asyncTest(function*() {
     .querySelector(".ruleview-colorswatch");
 
   yield testColorPickerHidesWhenImageTooltipAppears(view, swatch);
 });
 
 function* testColorPickerHidesWhenImageTooltipAppears(view, swatch) {
   let bgImageSpan = getRuleViewProperty(view, "body", "background-image").valueSpan;
   let uriSpan = bgImageSpan.querySelector(".theme-link");
-  let tooltip = view.colorPicker.tooltip;
+  let tooltip = view.tooltips.colorPicker.tooltip;
 
   info("Showing the color picker tooltip by clicking on the color swatch");
   let onShown = tooltip.once("shown");
   swatch.click();
   yield onShown;
 
   info("Now showing the image preview tooltip to hide the color picker");
   let onHidden = tooltip.once("hidden");
-  yield assertHoverTooltipOn(view.previewTooltip, uriSpan);
+  yield assertHoverTooltipOn(view.tooltips.previewTooltip, uriSpan);
   yield onHidden;
 
   ok(true, "The color picker closed when the image preview tooltip appeared");
 }
--- a/browser/devtools/styleinspector/test/browser_ruleview_colorpicker-multiple-changes.js
+++ b/browser/devtools/styleinspector/test/browser_ruleview_colorpicker-multiple-changes.js
@@ -37,17 +37,17 @@ let test = asyncTest(function*() {
 function* testSimpleMultipleColorChanges(inspector, ruleView) {
   yield selectNode("p", inspector);
 
   info("Getting the <p> tag's color property");
   let swatch = getRuleViewProperty(ruleView, "p", "color").valueSpan
     .querySelector(".ruleview-colorswatch");
 
   info("Opening the color picker");
-  let picker = ruleView.colorPicker;
+  let picker = ruleView.tooltips.colorPicker;
   let onShown = picker.tooltip.once("shown");
   swatch.click();
   yield onShown;
 
   info("Changing the color several times");
   let colors = [
     {rgba: [0, 0, 0, 1], computed: "rgb(0, 0, 0)"},
     {rgba: [100, 100, 100, 1], computed: "rgb(100, 100, 100)"},
@@ -65,17 +65,17 @@ function* testSimpleMultipleColorChanges
 function* testComplexMultipleColorChanges(inspector, ruleView) {
   yield selectNode("body", inspector);
 
   info("Getting the <body> tag's color property");
   let swatch = getRuleViewProperty(ruleView, "body", "background").valueSpan
     .querySelector(".ruleview-colorswatch");
 
   info("Opening the color picker");
-  let picker = ruleView.colorPicker;
+  let picker = ruleView.tooltips.colorPicker;
   let onShown = picker.tooltip.once("shown");
   swatch.click();
   yield onShown;
 
   info("Changing the color several times");
   let colors = [
     {rgba: [0, 0, 0, 1], computed: "rgb(0, 0, 0)"},
     {rgba: [100, 100, 100, 1], computed: "rgb(100, 100, 100)"},
@@ -98,17 +98,17 @@ function* testComplexMultipleColorChange
 function* testOverriddenMultipleColorChanges(inspector, ruleView) {
   yield selectNode("p", inspector);
 
   info("Getting the <body> tag's color property");
   let swatch = getRuleViewProperty(ruleView, "body", "color").valueSpan
     .querySelector(".ruleview-colorswatch");
 
   info("Opening the color picker");
-  let picker = ruleView.colorPicker;
+  let picker = ruleView.tooltips.colorPicker;
   let onShown = picker.tooltip.once("shown");
   swatch.click();
   yield onShown;
 
   info("Changing the color several times");
   let colors = [
     {rgba: [0, 0, 0, 1], computed: "rgb(0, 0, 0)"},
     {rgba: [100, 100, 100, 1], computed: "rgb(100, 100, 100)"},
--- a/browser/devtools/styleinspector/test/browser_ruleview_colorpicker-revert-on-ESC.js
+++ b/browser/devtools/styleinspector/test/browser_ruleview_colorpicker-revert-on-ESC.js
@@ -21,17 +21,17 @@ let test = asyncTest(function*() {
   let {toolbox, inspector, view} = yield openRuleView();
 
   let swatch = getRuleViewProperty(view, "body", "background-color").valueSpan
     .querySelector(".ruleview-colorswatch");
   yield testPressingEscapeRevertsChanges(swatch, view);
 });
 
 function* testPressingEscapeRevertsChanges(swatch, ruleView) {
-  let cPicker = ruleView.colorPicker;
+  let cPicker = ruleView.tooltips.colorPicker;
 
   let onShown = cPicker.tooltip.once("shown");
   swatch.click();
   yield onShown;
 
   yield simulateColorPickerChange(cPicker, [0, 0, 0, 1], {
     element: content.document.body,
     name: "backgroundColor",
--- a/browser/devtools/styleinspector/test/browser_ruleview_eyedropper.js
+++ b/browser/devtools/styleinspector/test/browser_ruleview_eyedropper.js
@@ -35,17 +35,17 @@ let test = asyncTest(function*() {
   yield inspector.once("inspector-updated");
 
   let property = getRuleViewProperty(view, "div", "background-color");
   let swatch = property.valueSpan.querySelector(".ruleview-colorswatch");
   ok(swatch, "Color swatch is displayed for the bg-color property");
 
   let dropper = yield openEyedropper(view, swatch);
 
-  let tooltip = view.colorPicker.tooltip;
+  let tooltip = view.tooltips.colorPicker.tooltip;
   ok(tooltip.isHidden(),
      "color picker tooltip is closed after opening eyedropper");
 
   yield testESC(swatch, dropper);
 
   dropper = yield openEyedropper(view, swatch);
 
   ok(dropper, "dropper opened");
@@ -93,17 +93,17 @@ function testSelect(swatch, dropper) {
 }
 
 
 /* Helpers */
 
 function openEyedropper(view, swatch) {
   let deferred = promise.defer();
 
-  let tooltip = view.colorPicker.tooltip;
+  let tooltip = view.tooltips.colorPicker.tooltip;
 
   tooltip.once("shown", () => {
     let tooltipDoc = tooltip.content.contentDocument;
     let dropperButton = tooltipDoc.querySelector("#eyedropper-button");
 
     tooltip.once("eyedropper-opened", (event, dropper) => {
       deferred.resolve(dropper)
     });
--- a/browser/devtools/styleinspector/test/browser_ruleview_user-agent-styles-uneditable.js
+++ b/browser/devtools/styleinspector/test/browser_ruleview_user-agent-styles-uneditable.js
@@ -39,12 +39,12 @@ function* userAgentStylesUneditable(insp
     ok (rule.editor.element.hasAttribute("uneditable"), "UA rules have uneditable attribute");
 
     ok (!rule.textProps[0].editor.nameSpan._editable, "nameSpan is not editable");
     ok (!rule.textProps[0].editor.valueSpan._editable, "valueSpan is not editable");
     ok (!rule.editor.closeBrace._editable, "closeBrace is not editable");
 
     let colorswatch = rule.editor.element.querySelector(".ruleview-colorswatch");
     if (colorswatch) {
-      ok (!view.colorPicker.swatches.has(colorswatch), "The swatch is not editable");
+      ok (!view.tooltips.colorPicker.swatches.has(colorswatch), "The swatch is not editable");
     }
   }
 }
--- a/browser/devtools/styleinspector/test/browser_styleinspector_context-menu-copy-color_02.js
+++ b/browser/devtools/styleinspector/test/browser_styleinspector_context-menu-copy-color_02.js
@@ -77,17 +77,17 @@ function* testManualEdit(inspector, view
 function* testColorPickerEdit(inspector, view) {
   info("Testing colors edited via color picker");
   yield selectNode("div", inspector);
 
   let swatch = getRuleViewProperty(view, "div", "color").valueSpan
     .querySelector(".ruleview-colorswatch");
 
   info("Opening the color picker");
-  let picker = view.colorPicker;
+  let picker = view.tooltips.colorPicker;
   let onShown = picker.tooltip.once("shown");
   swatch.click();
   yield onShown;
 
   let rgbaColor = [83, 183, 89, 1];
   let rgbaColorText = "rgba(83, 183, 89, 1)";
   yield simulateColorPickerChange(picker, rgbaColor);
 
--- a/browser/devtools/styleinspector/test/browser_styleinspector_tooltip-background-image.js
+++ b/browser/devtools/styleinspector/test/browser_styleinspector_tooltip-background-image.js
@@ -45,77 +45,77 @@ let test = asyncTest(function*() {
   let {view} = yield openComputedView();
 
   info("Testing that the background-image computed style has a tooltip too");
   yield testComputedView(view);
 });
 
 function* testBodyRuleView(view) {
   info("Testing tooltips in the rule view");
-  let panel = view.previewTooltip.panel;
+  let panel = view.tooltips.previewTooltip.panel;
 
   // Check that the rule view has a tooltip and that a XUL panel has been created
-  ok(view.previewTooltip, "Tooltip instance exists");
+  ok(view.tooltips.previewTooltip, "Tooltip instance exists");
   ok(panel, "XUL panel exists");
 
   // Get the background-image property inside the rule view
   let {valueSpan} = getRuleViewProperty(view, "body", "background-image");
   let uriSpan = valueSpan.querySelector(".theme-link");
 
-  yield assertHoverTooltipOn(view.previewTooltip, uriSpan);
+  yield assertHoverTooltipOn(view.tooltips.previewTooltip, uriSpan);
 
   let images = panel.getElementsByTagName("image");
   is(images.length, 1, "Tooltip contains an image");
   ok(images[0].getAttribute("src").indexOf("iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHe") !== -1,
     "The image URL seems fine");
 }
 
 function* testDivRuleView(view) {
-  let panel = view.previewTooltip.panel;
+  let panel = view.tooltips.previewTooltip.panel;
 
   // Get the background property inside the rule view
   let {valueSpan} = getRuleViewProperty(view, ".test-element", "background");
   let uriSpan = valueSpan.querySelector(".theme-link");
 
-  yield assertHoverTooltipOn(view.previewTooltip, uriSpan);
+  yield assertHoverTooltipOn(view.tooltips.previewTooltip, uriSpan);
 
   let images = panel.getElementsByTagName("image");
   is(images.length, 1, "Tooltip contains an image");
   ok(images[0].getAttribute("src").startsWith("data:"), "Tooltip contains a data-uri image as expected");
 }
 
 function* testTooltipAppearsEvenInEditMode(view) {
-  let panel = view.previewTooltip.panel;
+  let panel = view.tooltips.previewTooltip.panel;
 
   info("Switching to edit mode in the rule view");
   let editor = yield turnToEditMode(view);
 
   info("Now trying to show the preview tooltip");
   let {valueSpan} = getRuleViewProperty(view, ".test-element", "background");
   let uriSpan = valueSpan.querySelector(".theme-link");
-  yield assertHoverTooltipOn(view.previewTooltip, uriSpan);
+  yield assertHoverTooltipOn(view.tooltips.previewTooltip, uriSpan);
 
   is(view.doc.activeElement, editor.input,
     "Tooltip was shown in edit mode, and inplace-editor still focused");
 }
 
 function turnToEditMode(ruleView) {
   let brace = ruleView.doc.querySelector(".ruleview-ruleclose");
   return focusEditableField(brace);
 }
 
 function* testComputedView(view) {
-  let tooltip = view.tooltip;
+  let tooltip = view.tooltips.previewTooltip;
   ok(tooltip, "The computed-view has a tooltip defined");
 
   let panel = tooltip.panel;
   ok(panel, "The computed-view tooltip has a XUL panel");
 
   let {valueSpan} = getComputedViewProperty(view, "background-image");
   let uriSpan = valueSpan.querySelector(".theme-link");
 
-  yield assertHoverTooltipOn(view.tooltip, uriSpan);
+  yield assertHoverTooltipOn(view.tooltips.previewTooltip, uriSpan);
 
   let images = panel.getElementsByTagName("image");
   is(images.length, 1, "Tooltip contains an image");
 
   ok(images[0].getAttribute("src").startsWith("data:"), "Tooltip contains a data-uri in the computed-view too");
 }
--- a/browser/devtools/styleinspector/test/browser_styleinspector_tooltip-closes-on-new-selection.js
+++ b/browser/devtools/styleinspector/test/browser_styleinspector_tooltip-closes-on-new-selection.js
@@ -17,31 +17,31 @@ let test = asyncTest(function*() {
 
   info("Testing computed view tooltip closes on new selection");
   let {view} = yield openComputedView();
   yield testComputedView(view, inspector);
 });
 
 function* testRuleView(ruleView, inspector) {
   info("Showing the tooltip");
-  let tooltip = ruleView.previewTooltip;
+  let tooltip = ruleView.tooltips.previewTooltip;
   let onShown = tooltip.once("shown");
   tooltip.show();
   yield onShown;
 
   info("Selecting a new node");
   let onHidden = tooltip.once("hidden");
   yield selectNode(".two", inspector);
 
   ok(true, "Rule view tooltip closed after a new node got selected");
 }
 
 function* testComputedView(computedView, inspector) {
   info("Showing the tooltip");
-  let tooltip = computedView.tooltip;
+  let tooltip = computedView.tooltips.previewTooltip;
   let onShown = tooltip.once("shown");
   tooltip.show();
   yield onShown;
 
   info("Selecting a new node");
   let onHidden = tooltip.once("hidden");
   yield selectNode(".one", inspector);
 
--- a/browser/devtools/styleinspector/test/browser_styleinspector_tooltip-longhand-fontfamily.js
+++ b/browser/devtools/styleinspector/test/browser_styleinspector_tooltip-longhand-fontfamily.js
@@ -35,43 +35,45 @@ let test = asyncTest(function*() {
   let {toolbox, inspector, view} = yield openComputedView();
 
   yield testComputedView(view, inspector.selection.nodeFront);
 });
 
 function* testRuleView(ruleView, nodeFront) {
   info("Testing font-family tooltips in the rule view");
 
-  let panel = ruleView.previewTooltip.panel;
+  let tooltip = ruleView.tooltips.previewTooltip;
+  let panel = tooltip.panel;
 
   // Check that the rule view has a tooltip and that a XUL panel has been created
-  ok(ruleView.previewTooltip, "Tooltip instance exists");
+  ok(tooltip, "Tooltip instance exists");
   ok(panel, "XUL panel exists");
 
   // Get the font family property inside the rule view
   let {valueSpan} = getRuleViewProperty(ruleView, "#testElement", "font-family");
 
   // And verify that the tooltip gets shown on this property
-  yield assertHoverTooltipOn(ruleView.previewTooltip, valueSpan);
+  yield assertHoverTooltipOn(tooltip, valueSpan);
 
   let images = panel.getElementsByTagName("image");
   is(images.length, 1, "Tooltip contains an image");
   ok(images[0].getAttribute("src").startsWith("data:"), "Tooltip contains a data-uri image as expected");
 
   let dataURL = yield getFontFamilyDataURL(valueSpan.textContent, nodeFront);
   is(images[0].getAttribute("src"), dataURL, "Tooltip contains the correct data-uri image");
 }
 
 function* testComputedView(computedView, nodeFront) {
   info("Testing font-family tooltips in the computed view");
 
-  let panel = computedView.tooltip.panel;
+  let tooltip = computedView.tooltips.previewTooltip;
+  let panel = tooltip.panel;
   let {valueSpan} = getComputedViewProperty(computedView, "font-family");
 
-  yield assertHoverTooltipOn(computedView.tooltip, valueSpan);
+  yield assertHoverTooltipOn(tooltip, valueSpan);
 
   let images = panel.getElementsByTagName("image");
   is(images.length, 1, "Tooltip contains an image");
   ok(images[0].getAttribute("src").startsWith("data:"), "Tooltip contains a data-uri image as expected");
 
   let dataURL = yield getFontFamilyDataURL(valueSpan.textContent, nodeFront);
   is(images[0].getAttribute("src"), dataURL, "Tooltip contains the correct data-uri image");
 }
--- a/browser/devtools/styleinspector/test/browser_styleinspector_tooltip-shorthand-fontfamily.js
+++ b/browser/devtools/styleinspector/test/browser_styleinspector_tooltip-shorthand-fontfamily.js
@@ -33,48 +33,51 @@ let test = asyncTest(function*() {
   let {toolbox, inspector, view} = yield openComputedView();
 
   yield testComputedView(view, inspector.selection.nodeFront);
 });
 
 function* testRuleView(ruleView, nodeFront) {
   info("Testing font-family tooltips in the rule view");
 
-  let panel = ruleView.previewTooltip.panel;
+  let tooltip = ruleView.tooltips.previewTooltip;
+  let panel = tooltip.panel;
 
   // Check that the rule view has a tooltip and that a XUL panel has been created
-  ok(ruleView.previewTooltip, "Tooltip instance exists");
+  ok(tooltip, "Tooltip instance exists");
   ok(panel, "XUL panel exists");
 
   // Get the computed font family property inside the font rule view
   let propertyList = ruleView.element.querySelectorAll(".ruleview-propertylist");
   let fontExpander = propertyList[1].querySelectorAll(".ruleview-expander")[0];
   fontExpander.click();
 
   let rule = getRuleViewRule(ruleView, "#testElement");
   let valueSpan = rule.querySelector(".ruleview-computed .ruleview-propertyvalue");
 
   // And verify that the tooltip gets shown on this property
-  yield assertHoverTooltipOn(ruleView.previewTooltip, valueSpan);
+  yield assertHoverTooltipOn(tooltip, valueSpan);
 
   let images = panel.getElementsByTagName("image");
   is(images.length, 1, "Tooltip contains an image");
   ok(images[0].getAttribute("src").startsWith("data:"), "Tooltip contains a data-uri image as expected");
 
   let dataURL = yield getFontFamilyDataURL(valueSpan.textContent, nodeFront);
   is(images[0].getAttribute("src"), dataURL, "Tooltip contains the correct data-uri image");
 }
 
 function* testComputedView(computedView, nodeFront) {
   info("Testing font-family tooltips in the computed view");
 
-  let panel = computedView.tooltip.panel;
+  let tooltip = computedView.tooltips.previewTooltip;
+  let panel = tooltip.panel;
+
   let {valueSpan} = getComputedViewProperty(computedView, "font-family");
 
-  yield assertHoverTooltipOn(computedView.tooltip, valueSpan);
+  yield assertHoverTooltipOn(tooltip, valueSpan);
 
   let images = panel.getElementsByTagName("image");
   is(images.length, 1, "Tooltip contains an image");
   ok(images[0].getAttribute("src").startsWith("data:"), "Tooltip contains a data-uri image as expected");
 
   let dataURL = yield getFontFamilyDataURL(valueSpan.textContent, nodeFront);
   is(images[0].getAttribute("src"), dataURL, "Tooltip contains the correct data-uri image");
 }
--- a/browser/devtools/styleinspector/test/browser_styleinspector_tooltip-size.js
+++ b/browser/devtools/styleinspector/test/browser_styleinspector_tooltip-size.js
@@ -26,17 +26,17 @@ let test = asyncTest(function*() {
 
   yield testImageDimension(view);
   yield testPickerDimension(view);
 });
 
 function* testImageDimension(ruleView) {
   info("Testing background-image tooltip dimensions");
 
-  let tooltip = ruleView.previewTooltip;
+  let tooltip = ruleView.tooltips.previewTooltip;
   let panel = tooltip.panel;
   let {valueSpan} = getRuleViewProperty(ruleView, "div", "background");
   let uriSpan = valueSpan.querySelector(".theme-link");
 
   // Make sure there is a hover tooltip for this property, this also will fill
   // the tooltip with its content
   yield assertHoverTooltipOn(tooltip, uriSpan);
 
@@ -60,17 +60,17 @@ function* testImageDimension(ruleView) {
   yield onHidden;
 }
 
 function* testPickerDimension(ruleView) {
   info("Testing color-picker tooltip dimensions");
 
   let {valueSpan} = getRuleViewProperty(ruleView, "div", "background");
   let swatch = valueSpan.querySelector(".ruleview-colorswatch");
-  let cPicker = ruleView.colorPicker;
+  let cPicker = ruleView.tooltips.colorPicker;
 
   let onShown = cPicker.tooltip.once("shown");
   swatch.click();
   yield onShown;
 
   // The colorpicker spectrum's iframe has a fixed width height, so let's
   // make sure the tooltip is at least as big as that
   let w = cPicker.tooltip.panel.querySelector("iframe").width;
--- a/browser/devtools/styleinspector/test/browser_styleinspector_transform-highlighter-01.js
+++ b/browser/devtools/styleinspector/test/browser_styleinspector_transform-highlighter-01.js
@@ -10,29 +10,33 @@ const PAGE_CONTENT = [
   '<style type="text/css">',
   '  body {',
   '    transform: skew(16deg);',
   '  }',
   '</style>',
   'Test the css transform highlighter'
 ].join("\n");
 
+const TYPE = "CssTransformHighlighter";
+
 let test = asyncTest(function*() {
   yield addTab("data:text/html," + PAGE_CONTENT);
 
   let {view: rView} = yield openRuleView();
+  let overlay = rView.highlighters;
 
-  ok(!rView.transformHighlighter, "No highlighter exists in the rule-view");
-  let h = yield rView.getTransformHighlighter();
-  ok(rView.transformHighlighter, "The highlighter has been created in the rule-view");
-  is(h, rView.transformHighlighter, "The right highlighter has been created");
-  let h2 = yield rView.getTransformHighlighter();
+  ok(!overlay.highlighters[TYPE], "No highlighter exists in the rule-view");
+  let h = yield overlay._getHighlighter(TYPE);
+  ok(overlay.highlighters[TYPE], "The highlighter has been created in the rule-view");
+  is(h, overlay.highlighters[TYPE], "The right highlighter has been created");
+  let h2 = yield overlay._getHighlighter(TYPE);
   is(h, h2, "The same instance of highlighter is returned everytime in the rule-view");
 
   let {view: cView} = yield openComputedView();
+  let overlay = cView.highlighters;
 
-  ok(!cView.transformHighlighter, "No highlighter exists in the computed-view");
-  let h = yield cView.getTransformHighlighter();
-  ok(cView.transformHighlighter, "The highlighter has been created in the computed-view");
-  is(h, cView.transformHighlighter, "The right highlighter has been created");
-  let h2 = yield cView.getTransformHighlighter();
+  ok(!overlay.highlighters[TYPE], "No highlighter exists in the computed-view");
+  let h = yield overlay._getHighlighter(TYPE);
+  ok(overlay.highlighters[TYPE], "The highlighter has been created in the computed-view");
+  is(h, overlay.highlighters[TYPE], "The right highlighter has been created");
+  let h2 = yield overlay._getHighlighter(TYPE);
   is(h, h2, "The same instance of highlighter is returned everytime in the computed-view");
 });
--- a/browser/devtools/styleinspector/test/browser_styleinspector_transform-highlighter-02.js
+++ b/browser/devtools/styleinspector/test/browser_styleinspector_transform-highlighter-02.js
@@ -12,43 +12,52 @@ const PAGE_CONTENT = [
   '  body {',
   '    transform: skew(16deg);',
   '    color: yellow;',
   '  }',
   '</style>',
   'Test the css transform highlighter'
 ].join("\n");
 
+let TYPE = "CssTransformHighlighter";
+
 let test = asyncTest(function*() {
   yield addTab("data:text/html," + PAGE_CONTENT);
 
+
   let {view: rView} = yield openRuleView();
-  ok(!rView.transformHighlighter, "No highlighter exists in the rule-view (1)");
+  let hs = rView.highlighters;
+
+  ok(!hs.highlighters[TYPE], "No highlighter exists in the rule-view (1)");
+  ok(!hs.promises[TYPE], "No highlighter is being created in the rule-view (1)");
 
   info("Faking a mousemove on a non-transform property");
   let {valueSpan} = getRuleViewProperty(rView, "body", "color");
-  rView._onMouseMove({target: valueSpan});
-  ok(!rView.transformHighlighter, "No highlighter exists in the rule-view (2)");
-  ok(!rView.transformHighlighterPromise, "No highlighter is being initialized");
+  hs._onMouseMove({target: valueSpan});
+  ok(!hs.highlighters[TYPE], "No highlighter exists in the rule-view (2)");
+  ok(!hs.promises[TYPE], "No highlighter is being created in the rule-view (2)");
 
   info("Faking a mousemove on a transform property");
   let {valueSpan} = getRuleViewProperty(rView, "body", "transform");
-  rView._onMouseMove({target: valueSpan});
-  ok(rView.transformHighlighterPromise, "The highlighter is being initialized");
-  let h = yield rView.transformHighlighterPromise;
-  is(h, rView.transformHighlighter, "The initialized highlighter is the right one");
+  hs._onMouseMove({target: valueSpan});
+  ok(hs.promises[TYPE], "The highlighter is being initialized");
+  let h = yield hs.promises[TYPE];
+  is(h, hs.highlighters[TYPE], "The initialized highlighter is the right one");
 
   let {view: cView} = yield openComputedView();
-  ok(!cView.transformHighlighter, "No highlighter exists in the computed-view (1)");
+  let hs = cView.highlighters;
+
+  ok(!hs.highlighters[TYPE], "No highlighter exists in the computed-view (1)");
+  ok(!hs.promises[TYPE], "No highlighter is being created in the computed-view (1)");
 
   info("Faking a mousemove on a non-transform property");
   let {valueSpan} = getComputedViewProperty(cView, "color");
-  cView._onMouseMove({target: valueSpan});
-  ok(!cView.transformHighlighter, "No highlighter exists in the computed-view (2)");
-  ok(!cView.transformHighlighterPromise, "No highlighter is being initialized");
+  hs._onMouseMove({target: valueSpan});
+  ok(!hs.highlighters[TYPE], "No highlighter exists in the computed-view (2)");
+  ok(!hs.promises[TYPE], "No highlighter is being created in the computed-view (2)");
 
   info("Faking a mousemove on a transform property");
   let {valueSpan} = getComputedViewProperty(cView, "transform");
-  cView._onMouseMove({target: valueSpan});
-  ok(cView.transformHighlighterPromise, "The highlighter is being initialized");
-  let h = yield cView.transformHighlighterPromise;
-  is(h, cView.transformHighlighter, "The initialized highlighter is the right one");
+  hs._onMouseMove({target: valueSpan});
+  ok(hs.promises[TYPE], "The highlighter is being initialized");
+  let h = yield hs.promises[TYPE];
+  is(h, hs.highlighters[TYPE], "The initialized highlighter is the right one");
 });
--- a/browser/devtools/styleinspector/test/browser_styleinspector_transform-highlighter-03.js
+++ b/browser/devtools/styleinspector/test/browser_styleinspector_transform-highlighter-03.js
@@ -18,16 +18,18 @@ const PAGE_CONTENT = [
   '  body {',
   '    transform: skew(16deg);',
   '    color: purple;',
   '  }',
   '</style>',
   'Test the css transform highlighter'
 ].join("\n");
 
+const TYPE = "CssTransformHighlighter";
+
 let test = asyncTest(function*() {
   yield addTab("data:text/html," + PAGE_CONTENT);
 
   let {inspector, view: rView} = yield openRuleView();
 
   // Mock the highlighter front to get the reference of the NodeFront
   let HighlighterFront = {
     isShown: false,
@@ -40,50 +42,50 @@ let test = asyncTest(function*() {
     },
     hide: function() {
       this.nodeFront = null;
       this.isShown = false;
     }
   };
 
   // Inject the mock highlighter in the rule-view
-  rView.transformHighlighterPromise = {
+  rView.highlighters.promises[TYPE] = {
     then: function(cb) {
       cb(HighlighterFront);
     }
   };
 
   let {valueSpan} = getRuleViewProperty(rView, "body", "transform");
 
   info("Checking that the HighlighterFront's show/hide methods are called");
-  rView._onMouseMove({target: valueSpan});
+  rView.highlighters._onMouseMove({target: valueSpan});
   ok(HighlighterFront.isShown, "The highlighter is shown");
-  rView._onMouseLeave();
+  rView.highlighters._onMouseLeave();
   ok(!HighlighterFront.isShown, "The highlighter is hidden");
 
   info("Checking that hovering several times over the same property doesn't" +
     " show the highlighter several times");
   let nb = HighlighterFront.nbOfTimesShown;
-  rView._onMouseMove({target: valueSpan});
+  rView.highlighters._onMouseMove({target: valueSpan});
   is(HighlighterFront.nbOfTimesShown, nb + 1, "The highlighter was shown once");
-  rView._onMouseMove({target: valueSpan});
-  rView._onMouseMove({target: valueSpan});
+  rView.highlighters._onMouseMove({target: valueSpan});
+  rView.highlighters._onMouseMove({target: valueSpan});
   is(HighlighterFront.nbOfTimesShown, nb + 1,
     "The highlighter was shown once, after several mousemove");
 
   info("Checking that the right NodeFront reference is passed");
   yield selectNode(content.document.documentElement, inspector);
   let {valueSpan} = getRuleViewProperty(rView, "html", "transform");
-  rView._onMouseMove({target: valueSpan});
+  rView.highlighters._onMouseMove({target: valueSpan});
   is(HighlighterFront.nodeFront.tagName, "HTML",
     "The right NodeFront is passed to the highlighter (1)");
 
   yield selectNode("body", inspector);
   let {valueSpan} = getRuleViewProperty(rView, "body", "transform");
-  rView._onMouseMove({target: valueSpan});
+  rView.highlighters._onMouseMove({target: valueSpan});
   is(HighlighterFront.nodeFront.tagName, "BODY",
     "The right NodeFront is passed to the highlighter (2)");
 
   info("Checking that the highlighter gets hidden when hovering a non-transform property");
   let {valueSpan} = getRuleViewProperty(rView, "body", "color");
-  rView._onMouseMove({target: valueSpan});
+  rView.highlighters._onMouseMove({target: valueSpan});
   ok(!HighlighterFront.isShown, "The highlighter is hidden");
 });
--- a/browser/devtools/styleinspector/test/browser_styleinspector_transform-highlighter-04.js
+++ b/browser/devtools/styleinspector/test/browser_styleinspector_transform-highlighter-04.js
@@ -20,39 +20,43 @@ const PAGE_CONTENT = [
   '  }',
   '  .test {',
   '    transform: skew(25deg);',
   '  }',
   '</style>',
   '<div class="test"></div>'
 ].join("\n");
 
+const TYPE = "CssTransformHighlighter";
+
 let test = asyncTest(function*() {
   yield addTab("data:text/html," + PAGE_CONTENT);
 
   let {view: rView, inspector} = yield openRuleView();
   yield selectNode(".test", inspector);
 
+  let hs = rView.highlighters;
+
   info("Faking a mousemove on the overriden property");
   let {valueSpan} = getRuleViewProperty(rView, "div", "transform");
-  rView._onMouseMove({target: valueSpan});
-  ok(!rView.transformHighlighter, "No highlighter was created for the overriden property");
-  ok(!rView.transformHighlighterPromise, "And no highlighter is being initialized either");
+  hs._onMouseMove({target: valueSpan});
+  ok(!hs.highlighters[TYPE], "No highlighter was created for the overriden property");
+  ok(!hs.promises[TYPE], "And no highlighter is being initialized either");
 
   info("Disabling the applied property");
   let classRuleEditor = rView.element.children[1]._ruleEditor;
   let propEditor = classRuleEditor.rule.textProps[0].editor;
   propEditor.enable.click();
   yield classRuleEditor.rule._applyingModifications;
 
   info("Faking a mousemove on the disabled property");
   let {valueSpan} = getRuleViewProperty(rView, ".test", "transform");
-  rView._onMouseMove({target: valueSpan});
-  ok(!rView.transformHighlighter, "No highlighter was created for the disabled property");
-  ok(!rView.transformHighlighterPromise, "And no highlighter is being initialized either");
+  hs._onMouseMove({target: valueSpan});
+  ok(!hs.highlighters[TYPE], "No highlighter was created for the disabled property");
+  ok(!hs.promises[TYPE], "And no highlighter is being initialized either");
 
   info("Faking a mousemove on the now unoverriden property");
   let {valueSpan} = getRuleViewProperty(rView, "div", "transform");
-  rView._onMouseMove({target: valueSpan});
-  ok(rView.transformHighlighterPromise, "The highlighter is being initialized now");
-  let h = yield rView.transformHighlighterPromise;
-  is(h, rView.transformHighlighter, "The initialized highlighter is the right one");
+  hs._onMouseMove({target: valueSpan});
+  ok(hs.promises[TYPE], "The highlighter is being initialized now");
+  let h = yield hs.promises[TYPE];
+  is(h, hs.highlighters[TYPE], "The initialized highlighter is the right one");
 });
--- a/browser/themes/shared/devtools/styleeditor.css
+++ b/browser/themes/shared/devtools/styleeditor.css
@@ -60,17 +60,17 @@
   font-style: italic;
 }
 
 .splitview-nav.empty > p {
   padding: 0 10px;
 }
 
 .stylesheet-sidebar {
-  width: 230px;
+  width: 237px;
   -moz-border-start: 1px solid;
 }
 
 .theme-light .stylesheet-sidebar {
   border-color: #aaa; /* Splitters */
 }
 
 .theme-dark .stylesheet-sidebar {
@@ -86,16 +86,20 @@
 }
 
 .media-rule-label {
   padding: 4px;
   cursor: pointer;
   border-bottom: 1px solid;
 }
 
+.media-rule-line {
+  -moz-padding-start: 4px;
+}
+
 .theme-light .media-condition-unmatched {
   color: grey;
 }
 
 .theme-dark .media-condition-unmatched {
   color: #606C75;
 }
 
--- a/mobile/android/base/BrowserApp.java
+++ b/mobile/android/base/BrowserApp.java
@@ -1986,39 +1986,39 @@ abstract public class BrowserApp extends
     }
 
     /**
      * Hides certain UI elements (e.g. button toast, tabs tray) when the
      * user touches the main layout.
      */
     private class HideOnTouchListener implements TouchEventInterceptor {
         private boolean mIsHidingTabs = false;
+        private final Rect mTempRect = new Rect();
 
         @Override
         public boolean onInterceptTouchEvent(View view, MotionEvent event) {
             // Only try to hide the button toast if it's already inflated.
             if (mToast != null) {
                 mToast.hide(false, ButtonToast.ReasonHidden.TOUCH_OUTSIDE);
             }
 
             // We need to account for scroll state for the touched view otherwise
             // tapping on an "empty" part of the view will still be considered a
             // valid touch event.
             if (view.getScrollX() != 0 || view.getScrollY() != 0) {
-                Rect rect = new Rect();
-                view.getHitRect(rect);
-                rect.offset(-view.getScrollX(), -view.getScrollY());
+                view.getHitRect(mTempRect);
+                mTempRect.offset(-view.getScrollX(), -view.getScrollY());
 
                 int[] viewCoords = new int[2];
                 view.getLocationOnScreen(viewCoords);
 
                 int x = (int) event.getRawX() - viewCoords[0];
                 int y = (int) event.getRawY() - viewCoords[1];
 
-                if (!rect.contains(x, y))
+                if (!mTempRect.contains(x, y))
                     return false;
             }
 
             // If the tab tray is showing, hide the tab tray and don't send the event to content.
             if (event.getActionMasked() == MotionEvent.ACTION_DOWN && autoHideTabs()) {
                 mIsHidingTabs = true;
                 return true;
             }
--- a/toolkit/components/crashmonitor/CrashMonitor.jsm
+++ b/toolkit/components/crashmonitor/CrashMonitor.jsm
@@ -160,19 +160,20 @@ this.CrashMonitor = {
     // called after receiving it
     CrashMonitorInternal.checkpoints["profile-after-change"] = true;
 
     NOTIFICATIONS.forEach(function (aTopic) {
       Services.obs.addObserver(this, aTopic, false);
     }, this);
 
     // Add shutdown blocker for profile-before-change
-    AsyncShutdown.profileBeforeChange.addBlocker(
+    OS.File.profileBeforeChange.addBlocker(
       "CrashMonitor: Writing notifications to file after receiving profile-before-change",
-      CrashMonitorInternal.profileBeforeChangeDeferred.promise
+      CrashMonitorInternal.profileBeforeChangeDeferred.promise,
+      () => this.checkpoints
     );
 
     CrashMonitorInternal.initialized = true;
     if (Services.metro && Services.metro.immersive) {
       OS.File.makeDir(OS.Path.join(OS.Constants.Path.profileDir, "metro"));
     }
     return promise;
   },
--- a/toolkit/components/osfile/modules/osfile_async_front.jsm
+++ b/toolkit/components/osfile/modules/osfile_async_front.jsm
@@ -449,16 +449,17 @@ let Scheduler = {
       Scheduler.restartTimer();
 
 
       let data;
       let reply;
       let isError = false;
       try {
         try {
+          Scheduler.Debugging.messagesSent++;
           data = yield this.worker.post(method, ...args);
         } finally {
           Scheduler.Debugging.messagesReceived++;
         }
         reply = data;
       } catch (error) {
         reply = error;
         isError = true;
@@ -603,17 +604,28 @@ if (SharedAll.Config.DEBUG && Scheduler.
 const WEB_WORKERS_SHUTDOWN_TOPIC = "web-workers-shutdown";
 
 // Preference used to configure test shutdown observer.
 const PREF_OSFILE_TEST_SHUTDOWN_OBSERVER =
   "toolkit.osfile.test.shutdown.observer";
 
 AsyncShutdown.webWorkersShutdown.addBlocker(
   "OS.File: flush pending requests, warn about unclosed files, shut down service.",
-  () => Scheduler.kill({reset: false, shutdown: true})
+  Task.async(function*() {
+    // Give clients a last chance to enqueue requests.
+    yield Barriers.shutdown.wait({crashAfterMS: null});
+
+    // Wait until all requests are complete and kill the worker.
+    yield Scheduler.kill({reset: false, shutdown: true});
+  }),
+  () => {
+    let details = Barriers.getDetails();
+    details.clients = Barriers.shutdown.state;
+    return details;
+  }
 );
 
 
 // Attaching an observer for PREF_OSFILE_TEST_SHUTDOWN_OBSERVER to enable or
 // disable the test shutdown event observer.
 // Note: By default the PREF_OSFILE_TEST_SHUTDOWN_OBSERVER is unset.
 // Note: This is meant to be used for testing purposes only.
 Services.prefs.addObserver(PREF_OSFILE_TEST_SHUTDOWN_OBSERVER,
@@ -1501,51 +1513,62 @@ this.OS.Path = Path;
 
 // Returns a resolved promise when all the queued operation have been completed.
 Object.defineProperty(OS.File, "queue", {
   get: function() {
     return Scheduler.queue;
   }
 });
 
-// Auto-flush OS.File during profile-before-change. This ensures that any I/O
-// that has been queued *before* profile-before-change is properly completed.
-// To ensure that I/O queued *during* profile-before-change is completed,
-// clients should register using AsyncShutdown.addBlocker.
-AsyncShutdown.profileBeforeChange.addBlocker(
-  "OS.File: flush I/O queued before profile-before-change",
-  // Wait until the latest currently enqueued promise is satisfied/rejected
-  function() {
-    let DEBUG = false;
-    try {
-      DEBUG = Services.prefs.getBoolPref("toolkit.osfile.debug.failshutdown");
-    } catch (ex) {
-      // Ignore
-    }
-    if (DEBUG) {
-      // Return a promise that will never be satisfied
-      return Promise.defer().promise;
-    } else {
-      return Scheduler.queue;
-    }
-  },
-  function getDetails() {
+/**
+ * Shutdown barriers, to let clients register to be informed during shutdown.
+ */
+let Barriers = {
+  profileBeforeChange: new AsyncShutdown.Barrier("OS.File: Waiting for clients before profile-before-shutdown"),
+  shutdown: new AsyncShutdown.Barrier("OS.File: Waiting for clients before full shutdown"),
+  /**
+   * Return the shutdown state of OS.File
+   */
+  getDetails: function() {
     let result = {
       launched: Scheduler.launched,
       shutdown: Scheduler.shutdown,
       worker: !!Scheduler._worker,
       pendingReset: !!Scheduler.resetTimer,
       latestSent: Scheduler.Debugging.latestSent,
       latestReceived: Scheduler.Debugging.latestReceived,
       messagesSent: Scheduler.Debugging.messagesSent,
       messagesReceived: Scheduler.Debugging.messagesReceived,
       messagesQueued: Scheduler.Debugging.messagesQueued,
-      DEBUG: SharedAll.Config.DEBUG
+      DEBUG: SharedAll.Config.DEBUG,
     };
     // Convert dates to strings for better readability
     for (let key of ["latestSent", "latestReceived"]) {
       if (result[key] && typeof result[key][0] == "number") {
         result[key][0] = Date(result[key][0]);
       }
     }
     return result;
   }
+};
+
+File.profileBeforeChange = Barriers.profileBeforeChange.client;
+File.shutdown = Barriers.shutdown.client;
+
+// Auto-flush OS.File during profile-before-change. This ensures that any I/O
+// that has been queued *before* profile-before-change is properly completed.
+// To ensure that I/O queued *during* profile-before-change is completed,
+// clients should register using AsyncShutdown.addBlocker.
+AsyncShutdown.profileBeforeChange.addBlocker(
+  "OS.File: flush I/O queued before profile-before-change",
+  Task.async(function*() {
+    // Give clients a last chance to enqueue requests.
+    yield Barriers.profileBeforeChange.wait({crashAfterMS: null});
+
+    // Wait until all currently enqueued requests are completed.
+    yield Scheduler.queue;
+  }),
+  () => {
+    let details = Barriers.getDetails();
+    details.clients = Barriers.profileBeforeChange.state;
+    return details;
+  }
 );
--- a/toolkit/crashreporter/test/unit/test_crash_AsyncShutdown.js
+++ b/toolkit/crashreporter/test/unit/test_crash_AsyncShutdown.js
@@ -35,28 +35,30 @@ function after_crash(mdump, extra) {
 }
 
 // Test that AsyncShutdown + OS.File reports errors correctly, in a case in which
 // the latest operation succeeded
 
 function setup_osfile_crash_noerror() {
   Components.utils.import("resource://gre/modules/Services.jsm", this);
   Components.utils.import("resource://gre/modules/osfile.jsm", this);
+  Components.utils.import("resource://gre/modules/Promise.jsm", this);
 
-  Services.prefs.setBoolPref("toolkit.osfile.debug.failshutdown", true);
   Services.prefs.setIntPref("toolkit.asyncshutdown.crash_timeout", 1);
   Services.prefs.setBoolPref("toolkit.osfile.native", false);
 
+  OS.File.profileBeforeChange.addBlocker("Adding a blocker that will never be resolved", () => Promise.defer().promise);
   OS.File.getCurrentDirectory();
+
   Services.obs.notifyObservers(null, "profile-before-change", null);
   dump("Waiting for crash\n");
 };
 
 function after_osfile_crash_noerror(mdump, extra) {
-  do_print("after OS.File crash: " + JSON.stringify(extra.AsyncShutdownTimeout));
+  do_print("after OS.File crash: " + extra.AsyncShutdownTimeout);
   let info = JSON.parse(extra.AsyncShutdownTimeout);
   let state = info.conditions[0].state;
   do_print("Keys: " + Object.keys(state).join(", "));
   do_check_eq(info.phase, "profile-before-change");
   do_check_true(state.launched);
   do_check_false(state.shutdown);
   do_check_true(state.worker);
   do_check_true(!!state.latestSent);
@@ -64,28 +66,30 @@ function after_osfile_crash_noerror(mdum
 }
 
 // Test that AsyncShutdown + OS.File reports errors correctly, in a case in which
 // the latest operation failed
 
 function setup_osfile_crash_exn() {
   Components.utils.import("resource://gre/modules/Services.jsm", this);
   Components.utils.import("resource://gre/modules/osfile.jsm", this);
+  Components.utils.import("resource://gre/modules/Promise.jsm", this);
 
-  Services.prefs.setBoolPref("toolkit.osfile.debug.failshutdown", true);
   Services.prefs.setIntPref("toolkit.asyncshutdown.crash_timeout", 1);
   Services.prefs.setBoolPref("toolkit.osfile.native", false);
 
+  OS.File.profileBeforeChange.addBlocker("Adding a blocker that will never be resolved", () => Promise.defer().promise);
   OS.File.read("I do not exist");
+
   Services.obs.notifyObservers(null, "profile-before-change", null);
   dump("Waiting for crash\n");
 };
 
 function after_osfile_crash_exn(mdump, extra) {
-  do_print("after OS.File crash: " + JSON.stringify(extra.AsyncShutdownTimeout));
+  do_print("after OS.File crash: " + extra.AsyncShutdownTimeout);
   let info = JSON.parse(extra.AsyncShutdownTimeout);
   let state = info.conditions[0].state;
   do_print("Keys: " + Object.keys(state).join(", "));
   do_check_eq(info.phase, "profile-before-change");
   do_check_false(state.shutdown);
   do_check_true(state.worker);
   do_check_true(!!state.latestSent);
   do_check_eq(state.latestSent[1], "read");
--- a/toolkit/devtools/server/actors/stylesheets.js
+++ b/toolkit/devtools/server/actors/stylesheets.js
@@ -69,19 +69,16 @@ let StyleSheetsActor = protocol.ActorCla
     return { actor: this.actorID };
   },
 
   initialize: function (conn, tabActor) {
     protocol.Actor.prototype.initialize.call(this, null);
 
     this.parentActor = tabActor;
 
-    // so we can get events when stylesheets and rules are added
-    this.document.styleSheetChangeEventsEnabled = true;
-
     // keep a map of sheets-to-actors so we don't create two actors for one sheet
     this._sheets = new Map();
   },
 
   /**
    * Destroy the current StyleSheetsActor instance.
    */
   destroy: function()
@@ -474,34 +471,16 @@ let StyleSheetActor = protocol.ActorClas
 
     this._window = aWindow;
 
     // text and index are unknown until source load
     this.text = null;
     this._styleSheetIndex = -1;
 
     this._transitionRefCount = 0;
-
-    this._onRuleAddedOrRemoved = this._onRuleAddedOrRemoved.bind(this);
-
-    if (this.browser) {
-      this.browser.addEventListener("StyleRuleAdded", this._onRuleAddedOrRemoved, true);
-      this.browser.addEventListener("StyleRuleRemoved", this._onRuleAddedOrRemoved, true);
-    }
-  },
-
-  _onRuleAddedOrRemoved: function(event) {
-    if (event.target != this.document || event.stylesheet != this.rawSheet) {
-      return;
-    }
-    if (event.rule && event.rule.type == Ci.nsIDOMCSSRule.MEDIA_RULE) {
-      this._getMediaRules().then((rules) => {
-        events.emit(this, "media-rules-changed", rules);
-      });
-    }
   },
 
   /**
    * Get the raw stylesheet's cssRules once the sheet has been loaded.
    *
    * @return {Promise}
    *         Promise that resolves with a CSSRuleList
    */
@@ -916,16 +895,20 @@ let StyleSheetActor = protocol.ActorClas
     this._notifyPropertyChanged("ruleCount");
 
     if (transition) {
       this._insertTransistionRule();
     }
     else {
       this._notifyStyleApplied();
     }
+
+    this._getMediaRules().then((rules) => {
+      events.emit(this, "media-rules-changed", rules);
+    });
   }, {
     request: {
       text: Arg(0, "string"),
       transition: Arg(1, "boolean")
     }
   }),
 
   /**
--- a/toolkit/devtools/server/actors/webbrowser.js
+++ b/toolkit/devtools/server/actors/webbrowser.js
@@ -43,16 +43,18 @@ XPCOMUtils.defineLazyGetter(this, "event
 function allAppShellDOMWindows(aWindowType)
 {
   let e = Services.wm.getEnumerator(aWindowType);
   while (e.hasMoreElements()) {
     yield e.getNext();
   }
 }
 
+exports.allAppShellDOMWindows = allAppShellDOMWindows;
+
 /**
  * Retrieve the window type of the top-level window |aWindow|.
  */
 function appShellDOMWindowType(aWindow) {
   /* This is what nsIWindowMediator's enumerator checks. */
   return aWindow.document.documentElement.getAttribute('windowtype');
 }
 
--- a/webapprt/content/dbg-webapp-actors.js
+++ b/webapprt/content/dbg-webapp-actors.js
@@ -1,15 +1,19 @@
 /* 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 { Promise } = Cu.import("resource://gre/modules/Promise.jsm", {});
+const { devtools } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
+const { BrowserTabActor, BrowserTabList, allAppShellDOMWindows,
+        sendShutdownEvent } = devtools.require("devtools/server/actors/webbrowser");
+const { RootActor } = devtools.require("devtools/server/actors/root");
 
 /**
  * WebappRT-specific actors.
  */
 
 /**
  * Construct a root actor appropriate for use in a server running in the webapp
  * runtime. The returned root actor: