Bug 985597 - Use the AnonymousContent Document API for the highlighters; r=miker r=Gijs
authorPatrick Brosset <pbrosset@mozilla.com>
Thu, 06 Nov 2014 13:04:23 +0100
changeset 214456 95270cadf94bd905d92bbcb89064d364666dd8b4
parent 214455 5e34ddeb2042ef4d35a578f09c23c30e47a546d4
child 214457 151419ca5e9c6e0cd94ec8c5c6b06206bf5d7fcb
push id51494
push userkwierso@gmail.com
push dateFri, 07 Nov 2014 03:08:20 +0000
treeherdermozilla-inbound@c4b831696f15 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmiker, Gijs
bugs985597
milestone36.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 985597 - Use the AnonymousContent Document API for the highlighters; r=miker r=Gijs
browser/base/content/browser.css
browser/base/content/highlighter.css
browser/base/content/test/general/browser_parsable_css.js
browser/themes/linux/browser.css
browser/themes/linux/jar.mn
browser/themes/osx/browser.css
browser/themes/osx/jar.mn
browser/themes/shared/devtools/highlighter.css
browser/themes/shared/devtools/highlighter.inc.css
browser/themes/windows/browser.css
browser/themes/windows/jar.mn
toolkit/devtools/server/actors/highlighter.js
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -782,19 +782,16 @@ statuspanel[inactive][previoustype=overL
   -moz-binding: url("chrome://browser/content/urlbarBindings.xml#promobox");
 }
 
 /* tabview menus */
 .tabview-menuitem {
   max-width: 32em;
 }
 
-/* highlighter */
-%include highlighter.css
-
 /* gcli */
 
 html|*#gcli-tooltip-frame,
 html|*#gcli-output-frame,
 #gcli-output,
 #gcli-tooltip {
   overflow-x: hidden;
 }
deleted file mode 100644
--- a/browser/base/content/highlighter.css
+++ /dev/null
@@ -1,80 +0,0 @@
-/* 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/. */
-
-.highlighter-container {
-  pointer-events: none;
-}
-
-/*
- * Box model highlighter
- */
-svg|svg.box-model-root[hidden],
-svg|line.box-model-guide-top[hidden],
-svg|line.box-model-guide-right[hidden],
-svg|line.box-model-guide-left[hidden],
-svg|line.box-model-guide-bottom[hidden] {
-  display: none;
-}
-
-/*
- * Node Infobar
- */
-.highlighter-nodeinfobar-container {
-  position: relative;
-}
-
-.highlighter-nodeinfobar-positioner {
-  position: absolute;
-  max-width: 95%;
-}
-
-.highlighter-nodeinfobar-positioner[hidden] {
-  opacity: 0;
-  pointer-events: none;
-  display: -moz-box;
-}
-
-.highlighter-nodeinfobar-text {
-  overflow: hidden;
-  white-space: nowrap;
-  text-overflow: ellipsis;
-  direction: ltr;
-}
-
-html|*.highlighter-nodeinfobar-id,
-html|*.highlighter-nodeinfobar-classes,
-html|*.highlighter-nodeinfobar-pseudo-classes,
-html|*.highlighter-nodeinfobar-dimensions,
-html|*.highlighter-nodeinfobar-tagname {
-  -moz-user-select: text;
-  -moz-user-focus: normal;
-  cursor: text;
-}
-
-.highlighter-nodeinfobar-arrow {
-  display: none;
-}
-
-.highlighter-nodeinfobar-positioner[position="top"]:not([hide-arrow]) > .highlighter-nodeinfobar-arrow-bottom {
-  display: block;
-}
-
-.highlighter-nodeinfobar-positioner[position="bottom"]:not([hide-arrow]) > .highlighter-nodeinfobar-arrow-top {
-  display: block;
-}
-
-.highlighter-nodeinfobar-positioner[disabled] {
-  visibility: hidden;
-}
-
-html|*.highlighter-nodeinfobar-tagname {
-  text-transform: lowercase;
-}
-
-/*
- * Css transform highlighter
- */
-svg|svg.css-transform-root[hidden] {
-  display: none;
-}
--- a/browser/base/content/test/general/browser_parsable_css.js
+++ b/browser/base/content/test/general/browser_parsable_css.js
@@ -5,21 +5,30 @@
  * detect newly occurring issues in shipping CSS. It is a list of objects
  * specifying conditions under which an error should be ignored.
  *
  * Every property of the objects in it needs to consist of a regular expression
  * matching the offending error. If an object has multiple regex criteria, they
  * ALL need to match an error in order for that error not to cause a test
  * failure. */
 const kWhitelist = [
-  {sourceName: /cleopatra.*(tree|ui)\.css/i}, /* Cleopatra is imported as-is, see bug 1004421 */
-  {sourceName: /codemirror\.css/i}, /* CodeMirror is imported as-is, see bug 1004423 */
-  {sourceName: /web\/viewer\.css/i, errorMessage: /Unknown pseudo-class.*(fullscreen|selection)/i }, /* PDFjs is futureproofing its pseudoselectors, and those rules are dropped. */
-  {sourceName: /aboutaccounts\/(main|normalize)\.css/i}, /* Tracked in bug 1004428 */
-  {sourceName: /loop\/.*sdk-content\/.*\.css$/i /* TokBox SDK assets, see bug 1032469 */}
+  // Cleopatra is imported as-is, see bug 1004421.
+  {sourceName: /cleopatra.*(tree|ui)\.css/i},
+  // CodeMirror is imported as-is, see bug 1004423.
+  {sourceName: /codemirror\.css/i},
+  // PDFjs is futureproofing its pseudoselectors, and those rules are dropped.
+  {sourceName: /web\/viewer\.css/i,
+   errorMessage: /Unknown pseudo-class.*(fullscreen|selection)/i},
+  // Tracked in bug 1004428.
+  {sourceName: /aboutaccounts\/(main|normalize)\.css/i},
+  // TokBox SDK assets, see bug 1032469.
+  {sourceName: /loop\/.*sdk-content\/.*\.css$/i},
+  // Highlighter CSS uses chrome-only pseudo-class, see bug 985597.
+  {sourceName: /highlighter\.css/i,
+   errorMessage: /Unknown pseudo-class.*moz-native-anonymous/i}
 ];
 
 let moduleLocation = gTestPath.replace(/\/[^\/]*$/i, "/parsingTestHelpers.jsm");
 let {generateURIsFromDirTree} = Cu.import(moduleLocation, {});
 
 /**
  * Check if an error should be ignored due to matching one of the whitelist
  * objects defined in kWhitelist
--- a/browser/themes/linux/browser.css
+++ b/browser/themes/linux/browser.css
@@ -2050,17 +2050,16 @@ toolbarbutton.chevron > .toolbarbutton-i
 }
 
 .full-screen-approval-button,
 #full-screen-remember-decision {
   font-size: 120%;
 }
 
 %include ../shared/devtools/responsivedesign.inc.css
-%include ../shared/devtools/highlighter.inc.css
 %include ../shared/devtools/commandline.inc.css
 %include ../shared/plugin-doorhanger.inc.css
 %include ../shared/badcontent-doorhanger.inc.css
 
 %include downloads/indicator.css
 
 .gcli-panel {
   padding: 0;
--- a/browser/themes/linux/jar.mn
+++ b/browser/themes/linux/jar.mn
@@ -238,16 +238,17 @@ browser.jar:
   skin/classic/browser/devtools/alerticon-warning.png (../shared/devtools/images/alerticon-warning.png)
   skin/classic/browser/devtools/alerticon-warning@2x.png      (../shared/devtools/images/alerticon-warning@2x.png)
 * skin/classic/browser/devtools/ruleview.css          (../shared/devtools/ruleview.css)
 * skin/classic/browser/devtools/webconsole.css                  (devtools/webconsole.css)
   skin/classic/browser/devtools/webconsole_networkpanel.css     (devtools/webconsole_networkpanel.css)
   skin/classic/browser/devtools/webconsole.png                  (../shared/devtools/images/webconsole.png)
   skin/classic/browser/devtools/webconsole@2x.png               (../shared/devtools/images/webconsole@2x.png)
   skin/classic/browser/devtools/commandline.css              (devtools/commandline.css)
+  skin/classic/browser/devtools/highlighter.css              (../shared/devtools/highlighter.css)
   skin/classic/browser/devtools/markup-view.css       (../shared/devtools/markup-view.css)
   skin/classic/browser/devtools/editor-error.png       (../shared/devtools/images/editor-error.png)
   skin/classic/browser/devtools/editor-breakpoint.png  (../shared/devtools/images/editor-breakpoint.png)
   skin/classic/browser/devtools/editor-debug-location.png (../shared/devtools/images/editor-debug-location.png)
   skin/classic/browser/devtools/editor-debug-location@2x.png (../shared/devtools/images/editor-debug-location@2x.png)
   skin/classic/browser/devtools/breadcrumbs-divider@2x.png      (../shared/devtools/images/breadcrumbs-divider@2x.png)
   skin/classic/browser/devtools/breadcrumbs-scrollbutton.png    (../shared/devtools/images/breadcrumbs-scrollbutton.png)
   skin/classic/browser/devtools/breadcrumbs-scrollbutton@2x.png (../shared/devtools/images/breadcrumbs-scrollbutton@2x.png)
--- a/browser/themes/osx/browser.css
+++ b/browser/themes/osx/browser.css
@@ -4380,17 +4380,16 @@ menulist.translate-infobar-element > .me
 }
 
 .full-screen-approval-button,
 #full-screen-remember-decision {
   font-size: 120%;
 }
 
 %include ../shared/devtools/responsivedesign.inc.css
-%include ../shared/devtools/highlighter.inc.css
 %include ../shared/devtools/commandline.inc.css
 %include ../shared/plugin-doorhanger.inc.css
 %include ../shared/badcontent-doorhanger.inc.css
 
 %include downloads/indicator.css
 
 /* On mac, the popup notification contents are indented by default and so
   the default closebutton margins from notification.css require adjustment */
--- a/browser/themes/osx/jar.mn
+++ b/browser/themes/osx/jar.mn
@@ -355,16 +355,17 @@ browser.jar:
   skin/classic/browser/devtools/command-console.png           (../shared/devtools/images/command-console.png)
   skin/classic/browser/devtools/command-console@2x.png        (../shared/devtools/images/command-console@2x.png)
   skin/classic/browser/devtools/command-eyedropper.png        (../shared/devtools/images/command-eyedropper.png)
   skin/classic/browser/devtools/command-eyedropper@2x.png     (../shared/devtools/images/command-eyedropper@2x.png)
   skin/classic/browser/devtools/alerticon-warning.png         (../shared/devtools/images/alerticon-warning.png)
   skin/classic/browser/devtools/alerticon-warning@2x.png      (../shared/devtools/images/alerticon-warning@2x.png)
 * skin/classic/browser/devtools/ruleview.css                (../shared/devtools/ruleview.css)
   skin/classic/browser/devtools/commandline.css             (devtools/commandline.css)
+  skin/classic/browser/devtools/highlighter.css              (../shared/devtools/highlighter.css)
   skin/classic/browser/devtools/markup-view.css             (../shared/devtools/markup-view.css)
   skin/classic/browser/devtools/editor-error.png             (../shared/devtools/images/editor-error.png)
   skin/classic/browser/devtools/editor-breakpoint.png        (../shared/devtools/images/editor-breakpoint.png)
   skin/classic/browser/devtools/editor-breakpoint@2x.png        (../shared/devtools/images/editor-breakpoint@2x.png)
   skin/classic/browser/devtools/editor-debug-location.png    (../shared/devtools/images/editor-debug-location.png)
   skin/classic/browser/devtools/editor-debug-location@2x.png    (../shared/devtools/images/editor-debug-location@2x.png)
 * skin/classic/browser/devtools/webconsole.css                  (devtools/webconsole.css)
   skin/classic/browser/devtools/webconsole_networkpanel.css     (devtools/webconsole_networkpanel.css)
rename from browser/themes/shared/devtools/highlighter.inc.css
rename to browser/themes/shared/devtools/highlighter.css
--- a/browser/themes/shared/devtools/highlighter.inc.css
+++ b/browser/themes/shared/devtools/highlighter.css
@@ -1,133 +1,172 @@
-%if 0
 /* 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/. */
-%endif
+
+/*
+  The :-moz-native-anonymous selector prefix prevents the styles defined here
+  from impacting web content.
+  Indeed, this pseudo-class is only available to chrome code.
+  This stylesheet is loaded as a ua stylesheet via the addon sdk, so having this
+  pseudo-class is important.
+  Having bug 1086532 fixed would make it possible to load this stylesheet in a
+  <style scoped> node instead, directly in the native anonymous container
+  element.
+*/
+
+:-moz-native-anonymous .highlighter-container {
+  pointer-events: none;
+  position: absolute;
+  width: 100%;
+  height: 100%;
+}
+
+:-moz-native-anonymous .highlighter-container [hidden] {
+  display: none;
+}
 
 /* Box model highlighter */
 
-svg|g.box-model-container {
+:-moz-native-anonymous .box-model-container {
   opacity: 0.4;
 }
 
-svg|polygon.box-model-content {
+:-moz-native-anonymous .box-model-content {
   fill: #80d4ff;
 }
 
-svg|polygon.box-model-padding {
+:-moz-native-anonymous .box-model-padding {
   fill: #66cc52;
 }
 
-svg|polygon.box-model-border {
+:-moz-native-anonymous .box-model-border {
   fill: #ffe431;
 }
 
-svg|polygon.box-model-margin {
+:-moz-native-anonymous .box-model-margin {
   fill: #d89b28;
 }
 
-svg|polygon.box-model-content,
-svg|polygon.box-model-padding,
-svg|polygon.box-model-border,
-svg|polygon.box-model-margin {
+:-moz-native-anonymous .box-model-content,
+:-moz-native-anonymous .box-model-padding,
+:-moz-native-anonymous .box-model-border,
+:-moz-native-anonymous .box-model-margin {
   stroke: none;
 }
 
-svg|line.box-model-guide-top,
-svg|line.box-model-guide-right,
-svg|line.box-model-guide-bottom,
-svg|line.box-model-guide-left {
+:-moz-native-anonymous .box-model-guide-top,
+:-moz-native-anonymous .box-model-guide-right,
+:-moz-native-anonymous .box-model-guide-bottom,
+:-moz-native-anonymous .box-model-guide-left {
   stroke: #08C;
   stroke-dasharray: 5 3;
   shape-rendering: crispEdges;
 }
 
 /* Highlighter - Node Infobar */
 
-.highlighter-nodeinfobar {
-  color: hsl(216,33%,97%);
+:-moz-native-anonymous .box-model-nodeinfobar-container {
+  position: absolute;
+  max-width: 95%;
+
+  font: message-box;
+  font-size: 11px;
+}
+
+:-moz-native-anonymous .box-model-nodeinfobar {
+  position: relative;
+
+  /* Centering the nodeinfobar in the container */
+  left: -50%;
+
+  padding: 5px;
+  min-width: 75px;
+
   border-radius: 3px;
   background: hsl(214,13%,24%) no-repeat padding-box;
-  padding: 5px;
-  /* Avoid cases where the infobar is smaller than the arrow, when the text is
-  short */
-  min-width: 75px;
-   /* Avoid a shadow with lightweight themes - Bug 1037908 */
+
+  color: hsl(216,33%,97%);
   text-shadow: none;
 }
 
-/* Highlighter - Node Infobar - text */
+:-moz-native-anonymous .box-model-nodeinfobar-container[hide-arrow] > .box-model-nodeinfobar {
+  margin: 7px 0;
+}
+
+/* Arrows */
+
+:-moz-native-anonymous .box-model-nodeinfobar-container > .box-model-nodeinfobar:before {
+  content: "";
+  display: none;
+
+  position: absolute;
+  left: calc(50% - 14px);
+
+  height: 0;
+  width: 0;
+  border: 14px solid hsl(210,2%,22%);
+  border-left-color: transparent;
+  border-right-color: transparent;
+}
 
-.highlighter-nodeinfobar-text {
+:-moz-native-anonymous .box-model-nodeinfobar-container[position="top"]:not([hide-arrow]) > .box-model-nodeinfobar:before {
+  border-bottom: 0;
+  top: 100%;
+  display: block;
+}
+
+:-moz-native-anonymous .box-model-nodeinfobar-container[position="bottom"]:not([hide-arrow]) > .box-model-nodeinfobar:before {
+  border-top: 0;
+  bottom: 100%;
+  display: block;
+}
+
+/* Text container */
+
+:-moz-native-anonymous .box-model-nodeinfobar-text {
+  overflow: hidden;
+  white-space: nowrap;
+  direction: ltr;
   text-align: center;
-  /* 100% - size of the buttons and margins */
-  max-width: calc(100% - 2 * (26px + 6px));
   padding-bottom: 1px;
 }
 
-html|*.highlighter-nodeinfobar-tagname {
+:-moz-native-anonymous .box-model-nodeinfobar-tagname {
   color: hsl(285,100%,75%);
+  text-transform: lowercase;
 }
 
-html|*.highlighter-nodeinfobar-id {
+:-moz-native-anonymous .box-model-nodeinfobar-id {
   color: hsl(103,46%,54%);
 }
 
-html|*.highlighter-nodeinfobar-classes,
-html|*.highlighter-nodeinfobar-pseudo-classes {
+:-moz-native-anonymous .box-model-nodeinfobar-classes,
+:-moz-native-anonymous .box-model-nodeinfobar-pseudo-classes {
   color: hsl(200,74%,57%);
 }
 
-html|*.highlighter-nodeinfobar-dimensions {
+:-moz-native-anonymous .box-model-nodeinfobar-dimensions {
   color: hsl(210,30%,85%);
   -moz-border-start: 1px solid #5a6169;
   -moz-margin-start: 6px;
   -moz-padding-start: 6px;
 }
 
-/* Highlighter - Node Infobar - box & arrow */
-
-.highlighter-nodeinfobar-arrow {
-  width: 14px;
-  height: 14px;
-  -moz-margin-start: calc(50% - 7px);
-  transform: rotate(-45deg);
-  background-clip: padding-box;
-  background-repeat: no-repeat;
-}
-
-.highlighter-nodeinfobar-arrow-top {
-  margin-bottom: -8px;
-  margin-top: 8px;
-  background-image: linear-gradient(to top right, transparent 50%, hsl(210,2%,22%) 50%);
-}
-
-.highlighter-nodeinfobar-arrow-bottom {
-  margin-top: -8px;
-  margin-bottom: 8px;
-  background-image: linear-gradient(to bottom left, transparent 50%, hsl(210,2%,22%) 50%);
-}
-
-.highlighter-nodeinfobar-container[hide-arrow] > .highlighter-nodeinfobar {
-  margin: 7px 0;
-}
-
 /* Css transform highlighter */
 
-svg|polygon.css-transform-transformed {
+:-moz-native-anonymous .css-transform-transformed {
   fill: #80d4ff;
   opacity: 0.8;
 }
 
-svg|polygon.css-transform-untransformed {
+:-moz-native-anonymous .css-transform-untransformed {
   fill: #66cc52;
   opacity: 0.8;
 }
 
-svg|polygon.css-transform-transformed,
-svg|polygon.css-transform-untransformed,
-svg|line.css-transform-line {
+:-moz-native-anonymous .css-transform-transformed,
+:-moz-native-anonymous .css-transform-untransformed,
+:-moz-native-anonymous .css-transform-line {
   stroke: #08C;
   stroke-dasharray: 5 3;
   stroke-width: 2;
 }
--- a/browser/themes/windows/browser.css
+++ b/browser/themes/windows/browser.css
@@ -2671,17 +2671,16 @@ notification[value="translation"] {
 }
 
 .full-screen-approval-button,
 #full-screen-remember-decision {
   font-size: 120%;
 }
 
 %include ../shared/devtools/responsivedesign.inc.css
-%include ../shared/devtools/highlighter.inc.css
 %include ../shared/devtools/commandline.inc.css
 %include ../shared/plugin-doorhanger.inc.css
 %include ../shared/badcontent-doorhanger.inc.css
 
 %include downloads/indicator.css
 
 /* Error counter */
 
--- a/browser/themes/windows/jar.mn
+++ b/browser/themes/windows/jar.mn
@@ -269,16 +269,17 @@ browser.jar:
         skin/classic/browser/devtools/command-pick.png              (../shared/devtools/images/command-pick.png)
         skin/classic/browser/devtools/command-pick@2x.png           (../shared/devtools/images/command-pick@2x.png)
         skin/classic/browser/devtools/command-frames.png            (../shared/devtools/images/command-frames.png)
         skin/classic/browser/devtools/command-frames@2x.png         (../shared/devtools/images/command-frames@2x.png)
         skin/classic/browser/devtools/command-console.png           (../shared/devtools/images/command-console.png)
         skin/classic/browser/devtools/command-console@2x.png        (../shared/devtools/images/command-console@2x.png)
         skin/classic/browser/devtools/command-eyedropper.png        (../shared/devtools/images/command-eyedropper.png)
         skin/classic/browser/devtools/command-eyedropper@2x.png     (../shared/devtools/images/command-eyedropper@2x.png)
+        skin/classic/browser/devtools/highlighter.css               (../shared/devtools/highlighter.css)
         skin/classic/browser/devtools/markup-view.css               (../shared/devtools/markup-view.css)
         skin/classic/browser/devtools/editor-error.png              (../shared/devtools/images/editor-error.png)
         skin/classic/browser/devtools/editor-breakpoint.png         (../shared/devtools/images/editor-breakpoint.png)
         skin/classic/browser/devtools/editor-breakpoint@2x.png         (../shared/devtools/images/editor-breakpoint@2x.png)
         skin/classic/browser/devtools/editor-debug-location.png     (../shared/devtools/images/editor-debug-location.png)
         skin/classic/browser/devtools/editor-debug-location@2x.png     (../shared/devtools/images/editor-debug-location@2x.png)
 *       skin/classic/browser/devtools/webconsole.css                (devtools/webconsole.css)
         skin/classic/browser/devtools/webconsole_networkpanel.css   (devtools/webconsole_networkpanel.css)
@@ -704,16 +705,17 @@ browser.jar:
         skin/classic/aero/browser/devtools/command-console.png       (../shared/devtools/images/command-console.png)
         skin/classic/aero/browser/devtools/command-console@2x.png    (../shared/devtools/images/command-console@2x.png)
         skin/classic/aero/browser/devtools/command-eyedropper.png        (../shared/devtools/images/command-eyedropper.png)
         skin/classic/aero/browser/devtools/command-eyedropper@2x.png     (../shared/devtools/images/command-eyedropper@2x.png)
         skin/classic/aero/browser/devtools/alerticon-warning.png     (../shared/devtools/images/alerticon-warning.png)
         skin/classic/aero/browser/devtools/alerticon-warning@2x.png  (../shared/devtools/images/alerticon-warning@2x.png)
 *       skin/classic/aero/browser/devtools/ruleview.css              (../shared/devtools/ruleview.css)
         skin/classic/aero/browser/devtools/commandline.css           (devtools/commandline.css)
+        skin/classic/aero/browser/devtools/highlighter.css           (../shared/devtools/highlighter.css)
         skin/classic/aero/browser/devtools/markup-view.css           (../shared/devtools/markup-view.css)
         skin/classic/aero/browser/devtools/editor-error.png           (../shared/devtools/images/editor-error.png)
         skin/classic/aero/browser/devtools/editor-breakpoint.png      (../shared/devtools/images/editor-breakpoint.png)
         skin/classic/aero/browser/devtools/editor-breakpoint@2x.png      (../shared/devtools/images/editor-breakpoint@2x.png)
         skin/classic/aero/browser/devtools/editor-debug-location.png  (../shared/devtools/images/editor-debug-location.png)
         skin/classic/aero/browser/devtools/editor-debug-location@2x.png  (../shared/devtools/images/editor-debug-location@2x.png)
 *       skin/classic/aero/browser/devtools/webconsole.css                  (devtools/webconsole.css)
         skin/classic/aero/browser/devtools/webconsole_networkpanel.css     (devtools/webconsole_networkpanel.css)
--- a/toolkit/devtools/server/actors/highlighter.js
+++ b/toolkit/devtools/server/actors/highlighter.js
@@ -5,41 +5,52 @@
 "use strict";
 
 const {Cu, Cc, Ci} = require("chrome");
 const Services = require("Services");
 const protocol = require("devtools/server/protocol");
 const {Arg, Option, method} = protocol;
 const events = require("sdk/event/core");
 const Heritage = require("sdk/core/heritage");
-
 const {CssLogic} = require("devtools/styleinspector/css-logic");
 const EventEmitter = require("devtools/toolkit/event-emitter");
-const GUIDE_STROKE_WIDTH = 1;
 
 // Make sure the domnode type is known here
 require("devtools/server/actors/inspector");
 
 Cu.import("resource://gre/modules/devtools/LayoutHelpers.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 // FIXME: add ":visited" and ":link" after bug 713106 is fixed
 const PSEUDO_CLASSES = [":hover", ":active", ":focus"];
-const HIGHLIGHTED_PSEUDO_CLASS = ":-moz-devtools-highlighted";
-let HELPER_SHEET = ".__fx-devtools-hide-shortcut__ { visibility: hidden !important } ";
-HELPER_SHEET += ":-moz-devtools-highlighted { outline: 2px dashed #F06!important; outline-offset: -2px!important } ";
-const XHTML_NS = "http://www.w3.org/1999/xhtml";
+const BOX_MODEL_REGIONS = ["margin", "border", "padding", "content"];
+const BOX_MODEL_SIDES = ["top", "right", "bottom", "left"];
 const SVG_NS = "http://www.w3.org/2000/svg";
+const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+const HIGHLIGHTER_STYLESHEET_URI = "chrome://browser/skin/devtools/highlighter.css";
 const HIGHLIGHTER_PICKED_TIMER = 1000;
-const INFO_BAR_OFFSET = 5;
+// How high is the nodeinfobar
+const NODE_INFOBAR_HEIGHT = 40; //px
+const NODE_INFOBAR_ARROW_SIZE = 15; // px
+// Width of boxmodelhighlighter guides
+const GUIDE_STROKE_WIDTH = 1;
 // The minimum distance a line should be before it has an arrow marker-end
 const ARROW_LINE_MIN_DISTANCE = 10;
 // How many maximum nodes can be highlighted at the same time by the
 // SelectorHighlighter
 const MAX_HIGHLIGHTED_ELEMENTS = 100;
+// SimpleOutlineHighlighter's stylesheet
+const HIGHLIGHTED_PSEUDO_CLASS = ":-moz-devtools-highlighted";
+const SIMPLE_OUTLINE_SHEET = ".__fx-devtools-hide-shortcut__ {" +
+                             "  visibility: hidden !important" +
+                             "}" +
+                             HIGHLIGHTED_PSEUDO_CLASS + " {" +
+                             "  outline: 2px dashed #F06!important;" +
+                             "  outline-offset: -2px!important;" +
+                             "}";
 
 // All possible highlighter classes
 let HIGHLIGHTER_CLASSES = exports.HIGHLIGHTER_CLASSES = {
   "BoxModelHighlighter": BoxModelHighlighter,
   "CssTransformHighlighter": CssTransformHighlighter,
   "SelectorHighlighter": SelectorHighlighter
 };
 
@@ -77,40 +88,68 @@ let HighlighterActor = exports.Highlight
 
     this._autohide = autohide;
     this._inspector = inspector;
     this._walker = this._inspector.walker;
     this._tabActor = this._inspector.tabActor;
 
     this._highlighterReady = this._highlighterReady.bind(this);
     this._highlighterHidden = this._highlighterHidden.bind(this);
+    this._onNavigate = this._onNavigate.bind(this);
 
-    if (supportXULBasedHighlighter(this._tabActor)) {
-      this._boxModelHighlighter =
-        new BoxModelHighlighter(this._tabActor, this._inspector);
+    this._createHighlighter();
+
+    // Listen to navigation events to switch from the BoxModelHighlighter to the
+    // SimpleOutlineHighlighter, and back, if the top level window changes.
+    events.on(this._tabActor, "navigate", this._onNavigate);
+  },
+
+  get conn() this._inspector && this._inspector.conn,
 
-        this._boxModelHighlighter.on("ready", this._highlighterReady);
-        this._boxModelHighlighter.on("hide", this._highlighterHidden);
+  _createHighlighter: function() {
+    this._isPreviousWindowXUL = isXUL(this._tabActor);
+
+    if (!this._isPreviousWindowXUL) {
+      this._boxModelHighlighter = new BoxModelHighlighter(this._tabActor,
+                                                          this._inspector);
+      this._boxModelHighlighter.on("ready", this._highlighterReady);
+      this._boxModelHighlighter.on("hide", this._highlighterHidden);
     } else {
       this._boxModelHighlighter = new SimpleOutlineHighlighter(this._tabActor);
     }
   },
 
-  get conn() this._inspector && this._inspector.conn,
-
-  destroy: function() {
-    protocol.Actor.prototype.destroy.call(this);
+  _destroyHighlighter: function() {
     if (this._boxModelHighlighter) {
-      if (supportXULBasedHighlighter(this._tabActor)) {
+      if (!this._isPreviousWindowXUL) {
         this._boxModelHighlighter.off("ready", this._highlighterReady);
         this._boxModelHighlighter.off("hide", this._highlighterHidden);
       }
       this._boxModelHighlighter.destroy();
       this._boxModelHighlighter = null;
     }
+  },
+
+  _onNavigate: function({isTopLevel}) {
+    if (!isTopLevel) {
+      return;
+    }
+
+    // Only rebuild the highlighter if the window type changed.
+    if (isXUL(this._tabActor) !== this._isPreviousWindowXUL) {
+      this._destroyHighlighter();
+      this._createHighlighter();
+    }
+  },
+
+  destroy: function() {
+    protocol.Actor.prototype.destroy.call(this);
+
+    this._destroyHighlighter();
+    events.off(this._tabActor, "navigate", this._onNavigate);
     this._autohide = null;
     this._inspector = null;
     this._walker = null;
     this._tabActor = null;
   },
 
   /**
    * Display the box model highlighting on a given NodeActor.
@@ -280,19 +319,19 @@ let CustomHighlighterActor = exports.Cus
 
     let constructor = HIGHLIGHTER_CLASSES[typeName];
     if (!constructor) {
       throw new Error(typeName + " isn't a valid highlighter class (" +
         Object.keys(HIGHLIGHTER_CLASSES) + ")");
       return;
     }
 
-    // The assumption is that all custom highlighters need a XUL parent in the
-    // browser to append their elements
-    if (supportXULBasedHighlighter(inspector.tabActor)) {
+    // The assumption is that all custom highlighters need the canvasframe
+    // container to append their elements, so if this is a XUL window, bail out.
+    if (!isXUL(this._inspector.tabActor)) {
       this._highlighter = new constructor(inspector.tabActor);
     }
   },
 
   get conn() this._inspector && this._inspector.conn,
 
   destroy: function() {
     protocol.Actor.prototype.destroy.call(this);
@@ -340,29 +379,138 @@ let CustomHighlighterActor = exports.Cus
   }, {
     oneway: true
   })
 });
 
 let CustomHighlighterFront = protocol.FrontClass(CustomHighlighterActor, {});
 
 /**
- * Parent class for XUL-based complex highlighter that are inserted in the
- * parent browser structure
+ * Every highlighters should insert their markup content into the document's
+ * canvasFrame anonymous content container (see dom/webidl/Document.webidl).
+ *
+ * Since this container gets cleared when the document navigates, highlighters
+ * should use this helper to have their markup content automatically re-inserted
+ * in the new document.
+ *
+ * Since the markup content is inserted in the canvasFrame using
+ * insertAnonymousContent, this means that it can be modified using the API
+ * described in AnonymousContent.webidl.
+ * To retrieve the AnonymousContent instance, use the content getter.
+ *
+ * @param {TabActor} tabActor
+ *        The tabactor which windows will be used to insert the node
+ * @param {Function} nodeBuilder
+ *        A function that, when executed, returns a DOM node to be inserted into
+ *        the canvasFrame
  */
-function XULBasedHighlighter(tabActor) {
+function CanvasFrameAnonymousContentHelper(tabActor, nodeBuilder) {
+  this.tabActor = tabActor;
+  this.nodeBuilder = nodeBuilder;
+
+  this._insert();
+
+  this._onNavigate = this._onNavigate.bind(this);
+  events.on(this.tabActor, "navigate", this._onNavigate);
+}
+
+CanvasFrameAnonymousContentHelper.prototype = {
+  destroy: function() {
+    // If the current window isn't the one the content was inserted into, this
+    // will fail, but that's fine.
+    try {
+      let doc = this.tabActor.window.document;
+      doc.removeAnonymousContent(this._content);
+    } catch (e) {}
+    events.off(this.tabActor, "navigate", this._onNavigate);
+    this.tabActor = this.nodeBuilder = this._content = null;
+  },
+
+  _insert: function() {
+    // Re-insert the content node after page navigation only if the new page
+    // isn't XUL.
+    if (!isXUL(this.tabActor)) {
+      // For now highlighter.css is injected in content as a ua sheet because
+      // <style scoped> doesn't work inside anonymous content (see bug 1086532).
+      // If it did, highlighter.css would be injected as an anonymous content
+      // node using CanvasFrameAnonymousContentHelper instead.
+      installHelperSheet(this.tabActor.window,
+        "@import url('" + HIGHLIGHTER_STYLESHEET_URI + "');");
+      let node = this.nodeBuilder();
+      let doc = this.tabActor.window.document;
+      this._content = doc.insertAnonymousContent(node);
+    }
+  },
+
+  _onNavigate: function({isTopLevel}) {
+    if (isTopLevel) {
+      this._insert();
+    }
+  },
+
+  getTextContentForElement: function(id) {
+    if (!this.content) {
+      return null;
+    }
+    return this.content.getTextContentForElement(id);
+  },
+
+  setTextContentForElement: function(id, text) {
+    if (this.content) {
+      this.content.setTextContentForElement(id, text);
+    }
+  },
+
+  setAttributeForElement: function(id, name, value) {
+    if (this.content) {
+      this.content.setAttributeForElement(id, name, value);
+    }
+  },
+
+  getAttributeForElement: function(id, name) {
+    if (!this.content) {
+      return null;
+    }
+    return this.content.getAttributeForElement(id, name);
+  },
+
+  removeAttributeForElement: function(id, name) {
+    if (this.content) {
+      this.content.removeAttributeForElement(id, name);
+    }
+  },
+
+  get content() {
+    if (Cu.isDeadWrapper(this._content)) {
+      return null;
+    }
+    return this._content;
+  }
+};
+
+/**
+ * Base class for auto-refresh-on-change highlighters. Sub classes will have a
+ * chance to update whenever the current node's geometry changes.
+ *
+ * Sub classes must implement the following methods:
+ * _show: called when the highlighter should be shown,
+ * _hide: called when the highlighter should be hidden,
+ * _update: called while the highlighter is shown and the geometry of the
+ *          current node changes.
+ */
+function AutoRefreshHighlighter(tabActor) {
+  this.tabActor = tabActor;
   this.browser = tabActor.browser;
   this.win = tabActor.window;
-  this.chromeDoc = this.browser.ownerDocument;
   this.currentNode = null;
 
   this.update = this.update.bind(this);
 }
 
-XULBasedHighlighter.prototype = {
+AutoRefreshHighlighter.prototype = {
   /**
    * Show the highlighter on a given node
    * @param {DOMNode} node
    * @param {Object} options
    *        Object used for passing options
    */
   show: function(node, options={}) {
     if (!isNodeValid(node) || node === this.currentNode) {
@@ -387,28 +535,29 @@ XULBasedHighlighter.prototype = {
 
     this._hide();
     this._detachPageListeners();
     this.currentNode = null;
     this.options = null;
   },
 
   /**
-   * Update the highlighter while shown
+   * Update the highlighter on repaint
    */
-  update: function() {
-    if (isNodeValid(this.currentNode)) {
-      this._update();
+  update: function(e) {
+    if (!isNodeValid(this.currentNode)) {
+      return;
     }
+    this._update();
   },
 
   _show: function() {
     // To be implemented by sub classes
     // When called, sub classes should actually show the highlighter for
-    // this.currentNode
+    // this.currentNode, potentially using options in this.options
   },
 
   _update: function() {
     // To be implemented by sub classes
     // When called, sub classes should update the highlighter shown for
     // this.currentNode
     // This is called as a result of a page scroll, zoom or repaint
   },
@@ -418,37 +567,40 @@ XULBasedHighlighter.prototype = {
     // When called, sub classes should actually hide the highlighter
   },
 
   /**
    * Listen to changes on the content page to update the highlighter
    */
   _attachPageListeners: function() {
     if (isNodeValid(this.currentNode)) {
-      let win = this.currentNode.ownerDocument.defaultView;
-      this.browser.addEventListener("MozAfterPaint", this.update);
+      // With non-e10s, this.browser exists (browser xul) and should be used to
+      // listen to MozAfterPaint events.
+      // With e10s, the chrome window should be used
+      let target = this.browser || this.win;
+      target.addEventListener("MozAfterPaint", this.update);
     }
   },
 
   /**
    * Stop listening to page changes
    */
   _detachPageListeners: function() {
     if (isNodeValid(this.currentNode)) {
-      let win = this.currentNode.ownerDocument.defaultView;
-      this.browser.removeEventListener("MozAfterPaint", this.update);
+      let target = this.browser || this.win;
+      target.removeEventListener("MozAfterPaint", this.update);
     }
   },
 
   destroy: function() {
     this.hide();
 
+    this.tabActor = null;
     this.win = null;
     this.browser = null;
-    this.chromeDoc = null;
     this.currentNode = null;
   }
 };
 
 /**
  * The BoxModelHighlighter is the class that actually draws the the box model
  * regions on top of a node.
  * It is used by the HighlighterActor.
@@ -469,202 +621,190 @@ XULBasedHighlighter.prototype = {
  *   Defaults to false
  * - hideInfoBar {Boolean}
  *   Defaults to false
  * - showOnly {String}
  *   "content", "padding", "border" or "margin"
  *    If set, only this region will be highlighted
  *
  * Structure:
- * <stack class="highlighter-container">
+ * <div class="highlighter-container">
  *   <svg class="box-model-root" hidden="true">
  *     <g class="box-model-container">
  *       <polygon class="box-model-margin" points="317,122 747,36 747,181 317,267" />
  *       <polygon class="box-model-border" points="317,128 747,42 747,161 317,247" />
  *       <polygon class="box-model-padding" points="323,127 747,42 747,161 323,246" />
  *       <polygon class="box-model-content" points="335,137 735,57 735,152 335,232" />
  *     </g>
  *     <line class="box-model-guide-top" x1="0" y1="592" x2="99999" y2="592" />
  *     <line class="box-model-guide-right" x1="735" y1="0" x2="735" y2="99999" />
  *     <line class="box-model-guide-bottom" x1="0" y1="612" x2="99999" y2="612" />
  *     <line class="box-model-guide-left" x1="334" y1="0" x2="334" y2="99999" />
  *   </svg>
- *   <box class="highlighter-nodeinfobar-container">
- *     <box class="highlighter-nodeinfobar-positioner" position="top" />
- *       <box class="highlighter-nodeinfobar-arrow highlighter-nodeinfobar-arrow-top" />
- *       <hbox class="highlighter-nodeinfobar">
- *         <hbox class="highlighter-nodeinfobar-text" align="center" flex="1">
- *           <span class="highlighter-nodeinfobar-tagname">Node name</span>
- *           <span class="highlighter-nodeinfobar-id">Node id</span>
- *           <span class="highlighter-nodeinfobar-classes">.someClass</span>
- *           <span class="highlighter-nodeinfobar-pseudo-classes">:hover</span>
- *         </hbox>
- *       </hbox>
- *       <box class="highlighter-nodeinfobar-arrow highlighter-nodeinfobar-arrow-bottom"/>
- *     </box>
- *   </box>
- * </stack>
+ *   <div class="highlighter-nodeinfobar-container">
+ *     <div class="highlighter-nodeinfobar-arrow highlighter-nodeinfobar-arrow-top" />
+ *     <div class="highlighter-nodeinfobar">
+ *       <div class="highlighter-nodeinfobar-text" align="center" flex="1">
+ *         <span class="highlighter-nodeinfobar-tagname">Node name</span>
+ *         <span class="highlighter-nodeinfobar-id">Node id</span>
+ *         <span class="highlighter-nodeinfobar-classes">.someClass</span>
+ *         <span class="highlighter-nodeinfobar-pseudo-classes">:hover</span>
+ *       </div>
+ *     </div>
+ *     <div class="highlighter-nodeinfobar-arrow highlighter-nodeinfobar-arrow-bottom"/>
+ *   </div>
+ * </div>
  */
 function BoxModelHighlighter(tabActor) {
-  XULBasedHighlighter.call(this, tabActor);
+  AutoRefreshHighlighter.call(this, tabActor);
   this.layoutHelpers = new LayoutHelpers(this.win);
-  this._initMarkup();
   EventEmitter.decorate(this);
 
+  this.markup = new CanvasFrameAnonymousContentHelper(this.tabActor,
+    this._buildMarkup.bind(this));
+
   /**
    * Optionally customize each region's fill color by adding an entry to the
    * regionFill property: `highlighter.regionFill.margin = "red";
    */
   this.regionFill = {};
 
   this._currentNode = null;
+
+  // Caches the last queried box quads
+  this._currentQuads = {};
 }
 
-BoxModelHighlighter.prototype = Heritage.extend(XULBasedHighlighter.prototype, {
+BoxModelHighlighter.prototype = Heritage.extend(AutoRefreshHighlighter.prototype, {
+  ID_CLASS_PREFIX: "box-model-",
+
   get zoom() {
     return this.win.QueryInterface(Ci.nsIInterfaceRequestor)
                .getInterface(Ci.nsIDOMWindowUtils).fullZoom;
   },
 
   get currentNode() {
     return this._currentNode;
   },
 
   set currentNode(node) {
     this._currentNode = node;
     this._computedStyle = null;
   },
 
-  _initMarkup: function() {
-    let stack = this.browser.parentNode;
+  _buildMarkup: function() {
+    let doc = this.win.document;
 
-    this._highlighterContainer = this.chromeDoc.createElement("stack");
-    this._highlighterContainer.className = "highlighter-container";
+    let highlighterContainer = doc.createElement("div");
+    highlighterContainer.className = "highlighter-container";
 
-    this._svgRoot = this._createSVGNode("root", "svg", this._highlighterContainer);
+    // Building the SVG element with its polygons and lines
 
-    // Set the SVG canvas height to 0 to stop content jumping around on small
-    // screens.
-    this._svgRoot.setAttribute("height", "0");
-
-    this._boxModelContainer = this._createSVGNode("container", "g", this._svgRoot);
-
-    this._boxModelNodes = {
-      margin: this._createSVGNode("margin", "polygon", this._boxModelContainer),
-      border: this._createSVGNode("border", "polygon", this._boxModelContainer),
-      padding: this._createSVGNode("padding", "polygon", this._boxModelContainer),
-      content: this._createSVGNode("content", "polygon", this._boxModelContainer)
-    };
+    let svgRoot = this._createSVGNode("svg", highlighterContainer, {
+      "id": "root",
+      "class": "root",
+      "width": "100%",
+      "height": "100%",
+      "style": "width:100%;height:100%;",
+      "hidden": "true"
+    });
 
-    this._guideNodes = {
-      top: this._createSVGNode("guide-top", "line", this._svgRoot),
-      right: this._createSVGNode("guide-right", "line", this._svgRoot),
-      bottom: this._createSVGNode("guide-bottom", "line", this._svgRoot),
-      left: this._createSVGNode("guide-left", "line", this._svgRoot)
-    };
-
-    this._guideNodes.top.setAttribute("stroke-width", GUIDE_STROKE_WIDTH);
-    this._guideNodes.right.setAttribute("stroke-width", GUIDE_STROKE_WIDTH);
-    this._guideNodes.bottom.setAttribute("stroke-width", GUIDE_STROKE_WIDTH);
-    this._guideNodes.left.setAttribute("stroke-width", GUIDE_STROKE_WIDTH);
-
-    this._highlighterContainer.appendChild(this._svgRoot);
+    let boxModelContainer = this._createSVGNode("g", svgRoot, {
+      "class": "container"
+    });
 
-    let infobarContainer = this.chromeDoc.createElement("box");
-    infobarContainer.className = "highlighter-nodeinfobar-container";
-    this._highlighterContainer.appendChild(infobarContainer);
-
-    // Insert the highlighter right after the browser
-    stack.insertBefore(this._highlighterContainer, stack.childNodes[1]);
+    for (let region of BOX_MODEL_REGIONS) {
+      this._createSVGNode("polygon", boxModelContainer, {
+        "class": region,
+        "id": region
+      });
+    }
 
-    // Building the infobar
-    let infobarPositioner = this.chromeDoc.createElement("box");
-    infobarPositioner.className = "highlighter-nodeinfobar-positioner";
-    infobarPositioner.setAttribute("position", "top");
-    infobarPositioner.setAttribute("disabled", "true");
+    for (let side of BOX_MODEL_SIDES) {
+      this._createSVGNode("line", svgRoot, {
+        "class": "guide-" + side,
+        "id": "guide-" + side,
+        "stroke-width": GUIDE_STROKE_WIDTH
+      });
+    }
 
-    let nodeInfobar = this.chromeDoc.createElement("hbox");
-    nodeInfobar.className = "highlighter-nodeinfobar";
+    highlighterContainer.appendChild(svgRoot);
 
-    let arrowBoxTop = this.chromeDoc.createElement("box");
-    arrowBoxTop.className = "highlighter-nodeinfobar-arrow highlighter-nodeinfobar-arrow-top";
-
-    let arrowBoxBottom = this.chromeDoc.createElement("box");
-    arrowBoxBottom.className = "highlighter-nodeinfobar-arrow highlighter-nodeinfobar-arrow-bottom";
-
-    let tagNameLabel = this.chromeDoc.createElementNS(XHTML_NS, "span");
-    tagNameLabel.className = "highlighter-nodeinfobar-tagname";
-
-    let idLabel = this.chromeDoc.createElementNS(XHTML_NS, "span");
-    idLabel.className = "highlighter-nodeinfobar-id";
+    // Building the nodeinfo bar markup
 
-    let classesBox = this.chromeDoc.createElementNS(XHTML_NS, "span");
-    classesBox.className = "highlighter-nodeinfobar-classes";
-
-    let pseudoClassesBox = this.chromeDoc.createElementNS(XHTML_NS, "span");
-    pseudoClassesBox.className = "highlighter-nodeinfobar-pseudo-classes";
+    let infobarContainer = this._createNode("div", highlighterContainer, {
+      "class": "nodeinfobar-container",
+      "id": "nodeinfobar-container",
+      "position": "top",
+      "hidden": "true"
+    });
 
-    let dimensionBox = this.chromeDoc.createElementNS(XHTML_NS, "span");
-    dimensionBox.className = "highlighter-nodeinfobar-dimensions";
-
-    // Add some content to force a better boundingClientRect
-    pseudoClassesBox.textContent = "&nbsp;";
+    let nodeInfobar = this._createNode("div", infobarContainer, {
+      "class": "nodeinfobar"
+    });
 
-    // <hbox class="highlighter-nodeinfobar-text"/>
-    let texthbox = this.chromeDoc.createElement("hbox");
-    texthbox.className = "highlighter-nodeinfobar-text";
-    texthbox.setAttribute("align", "center");
-    texthbox.setAttribute("flex", "1");
-
-    texthbox.appendChild(tagNameLabel);
-    texthbox.appendChild(idLabel);
-    texthbox.appendChild(classesBox);
-    texthbox.appendChild(pseudoClassesBox);
-    texthbox.appendChild(dimensionBox);
-
-    nodeInfobar.appendChild(texthbox);
+    let texthbox = this._createNode("div", nodeInfobar, {
+      "class": "nodeinfobar-text"
+    });
+    this._createNode("span", texthbox, {
+      "class": "nodeinfobar-tagname",
+      "id": "nodeinfobar-tagname"
+    });
+    this._createNode("span", texthbox, {
+      "class": "nodeinfobar-id",
+      "id": "nodeinfobar-id"
+    });
+    this._createNode("span", texthbox, {
+      "class": "nodeinfobar-classes",
+      "id": "nodeinfobar-classes"
+    });
+    this._createNode("span", texthbox, {
+      "class": "nodeinfobar-pseudo-classes",
+      "id": "nodeinfobar-pseudo-classes"
+    });
+    this._createNode("span", texthbox, {
+      "class": "nodeinfobar-dimensions",
+      "id": "nodeinfobar-dimensions"
+    });
 
-    infobarPositioner.appendChild(arrowBoxTop);
-    infobarPositioner.appendChild(nodeInfobar);
-    infobarPositioner.appendChild(arrowBoxBottom);
-
-    infobarContainer.appendChild(infobarPositioner);
-
-    let barHeight = infobarPositioner.getBoundingClientRect().height;
-
-    this.nodeInfo = {
-      tagNameLabel: tagNameLabel,
-      idLabel: idLabel,
-      classesBox: classesBox,
-      pseudoClassesBox: pseudoClassesBox,
-      dimensionBox: dimensionBox,
-      positioner: infobarPositioner,
-      barHeight: barHeight,
-    };
+    return highlighterContainer;
   },
 
-  _createSVGNode: function(classPostfix, nodeType, parent) {
-    let node = this.chromeDoc.createElementNS(SVG_NS, nodeType);
-    node.setAttribute("class", "box-model-" + classPostfix);
+  _createSVGNode: function(nodeType, parent, attributes={}) {
+    return this._createNode(nodeType, parent, attributes, SVG_NS);
+  },
+
+  _createNode: function(nodeType, parent, attributes={}, namespace=null) {
+    let node;
+    if (namespace) {
+      node = this.win.document.createElementNS(namespace, nodeType);
+    } else {
+      node = this.win.document.createElement(nodeType);
+    }
+
+    for (let name in attributes) {
+      let value = attributes[name];
+      if (name === "class" || name === "id") {
+        value = this.ID_CLASS_PREFIX + value
+      }
+      node.setAttribute(name, value);
+    }
 
     parent.appendChild(node);
-
     return node;
   },
 
   /**
    * Destroy the nodes. Remove listeners.
    */
   destroy: function() {
-    XULBasedHighlighter.prototype.destroy.call(this);
+    AutoRefreshHighlighter.prototype.destroy.call(this);
 
-    this._highlighterContainer.remove();
-    this._highlighterContainer = null;
+    this.markup.destroy();
 
-    this.nodeInfo = null;
     this._currentNode = null;
   },
 
   /**
    * Show the highlighter on a given node. We override this method so that the
    * same node can be rehighlighted e.g. to highlight different regions from the
    * layout view.
    *
@@ -674,17 +814,17 @@ BoxModelHighlighter.prototype = Heritage
    */
   show: function(node, options={}) {
     if (!isNodeValid(node)) {
       return;
     }
 
     this.options = options;
 
-    if (!this.options.region) {
+    if (BOX_MODEL_REGIONS.indexOf(this.options.region) == -1)  {
       this.options.region = "content";
     }
 
     this._detachPageListeners();
     this.currentNode = node;
     this._attachPageListeners();
     this._show();
   },
@@ -742,75 +882,80 @@ BoxModelHighlighter.prototype = Heritage
     this._hideBoxModel();
     this._hideInfobar();
   },
 
   /**
    * Hide the infobar
    */
   _hideInfobar: function() {
-    this.nodeInfo.positioner.setAttribute("hidden", "true");
+    this.markup.setAttributeForElement(
+      this.ID_CLASS_PREFIX + "nodeinfobar-container", "hidden", "true");
   },
 
   /**
    * Show the infobar
    */
   _showInfobar: function() {
-    this.nodeInfo.positioner.removeAttribute("hidden");
+    this.markup.removeAttributeForElement(
+      this.ID_CLASS_PREFIX + "nodeinfobar-container", "hidden");
     this._updateInfobar();
   },
 
   /**
    * Hide the box model
    */
   _hideBoxModel: function() {
-    this._svgRoot.setAttribute("hidden", "true");
+    this.markup.setAttributeForElement(this.ID_CLASS_PREFIX + "root", "hidden",
+      "true");
   },
 
   /**
    * Show the box model
    */
   _showBoxModel: function() {
-    this._svgRoot.removeAttribute("hidden");
+    this.markup.removeAttributeForElement(this.ID_CLASS_PREFIX + "root",
+      "hidden");
   },
 
   /**
    * Update the box model as per the current node.
    *
    * @return {boolean}
    *         True if the current node has a box model to be highlighted
    */
   _updateBoxModel: function() {
     this.options.region = this.options.region || "content";
 
     if (this._nodeNeedsHighlighting()) {
-      for (let boxType in this._boxModelNodes) {
+      for (let boxType of BOX_MODEL_REGIONS) {
 
-        let quads = this.layoutHelpers.getAdjustedQuads(this.currentNode, boxType);
-        if (!quads) {
+        this._currentQuads[boxType] = this.layoutHelpers.getAdjustedQuads(
+          this.currentNode, boxType);
+        if (!this._currentQuads[boxType]) {
           continue;
         }
-        let {p1, p2, p3, p4} = quads;
-
-        let boxNode = this._boxModelNodes[boxType];
+        let {p1, p2, p3, p4} = this._currentQuads[boxType];
 
         if (this.regionFill[boxType]) {
-          boxNode.setAttribute("style", "fill:" + this.regionFill[boxType]);
+          this.markup.setAttributeForElement(this.ID_CLASS_PREFIX + boxType,
+            "style", "fill:" + this.regionFill[boxType]);
         } else {
-          boxNode.removeAttribute("style");
+          this.markup.setAttributeForElement(this.ID_CLASS_PREFIX + boxType,
+            "style", "");
         }
 
         if (!this.options.showOnly || this.options.showOnly === boxType) {
-          boxNode.setAttribute("points",
-                               p1.x + "," + p1.y + " " +
-                               p2.x + "," + p2.y + " " +
-                               p3.x + "," + p3.y + " " +
-                               p4.x + "," + p4.y);
+          this.markup.setAttributeForElement(this.ID_CLASS_PREFIX + boxType,
+            "points", p1.x + "," + p1.y + " " +
+                      p2.x + "," + p2.y + " " +
+                      p3.x + "," + p3.y + " " +
+                      p4.x + "," + p4.y);
         } else {
-          boxNode.setAttribute("points", "");
+          this.markup.removeAttributeForElement(this.ID_CLASS_PREFIX + boxType, "points");
         }
 
         if (boxType === this.options.region && !this.options.hideGuides) {
           this._showGuides(p1, p2, p3, p4);
         } else if (this.options.hideGuides) {
           this._hideGuides();
         }
       }
@@ -835,44 +980,39 @@ BoxModelHighlighter.prototype = Heritage
       this._computedStyle = CssLogic.getComputedStyle(this.currentNode);
     }
 
     return this._computedStyle.getPropertyValue("display") !== "none";
   },
 
   _getOuterBounds: function() {
     for (let region of ["margin", "border", "padding", "content"]) {
-      let quads = this.layoutHelpers.getAdjustedQuads(this.currentNode, region);
+      let quads = this._currentQuads[region];
 
       if (!quads) {
         // Invisible element such as a script tag.
         break;
       }
 
       let {bottom, height, left, right, top, width, x, y} = quads.bounds;
 
       if (width > 0 || height > 0) {
-        return this._boundsHelper(bottom, height, left, right, top, width, x, y);
+        return {bottom, height, left, right, top, width, x, y};
       }
     }
 
-    return this._boundsHelper();
-  },
-
-  _boundsHelper: function(bottom=0, height=0, left=0, right=0,
-                          top=0, width=0, x=0, y=0) {
     return {
-      bottom: bottom,
-      height: height,
-      left: left,
-      right: right,
-      top: top,
-      width: width,
-      x: x,
-      y: y
+      bottom: 0,
+      height: 0,
+      left: 0,
+      right: 0,
+      top: 0,
+      width: 0,
+      x: 0,
+      y: 0
     };
   },
 
   /**
    * We only want to show guides for horizontal and vertical edges as this helps
    * to line them up. This method finds these edges and displays a guide there.
    *
    * @param  {DOMPoint} p1
@@ -897,262 +1037,260 @@ BoxModelHighlighter.prototype = Heritage
             toShowY.push(val);
           }
           arr.splice(arr.lastIndexOf(val), 1);
         }
       }
     }
 
     // Move guide into place or hide it if no valid co-ordinate was found.
-    this._updateGuide(this._guideNodes.top, toShowY[0]);
-    this._updateGuide(this._guideNodes.right, toShowX[1]);
-    this._updateGuide(this._guideNodes.bottom, toShowY[1]);
-    this._updateGuide(this._guideNodes.left, toShowX[0]);
+    this._updateGuide("top", toShowY[0]);
+    this._updateGuide("right", toShowX[1]);
+    this._updateGuide("bottom", toShowY[1]);
+    this._updateGuide("left", toShowX[0]);
   },
 
   _hideGuides: function() {
-    for (let side in this._guideNodes) {
-      this._guideNodes[side].setAttribute("hidden", "true");
+    for (let side of BOX_MODEL_SIDES) {
+      this.markup.setAttributeForElement(
+        this.ID_CLASS_PREFIX + "guide-" + side, "hidden", "true");
     }
   },
 
   /**
    * Move a guide to the appropriate position and display it. If no point is
    * passed then the guide is hidden.
    *
-   * @param  {SVGLine} guide
+   * @param  {String} side
    *         The guide to update
    * @param  {Integer} point
    *         x or y co-ordinate. If this is undefined we hide the guide.
    */
-  _updateGuide: function(guide, point=-1) {
+  _updateGuide: function(side, point=-1) {
+    let guideId = this.ID_CLASS_PREFIX + "guide-" + side;
+
     if (point <= 0) {
-      guide.setAttribute("hidden", "true");
+      this.markup.setAttributeForElement(guideId, "hidden", "true");
       return false;
     }
 
     let offset = GUIDE_STROKE_WIDTH / 2;
 
-    if (guide === this._guideNodes.top || guide === this._guideNodes.left) {
+    if (side === "top" || side === "left") {
       point -= offset;
     } else {
       point += offset;
     }
 
-    if (guide === this._guideNodes.top || guide === this._guideNodes.bottom) {
-      guide.setAttribute("x1", 0);
-      guide.setAttribute("y1", point);
-      guide.setAttribute("x2", "100%");
-      guide.setAttribute("y2", point);
+    if (side === "top" || side === "bottom") {
+      this.markup.setAttributeForElement(guideId, "x1", "0");
+      this.markup.setAttributeForElement(guideId, "y1", point + "");
+      this.markup.setAttributeForElement(guideId, "x2", "100%");
+      this.markup.setAttributeForElement(guideId, "y2", point + "");
     } else {
-      guide.setAttribute("x1", point);
-      guide.setAttribute("y1", 0);
-      guide.setAttribute("x2", point);
-      guide.setAttribute("y2", "100%");
+      this.markup.setAttributeForElement(guideId, "x1", point + "");
+      this.markup.setAttributeForElement(guideId, "y1", "0");
+      this.markup.setAttributeForElement(guideId, "x2", point + "");
+      this.markup.setAttributeForElement(guideId, "y2", "100%");
     }
-    guide.removeAttribute("hidden");
+
+    this.markup.removeAttributeForElement(guideId, "hidden");
 
     return true;
   },
 
   /**
    * Update node information (tagName#id.class)
    */
   _updateInfobar: function() {
     if (!this.currentNode) {
       return;
     }
 
-    let info = this.nodeInfo;
-
     let {bindingElement:node, pseudo} =
       CssLogic.getBindingElementAndPseudo(this.currentNode);
 
-    // Update the tag, id, classes, pseudo-classes and dimensions only if they
-    // changed to avoid triggering paint events
+    // Update the tag, id, classes, pseudo-classes and dimensions
     let tagName = node.tagName;
-    if (info.tagNameLabel.textContent !== tagName) {
-      info.tagNameLabel.textContent = tagName;
-    }
 
     let id = node.id ? "#" + node.id : "";
-    if (info.idLabel.textContent !== id) {
-      info.idLabel.textContent = id;
-    }
 
     let classList = (node.classList || []).length ? "." + [...node.classList].join(".") : "";
-    if (info.classesBox.textContent !== classList) {
-      info.classesBox.textContent = classList;
-    }
 
     let pseudos = PSEUDO_CLASSES.filter(pseudo => {
       return DOMUtils.hasPseudoClassLock(node, pseudo);
     }, this).join("");
-
     if (pseudo) {
       // Display :after as ::after
       pseudos += ":" + pseudo;
     }
 
-    if (info.pseudoClassesBox.textContent !== pseudos) {
-      info.pseudoClassesBox.textContent = pseudos;
-    }
-
     let rect = node.getBoundingClientRect();
     let dim = Math.ceil(rect.width) + " x " + Math.ceil(rect.height);
-    if (info.dimensionBox.textContent !== dim) {
-      info.dimensionBox.textContent = dim;
-    }
+
+    let elementId = this.ID_CLASS_PREFIX + "nodeinfobar-";
+    this.markup.setTextContentForElement(elementId + "tagname", tagName);
+    this.markup.setTextContentForElement(elementId + "id", id);
+    this.markup.setTextContentForElement(elementId + "classes", classList);
+    this.markup.setTextContentForElement(elementId + "pseudo-classes", pseudos);
+    this.markup.setTextContentForElement(elementId + "dimensions", dim);
 
     this._moveInfobar();
   },
 
   /**
    * Move the Infobar to the right place in the highlighter.
    */
   _moveInfobar: function() {
     let bounds = this._getOuterBounds();
     let winHeight = this.win.innerHeight * this.zoom;
     let winWidth = this.win.innerWidth * this.zoom;
 
-    // Ensure that positionerBottom and positionerTop are at least zero to avoid
+    // Ensure that containerBottom and containerTop are at least zero to avoid
     // showing tooltips outside the viewport.
-    let positionerBottom = Math.max(0, bounds.bottom);
-    let positionerTop = Math.max(0, bounds.top);
-
-    // Avoid showing the nodeInfoBar on top of the findbar or awesomebar.
-    if (this.chromeDoc.defaultView.gBrowser) {
-      // Get the y co-ordinate of the top of the viewport
-      let viewportTop = this.browser.getBoundingClientRect().top;
+    let containerBottom = Math.max(0, bounds.bottom) + NODE_INFOBAR_ARROW_SIZE;
+    let containerTop = Math.min(winHeight, bounds.top);
+    let containerId = this.ID_CLASS_PREFIX + "nodeinfobar-container";
 
-      // Get the offset to the top of the findbar
-      let findbar = this.chromeDoc.defaultView.gBrowser.getFindBar();
-      let findTop = findbar.getBoundingClientRect().top - viewportTop;
-
-      // Either show the positioner where it is or move it above the findbar.
-      positionerTop = Math.min(positionerTop, findTop);
-    }
-
-    this.nodeInfo.positioner.removeAttribute("disabled");
     // Can the bar be above the node?
-    if (positionerTop < this.nodeInfo.barHeight) {
-      // No. Can we move the toolbar under the node?
-      if (positionerBottom + this.nodeInfo.barHeight > winHeight) {
+    let top;
+    if (containerTop < NODE_INFOBAR_HEIGHT) {
+      // No. Can we move the bar under the node?
+      if (containerBottom + NODE_INFOBAR_HEIGHT > winHeight) {
         // No. Let's move it inside.
-        this.nodeInfo.positioner.style.top = positionerTop + "px";
-        this.nodeInfo.positioner.setAttribute("position", "overlap");
+        top = containerTop;
+        this.markup.setAttributeForElement(containerId, "position", "overlap");
       } else {
         // Yes. Let's move it under the node.
-        this.nodeInfo.positioner.style.top = positionerBottom - INFO_BAR_OFFSET + "px";
-        this.nodeInfo.positioner.setAttribute("position", "bottom");
+        top = containerBottom;
+        this.markup.setAttributeForElement(containerId, "position", "bottom");
       }
     } else {
       // Yes. Let's move it on top of the node.
-      this.nodeInfo.positioner.style.top =
-        positionerTop + INFO_BAR_OFFSET - this.nodeInfo.barHeight + "px";
-      this.nodeInfo.positioner.setAttribute("position", "top");
+      top = containerTop - NODE_INFOBAR_HEIGHT;
+      this.markup.setAttributeForElement(containerId, "position", "top");
     }
 
-    let barWidth = this.nodeInfo.positioner.getBoundingClientRect().width;
-    let left = bounds.right - bounds.width / 2 - barWidth / 2;
-
-    // Make sure the whole infobar is visible
-    if (left < 0) {
-      left = 0;
-      this.nodeInfo.positioner.setAttribute("hide-arrow", "true");
+    // Align the bar with the box's center if possible.
+    let left = bounds.right - bounds.width / 2;
+    // Make sure the while infobar is visible.
+    let buffer = 100;
+    if (left < buffer) {
+      left = buffer;
+      this.markup.setAttributeForElement(containerId, "hide-arrow", "true");
+    } else if (left > winWidth - buffer) {
+      left = winWidth - buffer;
+      this.markup.setAttributeForElement(containerId, "hide-arrow", "true");
     } else {
-      if (left + barWidth > winWidth) {
-        left = winWidth - barWidth;
-        this.nodeInfo.positioner.setAttribute("hide-arrow", "true");
-      } else {
-        this.nodeInfo.positioner.removeAttribute("hide-arrow");
-      }
+      this.markup.removeAttributeForElement(containerId, "hide-arrow");
     }
-    this.nodeInfo.positioner.style.left = left + "px";
+
+    let style = "top:" + top + "px;left:" + left + "px;";
+    this.markup.setAttributeForElement(containerId, "style", style);
   }
 });
 
 /**
  * The CssTransformHighlighter is the class that draws an outline around a
  * transformed element and an outline around where it would be if untransformed
  * as well as arrows connecting the 2 outlines' corners.
  */
 function CssTransformHighlighter(tabActor) {
-  XULBasedHighlighter.call(this, tabActor);
+  AutoRefreshHighlighter.call(this, tabActor);
 
   this.layoutHelpers = new LayoutHelpers(tabActor.window);
-  this._initMarkup();
+
+  this.markup = new CanvasFrameAnonymousContentHelper(this.tabActor,
+    this._buildMarkup.bind(this));
 }
 
 let MARKER_COUNTER = 1;
 
-CssTransformHighlighter.prototype = Heritage.extend(XULBasedHighlighter.prototype, {
-  _initMarkup: function() {
-    let stack = this.browser.parentNode;
+CssTransformHighlighter.prototype = Heritage.extend(AutoRefreshHighlighter.prototype, {
+  ID_CLASS_PREFIX: "css-transform-",
+
+  _buildMarkup: function() {
+    let doc = this.win.document;
 
-    this._container = this.chromeDoc.createElement("stack");
-    this._container.className = "highlighter-container";
+    let container = doc.createElement("div");
+    container.className = "highlighter-container";
 
-    this._svgRoot = this._createSVGNode("root", "svg", this._container);
-    this._svgRoot.setAttribute("hidden", "true");
+    let svgRoot = this._createSVGNode("svg", container, {
+      "class": "root",
+      "id": "root",
+      "hidden": "true",
+      "width": "100%",
+      "height": "100%"
+    });
 
     // Add a marker tag to the svg root for the arrow tip
-    let marker = this.chromeDoc.createElementNS(SVG_NS, "marker");
-    this.markerId = "css-transform-arrow-marker-" + MARKER_COUNTER;
+    this.markerId = "arrow-marker-" + MARKER_COUNTER;
     MARKER_COUNTER ++;
-    marker.setAttribute("id", this.markerId);
-    marker.setAttribute("markerWidth", "10");
-    marker.setAttribute("markerHeight", "5");
-    marker.setAttribute("orient", "auto");
-    marker.setAttribute("markerUnits", "strokeWidth");
-    marker.setAttribute("refX", "10");
-    marker.setAttribute("refY", "5");
-    marker.setAttribute("viewBox", "0 0 10 10");
-    let path = this.chromeDoc.createElementNS(SVG_NS, "path");
-    path.setAttribute("d", "M 0 0 L 10 5 L 0 10 z");
-    path.setAttribute("fill", "#08C");
-    marker.appendChild(path);
-    this._svgRoot.appendChild(marker);
+    let marker = this._createSVGNode("marker", svgRoot, {
+      "id": this.markerId,
+      "markerWidth": "10",
+      "markerHeight": "5",
+      "orient": "auto",
+      "markerUnits": "strokeWidth",
+      "refX": "10",
+      "refY": "5",
+      "viewBox": "0 0 10 10",
+    });
+    this._createSVGNode("path", marker, {
+      "d": "M 0 0 L 10 5 L 0 10 z",
+      "fill": "#08C"
+    });
+
+    let shapesGroup = this._createSVGNode("g", svgRoot);
 
     // Create the 2 polygons (transformed and untransformed)
-    let shapesGroup = this._createSVGNode("container", "g", this._svgRoot);
-    this._shapes = {
-      untransformed: this._createSVGNode("untransformed", "polygon", shapesGroup),
-      transformed: this._createSVGNode("transformed", "polygon", shapesGroup)
-    };
+    this._createSVGNode("polygon", shapesGroup, {
+      "id": "untransformed",
+      "class": "untransformed"
+    });
+    this._createSVGNode("polygon", shapesGroup, {
+      "id": "transformed",
+      "class": "transformed"
+    });
 
     // Create the arrows
     for (let nb of ["1", "2", "3", "4"]) {
-      let line = this._createSVGNode("line", "line", shapesGroup);
-      line.setAttribute("marker-end", "url(#" + this.markerId + ")");
-      this._shapes["line" + nb] = line;
+      this._createSVGNode("line", shapesGroup, {
+        "id": "line" + nb,
+        "class": "line",
+        "marker-end": "url(#" + this.markerId + ")"
+      });
     }
 
-    this._container.appendChild(this._svgRoot);
+    container.appendChild(svgRoot);
 
-    // Insert the highlighter right after the browser
-    stack.insertBefore(this._container, stack.childNodes[1]);
+    return container;
   },
 
-  _createSVGNode: function(classPostfix, nodeType, parent) {
-    let node = this.chromeDoc.createElementNS(SVG_NS, nodeType);
-    node.setAttribute("class", "css-transform-" + classPostfix);
+  _createSVGNode: function(nodeType, parent, attributes={}) {
+    let node = this.win.document.createElementNS(SVG_NS, nodeType);
+
+    for (let name in attributes) {
+      let value = attributes[name];
+      if (name === "class" || name === "id") {
+        value = this.ID_CLASS_PREFIX + value
+      }
+      node.setAttribute(name, value);
+    }
 
     parent.appendChild(node);
     return node;
   },
 
   /**
    * Destroy the nodes. Remove listeners.
    */
   destroy: function() {
-    XULBasedHighlighter.prototype.destroy.call(this);
-
-    this._container.remove();
-    this._container = null;
+    AutoRefreshHighlighter.prototype.destroy.call(this);
+    this.markup.destroy();
   },
 
   /**
    * Show the highlighter on a given node
    * @param {DOMNode} node
    */
   _show: function() {
     if (!this._isTransformed(this.currentNode)) {
@@ -1166,35 +1304,38 @@ CssTransformHighlighter.prototype = Heri
   /**
    * Checks if the supplied node is transformed and not inline
    */
   _isTransformed: function(node) {
     let style = CssLogic.getComputedStyle(node);
     return style && (style.transform !== "none" && style.display !== "inline");
   },
 
-  _setPolygonPoints: function(quad, poly) {
+  _setPolygonPoints: function(quad, id) {
     let points = [];
     for (let point of ["p1","p2", "p3", "p4"]) {
       points.push(quad[point].x + "," + quad[point].y);
     }
-    poly.setAttribute("points", points.join(" "));
+    this.markup.setAttributeForElement(this.ID_CLASS_PREFIX + id,
+                                       "points",
+                                       points.join(" "));
   },
 
-  _setLinePoints: function(p1, p2, line) {
-    line.setAttribute("x1", p1.x);
-    line.setAttribute("y1", p1.y);
-    line.setAttribute("x2", p2.x);
-    line.setAttribute("y2", p2.y);
+  _setLinePoints: function(p1, p2, id) {
+    this.markup.setAttributeForElement(this.ID_CLASS_PREFIX + id, "x1", p1.x);
+    this.markup.setAttributeForElement(this.ID_CLASS_PREFIX + id, "y1", p1.y);
+    this.markup.setAttributeForElement(this.ID_CLASS_PREFIX + id, "x2", p2.x);
+    this.markup.setAttributeForElement(this.ID_CLASS_PREFIX + id, "y2", p2.y);
 
     let dist = Math.sqrt(Math.pow(p2.x - p1.x, 2) + Math.pow(p2.y - p1.y, 2));
     if (dist < ARROW_LINE_MIN_DISTANCE) {
-      line.removeAttribute("marker-end");
+      this.markup.removeAttributeForElement(this.ID_CLASS_PREFIX + id, "marker-end");
     } else {
-      line.setAttribute("marker-end", "url(#" + this.markerId + ")");
+      this.markup.setAttributeForElement(this.ID_CLASS_PREFIX + id, "marker-end",
+                                         "url(#" + this.markerId + ")");
     }
   },
 
   /**
    * Update the highlighter on the current highlighted node (the one that was
    * passed as an argument to show(node)).
    * Should be called whenever node size or attributes change
    */
@@ -1204,39 +1345,38 @@ CssTransformHighlighter.prototype = Heri
     if (!quad || quad.bounds.width <= 0 || quad.bounds.height <= 0) {
       this._hideShapes();
       return null;
     }
 
     // Getting the points for the untransformed shape
     let untransformedQuad = this.layoutHelpers.getNodeBounds(this.currentNode);
 
-    this._setPolygonPoints(quad, this._shapes.transformed);
-    this._setPolygonPoints(untransformedQuad, this._shapes.untransformed);
+    this._setPolygonPoints(quad, "transformed");
+    this._setPolygonPoints(untransformedQuad, "untransformed");
     for (let nb of ["1", "2", "3", "4"]) {
-      this._setLinePoints(untransformedQuad["p" + nb], quad["p" + nb],
-        this._shapes["line" + nb]);
+      this._setLinePoints(untransformedQuad["p" + nb], quad["p" + nb], "line" + nb);
     }
 
     this._showShapes();
   },
 
   /**
    * Hide the highlighter, the outline and the infobar.
    */
   _hide: function() {
     this._hideShapes();
   },
 
   _hideShapes: function() {
-    this._svgRoot.setAttribute("hidden", "true");
+    this.markup.setAttributeForElement(this.ID_CLASS_PREFIX + "root", "hidden", "true");
   },
 
   _showShapes: function() {
-    this._svgRoot.removeAttribute("hidden");
+    this.markup.removeAttributeForElement(this.ID_CLASS_PREFIX + "root", "hidden");
   }
 });
 
 /**
  * The SelectorHighlighter runs a given selector through querySelectorAll on the
  * document of the provided context node and then uses the BoxModelHighlighter
  * to highlight the matching nodes
  */
@@ -1296,89 +1436,57 @@ SelectorHighlighter.prototype = {
     this.hide();
     this.tabActor = null;
   }
 };
 
 /**
  * The SimpleOutlineHighlighter is a class that has the same API than the
  * BoxModelHighlighter, but adds a pseudo-class on the target element itself
- * to draw a simple outline.
- * It is used by the HighlighterActor too, but in case the more complex
- * BoxModelHighlighter can't be attached (which is the case for FirefoxOS and
- * Fennec targets for instance).
+ * to draw a simple css outline around the element.
+ * It is used by the HighlighterActor when canvasframe-based highlighters can't
+ * be used. This is the case for XUL windows.
  */
 function SimpleOutlineHighlighter(tabActor) {
   this.chromeDoc = tabActor.window.document;
 }
 
 SimpleOutlineHighlighter.prototype = {
   /**
    * Destroy the nodes. Remove listeners.
    */
   destroy: function() {
     this.hide();
-    if (this.installedHelpers) {
-      this.installedHelpers.clear();
-    }
     this.chromeDoc = null;
   },
 
-  _installHelperSheet: function(node) {
-    if (!this.installedHelpers) {
-      this.installedHelpers = new WeakMap;
-    }
-    let win = node.ownerDocument.defaultView;
-    if (!this.installedHelpers.has(win)) {
-      let {Style} = require("sdk/stylesheet/style");
-      let {attach} = require("sdk/content/mod");
-      let style = Style({source: HELPER_SHEET, type: "agent"});
-      attach(style, win);
-      this.installedHelpers.set(win, style);
-    }
-  },
-
   /**
    * Show the highlighter on a given node
    * @param {DOMNode} node
    */
   show: function(node) {
     if (!this.currentNode || node !== this.currentNode) {
       this.hide();
       this.currentNode = node;
-      this._installHelperSheet(node);
+      installHelperSheet(node.ownerDocument.defaultView, SIMPLE_OUTLINE_SHEET);
       DOMUtils.addPseudoClassLock(node, HIGHLIGHTED_PSEUDO_CLASS);
     }
   },
 
   /**
    * Hide the highlighter, the outline and the infobar.
    */
   hide: function() {
     if (this.currentNode) {
       DOMUtils.removePseudoClassLock(this.currentNode, HIGHLIGHTED_PSEUDO_CLASS);
       this.currentNode = null;
     }
   }
 };
 
-/**
- * Can the host support the XUL-based highlighters which require a parent
- * XUL node to get attached.
- * @param {TabActor}
- * @return {Boolean}
- */
-function supportXULBasedHighlighter(tabActor) {
-  // Note that <browser>s on Fennec also have a XUL parentNode but the box
-  // model highlighter doesn't display correctly on Fennec (bug 993190)
-  return tabActor.browser &&
-         !!tabActor.browser.parentNode &&
-         Services.appinfo.ID !== "{aa3c5121-dab2-40e2-81ca-7ea25febc110}";
-}
-
 function isNodeValid(node) {
   // Is it null or dead?
   if(!node || Cu.isDeadWrapper(node)) {
     return false;
   }
 
   // Is it an element node
   if (node.nodeType !== Ci.nsIDOMNode.ELEMENT_NODE) {
@@ -1396,11 +1504,33 @@ function isNodeValid(node) {
   let bindingParent = LayoutHelpers.getRootBindingParent(node);
   if (!doc.documentElement.contains(bindingParent)) {
     return false;
   }
 
   return true;
 }
 
+/**
+ * Inject a helper stylesheet in the window.
+ */
+let installedHelperSheets = new WeakMap;
+function installHelperSheet(win, source, type="agent") {
+  if (installedHelperSheets.has(win.document)) {
+    return;
+  }
+  let {Style} = require("sdk/stylesheet/style");
+  let {attach} = require("sdk/content/mod");
+  let style = Style({source, type});
+  attach(style, win);
+  installedHelperSheets.set(win.document, style);
+}
+
+/**
+ * Is the content window in this tabActor a XUL window
+ */
+function isXUL(tabActor) {
+  return tabActor.window.document.documentElement.namespaceURI === XUL_NS;
+}
+
 XPCOMUtils.defineLazyGetter(this, "DOMUtils", function () {
   return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils)
 });