Merge fx-team to m-c
authorWes Kocher <wkocher@mozilla.com>
Thu, 09 Jan 2014 16:50:03 -0800
changeset 162816 37516445a0b58a662a5fcd8734db0b1c8b633be9
parent 162768 f12b645b0890f151472951a7736e988676f5abb2 (current diff)
parent 162815 204056ecab571a5554db9757fb45f7a9b8dea193 (diff)
child 162817 30f3710477c2f3cf6d939146a6950801341d6d09
child 162832 2ae64c9c496f72df0bc08715fcf09e76104ce7be
child 162878 dfb64dc87f11894142c9d1b5e811c6408198c58a
child 162904 fb5e82e717088d07490213376a2eaa3b47e9d153
push id25971
push userkwierso@gmail.com
push dateFri, 10 Jan 2014 00:50:13 +0000
treeherderautoland@37516445a0b5 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone29.0a1
first release with
nightly linux32
37516445a0b5 / 29.0a1 / 20140110030650 / files
nightly linux64
37516445a0b5 / 29.0a1 / 20140110030650 / files
nightly mac
37516445a0b5 / 29.0a1 / 20140110030650 / files
nightly win32
37516445a0b5 / 29.0a1 / 20140110030650 / files
nightly win64
37516445a0b5 / 29.0a1 / 20140110030650 / 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
CLOBBER
browser/devtools/inspector/highlighter.js
browser/devtools/inspector/selection.js
browser/devtools/inspector/test/browser_inspector_bug_835722_infobar_reappears.js
browser/devtools/inspector/test/browser_inspector_highlighter_autohide.js
browser/metro/base/content/contenthandlers/FindHandler.js
mobile/android/base/moz.build
--- a/CLOBBER
+++ b/CLOBBER
@@ -17,9 +17,9 @@
 #
 # Modifying this file will now automatically clobber the buildbot machines \o/
 #
 
 # Are you updating CLOBBER because you think it's needed for your WebIDL
 # changes to stick? As of bug 928195, this shouldn't be necessary! Please
 # don't change CLOBBER for WebIDL changes any more.
 
-Bug 934756 causes the Fennec build to fail unless we clobber.
+Bug 944533 requires clobber to force a Proguard refresh
--- a/browser/base/content/browser-thumbnails.js
+++ b/browser/base/content/browser-thumbnails.js
@@ -1,14 +1,16 @@
 #ifdef 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
 
+Cu.import("resource://gre/modules/NewTabUtils.jsm");
+
 /**
  * Keeps thumbnails of open web pages up-to-date.
  */
 let gBrowserThumbnails = {
   /**
    * Pref that controls whether we can store SSL content on disk
    */
   PREF_DISK_CACHE_SSL: "browser.cache.disk_cache_ssl",
@@ -115,16 +117,21 @@ let gBrowserThumbnails = {
       this._clearTimeout(aBrowser);
       this._capture(aBrowser);
     }.bind(this), this._captureDelayMS);
 
     this._timeouts.set(aBrowser, timeout);
   },
 
   _shouldCapture: function Thumbnails_shouldCapture(aBrowser) {
+    // Capture only if it's a top site in about:newtab.
+    if (!NewTabUtils.links.getLinks().some(
+          (link) => link && link.url == aBrowser.currentURI.spec))
+      return false;
+
     // Capture only if it's the currently selected tab.
     if (aBrowser != gBrowser.selectedBrowser)
       return false;
 
     // Don't capture in per-window private browsing mode.
     if (PrivateBrowsingUtils.isWindowPrivate(window))
       return false;
 
--- a/browser/base/content/highlighter.css
+++ b/browser/base/content/highlighter.css
@@ -1,20 +1,16 @@
 /* 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;
 }
 
-.highlighter-controls {
-  position: relative;
-}
-
 .highlighter-outline-container {
   overflow: hidden;
   position: relative;
 }
 
 .highlighter-outline {
   position: absolute;
 }
@@ -29,75 +25,65 @@
   transition-property: opacity, top, left, width, height;
   transition-duration: 0.1s;
   transition-timing-function: linear;
 }
 
 /*
  * Node Infobar
  */
+.highlighter-nodeinfobar-container {
+  position: relative;
+}
 
-.highlighter-nodeinfobar-container {
+.highlighter-nodeinfobar-positioner {
   position: absolute;
   max-width: 95%;
 }
 
-.highlighter-nodeinfobar-container[hidden] {
+.highlighter-nodeinfobar-positioner[hidden] {
   opacity: 0;
   pointer-events: none;
   display: -moz-box;
 }
 
-.highlighter-nodeinfobar-container:not([disable-transitions]),
-.highlighter-nodeinfobar-container[disable-transitions][force-transitions] {
+.highlighter-nodeinfobar-positioner:not([disable-transitions]),
+.highlighter-nodeinfobar-positioner[disable-transitions][force-transitions] {
   transition-property: transform, opacity, top, left;
   transition-duration: 0.1s;
   transition-timing-function: linear;
 }
 
 .highlighter-nodeinfobar-text {
   overflow: hidden;
   white-space: nowrap;
   text-overflow: ellipsis;
   direction: ltr;
 }
 
-.highlighter-nodeinfobar-button > .toolbarbutton-text {
-  display: none;
-}
-
-.highlighter-nodeinfobar-container:not([locked]):not(:hover) > .highlighter-nodeinfobar > .highlighter-nodeinfobar-button {
-  visibility: hidden;
-}
-
-.highlighter-nodeinfobar-container[locked] > .highlighter-nodeinfobar,
-.highlighter-nodeinfobar-container:not([locked]):hover > .highlighter-nodeinfobar {
-  pointer-events: auto;
-}
-
 html|*.highlighter-nodeinfobar-id,
 html|*.highlighter-nodeinfobar-classes,
 html|*.highlighter-nodeinfobar-pseudo-classes,
 html|*.highlighter-nodeinfobar-tagname {
   -moz-user-select: text;
   -moz-user-focus: normal;
   cursor: text;
 }
 
 .highlighter-nodeinfobar-arrow {
   display: none;
 }
 
-.highlighter-nodeinfobar-container[position="top"]:not([hide-arrow]) > .highlighter-nodeinfobar-arrow-bottom {
+.highlighter-nodeinfobar-positioner[position="top"]:not([hide-arrow]) > .highlighter-nodeinfobar-arrow-bottom {
   display: block;
 }
 
-.highlighter-nodeinfobar-container[position="bottom"]:not([hide-arrow]) > .highlighter-nodeinfobar-arrow-top {
+.highlighter-nodeinfobar-positioner[position="bottom"]:not([hide-arrow]) > .highlighter-nodeinfobar-arrow-top {
   display: block;
 }
 
-.highlighter-nodeinfobar-container[disabled] {
+.highlighter-nodeinfobar-positioner[disabled] {
   visibility: hidden;
 }
 
 html|*.highlighter-nodeinfobar-tagname {
   text-transform: lowercase;
 }
--- a/browser/components/customizableui/src/CustomizeMode.jsm
+++ b/browser/components/customizableui/src/CustomizeMode.jsm
@@ -1101,16 +1101,20 @@ CustomizeMode.prototype = {
     if (aTargetArea.id == kPaletteId) {
       // Did we drag from outside the palette?
       if (aOriginArea.id !== kPaletteId) {
         if (!CustomizableUI.isWidgetRemovable(aDraggedItemId)) {
           return;
         }
 
         CustomizableUI.removeWidgetFromArea(aDraggedItemId);
+        // Special widgets are removed outright, we can return here:
+        if (CustomizableUI.isSpecialWidget(aDraggedItemId)) {
+          return;
+        }
       }
       draggedItem = draggedItem.parentNode;
 
       // If the target node is the palette itself, just append
       if (aTargetNode == this.visiblePalette) {
         this.visiblePalette.appendChild(draggedItem);
       } else {
         // The items in the palette are wrapped, so we need the target node's parent here:
--- a/browser/components/customizableui/test/browser.ini
+++ b/browser/components/customizableui/test/browser.ini
@@ -54,9 +54,10 @@ skip-if = os == "linux"
 [browser_941083_invalidate_wrapper_cache_createWidget.js]
 [browser_942581_unregisterArea_keeps_placements.js]
 [browser_943683_migration_test.js]
 [browser_944887_destroyWidget_should_destroy_in_palette.js]
 [browser_945739_showInPrivateBrowsing_customize_mode.js]
 [browser_947987_removable_default.js]
 [browser_948985_non_removable_defaultArea.js]
 [browser_952963_areaType_getter_no_area.js]
+[browser_956602_remove_special_widget.js]
 [browser_panel_toggle.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/customizableui/test/browser_956602_remove_special_widget.js
@@ -0,0 +1,31 @@
+/* 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";
+
+
+// Adding a separator and then dragging it out of the navbar shouldn't throw
+add_task(function() {
+  try {
+    let navbar = document.getElementById("nav-bar");
+    let separatorSelector = "toolbarseparator[id^=customizableui-special-separator]";
+    ok(!navbar.querySelector(separatorSelector), "Shouldn't be a separator in the navbar");
+    CustomizableUI.addWidgetToArea('separator', 'nav-bar');
+    yield startCustomizing();
+    let separator = navbar.querySelector(separatorSelector);
+    ok(separator, "There should be a separator in the navbar now.");
+    let palette = document.getElementById("customization-palette");
+    simulateItemDrag(separator, palette);
+    ok(!palette.querySelector(separatorSelector), "No separator in the palette.");
+  } catch (ex) {
+    Cu.reportError(ex);
+    ok(false, "Shouldn't throw an exception moving an item to the navbar.");
+  } finally {
+    yield endCustomizing();
+  }
+});
+
+add_task(function asyncCleanup() {
+  resetCustomization();
+});
--- a/browser/components/nsBrowserContentHandler.js
+++ b/browser/components/nsBrowserContentHandler.js
@@ -578,35 +578,16 @@ nsBrowserContentHandler.prototype = {
             willRestoreSession = ss.isAutomaticRestoreEnabled();
 
             overridePage = Services.urlFormatter.formatURLPref("startup.homepage_override_url");
             if (prefb.prefHasUserValue("app.update.postupdate"))
               overridePage = getPostUpdateOverridePage(overridePage);
 
             overridePage = overridePage.replace("%OLD_VERSION%", old_mstone);
             break;
-
-          // Temporary case for Australis whatsnew
-          case OVERRIDE_NEW_BUILD_ID:
-            let locale = "en-US";
-            try {
-              locale = Services.prefs.getCharPref("general.useragent.locale");
-            } catch (e) {}
-
-            let showedAustralisWhatsNew = false;
-            try {
-              showedAustralisWhatsNew = Services.prefs.getBoolPref("browser.showedAustralisWhatsNew");
-            } catch(e) {}
-
-            // Show the Australis whatsnew page for en-US if we haven't yet shown it
-            if (!showedAustralisWhatsNew && locale == "en-US") {
-              Services.prefs.setBoolPref("browser.showedAustralisWhatsNew", true);
-              overridePage = "https://www.mozilla.org/en-US/firefox/29.0a1/whatsnew/";
-            }
-            break;
         }
       }
     } catch (ex) {}
 
     // formatURLPref might return "about:blank" if getting the pref fails
     if (overridePage == "about:blank")
       overridePage = "";
 
--- a/browser/components/search/content/search.xml
+++ b/browser/components/search/content/search.xml
@@ -82,26 +82,32 @@
         var os =
                Components.classes["@mozilla.org/observer-service;1"]
                          .getService(Components.interfaces.nsIObserverService);
         os.addObserver(this, "browser-search-engine-modified", false);
 
         this._addedObserver = true;
 
         this.searchService.init((function search_init_cb(aStatus) {
+          // Bail out if the binding's been destroyed
+          if (this._destroyed)
+            return;
+
           if (Components.isSuccessCode(aStatus)) {
             // Refresh the display (updating icon, etc)
             this.updateDisplay();
           } else {
             Components.utils.reportError("Cannot initialize search service, bailing out: " + aStatus);
           }
         }).bind(this));
       ]]></constructor>
 
       <destructor><![CDATA[
+        this._destroyed = true;
+
         if (this._addedObserver) {
           var os = Components.classes["@mozilla.org/observer-service;1"]
                              .getService(Components.interfaces.nsIObserverService);
           os.removeObserver(this, "browser-search-engine-modified");
           this._addedObserver = false;
         }
 
         // Make sure to break the cycle from _textbox to us. Otherwise we leak
--- a/browser/devtools/app-manager/content/projects.js
+++ b/browser/devtools/app-manager/content/projects.js
@@ -127,17 +127,17 @@ let UI = {
     let icon;
     if (manifest.icons) {
       let size = Object.keys(manifest.icons).sort(function(a, b) b - a)[0];
       if (size) {
         icon = manifest.icons[size];
       }
     }
     if (!icon)
-      return null;
+      return "chrome://browser/skin/devtools/app-manager/default-app-icon.png";
     if (project.type == "hosted") {
       let manifestURL = Services.io.newURI(project.location, null, null);
       let origin = Services.io.newURI(manifestURL.prePath, null, null);
       return Services.io.newURI(icon, null, origin).spec;
     } else if (project.type == "packaged") {
       let projectFolder = FileUtils.File(project.location);
       let folderURI = Services.io.newFileURI(projectFolder).spec;
       return folderURI + icon.replace(/^\/|\\/, "");
--- a/browser/devtools/debugger/debugger-toolbar.js
+++ b/browser/devtools/debugger/debugger-toolbar.js
@@ -26,21 +26,22 @@ function ToolbarView() {
 ToolbarView.prototype = {
   /**
    * Initialization function, called when the debugger is started.
    */
   initialize: function() {
     dumpn("Initializing the ToolbarView");
 
     this._instrumentsPaneToggleButton = document.getElementById("instruments-pane-toggle");
-    this._resumeOrderPanel = document.getElementById("resumption-order-panel");
     this._resumeButton = document.getElementById("resume");
     this._stepOverButton = document.getElementById("step-over");
     this._stepInButton = document.getElementById("step-in");
     this._stepOutButton = document.getElementById("step-out");
+    this._resumeOrderTooltip = new Tooltip(document);
+    this._resumeOrderTooltip.defaultPosition = TOOLBAR_ORDER_POPUP_POSITION;
 
     let resumeKey = ShortcutUtils.prettifyShortcut(document.getElementById("resumeKey"));
     let stepOverKey = ShortcutUtils.prettifyShortcut(document.getElementById("stepOverKey"));
     let stepInKey = ShortcutUtils.prettifyShortcut(document.getElementById("stepInKey"));
     let stepOutKey = ShortcutUtils.prettifyShortcut(document.getElementById("stepOutKey"));
     this._resumeTooltip = L10N.getFormatStr("resumeButtonTooltip", resumeKey);
     this._pauseTooltip = L10N.getFormatStr("pauseButtonTooltip", resumeKey);
     this._stepOverTooltip = L10N.getFormatStr("stepOverTooltip", stepOverKey);
@@ -75,20 +76,18 @@ ToolbarView.prototype = {
    * Display a warning when trying to resume a debuggee while another is paused.
    * Debuggees must be unpaused in a Last-In-First-Out order.
    *
    * @param string aPausedUrl
    *        The URL of the last paused debuggee.
    */
   showResumeWarning: function(aPausedUrl) {
     let label = L10N.getFormatStr("resumptionOrderPanelTitle", aPausedUrl);
-    let descriptionNode = document.getElementById("resumption-panel-desc");
-    descriptionNode.setAttribute("value", label);
-
-    this._resumeOrderPanel.openPopup(this._resumeButton);
+    this._resumeOrderTooltip.setTextContent([label]);
+    this._resumeOrderTooltip.show(this._resumeButton);
   },
 
   /**
    * Sets the resume button state based on the debugger active thread.
    *
    * @param string aState
    *        Either "paused" or "attached".
    */
@@ -158,21 +157,21 @@ ToolbarView.prototype = {
     if (DebuggerController.activeThread.paused) {
       DebuggerController.StackFrames.currentFrameDepth = -1;
       let warn = DebuggerController._ensureResumptionOrder;
       DebuggerController.activeThread.stepOut(warn);
     }
   },
 
   _instrumentsPaneToggleButton: null,
-  _resumeOrderPanel: null,
   _resumeButton: null,
   _stepOverButton: null,
   _stepInButton: null,
   _stepOutButton: null,
+  _resumeOrderTooltip: null,
   _resumeTooltip: "",
   _pauseTooltip: "",
   _stepOverTooltip: "",
   _stepInTooltip: "",
   _stepOutTooltip: ""
 };
 
 /**
--- a/browser/devtools/debugger/debugger-view.js
+++ b/browser/devtools/debugger/debugger-view.js
@@ -23,16 +23,17 @@ const GLOBAL_SEARCH_ACTION_MAX_DELAY = 1
 const FUNCTION_SEARCH_ACTION_MAX_DELAY = 400; // ms
 const SEARCH_GLOBAL_FLAG = "!";
 const SEARCH_FUNCTION_FLAG = "@";
 const SEARCH_TOKEN_FLAG = "#";
 const SEARCH_LINE_FLAG = ":";
 const SEARCH_VARIABLE_FLAG = "*";
 const EDITOR_VARIABLE_HOVER_DELAY = 350; // ms
 const EDITOR_VARIABLE_POPUP_POSITION = "topcenter bottomleft";
+const TOOLBAR_ORDER_POPUP_POSITION = "topcenter bottomleft";
 
 /**
  * Object defining the debugger view components.
  */
 let DebuggerView = {
   /**
    * Initializes the debugger view.
    *
--- a/browser/devtools/debugger/debugger.xul
+++ b/browser/devtools/debugger/debugger.xul
@@ -508,20 +508,9 @@
          consumeoutsideclicks="false">
     <vbox>
       <label id="conditional-breakpoint-panel-description"
              value="&debuggerUI.condBreakPanelTitle;"/>
       <textbox id="conditional-breakpoint-panel-textbox"/>
     </vbox>
   </panel>
 
-  <panel id="resumption-order-panel"
-         type="arrow"
-         position="before_start"
-         noautofocus="true"
-         consumeoutsideclicks="false">
-    <hbox align="start">
-      <image class="alert-icon"/>
-      <label id="resumption-panel-desc" class="description"/>
-    </hbox>
-  </panel>
-
 </window>
--- a/browser/devtools/fontinspector/font-inspector.js
+++ b/browser/devtools/fontinspector/font-inspector.js
@@ -15,22 +15,18 @@ function FontInspector(inspector, window
   this.chromeDoc = window.document;
   this.init();
 }
 
 FontInspector.prototype = {
   init: function FI_init() {
     this.update = this.update.bind(this);
     this.onNewNode = this.onNewNode.bind(this);
-    this.onHighlighterLocked = this.onHighlighterLocked.bind(this);
     this.inspector.selection.on("new-node", this.onNewNode);
     this.inspector.sidebar.on("fontinspector-selected", this.onNewNode);
-    if (this.inspector.highlighter) {
-      this.inspector.highlighter.on("locked", this.onHighlighterLocked);
-    }
     this.update();
   },
 
   /**
    * Is the fontinspector visible in the sidebar?
    */
   isActive: function FI_isActive() {
     return this.inspector.sidebar &&
@@ -39,46 +35,34 @@ FontInspector.prototype = {
 
   /**
    * Remove listeners.
    */
   destroy: function FI_destroy() {
     this.chromeDoc = null;
     this.inspector.sidebar.off("layoutview-selected", this.onNewNode);
     this.inspector.selection.off("new-node", this.onNewNode);
-    if (this.inspector.highlighter) {
-      this.inspector.highlighter.off("locked", this.onHighlighterLocked);
-    }
   },
 
   /**
    * Selection 'new-node' event handler.
    */
   onNewNode: function FI_onNewNode() {
     if (this.isActive() &&
         this.inspector.selection.isLocal() &&
         this.inspector.selection.isConnected() &&
-        this.inspector.selection.isElementNode() &&
-        this.inspector.selection.reason != "highlighter") {
+        this.inspector.selection.isElementNode()) {
       this.undim();
       this.update();
     } else {
       this.dim();
     }
   },
 
   /**
-   * Highlighter 'locked' event handler
-   */
-  onHighlighterLocked: function FI_onHighlighterLocked() {
-    this.undim();
-    this.update();
-  },
-
-  /**
    * Hide the font list. No node are selected.
    */
   dim: function FI_dim() {
     this.chromeDoc.body.classList.add("dim");
     this.chromeDoc.querySelector("#all-fonts").innerHTML = "";
   },
 
   /**
@@ -205,17 +189,17 @@ FontInspector.prototype = {
    * Select the <body> to show all the fonts included in the document.
    */
   showAll: function FI_showAll() {
     if (!this.isActive() ||
         !this.inspector.selection.isConnected() ||
         !this.inspector.selection.isElementNode()) {
       return;
     }
-    let node = this.inspector.selection.node;
+    let node = this.inspector.selection.nodeFront;
     let contentDocument = node.ownerDocument;
     let root = contentDocument.documentElement;
     if (contentDocument.body) {
       root = contentDocument.body;
     }
     this.inspector.selection.setNode(root, "fontinspector");
   },
 }
new file mode 100644
--- /dev/null
+++ b/browser/devtools/framework/selection.js
@@ -0,0 +1,296 @@
+/* -*- Mode: Javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ft=javascript 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";
+
+const {Cu, Ci} = require("chrome");
+let EventEmitter = require("devtools/shared/event-emitter");
+
+/**
+ * API
+ *
+ *   new Selection(walker=null, node=null, track={attributes,detached});
+ *   destroy()
+ *   node (readonly)
+ *   setNode(node, origin="unknown")
+ *
+ * Helpers:
+ *
+ *   window
+ *   document
+ *   isRoot()
+ *   isNode()
+ *   isHTMLNode()
+ *
+ * Check the nature of the node:
+ *
+ *   isElementNode()
+ *   isAttributeNode()
+ *   isTextNode()
+ *   isCDATANode()
+ *   isEntityRefNode()
+ *   isEntityNode()
+ *   isProcessingInstructionNode()
+ *   isCommentNode()
+ *   isDocumentNode()
+ *   isDocumentTypeNode()
+ *   isDocumentFragmentNode()
+ *   isNotationNode()
+ *
+ * Events:
+ *   "new-node" when the inner node changed
+ *   "before-new-node" when the inner node is set to change
+ *   "attribute-changed" when an attribute is changed (only if tracked)
+ *   "detached" when the node (or one of its parents) is removed from the document (only if tracked)
+ *   "reparented" when the node (or one of its parents) is moved under a different node (only if tracked)
+ */
+
+/**
+ * A Selection object. Hold a reference to a node.
+ * Includes some helpers, fire some helpful events.
+ *
+ * @param node Inner node.
+ *    Can be null. Can be (un)set in the future via the "node" property;
+ * @param trackAttribute Tell if events should be fired when the attributes of
+ *    the node change.
+ *
+ */
+function Selection(walker, node=null, track={attributes:true,detached:true}) {
+  EventEmitter.decorate(this);
+
+  this._onMutations = this._onMutations.bind(this);
+  this.track = track;
+  this.setWalker(walker);
+  this.setNode(node);
+}
+
+exports.Selection = Selection;
+
+Selection.prototype = {
+  _walker: null,
+  _node: null,
+
+  _onMutations: function(mutations) {
+    let attributeChange = false;
+    let pseudoChange = false;
+    let detached = false;
+    let parentNode = null;
+
+    for (let m of mutations) {
+      if (!attributeChange && m.type == "attributes") {
+        attributeChange = true;
+      }
+      if (m.type == "childList") {
+        if (!detached && !this.isConnected()) {
+          if (this.isNode()) {
+            parentNode = m.target;
+          }
+          detached = true;
+        }
+      }
+      if (m.type == "pseudoClassLock") {
+        pseudoChange = true;
+      }
+    }
+
+    // Fire our events depending on what changed in the mutations array
+    if (attributeChange) {
+      this.emit("attribute-changed");
+    }
+    if (pseudoChange) {
+      this.emit("pseudoclass");
+    }
+    if (detached) {
+      let rawNode = null;
+      if (parentNode && parentNode.isLocal_toBeDeprecated()) {
+        rawNode = parentNode.rawNode();
+      }
+
+      this.emit("detached", rawNode, null);
+      this.emit("detached-front", parentNode);
+    }
+  },
+
+  destroy: function() {
+    this.setNode(null);
+    this.setWalker(null);
+  },
+
+  setWalker: function(walker) {
+    if (this._walker) {
+      this._walker.off("mutations", this._onMutations);
+    }
+    this._walker = walker;
+    if (this._walker) {
+      this._walker.on("mutations", this._onMutations);
+    }
+  },
+
+  // Not remote-safe
+  setNode: function(value, reason="unknown") {
+    if (value) {
+      value = this._walker.frontForRawNode(value);
+    }
+    this.setNodeFront(value, reason);
+  },
+
+  // Not remote-safe
+  get node() {
+    return this._node;
+  },
+
+  // Not remote-safe
+  get window() {
+    if (this.isNode()) {
+      return this.node.ownerDocument.defaultView;
+    }
+    return null;
+  },
+
+  // Not remote-safe
+  get document() {
+    if (this.isNode()) {
+      return this.node.ownerDocument;
+    }
+    return null;
+  },
+
+  setNodeFront: function(value, reason="unknown") {
+    this.reason = reason;
+    if (value !== this._nodeFront) {
+      let rawValue = null;
+      if (value && value.isLocal_toBeDeprecated()) {
+        rawValue = value.rawNode();
+      }
+      this.emit("before-new-node", rawValue, reason);
+      this.emit("before-new-node-front", value, reason);
+      let previousNode = this._node;
+      let previousFront = this._nodeFront;
+      this._node = rawValue;
+      this._nodeFront = value;
+      this.emit("new-node", previousNode, this.reason);
+      this.emit("new-node-front", value, this.reason);
+    }
+  },
+
+  get documentFront() {
+    return this._walker.document(this._nodeFront);
+  },
+
+  get nodeFront() {
+    return this._nodeFront;
+  },
+
+  isRoot: function() {
+    return this.isNode() &&
+           this.isConnected() &&
+           this._nodeFront.isDocumentElement;
+  },
+
+  isNode: function() {
+    if (!this._nodeFront) {
+      return false;
+    }
+
+    // As long as tools are still accessing node.rawNode(),
+    // this needs to stay here.
+    if (this._node && Cu.isDeadWrapper(this._node)) {
+      return false;
+    }
+
+    return true;
+  },
+
+  isLocal: function() {
+    return !!this._node;
+  },
+
+  isConnected: function() {
+    let node = this._nodeFront;
+    if (!node || !node.actorID) {
+      return false;
+    }
+
+    // As long as there are still tools going around
+    // accessing node.rawNode, this needs to stay.
+    let rawNode = null;
+    if (node.isLocal_toBeDeprecated()) {
+      rawNode = node.rawNode();
+    }
+    if (rawNode) {
+      try {
+        let doc = this.document;
+        return (doc && doc.defaultView && doc.documentElement.contains(rawNode));
+      } catch (e) {
+        // "can't access dead object" error
+        return false;
+      }
+    }
+
+    while(node) {
+      if (node === this._walker.rootNode) {
+        return true;
+      }
+      node = node.parentNode();
+    };
+    return false;
+  },
+
+  isHTMLNode: function() {
+    let xhtml_ns = "http://www.w3.org/1999/xhtml";
+    return this.isNode() && this.node.namespaceURI == xhtml_ns;
+  },
+
+  // Node type
+
+  isElementNode: function() {
+    return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.ELEMENT_NODE;
+  },
+
+  isAttributeNode: function() {
+    return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.ATTRIBUTE_NODE;
+  },
+
+  isTextNode: function() {
+    return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.TEXT_NODE;
+  },
+
+  isCDATANode: function() {
+    return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.CDATA_SECTION_NODE;
+  },
+
+  isEntityRefNode: function() {
+    return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.ENTITY_REFERENCE_NODE;
+  },
+
+  isEntityNode: function() {
+    return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.ENTITY_NODE;
+  },
+
+  isProcessingInstructionNode: function() {
+    return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.PROCESSING_INSTRUCTION_NODE;
+  },
+
+  isCommentNode: function() {
+    return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.PROCESSING_INSTRUCTION_NODE;
+  },
+
+  isDocumentNode: function() {
+    return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.DOCUMENT_NODE;
+  },
+
+  isDocumentTypeNode: function() {
+    return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.DOCUMENT_TYPE_NODE;
+  },
+
+  isDocumentFragmentNode: function() {
+    return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.DOCUMENT_FRAGMENT_NODE;
+  },
+
+  isNotationNode: function() {
+    return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.NOTATION_NODE;
+  },
+};
--- a/browser/devtools/framework/test/browser_keybindings.js
+++ b/browser/devtools/framework/test/browser_keybindings.js
@@ -12,16 +12,17 @@ function test()
   let node;
   let inspector;
   let keysetMap = { };
 
   gBrowser.selectedTab = gBrowser.addTab();
   gBrowser.selectedBrowser.addEventListener("load", function onload() {
     gBrowser.selectedBrowser.removeEventListener("load", onload, true);
     doc = content.document;
+    node = doc.querySelector("h1");
     waitForFocus(setupKeyBindingsTest, content);
   }, true);
 
   content.location = "data:text/html,<html><head><title>Test for the " +
                      "highlighter keybindings</title></head><body>" +
                      "<h1>Keybindings!</h1></body></html>";
 
   function buildDevtoolsKeysetMap(keyset) {
@@ -58,34 +59,47 @@ function test()
 
     gDevTools.once("toolbox-ready", (e, toolbox) => {
       inspectorShouldBeOpenAndHighlighting(toolbox.getCurrentPanel(), toolbox)
     });
 
     keysetMap.inspector.synthesizeKey();
   }
 
+  function moveMouseOver(aElement, aInspector, cb)
+  {
+    EventUtils.synthesizeMouse(aElement, 2, 2, {type: "mousemove"},
+      aElement.ownerDocument.defaultView);
+    aInspector.toolbox.once("picker-node-hovered", () => {
+      executeSoon(cb);
+    });
+  }
+
+  function isHighlighting()
+  {
+    let outline = gBrowser.selectedBrowser.parentNode
+      .querySelector(".highlighter-container .highlighter-outline");
+    return outline && !outline.hasAttribute("hidden");
+  }
+
   function inspectorShouldBeOpenAndHighlighting(aInspector, aToolbox)
   {
     is (aToolbox.currentToolId, "inspector", "Correct tool has been loaded");
-    is (aInspector.highlighter.locked, true, "Highlighter should be locked");
 
-    aInspector.highlighter.once("unlocked", () => {
-      is (aInspector.highlighter.locked, false, "Highlighter should be unlocked");
-      keysetMap.inspector.synthesizeKey();
-      is (aInspector.highlighter.locked, true, "Highlighter should be locked");
+    aToolbox.once("picker-started", () => {
+      ok(true, "picker-started event received, highlighter started");
       keysetMap.inspector.synthesizeKey();
-      is (aInspector.highlighter.locked, false, "Highlighter should be unlocked");
-      keysetMap.inspector.synthesizeKey();
-      is (aInspector.highlighter.locked, true, "Highlighter should be locked");
 
-      aToolbox.once("webconsole-ready", (e, panel) => {
-        webconsoleShouldBeSelected(aToolbox, panel);
+      aToolbox.once("picker-stopped", () => {
+        ok(true, "picker-stopped event received, highlighter stopped");
+        aToolbox.once("webconsole-ready", (e, panel) => {
+          webconsoleShouldBeSelected(aToolbox, panel);
+        });
+        keysetMap.webconsole.synthesizeKey();
       });
-      keysetMap.webconsole.synthesizeKey();
     });
   }
 
   function webconsoleShouldBeSelected(aToolbox, panel)
   {
       is (aToolbox.currentToolId, "webconsole");
 
       aToolbox.once("jsdebugger-ready", (e, panel) => {
--- a/browser/devtools/framework/toolbox.js
+++ b/browser/devtools/framework/toolbox.js
@@ -41,16 +41,19 @@ loader.lazyGetter(this, "toolboxStrings"
 });
 
 loader.lazyGetter(this, "Requisition", () => {
   let {require} = Cu.import("resource://gre/modules/devtools/Require.jsm", {});
   Cu.import("resource://gre/modules/devtools/gcli.jsm", {});
   return require("gcli/cli").Requisition;
 });
 
+loader.lazyGetter(this, "Selection", () => require("devtools/framework/selection").Selection);
+loader.lazyGetter(this, "InspectorFront", () => require("devtools/server/actors/inspector").InspectorFront);
+
 /**
  * A "Toolbox" is the component that holds all the tools for one specific
  * target. Visually, it's a document that includes the tools tabs and all
  * the iframes where the tool panels will be living in.
  *
  * @param {object} target
  *        The object the toolbox is debugging.
  * @param {string} selectedTool
@@ -179,16 +182,52 @@ Toolbox.prototype = {
   /**
    * Get current zoom level of toolbox
    */
   get zoomValue() {
     return parseFloat(Services.prefs.getCharPref(ZOOM_PREF));
   },
 
   /**
+   * Get the toolbox highlighter front. Note that it may not always have been
+   * initialized first. Use `initInspector()` if needed.
+   */
+  get highlighter() {
+    if (this.isRemoteHighlightable) {
+      return this._highlighter;
+    } else {
+      return null;
+    }
+  },
+
+  /**
+   * Get the toolbox's inspector front. Note that it may not always have been
+   * initialized first. Use `initInspector()` if needed.
+   */
+  get inspector() {
+    return this._inspector;
+  },
+
+  /**
+   * Get the toolbox's walker front. Note that it may not always have been
+   * initialized first. Use `initInspector()` if needed.
+   */
+  get walker() {
+    return this._walker;
+  },
+
+  /**
+   * Get the toolbox's node selection. Note that it may not always have been
+   * initialized first. Use `initInspector()` if needed.
+   */
+  get selection() {
+    return this._selection;
+  },
+
+  /**
    * Get the toggled state of the split console
    */
   get splitConsole() {
     return this._splitConsole;
   },
 
   /**
    * Open the toolbox
@@ -209,21 +248,24 @@ Toolbox.prototype = {
         this._buildOptions();
         this._buildTabs();
         this._buildButtons();
         this._addKeysToWindow();
         this._addToolSwitchingKeys();
         this._addZoomKeys();
         this._loadInitialZoom();
 
-        this._telemetry.toolOpened("toolbox");
+        // Load the toolbox-level actor fronts and utilities now
+        this._target.makeRemote().then(() => {
+          this._telemetry.toolOpened("toolbox");
 
-        this.selectTool(this._defaultToolId).then(panel => {
-          this.emit("ready");
-          deferred.resolve();
+          this.selectTool(this._defaultToolId).then(panel => {
+            this.emit("ready");
+            deferred.resolve();
+          });
         });
       };
 
       iframe.setAttribute("src", this._URL);
 
       let domHelper = new DOMHelpers(iframe.contentWindow);
       domHelper.onceDOMReady(domReady);
 
@@ -410,17 +452,16 @@ Toolbox.prototype = {
       key.setAttribute("oncommand", "void(0)"); // needed. See bug 371900
       key.addEventListener("command", () => {
         HUDService.toggleBrowserConsole();
       }, true);
       doc.getElementById("toolbox-keyset").appendChild(key);
     }
   },
 
-
   /**
    * Handle any custom key events.  Returns true if there was a custom key binding run
    * @param {string} toolId
    *        Which tool to run the command on (skip if not current)
    */
   fireCustomKey: function(toolId) {
     let toolDefinition = gDevTools.getToolDefinition(toolId);
 
@@ -479,33 +520,52 @@ Toolbox.prototype = {
    */
   _buildTabs: function() {
     for (let definition of gDevTools.getToolDefinitionArray()) {
       this._buildTabForTool(definition);
     }
   },
 
   /**
-   * Add buttons to the UI as specified in the devtools.window.toolbarSpec pref
+   * Add buttons to the UI as specified in the devtools.toolbox.toolbarSpec pref
    */
   _buildButtons: function() {
+    this._buildPickerButton();
+
     if (!this.target.isLocalTab) {
       return;
     }
 
     let spec = CommandUtils.getCommandbarSpec("devtools.toolbox.toolbarSpec");
     let env = CommandUtils.createEnvironment(this.target.tab.ownerDocument,
                                              this.target.window.document);
     let req = new Requisition(env);
     let buttons = CommandUtils.createButtons(spec, this._target, this.doc, req);
     let container = this.doc.getElementById("toolbox-buttons");
     buttons.forEach(container.appendChild.bind(container));
   },
 
   /**
+   * Adding the element picker button is done here unlike the other buttons
+   * since we want it to work for remote targets too
+   */
+  _buildPickerButton: function() {
+    this._pickerButton = this.doc.createElement("toolbarbutton");
+    this._pickerButton.id = "command-button-pick";
+    this._pickerButton.className = "command-button";
+    this._pickerButton.setAttribute("tooltiptext", toolboxStrings("pickButton.tooltip"));
+
+    let container = this.doc.querySelector("#toolbox-buttons");
+    container.appendChild(this._pickerButton);
+
+    this.togglePicker = this.togglePicker.bind(this);
+    this._pickerButton.addEventListener("command", this.togglePicker, false);
+  },
+
+  /**
    * Build a tab for one tool definition and add to the toolbox
    *
    * @param {string} toolDefinition
    *        Tool definition of the tool to build a tab for.
    */
   _buildTabForTool: function(toolDefinition) {
     if (!toolDefinition.isTargetSupported(this._target)) {
       return;
@@ -595,16 +655,22 @@ Toolbox.prototype = {
 
   /**
    * Ensure the tool with the given id is loaded.
    *
    * @param {string} id
    *        The id of the tool to load.
    */
   loadTool: function(id) {
+    if (id === "inspector" && !this._inspector) {
+      return this.initInspector().then(() => {
+        return this.loadTool(id);
+      });
+    }
+
     let deferred = promise.defer();
     let iframe = this.doc.getElementById("toolbox-panel-iframe-" + id);
 
     if (iframe) {
       let panel = this._toolPanels.get(id);
       if (panel) {
         deferred.resolve(panel);
       } else {
@@ -953,16 +1019,164 @@ Toolbox.prototype = {
       let key = doc.getElementById("key_" + toolId);
       if (key) {
         key.parentNode.removeChild(key);
       }
     }
   },
 
   /**
+   * Initialize the inspector/walker/selection/highlighter fronts.
+   * Returns a promise that resolves when the fronts are initialized
+   */
+  initInspector: function() {
+    let deferred = promise.defer();
+
+    if (!this._inspector) {
+      this._inspector = InspectorFront(this._target.client, this._target.form);
+      this._inspector.getWalker().then(walker => {
+        this._walker = walker;
+        this._selection = new Selection(this._walker);
+        if (this.isRemoteHighlightable) {
+          this._inspector.getHighlighter().then(highlighter => {
+            this._highlighter = highlighter;
+            deferred.resolve();
+          });
+        } else {
+          deferred.resolve();
+        }
+      });
+    } else {
+      deferred.resolve();
+    }
+
+    return deferred.promise;
+  },
+
+  /**
+   * Destroy the inspector/walker/selection fronts
+   * Returns a promise that resolves when the fronts are destroyed
+   */
+  destroyInspector: function() {
+    let deferred = promise.defer();
+
+    if (this._inspector) {
+      this._selection.destroy();
+      this._selection = null;
+      this._walker.release().then(
+        () => {
+          this._inspector.destroy();
+          this._highlighter.destroy();
+        },
+        (e) => {
+          console.error("Walker.release() failed: " + e);
+          this._inspector.destroy();
+          return this._highlighter.destroy();
+        }
+      ).then(() => {
+        this._inspector = null;
+        this._highlighter = null;
+        this._walker = null;
+        deferred.resolve();
+      });
+    } else {
+      deferred.resolve();
+    }
+
+    return deferred.promise;
+  },
+
+  /**
+   * Start/stop the element picker on the debuggee target.
+   */
+  togglePicker: function() {
+    if (this._isPicking) {
+      return this.stopPicker();
+    } else {
+      return this.startPicker();
+    }
+  },
+
+  get isRemoteHighlightable() {
+    return this._target.client.traits.highlightable;
+  },
+
+  /**
+   * Start the element picker on the debuggee target.
+   * This will request the inspector actor to start listening for mouse/touch
+   * events on the target to highlight the hovered/picked element.
+   * Depending on the server-side capabilities, this may fire events when nodes
+   * are hovered.
+   * @return A promise that resolves when the picker has started
+   */
+  startPicker: function() {
+    let deferred = promise.defer();
+
+    let done = () => {
+      this.emit("picker-started");
+      deferred.resolve();
+    };
+
+    this.initInspector().then(() => {
+      this._isPicking = true;
+      this._pickerButton.setAttribute("checked", "true");
+
+      if (this.isRemoteHighlightable) {
+        this.highlighter.pick().then(done);
+
+        this._onPickerNodeHovered = res => {
+          this.emit("picker-node-hovered", res.node);
+        };
+        this.walker.on("picker-node-hovered", this._onPickerNodeHovered);
+
+        this._onPickerNodePicked = res => {
+          this.selection.setNodeFront(res.node, "picker-node-picked");
+          this.stopPicker();
+        };
+        this.walker.on("picker-node-picked", this._onPickerNodePicked);
+      } else {
+        this.walker.pick().then(node => {
+          this.selection.setNodeFront(node, "picker-node-picked");
+          this.stopPicker();
+        });
+        done();
+      }
+    });
+
+    return deferred.promise;
+  },
+
+  /**
+   * Stop the element picker
+   * @return A promise that resolves when the picker has stopped
+   */
+  stopPicker: function() {
+    let deferred = promise.defer();
+
+    let done = () => {
+      this.emit("picker-stopped");
+      deferred.resolve();
+    };
+
+    this.initInspector().then(() => {
+      this._isPicking = false;
+      this._pickerButton.removeAttribute("checked");
+      if (this.isRemoteHighlightable) {
+        this.highlighter.cancelPick().then(done);
+        this.walker.off("picker-node-hovered", this._onPickerNodeHovered);
+        this.walker.off("picker-node-picked", this._onPickerNodePicked);
+      } else {
+        this.walker.cancelPick().then(done);
+      }
+    });
+
+    return deferred.promise;
+  },
+
+  /**
    * Get the toolbox's notification box
    *
    * @return The notification box element.
    */
   getNotificationBox: function() {
     return this.doc.getElementById("toolbox-notificationbox");
   },
 
@@ -999,16 +1213,22 @@ Toolbox.prototype = {
       try {
         outstanding.push(panel.destroy());
       } catch (e) {
         // We don't want to stop here if any panel fail to close.
         console.error(e);
       }
     }
 
+    // Destroying the walker and inspector fronts
+    outstanding.push(this.destroyInspector());
+
+    // Removing buttons
+    this._pickerButton.removeEventListener("command", this.togglePicker, false);
+    this._pickerButton = null;
     let container = this.doc.getElementById("toolbox-buttons");
     while (container.firstChild) {
       container.removeChild(container.firstChild);
     }
 
     outstanding.push(this.destroyHost());
 
     this._telemetry.destroy();
deleted file mode 100644
--- a/browser/devtools/inspector/highlighter.js
+++ /dev/null
@@ -1,877 +0,0 @@
-/* -*- Mode: Javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim: set ft=javascript 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/. */
-
-const {Cu, Cc, Ci} = require("chrome");
-
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/devtools/LayoutHelpers.jsm");
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-
-let EventEmitter = require("devtools/shared/event-emitter");
-
-const PSEUDO_CLASSES = [":hover", ":active", ":focus"];
-  // add ":visited" and ":link" after bug 713106 is fixed
-
-exports._forceBasic = {value: false};
-
-exports.Highlighter = function Highlighter(aTarget, aInspector, aToolbox) {
-  if (aTarget.isLocalTab && !exports._forceBasic.value) {
-    return new LocalHighlighter(aTarget, aInspector, aToolbox);
-  } else {
-    return new BasicHighlighter(aTarget, aInspector, aToolbox);
-  }
-}
-
-exports.LocalHighlighter = LocalHighlighter;
-exports.BasicHighlighter = BasicHighlighter;
-
-/**
- * A highlighter mechanism.
- *
- * The highlighter is built dynamically into the browser element.
- * The caller is in charge of destroying the highlighter (ie, the highlighter
- * won't be destroyed if a new tab is selected for example).
- *
- * API:
- *
- *   // Constructor and destructor.
- *   Highlighter(aTab, aInspector)
- *   void destroy();
- *
- *   // Show and hide the highlighter
- *   void show();
- *   void hide();
- *   boolean isHidden();
- *
- *   // Redraw the highlighter if the visible portion of the node has changed.
- *   void invalidateSize(aScroll);
- *
- * Events:
- *
- *   "closed" - Highlighter is closing
- *   "highlighting" - Highlighter is highlighting
- *   "locked" - The selected node has been locked
- *   "unlocked" - The selected ndoe has been unlocked
- *
- * Structure:
- *  <stack class="highlighter-container">
- *    <box class="highlighter-outline-container">
- *      <box class="highlighter-outline" locked="true/false"/>
- *    </box>
- *    <box class="highlighter-controls">
- *      <box class="highlighter-nodeinfobar-container" position="top/bottom" locked="true/false">
- *        <box class="highlighter-nodeinfobar-arrow highlighter-nodeinfobar-arrow-top"/>
- *        <hbox class="highlighter-nodeinfobar">
- *          <toolbarbutton class="highlighter-nodeinfobar-inspectbutton highlighter-nodeinfobar-button"/>
- *          <hbox class="highlighter-nodeinfobar-text">tagname#id.class1.class2</hbox>
- *          <toolbarbutton class="highlighter-nodeinfobar-menu highlighter-nodeinfobar-button">…</toolbarbutton>
- *        </hbox>
- *        <box class="highlighter-nodeinfobar-arrow highlighter-nodeinfobar-arrow-bottom"/>
- *      </box>
- *    </box>
- *  </stack>
- *
- */
-
-
-/**
- * Constructor.
- *
- * @param aTarget The inspection target.
- * @param aInspector Inspector panel.
- * @param aToolbox The toolbox holding the inspector.
- */
-function LocalHighlighter(aTarget, aInspector, aToolbox)
-{
-  this.target = aTarget;
-  this.tab = aTarget.tab;
-  this.toolbox = aToolbox;
-  this.browser = this.tab.linkedBrowser;
-  this.chromeDoc = this.tab.ownerDocument;
-  this.chromeWin = this.chromeDoc.defaultView;
-  this.inspector = aInspector
-  this.layoutHelpers = new LayoutHelpers(this.browser.contentWindow);
-
-  EventEmitter.decorate(this);
-
-  this._init();
-}
-
-LocalHighlighter.prototype = {
-  get selection() {
-    return this.inspector.selection;
-  },
-
-  _init: function LocalHighlighter__init()
-  {
-    this.toggleLockState = this.toggleLockState.bind(this);
-    this.unlockAndFocus = this.unlockAndFocus.bind(this);
-    this.updateInfobar = this.updateInfobar.bind(this);
-    this.highlight = this.highlight.bind(this);
-
-    let stack = this.browser.parentNode;
-    this.win = this.browser.contentWindow;
-    this._highlighting = false;
-
-    this.highlighterContainer = this.chromeDoc.createElement("stack");
-    this.highlighterContainer.className = "highlighter-container";
-
-    this.outline = this.chromeDoc.createElement("box");
-    this.outline.className = "highlighter-outline";
-
-    let outlineContainer = this.chromeDoc.createElement("box");
-    outlineContainer.appendChild(this.outline);
-    outlineContainer.className = "highlighter-outline-container";
-
-    // The controlsBox will host the different interactive
-    // elements of the highlighter (buttons, toolbars, ...).
-    let controlsBox = this.chromeDoc.createElement("box");
-    controlsBox.className = "highlighter-controls";
-    this.highlighterContainer.appendChild(outlineContainer);
-    this.highlighterContainer.appendChild(controlsBox);
-
-    // Insert the highlighter right after the browser
-    stack.insertBefore(this.highlighterContainer, stack.childNodes[1]);
-
-    this.buildInfobar(controlsBox);
-
-    this.transitionDisabler = null;
-    this.pageEventsMuter = null;
-
-    this.selection.on("new-node", this.highlight);
-    this.selection.on("new-node", this.updateInfobar);
-    this.selection.on("pseudoclass", this.updateInfobar);
-    this.selection.on("attribute-changed", this.updateInfobar);
-
-    this.onToolSelected = function(event, id) {
-      if (id != "inspector") {
-        this.chromeWin.clearTimeout(this.pageEventsMuter);
-        this.detachMouseListeners();
-        this.disabled = true;
-        this.hide();
-      } else {
-        if (!this.locked) {
-          this.attachMouseListeners();
-        }
-        this.disabled = false;
-        this.show();
-      }
-    }.bind(this);
-    this.toolbox.on("select", this.onToolSelected);
-
-    this.hidden = true;
-    this.highlight();
-  },
-
-  /**
-   * Destroy the nodes. Remove listeners.
-   */
-  destroy: function LocalHighlighter_destroy()
-  {
-    this.inspectButton.removeEventListener("command", this.unlockAndFocus);
-    this.inspectButton = null;
-
-    this.toolbox.off("select", this.onToolSelected);
-    this.toolbox = null;
-
-    this.selection.off("new-node", this.highlight);
-    this.selection.off("new-node", this.updateInfobar);
-    this.selection.off("pseudoclass", this.updateInfobar);
-    this.selection.off("attribute-changed", this.updateInfobar);
-
-    this.detachMouseListeners();
-    this.detachPageListeners();
-
-    this.chromeWin.clearTimeout(this.transitionDisabler);
-    this.chromeWin.clearTimeout(this.pageEventsMuter);
-    this.boundCloseEventHandler = null;
-    this._contentRect = null;
-    this._highlightRect = null;
-    this._highlighting = false;
-    this.outline = null;
-    this.nodeInfo = null;
-    this.highlighterContainer.parentNode.removeChild(this.highlighterContainer);
-    this.highlighterContainer = null;
-    this.win = null
-    this.browser = null;
-    this.chromeDoc = null;
-    this.chromeWin = null;
-    this.tabbrowser = null;
-
-    this.emit("closed");
-  },
-
-  /**
-   * Show the outline, and select a node.
-   */
-  highlight: function LocalHighlighter_highlight()
-  {
-    if (this.selection.reason != "highlighter") {
-      this.lock();
-    }
-
-    let canHighlightNode = this.selection.isNode() &&
-                          this.selection.isConnected() &&
-                          this.selection.isElementNode();
-
-    if (canHighlightNode) {
-      if (this.selection.reason != "navigateaway") {
-        this.disabled = false;
-      }
-      this.show();
-      this.updateInfobar();
-      this.invalidateSize();
-      if (!this._highlighting &&
-          this.selection.reason != "highlighter") {
-        this.layoutHelpers.scrollIntoViewIfNeeded(this.selection.node);
-      }
-    } else {
-      this.disabled = true;
-      this.hide();
-    }
-  },
-
-  /**
-   * Update the highlighter size and position.
-   */
-  invalidateSize: function LocalHighlighter_invalidateSize()
-  {
-    let canHiglightNode = this.selection.isNode() &&
-                          this.selection.isConnected() &&
-                          this.selection.isElementNode();
-
-    if (!canHiglightNode)
-      return;
-
-    // The highlighter runs locally while the selection runs remotely,
-    // so we can't quite trust the selection's isConnected to protect us
-    // here, do the check manually.
-    if (!this.selection.node ||
-        !this.selection.node.ownerDocument ||
-        !this.selection.node.ownerDocument.defaultView) {
-      return;
-    }
-
-    let clientRect = this.selection.node.getBoundingClientRect();
-    let rect = this.layoutHelpers.getDirtyRect(this.selection.node);
-    this.highlightRectangle(rect);
-
-    this.moveInfobar();
-
-    if (this._highlighting) {
-      this.showOutline();
-      this.emit("highlighting");
-    }
-  },
-
-  /**
-   * Show the highlighter if it has been hidden.
-   */
-  show: function() {
-    if (!this.hidden || this.disabled) return;
-    this.showOutline();
-    this.showInfobar();
-    this.computeZoomFactor();
-    this.attachPageListeners();
-    this.invalidateSize();
-    this.hidden = false;
-  },
-
-  /**
-   * Hide the highlighter, the outline and the infobar.
-   */
-  hide: function() {
-    if (this.hidden) return;
-    this.hideOutline();
-    this.hideInfobar();
-    this.detachPageListeners();
-    this.hidden = true;
-  },
-
-  /**
-   * Is the highlighter visible?
-   *
-   * @return boolean
-   */
-  isHidden: function() {
-    return this.hidden;
-  },
-
-  /**
-   * Lock a node. Stops the inspection.
-   */
-  lock: function() {
-    if (this.locked === true) return;
-    this.outline.setAttribute("locked", "true");
-    this.nodeInfo.container.setAttribute("locked", "true");
-    this.detachMouseListeners();
-    this.locked = true;
-    this.emit("locked");
-  },
-
-  /**
-   * Start inspecting.
-   * Unlock the current node (if any), and select any node being hovered.
-   */
-  unlock: function() {
-    if (this.locked === false) return;
-    this.outline.removeAttribute("locked");
-    this.nodeInfo.container.removeAttribute("locked");
-    this.attachMouseListeners();
-    this.locked = false;
-    if (this.selection.isElementNode() &&
-        this.selection.isConnected()) {
-      this.showOutline();
-    }
-    this.emit("unlocked");
-  },
-
-  /**
-   * Toggle between locked and unlocked
-   */
-  toggleLockState: function() {
-    if (this.locked) {
-      this.startNode = this.selection.node;
-      this.unlockAndFocus();
-    } else {
-      this.selection.setNode(this.startNode);
-      this.lock();
-    }
-  },
-
-  /**
-   * Focus the browser before unlocking.
-   */
-  unlockAndFocus: function LocalHighlighter_unlockAndFocus() {
-    if (this.locked === false) return;
-    this.chromeWin.focus();
-    this.unlock();
-  },
-
-  /**
-   * Hide the infobar
-   */
-   hideInfobar: function LocalHighlighter_hideInfobar() {
-     this.nodeInfo.container.setAttribute("force-transitions", "true");
-     this.nodeInfo.container.setAttribute("hidden", "true");
-   },
-
-  /**
-   * Show the infobar
-   */
-   showInfobar: function LocalHighlighter_showInfobar() {
-     this.nodeInfo.container.removeAttribute("hidden");
-     this.moveInfobar();
-     this.nodeInfo.container.removeAttribute("force-transitions");
-   },
-
-  /**
-   * Hide the outline
-   */
-   hideOutline: function LocalHighlighter_hideOutline() {
-     this.outline.setAttribute("hidden", "true");
-   },
-
-  /**
-   * Show the outline
-   */
-   showOutline: function LocalHighlighter_showOutline() {
-     if (this._highlighting)
-       this.outline.removeAttribute("hidden");
-   },
-
-  /**
-   * Build the node Infobar.
-   *
-   * <box class="highlighter-nodeinfobar-container">
-   *   <box class="highlighter-nodeinfobar-arrow-top"/>
-   *   <hbox class="highlighter-nodeinfobar">
-   *     <toolbarbutton class="highlighter-nodeinfobar-button highlighter-nodeinfobar-inspectbutton"/>
-   *     <hbox class="highlighter-nodeinfobar-text">
-   *       <xhtml:span class="highlighter-nodeinfobar-tagname"/>
-   *       <xhtml:span class="highlighter-nodeinfobar-id"/>
-   *       <xhtml:span class="highlighter-nodeinfobar-classes"/>
-   *       <xhtml:span class="highlighter-nodeinfobar-pseudo-classes"/>
-   *     </hbox>
-   *     <toolbarbutton class="highlighter-nodeinfobar-button highlighter-nodeinfobar-menu"/>
-   *   </hbox>
-   *   <box class="highlighter-nodeinfobar-arrow-bottom"/>
-   * </box>
-   *
-   * @param nsIDOMElement aParent
-   *        The container of the infobar.
-   */
-  buildInfobar: function LocalHighlighter_buildInfobar(aParent)
-  {
-    let container = this.chromeDoc.createElement("box");
-    container.className = "highlighter-nodeinfobar-container";
-    container.setAttribute("position", "top");
-    container.setAttribute("disabled", "true");
-
-    let nodeInfobar = this.chromeDoc.createElement("hbox");
-    nodeInfobar.className = "highlighter-nodeinfobar";
-
-    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("http://www.w3.org/1999/xhtml", "span");
-    tagNameLabel.className = "highlighter-nodeinfobar-tagname";
-
-    let idLabel = this.chromeDoc.createElementNS("http://www.w3.org/1999/xhtml", "span");
-    idLabel.className = "highlighter-nodeinfobar-id";
-
-    let classesBox = this.chromeDoc.createElementNS("http://www.w3.org/1999/xhtml", "span");
-    classesBox.className = "highlighter-nodeinfobar-classes";
-
-    let pseudoClassesBox = this.chromeDoc.createElementNS("http://www.w3.org/1999/xhtml", "span");
-    pseudoClassesBox.className = "highlighter-nodeinfobar-pseudo-classes";
-
-    // Add some content to force a better boundingClientRect down below.
-    pseudoClassesBox.textContent = "&nbsp;";
-
-    // Create buttons
-
-    this.inspectButton = this.chromeDoc.createElement("toolbarbutton");
-    this.inspectButton.className = "highlighter-nodeinfobar-button highlighter-nodeinfobar-inspectbutton"
-    let toolbarInspectButton = this.inspector.panelDoc.getElementById("inspector-inspect-toolbutton");
-    this.inspectButton.setAttribute("tooltiptext", toolbarInspectButton.getAttribute("tooltiptext"));
-    this.inspectButton.addEventListener("command", this.toggleLockState);
-
-    let nodemenu = this.chromeDoc.createElement("toolbarbutton");
-    nodemenu.setAttribute("type", "menu");
-    nodemenu.className = "highlighter-nodeinfobar-button highlighter-nodeinfobar-menu"
-    nodemenu.setAttribute("tooltiptext",
-                          this.strings.GetStringFromName("nodeMenu.tooltiptext"));
-
-    nodemenu.onclick = function() {
-      this.inspector.showNodeMenu(nodemenu, "after_start");
-    }.bind(this);
-
-    // <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.addEventListener("mousedown", function(aEvent) {
-      // On click, show the node:
-      if (this.selection.isElementNode()) {
-        this.layoutHelpers.scrollIntoViewIfNeeded(this.selection.node);
-      }
-    }.bind(this), true);
-
-    texthbox.appendChild(tagNameLabel);
-    texthbox.appendChild(idLabel);
-    texthbox.appendChild(classesBox);
-    texthbox.appendChild(pseudoClassesBox);
-
-    nodeInfobar.appendChild(this.inspectButton);
-    nodeInfobar.appendChild(texthbox);
-    nodeInfobar.appendChild(nodemenu);
-
-    container.appendChild(arrowBoxTop);
-    container.appendChild(nodeInfobar);
-    container.appendChild(arrowBoxBottom);
-
-    aParent.appendChild(container);
-
-    let barHeight = container.getBoundingClientRect().height;
-
-    this.nodeInfo = {
-      tagNameLabel: tagNameLabel,
-      idLabel: idLabel,
-      classesBox: classesBox,
-      pseudoClassesBox: pseudoClassesBox,
-      container: container,
-      barHeight: barHeight,
-    };
-  },
-
-  /**
-   * Highlight a rectangular region.
-   *
-   * @param object aRect
-   *        The rectangle region to highlight.
-   * @returns boolean
-   *          True if the rectangle was highlighted, false otherwise.
-   */
-  highlightRectangle: function LocalHighlighter_highlightRectangle(aRect)
-  {
-    if (!aRect) {
-      this.unhighlight();
-      return;
-    }
-
-    let oldRect = this._contentRect;
-
-    if (oldRect && aRect.top == oldRect.top && aRect.left == oldRect.left &&
-        aRect.width == oldRect.width && aRect.height == oldRect.height) {
-      return; // same rectangle
-    }
-
-    let aRectScaled = this.layoutHelpers.getZoomedRect(this.win, aRect);
-
-    if (aRectScaled.left >= 0 && aRectScaled.top >= 0 &&
-        aRectScaled.width > 0 && aRectScaled.height > 0) {
-
-      this.showOutline();
-
-      // The bottom div and the right div are flexibles (flex=1).
-      // We don't need to resize them.
-      let top = "top:" + aRectScaled.top + "px;";
-      let left = "left:" + aRectScaled.left + "px;";
-      let width = "width:" + aRectScaled.width + "px;";
-      let height = "height:" + aRectScaled.height + "px;";
-      this.outline.setAttribute("style", top + left + width + height);
-
-      this._highlighting = true;
-    } else {
-      this.unhighlight();
-    }
-
-    this._contentRect = aRect; // save orig (non-scaled) rect
-    this._highlightRect = aRectScaled; // and save the scaled rect.
-
-    return;
-  },
-
-  /**
-   * Clear the highlighter surface.
-   */
-  unhighlight: function LocalHighlighter_unhighlight()
-  {
-    this._highlighting = false;
-    this.hideOutline();
-  },
-
-  /**
-   * Update node information (tagName#id.class)
-   */
-  updateInfobar: function LocalHighlighter_updateInfobar()
-  {
-    if (!this.selection.isElementNode()) {
-      this.nodeInfo.tagNameLabel.textContent = "";
-      this.nodeInfo.idLabel.textContent = "";
-      this.nodeInfo.classesBox.textContent = "";
-      this.nodeInfo.pseudoClassesBox.textContent = "";
-      return;
-    }
-
-    let node = this.selection.node;
-
-    // Tag name
-    this.nodeInfo.tagNameLabel.textContent = node.tagName;
-
-    // ID
-    this.nodeInfo.idLabel.textContent = node.id ? "#" + node.id : "";
-
-    // Classes
-    let classes = this.nodeInfo.classesBox;
-
-    classes.textContent = node.classList.length ?
-                            "." + Array.join(node.classList, ".") : "";
-
-    // Pseudo-classes
-    let pseudos = PSEUDO_CLASSES.filter(function(pseudo) {
-      return DOMUtils.hasPseudoClassLock(node, pseudo);
-    }, this);
-
-    let pseudoBox = this.nodeInfo.pseudoClassesBox;
-    pseudoBox.textContent = pseudos.join("");
-  },
-
-  /**
-   * Move the Infobar to the right place in the highlighter.
-   */
-  moveInfobar: function LocalHighlighter_moveInfobar()
-  {
-    if (this._highlightRect) {
-      let winHeight = this.win.innerHeight * this.zoom;
-      let winWidth = this.win.innerWidth * this.zoom;
-
-      let rect = {top: this._highlightRect.top,
-                  left: this._highlightRect.left,
-                  width: this._highlightRect.width,
-                  height: this._highlightRect.height};
-
-      rect.top = Math.max(rect.top, 0);
-      rect.left = Math.max(rect.left, 0);
-      rect.width = Math.max(rect.width, 0);
-      rect.height = Math.max(rect.height, 0);
-
-      rect.top = Math.min(rect.top, winHeight);
-      rect.left = Math.min(rect.left, winWidth);
-
-      this.nodeInfo.container.removeAttribute("disabled");
-      // Can the bar be above the node?
-      if (rect.top < this.nodeInfo.barHeight) {
-        // No. Can we move the toolbar under the node?
-        if (rect.top + rect.height +
-            this.nodeInfo.barHeight > winHeight) {
-          // No. Let's move it inside.
-          this.nodeInfo.container.style.top = rect.top + "px";
-          this.nodeInfo.container.setAttribute("position", "overlap");
-        } else {
-          // Yes. Let's move it under the node.
-          this.nodeInfo.container.style.top = rect.top + rect.height + "px";
-          this.nodeInfo.container.setAttribute("position", "bottom");
-        }
-      } else {
-        // Yes. Let's move it on top of the node.
-        this.nodeInfo.container.style.top =
-          rect.top - this.nodeInfo.barHeight + "px";
-        this.nodeInfo.container.setAttribute("position", "top");
-      }
-
-      let barWidth = this.nodeInfo.container.getBoundingClientRect().width;
-      let left = rect.left + rect.width / 2 - barWidth / 2;
-
-      // Make sure the whole infobar is visible
-      if (left < 0) {
-        left = 0;
-        this.nodeInfo.container.setAttribute("hide-arrow", "true");
-      } else {
-        if (left + barWidth > winWidth) {
-          left = winWidth - barWidth;
-          this.nodeInfo.container.setAttribute("hide-arrow", "true");
-        } else {
-          this.nodeInfo.container.removeAttribute("hide-arrow");
-        }
-      }
-      this.nodeInfo.container.style.left = left + "px";
-    } else {
-      this.nodeInfo.container.style.left = "0";
-      this.nodeInfo.container.style.top = "0";
-      this.nodeInfo.container.setAttribute("position", "top");
-      this.nodeInfo.container.setAttribute("hide-arrow", "true");
-    }
-  },
-
-  /**
-   * Store page zoom factor.
-   */
-  computeZoomFactor: function LocalHighlighter_computeZoomFactor() {
-    this.zoom =
-      this.win.QueryInterface(Ci.nsIInterfaceRequestor)
-      .getInterface(Ci.nsIDOMWindowUtils)
-      .fullZoom;
-  },
-
-  /////////////////////////////////////////////////////////////////////////
-  //// Event Handling
-
-  attachMouseListeners: function LocalHighlighter_attachMouseListeners()
-  {
-    this.browser.addEventListener("mousemove", this, true);
-    this.browser.addEventListener("click", this, true);
-    this.browser.addEventListener("dblclick", this, true);
-    this.browser.addEventListener("mousedown", this, true);
-    this.browser.addEventListener("mouseup", this, true);
-  },
-
-  detachMouseListeners: function LocalHighlighter_detachMouseListeners()
-  {
-    this.browser.removeEventListener("mousemove", this, true);
-    this.browser.removeEventListener("click", this, true);
-    this.browser.removeEventListener("dblclick", this, true);
-    this.browser.removeEventListener("mousedown", this, true);
-    this.browser.removeEventListener("mouseup", this, true);
-  },
-
-  attachPageListeners: function LocalHighlighter_attachPageListeners()
-  {
-    this.browser.addEventListener("resize", this, true);
-    this.browser.addEventListener("scroll", this, true);
-    this.browser.addEventListener("MozAfterPaint", this, true);
-  },
-
-  detachPageListeners: function LocalHighlighter_detachPageListeners()
-  {
-    this.browser.removeEventListener("resize", this, true);
-    this.browser.removeEventListener("scroll", this, true);
-    this.browser.removeEventListener("MozAfterPaint", this, true);
-  },
-
-  /**
-   * Generic event handler.
-   *
-   * @param nsIDOMEvent aEvent
-   *        The DOM event object.
-   */
-  handleEvent: function LocalHighlighter_handleEvent(aEvent)
-  {
-    switch (aEvent.type) {
-      case "click":
-        this.handleClick(aEvent);
-        break;
-      case "mousemove":
-        this.brieflyIgnorePageEvents();
-        this.handleMouseMove(aEvent);
-        break;
-      case "resize":
-        this.computeZoomFactor();
-        break;
-      case "MozAfterPaint":
-      case "scroll":
-        this.brieflyDisableTransitions();
-        this.invalidateSize();
-        break;
-      case "dblclick":
-      case "mousedown":
-      case "mouseup":
-        aEvent.stopPropagation();
-        aEvent.preventDefault();
-        break;
-    }
-  },
-
-  /**
-   * Disable the CSS transitions for a short time to avoid laggy animations
-   * during scrolling or resizing.
-   */
-  brieflyDisableTransitions: function LocalHighlighter_brieflyDisableTransitions()
-  {
-    if (this.transitionDisabler) {
-      this.chromeWin.clearTimeout(this.transitionDisabler);
-    } else {
-      this.outline.setAttribute("disable-transitions", "true");
-      this.nodeInfo.container.setAttribute("disable-transitions", "true");
-    }
-    this.transitionDisabler =
-      this.chromeWin.setTimeout(function() {
-        this.outline.removeAttribute("disable-transitions");
-        this.nodeInfo.container.removeAttribute("disable-transitions");
-        this.transitionDisabler = null;
-      }.bind(this), 500);
-  },
-
-  /**
-   * Don't listen to page events while inspecting with the mouse.
-   */
-  brieflyIgnorePageEvents: function LocalHighlighter_brieflyIgnorePageEvents()
-  {
-    // The goal is to keep smooth animations while inspecting.
-    // CSS Transitions might be interrupted because of a MozAfterPaint
-    // event that would triger an invalidateSize() call.
-    // So we don't listen to events that would trigger an invalidateSize()
-    // call.
-    //
-    // Side effect, zoom levels are not updated during this short period.
-    // It's very unlikely this would happen, but just in case, we call
-    // computeZoomFactor() when reattaching the events.
-    if (this.pageEventsMuter) {
-      this.chromeWin.clearTimeout(this.pageEventsMuter);
-    } else {
-      this.detachPageListeners();
-    }
-    this.pageEventsMuter =
-      this.chromeWin.setTimeout(function() {
-        this.attachPageListeners();
-        // Just in case the zoom level changed while ignoring the paint events
-        this.computeZoomFactor();
-        this.pageEventsMuter = null;
-      }.bind(this), 500);
-  },
-
-  /**
-   * Handle clicks.
-   *
-   * @param nsIDOMEvent aEvent
-   *        The DOM event.
-   */
-  handleClick: function LocalHighlighter_handleClick(aEvent)
-  {
-    // Stop inspection when the user clicks on a node.
-    if (aEvent.button == 0) {
-      this.lock();
-      let node = this.selection.node;
-      this.selection.setNode(node, "highlighter-lock");
-      aEvent.preventDefault();
-      aEvent.stopPropagation();
-    }
-  },
-
-  /**
-   * Handle mousemoves in panel.
-   *
-   * @param nsiDOMEvent aEvent
-   *        The MouseEvent triggering the method.
-   */
-  handleMouseMove: function LocalHighlighter_handleMouseMove(aEvent)
-  {
-    let doc = aEvent.target.ownerDocument;
-
-    // This should never happen, but just in case, we don't let the
-    // highlighter highlight browser nodes.
-    if (doc && doc != this.chromeDoc) {
-      let element = this.layoutHelpers.getElementFromPoint(aEvent.target.ownerDocument,
-        aEvent.clientX, aEvent.clientY);
-      if (element && element != this.selection.node) {
-        this.selection.setNode(element, "highlighter");
-      }
-    }
-  },
-};
-
-// BasicHighlighter. Doesn't implement any fancy features. Just change
-// the outline of the selected node. Works with remote target.
-
-function BasicHighlighter(aTarget, aInspector)
-{
-  this.walker = aInspector.walker;
-  this.selection = aInspector.selection;
-  this.highlight = this.highlight.bind(this);
-  this.selection.on("new-node-front", this.highlight);
-  EventEmitter.decorate(this);
-  this.locked = true;
-}
-
-BasicHighlighter.prototype = {
-  destroy: function() {
-    this.walker.highlight(null);
-    this.selection.off("new-node-front", this.highlight);
-    this.walker = null;
-    this.selection = null;
-  },
-  toggleLockState: function() {
-    this.locked = !this.locked;
-    if (this.locked) {
-      this.walker.cancelPick();
-    } else {
-      this.emit("unlocked");
-      this.walker.pick().then(
-        (node) => this._onPick(node),
-        () => this._onPick(null)
-      );
-    }
-  },
-  highlight: function() {
-    this.walker.highlight(this.selection.nodeFront);
-  },
-  _onPick: function(node) {
-    if (node) {
-      this.selection.setNodeFront(node);
-    }
-    this.locked = true;
-    this.emit("locked");
-  },
-  hide: function() {},
-  show: function() {},
-}
-
-///////////////////////////////////////////////////////////////////////////
-
-XPCOMUtils.defineLazyGetter(this, "DOMUtils", function () {
-  return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils)
-});
-
-XPCOMUtils.defineLazyGetter(LocalHighlighter.prototype, "strings", function () {
-    return Services.strings.createBundle(
-            "chrome://browser/locale/devtools/inspector.properties");
-});
--- a/browser/devtools/inspector/inspector-panel.js
+++ b/browser/devtools/inspector/inspector-panel.js
@@ -8,30 +8,26 @@ const {Cc, Ci, Cu, Cr} = require("chrome
 
 Cu.import("resource://gre/modules/Services.jsm");
 
 let promise = require("sdk/core/promise");
 let EventEmitter = require("devtools/shared/event-emitter");
 let {CssLogic} = require("devtools/styleinspector/css-logic");
 
 loader.lazyGetter(this, "MarkupView", () => require("devtools/markupview/markup-view").MarkupView);
-loader.lazyGetter(this, "Selection", () => require("devtools/inspector/selection").Selection);
 loader.lazyGetter(this, "HTMLBreadcrumbs", () => require("devtools/inspector/breadcrumbs").HTMLBreadcrumbs);
-loader.lazyGetter(this, "Highlighter", () => require("devtools/inspector/highlighter").Highlighter);
 loader.lazyGetter(this, "ToolSidebar", () => require("devtools/framework/sidebar").ToolSidebar);
 loader.lazyGetter(this, "SelectorSearch", () => require("devtools/inspector/selector-search").SelectorSearch);
-loader.lazyGetter(this, "InspectorFront", () => require("devtools/server/actors/inspector").InspectorFront);
 
 const LAYOUT_CHANGE_TIMER = 250;
 
 /**
  * Represents an open instance of the Inspector for a tab.
- * The inspector controls the highlighter, the breadcrumbs,
- * the markup view, and the sidebar (computed view, rule view
- * and layout view).
+ * The inspector controls the breadcrumbs, the markup view, and the sidebar
+ * (computed view, rule view, font view and layout view).
  *
  * Events:
  * - ready
  *      Fired when the inspector panel is opened for the first time and ready to
  *      use
  * - new-root
  *      Fired after a new root (navigation to a new page) event was fired by
  *      the walker, and taken into account by the inspector (after the markup
@@ -72,51 +68,57 @@ function InspectorPanel(iframeWindow, to
 exports.InspectorPanel = InspectorPanel;
 
 InspectorPanel.prototype = {
   /**
    * open is effectively an asynchronous constructor
    */
   open: function InspectorPanel_open() {
     return this.target.makeRemote().then(() => {
-      return this._getWalker();
+      return this._getPageStyle();
     }).then(() => {
       return this._getDefaultNodeForSelection();
     }).then(defaultSelection => {
       return this._deferredOpen(defaultSelection);
     }).then(null, console.error);
   },
 
+  get toolbox() {
+    return this._toolbox;
+  },
+
   get inspector() {
-    if (!this._target.form) {
-      throw new Error("Target.inspector requires an initialized remote actor.");
-    }
-    if (!this._inspector) {
-      this._inspector = InspectorFront(this._target.client, this._target.form);
-    }
-    return this._inspector;
+    return this._toolbox.inspector;
+  },
+
+  get walker() {
+    return this._toolbox.walker;
+  },
+
+  get selection() {
+    return this._toolbox.selection;
+  },
+
+  get isOuterHTMLEditable() {
+    return this._target.client.traits.editOuterHTML;
   },
 
   _deferredOpen: function(defaultSelection) {
     let deferred = promise.defer();
 
-    this.outerHTMLEditable = this._target.client.traits.editOuterHTML;
-
     this.onNewRoot = this.onNewRoot.bind(this);
     this.walker.on("new-root", this.onNewRoot);
 
     this.nodemenu = this.panelDoc.getElementById("inspector-node-popup");
     this.lastNodemenuItem = this.nodemenu.lastChild;
     this._setupNodeMenu = this._setupNodeMenu.bind(this);
     this._resetNodeMenu = this._resetNodeMenu.bind(this);
     this.nodemenu.addEventListener("popupshowing", this._setupNodeMenu, true);
     this.nodemenu.addEventListener("popuphiding", this._resetNodeMenu, true);
 
-    // Create an empty selection
-    this._selection = new Selection(this.walker);
     this.onNewSelection = this.onNewSelection.bind(this);
     this.selection.on("new-node-front", this.onNewSelection);
     this.onBeforeNewSelection = this.onBeforeNewSelection.bind(this);
     this.selection.on("before-new-node-front", this.onBeforeNewSelection);
     this.onDetached = this.onDetached.bind(this);
     this.selection.on("detached-front", this.onDetached);
 
     this.breadcrumbs = new HTMLBreadcrumbs(this);
@@ -149,37 +151,24 @@ InspectorPanel.prototype = {
 
       }.bind(this);
       this.target.on("thread-paused", this.updateDebuggerPausedWarning);
       this.target.on("thread-resumed", this.updateDebuggerPausedWarning);
       this._toolbox.on("select", this.updateDebuggerPausedWarning);
       this.updateDebuggerPausedWarning();
     }
 
-    this.highlighter = new Highlighter(this.target, this, this._toolbox);
-    let button = this.panelDoc.getElementById("inspector-inspect-toolbutton");
-    this.onLockStateChanged = function() {
-      if (this.highlighter.locked) {
-        button.removeAttribute("checked");
-        this._toolbox.raise();
-      } else {
-        button.setAttribute("checked", "true");
-      }
-    }.bind(this);
-    this.highlighter.on("locked", this.onLockStateChanged);
-    this.highlighter.on("unlocked", this.onLockStateChanged);
-
     this._initMarkup();
     this.isReady = false;
 
     this.once("markuploaded", function() {
       this.isReady = true;
 
       // All the components are initialized. Let's select a node.
-      this._selection.setNodeFront(defaultSelection);
+      this.selection.setNodeFront(defaultSelection, "inspector-open");
 
       this.markup.expandNode(this.selection.nodeFront);
 
       this.emit("ready");
       deferred.resolve(this);
     }.bind(this));
 
     this.setupSearchBox();
@@ -190,21 +179,18 @@ InspectorPanel.prototype = {
 
   _onBeforeNavigate: function() {
     this._defaultNode = null;
     this.selection.setNodeFront(null);
     this._destroyMarkup();
     this.isDirty = false;
   },
 
-  _getWalker: function() {
-    return this.inspector.getWalker().then(walker => {
-      this.walker = walker;
-      return this.inspector.getPageStyle();
-    }).then(pageStyle => {
+  _getPageStyle: function() {
+    return this._toolbox.inspector.getPageStyle().then(pageStyle => {
       this.pageStyle = pageStyle;
     });
   },
 
   /**
    * Return a promise that will resolve to the default node for selection.
    */
   _getDefaultNodeForSelection: function() {
@@ -234,23 +220,16 @@ InspectorPanel.prototype = {
         promise.reject(null);
       }
       this._defaultNode = node;
       return node;
     });
   },
 
   /**
-   * Selection object (read only)
-   */
-  get selection() {
-    return this._selection;
-  },
-
-  /**
    * Target getter.
    */
   get target() {
     return this._target;
   },
 
   /**
    * Target setter.
@@ -309,17 +288,16 @@ InspectorPanel.prototype = {
 
     let defaultTab = Services.prefs.getCharPref("devtools.inspector.activeSidebar");
 
     this._setDefaultSidebar = function(event, toolId) {
       Services.prefs.setCharPref("devtools.inspector.activeSidebar", toolId);
     }.bind(this);
 
     this.sidebar.on("select", this._setDefaultSidebar);
-    this.toggleHighlighter = this.toggleHighlighter.bind(this);
 
     this.sidebar.addTab("ruleview",
                         "chrome://browser/content/devtools/cssruleview.xhtml",
                         "ruleview" == defaultTab);
 
     this.sidebar.addTab("computedview",
                         "chrome://browser/content/devtools/computedview.xhtml",
                         "computedview" == defaultTab);
@@ -330,40 +308,35 @@ InspectorPanel.prototype = {
                           "fontinspector" == defaultTab);
     }
 
     this.sidebar.addTab("layoutview",
                         "chrome://browser/content/devtools/layoutview/view.xhtml",
                         "layoutview" == defaultTab);
 
     let ruleViewTab = this.sidebar.getTab("ruleview");
-    ruleViewTab.addEventListener("mouseover", this.toggleHighlighter, false);
-    ruleViewTab.addEventListener("mouseout", this.toggleHighlighter, false);
 
     this.sidebar.show();
   },
 
   /**
    * Reset the inspector on new root mutation.
    */
   onNewRoot: function InspectorPanel_onNewRoot() {
     this._defaultNode = null;
     this.selection.setNodeFront(null);
     this._destroyMarkup();
     this.isDirty = false;
 
     this._getDefaultNodeForSelection().then(defaultNode => {
-      if (this._destroyPromise) {
-        return;
-      }
       this.selection.setNodeFront(defaultNode, "navigateaway");
 
       this._initMarkup();
       this.once("markuploaded", () => {
-        if (this._destroyPromise) {
+        if (!this.markup) {
           return;
         }
         this.markup.expandNode(this.selection.nodeFront);
         this.setupSearchBox();
         this.emit("new-root");
       });
     });
   },
@@ -394,16 +367,20 @@ InspectorPanel.prototype = {
       return null;
     }
   },
 
   /**
    * When a new node is selected.
    */
   onNewSelection: function InspectorPanel_onNewSelection(event, value, reason) {
+    if (reason === "selection-destroy") {
+      return;
+    }
+
     this.cancelLayoutChange();
 
     // Wait for all the known tools to finish updating and then let the
     // client know.
     let selection = this.selection.nodeFront;
 
     // On any new selection made by the user, store the unique css selector
     // of the selected node so it can be restored after reload of the same page
@@ -495,89 +472,66 @@ InspectorPanel.prototype = {
     this.breadcrumbs.cutAfter(this.breadcrumbs.indexOf(parentNode));
     this.selection.setNodeFront(parentNode ? parentNode : this._defaultNode, "detached");
   },
 
   /**
    * Destroy the inspector.
    */
   destroy: function InspectorPanel__destroy() {
-    if (this._destroyPromise) {
-      return this._destroyPromise;
+    if (this._panelDestroyer) {
+      return this._panelDestroyer.promise;
     }
-
-    if (this.highlighter) {
-      this.highlighter.off("locked", this.onLockStateChanged);
-      this.highlighter.off("unlocked", this.onLockStateChanged);
-      this.highlighter.destroy();
-    }
-
-    delete this.onLockStateChanged;
+    this._panelDestroyer = promise.defer();
 
     if (this.walker) {
       this.walker.off("new-root", this.onNewRoot);
-      this._destroyPromise = this.walker.release()
-        .then(() => this._inspector.destroy(),
-              (e) => {
-                console.error("Walker.release() failed: " + e);
-                return this._inspector.destroy();
-              })
-        .then(() => {
-          this._inspector = null;
-        }, console.error);
-
-      delete this.walker;
-      delete this.pageStyle;
-    } else {
-      this._destroyPromise = promise.resolve(null);
+      this.pageStyle = null;
     }
 
     this.cancelUpdate();
     this.cancelLayoutChange();
 
     if (this.browser) {
       this.browser.removeEventListener("resize", this.scheduleLayoutChange, true);
       this.browser = null;
     }
 
     this.target.off("will-navigate", this._onBeforeNavigate);
 
     this.target.off("thread-paused", this.updateDebuggerPausedWarning);
     this.target.off("thread-resumed", this.updateDebuggerPausedWarning);
     this._toolbox.off("select", this.updateDebuggerPausedWarning);
 
-    this._toolbox = null;
-
     this.sidebar.off("select", this._setDefaultSidebar);
     this.sidebar.destroy();
     this.sidebar = null;
 
     this.nodemenu.removeEventListener("popupshowing", this._setupNodeMenu, true);
     this.nodemenu.removeEventListener("popuphiding", this._resetNodeMenu, true);
     this.breadcrumbs.destroy();
     this.searchSuggestions.destroy();
-    delete this.searchBox;
+    this.searchBox = null;
     this.selection.off("new-node-front", this.onNewSelection);
     this.selection.off("before-new-node", this.onBeforeNewSelection);
     this.selection.off("before-new-node-front", this.onBeforeNewSelection);
     this.selection.off("detached-front", this.onDetached);
     this._destroyMarkup();
-    this._selection.destroy();
-    this._selection = null;
     this.panelWin.inspector = null;
     this.target = null;
     this.panelDoc = null;
     this.panelWin = null;
     this.breadcrumbs = null;
     this.searchSuggestions = null;
     this.lastNodemenuItem = null;
     this.nodemenu = null;
-    this.highlighter = null;
+    this._toolbox = null;
 
-    return this._destroyPromise;
+    this._panelDestroyer.resolve(null);
+    return this._panelDestroyer.promise;
   },
 
   /**
    * Show the node menu.
    */
   showNodeMenu: function InspectorPanel_showNodeMenu(aButton, aPosition, aExtraItems) {
     if (aExtraItems) {
       for (let item of aExtraItems) {
@@ -628,17 +582,17 @@ InspectorPanel.prototype = {
       copyOuterHTML.removeAttribute("disabled");
     } else {
       unique.setAttribute("disabled", "true");
       copyInnerHTML.setAttribute("disabled", "true");
       copyOuterHTML.setAttribute("disabled", "true");
     }
 
     let editHTML = this.panelDoc.getElementById("node-menu-edithtml");
-    if (this.outerHTMLEditable && selectionIsElement) {
+    if (this.isOuterHTMLEditable && selectionIsElement) {
       editHTML.removeAttribute("disabled");
     } else {
       editHTML.setAttribute("disabled", "true");
     }
   },
 
   _resetNodeMenu: function InspectorPanel_resetNodeMenu() {
     // Remove any extra items
@@ -681,74 +635,58 @@ InspectorPanel.prototype = {
     this.markup = new MarkupView(this, this._markupFrame, controllerWindow);
 
     this.emit("markuploaded");
   },
 
   _destroyMarkup: function InspectorPanel__destroyMarkup() {
     if (this._boundMarkupFrameLoad) {
       this._markupFrame.removeEventListener("load", this._boundMarkupFrameLoad, true);
-      delete this._boundMarkupFrameLoad;
+      this._boundMarkupFrameLoad = null;
     }
 
     if (this.markup) {
       this.markup.destroy();
-      delete this.markup;
+      this.markup = null;
     }
 
     if (this._markupFrame) {
       this._markupFrame.parentNode.removeChild(this._markupFrame);
-      delete this._markupFrame;
+      this._markupFrame = null;
     }
 
     this._markupBox = null;
   },
 
   /**
    * Toggle a pseudo class.
    */
   togglePseudoClass: function InspectorPanel_togglePseudoClass(aPseudo) {
     if (this.selection.isElementNode()) {
       let node = this.selection.nodeFront;
       if (node.hasPseudoClassLock(aPseudo)) {
-        return this.walker.removePseudoClassLock(node, aPseudo, { parents: true });
+        return this.walker.removePseudoClassLock(node, aPseudo, {parents: true});
       }
 
       let hierarchical = aPseudo == ":hover" || aPseudo == ":active";
-      return this.walker.addPseudoClassLock(node, aPseudo, { parents: hierarchical });
+      return this.walker.addPseudoClassLock(node, aPseudo, {parents: hierarchical});
     }
   },
 
   /**
    * Clear any pseudo-class locks applied to the current hierarchy.
    */
   clearPseudoClasses: function InspectorPanel_clearPseudoClasses() {
     if (!this.walker) {
       return;
     }
     return this.walker.clearPseudoClassLocks().then(null, console.error);
   },
 
   /**
-   * Toggle the highlighter when ruleview is hovered.
-   */
-  toggleHighlighter: function InspectorPanel_toggleHighlighter(event)
-  {
-    if (!this.highlighter) {
-      return;
-    }
-    if (event.type == "mouseover") {
-      this.highlighter.hide();
-    }
-    else if (event.type == "mouseout") {
-      this.highlighter.show();
-    }
-  },
-
-  /**
    * Edit the outerHTML of the selected Node.
    */
   editHTML: function InspectorPanel_editHTML()
   {
     if (!this.selection.isNode()) {
       return;
     }
     if (this.markup) {
--- a/browser/devtools/inspector/inspector.xul
+++ b/browser/devtools/inspector/inspector.xul
@@ -70,20 +70,16 @@
     </menupopup>
   </popupset>
 
   <box flex="1" class="devtools-responsive-container theme-body">
     <vbox flex="1">
       <toolbar id="inspector-toolbar"
         class="devtools-toolbar"
         nowindowdrag="true">
-        <toolbarbutton id="inspector-inspect-toolbutton"
-          tooltiptext="&inspector.selectButton.tooltip;"
-          class="devtools-toolbarbutton"
-          oncommand="inspector.highlighter.toggleLockState()"/>
         <arrowscrollbox id="inspector-breadcrumbs"
           class="breadcrumbs-widget-container"
           flex="1" orient="horizontal"
           clicktoscroll="true"/>
         <textbox id="inspector-searchbox"
           type="search"
           timeout="50"
           class="devtools-searchinput"
deleted file mode 100644
--- a/browser/devtools/inspector/selection.js
+++ /dev/null
@@ -1,296 +0,0 @@
-/* -*- Mode: Javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim: set ft=javascript 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";
-
-const {Cu, Ci} = require("chrome");
-let EventEmitter = require("devtools/shared/event-emitter");
-
-/**
- * API
- *
- *   new Selection(walker=null, node=null, track={attributes,detached});
- *   destroy()
- *   node (readonly)
- *   setNode(node, origin="unknown")
- *
- * Helpers:
- *
- *   window
- *   document
- *   isRoot()
- *   isNode()
- *   isHTMLNode()
- *
- * Check the nature of the node:
- *
- *   isElementNode()
- *   isAttributeNode()
- *   isTextNode()
- *   isCDATANode()
- *   isEntityRefNode()
- *   isEntityNode()
- *   isProcessingInstructionNode()
- *   isCommentNode()
- *   isDocumentNode()
- *   isDocumentTypeNode()
- *   isDocumentFragmentNode()
- *   isNotationNode()
- *
- * Events:
- *   "new-node" when the inner node changed
- *   "before-new-node" when the inner node is set to change
- *   "attribute-changed" when an attribute is changed (only if tracked)
- *   "detached" when the node (or one of its parents) is removed from the document (only if tracked)
- *   "reparented" when the node (or one of its parents) is moved under a different node (only if tracked)
- */
-
-/**
- * A Selection object. Hold a reference to a node.
- * Includes some helpers, fire some helpful events.
- *
- * @param node Inner node.
- *    Can be null. Can be (un)set in the future via the "node" property;
- * @param trackAttribute Tell if events should be fired when the attributes of
- *    the node change.
- *
- */
-function Selection(walker, node=null, track={attributes:true,detached:true}) {
-  EventEmitter.decorate(this);
-
-  this._onMutations = this._onMutations.bind(this);
-  this.track = track;
-  this.setWalker(walker);
-  this.setNode(node);
-}
-
-exports.Selection = Selection;
-
-Selection.prototype = {
-  _walker: null,
-  _node: null,
-
-  _onMutations: function(mutations) {
-    let attributeChange = false;
-    let pseudoChange = false;
-    let detached = false;
-    let parentNode = null;
-
-    for (let m of mutations) {
-      if (!attributeChange && m.type == "attributes") {
-        attributeChange = true;
-      }
-      if (m.type == "childList") {
-        if (!detached && !this.isConnected()) {
-          if (this.isNode()) {
-            parentNode = m.target;
-          }
-          detached = true;
-        }
-      }
-      if (m.type == "pseudoClassLock") {
-        pseudoChange = true;
-      }
-    }
-
-    // Fire our events depending on what changed in the mutations array
-    if (attributeChange) {
-      this.emit("attribute-changed");
-    }
-    if (pseudoChange) {
-      this.emit("pseudoclass");
-    }
-    if (detached) {
-      let rawNode = null;
-      if (parentNode && parentNode.isLocal_toBeDeprecated()) {
-        rawNode = parentNode.rawNode();
-      }
-
-      this.emit("detached", rawNode, null);
-      this.emit("detached-front", parentNode);
-    }
-  },
-
-  destroy: function() {
-    this.setNode(null);
-    this.setWalker(null);
-  },
-
-  setWalker: function(walker) {
-    if (this._walker) {
-      this._walker.off("mutations", this._onMutations);
-    }
-    this._walker = walker;
-    if (this._walker) {
-      this._walker.on("mutations", this._onMutations);
-    }
-  },
-
-  // Not remote-safe
-  setNode: function(value, reason="unknown") {
-    if (value) {
-      value = this._walker.frontForRawNode(value);
-    }
-    this.setNodeFront(value, reason);
-  },
-
-  // Not remote-safe
-  get node() {
-    return this._node;
-  },
-
-  // Not remote-safe
-  get window() {
-    if (this.isNode()) {
-      return this.node.ownerDocument.defaultView;
-    }
-    return null;
-  },
-
-  // Not remote-safe
-  get document() {
-    if (this.isNode()) {
-      return this.node.ownerDocument;
-    }
-    return null;
-  },
-
-  setNodeFront: function(value, reason="unknown") {
-    this.reason = reason;
-    if (value !== this._nodeFront) {
-      let rawValue = null;
-      if (value && value.isLocal_toBeDeprecated()) {
-        rawValue = value.rawNode();
-      }
-      this.emit("before-new-node", rawValue, reason);
-      this.emit("before-new-node-front", value, reason);
-      let previousNode = this._node;
-      let previousFront = this._nodeFront;
-      this._node = rawValue;
-      this._nodeFront = value;
-      this.emit("new-node", previousNode, this.reason);
-      this.emit("new-node-front", value, this.reason);
-    }
-  },
-
-  get documentFront() {
-    return this._walker.document(this._nodeFront);
-  },
-
-  get nodeFront() {
-    return this._nodeFront;
-  },
-
-  isRoot: function() {
-    return this.isNode() &&
-           this.isConnected() &&
-           this._nodeFront.isDocumentElement;
-  },
-
-  isNode: function() {
-    if (!this._nodeFront) {
-      return false;
-    }
-
-    // As long as tools are still accessing node.rawNode(),
-    // this needs to stay here.
-    if (this._node && Cu.isDeadWrapper(this._node)) {
-      return false;
-    }
-
-    return true;
-  },
-
-  isLocal: function() {
-    return !!this._node;
-  },
-
-  isConnected: function() {
-    let node = this._nodeFront;
-    if (!node || !node.actorID) {
-      return false;
-    }
-
-    // As long as there are still tools going around
-    // accessing node.rawNode, this needs to stay.
-    let rawNode = null;
-    if (node.isLocal_toBeDeprecated()) {
-      rawNode = node.rawNode();
-    }
-    if (rawNode) {
-      try {
-        let doc = this.document;
-        return (doc && doc.defaultView && doc.documentElement.contains(rawNode));
-      } catch (e) {
-        // "can't access dead object" error
-        return false;
-      }
-    }
-
-    while(node) {
-      if (node === this._walker.rootNode) {
-        return true;
-      }
-      node = node.parentNode();
-    };
-    return false;
-  },
-
-  isHTMLNode: function() {
-    let xhtml_ns = "http://www.w3.org/1999/xhtml";
-    return this.isNode() && this.node.namespaceURI == xhtml_ns;
-  },
-
-  // Node type
-
-  isElementNode: function() {
-    return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.ELEMENT_NODE;
-  },
-
-  isAttributeNode: function() {
-    return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.ATTRIBUTE_NODE;
-  },
-
-  isTextNode: function() {
-    return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.TEXT_NODE;
-  },
-
-  isCDATANode: function() {
-    return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.CDATA_SECTION_NODE;
-  },
-
-  isEntityRefNode: function() {
-    return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.ENTITY_REFERENCE_NODE;
-  },
-
-  isEntityNode: function() {
-    return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.ENTITY_NODE;
-  },
-
-  isProcessingInstructionNode: function() {
-    return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.PROCESSING_INSTRUCTION_NODE;
-  },
-
-  isCommentNode: function() {
-    return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.PROCESSING_INSTRUCTION_NODE;
-  },
-
-  isDocumentNode: function() {
-    return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.DOCUMENT_NODE;
-  },
-
-  isDocumentTypeNode: function() {
-    return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.DOCUMENT_TYPE_NODE;
-  },
-
-  isDocumentFragmentNode: function() {
-    return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.DOCUMENT_FRAGMENT_NODE;
-  },
-
-  isNotationNode: function() {
-    return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.NOTATION_NODE;
-  },
-};
--- a/browser/devtools/inspector/test/browser.ini
+++ b/browser/devtools/inspector/test/browser.ini
@@ -19,24 +19,22 @@ support-files =
 [browser_inspector_bug_672902_keyboard_shortcuts.js]
 [browser_inspector_bug_674871.js]
 [browser_inspector_bug_699308_iframe_navigation.js]
 [browser_inspector_bug_817558_delete_node.js]
 [browser_inspector_bug_831693_combinator_suggestions.js]
 [browser_inspector_bug_831693_input_suggestion.js]
 # [browser_inspector_bug_831693_searchbox_panel_navigation.js]
 # Disabled for too many intermittent failures (bug 851349)
-[browser_inspector_bug_835722_infobar_reappears.js]
 [browser_inspector_bug_840156_destroy_after_navigation.js]
 [browser_inspector_changes.js]
 [browser_inspector_cmd_inspect.js]
 [browser_inspector_dead_node_exception.js]
 [browser_inspector_destroyselection.js]
 [browser_inspector_highlighter.js]
-[browser_inspector_highlighter_autohide.js]
 [browser_inspector_iframeTest.js]
 [browser_inspector_infobar.js]
 [browser_inspector_initialization.js]
 [browser_inspector_invalidate.js]
 [browser_inspector_menu.js]
 [browser_inspector_navigation.js]
 [browser_inspector_pseudoClass_menu.js]
 [browser_inspector_pseudoclass_lock.js]
--- a/browser/devtools/inspector/test/browser_inspector_basic_highlighter.js
+++ b/browser/devtools/inspector/test/browser_inspector_basic_highlighter.js
@@ -1,93 +1,84 @@
 /* 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/. */
 
-
 function test() {
-  let inspector, doc;
+  let inspector, doc, toolbox;
   let {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
   let {require} = devtools;
   let promise = require("sdk/core/promise");
-  let { Task } = Cu.import("resource://gre/modules/Task.jsm", {});
+  let {Task} = Cu.import("resource://gre/modules/Task.jsm", {});
 
   waitForExplicitFinish();
 
   gBrowser.selectedTab = gBrowser.addTab();
   gBrowser.selectedBrowser.addEventListener("load", function onload() {
     gBrowser.selectedBrowser.removeEventListener("load", onload, true);
     doc = content.document;
     waitForFocus(setupTest, content);
   }, true);
 
-  content.location = "data:text/html,<h1>foo<h1><h2>bar</h2>";
+  content.location = "data:text/html,<h1>foo</h1><h2>bar</h2>";
 
   function setupTest() {
-    let h = require("devtools/inspector/highlighter");
-    h._forceBasic.value = true;
-    openInspector(runTests);
+    openInspector((aInspector, aToolbox) => {
+      toolbox = aToolbox;
+      inspector = aInspector;
+      inspector.selection.setNode(doc.querySelector("h2"), null);
+      inspector.once("inspector-updated", runTests);
+    });
   }
 
   function runTests(aInspector) {
-    inspector = aInspector;
-
+    getHighlighterOutline().setAttribute("disable-transitions", "true");
     Task.spawn(function() {
-      yield selectH1();
-      yield verifyH1Selected();
-      yield deselect();
-      yield verifyNoNodeSelected();
-
-      yield selectH1();
-      yield verifyH1Selected();
-      yield destroyInspector();
-      yield verifyNoNodeSelected();
+      yield hoverH1InMarkupView();
+      yield assertH1Highlighted();
+      yield mouseLeaveMarkupView();
+      yield assertNoNodeHighlighted();
 
       finishUp();
     }).then(null, Cu.reportError);
   }
 
-  function selectH1() {
+  function hoverH1InMarkupView() {
     let deferred = promise.defer();
-    let h1 = doc.querySelector("h1");
-    inspector.selection.once("new-node-front", () => {
-      executeSoon(deferred.resolve);
-    });
-    inspector.selection.setNode(h1);
+
+    let container = getContainerForRawNode(inspector.markup, doc.querySelector("h1"));
+    EventUtils.synthesizeMouse(container.tagLine, 2, 2, {type: "mousemove"},
+      inspector.markup.doc.defaultView);
+    inspector.markup.once("node-highlight", deferred.resolve);
+
     return deferred.promise;
   }
 
-  function verifyH1Selected() {
-    let h1 = doc.querySelector("h1");
-    let nodes = doc.querySelectorAll(":-moz-devtools-highlighted");
-    is(nodes.length, 1, "only one node selected");
-    is(nodes[0], h1, "h1 selected");
+  function assertH1Highlighted() {
+    ok(isHighlighting(), "The highlighter is shown on a markup container hover");
+    is(getHighlitNode(), doc.querySelector("h1"), "The highlighter highlights the right node");
     return promise.resolve();
   }
 
-  function deselect() {
+  function mouseLeaveMarkupView() {
     let deferred = promise.defer();
-    inspector.selection.once("new-node-front", () => {
-      executeSoon(deferred.resolve);
-    });
-    inspector.selection.setNode(null);
+
+    // Find another element to mouseover over in order to leave the markup-view
+    let btn = toolbox.doc.querySelector(".toolbox-dock-button");
+
+    EventUtils.synthesizeMouse(btn, 2, 2, {type: "mousemove"},
+      toolbox.doc.defaultView);
+    executeSoon(deferred.resolve);
+
     return deferred.promise;
   }
 
-  function destroyInspector() {
-    return inspector.destroy();
-  }
-
-  function verifyNoNodeSelected() {
-    is(doc.querySelectorAll(":-moz-devtools-highlighted").length, 0, "no node selected");
+  function assertNoNodeHighlighted() {
+    ok(!isHighlighting(), "After the mouse left the markup view, the highlighter is hidden");
     return promise.resolve();
   }
 
   function finishUp() {
-    let h = require("devtools/inspector/highlighter");
-    h._forceBasic.value = false;
-    inspector = doc = null;
+    inspector = doc = toolbox = null;
     gBrowser.removeCurrentTab();
     finish();
   }
 }
-
-
--- a/browser/devtools/inspector/test/browser_inspector_breadcrumbs.js
+++ b/browser/devtools/inspector/test/browser_inspector_breadcrumbs.js
@@ -52,17 +52,20 @@ function test()
 
   function nodeSelected()
   {
     performTest();
     cursor++;
 
     if (cursor >= nodes.length) {
       inspector.off("breadcrumbs-updated", nodeSelected);
-      finishUp();
+      // breadcrumbs-updated is an event that is fired before the rest of the
+      // inspector is updated, so there'll be hanging connections if we finish
+      // up before waiting for everything to end.
+      inspector.once("inspector-updated", finishUp);
     } else {
       let node = nodes[cursor].node;
       inspector.selection.setNode(node);
     }
   }
 
   function performTest()
   {
--- a/browser/devtools/inspector/test/browser_inspector_bug_665880.js
+++ b/browser/devtools/inspector/test/browser_inspector_bug_665880.js
@@ -1,52 +1,45 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
-
-function test()
-{
+function test() {
   waitForExplicitFinish();
   ignoreAllUncaughtExceptions();
 
   let doc;
   let objectNode;
 
   gBrowser.selectedTab = gBrowser.addTab();
   gBrowser.selectedBrowser.addEventListener("load", function() {
     gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
     doc = content.document;
     waitForFocus(setupObjectInspectionTest, content);
   }, true);
 
   content.location = "data:text/html,<object style='padding: 100px'><p>foobar</p></object>";
 
-  function setupObjectInspectionTest()
-  {
+  function setupObjectInspectionTest() {
     objectNode = doc.querySelector("object");
     ok(objectNode, "we have the object node");
     openInspector(runObjectInspectionTest);
   }
 
-  function runObjectInspectionTest(inspector)
-  {
-    inspector.highlighter.once("locked", performTestComparison);
-    inspector.highlighter.unlock();
+  function runObjectInspectionTest(inspector) {
+    inspector.once("inspector-updated", performTestComparison);
     inspector.selection.setNode(objectNode, "");
   }
 
-  function performTestComparison()
-  {
+  function performTestComparison() {
     is(getActiveInspector().selection.node, objectNode, "selection matches node");
     let target = TargetFactory.forTab(gBrowser.selectedTab);
     executeSoon(function() {
       gDevTools.closeToolbox(target);
       finishUp();
     });
   }
 
-
   function finishUp() {
     doc = objectNode = null;
     gBrowser.removeCurrentTab();
     finish();
   }
 }
--- a/browser/devtools/inspector/test/browser_inspector_bug_674871.js
+++ b/browser/devtools/inspector/test/browser_inspector_bug_674871.js
@@ -2,16 +2,17 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 function test()
 {
   waitForExplicitFinish();
 
   let doc;
   let iframeNode, iframeBodyNode;
+  let inspector;
 
   let iframeSrc = "<style>" +
                   "body {" +
                   "margin:0;" +
                   "height:100%;" +
                   "background-color:red" +
                   "}" +
                   "</style>" +
@@ -40,62 +41,68 @@ function test()
   content.location = "data:text/html," + docSrc;
 
   function setupTest()
   {
     iframeNode = doc.querySelector("iframe");
     iframeBodyNode = iframeNode.contentDocument.querySelector("body");
     ok(iframeNode, "we have the iframe node");
     ok(iframeBodyNode, "we have the body node");
-    openInspector(runTests);
-  }
-
-  function runTests(inspector)
-  {
-    inspector.highlighter.unlock();
-    executeSoon(function() {
-      inspector.highlighter.once("highlighting", isTheIframeSelected);
-      moveMouseOver(iframeNode, 1, 1);
+    openInspector(aInspector => {
+      inspector = aInspector;
+      // Make sure the highlighter is shown so we can disable transitions
+      inspector.toolbox.highlighter.showBoxModel(getNodeFront(doc.body)).then(() => {
+        getHighlighterOutline().setAttribute("disable-transitions", "true");
+        runTests();
+      });
     });
   }
 
-  function isTheIframeSelected()
+  function runTests()
   {
-    let inspector = getActiveInspector();
+    inspector.toolbox.startPicker().then(() => {
+      moveMouseOver(iframeNode, 1, 1, isTheIframeHighlighted);
+    });
+  }
 
-    is(inspector.selection.node, iframeNode, "selection matches node");
+  function isTheIframeHighlighted()
+  {
+    let outlineRect = getHighlighterOutlineRect();
+    let iframeRect = iframeNode.getBoundingClientRect();
+    for (let dim of ["width", "height", "top", "left"]) {
+      is(Math.floor(outlineRect[dim]), Math.floor(iframeRect[dim]), "Outline dimension is correct");
+    }
+
     iframeNode.style.marginBottom = doc.defaultView.innerHeight + "px";
     doc.defaultView.scrollBy(0, 40);
 
-    executeSoon(function() {
-      inspector.selection.once("new-node", isTheIframeContentSelected);
-      moveMouseOver(iframeNode, 40, 40);
+    moveMouseOver(iframeNode, 40, 40, isTheIframeContentHighlighted);
+  }
+
+  function isTheIframeContentHighlighted()
+  {
+    is(getHighlitNode(), iframeBodyNode, "highlighter shows the right node");
+
+    // 184 == 200 + 11(border) + 13(padding) - 40(scroll)
+    let outlineRect = getHighlighterOutlineRect();
+    is(outlineRect.height, 184, "highlighter height");
+
+    inspector.toolbox.stopPicker().then(() => {
+      let target = TargetFactory.forTab(gBrowser.selectedTab);
+      gDevTools.closeToolbox(target);
+      finishUp();
     });
   }
 
-  function isTheIframeContentSelected()
+  function finishUp()
   {
-    let inspector = getActiveInspector();
-    is(inspector.selection.node, iframeBodyNode, "selection matches node");
-    // 184 == 200 + 11(border) + 13(padding) - 40(scroll)
-    is(inspector.highlighter._highlightRect.height, 184,
-      "highlighter height");
-
-    let target = TargetFactory.forTab(gBrowser.selectedTab);
-    gDevTools.closeToolbox(target);
-    finishUp();
-  }
-
-  function finishUp() {
-    doc = iframeNode = iframeBodyNode = null;
+    doc = inspector = iframeNode = iframeBodyNode = null;
     gBrowser.removeCurrentTab();
     finish();
   }
 
-
-  function moveMouseOver(aElement, x, y)
+  function moveMouseOver(aElement, x, y, cb)
   {
     EventUtils.synthesizeMouse(aElement, x, y, {type: "mousemove"},
                                aElement.ownerDocument.defaultView);
+    inspector.toolbox.once("picker-node-hovered", cb);
   }
-
 }
-
--- a/browser/devtools/inspector/test/browser_inspector_bug_699308_iframe_navigation.js
+++ b/browser/devtools/inspector/test/browser_inspector_bug_699308_iframe_navigation.js
@@ -3,58 +3,76 @@
 
 function test() {
   let iframe;
   let iframeLoads = 0;
   let checksAfterLoads = false;
   let inspector;
 
   function startTest() {
-    openInspector(runInspectorTests);
+    openInspector(aInspector => {
+      inspector = aInspector;
+      runInspectorTests();
+    });
   }
 
-  function runInspectorTests(aInspector) {
-    inspector = aInspector;
+  function showHighlighter(cb) {
+    inspector.toolbox.startPicker().then(() => {
+      EventUtils.synthesizeMouse(content.document.body, 1, 1,
+        {type: "mousemove"}, content);
+      inspector.toolbox.once("picker-node-hovered", () => {
+        executeSoon(() => {
+          getHighlighterOutline().setAttribute("disable-transitions", "true");
+          cb();
+        });
+      });
+    });
+  }
 
+  function runInspectorTests() {
     iframe = content.document.querySelector("iframe");
     ok(iframe, "found the iframe element");
 
-    ok(inspector.highlighter._highlighting, "Inspector is highlighting");
+    showHighlighter(() => {
+      ok(isHighlighting(), "Inspector is highlighting");
 
-    iframe.addEventListener("load", onIframeLoad, false);
+      iframe.addEventListener("load", onIframeLoad, false);
 
-    executeSoon(function() {
-      iframe.contentWindow.location = "javascript:location.reload()";
+      executeSoon(function() {
+        iframe.contentWindow.location = "javascript:location.reload()";
+      });
     });
   }
 
   function onIframeLoad() {
     if (++iframeLoads != 2) {
       executeSoon(function() {
         iframe.contentWindow.location = "javascript:location.reload()";
       });
       return;
     }
 
     iframe.removeEventListener("load", onIframeLoad, false);
 
-    ok(inspector.highlighter._highlighting, "Inspector is highlighting after iframe nav");
+    ok(isHighlighting(), "Inspector is highlighting after iframe nav");
 
     checksAfterLoads = true;
 
     finishTest();
   }
 
   function finishTest() {
     is(iframeLoads, 2, "iframe loads");
     ok(checksAfterLoads, "the Inspector tests got the chance to run after iframe reloads");
 
-    iframe = null;
-    gBrowser.removeCurrentTab();
-    executeSoon(finish);
+    inspector.toolbox.stopPicker().then(() => {
+      iframe = null;
+      gBrowser.removeCurrentTab();
+      executeSoon(finish);
+    });
   }
 
   waitForExplicitFinish();
 
   gBrowser.selectedTab = gBrowser.addTab();
   gBrowser.selectedBrowser.addEventListener("load", function onBrowserLoad() {
     gBrowser.selectedBrowser.removeEventListener("load", onBrowserLoad, true);
     waitForFocus(startTest, content);
deleted file mode 100644
--- a/browser/devtools/inspector/test/browser_inspector_bug_835722_infobar_reappears.js
+++ /dev/null
@@ -1,103 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
-   http://creativecommons.org/publicdomain/zero/1.0/ */
-
-function test() {
-  let inspector, utils;
-
-  function startLocationTests() {
-    openInspector(runInspectorTests);
-  }
-
-  function runInspectorTests(aInspector) {
-    inspector = aInspector;
-    utils = inspector.panelWin
-                     .QueryInterface(Ci.nsIInterfaceRequestor)
-                     .getInterface(Ci.nsIDOMWindowUtils);
-    ok(utils, "utils is defined");
-    executeSoon(function() {
-      inspector.selection.once("new-node", onNewSelection);
-      info("selecting the DOCTYPE node");
-      inspector.selection.setNode(content.document.doctype, "test");
-    });
-  }
-
-  function sendMouseEvent(node, type, x, y) {
-    let rect = node.getBoundingClientRect();
-    let left = rect.left + x;
-    let top = rect.top + y;
-    utils.sendMouseEventToWindow(type, left, top, 0, 1, 0, false, 0, 0);
-  }
-
-  function onNewSelection() {
-    is(inspector.highlighter.isHidden(), true,
-       "The infobar should be hidden now on selecting a non element node.");
-    inspector.sidebar.select("ruleview");
-    let ruleView = inspector.sidebar.getTab("ruleview");
-    ruleView.addEventListener("mouseover", function onMouseOver() {
-      ruleView.removeEventListener("mouseover", onMouseOver, false);
-      is(inspector.highlighter.isHidden(), true,
-         "The infobar was hidden so mouseover on the rules view did nothing");
-      executeSoon(mouseOutAndContinue);
-    }, false);
-    sendMouseEvent(ruleView, "mouseover", 10, 10);
-  }
-
-  function mouseOutAndContinue() {
-    let ruleView = inspector.sidebar.getTab("ruleview");
-    info("adding mouseout listener");
-    ruleView.addEventListener("mouseout", function onMouseOut() {
-      info("mouseout happened");
-      ruleView.removeEventListener("mouseout", onMouseOut, false);
-      is(inspector.highlighter.isHidden(), true,
-         "The infobar should not be visible after we mouseout of rules view");
-      switchToWebConsole();
-    }, false);
-    info("Synthesizing mouseout on " + ruleView);
-    sendMouseEvent(inspector._markupBox, "mousemove", 50, 50);
-    info("mouseout synthesized");
-  }
-
-  function switchToWebConsole() {
-    inspector.selection.once("new-node", function() {
-      is(inspector.highlighter.isHidden(), false,
-         "The infobar should be visible after we select a div.");
-      gDevTools.showToolbox(inspector.target, "webconsole").then(function() {
-        is(inspector.highlighter.isHidden(), true,
-           "The infobar should not be visible after we switched to webconsole");
-        reloadAndWait();
-      });
-    });
-    inspector.selection.setNode(content.document.querySelector("div"), "test");
-  }
-
-  function reloadAndWait() {
-    gBrowser.selectedBrowser.addEventListener("load", function onBrowserLoad() {
-      gBrowser.selectedBrowser.removeEventListener("load", onBrowserLoad, true);
-      waitForFocus(testAfterReload, content);
-    }, true);
-    content.location.reload();
-  }
-
-  function testAfterReload() {
-    is(inspector.highlighter.isHidden(), true,
-       "The infobar should not be visible after we reload with webconsole shown");
-    testEnd();
-  }
-
-  function testEnd() {
-    gBrowser.removeCurrentTab();
-    utils = null;
-    executeSoon(finish);
-  }
-
-  waitForExplicitFinish();
-
-  gBrowser.selectedTab = gBrowser.addTab();
-  gBrowser.selectedBrowser.addEventListener("load", function onBrowserLoad() {
-    gBrowser.selectedBrowser.removeEventListener("load", onBrowserLoad, true);
-    waitForFocus(startLocationTests, content);
-  }, true);
-
-  content.location = "data:text/html,<!DOCTYPE html><div>Infobar should not " +
-                     "reappear</div><p>init</p>";
-}
--- a/browser/devtools/inspector/test/browser_inspector_bug_840156_destroy_after_navigation.js
+++ b/browser/devtools/inspector/test/browser_inspector_bug_840156_destroy_after_navigation.js
@@ -28,16 +28,23 @@ function test() {
   // open devtools panel
   deferred.promise
     .then(function () gDevTools.showToolbox(target, null, Toolbox.HostType.BOTTOM))
     .then(function (aToolbox) { toolbox = aToolbox; })
 
   // select the inspector
     .then(function () toolbox.selectTool("inspector"))
 
+  // wait until inspector ready
+    .then(function () {
+      let deferred = promise.defer();
+      toolbox.getPanel("inspector").once("inspector-updated", deferred.resolve);
+      return deferred.promise;
+    })
+
   // navigate to URL_2
     .then(function () {
       let deferred = promise.defer();
       target.once("navigate", function () deferred.resolve());
       browser.loadURI(URL_2);
       return deferred.promise;
     })
 
--- a/browser/devtools/inspector/test/browser_inspector_bug_848731_reset_selection_on_delete.js
+++ b/browser/devtools/inspector/test/browser_inspector_bug_848731_reset_selection_on_delete.js
@@ -134,13 +134,10 @@ function test() {
     // Right node selected?
     is(inspector.selection.nodeFront, getNodeFront(node),
       "The right node is selected");
 
     // breadcrumbs updated?
     let breadcrumbs = inspector.panelDoc.getElementById("inspector-breadcrumbs");
     is(breadcrumbs.querySelector("button[checked=true]").textContent, crumbLabel,
       "The right breadcrumb is selected");
-
-    // Highlighter is shown?
-    ok(!inspector.highlighter.isHidden(), "The highlighter is shown");
   }
 }
--- a/browser/devtools/inspector/test/browser_inspector_bug_922125_destroy_on_navigate.js
+++ b/browser/devtools/inspector/test/browser_inspector_bug_922125_destroy_on_navigate.js
@@ -29,17 +29,16 @@ function test() {
   deferred.promise.then(() => {
     return gDevTools.showToolbox(target, null, Toolbox.HostType.BOTTOM);
   }).then(aToolbox => {
     toolbox = aToolbox;
   }).then(() => {
     // select the inspector
     return toolbox.selectTool("inspector").then(i => {
       inspector = i;
-
       // Verify we are on page one
       let testNode = content.document.querySelector("#one");
       ok(testNode, "We have the test node on page 1");
 
       assertMarkupViewIsLoaded();
     });
   }).then(() => {
     // navigate to URL_2
--- a/browser/devtools/inspector/test/browser_inspector_changes.js
+++ b/browser/devtools/inspector/test/browser_inspector_changes.js
@@ -27,44 +27,47 @@ function test() {
       }
     }
     return null;
   }
 
   function runInspectorTests(aInspector)
   {
     inspector = aInspector;
-    inspector.sidebar.once("computedview-ready", function() {
+    inspector.sidebar.once("computedview-ready", () => {
       info("Computed View ready");
       inspector.sidebar.select("computedview");
 
       testDiv = doc.getElementById("testdiv");
 
       testDiv.style.fontSize = "10px";
 
       // Start up the style inspector panel...
-      inspector.once("computed-view-refreshed", computedStylePanelTests);
-
+      inspector.once("computed-view-refreshed", () => {
+        executeSoon(computedStylePanelTests);
+      });
       inspector.selection.setNode(testDiv);
     });
   }
 
   function computedStylePanelTests()
   {
     let computedview = inspector.sidebar.getWindowForTab("computedview").computedview;
     ok(computedview, "Style Panel has a cssHtmlTree");
 
     let fontSize = getComputedPropertyValue("font-size");
     is(fontSize, "10px", "Style inspector should be showing the correct font size.");
 
     testDiv.style.cssText = "font-size: 15px; color: red;";
 
     // Wait until layout-change fires from mutation to skip earlier refresh event
     inspector.once("layout-change", () => {
-      inspector.once("computed-view-refreshed", computedStylePanelAfterChange);
+      inspector.once("computed-view-refreshed", () => {
+        executeSoon(computedStylePanelAfterChange);
+      });
     });
   }
 
   function computedStylePanelAfterChange()
   {
     let fontSize = getComputedPropertyValue("font-size");
     is(fontSize, "15px", "Style inspector should be showing the new font size.");
 
@@ -74,22 +77,21 @@ function test() {
     computedStylePanelNotActive();
   }
 
   function computedStylePanelNotActive()
   {
     // Tests changes made while the style panel is not active.
     inspector.sidebar.select("ruleview");
 
-    testDiv.style.fontSize = "20px";
-    testDiv.style.color = "blue";
-    testDiv.style.textAlign = "center";
+    testDiv.style.cssText = "font-size: 20px; color: blue; text-align: center";
 
-    inspector.once("computed-view-refreshed", computedStylePanelAfterSwitch);
-    inspector.sidebar.select("computedview");
+    inspector.once("computed-view-refreshed", () => {
+      executeSoon(computedStylePanelAfterSwitch);
+    });
   }
 
   function computedStylePanelAfterSwitch()
   {
     let fontSize = getComputedPropertyValue("font-size");
     is(fontSize, "20px", "Style inspector should be showing the new font size.");
 
     let color = getComputedPropertyValue("color");
@@ -105,23 +107,21 @@ function test() {
   {
     inspector.sidebar.select("ruleview");
     let ruleview = inspector.sidebar.getWindowForTab("ruleview").ruleview;
     ok(ruleview, "Style Panel has a ruleview");
 
     let propView = getInspectorRuleProp("text-align");
     is(propView.value, "center", "Style inspector should be showing the new text align.");
 
-    testDiv.style.textAlign = "right";
-    testDiv.style.color = "lightgoldenrodyellow";
-    testDiv.style.fontSize = "3em";
-    testDiv.style.textTransform = "uppercase";
+    testDiv.style.cssText = "font-size: 3em; color: lightgoldenrodyellow; text-align: right; text-transform: uppercase";
 
-
-    inspector.once("rule-view-refreshed", rulePanelAfterChange);
+    inspector.once("rule-view-refreshed", () => {
+      executeSoon(rulePanelAfterChange);
+    });
   }
 
   function rulePanelAfterChange()
   {
     let propView = getInspectorRuleProp("text-align");
     is(propView.value, "right", "Style inspector should be showing the new text align.");
 
     let propView = getInspectorRuleProp("color");
--- a/browser/devtools/inspector/test/browser_inspector_destroyselection.js
+++ b/browser/devtools/inspector/test/browser_inspector_destroyselection.js
@@ -1,15 +1,14 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 function test()
 {
   waitForExplicitFinish();
-  //ignoreAllUncaughtExceptions();
 
   let node, iframe, inspector;
 
   gBrowser.selectedTab = gBrowser.addTab();
   gBrowser.selectedBrowser.addEventListener("load", function onload() {
     gBrowser.selectedBrowser.removeEventListener("load", onload, true);
     waitForFocus(setupTest, content);
   }, true);
@@ -23,27 +22,31 @@ function test()
     openInspector(runTests);
   }
 
   function runTests(aInspector)
   {
     inspector = aInspector;
     inspector.selection.setNode(node);
 
-    iframe.parentNode.removeChild(iframe);
-    iframe = null;
+    inspector.once("inspector-updated", () => {
+      iframe.parentNode.removeChild(iframe);
+      iframe = null;
 
-    let tmp = {};
-    Cu.import("resource://gre/modules/devtools/LayoutHelpers.jsm", tmp);
-    let lh = new tmp.LayoutHelpers(window.content);
-    ok(!lh.isNodeConnected(node), "Node considered as disconnected.");
-    ok(!inspector.selection.isConnected(), "Selection considered as disconnected");
+      let tmp = {};
+      Cu.import("resource://gre/modules/devtools/LayoutHelpers.jsm", tmp);
+      let lh = new tmp.LayoutHelpers(window.content);
+      ok(!lh.isNodeConnected(node), "Node considered as disconnected.");
+      ok(!inspector.selection.isConnected(), "Selection considered as disconnected");
 
-    finishUp();
+      inspector.once("inspector-updated", () => {
+        finishUp();
+      });
+    });
   }
 
   function finishUp() {
-    node = null;
+    node = inspector = null;
     gBrowser.removeCurrentTab();
     finish();
   }
 }
 
--- a/browser/devtools/inspector/test/browser_inspector_highlighter.js
+++ b/browser/devtools/inspector/test/browser_inspector_highlighter.js
@@ -1,22 +1,21 @@
 /* -*- 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/. */
 
 let doc;
 let h1;
-let div;
+let inspector;
 
-function createDocument()
-{
+function createDocument() {
   let div = doc.createElement("div");
-  let h1 = doc.createElement("h1");
+  h1 = doc.createElement("h1");
   let p1 = doc.createElement("p");
   let p2 = doc.createElement("p");
   let div2 = doc.createElement("div");
   let p3 = doc.createElement("p");
   doc.title = "Inspector Highlighter Meatballs";
   h1.textContent = "Inspector Tree Selection Test";
   p1.textContent = "This is some example text";
   p2.textContent = "Lorem ipsum dolor sit amet, consectetur adipisicing " +
@@ -43,135 +42,90 @@ function createDocument()
   div.appendChild(p1);
   div.appendChild(p2);
   div2.appendChild(p3);
   div3.appendChild(p4);
   doc.body.appendChild(div);
   doc.body.appendChild(div2);
   doc.body.appendChild(div3);
 
-  openInspector(setupHighlighterTests);
-}
-
-function setupHighlighterTests()
-{
-  h1 = doc.querySelector("h1");
-  ok(h1, "we have the header");
-
-  let i = getActiveInspector();
-  i.selection.setNode(div);
-  i.highlighter.unlockAndFocus();
-  i.highlighter.outline.setAttribute("disable-transitions", "true");
-
-  executeSoon(function() {
-    i.selection.once("new-node", performToggleComparisons);
-    EventUtils.synthesizeMouse(h1, 2, 2, {type: "mousemove"}, content);
+  openInspector(aInspector => {
+    inspector = aInspector;
+    inspector.selection.setNode(div, null);
+    inspector.once("inspector-updated", () => {
+      getHighlighterOutline().setAttribute("disable-transitions", "true");
+      inspector.toolbox.startPicker().then(testMouseOverH1Highlights);
+    });
   });
 }
 
-function performToggleComparisons(evt)
-{
-  let i = getActiveInspector();
+function testMouseOverH1Highlights() {
+  inspector.toolbox.once("picker-node-hovered", () => {
+    ok(isHighlighting(), "Highlighter is shown");
+    is(getHighlitNode(), h1, "Highlighter's outline correspond to the selected node");
+    testOutlineDimensions();
+  });
 
-  i.highlighter.toggleLockState();
-  ok(i.highlighter.locked, "highlighter locks");
-  is(i.selection.node, div);
-  i.highlighter.toggleLockState();
-  ok(!i.highlighter.locked, "highlighter unlocks");
-
-  i.highlighter.toggleLockState();
-  ok(i.highlighter.locked, "highlighter locks if selection is unchanged");
-  i.highlighter.toggleLockState();
-
-  executeSoon(function() {
-    i.selection.once("new-node", performTestComparisons);
-    EventUtils.synthesizeMouse(h1, 2, 2, {type: "mousemove"}, content);
-  });
+  EventUtils.synthesizeMouse(h1, 2, 2, {type: "mousemove"}, content);
 }
 
-function performTestComparisons(evt)
-{
-  let i = getActiveInspector();
-  i.highlighter.lock();
-  ok(isHighlighting(), "highlighter is highlighting");
-  is(getHighlitNode(), h1, "highlighter matches selection")
-  is(i.selection.node, h1, "selection matches node");
-  is(i.selection.node, getHighlitNode(), "selection matches highlighter");
-
-
-  div = doc.querySelector("div#checkOutThisWickedSpread");
+function testOutlineDimensions() {
+  let h1Dims = h1.getBoundingClientRect();
+  let h1Width = h1Dims.width;
+  let h1Height = h1Dims.height;
 
-  executeSoon(function() {
-    i.selection.once("new-node", finishTestComparisons);
-    i.selection.setNode(div);
-  });
-}
-
-function finishTestComparisons()
-{
-  let i = getActiveInspector();
-
-  // get dimensions of div element
-  let divDims = div.getBoundingClientRect();
-  let divWidth = divDims.width;
-  let divHeight = divDims.height;
-
-  // get dimensions of the outline
-  let outlineDims = i.highlighter.outline.getBoundingClientRect();
+  let outlineDims = getHighlighterOutlineRect();
   let outlineWidth = outlineDims.width;
   let outlineHeight = outlineDims.height;
 
   // Disabled due to bug 716245
-  //is(outlineWidth, divWidth, "outline width matches dimensions of element (no zoom)");
-  //is(outlineHeight, divHeight, "outline height matches dimensions of element (no zoom)");
+  is(outlineWidth, h1Width, "outline width matches dimensions of element (no zoom)");
+  is(outlineHeight, h1Height, "outline height matches dimensions of element (no zoom)");
 
   // zoom the page by a factor of 2
   let contentViewer = gBrowser.selectedBrowser.docShell.contentViewer
                              .QueryInterface(Ci.nsIMarkupDocumentViewer);
   contentViewer.fullZoom = 2;
 
   // We wait at least 500ms to make sure the highlighter is not "mutting" the
   // resize event
 
   window.setTimeout(function() {
-    // check what zoom factor we're at, should be 2
-    let zoom = i.highlighter.zoom;
-    is(zoom, 2, "zoom is 2?");
+    // simulate the zoomed dimensions of the div element
+    let h1Dims = h1.getBoundingClientRect();
+    // There seems to be some very minor differences in the floats, so let's
+    // floor the values
+    let h1Width = Math.floor(h1Dims.width * contentViewer.fullZoom);
+    let h1Height = Math.floor(h1Dims.height * contentViewer.fullZoom);
 
-    // simulate the zoomed dimensions of the div element
-    let divDims = div.getBoundingClientRect();
-    let divWidth = divDims.width * zoom;
-    let divHeight = divDims.height * zoom;
-
-    // now zoomed, get new dimensions the outline
-    let outlineDims = i.highlighter.outline.getBoundingClientRect();
-    let outlineWidth = outlineDims.width;
-    let outlineHeight = outlineDims.height;
+    let outlineDims = getHighlighterOutlineRect();
+    let outlineWidth = Math.floor(outlineDims.width);
+    let outlineHeight = Math.floor(outlineDims.height);
 
     // Disabled due to bug 716245
-    //is(outlineWidth, divWidth, "outline width matches dimensions of element (no zoom)");
-    //is(outlineHeight, divHeight, "outline height matches dimensions of element (no zoom)");
+    is(outlineWidth, h1Width, "outline width matches dimensions of element (zoomed)");
+    is(outlineHeight, h1Height, "outline height matches dimensions of element (zoomed)");
 
-    doc = h1 = div = null;
     executeSoon(finishUp);
   }, 500);
 }
 
 function finishUp() {
-  let target = TargetFactory.forTab(gBrowser.selectedTab);
-  gDevTools.closeToolbox(target);
-  gBrowser.removeCurrentTab();
-  finish();
+  inspector.toolbox.stopPicker().then(() => {
+    doc = h1 = inspector = null;
+    let target = TargetFactory.forTab(gBrowser.selectedTab);
+    gDevTools.closeToolbox(target);
+    gBrowser.removeCurrentTab();
+    finish();
+  });
 }
 
-function test()
-{
+function test() {
   waitForExplicitFinish();
   gBrowser.selectedTab = gBrowser.addTab();
   gBrowser.selectedBrowser.addEventListener("load", function() {
     gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
     doc = content.document;
     waitForFocus(createDocument, content);
   }, true);
 
   content.location = "data:text/html,basic tests for inspector";
 }
-
deleted file mode 100644
--- a/browser/devtools/inspector/test/browser_inspector_highlighter_autohide.js
+++ /dev/null
@@ -1,46 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
-   http://creativecommons.org/publicdomain/zero/1.0/ */
-
-
-function test()
-{
-  let toolbox;
-  let inspector;
-
-  waitForExplicitFinish();
-
-  gBrowser.selectedTab = gBrowser.addTab();
-  gBrowser.selectedBrowser.addEventListener("load", function onload() {
-    gBrowser.selectedBrowser.removeEventListener("load", onload, true);
-    waitForFocus(startInspector, content);
-  }, true);
-  content.location = "data:text/html,mop"
-
-  function startInspector() {
-    info("Tab loaded");
-    openInspector(function(aInspector) {
-      inspector = aInspector;
-      ok(!inspector.highlighter.hidden, "Highlighter is visible");
-      toolbox = inspector._toolbox;
-      toolbox.once("webconsole-selected", onWebConsoleSelected);
-      toolbox.selectTool("webconsole");
-    });
-  }
-
-  function onWebConsoleSelected() {
-    executeSoon(function() {
-      ok(inspector.highlighter.hidden, "Highlighter is hidden");
-      toolbox.once("inspector-selected", onInspectorSelected);
-      toolbox.selectTool("inspector");
-    });
-  }
-
-  function onInspectorSelected() {
-    executeSoon(function() {
-      ok(!inspector.highlighter.hidden, "Highlighter is visible once inspector reopen");
-      gBrowser.removeCurrentTab();
-      finish();
-    });
-  }
-}
-
--- a/browser/devtools/inspector/test/browser_inspector_iframeTest.js
+++ b/browser/devtools/inspector/test/browser_inspector_iframeTest.js
@@ -4,19 +4,19 @@
  * 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/. */
 
 let doc;
 let div1;
 let div2;
 let iframe1;
 let iframe2;
+let inspector;
 
-function createDocument()
-{
+function createDocument() {
   doc.title = "Inspector iframe Tests";
 
   iframe1 = doc.createElement('iframe');
 
   iframe1.addEventListener("load", function () {
     iframe1.removeEventListener("load", arguments.callee, false);
 
     div1 = iframe1.contentDocument.createElement('div');
@@ -27,95 +27,90 @@ function createDocument()
 
     iframe2.addEventListener('load', function () {
       iframe2.removeEventListener("load", arguments.callee, false);
 
       div2 = iframe2.contentDocument.createElement('div');
       div2.textContent = 'nested div';
       iframe2.contentDocument.body.appendChild(div2);
 
-      openInspector(runIframeTests);
+      // Open the inspector, start the picker mode, and start the tests
+      openInspector(aInspector => {
+        inspector = aInspector;
+        inspector.toolbox.startPicker().then(runTests);
+      });
     }, false);
 
     iframe2.src = 'data:text/html,nested iframe';
     iframe1.contentDocument.body.appendChild(iframe2);
   }, false);
 
   iframe1.src = 'data:text/html,little iframe';
   doc.body.appendChild(iframe1);
 }
 
-function moveMouseOver(aElement)
-{
+function moveMouseOver(aElement, cb) {
   EventUtils.synthesizeMouse(aElement, 2, 2, {type: "mousemove"},
     aElement.ownerDocument.defaultView);
+  inspector.toolbox.once("picker-node-hovered", () => {
+    executeSoon(cb);
+  });
 }
 
-function runIframeTests()
-{
-  getActiveInspector().highlighter.unlock();
-  getActiveInspector().selection.once("new-node", performTestComparisons1);
-  moveMouseOver(div1)
+function runTests() {
+  testDiv1Highlighter();
 }
 
-function performTestComparisons1()
-{
-  let i = getActiveInspector();
-  is(i.selection.node, div1, "selection matches div1 node");
-  is(getHighlitNode(), div1, "highlighter matches selection");
-
-  i.selection.once("new-node", performTestComparisons2);
-  executeSoon(function() {
-    moveMouseOver(div2);
+function testDiv1Highlighter() {
+  moveMouseOver(div1, () => {
+    getHighlighterOutline().setAttribute("disable-transitions", "true");
+    is(getHighlitNode(), div1, "highlighter matches selection");
+    testDiv2Highlighter();
   });
 }
 
-function performTestComparisons2()
-{
-  let i = getActiveInspector();
+function testDiv2Highlighter() {
+  moveMouseOver(div2, () => {
+    is(getHighlitNode(), div2, "highlighter matches selection");
+    selectRoot();
+  });
+}
 
-  is(i.selection.node, div2, "selection matches div2 node");
-  is(getHighlitNode(), div2, "highlighter matches selection");
-
-  selectRoot();
+function selectRoot() {
+  // Select the root document element to clear the breadcrumbs.
+  inspector.selection.setNode(doc.documentElement);
+  inspector.once("inspector-updated", selectIframe);
 }
 
-function selectRoot()
-{
-  // Select the root document element to clear the breadcrumbs.
-  let i = getActiveInspector();
-  i.selection.setNode(doc.documentElement);
-  i.once("inspector-updated", selectIframe);
+function selectIframe() {
+  // Directly select an element in an iframe (without navigating to it
+  // with mousemoves).
+  inspector.selection.setNode(div2);
+  inspector.once("inspector-updated", () => {
+    let breadcrumbs = inspector.breadcrumbs;
+    is(breadcrumbs.nodeHierarchy.length, 9, "Should have 9 items");
+    finishUp();
+  });
 }
 
-function selectIframe()
-{
-  // Directly select an element in an iframe (without navigating to it
-  // with mousemoves).
-  let i = getActiveInspector();
-  i.selection.setNode(div2);
-  i.once("inspector-updated", () => {
-    let breadcrumbs = i.breadcrumbs;
-    is(breadcrumbs.nodeHierarchy.length, 9, "Should have 9 items");
+function finishUp() {
+  inspector.toolbox.stopPicker().then(() => {
+    doc = div1 = div2 = iframe1 = iframe2 = inspector = null;
+    let target = TargetFactory.forTab(gBrowser.selectedTab);
+    gDevTools.closeToolbox(target);
+    gBrowser.removeCurrentTab();
     finish();
   });
 }
 
 function test() {
   waitForExplicitFinish();
 
   gBrowser.selectedTab = gBrowser.addTab();
   gBrowser.selectedBrowser.addEventListener("load", function() {
     gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
     doc = content.document;
     gBrowser.selectedBrowser.focus();
     createDocument();
   }, true);
 
   content.location = "data:text/html,iframe tests for inspector";
-
-  registerCleanupFunction(function () {
-    let target = TargetFactory.forTab(gBrowser.selectedTab);
-    gDevTools.closeToolbox(target);
-    gBrowser.removeCurrentTab();
-  });
 }
-
--- a/browser/devtools/inspector/test/browser_inspector_infobar.js
+++ b/browser/devtools/inspector/test/browser_inspector_infobar.js
@@ -1,13 +1,12 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
-function test()
-{
+function test() {
   waitForExplicitFinish();
   ignoreAllUncaughtExceptions();
 
   let doc;
   let nodes;
   let cursor;
   let inspector;
 
@@ -18,64 +17,68 @@ function test()
     waitForFocus(setupInfobarTest, content);
   }, true);
 
   let style = "body{width:100%;height: 100%} div {position: absolute;height: 100px;width: 500px}#bottom {bottom: 0px}#vertical {height: 100%}#farbottom{bottom: -200px}";
   let html = "<style>" + style + "</style><div id=vertical></div><div id=top class='class1 class2'></div><div id=bottom></div><div id=farbottom></div>"
 
   content.location = "data:text/html," + encodeURIComponent(html);
 
-  function setupInfobarTest()
-  {
+  function setupInfobarTest() {
     nodes = [
       {node: doc.querySelector("#top"), position: "bottom", tag: "DIV", id: "#top", classes: ".class1.class2"},
       {node: doc.querySelector("#vertical"), position: "overlap", tag: "DIV", id: "#vertical", classes: ""},
       {node: doc.querySelector("#bottom"), position: "top", tag: "DIV", id: "#bottom", classes: ""},
       {node: doc.querySelector("body"), position: "overlap", tag: "BODY", id: "", classes: ""},
       {node: doc.querySelector("#farbottom"), position: "top", tag: "DIV", id: "#farbottom", classes: ""},
     ]
 
     for (let i = 0; i < nodes.length; i++) {
       ok(nodes[i].node, "node " + i + " found");
     }
 
     openInspector(runTests);
   }
 
-  function runTests(aInspector)
-  {
+  function mouseOverContainerToShowHighlighter(node, cb) {
+    let container = getContainerForRawNode(inspector.markup, node);
+    EventUtils.synthesizeMouse(container.tagLine, 2, 2, {type: "mousemove"},
+      inspector.markup.doc.defaultView);
+    executeSoon(cb);
+  }
+
+  function runTests(aInspector) {
     inspector = aInspector;
-    cursor = 0;
-    executeSoon(function() {
-      inspector.selection.setNode(nodes[0].node, "");
-      nodeSelected();
+    inspector.selection.setNode(content.document.querySelector("body"));
+    inspector.once("inspector-updated", () => {
+      cursor = 0;
+      executeSoon(function() {
+        mouseOverContainerToShowHighlighter(nodes[0].node, nodeSelected);
+      });
     });
   }
 
-  function nodeSelected()
-  {
+  function nodeSelected() {
     executeSoon(function() {
       performTest();
       cursor++;
       if (cursor >= nodes.length) {
         finishUp();
       } else {
         let node = nodes[cursor].node;
-        inspector.selection.setNode(node, "");
-        nodeSelected();
+        mouseOverContainerToShowHighlighter(node, nodeSelected);
       }
     });
   }
 
-  function performTest()
-  {
+  function performTest() {
     let browser = gBrowser.selectedBrowser;
     let stack = browser.parentNode;
 
-    let container = stack.querySelector(".highlighter-nodeinfobar-container");
+    let container = stack.querySelector(".highlighter-nodeinfobar-positioner");
     is(container.getAttribute("position"), nodes[cursor].position, "node " + cursor + ": position matches.");
 
     let tagNameLabel = stack.querySelector(".highlighter-nodeinfobar-tagname");
     is(tagNameLabel.textContent, nodes[cursor].tag, "node " + cursor  + ": tagName matches.");
 
     let idLabel = stack.querySelector(".highlighter-nodeinfobar-id");
     is(idLabel.textContent, nodes[cursor].id, "node " + cursor  + ": id matches.");
 
@@ -84,9 +87,8 @@ function test()
   }
 
   function finishUp() {
     doc = nodes = null;
     gBrowser.removeCurrentTab();
     finish();
   }
 }
-
--- a/browser/devtools/inspector/test/browser_inspector_initialization.js
+++ b/browser/devtools/inspector/test/browser_inspector_initialization.js
@@ -30,53 +30,43 @@ function createDocument()
 function startInspectorTests(toolbox)
 {
   let inspector = toolbox.getCurrentPanel();
   ok(true, "Inspector started, and notification received.");
 
   ok(inspector, "Inspector instance is accessible");
   ok(inspector.isReady, "Inspector instance is ready");
   is(inspector.target.tab, gBrowser.selectedTab, "Valid target");
-  ok(inspector.highlighter, "Highlighter is up");
 
   let p = doc.querySelector("p");
 
   inspector.selection.setNode(p);
   inspector.once("inspector-updated", () => {
-    testHighlighter(p);
     testMarkupView(p);
     testBreadcrumbs(p);
 
     let span = doc.querySelector("span");
     span.scrollIntoView();
 
     inspector.selection.setNode(span);
     inspector.once("inspector-updated", () => {
-      testHighlighter(span);
       testMarkupView(span);
       testBreadcrumbs(span);
 
       toolbox.once("destroyed", function() {
         ok("true", "'destroyed' notification received.");
         let target = TargetFactory.forTab(gBrowser.selectedTab);
         ok(!gDevTools.getToolbox(target), "Toolbox destroyed.");
         executeSoon(runContextMenuTest);
       });
       toolbox.destroy();
     });
   });
 }
 
-
-function testHighlighter(node)
-{
-  ok(isHighlighting(), "Highlighter is highlighting");
-  is(getHighlitNode(), node, "Right node is highlighted");
-}
-
 let callNo = 0;
 function testMarkupView(node)
 {
   let i = getActiveInspector();
   try {
     is(i.markup._selectedContainer.node.rawNode(), node, "Right node is selected in the markup view");
   } catch(ex) { console.error(ex); }
 }
@@ -107,32 +97,30 @@ function _clickOnInspectMenuItem(node) {
 
 function runContextMenuTest()
 {
   salutation = doc.getElementById("salutation");
   _clickOnInspectMenuItem(salutation).then(testInitialNodeIsSelected);
 }
 
 function testInitialNodeIsSelected() {
-  testHighlighter(salutation);
   testMarkupView(salutation);
   testBreadcrumbs(salutation);
   inspectNodesFromContextTestWhileOpen();
 }
 
 function inspectNodesFromContextTestWhileOpen()
 {
   let closing = doc.getElementById("closing");
   getActiveInspector().selection.once("new-node", function() {
     ok(true, "Get selection's 'new-node' selection");
     executeSoon(function() {
-      testHighlighter(closing);
       testMarkupView(closing);
       testBreadcrumbs(closing);
-      finishInspectorTests();
+      getActiveInspector().once("inspector-updated", finishInspectorTests)
     }
   )});
   _clickOnInspectMenuItem(closing);
 }
 
 function finishInspectorTests(subject, topic, aWinIdString)
 {
   gBrowser.removeCurrentTab();
--- a/browser/devtools/inspector/test/browser_inspector_invalidate.js
+++ b/browser/devtools/inspector/test/browser_inspector_invalidate.js
@@ -1,45 +1,48 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 function test() {
-
   let doc;
   let div;
   let inspector;
 
-  function createDocument()
-  {
+  function createDocument() {
     div = doc.createElement("div");
     div.setAttribute("style", "width: 100px; height: 100px; background:yellow;");
     doc.body.appendChild(div);
 
-    openInspector(runTest);
+    openInspector(aInspector => {
+      inspector = aInspector;
+      inspector.toolbox.highlighter.showBoxModel(getNodeFront(div)).then(runTest);
+    });
   }
 
-  function runTest(inspector)
-  {
-    inspector.selection.setNode(div);
-
-    executeSoon(function() {
-      let outline = inspector.highlighter.outline;
-      is(outline.style.width, "100px", "selection has the right width");
+  function runTest() {
+    let outline = getHighlighterOutline();
+    is(outline.style.width, "100px", "outline has the right width");
 
-      div.style.width = "200px";
-      function pollTest() {
-        if (outline.style.width == "100px") {
-          setTimeout(pollTest, 10);
-          return;
-        }
-        is(outline.style.width, "200px", "selection updated");
-        gBrowser.removeCurrentTab();
-        finish();
+    div.style.width = "200px";
+    function pollTest() {
+      if (outline.style.width == "100px") {
+        setTimeout(pollTest, 10);
+        return;
       }
-      setTimeout(pollTest, 10);
+      is(outline.style.width, "200px", "outline updated");
+      finishUp();
+    }
+    setTimeout(pollTest, 10);
+  }
+
+  function finishUp() {
+    inspector.toolbox.highlighter.hideBoxModel().then(() => {
+      doc = div = inspector = null;
+      gBrowser.removeCurrentTab();
+      finish();
     });
   }
 
   waitForExplicitFinish();
   gBrowser.selectedTab = gBrowser.addTab();
   gBrowser.selectedBrowser.addEventListener("load", function() {
     gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
     doc = content.document;
--- a/browser/devtools/inspector/test/browser_inspector_menu.js
+++ b/browser/devtools/inspector/test/browser_inspector_menu.js
@@ -98,39 +98,42 @@ function test() {
                      function() { copyUniqueSelector.doCommand(); },
                      testDeleteNode, testDeleteNode);
   }
 
   function testDeleteNode() {
     let deleteNode = inspector.panelDoc.getElementById("node-menu-delete");
     ok(deleteNode, "the popup menu has a delete menu item");
 
-    inspector.once("markupmutation", deleteTest);
+    inspector.once("inspector-updated", deleteTest);
 
     let commandEvent = document.createEvent("XULCommandEvent");
     commandEvent.initCommandEvent("command", true, true, window, 0, false, false,
                                   false, false, null);
     deleteNode.dispatchEvent(commandEvent);
   }
 
   function deleteTest() {
     let p = doc.querySelector("P");
     is(p, null, "node deleted");
 
     deleteRootNode();
   }
 
   function deleteRootNode() {
     inspector.selection.setNode(doc.documentElement);
-    let deleteNode = inspector.panelDoc.getElementById("node-menu-delete");
-    let commandEvent = inspector.panelDoc.createEvent("XULCommandEvent");
-    commandEvent.initCommandEvent("command", true, true, window, 0, false, false,
-                                  false, false, null);
-    deleteNode.dispatchEvent(commandEvent);
-    executeSoon(isRootStillAlive);
+
+    inspector.once("inspector-updated", () => {
+      let deleteNode = inspector.panelDoc.getElementById("node-menu-delete");
+      let commandEvent = inspector.panelDoc.createEvent("XULCommandEvent");
+      commandEvent.initCommandEvent("command", true, true, window, 0, false, false,
+                                    false, false, null);
+      deleteNode.dispatchEvent(commandEvent);
+      executeSoon(isRootStillAlive);
+    });
   }
 
   function isRootStillAlive() {
     ok(doc.documentElement, "Document element still alive.");
     gBrowser.removeCurrentTab();
     finish();
   }
 
--- a/browser/devtools/inspector/test/browser_inspector_pseudoClass_menu.js
+++ b/browser/devtools/inspector/test/browser_inspector_pseudoClass_menu.js
@@ -32,17 +32,17 @@ function test() {
 
     openInspector(selectNode);
   }
 
   function selectNode(aInspector)
   {
     inspector = aInspector;
     inspector.selection.setNode(div);
-    performTests();
+    inspector.once("inspector-updated", performTests);
   }
 
   function performTests()
   {
     menu = inspector.panelDoc.getElementById("inspector-node-popup");
     menu.addEventListener("popupshowing", testMenuItems, true);
     menu.openPopup();
   }
--- a/browser/devtools/inspector/test/browser_inspector_pseudoclass_lock.js
+++ b/browser/devtools/inspector/test/browser_inspector_pseudoclass_lock.js
@@ -62,109 +62,119 @@ function selectNode(aInspector)
 function performTests()
 {
   // toggle the class
   inspector.togglePseudoClass(pseudo);
 
   // Wait for the "pseudoclass" event so we know the
   // inspector has been told of the pseudoclass lock change.
   inspector.selection.once("pseudoclass", () => {
-    // Give the rule view time to update.
     inspector.once("rule-view-refreshed", () => {
-      testAdded();
-      // Change the pseudo class and give the rule view time to update.
-      inspector.togglePseudoClass(pseudo);
-      inspector.selection.once("pseudoclass", () => {
-        inspector.once("rule-view-refreshed", () => {
-          testRemoved();
-          testRemovedFromUI();
-
-          // toggle it back on
-          inspector.togglePseudoClass(pseudo);
-          inspector.selection.once("pseudoclass", () => {
-            testNavigate(() => {
-              // close the inspector
-              finishUp();
+      testAdded(() => {
+        // Change the pseudo class and give the rule view time to update.
+        inspector.togglePseudoClass(pseudo);
+        inspector.selection.once("pseudoclass", () => {
+          inspector.once("rule-view-refreshed", () => {
+            testRemoved();
+            testRemovedFromUI(() => {
+              // toggle it back on
+              inspector.togglePseudoClass(pseudo);
+              inspector.selection.once("pseudoclass", () => {
+                inspector.once("rule-view-refreshed", () => {
+                  testNavigate(() => {
+                    // close the inspector
+                    finishUp();
+                  });
+                });
+              });
             });
           });
         });
       });
     });
   });
 }
 
 function testNavigate(callback)
 {
   inspector.selection.setNode(parentDiv);
   inspector.once("inspector-updated", () => {
 
     // make sure it's still on after naving to parent
     is(DOMUtils.hasPseudoClassLock(div, pseudo), true,
-         "pseudo-class lock is still applied after inspecting ancestor");
+      "pseudo-class lock is still applied after inspecting ancestor");
 
     inspector.selection.setNode(div2);
     inspector.selection.once("pseudoclass", () => {
       // make sure it's removed after naving to a non-hierarchy node
       is(DOMUtils.hasPseudoClassLock(div, pseudo), false,
-           "pseudo-class lock is removed after inspecting sibling node");
+        "pseudo-class lock is removed after inspecting sibling node");
 
       // toggle it back on
       inspector.selection.setNode(div);
       inspector.once("inspector-updated", () => {
         inspector.togglePseudoClass(pseudo);
-        inspector.selection.once("pseudoclass", () => {
-          callback();
-        });
+        inspector.once("computed-view-refreshed", callback);
       });
     });
   });
 }
 
-function testAdded()
+function showPickerOn(node, cb)
+{
+  let highlighter = inspector.toolbox.highlighter;
+  highlighter.showBoxModel(getNodeFront(node)).then(cb);
+}
+
+function testAdded(cb)
 {
   // lock is applied to it and ancestors
   let node = div;
   do {
     is(DOMUtils.hasPseudoClassLock(node, pseudo), true,
-       "pseudo-class lock has been applied");
+      "pseudo-class lock has been applied");
     node = node.parentNode;
   } while (node.parentNode)
 
-  // infobar selector contains pseudo-class
-  let pseudoClassesBox = getActiveInspector().highlighter.nodeInfo.pseudoClassesBox;
-  is(pseudoClassesBox.textContent, pseudo, "pseudo-class in infobar selector");
+  // ruleview contains pseudo-class rule
+  let rules = ruleview.element.querySelectorAll(".ruleview-rule.theme-separator");
+  is(rules.length, 3, "rule view is showing 3 rules for pseudo-class locked div");
+  is(rules[1]._ruleEditor.rule.selectorText, "div:hover", "rule view is showing " + pseudo + " rule");
 
-  // ruleview contains pseudo-class rule
-  is(ruleview.element.children.length, 3,
-     "rule view is showing 3 rules for pseudo-class locked div");
-
-  is(ruleview.element.children[1]._ruleEditor.rule.selectorText,
-     "div:hover", "rule view is showing " + pseudo + " rule");
+  // Show the highlighter by starting the pick mode and hovering over the div
+  showPickerOn(div, () => {
+    // infobar selector contains pseudo-class
+    let pseudoClassesBox = getHighlighter().querySelector(".highlighter-nodeinfobar-pseudo-classes");
+    is(pseudoClassesBox.textContent, pseudo, "pseudo-class in infobar selector");
+    cb();
+  });
 }
 
 function testRemoved()
 {
   // lock removed from node and ancestors
   let node = div;
   do {
     is(DOMUtils.hasPseudoClassLock(node, pseudo), false,
        "pseudo-class lock has been removed");
     node = node.parentNode;
   } while (node.parentNode)
 }
 
-function testRemovedFromUI()
+function testRemovedFromUI(cb)
 {
-  // infobar selector doesn't contain pseudo-class
-  let pseudoClassesBox = getActiveInspector().highlighter.nodeInfo.pseudoClassesBox;
-  is(pseudoClassesBox.textContent, "", "pseudo-class removed from infobar selector");
+  // ruleview no longer contains pseudo-class rule
+  let rules = ruleview.element.querySelectorAll(".ruleview-rule.theme-separator");
+  is(rules.length, 2, "rule view is showing 2 rules after removing lock");
 
-  // ruleview no longer contains pseudo-class rule
-  is(ruleview.element.children.length, 2,
-     "rule view is showing 2 rules after removing lock");
+  showPickerOn(div, () => {
+    let pseudoClassesBox = getHighlighter().querySelector(".highlighter-nodeinfobar-pseudo-classes");
+    is(pseudoClassesBox.textContent, "", "pseudo-class removed from infobar selector");
+    cb();
+  });
 }
 
 function finishUp()
 {
   gDevTools.once("toolbox-destroyed", function() {
     testRemoved();
     inspector = ruleview = null;
     doc = div = null;
--- a/browser/devtools/inspector/test/browser_inspector_reload.js
+++ b/browser/devtools/inspector/test/browser_inspector_reload.js
@@ -39,13 +39,14 @@ function test() {
 
   function onReload() {
     info("Page reloaded");
     let p = content.document.querySelector("p");
     inspector.selection.setNode(p);
     inspector.once("inspector-updated", () => {
       is(inspector.selection.node, p, "Node re-selected.");
       toolbox.destroy();
+      toolbox = inspector = null;
       gBrowser.removeCurrentTab();
       finish();
     });
   }
 }
--- a/browser/devtools/inspector/test/browser_inspector_scrolling.js
+++ b/browser/devtools/inspector/test/browser_inspector_scrolling.js
@@ -28,19 +28,18 @@ function createDocument()
   iframe.src = "data:text/html,foo bar";
   doc.body.appendChild(iframe);
 }
 
 function inspectNode(aInspector)
 {
   inspector = aInspector;
 
-  inspector.highlighter.once("locked", performScrollingTest);
+  inspector.once("inspector-updated", performScrollingTest);
   executeSoon(function() {
-    inspector.highlighter.unlock();
     inspector.selection.setNode(div, "");
   });
 }
 
 function performScrollingTest()
 {
   executeSoon(function() {
     // FIXME: this will fail on retina displays. EventUtils will only scroll
--- a/browser/devtools/inspector/test/browser_inspector_select_last_selected.js
+++ b/browser/devtools/inspector/test/browser_inspector_select_last_selected.js
@@ -25,27 +25,26 @@ function test() {
   }, true);
   content.location = page1;
 
   function startTests() {
     testSameNodeSelectedOnPageReload();
   }
 
   function endTests() {
-    inspector.destroy().then(() =>
-      toolbox.destroy()
-    ).then(() => {
+    inspector.destroy();
+    toolbox.destroy().then(() => {
       toolbox = inspector = page1 = page2 = null;
       gBrowser.removeCurrentTab();
       finish();
     });
   }
 
   function loadPageAnd(page, callback) {
-    inspector.once("markuploaded", () => {
+    inspector.once("new-root", () => {
       callback();
     });
 
     if (page) {
       content.location = page;
     } else {
       content.location.reload();
     }
--- a/browser/devtools/inspector/test/head.js
+++ b/browser/devtools/inspector/test/head.js
@@ -1,21 +1,20 @@
 /* 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/. */
 
 const Cu = Components.utils;
 const Ci = Components.interfaces;
 const Cc = Components.classes;
 
-Services.prefs.setBoolPref("devtools.debugger.log", true);
-SimpleTest.registerCleanupFunction(() => {
-  Services.prefs.clearUserPref("devtools.debugger.log");
-});
-
+// Services.prefs.setBoolPref("devtools.debugger.log", true);
+// SimpleTest.registerCleanupFunction(() => {
+//   Services.prefs.clearUserPref("devtools.debugger.log");
+// });
 
 let tempScope = {};
 Cu.import("resource://gre/modules/devtools/LayoutHelpers.jsm", tempScope);
 let LayoutHelpers = tempScope.LayoutHelpers;
 
 let {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", tempScope);
 let TargetFactory = devtools.TargetFactory;
 
@@ -47,50 +46,73 @@ function getActiveInspector()
 }
 
 function getNodeFront(node)
 {
   let inspector = getActiveInspector();
   return inspector.walker.frontForRawNode(node);
 }
 
+function getHighlighter()
+{
+  return gBrowser.selectedBrowser.parentNode.querySelector(".highlighter-container");
+}
+
+function getHighlighterOutline()
+{
+  let h = getHighlighter();
+  if (h) {
+    return h.querySelector(".highlighter-outline");
+  }
+}
+
+function getHighlighterOutlineRect() {
+  let helper = new LayoutHelpers(window.content);
+  let outline = getHighlighterOutline();
+
+  if (outline) {
+    let browserOffsetRect = helper.getDirtyRect(gBrowser.selectedBrowser);
+    let outlineRect = helper.getDirtyRect(outline);
+    outlineRect.top -= browserOffsetRect.top;
+    outlineRect.left -= browserOffsetRect.left;
+
+    return outlineRect;
+  }
+}
+
 function isHighlighting()
 {
-  let outline = getActiveInspector().highlighter.outline;
-  return !(outline.getAttribute("hidden") == "true");
+  let outline = getHighlighterOutline();
+  return outline && !outline.hasAttribute("hidden");
 }
 
 function getHighlitNode()
 {
-  let h = getActiveInspector().highlighter;
-  if (!isHighlighting() || !h._contentRect)
-    return null;
+  if (isHighlighting()) {
+    let helper = new LayoutHelpers(window.content);
+    let outlineRect = getHighlighterOutlineRect();
 
-  let a = {
-    x: h._contentRect.left,
-    y: h._contentRect.top
-  };
+    let a = {
+      x: outlineRect.left,
+      y: outlineRect.top
+    };
 
-  let b = {
-    x: a.x + h._contentRect.width,
-    y: a.y + h._contentRect.height
-  };
+    let b = {
+      x: a.x + outlineRect.width,
+      y: a.y + outlineRect.height
+    };
 
-  // Get midpoint of diagonal line.
-  let midpoint = midPoint(a, b);
-
-  let lh = new LayoutHelpers(window.content);
-  return lh.getElementFromPoint(h.win.document, midpoint.x,
-    midpoint.y);
+    let {x, y} = getMidPoint(a, b);
+    return helper.getElementFromPoint(window.content.document, x, y);
+  }
 }
 
-
-function midPoint(aPointA, aPointB)
+function getMidPoint(aPointA, aPointB)
 {
-  let pointC = { };
+  let pointC = {};
   pointC.x = (aPointB.x - aPointA.x) / 2 + aPointA.x;
   pointC.y = (aPointB.y - aPointA.y) / 2 + aPointA.y;
   return pointC;
 }
 
 function computedView()
 {
   let sidebar = getActiveInspector().sidebar;
@@ -184,12 +206,19 @@ function getComputedPropertyValue(aName)
 
     if (name.textContent === aName) {
       let value = prop.querySelector(".property-value");
       return value.textContent;
     }
   }
 }
 
+function getContainerForRawNode(markupView, rawNode)
+{
+  let front = markupView.walker.frontForRawNode(rawNode);
+  let container = markupView.getContainer(front);
+  return container;
+}
+
 SimpleTest.registerCleanupFunction(function () {
   let target = TargetFactory.forTab(gBrowser.selectedTab);
   gDevTools.closeToolbox(target);
 });
--- a/browser/devtools/layoutview/view.js
+++ b/browser/devtools/layoutview/view.js
@@ -29,22 +29,18 @@ function LayoutView(aInspector, aWindow)
   this.init();
 }
 
 LayoutView.prototype = {
   init: function LV_init() {
     this.update = this.update.bind(this);
     this.onNewNode = this.onNewNode.bind(this);
     this.onNewSelection = this.onNewSelection.bind(this);
-    this.onHighlighterLocked = this.onHighlighterLocked.bind(this);
     this.inspector.selection.on("new-node-front", this.onNewSelection);
     this.inspector.sidebar.on("layoutview-selected", this.onNewNode);
-    if (this.inspector.highlighter) {
-      this.inspector.highlighter.on("locked", this.onHighlighterLocked);
-    }
 
     // Store for the different dimensions of the node.
     // 'selector' refers to the element that holds the value in view.xhtml;
     // 'property' is what we are measuring;
     // 'value' is the computed dimension, computed in update().
     this.map = {
       position: {selector: "#element-position",
                  property: "position",
@@ -101,19 +97,16 @@ LayoutView.prototype = {
    * Destroy the nodes. Remove listeners.
    */
   destroy: function LV_destroy() {
     this.inspector.sidebar.off("layoutview-selected", this.onNewNode);
     this.inspector.selection.off("new-node-front", this.onNewSelection);
     if (this.browser) {
       this.browser.removeEventListener("MozAfterPaint", this.update, true);
     }
-    if (this.inspector.highlighter) {
-      this.inspector.highlighter.off("locked", this.onHighlighterLocked);
-    }
     this.sizeHeadingLabel = null;
     this.sizeLabel = null;
     this.inspector = null;
     this.doc = null;
   },
 
   /**
    * Selection 'new-node-front' event handler.
@@ -121,34 +114,25 @@ LayoutView.prototype = {
   onNewSelection: function() {
     let done = this.inspector.updating("layoutview");
     this.onNewNode().then(done, (err) => { console.error(err); done() });
   },
 
   onNewNode: function LV_onNewNode() {
     if (this.isActive() &&
         this.inspector.selection.isConnected() &&
-        this.inspector.selection.isElementNode() &&
-        this.inspector.selection.reason != "highlighter") {
+        this.inspector.selection.isElementNode()) {
       this.undim();
     } else {
       this.dim();
     }
     return this.update();
   },
 
   /**
-   * Highlighter 'locked' event handler
-   */
-  onHighlighterLocked: function LV_onHighlighterLocked() {
-    this.undim();
-    this.update();
-  },
-
-  /**
    * Hide the layout boxes. No node are selected.
    */
   dim: function LV_dim() {
     if (this.browser) {
       this.browser.removeEventListener("MozAfterPaint", this.update, true);
     }
     this.trackingPaint = false;
     this.doc.body.classList.add("dim");
--- a/browser/devtools/main.js
+++ b/browser/devtools/main.js
@@ -104,19 +104,17 @@ Tools.inspector = {
   icon: "chrome://browser/skin/devtools/tool-inspector@2x.png",
   url: "chrome://browser/content/devtools/inspector/inspector.xul",
   label: l10n("inspector.label", inspectorStrings),
   tooltip: l10n("inspector.tooltip", inspectorStrings),
   inMenu: true,
 
   preventClosingOnKey: true,
   onkey: function(panel) {
-    if (panel.highlighter) {
-      panel.highlighter.toggleLockState();
-    }
+    panel.toolbox.togglePicker();
   },
 
   isTargetSupported: function(target) {
     return true;
   },
 
   build: function(iframeWindow, toolbox) {
     let panel = new InspectorPanel(iframeWindow, toolbox);
--- a/browser/devtools/markupview/markup-view.css
+++ b/browser/devtools/markupview/markup-view.css
@@ -65,17 +65,17 @@
 .html-editor-container {
   position: relative;
   min-height: 200px;
 }
 
 /* This extra element placed in each tag is positioned absolutely to cover the
  * whole tag line and is used for background styling (when a selection is made
  * or when the tag is flashing) */
-.tag-line .highlighter {
+.tag-line .tag-state {
   position: absolute;
   left: -1000em;
   right: 0;
   height: 100%;
   z-index: -1;
 }
 
 .expander {
@@ -115,17 +115,17 @@
   margin-right: -1em;
   padding: 1px 0;
 }
 
 .newattr:focus {
   margin-right: 0;
 }
 
-.highlighter.flash-out {
+.tag-state.flash-out {
   transition: background .5s;
 }
 
 /* Preview */
 
 #previewbar {
   position: fixed;
   top: 0;
--- a/browser/devtools/markupview/markup-view.js
+++ b/browser/devtools/markupview/markup-view.js
@@ -10,24 +10,26 @@ const {Cc, Cu, Ci} = require("chrome");
 const PAGE_SIZE = 10;
 const PREVIEW_AREA = 700;
 const DEFAULT_MAX_CHILDREN = 100;
 const COLLAPSE_ATTRIBUTE_LENGTH = 120;
 const COLLAPSE_DATA_URL_REGEX = /^data.+base64/;
 const COLLAPSE_DATA_URL_LENGTH = 60;
 const CONTAINER_FLASHING_DURATION = 500;
 const IMAGE_PREVIEW_MAX_DIM = 400;
+const NEW_SELECTION_HIGHLIGHTER_TIMER = 1000;
 
 const {UndoStack} = require("devtools/shared/undo");
 const {editableField, InplaceEditor} = require("devtools/shared/inplace-editor");
 const {gDevTools} = Cu.import("resource:///modules/devtools/gDevTools.jsm", {});
 const {HTMLEditor} = require("devtools/markupview/html-editor");
 const {OutputParser} = require("devtools/output-parser");
 const promise = require("sdk/core/promise");
 const {Tooltip} = require("devtools/shared/widgets/Tooltip");
+const EventEmitter = require("devtools/shared/event-emitter");
 
 Cu.import("resource://gre/modules/devtools/LayoutHelpers.jsm");
 Cu.import("resource://gre/modules/devtools/Templater.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 
 loader.lazyGetter(this, "DOMParser", function() {
  return Cc["@mozilla.org/xmlextras/domparser;1"].createInstance(Ci.nsIDOMParser);
 });
@@ -47,16 +49,20 @@ loader.lazyGetter(this, "AutocompletePop
 /**
  * The markup tree.  Manages the mapping of nodes to MarkupContainers,
  * updating based on mutations, and the undo/redo bindings.
  *
  * @param Inspector aInspector
  *        The inspector we're watching.
  * @param iframe aFrame
  *        An iframe in which the caller has kindly loaded markup-view.xhtml.
+ *
+ * Fires the following events:
+ * - node-highlight: When a node in the markup-view is hovered and the
+ *   corresponding node in the content gets highlighted
  */
 function MarkupView(aInspector, aFrame, aControllerWindow) {
   this._inspector = aInspector;
   this.walker = this._inspector.walker;
   this._frame = aFrame;
   this.doc = this._frame.contentDocument;
   this._elt = this.doc.querySelector("#root");
   this._outputParser = new OutputParser();
@@ -95,27 +101,142 @@ function MarkupView(aInspector, aFrame, 
 
   this._boundFocus = this._onFocus.bind(this);
   this._frame.addEventListener("focus", this._boundFocus, false);
 
   this._handlePrefChange = this._handlePrefChange.bind(this);
   gDevTools.on("pref-changed", this._handlePrefChange);
 
   this._initPreview();
+  this._initTooltips();
+  this._initHighlighter();
 
-  this.tooltip = new Tooltip(this._inspector.panelDoc);
-  this.tooltip.startTogglingOnHover(this._elt,
-    this._buildTooltipContent.bind(this));
+  EventEmitter.decorate(this);
 }
 
 exports.MarkupView = MarkupView;
 
 MarkupView.prototype = {
   _selectedContainer: null,
 
+  _initTooltips: function() {
+    this.tooltip = new Tooltip(this._inspector.panelDoc);
+    this.tooltip.startTogglingOnHover(this._elt,
+      this._buildTooltipContent.bind(this));
+  },
+
+  _initHighlighter: function() {
+    // Show the box model on markup-view mousemove
+    this._onMouseMove = this._onMouseMove.bind(this);
+    this._elt.addEventListener("mousemove", this._onMouseMove, false);
+    this._onMouseLeave = this._onMouseLeave.bind(this);
+    this._elt.addEventListener("mouseleave", this._onMouseLeave, false);
+
+    // Show markup-containers as hovered on toolbox "picker-node-hovered" event
+    // which happens when the "pick" button is pressed
+    this._onToolboxPickerHover = (event, nodeFront) => {
+      this.showNode(nodeFront, true).then(() => {
+        this._showContainerAsHovered(nodeFront);
+      });
+    }
+    this._inspector.toolbox.on("picker-node-hovered", this._onToolboxPickerHover);
+  },
+
+  _onMouseMove: function(event) {
+    let target = event.target;
+
+    // Search target for a markupContainer reference, if not found, walk up
+    while (!target.container) {
+      if (target.tagName.toLowerCase() === "body") {
+        return;
+      }
+      target = target.parentNode;
+    }
+
+    let container = target.container;
+    if (this._hoveredNode !== container.node) {
+      if (container.node.nodeType !== Ci.nsIDOMNode.TEXT_NODE) {
+        this._showBoxModel(container.node);
+      } else {
+        this._hideBoxModel();
+      }
+    }
+    this._showContainerAsHovered(container.node);
+  },
+
+  _hoveredNode: null,
+  _showContainerAsHovered: function(nodeFront) {
+    if (this._hoveredNode !== nodeFront) {
+      if (this._hoveredNode) {
+        this._containers.get(this._hoveredNode).hovered = false;
+      }
+      this._containers.get(nodeFront).hovered = true;
+
+      this._hoveredNode = nodeFront;
+    }
+  },
+
+  _onMouseLeave: function() {
+    this._hideBoxModel();
+  },
+
+  _showBoxModel: function(nodeFront, options={}) {
+    let toolbox = this._inspector.toolbox;
+
+    // If the remote highlighter exists on the target, use it
+    if (toolbox.isRemoteHighlightable) {
+      toolbox.initInspector().then(() => {
+        toolbox.highlighter.showBoxModel(nodeFront, options).then(() => {
+          this.emit("node-highlight", nodeFront);
+        });
+      });
+    }
+    // Else, revert to the "older" version of the highlighter in the walker
+    // actor
+    else {
+      this.walker.highlight(nodeFront).then(() => {
+        this.emit("node-highlight", nodeFront);
+      });
+    }
+  },
+
+  _hideBoxModel: function() {
+    let deferred = promise.defer();
+    let toolbox = this._inspector.toolbox;
+
+    // If the remote highlighter exists on the target, use it
+    if (toolbox.isRemoteHighlightable) {
+      toolbox.initInspector().then(() => {
+        toolbox.highlighter.hideBoxModel().then(deferred.resolve);
+      });
+    } else {
+      deferred.resolve();
+    }
+    // If not, no need to unhighlight as the older highlight method uses a
+    // setTimeout to hide itself
+
+    return deferred.promise;
+  },
+
+  _briefBoxModelTimer: null,
+  _brieflyShowBoxModel: function(nodeFront, options) {
+    let win = this._frame.contentWindow;
+
+    if (this._briefBoxModelTimer) {
+      win.clearTimeout(this._briefBoxModelTimer);
+      this._briefBoxModelTimer = null;
+    }
+
+    this._showBoxModel(nodeFront, options);
+
+    this._briefBoxModelTimer = this._frame.contentWindow.setTimeout(() => {
+      this._hideBoxModel();
+    }, NEW_SELECTION_HIGHLIGHTER_TIMER);
+  },
+
   template: function(aName, aDest, aOptions={stack: "markup-view.xhtml"}) {
     let node = this.doc.getElementById("template-" + aName).cloneNode(true);
     node.removeAttribute("id");
     template(node, aDest, aOptions);
     return node;
   },
 
   /**
@@ -171,21 +292,32 @@ MarkupView.prototype = {
       return container._buildTooltipContent(target, this.tooltip);
     }
   },
 
   /**
    * Highlight the inspector selected node.
    */
   _onNewSelection: function() {
+    let selection = this._inspector.selection;
+
     this.htmlEditor.hide();
     let done = this._inspector.updating("markup-view");
-    if (this._inspector.selection.isNode()) {
-      this.showNode(this._inspector.selection.nodeFront, true).then(() => {
-        this.markNodeAsSelected(this._inspector.selection.nodeFront);
+    if (selection.isNode()) {
+      let reason = selection.reason;
+      if (reason && reason !== "inspector-open" && reason !== "navigateaway") {
+        this._brieflyShowBoxModel(selection.nodeFront, {
+          scrollIntoView: true
+        });
+      }
+
+      this.showNode(selection.nodeFront, true).then(() => {
+        if (selection.reason !== "treepanel") {
+          this.markNodeAsSelected(selection.nodeFront);
+        }
         done();
       });
     } else {
       this.unmarkSelectedNode();
       done();
     }
   },
 
@@ -365,21 +497,16 @@ MarkupView.prototype = {
   navigate: function(aContainer, aIgnoreFocus) {
     if (!aContainer) {
       return;
     }
 
     let node = aContainer.node;
     this.markNodeAsSelected(node, "treepanel");
 
-    // This event won't be fired if the node is the same. But the highlighter
-    // need to lock the node if it wasn't.
-    this._inspector.selection.emit("new-node");
-    this._inspector.selection.emit("new-node-front");
-
     if (!aIgnoreFocus) {
       aContainer.focus();
     }
   },
 
   /**
    * Make sure a node is included in the markup tool.
    *
@@ -935,65 +1062,74 @@ MarkupView.prototype = {
   },
 
   /**
    * Tear down the markup panel.
    */
   destroy: function() {
     gDevTools.off("pref-changed", this._handlePrefChange);
 
-    delete this._outputParser;
+    // Note that if the toolbox is closed, this will work fine, but will fail
+    // in case the browser is closed and will trigger a noSuchActor message.
+    this._hideBoxModel();
+
+    this._hoveredNode = null;
+    this._inspector.toolbox.off("picker-node-hovered", this._onToolboxPickerHover);
+
+    this._outputParser = null;
 
     this.htmlEditor.destroy();
-    delete this.htmlEditor;
+    this.htmlEditor = null;
 
     this.undo.destroy();
-    delete this.undo;
+    this.undo = null;
 
     this.popup.destroy();
-    delete this.popup;
+    this.popup = null;
 
     this._frame.removeEventListener("focus", this._boundFocus, false);
-    delete this._boundFocus;
+    this._boundFocus = null;
 
     if (this._boundUpdatePreview) {
       this._frame.contentWindow.removeEventListener("scroll",
         this._boundUpdatePreview, true);
-      delete this._boundUpdatePreview;
+      this._boundUpdatePreview = null;
     }
 
     if (this._boundResizePreview) {
       this._frame.contentWindow.removeEventListener("resize",
         this._boundResizePreview, true);
       this._frame.contentWindow.removeEventListener("overflow",
         this._boundResizePreview, true);
       this._frame.contentWindow.removeEventListener("underflow",
         this._boundResizePreview, true);
-      delete this._boundResizePreview;
+      this._boundResizePreview = null;
     }
 
     this._frame.contentWindow.removeEventListener("keydown",
       this._boundKeyDown, false);
-    delete this._boundKeyDown;
+    this._boundKeyDown = null;
 
     this._inspector.selection.off("new-node-front", this._boundOnNewSelection);
-    delete this._boundOnNewSelection;
+    this._boundOnNewSelection = null;
 
     this.walker.off("mutations", this._boundMutationObserver)
-    delete this._boundMutationObserver;
+    this._boundMutationObserver = null;
 
-    delete this._elt;
+    this._elt.removeEventListener("mousemove", this._onMouseMove, false);
+    this._elt.removeEventListener("mouseleave", this._onMouseLeave, false);
+    this._elt = null;
 
     for (let [key, container] of this._containers) {
       container.destroy();
     }
-    delete this._containers;
+    this._containers = null;
 
     this.tooltip.destroy();
-    delete this.tooltip;
+    this.tooltip = null;
   },
 
   /**
    * Initialize the preview panel.
    */
   _initPreview: function() {
     this._previewEnabled = Services.prefs.getBoolPref("devtools.inspector.markupPreview");
     if (!this._previewEnabled) {
@@ -1109,37 +1245,28 @@ function MarkupContainer(aMarkupView, aN
     this.editor = new DoctypeEditor(this, aNode);
   } else {
     this.editor = new GenericEditor(this, aNode);
   }
 
   // The template will fill the following properties
   this.elt = null;
   this.expander = null;
-  this.highlighter = null;
+  this.tagState = null;
   this.tagLine = null;
   this.children = null;
   this.markup.template("container", this);
   this.elt.container = this;
   this.children.container = this;
 
   // Expanding/collapsing the node on dblclick of the whole tag-line element
   this._onToggle = this._onToggle.bind(this);
   this.elt.addEventListener("dblclick", this._onToggle, false);
   this.expander.addEventListener("click", this._onToggle, false);
 
-  // Dealing with the highlighting of the row via javascript rather than :hover
-  // This is to allow highlighting the closing tag-line as well as reusing the
-  // theme css classes (which wouldn't have been possible with a :hover pseudo)
-  this._onMouseOver = this._onMouseOver.bind(this);
-  this.elt.addEventListener("mouseover", this._onMouseOver, false);
-
-  this._onMouseOut = this._onMouseOut.bind(this);
-  this.elt.addEventListener("mouseout", this._onMouseOut, false);
-
   // Appending the editor element and attaching event listeners
   this.tagLine.appendChild(this.editor.elt);
 
   this._onMouseDown = this._onMouseDown.bind(this);
   this.elt.addEventListener("mousedown", this._onMouseDown, false);
 
   this._onClick = this._onClick.bind(this);
   this.elt.addEventListener("click", this._onClick, false);
@@ -1230,32 +1357,30 @@ MarkupContainer.prototype = {
       // tag-line that the user can interact with and showing the children.
       if (this.editor instanceof ElementEditor) {
         let closingTag = this.elt.querySelector(".close");
         if (closingTag) {
           if (!this.closeTagLine) {
             let line = this.markup.doc.createElement("div");
             line.classList.add("tag-line");
 
-            let highlighter = this.markup.doc.createElement("div");
-            highlighter.classList.add("highlighter");
-            line.appendChild(highlighter);
+            let tagState = this.markup.doc.createElement("div");
+            tagState.classList.add("tag-state");
+            line.appendChild(tagState);
 
             line.appendChild(closingTag.cloneNode(true));
-            line.addEventListener("mouseover", this._onMouseOver, false);
-            line.addEventListener("mouseout", this._onMouseOut, false);
 
             this.closeTagLine = line;
           }
           this.elt.appendChild(this.closeTagLine);
         }
       }
       this.elt.classList.remove("collapsed");
       this.expander.setAttribute("open", "");
-      this.highlighted = false;
+      this.hovered = false;
     } else if (!aValue) {
       if (this.editor instanceof ElementEditor && this.closeTagLine) {
         this.elt.removeChild(this.closeTagLine);
       }
       this.elt.classList.add("collapsed");
       this.expander.removeAttribute("open");
     }
   },
@@ -1263,29 +1388,19 @@ MarkupContainer.prototype = {
   _onToggle: function(event) {
     this.markup.navigate(this);
     if(this.hasChildren) {
       this.markup.setNodeExpanded(this.node, !this.expanded);
     }
     event.stopPropagation();
   },
 
-  _onMouseOver: function(event) {
-    this.highlighted = true;
-    event.stopPropagation();
-  },
-
-  _onMouseOut: function(event) {
-    this.highlighted = false;
-    event.stopPropagation();
-  },
-
   _onMouseDown: function(event) {
     if (event.target.nodeName !== "a") {
-      this.highlighted = false;
+      this.hovered = false;
       this.markup.navigate(this);
       event.stopPropagation();
     }
   },
 
   _onClick: function(event) {
     let target = event.target;
 
@@ -1314,64 +1429,64 @@ MarkupContainer.prototype = {
         this.flashed = false;
       }, CONTAINER_FLASHING_DURATION);
     }
   },
 
   set flashed(aValue) {
     if (aValue) {
       // Make sure the animation class is not here
-      this.highlighter.classList.remove("flash-out");
+      this.tagState.classList.remove("flash-out");
 
       // Change the background
-      this.highlighter.classList.add("theme-bg-contrast");
+      this.tagState.classList.add("theme-bg-contrast");
 
       // Change the text color
       this.editor.elt.classList.add("theme-fg-contrast");
       [].forEach.call(
         this.editor.elt.querySelectorAll("[class*=theme-fg-color]"),
         span => span.classList.add("theme-fg-contrast")
       );
     } else {
       // Add the animation class to smoothly remove the background
-      this.highlighter.classList.add("flash-out");
+      this.tagState.classList.add("flash-out");
 
       // Remove the background
-      this.highlighter.classList.remove("theme-bg-contrast");
+      this.tagState.classList.remove("theme-bg-contrast");
 
       // Remove the text color
       this.editor.elt.classList.remove("theme-fg-contrast");
       [].forEach.call(
         this.editor.elt.querySelectorAll("[class*=theme-fg-color]"),
         span => span.classList.remove("theme-fg-contrast")
       );
     }
   },
 
-  _highlighted: false,
+  _hovered: false,
 
   /**
    * Highlight the currently hovered tag + its closing tag if necessary
    * (that is if the tag is expanded)
    */
-  set highlighted(aValue) {
-    this.highlighter.classList.remove("flash-out");
-    this._highlighted = aValue;
+  set hovered(aValue) {
+    this.tagState.classList.remove("flash-out");
+    this._hovered = aValue;
     if (aValue) {
       if (!this.selected) {
-        this.highlighter.classList.add("theme-bg-darker");
+        this.tagState.classList.add("theme-bg-darker");
       }
       if (this.closeTagLine) {
-        this.closeTagLine.querySelector(".highlighter").classList.add(
+        this.closeTagLine.querySelector(".tag-state").classList.add(
           "theme-bg-darker");
       }
     } else {
-      this.highlighter.classList.remove("theme-bg-darker");
+      this.tagState.classList.remove("theme-bg-darker");
       if (this.closeTagLine) {
-        this.closeTagLine.querySelector(".highlighter").classList.remove(
+        this.closeTagLine.querySelector(".tag-state").classList.remove(
           "theme-bg-darker");
       }
     }
   },
 
   /**
    * True if the container is visible in the markup tree.
    */
@@ -1384,25 +1499,25 @@ MarkupContainer.prototype = {
    */
   _selected: false,
 
   get selected() {
     return this._selected;
   },
 
   set selected(aValue) {
-    this.highlighter.classList.remove("flash-out");
+    this.tagState.classList.remove("flash-out");
     this._selected = aValue;
     this.editor.selected = aValue;
     if (this._selected) {
       this.tagLine.setAttribute("selected", "");
-      this.highlighter.classList.add("theme-selected");
+      this.tagState.classList.add("theme-selected");
     } else {
       this.tagLine.removeAttribute("selected");
-      this.highlighter.classList.remove("theme-selected");
+      this.tagState.classList.remove("theme-selected");
     }
   },
 
   /**
    * Update the container's editor to the current state of the
    * viewed node.
    */
   update: function() {
--- a/browser/devtools/markupview/markup-view.xhtml
+++ b/browser/devtools/markupview/markup-view.xhtml
@@ -18,17 +18,17 @@
 <body class="theme-body devtools-monospace" role="application">
   <div id="root-wrapper">
     <div id="root"></div>
   </div>
   <div id="templates" style="display:none">
 
     <ul class="children">
       <li id="template-container" save="${elt}" class="child collapsed">
-        <div save="${tagLine}" class="tag-line"><span save="${highlighter}" class="highlighter"></span><span save="${expander}" class="theme-twisty expander"></span></div>
+        <div save="${tagLine}" class="tag-line"><span save="${tagState}" class="tag-state"></span><span save="${expander}" class="theme-twisty expander"></span></div>
         <ul save="${children}" class="children"></ul>
       </li>
 
       <li id="template-more-nodes" class="more-nodes devtools-class-comment" save="${elt}"><span>${showing}</span> <button href="#" onclick="${allButtonClick}">${showAll}</button></li>
     </ul>
 
     <span id="template-element" save="${elt}" class="editor"><span class="open">&lt;<span save="${tag}" class="tag theme-fg-color3" tabindex="0"></span><span save="${attrList}"></span><span save="${newAttr}" class="newattr" tabindex="0"></span><span class="closing-bracket">&gt;</span></span><span class="close">&lt;/<span save="${closeTag}" class="tag theme-fg-color3"></span>&gt;</span></span>
 
--- a/browser/devtools/markupview/test/browser_inspector_markup_edit.js
+++ b/browser/devtools/markupview/test/browser_inspector_markup_edit.js
@@ -676,19 +676,16 @@ function test() {
       desc: "Add attributes by adding to an existing attribute's entry",
       setup: function() {
         inspector.selection.setNode(doc.querySelector("#node18"));
       },
       before: function() {
         assertAttributes(doc.querySelector("#node18"), {
           id: "node18",
         });
-
-        is(inspector.highlighter.nodeInfo.classesBox.textContent, "",
-           "No classes in the infobar before edit.");
       },
       execute: function(after) {
         inspector.once("markupmutation", function() {
           // needed because we need to make sure the infobar is updated
           // not just the markupview (which happens in this event loop)
           executeSoon(after);
         });
         let editor = getContainerForRawNode(markup, doc.querySelector("#node18")).editor;
@@ -696,19 +693,16 @@ function test() {
         editField(attr, attr.textContent + ' class="newclass" style="color:green"');
       },
       after: function() {
         assertAttributes(doc.querySelector("#node18"), {
           id: "node18",
           class: "newclass",
           style: "color:green"
         });
-
-        is(inspector.highlighter.nodeInfo.classesBox.textContent, ".newclass",
-           "Correct classes in the infobar after edit.");
       }
     };
     testAsyncSetup(test, editTagName);
   }
 
   function editTagName() {
     let test =  {
       desc: "Edit the tag name",
--- a/browser/devtools/markupview/test/browser_inspector_markup_mutation_flashing.js
+++ b/browser/devtools/markupview/test/browser_inspector_markup_mutation_flashing.js
@@ -118,13 +118,13 @@ function test() {
   }
 
   function assertNodeFlashing(rawNode) {
     let container = getContainerForRawNode(markup, rawNode);
 
     if(!container) {
       ok(false, "Node not found");
     } else {
-      ok(container.highlighter.classList.contains("theme-bg-contrast"),
+      ok(container.tagState.classList.contains("theme-bg-contrast"),
         "Node is flashing");
     }
   }
 }
--- a/browser/devtools/styleinspector/computed-view.js
+++ b/browser/devtools/styleinspector/computed-view.js
@@ -386,19 +386,16 @@ CssHtmlTree.prototype = {
       // Reset zebra striping.
       this._darkStripe = true;
 
       let deferred = promise.defer();
       this._refreshProcess = new UpdateProcess(this.styleWindow, this.propertyViews, {
         onItem: (aPropView) => {
           aPropView.refresh();
         },
-        onCancel: () => {
-          deferred.reject("refresh cancelled");
-        },
         onDone: () => {
           this._refreshProcess = null;
           this.noResults.hidden = this.numVisibleProperties > 0;
           this.styleInspector.inspector.emit("computed-view-refreshed");
           deferred.resolve(undefined);
         }
       });
       this._refreshProcess.schedule();
--- a/browser/devtools/styleinspector/rule-view.js
+++ b/browser/devtools/styleinspector/rule-view.js
@@ -1379,17 +1379,17 @@ CssRuleView.prototype = {
     // Repopulate the element style.
     this._populate();
   },
 
   _populate: function() {
     let elementStyle = this._elementStyle;
     return this._elementStyle.populate().then(() => {
       if (this._elementStyle != elementStyle) {
-        return promise.reject("element changed");
+        return;
       }
       this._createEditors();
 
       // Notify anyone that cares that we refreshed.
       var evt = this.doc.createEvent("Events");
       evt.initEvent("CssRuleViewRefreshed", true, false);
       this.element.dispatchEvent(evt);
       return undefined;
--- a/browser/devtools/styleinspector/style-inspector.js
+++ b/browser/devtools/styleinspector/style-inspector.js
@@ -77,19 +77,16 @@ function RuleViewTool(aInspector, aWindo
 
   this._onSelect = this.onSelect.bind(this);
   this.inspector.selection.on("detached", this._onSelect);
   this.inspector.selection.on("new-node-front", this._onSelect);
   this.refresh = this.refresh.bind(this);
   this.inspector.on("layout-change", this.refresh);
 
   this.inspector.selection.on("pseudoclass", this.refresh);
-  if (this.inspector.highlighter) {
-    this.inspector.highlighter.on("locked", this._onSelect);
-  }
 
   this.onSelect();
 }
 
 exports.RuleViewTool = RuleViewTool;
 
 RuleViewTool.prototype = {
   onSelect: function RVT_onSelect(aEvent) {
@@ -97,41 +94,29 @@ RuleViewTool.prototype = {
 
     if (!this.inspector.selection.isConnected() ||
         !this.inspector.selection.isElementNode()) {
       this.view.highlight(null);
       return;
     }
 
     if (!aEvent || aEvent == "new-node-front") {
-      if (this.inspector.selection.reason == "highlighter") {
-        this.view.highlight(null);
-      } else {
-        let done = this.inspector.updating("rule-view");
-        this.view.highlight(this.inspector.selection.nodeFront).then(done, done);
-      }
-    }
-
-    if (aEvent == "locked") {
       let done = this.inspector.updating("rule-view");
       this.view.highlight(this.inspector.selection.nodeFront).then(done, done);
     }
   },
 
   refresh: function RVT_refresh() {
     this.view.nodeChanged();
   },
 
   destroy: function RVT_destroy() {
     this.inspector.off("layout-change", this.refresh);
     this.inspector.selection.off("pseudoclass", this.refresh);
     this.inspector.selection.off("new-node-front", this._onSelect);
-    if (this.inspector.highlighter) {
-      this.inspector.highlighter.off("locked", this._onSelect);
-    }
 
     this.view.element.removeEventListener("CssRuleViewCSSLinkClicked",
       this._cssLinkHandler);
 
     this.view.element.removeEventListener("CssRuleViewChanged",
       this._changeHandler);
 
     this.view.element.removeEventListener("CssRuleViewRefreshed",
@@ -154,19 +139,16 @@ function ComputedViewTool(aInspector, aW
   this.window = aWindow;
   this.document = aWindow.document;
   this.outerIFrame = aIFrame;
   this.view = new ComputedView.CssHtmlTree(this, aInspector.pageStyle);
 
   this._onSelect = this.onSelect.bind(this);
   this.inspector.selection.on("detached", this._onSelect);
   this.inspector.selection.on("new-node-front", this._onSelect);
-  if (this.inspector.highlighter) {
-    this.inspector.highlighter.on("locked", this._onSelect);
-  }
   this.refresh = this.refresh.bind(this);
   this.inspector.on("layout-change", this.refresh);
   this.inspector.selection.on("pseudoclass", this.refresh);
 
   this.view.highlight(null);
 
   this.onSelect();
 }
@@ -180,27 +162,16 @@ ComputedViewTool.prototype = {
 
     if (!this.inspector.selection.isConnected() ||
         !this.inspector.selection.isElementNode()) {
       this.view.highlight(null);
       return;
     }
 
     if (!aEvent || aEvent == "new-node-front") {
-      if (this.inspector.selection.reason == "highlighter") {
-        // FIXME: We should hide view's content
-      } else {
-        let done = this.inspector.updating("computed-view");
-        this.view.highlight(this.inspector.selection.nodeFront).then(() => {
-          done();
-        });
-      }
-    }
-
-    if (aEvent == "locked" && this.inspector.selection.nodeFront != this.view.viewedElement) {
       let done = this.inspector.updating("computed-view");
       this.view.highlight(this.inspector.selection.nodeFront).then(() => {
         done();
       });
     }
   },
 
   refresh: function CVT_refresh() {
@@ -208,19 +179,16 @@ ComputedViewTool.prototype = {
   },
 
   destroy: function CVT_destroy(aContext)
   {
     this.inspector.off("layout-change", this.refresh);
     this.inspector.sidebar.off("computedview-selected", this.refresh);
     this.inspector.selection.off("pseudoclass", this.refresh);
     this.inspector.selection.off("new-node-front", this._onSelect);
-    if (this.inspector.highlighter) {
-      this.inspector.highlighter.off("locked", this._onSelect);
-    }
 
     this.view.destroy();
     delete this.view;
 
     delete this.outerIFrame;
     delete this.cssLogic;
     delete this.cssHtmlTree;
     delete this.window;
--- a/browser/devtools/styleinspector/test/browser_bug705707_is_content_stylesheet.js
+++ b/browser/devtools/styleinspector/test/browser_bug705707_is_content_stylesheet.js
@@ -62,27 +62,28 @@ function reselectElement(target, cb)
 function testModifyRules()
 {
   // Set a property on all rules, then refresh and make sure they are still
   // there (and there wasn't an error on the server side)
   for (let rule of ruleView._elementStyle.rules) {
     rule.editor.addProperty("font-weight", "bold", "");
   }
 
-  reselectElement(doc.querySelector("#target"), () => {
-
-    for (let rule of ruleView._elementStyle.rules) {
-      let lastRule = rule.textProps[rule.textProps.length - 1];
+  executeSoon(() => {
+    reselectElement(doc.querySelector("#target"), () => {
+      for (let rule of ruleView._elementStyle.rules) {
+        let lastRule = rule.textProps[rule.textProps.length - 1];
 
-      is (lastRule.name, "font-weight", "Last rule name is font-weight");
-      is (lastRule.value, "bold", "Last rule value is bold");
-    }
+        is (lastRule.name, "font-weight", "Last rule name is font-weight");
+        is (lastRule.value, "bold", "Last rule value is bold");
+      }
 
-    gBrowser.removeCurrentTab();
-    openXUL();
+      gBrowser.removeCurrentTab();
+      openXUL();
+    });
   });
 }
 
 
 function openXUL()
 {
   Cc["@mozilla.org/permissionmanager;1"].getService(Ci.nsIPermissionManager)
     .addFromPrincipal(XUL_PRINCIPAL, 'allowXULXBL', Ci.nsIPermissionManager.ALLOW_ACTION);
--- a/browser/devtools/styleinspector/test/browser_ruleview_override.js
+++ b/browser/devtools/styleinspector/test/browser_ruleview_override.js
@@ -15,44 +15,45 @@ function simpleOverride(aInspector, aRul
     '  background-color: blue;' +
     '} ' +
     '.testclass {' +
     '  background-color: green;' +
     '}';
 
   let styleNode = addStyle(doc, style);
   doc.body.innerHTML = '<div id="testid" class="testclass">Styled Node</div>';
-
-  inspector.selection.setNode(doc.getElementById("testid"));
-  inspector.once("inspector-updated", () => {
-    let elementStyle = view._elementStyle;
+  inspector.once("markupmutation", () => {
+    inspector.selection.setNode(doc.getElementById("testid"));
+    inspector.once("inspector-updated", () => {
+      let elementStyle = view._elementStyle;
 
-    let idRule = elementStyle.rules[1];
-    let idProp = idRule.textProps[0];
-    is(idProp.name, "background-color", "First ID prop should be background-color");
-    ok(!idProp.overridden, "ID prop should not be overridden.");
+      let idRule = elementStyle.rules[1];
+      let idProp = idRule.textProps[0];
+      is(idProp.name, "background-color", "First ID prop should be background-color");
+      ok(!idProp.overridden, "ID prop should not be overridden.");
+
+      let classRule = elementStyle.rules[2];
+      let classProp = classRule.textProps[0];
+      is(classProp.name, "background-color", "First class prop should be background-color");
+      ok(classProp.overridden, "Class property should be overridden.");
 
-    let classRule = elementStyle.rules[2];
-    let classProp = classRule.textProps[0];
-    is(classProp.name, "background-color", "First class prop should be background-color");
-    ok(classProp.overridden, "Class property should be overridden.");
+      // Override background-color by changing the element style.
+      let elementRule = elementStyle.rules[0];
+      elementRule.createProperty("background-color", "purple", "");
+      promiseDone(elementRule._applyingModifications.then(() => {
+        let elementProp = elementRule.textProps[0];
+        is(classProp.name, "background-color", "First element prop should now be background-color");
+        ok(!elementProp.overridden, "Element style property should not be overridden");
+        ok(idProp.overridden, "ID property should be overridden");
+        ok(classProp.overridden, "Class property should be overridden");
 
-    // Override background-color by changing the element style.
-    let elementRule = elementStyle.rules[0];
-    elementRule.createProperty("background-color", "purple", "");
-    promiseDone(elementRule._applyingModifications.then(() => {
-      let elementProp = elementRule.textProps[0];
-      is(classProp.name, "background-color", "First element prop should now be background-color");
-      ok(!elementProp.overridden, "Element style property should not be overridden");
-      ok(idProp.overridden, "ID property should be overridden");
-      ok(classProp.overridden, "Class property should be overridden");
-
-      styleNode.parentNode.removeChild(styleNode);
-      partialOverride();
-    }));
+        styleNode.parentNode.removeChild(styleNode);
+        partialOverride();
+      }));
+    });
   });
 }
 
 function partialOverride()
 {
   let style = '' +
     // Margin shorthand property...
     '.testclass {' +
@@ -60,105 +61,108 @@ function partialOverride()
     '}' +
     // ... will be partially overridden.
     '#testid {' +
     '  margin-left: 1px;' +
     '}';
 
   let styleNode = addStyle(doc, style);
   doc.body.innerHTML = '<div id="testid" class="testclass">Styled Node</div>';
-
-  inspector.selection.setNode(doc.getElementById("testid"));
-  inspector.once("inspector-updated", () => {
-    let elementStyle = view._elementStyle;
+  inspector.once("markupmutation", () => {
+    inspector.selection.setNode(doc.getElementById("testid"));
+    inspector.once("inspector-updated", () => {
+      let elementStyle = view._elementStyle;
 
-    let classRule = elementStyle.rules[2];
-    let classProp = classRule.textProps[0];
-    ok(!classProp.overridden, "Class prop shouldn't be overridden, some props are still being used.");
-    for (let computed of classProp.computed) {
-      if (computed.name.indexOf("margin-left") == 0) {
-        ok(computed.overridden, "margin-left props should be overridden.");
-      } else {
-        ok(!computed.overridden, "Non-margin-left props should not be overridden.");
+      let classRule = elementStyle.rules[2];
+      let classProp = classRule.textProps[0];
+      ok(!classProp.overridden, "Class prop shouldn't be overridden, some props are still being used.");
+      for (let computed of classProp.computed) {
+        if (computed.name.indexOf("margin-left") == 0) {
+          ok(computed.overridden, "margin-left props should be overridden.");
+        } else {
+          ok(!computed.overridden, "Non-margin-left props should not be overridden.");
+        }
       }
-    }
+
+      styleNode.parentNode.removeChild(styleNode);
 
-    styleNode.parentNode.removeChild(styleNode);
-
-    importantOverride();
+      importantOverride();
+    });
   });
 }
 
 function importantOverride()
 {
   let style = '' +
     // Margin shorthand property...
     '.testclass {' +
     '  background-color: green !important;' +
     '}' +
     // ... will be partially overridden.
     '#testid {' +
     '  background-color: blue;' +
     '}';
   let styleNode = addStyle(doc, style);
   doc.body.innerHTML = '<div id="testid" class="testclass">Styled Node</div>';
+  inspector.once("markupmutation", () => {
+    inspector.selection.setNode(doc.getElementById("testid"));
+    inspector.once("inspector-updated", () => {
+      let elementStyle = view._elementStyle;
 
-  inspector.selection.setNode(doc.getElementById("testid"));
-  inspector.once("inspector-updated", () => {
-    let elementStyle = view._elementStyle;
-
-    let idRule = elementStyle.rules[1];
-    let idProp = idRule.textProps[0];
-    ok(idProp.overridden, "Not-important rule should be overridden.");
+      let idRule = elementStyle.rules[1];
+      let idProp = idRule.textProps[0];
+      ok(idProp.overridden, "Not-important rule should be overridden.");
 
-    let classRule = elementStyle.rules[2];
-    let classProp = classRule.textProps[0];
-    ok(!classProp.overridden, "Important rule should not be overridden.");
+      let classRule = elementStyle.rules[2];
+      let classProp = classRule.textProps[0];
+      ok(!classProp.overridden, "Important rule should not be overridden.");
 
-    styleNode.parentNode.removeChild(styleNode);
+      styleNode.parentNode.removeChild(styleNode);
 
-    let elementRule = elementStyle.rules[0];
-    let elementProp = elementRule.createProperty("background-color", "purple", "important");
-    promiseDone(elementRule._applyingModifications.then(() => {
-      ok(classProp.overridden, "New important prop should override class property.");
-      ok(!elementProp.overridden, "New important prop should not be overriden.");
+      let elementRule = elementStyle.rules[0];
+      let elementProp = elementRule.createProperty("background-color", "purple", "important");
+      promiseDone(elementRule._applyingModifications.then(() => {
+        ok(classProp.overridden, "New important prop should override class property.");
+        ok(!elementProp.overridden, "New important prop should not be overriden.");
 
-      disableOverride();
-    }));
+        disableOverride();
+      }));
+    });
   });
 }
 
 function disableOverride()
 {
   let style = '' +
     '#testid {' +
     '  background-color: blue;' +
     '}' +
     '.testclass {' +
     '  background-color: green;' +
     '}';
   let styleNode = addStyle(doc, style);
   doc.body.innerHTML = '<div id="testid" class="testclass">Styled Node</div>';
-
-  inspector.selection.setNode(doc.getElementById("testid"));
-  inspector.once("inspector-updated", () => {
-    let elementStyle = view._elementStyle;
+  inspector.once("markupmutation", () => {
+    inspector.selection.setNode(doc.getElementById("testid"));
+    inspector.once("inspector-updated", () => {
+      let elementStyle = view._elementStyle;
 
-    let idRule = elementStyle.rules[1];
-    let idProp = idRule.textProps[0];
-    idProp.setEnabled(false);
-    promiseDone(idRule._applyingModifications.then(() => {
-      let classRule = elementStyle.rules[2];
-      let classProp = classRule.textProps[0];
-      ok(!classProp.overridden, "Class prop should not be overridden after id prop was disabled.");
+      let idRule = elementStyle.rules[1];
+      let idProp = idRule.textProps[0];
+      idProp.setEnabled(false);
+      promiseDone(idRule._applyingModifications.then(() => {
+        let classRule = elementStyle.rules[2];
+        let classProp = classRule.textProps[0];
+        ok(!classProp.overridden, "Class prop should not be overridden after id prop was disabled.");
 
-      styleNode.parentNode.removeChild(styleNode);
+        styleNode.parentNode.removeChild(styleNode);
 
-      finishTest();
-    }));
+        finishTest();
+      }));
+    });
   });
 }
 
 function finishTest()
 {
   doc = inspector = view = null;
   gBrowser.removeCurrentTab();
   finish();
--- a/browser/devtools/tilt/tilt-visualizer.js
+++ b/browser/devtools/tilt/tilt-visualizer.js
@@ -267,20 +267,21 @@ TiltVisualizer.prototype = {
    */
   onNewNodeFromTilt: function TV_onNewNodeFromTilt()
   {
     if (!this.inspector) {
       return;
     }
     let nodeIndex = this.presenter._currentSelection;
     if (nodeIndex < 0) {
-      this.inspector.selection.setNode(null, "tilt");
+      this.inspector.selection.setNodeFront(null, "tilt");
     }
     let node = this.presenter._traverseData.nodes[nodeIndex];
-    this.inspector.selection.setNode(node, "tilt");
+    node = this.inspector.walker.frontForRawNode(node);
+    this.inspector.selection.setNodeFront(node, "tilt");
   },
 };
 
 /**
  * This object manages the visualization logic and drawing loop.
  *
  * @param {HTMLCanvasElement} aCanvas
  *                            the canvas element used for rendering
--- a/browser/devtools/webconsole/test/browser_webconsole_bug_653531_highlighter_console_helper.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_653531_highlighter_console_helper.js
@@ -1,20 +1,22 @@
 /* vim: set ft=javascript 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/. */
 
 // Tests that the $0 console helper works as intended.
 
+let inspector, h1;
+
 function createDocument()
 {
   let doc = content.document;
   let div = doc.createElement("div");
-  let h1 = doc.createElement("h1");
+  h1 = doc.createElement("h1");
   let p1 = doc.createElement("p");
   let p2 = doc.createElement("p");
   let div2 = doc.createElement("div");
   let p3 = doc.createElement("p");
   doc.title = "Inspector Tree Selection Test";
   h1.textContent = "Inspector Tree Selection Test";
   p1.textContent = "This is some example text";
   p2.textContent = "Lorem ipsum dolor sit amet, consectetur adipisicing " +
@@ -37,49 +39,47 @@ function createDocument()
   div2.appendChild(p3);
   doc.body.appendChild(div);
   doc.body.appendChild(div2);
   setupHighlighterTests();
 }
 
 function setupHighlighterTests()
 {
-  let h1 = content.document.querySelector("h1");
   ok(h1, "we have the header node");
-
   openInspector(runSelectionTests);
 }
 
 function runSelectionTests(aInspector)
 {
-  aInspector.highlighter.unlockAndFocus();
-  aInspector.highlighter.outline.setAttribute("disable-transitions", "true");
+  inspector = aInspector;
+  inspector.toolbox.startPicker();
+  inspector.toolbox.once("picker-started", () => {
+    EventUtils.synthesizeMouse(h1, 2, 2, {type: "mousemove"}, content);
+    inspector.toolbox.once("picker-node-hovered", () => {
+      executeSoon(performTestComparisons);
+    });
+  });
+}
 
-  executeSoon(function() {
-    aInspector.selection.once("new-node", performTestComparisons);
-    let h1 = content.document.querySelector("h1");
-    EventUtils.synthesizeMouse(h1, 2, 2, {type: "mousemove"}, content);
-  });
+function getHighlighterOutline()
+{
+  return gBrowser.selectedBrowser.parentNode
+    .querySelector(".highlighter-container .highlighter-outline");
 }
 
 function performTestComparisons()
 {
-  let target = TargetFactory.forTab(gBrowser.selectedTab);
-  let inspector = gDevTools.getToolbox(target).getPanel("inspector");
-  inspector.highlighter.lock();
-
-  let isHighlighting =
-    !(inspector.highlighter.outline.getAttribute("hidden") == "true");
+  let outline = getHighlighterOutline();
+  ok(outline && !outline.hasAttribute("hidden"), "inspector is highlighting");
 
-  ok(isHighlighting, "inspector is highlighting");
-
-  let h1 = content.document.querySelector("h1");
-  is(inspector.selection.node, h1, "selection matches node");
-
-  openConsole(gBrowser.selectedTab, performWebConsoleTests);
+  EventUtils.synthesizeMouseAtCenter(h1, {}, content);
+  inspector.toolbox.once("picker-stopped", () => {
+    openConsole(gBrowser.selectedTab, performWebConsoleTests);
+  });
 }
 
 function performWebConsoleTests(hud)
 {
   let target = TargetFactory.forTab(gBrowser.selectedTab);
   let jsterm = hud.jsterm;
   outputNode = hud.outputNode;
 
@@ -93,29 +93,29 @@ function performWebConsoleTests(hud)
     jsterm.clearOutput();
     jsterm.execute("$0.textContent = 'bug653531'", onNodeUpdate);
   }
 
   function onNodeUpdate(node)
   {
     isnot(node.textContent.indexOf("bug653531"), -1,
           "correct output for $0.textContent");
-    let inspector = gDevTools.getToolbox(target).getPanel("inspector");
     is(inspector.selection.node.textContent, "bug653531",
        "node successfully updated");
 
+    inspector = h1 = null;
     gBrowser.removeCurrentTab();
     finishTest();
   }
 }
 
 function test()
 {
   waitForExplicitFinish();
+
   gBrowser.selectedTab = gBrowser.addTab();
   gBrowser.selectedBrowser.addEventListener("load", function onLoad() {
     gBrowser.selectedBrowser.removeEventListener("load", onLoad, true);
     waitForFocus(createDocument, content);
   }, true);
 
   content.location = "data:text/html;charset=utf-8,test for highlighter helper in web console";
 }
-
--- a/browser/extensions/shumway/content/ShumwayStreamConverter.jsm
+++ b/browser/extensions/shumway/content/ShumwayStreamConverter.jsm
@@ -183,16 +183,26 @@ function isShumwayEnabledFor(actions) {
     /sndcdn\.com\/assets\/swf/.test(url) /* soundcloud */ ||
     /vimeocdn\.com/.test(url) /* vimeo */) {
     return false;
   }
 
   return true;
 }
 
+function fallbackToNativePlugin(window, userAction, activateCTP) {
+  var obj = window.frameElement;
+  var doc = obj.ownerDocument;
+  var e = doc.createEvent("CustomEvent");
+  e.initCustomEvent("MozPlayPlugin", true, true, activateCTP);
+  obj.dispatchEvent(e);
+
+  ShumwayTelemetry.onFallback(userAction);
+}
+
 // All the priviledged actions.
 function ChromeActions(url, window, document) {
   this.url = url;
   this.objectParams = null;
   this.movieParams = null;
   this.baseUrl = url;
   this.isOverlay = false;
   this.isPausedAtStart = false;
@@ -351,23 +361,18 @@ ChromeActions.prototype = {
       } else {
         log("data access id prohibited to " + url + " from " + baseUrl);
         win.postMessage({callback:"loadFile", sessionId: sessionId, topic: "error",
           error: "only original swf file or file from the same origin loading supported"}, "*");
       }
     });
   },
   fallback: function(automatic) {
-    var obj = this.window.frameElement;
-    var doc = obj.ownerDocument;
-    var e = doc.createEvent("CustomEvent");
-    e.initCustomEvent("MozPlayPlugin", true, true, null);
-    obj.dispatchEvent(e);
-
-    ShumwayTelemetry.onFallback(!automatic);
+    automatic = !!automatic; // cast to boolean
+    fallbackToNativePlugin(this.window, !automatic, automatic);
   },
   setClipboard: function (data) {
     if (typeof data !== 'string' ||
         data.length > MAX_CLIPBOARD_DATA_SIZE ||
         !this.document.hasFocus()) {
       return;
     }
     // TODO other security checks?
@@ -897,17 +902,17 @@ ShumwayStreamConverterBase.prototype = {
         aRequest.cancel(Cr.NS_BINDING_ABORTED);
 
         var domWindow = getDOMWindow(channel);
         let actions = converter.createChromeActions(domWindow,
                                                     domWindow.document,
                                                     converter.getUrlHint(originalURI));
 
         if (!isShumwayEnabledFor(actions)) {
-          actions.fallback(true);
+          fallbackToNativePlugin(domWindow, false, true);
           return;
         }
 
         // Report telemetry on amount of swfs on the page
         if (actions.isOverlay) {
           // Looking for last actions with same baseUrl
           var prevPageActions = ActivationQueue.findLastOnPage(actions.baseUrl);
           var pageIndex = !prevPageActions ? 1 : (prevPageActions.telemetry.pageIndex + 1);
index 53a0322aad8d22d0384443adc5a89a881df5a345..86e367fc1698218dcfe42cc2693dcb3a023b7dd9
GIT binary patch
literal 81529
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
AWdHyG
--- a/browser/extensions/shumway/content/shumway-worker.js
+++ b/browser/extensions/shumway/content/shumway-worker.js
@@ -182,42 +182,22 @@ function scriptProperties(namespace, pro
   }, {});
 }
 function cloneObject(obj) {
   var clone = Object.create(null);
   for (var prop in obj)
     clone[prop] = obj[prop];
   return clone;
 }
-function sortByDepth(a, b) {
-  var levelA = a._level;
-  var levelB = b._level;
-  if (a._parent !== b._parent && a._index > -1 && b._index > -1) {
-    while (a._level > levelB) {
-      a = a._parent;
-    }
-    while (b._level > levelA) {
-      b = b._parent;
-    }
-    while (a._level > 1) {
-      if (a._parent === b._parent) {
-        break;
-      }
-      a = a._parent;
-      b = b._parent;
-    }
-  }
-  if (a === b) {
-    return levelA - levelB;
-  }
-  return a._index - b._index;
-}
 function sortNumeric(a, b) {
   return a - b;
 }
+function sortByZindex(a, b) {
+  return a._zindex - b._zindex;
+}
 function rgbaObjToStr(color) {
   return 'rgba(' + color.red + ',' + color.green + ',' + color.blue + ',' + color.alpha / 255 + ')';
 }
 function rgbIntAlphaToStr(color, alpha) {
   color |= 0;
   if (alpha >= 1) {
     var colorStr = color.toString(16);
     while (colorStr.length < 6) {
@@ -291,168 +271,346 @@ function randomStyle() {
       '#ff1300',
       '#1f1f21',
       '#bdbec2',
       '#ff3a2d'
     ];
   }
   return randomStyleCache[nextStyle++ % randomStyleCache.length];
 }
-var Promise = function PromiseClosure() {
-    function isPromise(obj) {
-      return typeof obj === 'object' && obj !== null && typeof obj.then === 'function';
-    }
-    function defaultOnFulfilled(value) {
-      return value;
-    }
-    function defaultOnRejected(reason) {
-      throw reason;
-    }
-    function propagateFulfilled(subject, value) {
-      subject.subpromisesValue = value;
-      var subpromises = subject.subpromises;
-      if (!subpromises) {
-        return;
-      }
-      for (var i = 0; i < subpromises.length; i++) {
-        subpromises[i].fulfill(value);
-      }
-      delete subject.subpromises;
-    }
-    function propagateRejected(subject, reason) {
-      subject.subpromisesReason = reason;
-      var subpromises = subject.subpromises;
-      if (!subpromises) {
-        if (!true) {
-          console.warn(reason);
-        }
-        return;
-      }
-      for (var i = 0; i < subpromises.length; i++) {
-        subpromises[i].reject(reason);
-      }
-      delete subject.subpromises;
-    }
-    function performCall(callback, arg, subject) {
-      try {
-        var value = callback(arg);
-        if (isPromise(value)) {
-          value.then(function Promise_queueCall_onFulfilled(value) {
-            propagateFulfilled(subject, value);
-          }, function Promise_queueCall_onRejected(reason) {
-            propagateRejected(subject, reason);
+(function PromiseClosure() {
+  var global = Function('return this')();
+  if (global.Promise) {
+    if (typeof global.Promise.all !== 'function') {
+      global.Promise.all = function (iterable) {
+        var count = 0, results = [], resolve, reject;
+        var promise = new global.Promise(function (resolve_, reject_) {
+            resolve = resolve_;
+            reject = reject_;
           });
-          return;
-        }
-        propagateFulfilled(subject, value);
-      } catch (ex) {
-        propagateRejected(subject, ex);
-      }
-    }
-    var queue = [];
-    function processQueue() {
-      while (queue.length > 0) {
-        var task = queue[0];
-        if (task.directCallback) {
-          task.callback.call(task.subject, task.arg);
-        } else {
-          performCall(task.callback, task.arg, task.subject);
-        }
-        queue.shift();
-      }
-    }
-    function queueCall(callback, arg, subject, directCallback) {
-      if (queue.length === 0) {
-        setTimeout(processQueue, 0);
-      }
-      queue.push({
-        callback: callback,
-        arg: arg,
-        subject: subject,
-        directCallback: directCallback
-      });
-    }
-    function Promise(onFulfilled, onRejected) {
-      this.state = 'pending';
-      this.onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : defaultOnFulfilled;
-      this.onRejected = typeof onRejected === 'function' ? onRejected : defaultOnRejected;
-    }
-    Promise.prototype = {
-      fulfill: function Promise_resolve(value) {
-        if (this.state !== 'pending') {
-          return;
-        }
-        this.state = 'fulfilled';
-        this.value = value;
-        queueCall(this.onFulfilled, value, this, false);
-      },
-      reject: function Promise_reject(reason) {
-        if (this.state !== 'pending') {
-          return;
-        }
-        this.state = 'rejected';
-        this.reason = reason;
-        queueCall(this.onRejected, reason, this, false);
-      },
-      then: function Promise_then(onFulfilled, onRejected) {
-        var promise = new Promise(onFulfilled, onRejected);
-        if ('subpromisesValue' in this) {
-          queueCall(promise.fulfill, this.subpromisesValue, promise, true);
-        } else if ('subpromisesReason' in this) {
-          queueCall(promise.reject, this.subpromisesReason, promise, true);
-        } else {
-          var subpromises = this.subpromises || (this.subpromises = []);
-          subpromises.push(promise);
+        iterable.forEach(function (p, i) {
+          count++;
+          p.then(function (result) {
+            results[i] = result;
+            count--;
+            if (count === 0) {
+              resolve(results);
+            }
+          }, reject);
+        });
+        if (count === 0) {
+          resolve(results);
         }
         return promise;
-      },
-      get resolved() {
-        return this.state === 'fulfilled';
-      },
-      resolve: function (value) {
-        this.fulfill(value);
+      };
+    }
+    if (typeof global.Promise.resolve !== 'function') {
+      global.Promise.resolve = function (x) {
+        return new global.Promise(function (resolve) {
+          resolve(x);
+        });
+      };
+    }
+    return;
+  }
+  function getDeferred(C) {
+    if (typeof C !== 'function') {
+      throw new TypeError('Invalid deferred constructor');
+    }
+    var resolver = createDeferredConstructionFunctions();
+    var promise = new C(resolver);
+    var resolve = resolver.resolve;
+    if (typeof resolve !== 'function') {
+      throw new TypeError('Invalid resolve construction function');
+    }
+    var reject = resolver.reject;
+    if (typeof reject !== 'function') {
+      throw new TypeError('Invalid reject construction function');
+    }
+    return {
+      promise: promise,
+      resolve: resolve,
+      reject: reject
+    };
+  }
+  function updateDeferredFromPotentialThenable(x, deferred) {
+    if (typeof x !== 'object' || x === null) {
+      return false;
+    }
+    try {
+      var then = x.then;
+      if (typeof then !== 'function') {
+        return false;
+      }
+      var thenCallResult = then.call(x, deferred.resolve, deferred.reject);
+    } catch (e) {
+      var reject = deferred.reject;
+      reject(e);
+    }
+    return true;
+  }
+  function isPromise(x) {
+    return typeof x === 'object' && x !== null && typeof x.promiseStatus !== 'undefined';
+  }
+  function rejectPromise(promise, reason) {
+    if (promise.promiseStatus !== 'unresolved') {
+      return;
+    }
+    var reactions = promise.rejectReactions;
+    promise.result = reason;
+    promise.resolveReactions = undefined;
+    promise.rejectReactions = undefined;
+    promise.promiseStatus = 'has-rejection';
+    triggerPromiseReactions(reactions, reason);
+  }
+  function resolvePromise(promise, resolution) {
+    if (promise.promiseStatus !== 'unresolved') {
+      return;
+    }
+    var reactions = promise.resolveReactions;
+    promise.result = resolution;
+    promise.resolveReactions = undefined;
+    promise.rejectReactions = undefined;
+    promise.promiseStatus = 'has-resolution';
+    triggerPromiseReactions(reactions, resolution);
+  }
+  function triggerPromiseReactions(reactions, argument) {
+    for (var i = 0; i < reactions.length; i++) {
+      queueMicrotask({
+        reaction: reactions[i],
+        argument: argument
+      });
+    }
+  }
+  function queueMicrotask(task) {
+    if (microtasksQueue.length === 0) {
+      setTimeout(handleMicrotasksQueue, 0);
+    }
+    microtasksQueue.push(task);
+  }
+  function executePromiseReaction(reaction, argument) {
+    var deferred = reaction.deferred;
+    var handler = reaction.handler;
+    var handlerResult, updateResult;
+    try {
+      handlerResult = handler(argument);
+    } catch (e) {
+      var reject = deferred.reject;
+      return reject(e);
+    }
+    if (handlerResult === deferred.promise) {
+      var reject = deferred.reject;
+      return reject(new TypeError('Self resolution'));
+    }
+    try {
+      updateResult = updateDeferredFromPotentialThenable(handlerResult, deferred);
+      if (!updateResult) {
+        var resolve = deferred.resolve;
+        return resolve(handlerResult);
+      }
+    } catch (e) {
+      var reject = deferred.reject;
+      return reject(e);
+    }
+  }
+  var microtasksQueue = [];
+  function handleMicrotasksQueue() {
+    while (microtasksQueue.length > 0) {
+      var task = microtasksQueue[0];
+      try {
+        executePromiseReaction(task.reaction, task.argument);
+      } catch (e) {
+        if (typeof Promise.onerror === 'function') {
+          Promise.onerror(e);
+        }
+      }
+      microtasksQueue.shift();
+    }
+  }
+  function throwerFunction(e) {
+    throw e;
+  }
+  function identityFunction(x) {
+    return x;
+  }
+  function createRejectPromiseFunction(promise) {
+    return function (reason) {
+      rejectPromise(promise, reason);
+    };
+  }
+  function createResolvePromiseFunction(promise) {
+    return function (resolution) {
+      resolvePromise(promise, resolution);
+    };
+  }
+  function createDeferredConstructionFunctions() {
+    var fn = function (resolve, reject) {
+      fn.resolve = resolve;
+      fn.reject = reject;
+    };
+    return fn;
+  }
+  function createPromiseResolutionHandlerFunctions(promise, fulfillmentHandler, rejectionHandler) {
+    return function (x) {
+      if (x === promise) {
+        return rejectionHandler(new TypeError('Self resolution'));
+      }
+      var cstr = promise.promiseConstructor;
+      if (isPromise(x)) {
+        var xConstructor = x.promiseConstructor;
+        if (xConstructor === cstr) {
+          return x.then(fulfillmentHandler, rejectionHandler);
+        }
+      }
+      var deferred = getDeferred(cstr);
+      var updateResult = updateDeferredFromPotentialThenable(x, deferred);
+      if (updateResult) {
+        var deferredPromise = deferred.promise;
+        return deferredPromise.then(fulfillmentHandler, rejectionHandler);
+      }
+      return fulfillmentHandler(x);
+    };
+  }
+  function createPromiseAllCountdownFunction(index, values, deferred, countdownHolder) {
+    return function (x) {
+      values[index] = x;
+      countdownHolder.countdown--;
+      if (countdownHolder.countdown === 0) {
+        deferred.resolve(values);
       }
     };
-    Promise.when = function Promise_when() {
-      var promise = new Promise();
-      if (arguments.length === 0) {
-        promise.resolve();
-        return promise;
-      }
-      var promises = slice.call(arguments, 0);
-      var result = [];
-      var i = 1;
-      function fulfill(value) {
-        result.push(value);
-        if (i < promises.length) {
-          promises[i++].then(fulfill, reject);
-        } else {
-          promise.resolve(result);
-        }
-        return value;
-      }
-      function reject(reason) {
-        promise.reject(reason);
-      }
-      promises[0].then(fulfill, reject);
-      return promise;
-    };
-    return Promise;
-  }();
-var QuadTree = function (x, y, width, height, level) {
+  }
+  function Promise(resolver) {
+    if (typeof resolver !== 'function') {
+      throw new TypeError('resolver is not a function');
+    }
+    var promise = this;
+    if (typeof promise !== 'object') {
+      throw new TypeError('Promise to initialize is not an object');
+    }
+    promise.promiseStatus = 'unresolved';
+    promise.resolveReactions = [];
+    promise.rejectReactions = [];
+    promise.result = undefined;
+    var resolve = createResolvePromiseFunction(promise);
+    var reject = createRejectPromiseFunction(promise);
+    try {
+      var result = resolver(resolve, reject);
+    } catch (e) {
+      rejectPromise(promise, e);
+    }
+    promise.promiseConstructor = Promise;
+    return promise;
+  }
+  Promise.all = function (iterable) {
+    var deferred = getDeferred(this);
+    var values = [];
+    var countdownHolder = {
+        countdown: 0
+      };
+    var index = 0;
+    iterable.forEach(function (nextValue) {
+      var nextPromise = this.cast(nextValue);
+      var fn = createPromiseAllCountdownFunction(index, values, deferred, countdownHolder);
+      nextPromise.then(fn, deferred.reject);
+      index++;
+      countdownHolder.countdown++;
+    }, this);
+    if (index === 0) {
+      deferred.resolve(values);
+    }
+    return deferred.promise;
+  };
+  Promise.cast = function (x) {
+    if (isPromise(x)) {
+      return x;
+    }
+    var deferred = getDeferred(this);
+    deferred.resolve(x);
+    return deferred.promise;
+  };
+  Promise.reject = function (r) {
+    var deferred = getDeferred(this);
+    var rejectResult = deferred.reject(r);
+    return deferred.promise;
+  };
+  Promise.resolve = function (x) {
+    var deferred = getDeferred(this);
+    var rejectResult = deferred.resolve(x);
+    return deferred.promise;
+  };
+  Promise.prototype = {
+    'catch': function (onRejected) {
+      this.then(undefined, onRejected);
+    },
+    then: function (onFulfilled, onRejected) {
+      var promise = this;
+      if (!isPromise(promise)) {
+        throw new TypeError('this is not a Promises');
+      }
+      var cstr = promise.promiseConstructor;
+      var deferred = getDeferred(cstr);
+      var rejectionHandler = typeof onRejected === 'function' ? onRejected : throwerFunction;
+      var fulfillmentHandler = typeof onFulfilled === 'function' ? onFulfilled : identityFunction;
+      var resolutionHandler = createPromiseResolutionHandlerFunctions(promise, fulfillmentHandler, rejectionHandler);
+      var resolveReaction = {
+          deferred: deferred,
+          handler: resolutionHandler
+        };
+      var rejectReaction = {
+          deferred: deferred,
+          handler: rejectionHandler
+        };
+      switch (promise.promiseStatus) {
+      case 'unresolved':
+        promise.resolveReactions.push(resolveReaction);
+        promise.rejectReactions.push(rejectReaction);
+        break;
+      case 'has-resolution':
+        var resolution = promise.result;
+        queueMicrotask({
+          reaction: resolveReaction,
+          argument: resolution
+        });
+        break;
+      case 'has-rejection':
+        var rejection = promise.result;
+        queueMicrotask({
+          reaction: rejectReaction,
+          argument: rejection
+        });
+        break;
+      }
+      return deferred.promise;
+    }
+  };
+  global.Promise = Promise;
+}());
+var QuadTree = function (x, y, width, height, parent) {
   this.x = x | 0;
   this.y = y | 0;
   this.width = width | 0;
   this.height = height | 0;
-  this.level = level | 0;
-  this.stuckObjects = [];
-  this.objects = [];
+  if (parent) {
+    this.root = parent.root;
+    this.parent = parent;
+    this.level = parent.level + 1;
+  } else {
+    this.root = this;
+    this.parent = null;
+    this.level = 0;
+  }
+  this.reset();
+};
+QuadTree.prototype.reset = function () {
+  this.stuckObjects = null;
+  this.objects = null;
   this.nodes = [];
 };
-QuadTree.prototype._findIndex = function (xMin, yMin, xMax, yMax) {
+QuadTree.prototype._findIndex = function (xMin, xMax, yMin, yMax) {
   var midX = this.x + (this.width / 2 | 0);
   var midY = this.y + (this.height / 2 | 0);
   var top = yMin < midY && yMax < midY;
   var bottom = yMin > midY;
   if (xMin < midX && xMax < midX) {
     if (top) {
       return 1;
     } else if (bottom) {
@@ -465,81 +623,218 @@ QuadTree.prototype._findIndex = function
       return 3;
     }
   }
   return -1;
 };
 QuadTree.prototype.insert = function (obj) {
   var nodes = this.nodes;
   if (nodes.length) {
-    var index = this._findIndex(obj.xMin, obj.yMin, obj.xMax, obj.yMax);
+    var index = this._findIndex(obj.xMin, obj.xMax, obj.yMin, obj.yMax);
     if (index > -1) {
       nodes[index].insert(obj);
     } else {
-      this.stuckObjects.push(obj);
-      obj._qtree = this;
-    }
-    return;
-  }
-  var objects = this.objects;
-  objects.push(obj);
-  if (objects.length > 4 && this.level < 10) {
-    this._subdivide();
-    while (objects.length) {
-      this.insert(objects.shift());
+      obj.prev = null;
+      if (this.stuckObjects) {
+        obj.next = this.stuckObjects;
+        this.stuckObjects.prev = obj;
+      } else {
+        obj.next = null;
+      }
+      this.stuckObjects = obj;
+      obj.parent = this;
     }
     return;
   }
-  obj._qtree = this;
-};
-QuadTree.prototype.delete = function (obj) {
-  if (obj._qtree !== this) {
+  var numChildren = 1;
+  var item = this.objects;
+  if (!item) {
+    obj.prev = null;
+    obj.next = null;
+    this.objects = obj;
+  } else {
+    while (item.next) {
+      numChildren++;
+      item = item.next;
+    }
+    obj.prev = item;
+    obj.next = null;
+    item.next = obj;
+  }
+  if (numChildren > 4 && this.level < 10) {
+    this._subdivide();
+    item = this.objects;
+    while (item) {
+      var next = item.next;
+      this.insert(item);
+      item = next;
+    }
+    this.objects = null;
     return;
   }
-  var index = this.objects.indexOf(obj);
-  if (index > -1) {
-    this.objects.splice(index, 1);
-  } else {
-    index = this.stuckObjects.indexOf(obj);
-    this.stuckObjects.splice(index, 1);
+  obj.parent = this;
+};
+QuadTree.prototype.update = function (obj) {
+  var node = obj.parent;
+  if (node) {
+    if (obj.xMin >= node.x && obj.xMax <= node.x + node.width && obj.yMin >= node.y && obj.yMax <= node.y + node.height) {
+      if (node.nodes.length) {
+        var index = this._findIndex(obj.xMin, obj.xMax, obj.yMin, obj.yMax);
+        if (index > -1) {
+          node.remove(obj);
+          node = this.nodes[index];
+          node.insert(obj);
+        }
+      } else {
+        node.remove(obj);
+        node.insert(obj);
+      }
+      return;
+    }
+    node.remove(obj);
   }
-  obj._qtree = null;
+  this.root.insert(obj);
 };
-QuadTree.prototype._stack = [];
-QuadTree.prototype._out = [];
-QuadTree.prototype.retrieve = function (xMin, yMin, xMax, yMax) {
-  var stack = this._stack;
-  var out = this._out;
-  out.length = 0;
+QuadTree.prototype.remove = function (obj) {
+  var prev = obj.prev;
+  var next = obj.next;
+  if (prev) {
+    prev.next = next;
+    obj.prev = null;
+  } else {
+    var node = obj.parent;
+    if (node.objects === obj) {
+      node.objects = next;
+    } else if (node.stuckObjects === obj) {
+      node.stuckObjects = next;
+    }
+  }
+  if (next) {
+    next.prev = prev;
+    obj.next = null;
+  }
+  obj.parent = null;
+};
+QuadTree.prototype.retrieve = function (xMin, xMax, yMin, yMax) {
+  var stack = [];
+  var out = [];
   var node = this;
   do {
     if (node.nodes.length) {
-      var index = node._findIndex(xMin, yMin, xMax, yMax);
+      var index = node._findIndex(xMin, xMax, yMin, yMax);
       if (index > -1) {
         stack.push(node.nodes[index]);
       } else {
         stack.push.apply(stack, node.nodes);
       }
     }
-    out.push.apply(out, node.stuckObjects);
-    out.push.apply(out, node.objects);
+    var item = node.objects;
+    for (var i = 0; i < 2; i++) {
+      while (item) {
+        if (!(item.xMin > xMax || item.xMax < xMin || item.yMin > yMax || item.yMax < yMin)) {
+          out.push(item);
+        }
+        item = item.next;
+      }
+      item = node.stuckObjects;
+    }
     node = stack.pop();
   } while (node);
   return out;
 };
 QuadTree.prototype._subdivide = function () {
   var halfWidth = this.width / 2 | 0;
   var halfHeight = this.height / 2 | 0;
   var midX = this.x + halfWidth;
   var midY = this.y + halfHeight;
-  var level = this.level + 1;
-  this.nodes[0] = new QuadTree(midX, this.y, halfWidth, halfHeight, level);
-  this.nodes[1] = new QuadTree(this.x, this.y, halfWidth, halfHeight, level);
-  this.nodes[2] = new QuadTree(this.x, midY, halfWidth, halfHeight, level);
-  this.nodes[3] = new QuadTree(midX, midY, halfWidth, halfHeight, level);
+  this.nodes[0] = new QuadTree(midX, this.y, halfWidth, halfHeight, this);
+  this.nodes[1] = new QuadTree(this.x, this.y, halfWidth, halfHeight, this);
+  this.nodes[2] = new QuadTree(this.x, midY, halfWidth, halfHeight, this);
+  this.nodes[3] = new QuadTree(midX, midY, halfWidth, halfHeight, this);
+};
+var RegionCluster = function () {
+  this.regions = [];
+};
+RegionCluster.prototype.reset = function () {
+  this.regions.length = 0;
+};
+RegionCluster.prototype.insert = function (region) {
+  var regions = this.regions;
+  if (regions.length < 3) {
+    regions.push({
+      xMin: region.xMin,
+      xMax: region.xMax,
+      yMin: region.yMin,
+      yMax: region.yMax
+    });
+    return;
+  }
+  var a = region;
+  var b = regions[0];
+  var c = regions[1];
+  var d = regions[2];
+  var ab = (max(a.xMax, b.xMax) - min(a.xMin, b.xMin)) * (max(a.yMax, b.yMax) - min(a.yMin, b.yMin));
+  var rb = regions[0];
+  var ac = (max(a.xMax, c.xMax) - min(a.xMin, c.xMin)) * (max(a.yMax, c.yMax) - min(a.yMin, c.yMin));
+  var ad = (max(a.xMax, d.xMax) - min(a.xMin, d.xMin)) * (max(a.yMax, d.yMax) - min(a.yMin, d.yMin));
+  if (ac < ab) {
+    ab = ac;
+    rb = c;
+  }
+  if (ad < ab) {
+    ab = ad;
+    rb = d;
+  }
+  var bc = (max(b.xMax, c.xMax) - min(b.xMin, c.xMin)) * (max(b.yMax, c.yMax) - min(b.yMin, c.yMin));
+  var bd = (max(b.xMax, d.xMax) - min(b.xMin, d.xMin)) * (max(b.yMax, d.yMax) - min(b.yMin, d.yMin));
+  var cd = (max(c.xMax, d.xMax) - min(c.xMin, d.xMin)) * (max(c.yMax, d.yMax) - min(c.yMin, d.yMin));
+  if (ab < bc && ab < bd && ab < cd) {
+    if (a.xMin < rb.xMin) {
+      rb.xMin = a.xMin;
+    }
+    if (a.xMax > rb.xMax) {
+      rb.xMax = a.xMax;
+    }
+    if (a.yMin < rb.yMin) {
+      rb.yMin = a.yMin;
+    }
+    if (a.yMax > rb.yMax) {
+      rb.yMax = a.yMax;
+    }
+    return;
+  }
+  rb = regions[0];
+  var rc = regions[1];
+  if (bd < bc) {
+    bc = bd;
+    rc = regions[2];
+  }
+  if (cd < bc) {
+    rb = regions[1];
+    rc = regions[2];
+  }
+  if (rc.xMin < rb.xMin) {
+    rb.xMin = rc.xMin;
+  }
+  if (rc.xMax > rb.xMax) {
+    rb.xMax = rc.xMax;
+  }
+  if (rc.yMin < rb.yMin) {
+    rb.yMin = rc.yMin;
+  }
+  if (rc.yMax > rb.yMax) {
+    rb.yMax = rc.yMax;
+  }
+  rc.xMin = a.xMin;
+  rc.xMax = a.xMax;
+  rc.yMin = a.yMin;
+  rc.yMax = a.yMax;
+};
+RegionCluster.prototype.retrieve = function () {
+  return this.regions;
 };
 var EXTERNAL_INTERFACE_FEATURE = 1;
 var CLIPBOARD_FEATURE = 2;
 var SHAREDOBJECT_FEATURE = 3;
 var VIDEO_FEATURE = 4;
 var SOUND_FEATURE = 5;
 var NETCONNECTION_FEATURE = 6;
 if (!this.performance) {
@@ -1847,16 +2142,19 @@ function ShapePath(fillStyle, lineStyle,
       this.buffers.push(this.morphData.buffer);
       transferables.push(this.morphData.buffer);
     }
   } else {
     this.buffers = null;
   }
 }
 ShapePath.prototype = {
+  get isEmpty() {
+    return this.commands.length === 0;
+  },
   moveTo: function (x, y) {
     if (this.commands[this.commands.length - 1] === SHAPE_MOVE_TO) {
       this.data[this.data.length - 2] = x;
       this.data[this.data.length - 1] = y;
       return;
     }
     this.commands.push(SHAPE_MOVE_TO);
     this.data.push(x, y);
@@ -2008,16 +2306,18 @@ ShapePath.prototype = {
         colorTransform.setAlpha(ctx);
         ctx.lineWidth = Math.max(lineStyle.width / 20, 1);
         ctx.lineCap = lineStyle.lineCap;
         ctx.lineJoin = lineStyle.lineJoin;
         ctx.miterLimit = lineStyle.miterLimit;
         ctx.stroke();
         ctx.restore();
       }
+    } else {
+      ctx.fill();
     }
     ctx.closePath();
   },
   isPointInPath: function (x, y) {
     if (!(this.fillStyle || this.lineStyle)) {
       return false;
     }
     var bounds = this.strokeBounds || this.bounds || this._calculateBounds();
@@ -2689,32 +2989,32 @@ function extendBoundsByY(bounds, y) {
     bounds.yMin = y;
   } else if (y > bounds.yMax) {
     bounds.yMax = y;
   }
 }
 function morph(start, end, ratio) {
   return start + (end - start) * ratio;
 }
-function finishShapePath(path, dictionary) {
+function finishShapePath(path, dictionaryResolved) {
   if (path.fullyInitialized) {
     return path;
   }
   if (!(path instanceof ShapePath)) {
     var untypedPath = path;
     path = new ShapePath(path.fillStyle, path.lineStyle, 0, 0, path.isMorph);
     path.commands = new Uint8Array(untypedPath.buffers[0]);
     path.data = new Int32Array(untypedPath.buffers[1]);
     if (untypedPath.isMorph) {
       path.morphData = new Int32Array(untypedPath.buffers[2]);
     }
     path.buffers = null;
   }
-  path.fillStyle && initStyle(path.fillStyle, dictionary);
-  path.lineStyle && initStyle(path.lineStyle, dictionary);
+  path.fillStyle && initStyle(path.fillStyle, dictionaryResolved);
+  path.lineStyle && initStyle(path.lineStyle, dictionaryResolved);
   path.fullyInitialized = true;
   return path;
 }
 var inWorker = typeof window === 'undefined';
 var factoryCtx = !inWorker ? document.createElement('canvas').getContext('2d') : null;
 function buildLinearGradientFactory(colorStops) {
   var defaultGradient = factoryCtx.createLinearGradient(-1, 0, 1, 0);
   for (var i = 0; i < colorStops.length; i++) {
@@ -2764,17 +3064,17 @@ function buildBitmapPatternFactory(img, 
     ctx.drawImage(img, 0, 0);
     cachedTransform = ctx.createPattern(canvas, repeat);
     cachedTransformKey = key;
     return cachedTransform;
   };
   fn.defaultFillStyle = defaultPattern;
   return fn;
 }
-function initStyle(style, dictionary) {
+function initStyle(style, dictionaryResolved) {
   if (style.type === undefined) {
     return;
   }
   switch (style.type) {
   case GRAPHICS_FILL_SOLID:
     break;
   case GRAPHICS_FILL_LINEAR_GRADIENT:
   case GRAPHICS_FILL_RADIAL_GRADIENT:
@@ -2796,19 +3096,19 @@ function initStyle(style, dictionary) {
       gradientConstructor = buildRadialGradientFactory((style.focalPoint | 0) / 20, colorStops);
     }
     style.style = gradientConstructor;
     break;
   case GRAPHICS_FILL_REPEATING_BITMAP:
   case GRAPHICS_FILL_CLIPPED_BITMAP:
   case GRAPHICS_FILL_NONSMOOTHED_REPEATING_BITMAP:
   case GRAPHICS_FILL_NONSMOOTHED_CLIPPED_BITMAP:
-    var bitmap = dictionary[style.bitmapId];
+    var bitmap = dictionaryResolved[style.bitmapId];
     var repeat = style.type === GRAPHICS_FILL_REPEATING_BITMAP || style.type === GRAPHICS_FILL_NONSMOOTHED_REPEATING_BITMAP;
-    style.style = buildBitmapPatternFactory(bitmap.value.props.img, repeat ? 'repeat' : 'no-repeat');
+    style.style = buildBitmapPatternFactory(bitmap.props.img, repeat ? 'repeat' : 'no-repeat');
     break;
   default:
     fail('invalid fill style', 'shape');
   }
 }
 var SOUND_SIZE_8_BIT = 0;
 var SOUND_SIZE_16_BIT = 1;
 var SOUND_TYPE_MONO = 0;
@@ -5741,17 +6041,17 @@ BodyParser.prototype = {
       finalBlock = progressInfo.bytesLoaded >= progressInfo.bytesTotal;
     }
     var readStartTime = performance.now();
     readTags(swf, stream, swfVersion, finalBlock, options.onprogress);
     swf.parseTime += performance.now() - readStartTime;
     var read = stream.pos;
     buffer.removeHead(read);
     this.totalRead += read;
-    if (this.totalRead >= this.length && options.oncomplete) {
+    if (options.oncomplete && swf.tags[swf.tags.length - 1].finalTag) {
       options.oncomplete(swf);
     }
   }
 };
 SWF.parseAsync = function swf_parseAsync(options) {
   var buffer = new HeadTailBuffer();
   var pipe = {
       push: function (data, progressInfo) {
@@ -5848,13 +6148,14 @@ SWF.parse = function (buffer, options) {
     options = {};
   var pipe = SWF.parseAsync(options);
   var bytes = new Uint8Array(buffer);
   var progressInfo = {
       bytesLoaded: bytes.length,
       bytesTotal: bytes.length
     };
   pipe.push(bytes, progressInfo);
+  pipe.close();
 };
 (function (global) {
   global['SWF']['parse'] = SWF.parse;
   global['SWF']['parseAsync'] = SWF.parseAsync;
 }(this));
--- a/browser/extensions/shumway/content/shumway.js
+++ b/browser/extensions/shumway/content/shumway.js
@@ -151,16 +151,463 @@
       for (var i = 0; i < size; i++)
         view._bytes[offset + i] = temp[size - 1 - i];
     }
   }
   function fail(msg) {
     throw new Error(msg);
   }
 }(this));
+;
+var ByteArray = ByteArray || function (undefined) {
+    ByteArrayClass.INITIAL_SIZE = 128;
+    ByteArrayClass.DEFAULT_OBJECT_ENCODING = 3;
+    function ByteArrayClass(bytes) {
+      if (bytes instanceof ByteArray) {
+        return bytes;
+      }
+      var initData = bytes || this.symbol && this.symbol.data;
+      if (initData) {
+        this.a = new ArrayBuffer(initData.length);
+        this.length = initData.length;
+        new Uint8Array(this.a).set(initData);
+      } else {
+        this.a = new ArrayBuffer(ByteArrayClass.INITIAL_SIZE);
+        this.length = 0;
+      }
+      this.position = 0;
+      this.cacheViews();
+      this.nativele = new Int8Array(new Int32Array([]).buffer)[0] === 1;
+      this.le = this.nativele;
+      this.objectEncoding = ByteArrayClass.DEFAULT_OBJECT_ENCODING;
+      this.bitBuffer = 0;
+      this.bitLength = 0;
+    }
+    ;
+    function throwEOFError() {
+      runtime.throwErrorFromVM('flash.errors.EOFError', 'End of file was encountered.');
+    }
+    function throwRangeError() {
+      var error = Errors.ParamRangeError;
+      runtime.throwErrorFromVM('RangeError', getErrorMessage(error.code), error.code);
+    }
+    function throwCompressedDataError() {
+      var error = Errors.CompressedDataError;
+      runtime.throwErrorFromVM('CompressedDataError', getErrorMessage(error.code), error.code);
+    }
+    function checkRange(x, min, max) {
+      if (x !== clamp(x, min, max)) {
+        throwRangeError();
+      }
+    }
+    function get(b, m, size) {
+      if (b.position + size > b.length) {
+        throwEOFError();
+      }
+      var v = b.view[m](b.position, b.le);
+      b.position += size;
+      return v;
+    }
+    function set(b, m, size, v) {
+      var len = b.position + size;
+      b.ensureCapacity(len);
+      b.view[m](b.position, v, b.le);
+      b.position = len;
+      if (len > b.length) {
+        b.length = len;
+      }
+    }
+    var BAp = ByteArrayClass.prototype;
+    BAp.cacheViews = function cacheViews() {
+      var a = this.a;
+      this.int8v = new Int8Array(a);
+      this.uint8v = new Uint8Array(a);
+      this.view = new DataView(a);
+    };
+    BAp.getBytes = function getBytes() {
+      return new Uint8Array(this.a, 0, this.length);
+    };
+    BAp.ensureCapacity = function ensureCapacity(size) {
+      var origa = this.a;
+      if (origa.byteLength < size) {
+        var newSize = origa.byteLength;
+        while (newSize < size) {
+          newSize *= 2;
+        }
+        var copya = new ArrayBuffer(newSize);
+        var origv = this.int8v;
+        this.a = copya;
+        this.cacheViews();
+        this.int8v.set(origv);
+      }
+    };
+    BAp.clear = function clear() {
+      this.length = 0;
+      this.position = 0;
+    };
+    BAp.readBoolean = function readBoolean() {
+      if (this.position + 1 > this.length) {
+        throwEOFError();
+      }
+      return this.int8v[this.position++] !== 0;
+    };
+    BAp.readByte = function readByte() {
+      if (this.position + 1 > this.length) {
+        throwEOFError();
+      }
+      return this.int8v[this.position++];
+    };
+    BAp.readUnsignedByte = function readUnsignedByte() {
+      if (this.position + 1 > this.length) {
+        throwEOFError();
+      }
+      return this.uint8v[this.position++];
+    };
+    BAp.readBytes = function readBytes(bytes, offset, length) {
+      var pos = this.position;
+      if (!offset) {
+        offset = 0;
+      }
+      if (!length) {
+        length = this.length - pos;
+      }
+      if (pos + length > this.length) {
+        throwEOFError();
+      }
+      if (bytes.length < offset + length) {
+        bytes.ensureCapacity(offset + length);
+        bytes.length = offset + length;
+      }
+      bytes.int8v.set(new Int8Array(this.a, pos, length), offset);
+      this.position += length;
+    };
+    BAp.writeBoolean = function writeBoolean(v) {
+      var len = this.position + 1;
+      this.ensureCapacity(len);
+      this.int8v[this.position++] = v ? 1 : 0;
+      if (len > this.length) {
+        this.length = len;
+      }
+    };
+    BAp.writeByte = function writeByte(v) {
+      var len = this.position + 1;
+      this.ensureCapacity(len);
+      this.int8v[this.position++] = v;
+      if (len > this.length) {
+        this.length = len;
+      }
+    };
+    BAp.writeUnsignedByte = function writeUnsignedByte(v) {
+      var len = this.position + 1;
+      this.ensureCapacity(len);
+      this.uint8v[this.position++] = v;
+      if (len > this.length) {
+        this.length = len;
+      }
+    };
+    BAp.writeRawBytes = function writeRawBytes(bytes) {
+      var len = this.position + bytes.length;
+      this.ensureCapacity(len);
+      this.int8v.set(bytes, this.position);
+      this.position = len;
+      if (len > this.length) {
+        this.length = len;
+      }
+    };
+    BAp.readRawBytes = function readRawBytes() {
+      return new Int8Array(this.a, 0, this.length);
+    };
+    BAp.writeBytes = function writeBytes(bytes, offset, length) {
+      if (arguments.length < 2) {
+        offset = 0;
+      }
+      if (arguments.length < 3) {
+        length = 0;
+      }
+      checkRange(offset, 0, bytes.length);
+      checkRange(offset + length, 0, bytes.length);
+      if (length === 0) {
+        length = bytes.length - offset;
+      }
+      this.writeRawBytes(new Int8Array(bytes.a, offset, length));
+    };
+    BAp.readDouble = function readDouble() {
+      return get(this, 'getFloat64', 8);
+    };
+    BAp.readFloat = function readFloat() {
+      return get(this, 'getFloat32', 4);
+    };
+    BAp.readInt = function readInt() {
+      return get(this, 'getInt32', 4);
+    };
+    BAp.readShort = function readShort() {
+      return get(this, 'getInt16', 2);
+    };
+    BAp.readUnsignedInt = function readUnsignedInt() {
+      return get(this, 'getUint32', 4);
+    };
+    BAp.readUnsignedShort = function readUnsignedShort() {
+      return get(this, 'getUint16', 2);
+    };
+    BAp.writeDouble = function writeDouble(v) {
+      set(this, 'setFloat64', 8, v);
+    };
+    BAp.writeFloat = function writeFloat(v) {
+      set(this, 'setFloat32', 4, v);
+    };
+    BAp.writeInt = function writeInt(v) {
+      set(this, 'setInt32', 4, v);
+    };
+    BAp.writeShort = function writeShort(v) {
+      set(this, 'setInt16', 2, v);
+    };
+    BAp.writeUnsignedInt = function writeUnsignedInt(v) {
+      set(this, 'setUint32', 4, v);
+    };
+    BAp.writeUnsignedShort = function writeUnsignedShort(v) {
+      set(this, 'setUint16', 2, v);
+    };
+    var codeLengthOrder = [
+        16,
+        17,
+        18,
+        0,
+        8,
+        7,
+        9,
+        6,
+        10,
+        5,
+        11,
+        4,
+        12,
+        3,
+        13,
+        2,
+        14,
+        1,
+        15
+      ];
+    var distanceCodes = [];
+    var distanceExtraBits = [];
+    for (var i = 0, j = 0, code = 1; i < 30; ++i) {
+      distanceCodes[i] = code;
+      code += 1 << (distanceExtraBits[i] = ~(~((j += i > 2 ? 1 : 0) / 2)));
+    }
+    var bitLengths = [];
+    for (var i = 0; i < 32; ++i) {
+      bitLengths[i] = 5;
+    }
+    var fixedDistanceTable = makeHuffmanTable(bitLengths);
+    var lengthCodes = [];
+    var lengthExtraBits = [];
+    for (var i = 0, j = 0, code = 3; i < 29; ++i) {
+      lengthCodes[i] = code - (i == 28 ? 1 : 0);
+      code += 1 << (lengthExtraBits[i] = ~(~((j += i > 4 ? 1 : 0) / 4 % 6)));
+    }
+    for (var i = 0; i < 288; ++i) {
+      bitLengths[i] = i < 144 || i > 279 ? 8 : i < 256 ? 9 : 7;
+    }
+    var fixedLiteralTable = makeHuffmanTable(bitLengths);
+    function makeHuffmanTable(bitLengths) {
+      var maxBits = Math.max.apply(null, bitLengths);
+      var numLengths = bitLengths.length;
+      var size = 1 << maxBits;
+      var codes = new Uint32Array(size);
+      for (var code = 0, len = 1, skip = 2; len <= maxBits; code <<= 1, ++len, skip <<= 1) {
+        for (var val = 0; val < numLengths; ++val) {
+          if (bitLengths[val] === len) {
+            var lsb = 0;
+            for (var i = 0; i < len; ++i) {
+              lsb = lsb * 2 + (code >> i & 1);
+            }
+            for (var i = lsb; i < size; i += skip) {
+              codes[i] = len << 16 | val;
+            }
+            ++code;
+          }
+        }
+      }
+      return {
+        codes: codes,
+        maxBits: maxBits
+      };
+    }
+    function inflateBlock(input, output) {
+      var header = readBits(input, 3);
+      switch (header >> 1) {
+      case 0:
+        input.bitBuffer = input.bitLength = 0;
+        var len = input.readUnsignedShort();
+        var nlen = input.readUnsignedShort();
+        if ((~nlen & 65535) !== len) {
+          throwCompressedDataError();
+        }
+        output.writeBytes(input, input.position, len);
+        input.position += len;
+        break;
+      case 1:
+        inflate(input, output, fixedLiteralTable, fixedDistanceTable);
+        break;
+      case 2:
+        var bitLengths = [];
+        var numLiteralCodes = readBits(input, 5) + 257;
+        var numDistanceCodes = readBits(input, 5) + 1;
+        var numCodes = numLiteralCodes + numDistanceCodes;
+        var numLengthCodes = readBits(input, 4) + 4;
+        for (var i = 0; i < 19; ++i) {
+          bitLengths[codeLengthOrder[i]] = i < numLengthCodes ? readBits(sbytes, sstream, 3) : 0;
+        }
+        var codeLengthTable = makeHuffmanTable(bitLengths);
+        bitLengths = [];
+        var i = 0;
+        var prev = 0;
+        while (i < numCodes) {
+          var j = 1;
+          var sym = readCode(input, codeLengthTable);
+          switch (sym) {
+          case 16:
+            j = readBits(input, 2) + 3;
+            sym = prev;
+            break;
+          case 17:
+            j = readBits(input, 3) + 3;
+            sym = 0;
+            break;
+          case 18:
+            j = readBits(input, 7) + 11;
+            sym = 0;
+            break;
+          default:
+            prev = sym;
+          }
+          while (j--) {
+            bitLengths[i++] = sym;
+          }
+        }
+        var distanceTable = makeHuffmanTable(bitLengths.splice(numLiteralCodes, numDistanceCodes));
+        var literalTable = makeHuffmanTable(bitLengths);
+        inflate(input, output, literalTable, distanceTable);
+        break;
+      default:
+        fail('unknown block type', 'inflate');
+      }
+    }
+    function readBits(input, size) {
+      var buffer = input.bitBuffer;
+      var bufflen = input.bitLength;
+      while (size > bufflen) {
+        buffer |= input.readUnsignedByte() << bufflen;
+        bufflen += 8;
+      }
+      input.bitBuffer = buffer >>> size;
+      input.bitLength = bufflen - size;
+      return buffer & (1 << size) - 1;
+    }
+    function inflate(input, output, literalTable, distanceTable) {
+      var sym;
+      while ((sym = readCode(input, literalTable)) !== 256) {
+        if (sym < 256) {
+          output.writeUnsignedByte(sym);
+        } else {
+          sym -= 257;
+          var len = lengthCodes[sym] + readBits(input, lengthExtraBits[sym]);
+          sym = readCode(input, distanceTable);
+          var distance = distanceCodes[sym] + readBits(input, distanceExtraBits[sym]);
+          output.writeBytes(output, output.position - distance, len);
+        }
+      }
+    }
+    function readCode(input, codeTable) {
+      var buffer = input.bitBuffer;
+      var bitlen = input.bitLength;
+      var maxBits = codeTable.maxBits;
+      while (maxBits > bitlen) {
+        buffer |= input.readUnsignedByte() << bitlen;
+        bitlen += 8;
+      }
+      var code = codeTable.codes[buffer & (1 << maxBits) - 1];
+      var len = code >> 16;
+      if (!len) {
+        throwCompressedDataError();
+      }
+      input.bitBuffer = buffer >>> len;
+      input.bitLength = bitlen - len;
+      return code & 65535;
+    }
+    function adler32(data, start, end) {
+      var a = 1;
+      var b = 0;
+      for (var i = start; i < end; ++i) {
+        a = (a + (data[i] & 255)) % 65521;
+        b = (b + a) % 65521;
+      }
+      return b << 16 | a;
+    }
+    BAp.compress = function (algorithm) {
+      this.position = 0;
+      var output = new ByteArray();
+      switch (algorithm) {
+      case 'zlib':
+        output.writeUnsignedByte(120);
+        output.writeUnsignedByte(156);
+      case 'deflate':
+        output.le = true;
+        var len = this.length;
+        output.ensureCapacity(len + Math.ceil(len / 65535) * 5 + 4);
+        while (len > 65535) {
+          output.writeUnsignedByte(0);
+          output.writeUnsignedShort(65535);
+          output.writeUnsignedShort(0);
+          output.writeBytes(this, this.position, 65535);
+          this.position += 65535;
+          len -= 65535;
+        }
+        output.writeUnsignedByte(0);
+        output.writeUnsignedShort(len);
+        output.writeUnsignedShort(~len & 65535);
+        output.writeBytes(this, this.position, len);
+        if (algorithm === 'zlib') {
+          output.writeUnsignedInt(adler32(this.uint8v, 0, this.length));
+        }
+        break;
+      default:
+        return;
+      }
+      this.ensureCapacity(output.uint8v.length);
+      this.uint8v.set(output.uint8v);
+      this.length = output.length;
+      this.position = 0;
+    };
+    BAp.uncompress = function (algorithm) {
+      var output = new ByteArray();
+      switch (algorithm) {
+      case 'zlib':
+        var header = this.readUnsignedShort();
+        if ((header & 3840) !== 2048 || header % 31 !== 0 || header & 32) {
+          throwCompressedDataError();
+        }
+      case 'deflate':
+        var le = this.le;
+        this.le = true;
+        while (this.position < this.length - 6) {
+          inflateBlock(this, output);
+        }
+        this.le = le;
+        break;
+      default:
+        return;
+      }
+      this.ensureCapacity(output.uint8v.length);
+      this.uint8v.set(output.uint8v);
+      this.length = output.length;
+      this.position = 0;
+    };
+    return ByteArrayClass;
+  }();
 (function (exports) {
   var ArgumentParser = function () {
       var Argument = function () {
           function argument(shortName, longName, type, options) {
             this.shortName = shortName;
             this.longName = longName;
             this.type = type;
             options = options || {};
@@ -304,16 +751,223 @@
     }();
   exports.Option = Option;
   exports.OptionSet = OptionSet;
   exports.ArgumentParser = ArgumentParser;
 }(typeof exports === 'undefined' ? options = {} : exports));
 var Option = options.Option;
 var OptionSet = options.OptionSet;
 var coreOptions = new OptionSet('Core Options');
+var Timeline = function () {
+    var barColor = 'rgba(255,255,255, 0.075)';
+    var backgroundColor = 'rgb(61, 61, 61)';
+    var backgroundColorInfo = 'rgba(0,0,0, 0.85)';
+    var fpsLineColor = 'rgb(255,64,0)';
+    var textColor = '#ccc';
+    function timeline(canvas) {
+      this.depth = 0;
+      this.start = 0;
+      this.index = 0;
+      this.marks = new CircularBuffer(Int32Array);
+      this.times = new CircularBuffer(Float64Array);
+      this.frameRate = 12;
+      this.maxFrameTime = 1000 * 2 / this.frameRate;
+      this.refreshFrequency = 10;
+      this.refreshCounter = 0;
+      this.count = 0;
+      this.kinds = createEmptyObject();
+      this.kindCount = 0;
+      this.canvas = canvas;
+      this.context = canvas.getContext('2d', {
+        original: true
+      });
+      this.fillStyles = [
+        'rgb(85, 152, 213)',
+        '#bfd8a7',
+        '#d906d7'
+      ];
+      window.addEventListener('resize', this.resizeHandler.bind(this), false);
+      this.resizeHandler();
+    }
+    timeline.prototype.setFrameRate = function setFrameRate(frameRate) {
+      this.frameRate = frameRate;
+      this.maxFrameTime = 1000 * 2 / frameRate;
+    };
+    timeline.prototype.refreshEvery = function refreshEvery(freq) {
+      this.refreshFrequency = freq;
+      this.refreshCounter = 0;
+    };
+    var ENTER = 3203334144 | 0;
+    var LEAVE = 3735879680 | 0;
+    timeline.prototype.registerKind = function getKind(name, fillStyle) {
+      if (this.kinds[name] === undefined) {
+        this.fillStyles[this.kindCount] = fillStyle;
+        this.kinds[name] = this.kindCount++;
+      } else {
+        this.fillStyles[this.kinds[name]] = fillStyle;
+      }
+    };
+    timeline.prototype.getKind = function getKind(name) {
+      if (this.kinds[name] === undefined) {
+        this.kinds[name] = this.kindCount++;
+        if (this.kindCount > this.fillStyles.length) {
+          this.fillStyles.push(randomStyle());
+        }
+      }
+      return this.kinds[name];
+    };
+    timeline.prototype.enter = function enter(name) {
+      this.depth++;
+      this.marks.write(ENTER | this.getKind(name));
+      this.times.write(performance.now());
+    };
+    timeline.prototype.leave = function leave(name) {
+      this.marks.write(LEAVE | this.getKind(name));
+      this.times.write(performance.now());
+      this.depth--;
+      if (this.depth === 0) {
+        this.count++;
+        if (++this.refreshCounter == this.refreshFrequency) {
+          this.refreshCounter = 0;
+          this.paint();
+        }
+      }
+    };
+    timeline.prototype.gatherFrames = function gatherFrames(maxFrames) {
+      var stack = [];
+      var frames = [];
+      var times = this.times;
+      maxFrames++;
+      this.marks.forEachInReverse(function (mark, i) {
+        var time = times.get(i);
+        if ((mark & 4294901760) === ENTER) {
+          var node = stack.pop();
+          node.startTime = time;
+          if (!stack.length) {
+            if (frames.length && !frames[0].total) {
+              frames[0].total = frames[0].startTime - time;
+            }
+            frames.unshift(node);
+          } else {
+            var top = stack.top();
+            if (!top.children) {
+              top.children = [
+                node
+              ];
+            } else {
+              top.children.push(node);
+            }
+          }
+        } else if ((mark & 4294901760) === LEAVE) {
+          if (frames.length > maxFrames) {
+            return true;
+          }
+          stack.push({
+            kind: mark & 65535,
+            endTime: time
+          });
+        }
+      });
+      return frames;
+    };
+    timeline.prototype.resizeHandler = function resizeHandler(event) {
+      var parent = this.canvas.parentElement;
+      this.cw = parent.offsetWidth;
+      this.ch = parent.offsetHeight - 1;
+      var devicePixelRatio = window.devicePixelRatio || 1;
+      var backingStoreRatio = this.context.webkitBackingStorePixelRatio || this.context.mozBackingStorePixelRatio || this.context.msBackingStorePixelRatio || this.context.oBackingStorePixelRatio || this.context.backingStorePixelRatio || 1;
+      if (devicePixelRatio !== backingStoreRatio) {
+        var ratio = devicePixelRatio / backingStoreRatio;
+        this.canvas.width = this.cw * ratio;
+        this.canvas.height = this.ch * ratio;
+        this.canvas.style.width = this.cw + 'px';
+        this.canvas.style.height = this.ch + 'px';
+        this.context.scale(ratio, ratio);
+      } else {
+        this.canvas.width = this.cw;
+        this.canvas.height = this.ch;
+      }
+      this.context.font = '10px Consolas, "Liberation Mono", Courier, monospace';
+    };
+    timeline.prototype.paint = function paint() {
+      var w = 10;
+      var gap = 1;
+      var maxFrames = this.cw / (w + gap) | 0;
+      var frames = this.gatherFrames(maxFrames);
+      var context = this.context;
+      var maxFrameTime = this.maxFrameTime;
+      var fillStyles = this.fillStyles;
+      context.clearRect(0, 0, this.cw, this.ch);
+      var maxFrameRate = 0;
+      var maxFrameRateCount = 0;
+      var avgFrameRate = 0;
+      var avgFrameRateCount = 0;
+      var offsetW;
+      context.save();
+      context.translate(0, this.ch);
+      context.scale(1, -this.ch / maxFrameTime);
+      for (var i = 0; i < frames.length - 1; i++) {
+        var frame = frames[i];
+        maxFrameRate += frame.endTime - frame.startTime;
+        maxFrameRateCount++;
+        if (frame.total) {
+          avgFrameRate += frame.total;
+          avgFrameRateCount++;
+        }
+        offsetW = i * (w + gap);
+        context.fillStyle = barColor;
+        context.fillRect(offsetW, 0, w, frames[i + 1].startTime - frame.startTime);
+        drawNode(frame, frame.startTime);
+      }
+      function drawNode(node, frameStartTime) {
+        var nodeTime = node.endTime - node.startTime;
+        var offsetH = node.startTime - frameStartTime;
+        context.fillStyle = fillStyles[node.kind];
+        context.fillRect(offsetW, offsetH, w, nodeTime);
+        if (node.children) {
+          var children = node.children;
+          for (var i = 0, n = children.length; i < n; i++) {
+            drawNode(children[i], frameStartTime);
+          }
+        }
+      }
+      var lineH = 1000 / this.frameRate;
+      context.beginPath();
+      context.lineWidth = 0.5;
+      context.moveTo(0, lineH);
+      context.lineTo(this.cw, lineH);
+      context.strokeStyle = fpsLineColor;
+      context.stroke();
+      context.restore();
+      context.fillStyle = backgroundColorInfo;
+      context.fillRect(0, 0, this.cw, 20);
+      var textOffset;
+      var sFrameCount = this.count;
+      var sMaxFrameRate = Math.round(1000 * maxFrameRateCount / maxFrameRate);
+      var sAvgFrameRate = Math.round(1000 * avgFrameRateCount / avgFrameRate);
+      var space = 5;
+      textOffset = 5;
+      context.fillStyle = textColor;
+      context.fillText(sFrameCount, textOffset, 13);
+      textOffset += context.measureText(sFrameCount).width + space;
+      context.fillText(sMaxFrameRate, textOffset, 13);
+      textOffset += context.measureText(sMaxFrameRate).width + space;
+      context.fillText(sAvgFrameRate, textOffset, 13);
+      var basicOffset = textOffset + context.measureText(sAvgFrameRate).width + space;
+      textOffset = this.cw;
+      for (var k in this.kinds) {
+        context.fillStyle = this.fillStyles[this.getKind(k)];
+        textOffset -= context.measureText(k).width + space;
+        if (textOffset > basicOffset) {
+          this.context.fillText(k, textOffset, 13);
+        }
+      }
+    };
+    return timeline;
+  }();
 var create = Object.create;
 var defineProperty = Object.defineProperty;
 var keys = Object.keys;
 var isArray = Array.isArray;
 var fromCharCode = String.fromCharCode;
 var logE = Math.log;
 var max = Math.max;
 var min = Math.min;
@@ -335,42 +989,22 @@ function scriptProperties(namespace, pro
   }, {});
 }
 function cloneObject(obj) {
   var clone = Object.create(null);
   for (var prop in obj)
     clone[prop] = obj[prop];
   return clone;
 }
-function sortByDepth(a, b) {
-  var levelA = a._level;
-  var levelB = b._level;
-  if (a._parent !== b._parent && a._index > -1 && b._index > -1) {
-    while (a._level > levelB) {
-      a = a._parent;
-    }
-    while (b._level > levelA) {
-      b = b._parent;
-    }
-    while (a._level > 1) {
-      if (a._parent === b._parent) {
-        break;
-      }
-      a = a._parent;
-      b = b._parent;
-    }
-  }
-  if (a === b) {
-    return levelA - levelB;
-  }
-  return a._index - b._index;
-}
 function sortNumeric(a, b) {
   return a - b;
 }
+function sortByZindex(a, b) {
+  return a._zindex - b._zindex;
+}
 function rgbaObjToStr(color) {
   return 'rgba(' + color.red + ',' + color.green + ',' + color.blue + ',' + color.alpha / 255 + ')';
 }
 function rgbIntAlphaToStr(color, alpha) {
   color |= 0;
   if (alpha >= 1) {
     var colorStr = color.toString(16);
     while (colorStr.length < 6) {
@@ -444,168 +1078,346 @@ function randomStyle() {
       '#ff1300',
       '#1f1f21',
       '#bdbec2',
       '#ff3a2d'
     ];
   }
   return randomStyleCache[nextStyle++ % randomStyleCache.length];
 }
-var Promise = function PromiseClosure() {
-    function isPromise(obj) {
-      return typeof obj === 'object' && obj !== null && typeof obj.then === 'function';
-    }
-    function defaultOnFulfilled(value) {
-      return value;
-    }
-    function defaultOnRejected(reason) {
-      throw reason;
-    }
-    function propagateFulfilled(subject, value) {
-      subject.subpromisesValue = value;
-      var subpromises = subject.subpromises;
-      if (!subpromises) {
-        return;
-      }
-      for (var i = 0; i < subpromises.length; i++) {
-        subpromises[i].fulfill(value);
-      }
-      delete subject.subpromises;
-    }
-    function propagateRejected(subject, reason) {
-      subject.subpromisesReason = reason;
-      var subpromises = subject.subpromises;
-      if (!subpromises) {
-        if (!true) {
-          console.warn(reason);
-        }
-        return;
-      }
-      for (var i = 0; i < subpromises.length; i++) {
-        subpromises[i].reject(reason);
-      }
-      delete subject.subpromises;
-    }
-    function performCall(callback, arg, subject) {
-      try {
-        var value = callback(arg);
-        if (isPromise(value)) {
-          value.then(function Promise_queueCall_onFulfilled(value) {
-            propagateFulfilled(subject, value);
-          }, function Promise_queueCall_onRejected(reason) {
-            propagateRejected(subject, reason);
-          });
-          return;
-        }
-        propagateFulfilled(subject, value);
-      } catch (ex) {
-        propagateRejected(subject, ex);
-      }
-    }
-    var queue = [];
-    function processQueue() {
-      while (queue.length > 0) {
-        var task = queue[0];
-        if (task.directCallback) {
-          task.callback.call(task.subject, task.arg);
-        } else {
-          performCall(task.callback, task.arg, task.subject);
-        }
-        queue.shift();
-      }
-    }
-    function queueCall(callback, arg, subject, directCallback) {
-      if (queue.length === 0) {
-        setTimeout(processQueue, 0);
-      }
-      queue.push({
-        callback: callback,
-        arg: arg,
-        subject: subject,
-        directCallback: directCallback
-      });
-    }
-    function Promise(onFulfilled, onRejected) {
-      this.state = 'pending';
-      this.onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : defaultOnFulfilled;
-      this.onRejected = typeof onRejected === 'function' ? onRejected : defaultOnRejected;
-    }
-    Promise.prototype = {
-      fulfill: function Promise_resolve(value) {
-        if (this.state !== 'pending') {
-          return;
-        }
-        this.state = 'fulfilled';
-        this.value = value;
-        queueCall(this.onFulfilled, value, this, false);
-      },
-      reject: function Promise_reject(reason) {
-        if (this.state !== 'pending') {
-          return;
-        }
-        this.state = 'rejected';
-        this.reason = reason;
-        queueCall(this.onRejected, reason, this, false);
-      },
-      then: function Promise_then(onFulfilled, onRejected) {
-        var promise = new Promise(onFulfilled, onRejected);
-        if ('subpromisesValue' in this) {
-          queueCall(promise.fulfill, this.subpromisesValue, promise, true);
-        } else if ('subpromisesReason' in this) {
-          queueCall(promise.reject, this.subpromisesReason, promise, true);
-        } else {
-          var subpromises = this.subpromises || (this.subpromises = []);
-          subpromises.push(promise);
+(function PromiseClosure() {
+  var global = Function('return this')();
+  if (global.Promise) {
+    if (typeof global.Promise.all !== 'function') {
+      global.Promise.all = function (iterable) {
+        var count = 0, results = [], resolve, reject;
+        var promise = new global.Promise(function (resolve_, reject_) {
+            resolve = resolve_;
+            reject = reject_;
+          });
+        iterable.forEach(function (p, i) {
+          count++;
+          p.then(function (result) {
+            results[i] = result;
+            count--;
+            if (count === 0) {
+              resolve(results);
+            }
+          }, reject);
+        });
+        if (count === 0) {
+          resolve(results);
         }
         return promise;
-      },
-      get resolved() {
-        return this.state === 'fulfilled';
-      },
-      resolve: function (value) {
-        this.fulfill(value);
-      }
-    };
-    Promise.when = function Promise_when() {
-      var promise = new Promise();
-      if (arguments.length === 0) {
-        promise.resolve();
-        return promise;
-      }
-      var promises = slice.call(arguments, 0);
-      var result = [];
-      var i = 1;
-      function fulfill(value) {
-        result.push(value);
-        if (i < promises.length) {
-          promises[i++].then(fulfill, reject);
-        } else {
-          promise.resolve(result);
-        }
-        return value;
-      }
-      function reject(reason) {
-        promise.reject(reason);
-      }
-      promises[0].then(fulfill, reject);
-      return promise;
-    };
-    return Promise;
-  }();
-var QuadTree = function (x, y, width, height, level) {
+      };
+    }
+    if (typeof global.Promise.resolve !== 'function') {
+      global.Promise.resolve = function (x) {
+        return new global.Promise(function (resolve) {
+          resolve(x);
+        });
+      };
+    }
+    return;
+  }
+  function getDeferred(C) {
+    if (typeof C !== 'function') {
+      throw new TypeError('Invalid deferred constructor');
+    }
+    var resolver = createDeferredConstructionFunctions();
+    var promise = new C(resolver);
+    var resolve = resolver.resolve;
+    if (typeof resolve !== 'function') {
+      throw new TypeError('Invalid resolve construction function');
+    }
+    var reject = resolver.reject;
+    if (typeof reject !== 'function') {
+      throw new TypeError('Invalid reject construction function');
+    }
+    return {
+      promise: promise,
+      resolve: resolve,
+      reject: reject
+    };
+  }
+  function updateDeferredFromPotentialThenable(x, deferred) {
+    if (typeof x !== 'object' || x === null) {
+      return false;
+    }
+    try {
+      var then = x.then;
+      if (typeof then !== 'function') {
+        return false;
+      }
+      var thenCallResult = then.call(x, deferred.resolve, deferred.reject);
+    } catch (e) {
+      var reject = deferred.reject;
+      reject(e);
+    }
+    return true;
+  }
+  function isPromise(x) {
+    return typeof x === 'object' && x !== null && typeof x.promiseStatus !== 'undefined';
+  }
+  function rejectPromise(promise, reason) {
+    if (promise.promiseStatus !== 'unresolved') {
+      return;
+    }
+    var reactions = promise.rejectReactions;
+    promise.result = reason;
+    promise.resolveReactions = undefined;
+    promise.rejectReactions = undefined;
+    promise.promiseStatus = 'has-rejection';
+    triggerPromiseReactions(reactions, reason);
+  }
+  function resolvePromise(promise, resolution) {
+    if (promise.promiseStatus !== 'unresolved') {
+      return;
+    }
+    var reactions = promise.resolveReactions;
+    promise.result = resolution;
+    promise.resolveReactions = undefined;
+    promise.rejectReactions = undefined;
+    promise.promiseStatus = 'has-resolution';
+    triggerPromiseReactions(reactions, resolution);
+  }
+  function triggerPromiseReactions(reactions, argument) {
+    for (var i = 0; i < reactions.length; i++) {
+      queueMicrotask({
+        reaction: reactions[i],
+        argument: argument
+      });
+    }
+  }
+  function queueMicrotask(task) {
+    if (microtasksQueue.length === 0) {
+      setTimeout(handleMicrotasksQueue, 0);
+    }
+    microtasksQueue.push(task);
+  }
+  function executePromiseReaction(reaction, argument) {
+    var deferred = reaction.deferred;
+    var handler = reaction.handler;
+    var handlerResult, updateResult;
+    try {
+      handlerResult = handler(argument);
+    } catch (e) {
+      var reject = deferred.reject;
+      return reject(e);
+    }
+    if (handlerResult === deferred.promise) {
+      var reject = deferred.reject;
+      return reject(new TypeError('Self resolution'));
+    }
+    try {
+      updateResult = updateDeferredFromPotentialThenable(handlerResult, deferred);
+      if (!updateResult) {
+        var resolve = deferred.resolve;
+        return resolve(handlerResult);
+      }
+    } catch (e) {
+      var reject = deferred.reject;
+      return reject(e);
+    }
+  }
+  var microtasksQueue = [];
+  function handleMicrotasksQueue() {
+    while (microtasksQueue.length > 0) {
+      var task = microtasksQueue[0];
+      try {
+        executePromiseReaction(task.reaction, task.argument);
+      } catch (e) {
+        if (typeof Promise.onerror === 'function') {
+          Promise.onerror(e);
+        }
+      }
+      microtasksQueue.shift();
+    }
+  }
+  function throwerFunction(e) {
+    throw e;
+  }
+  function identityFunction(x) {
+    return x;
+  }
+  function createRejectPromiseFunction(promise) {
+    return function (reason) {
+      rejectPromise(promise, reason);
+    };
+  }
+  function createResolvePromiseFunction(promise) {
+    return function (resolution) {
+      resolvePromise(promise, resolution);
+    };
+  }
+  function createDeferredConstructionFunctions() {
+    var fn = function (resolve, reject) {
+      fn.resolve = resolve;
+      fn.reject = reject;
+    };
+    return fn;
+  }
+  function createPromiseResolutionHandlerFunctions(promise, fulfillmentHandler, rejectionHandler) {
+    return function (x) {
+      if (x === promise) {
+        return rejectionHandler(new TypeError('Self resolution'));
+      }
+      var cstr = promise.promiseConstructor;
+      if (isPromise(x)) {
+        var xConstructor = x.promiseConstructor;
+        if (xConstructor === cstr) {
+          return x.then(fulfillmentHandler, rejectionHandler);
+        }
+      }
+      var deferred = getDeferred(cstr);
+      var updateResult = updateDeferredFromPotentialThenable(x, deferred);
+      if (updateResult) {
+        var deferredPromise = deferred.promise;
+        return deferredPromise.then(fulfillmentHandler, rejectionHandler);
+      }
+      return fulfillmentHandler(x);
+    };
+  }
+  function createPromiseAllCountdownFunction(index, values, deferred, countdownHolder) {
+    return function (x) {
+      values[index] = x;
+      countdownHolder.countdown--;
+      if (countdownHolder.countdown === 0) {
+        deferred.resolve(values);
+      }
+    };
+  }
+  function Promise(resolver) {
+    if (typeof resolver !== 'function') {
+      throw new TypeError('resolver is not a function');
+    }
+    var promise = this;
+    if (typeof promise !== 'object') {
+      throw new TypeError('Promise to initialize is not an object');
+    }
+    promise.promiseStatus = 'unresolved';
+    promise.resolveReactions = [];
+    promise.rejectReactions = [];
+    promise.result = undefined;
+    var resolve = createResolvePromiseFunction(promise);
+    var reject = createRejectPromiseFunction(promise);
+    try {
+      var result = resolver(resolve, reject);
+    } catch (e) {
+      rejectPromise(promise, e);
+    }
+    promise.promiseConstructor = Promise;
+    return promise;
+  }
+  Promise.all = function (iterable) {
+    var deferred = getDeferred(this);
+    var values = [];
+    var countdownHolder = {
+        countdown: 0
+      };
+    var index = 0;
+    iterable.forEach(function (nextValue) {
+      var nextPromise = this.cast(nextValue);
+      var fn = createPromiseAllCountdownFunction(index, values, deferred, countdownHolder);
+      nextPromise.then(fn, deferred.reject);
+      index++;
+      countdownHolder.countdown++;
+    }, this);
+    if (index === 0) {
+      deferred.resolve(values);
+    }
+    return deferred.promise;
+  };
+  Promise.cast = function (x) {
+    if (isPromise(x)) {
+      return x;
+    }
+    var deferred = getDeferred(this);
+    deferred.resolve(x);
+    return deferred.promise;
+  };
+  Promise.reject = function (r) {
+    var deferred = getDeferred(this);
+    var rejectResult = deferred.reject(r);
+    return deferred.promise;
+  };
+  Promise.resolve = function (x) {
+    var deferred = getDeferred(this);
+    var rejectResult = deferred.resolve(x);
+    return deferred.promise;
+  };
+  Promise.prototype = {
+    'catch': function (onRejected) {
+      this.then(undefined, onRejected);
+    },
+    then: function (onFulfilled, onRejected) {
+      var promise = this;
+      if (!isPromise(promise)) {
+        throw new TypeError('this is not a Promises');
+      }
+      var cstr = promise.promiseConstructor;
+      var deferred = getDeferred(cstr);
+      var rejectionHandler = typeof onRejected === 'function' ? onRejected : throwerFunction;
+      var fulfillmentHandler = typeof onFulfilled === 'function' ? onFulfilled : identityFunction;
+      var resolutionHandler = createPromiseResolutionHandlerFunctions(promise, fulfillmentHandler, rejectionHandler);
+      var resolveReaction = {
+          deferred: deferred,
+          handler: resolutionHandler
+        };
+      var rejectReaction = {
+          deferred: deferred,
+          handler: rejectionHandler
+        };
+      switch (promise.promiseStatus) {
+      case 'unresolved':
+        promise.resolveReactions.push(resolveReaction);
+        promise.rejectReactions.push(rejectReaction);
+        break;
+      case 'has-resolution':
+        var resolution = promise.result;
+        queueMicrotask({
+          reaction: resolveReaction,
+          argument: resolution
+        });
+        break;
+      case 'has-rejection':
+        var rejection = promise.result;
+        queueMicrotask({
+          reaction: rejectReaction,
+          argument: rejection
+        });
+        break;
+      }
+      return deferred.promise;
+    }
+  };
+  global.Promise = Promise;
+}());
+var QuadTree = function (x, y, width, height, parent) {
   this.x = x | 0;
   this.y = y | 0;
   this.width = width | 0;
   this.height = height | 0;
-  this.level = level | 0;
-  this.stuckObjects = [];
-  this.objects = [];
+  if (parent) {
+    this.root = parent.root;
+    this.parent = parent;
+    this.level = parent.level + 1;
+  } else {
+    this.root = this;
+    this.parent = null;
+    this.level = 0;
+  }
+  this.reset();
+};
+QuadTree.prototype.reset = function () {
+  this.stuckObjects = null;
+  this.objects = null;
   this.nodes = [];
 };
-QuadTree.prototype._findIndex = function (xMin, yMin, xMax, yMax) {
+QuadTree.prototype._findIndex = function (xMin, xMax, yMin, yMax) {
   var midX = this.x + (this.width / 2 | 0);
   var midY = this.y + (this.height / 2 | 0);
   var top = yMin < midY && yMax < midY;
   var bottom = yMin > midY;
   if (xMin < midX && xMax < midX) {
     if (top) {
       return 1;
     } else if (bottom) {
@@ -618,81 +1430,218 @@ QuadTree.prototype._findIndex = function
       return 3;
     }
   }
   return -1;
 };
 QuadTree.prototype.insert = function (obj) {
   var nodes = this.nodes;
   if (nodes.length) {
-    var index = this._findIndex(obj.xMin, obj.yMin, obj.xMax, obj.yMax);
+    var index = this._findIndex(obj.xMin, obj.xMax, obj.yMin, obj.yMax);
     if (index > -1) {
       nodes[index].insert(obj);
     } else {
-      this.stuckObjects.push(obj);
-      obj._qtree = this;
-    }
-    return;
-  }
-  var objects = this.objects;
-  objects.push(obj);
-  if (objects.length > 4 && this.level < 10) {
-    this._subdivide();
-    while (objects.length) {
-      this.insert(objects.shift());
+      obj.prev = null;
+      if (this.stuckObjects) {
+        obj.next = this.stuckObjects;
+        this.stuckObjects.prev = obj;
+      } else {
+        obj.next = null;
+      }
+      this.stuckObjects = obj;
+      obj.parent = this;
     }
     return;
   }
-  obj._qtree = this;
-};
-QuadTree.prototype.delete = function (obj) {
-  if (obj._qtree !== this) {
-    return;
-  }
-  var index = this.objects.indexOf(obj);
-  if (index > -1) {
-    this.objects.splice(index, 1);
+  var numChildren = 1;
+  var item = this.objects;
+  if (!item) {
+    obj.prev = null;
+    obj.next = null;
+    this.objects = obj;
   } else {
-    index = this.stuckObjects.indexOf(obj);
-    this.stuckObjects.splice(index, 1);
-  }
-  obj._qtree = null;
-};
-QuadTree.prototype._stack = [];
-QuadTree.prototype._out = [];
-QuadTree.prototype.retrieve = function (xMin, yMin, xMax, yMax) {
-  var stack = this._stack;
-  var out = this._out;
-  out.length = 0;
+    while (item.next) {
+      numChildren++;
+      item = item.next;
+    }
+    obj.prev = item;
+    obj.next = null;
+    item.next = obj;
+  }
+  if (numChildren > 4 && this.level < 10) {
+    this._subdivide();
+    item = this.objects;
+    while (item) {
+      var next = item.next;
+      this.insert(item);
+      item = next;
+    }
+    this.objects = null;
+    return;
+  }
+  obj.parent = this;
+};
+QuadTree.prototype.update = function (obj) {
+  var node = obj.parent;
+  if (node) {
+    if (obj.xMin >= node.x && obj.xMax <= node.x + node.width && obj.yMin >= node.y && obj.yMax <= node.y + node.height) {
+      if (node.nodes.length) {
+        var index = this._findIndex(obj.xMin, obj.xMax, obj.yMin, obj.yMax);
+        if (index > -1) {
+          node.remove(obj);
+          node = this.nodes[index];
+          node.insert(obj);
+        }
+      } else {
+        node.remove(obj);
+        node.insert(obj);
+      }
+      return;
+    }
+    node.remove(obj);
+  }
+  this.root.insert(obj);
+};
+QuadTree.prototype.remove = function (obj) {
+  var prev = obj.prev;
+  var next = obj.next;
+  if (prev) {
+    prev.next = next;
+    obj.prev = null;
+  } else {
+    var node = obj.parent;
+    if (node.objects === obj) {
+      node.objects = next;
+    } else if (node.stuckObjects === obj) {
+      node.stuckObjects = next;
+    }
+  }
+  if (next) {
+    next.prev = prev;
+    obj.next = null;
+  }
+  obj.parent = null;
+};
+QuadTree.prototype.retrieve = function (xMin, xMax, yMin, yMax) {
+  var stack = [];
+  var out = [];
   var node = this;
   do {
     if (node.nodes.length) {
-      var index = node._findIndex(xMin, yMin, xMax, yMax);
+      var index = node._findIndex(xMin, xMax, yMin, yMax);
       if (index > -1) {
         stack.push(node.nodes[index]);
       } else {
         stack.push.apply(stack, node.nodes);
       }
     }
-    out.push.apply(out, node.stuckObjects);
-    out.push.apply(out, node.objects);
+    var item = node.objects;
+    for (var i = 0; i < 2; i++) {
+      while (item) {
+        if (!(item.xMin > xMax || item.xMax < xMin || item.yMin > yMax || item.yMax < yMin)) {
+          out.push(item);
+        }
+        item = item.next;
+      }
+      item = node.stuckObjects;
+    }
     node = stack.pop();
   } while (node);
   return out;
 };
 QuadTree.prototype._subdivide = function () {
   var halfWidth = this.width / 2 | 0;
   var halfHeight = this.height / 2 | 0;
   var midX = this.x + halfWidth;
   var midY = this.y + halfHeight;
-  var level = this.level + 1;
-  this.nodes[0] = new QuadTree(midX, this.y, halfWidth, halfHeight, level);
-  this.nodes[1] = new QuadTree(this.x, this.y, halfWidth, halfHeight, level);
-  this.nodes[2] = new QuadTree(this.x, midY, halfWidth, halfHeight, level);
-  this.nodes[3] = new QuadTree(midX, midY, halfWidth, halfHeight, level);
+  this.nodes[0] = new QuadTree(midX, this.y, halfWidth, halfHeight, this);
+  this.nodes[1] = new QuadTree(this.x, this.y, halfWidth, halfHeight, this);
+  this.nodes[2] = new QuadTree(this.x, midY, halfWidth, halfHeight, this);
+  this.nodes[3] = new QuadTree(midX, midY, halfWidth, halfHeight, this);
+};
+var RegionCluster = function () {
+  this.regions = [];
+};
+RegionCluster.prototype.reset = function () {
+  this.regions.length = 0;
+};
+RegionCluster.prototype.insert = function (region) {
+  var regions = this.regions;
+  if (regions.length < 3) {
+    regions.push({
+      xMin: region.xMin,
+      xMax: region.xMax,
+      yMin: region.yMin,
+      yMax: region.yMax
+    });
+    return;
+  }
+  var a = region;
+  var b = regions[0];
+  var c = regions[1];
+  var d = regions[2];
+  var ab = (max(a.xMax, b.xMax) - min(a.xMin, b.xMin)) * (max(a.yMax, b.yMax) - min(a.yMin, b.yMin));
+  var rb = regions[0];
+  var ac = (max(a.xMax, c.xMax) - min(a.xMin, c.xMin)) * (max(a.yMax, c.yMax) - min(a.yMin, c.yMin));
+  var ad = (max(a.xMax, d.xMax) - min(a.xMin, d.xMin)) * (max(a.yMax, d.yMax) - min(a.yMin, d.yMin));
+  if (ac < ab) {
+    ab = ac;
+    rb = c;
+  }
+  if (ad < ab) {
+    ab = ad;
+    rb = d;
+  }
+  var bc = (max(b.xMax, c.xMax) - min(b.xMin, c.xMin)) * (max(b.yMax, c.yMax) - min(b.yMin, c.yMin));
+  var bd = (max(b.xMax, d.xMax) - min(b.xMin, d.xMin)) * (max(b.yMax, d.yMax) - min(b.yMin, d.yMin));
+  var cd = (max(c.xMax, d.xMax) - min(c.xMin, d.xMin)) * (max(c.yMax, d.yMax) - min(c.yMin, d.yMin));
+  if (ab < bc && ab < bd && ab < cd) {
+    if (a.xMin < rb.xMin) {
+      rb.xMin = a.xMin;
+    }
+    if (a.xMax > rb.xMax) {
+      rb.xMax = a.xMax;
+    }
+    if (a.yMin < rb.yMin) {
+      rb.yMin = a.yMin;
+    }
+    if (a.yMax > rb.yMax) {
+      rb.yMax = a.yMax;
+    }
+    return;
+  }
+  rb = regions[0];
+  var rc = regions[1];
+  if (bd < bc) {
+    bc = bd;
+    rc = regions[2];
+  }
+  if (cd < bc) {
+    rb = regions[1];
+    rc = regions[2];
+  }
+  if (rc.xMin < rb.xMin) {
+    rb.xMin = rc.xMin;
+  }
+  if (rc.xMax > rb.xMax) {
+    rb.xMax = rc.xMax;
+  }
+  if (rc.yMin < rb.yMin) {
+    rb.yMin = rc.yMin;
+  }
+  if (rc.yMax > rb.yMax) {
+    rb.yMax = rc.yMax;
+  }
+  rc.xMin = a.xMin;
+  rc.xMax = a.xMax;
+  rc.yMin = a.yMin;
+  rc.yMax = a.yMax;
+};
+RegionCluster.prototype.retrieve = function () {
+  return this.regions;
 };
 var EXTERNAL_INTERFACE_FEATURE = 1;
 var CLIPBOARD_FEATURE = 2;
 var SHAREDOBJECT_FEATURE = 3;
 var VIDEO_FEATURE = 4;
 var SOUND_FEATURE = 5;
 var NETCONNECTION_FEATURE = 6;
 if (!this.performance) {
@@ -2297,16 +3246,19 @@ function ShapePath(fillStyle, lineStyle,
       this.buffers.push(this.morphData.buffer);
       transferables.push(this.morphData.buffer);
     }
   } else {
     this.buffers = null;
   }
 }
 ShapePath.prototype = {
+  get isEmpty() {
+    return this.commands.length === 0;
+  },
   moveTo: function (x, y) {
     if (this.commands[this.commands.length - 1] === SHAPE_MOVE_TO) {
       this.data[this.data.length - 2] = x;
       this.data[this.data.length - 1] = y;
       return;
     }
     this.commands.push(SHAPE_MOVE_TO);
     this.data.push(x, y);
@@ -2458,16 +3410,18 @@ ShapePath.prototype = {
         colorTransform.setAlpha(ctx);
         ctx.lineWidth = Math.max(lineStyle.width / 20, 1);
         ctx.lineCap = lineStyle.lineCap;
         ctx.lineJoin = lineStyle.lineJoin;
         ctx.miterLimit = lineStyle.miterLimit;
         ctx.stroke();
         ctx.restore();
       }
+    } else {
+      ctx.fill();
     }
     ctx.closePath();
   },
   isPointInPath: function (x, y) {
     if (!(this.fillStyle || this.lineStyle)) {
       return false;
     }
     var bounds = this.strokeBounds || this.bounds || this._calculateBounds();
@@ -3139,32 +4093,32 @@ function extendBoundsByY(bounds, y) {
     bounds.yMin = y;
   } else if (y > bounds.yMax) {
     bounds.yMax = y;
   }
 }
 function morph(start, end, ratio) {
   return start + (end - start) * ratio;
 }
-function finishShapePath(path, dictionary) {
+function finishShapePath(path, dictionaryResolved) {
   if (path.fullyInitialized) {
     return path;
   }
   if (!(path instanceof ShapePath)) {
     var untypedPath = path;
     path = new ShapePath(path.fillStyle, path.lineStyle, 0, 0, path.isMorph);
     path.commands = new Uint8Array(untypedPath.buffers[0]);
     path.data = new Int32Array(untypedPath.buffers[1]);
     if (untypedPath.isMorph) {
       path.morphData = new Int32Array(untypedPath.buffers[2]);
     }
     path.buffers = null;
   }
-  path.fillStyle && initStyle(path.fillStyle, dictionary);
-  path.lineStyle && initStyle(path.lineStyle, dictionary);
+  path.fillStyle && initStyle(path.fillStyle, dictionaryResolved);
+  path.lineStyle && initStyle(path.lineStyle, dictionaryResolved);
   path.fullyInitialized = true;
   return path;
 }
 var inWorker = typeof window === 'undefined';
 var factoryCtx = !inWorker ? document.createElement('canvas').getContext('2d') : null;
 function buildLinearGradientFactory(colorStops) {
   var defaultGradient = factoryCtx.createLinearGradient(-1, 0, 1, 0);
   for (var i = 0; i < colorStops.length; i++) {
@@ -3214,17 +4168,17 @@ function buildBitmapPatternFactory(img, 
     ctx.drawImage(img, 0, 0);
     cachedTransform = ctx.createPattern(canvas, repeat);
     cachedTransformKey = key;
     return cachedTransform;
   };
   fn.defaultFillStyle = defaultPattern;
   return fn;
 }
-function initStyle(style, dictionary) {
+function initStyle(style, dictionaryResolved) {
   if (style.type === undefined) {
     return;
   }
   switch (style.type) {
   case GRAPHICS_FILL_SOLID:
     break;
   case GRAPHICS_FILL_LINEAR_GRADIENT:
   case GRAPHICS_FILL_RADIAL_GRADIENT:
@@ -3246,19 +4200,19 @@ function initStyle(style, dictionary) {
       gradientConstructor = buildRadialGradientFactory((style.focalPoint | 0) / 20, colorStops);
     }
     style.style = gradientConstructor;
     break;
   case GRAPHICS_FILL_REPEATING_BITMAP:
   case GRAPHICS_FILL_CLIPPED_BITMAP:
   case GRAPHICS_FILL_NONSMOOTHED_REPEATING_BITMAP:
   case GRAPHICS_FILL_NONSMOOTHED_CLIPPED_BITMAP:
-    var bitmap = dictionary[style.bitmapId];
+    var bitmap = dictionaryResolved[style.bitmapId];
     var repeat = style.type === GRAPHICS_FILL_REPEATING_BITMAP || style.type === GRAPHICS_FILL_NONSMOOTHED_REPEATING_BITMAP;
-    style.style = buildBitmapPatternFactory(bitmap.value.props.img, repeat ? 'repeat' : 'no-repeat');
+    style.style = buildBitmapPatternFactory(bitmap.props.img, repeat ? 'repeat' : 'no-repeat');
     break;
   default:
     fail('invalid fill style', 'shape');
   }
 }
 var SOUND_SIZE_8_BIT = 0;
 var SOUND_SIZE_16_BIT = 1;
 var SOUND_TYPE_MONO = 0;
@@ -4037,47 +4991,53 @@ SWF.embed = function (file, doc, contain
     }
     stage._color = bgcolor;
     ctx.fillStyle = rgbaObjToStr(bgcolor);
     ctx.fillRect(0, 0, canvas.width, canvas.height);
     var root = loader._content;
     root._dispatchEvent('added', undefined, true);
     root._dispatchEvent('addedToStage');
     container.appendChild(canvas);
+    stage._domContainer = container;
     if (options.onStageInitialized) {
       options.onStageInitialized(stage);
     }
     renderStage(stage, ctx, options);
   });
   if (options.onComplete) {
     loaderInfo._addEventListener('complete', function () {
       options.onComplete();
     });
   }
   loader._load(typeof file === 'string' ? new flash.net.URLRequest(file) : file);
   return loader;
 };
 var rendererOptions = coreOptions.register(new OptionSet('Renderer Options'));
 var traceRenderer = rendererOptions.register(new Option('tr', 'traceRenderer', 'number', 0, 'trace renderer execution'));
-var disablePreVisitor = rendererOptions.register(new Option('dpv', 'disablePreVisitor', 'boolean', false, 'disable pre visitor'));
 var disableRenderVisitor = rendererOptions.register(new Option('drv', 'disableRenderVisitor', 'boolean', false, 'disable render visitor'));
 var disableMouseVisitor = rendererOptions.register(new Option('dmv', 'disableMouseVisitor', 'boolean', false, 'disable mouse visitor'));
 var showRedrawRegions = rendererOptions.register(new Option('rr', 'showRedrawRegions', 'boolean', false, 'show redraw regions'));
 var renderAsWireframe = rendererOptions.register(new Option('raw', 'renderAsWireframe', 'boolean', false, 'render as wireframe'));
 var showQuadTree = rendererOptions.register(new Option('qt', 'showQuadTree', 'boolean', false, 'show quad tree'));
 var turboMode = rendererOptions.register(new Option('', 'turbo', 'boolean', false, 'turbo mode'));
 var forceHidpi = rendererOptions.register(new Option('', 'forceHidpi', 'boolean', false, 'force hidpi'));
+var skipFrameDraw = rendererOptions.register(new Option('', 'skipFrameDraw', 'boolean', true, 'skip frame when not on time'));
+var hud = rendererOptions.register(new Option('', 'hud', 'boolean', false, 'show hud mode'));
 var enableConstructChildren = rendererOptions.register(new Option('', 'constructChildren', 'boolean', true, 'Construct Children'));
 var enableEnterFrame = rendererOptions.register(new Option('', 'enterFrame', 'boolean', true, 'Enter Frame'));
 var enableAdvanceFrame = rendererOptions.register(new Option('', 'advanceFrame', 'boolean', true, 'Advance Frame'));
 if (typeof FirefoxCom !== 'undefined') {
   turboMode.value = FirefoxCom.requestSync('getBoolPref', {
     pref: 'shumway.turboMode',
     def: false
   });
+  hud.value = FirefoxCom.requestSync('getBoolPref', {
+    pref: 'shumway.hud',
+    def: false
+  });
   forceHidpi.value = FirefoxCom.requestSync('getBoolPref', {
     pref: 'shumway.force_hidpi',
     def: false
   });
 }
 var CanvasCache = {
     cache: [],
     getCanvas: function getCanvas(protoCanvas) {
@@ -4179,17 +5139,17 @@ RenderVisitor.prototype = {
       root._invalidateTransform();
     }
   },
   childrenStart: function (parent) {
     if (this.depth === 0) {
       var ctx = this.ctx;
       ctx.save();
       if (this.invalidPath && !this.refreshStage && !renderAsWireframe.value) {
-        this.invalidPath.draw(ctx);
+        this.invalidPath.draw(ctx, false, 0, null);
         ctx.clip();
       }
       var bgcolor = this.root._color;
       if (bgcolor) {
         if (bgcolor.alpha < 255) {
           ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
         }
         if (bgcolor.alpha > 0) {
@@ -4239,33 +5199,34 @@ RenderVisitor.prototype = {
     var clippingMask = parentHasClippingMask === true;
     if (child._cxform) {
       context.colorTransform = parentColorTransform.applyCXForm(child._cxform);
     }
     if (!clippingMask) {
       while (this.clipDepth && this.clipDepth.length > 0 && child._depth > this.clipDepth[0].clipDepth) {
         var clipDepthInfo = this.clipDepth.shift();
         this.clipEnd(clipDepthInfo);
+        context.parentCtxs.shift();
         ctx = this.ctx = clipDepthInfo.ctx;
       }
+      if (this.clipDepth && this.clipDepth.length > 0 && child._depth <= this.clipDepth[0].clipDepth) {
+        ctx = this.ctx = this.clipDepth[0].maskee.ctx;
+      }
       if (child._clipDepth) {
         context.isClippingMask = clippingMask = true;
         var clipDepthInfo = this.clipStart(child);
         if (!this.clipDepth) {
           this.clipDepth = [
             clipDepthInfo
           ];
         } else {
           this.clipDepth.unshift(clipDepthInfo);
         }
+        context.parentCtxs.unshift(ctx);
         ctx = this.ctx = clipDepthInfo.mask.ctx;
-      } else {
-        if (this.clipDepth && this.clipDepth.length > 0 && child._depth <= this.clipDepth[0].clipDepth) {
-          ctx = this.ctx = this.clipDepth[0].maskee.ctx;
-        }
       }
     }
     if (clippingMask && child._isContainer) {
       ctx.save();
       renderDisplayObject(child, ctx, context);
       for (var i = 0, n = child._children.length; i < n; i++) {
         var child1 = child._children[i];
         if (!child1) {
@@ -4282,44 +5243,46 @@ RenderVisitor.prototype = {
       return;
     }
     ctx.save();
     ctx.globalCompositeOperation = getBlendModeName(child._blendMode);
     if (child._mask) {
       var clipInfo = this.clipStart(child);
       var mask = clipInfo.mask;
       var maskee = clipInfo.maskee;
+      context.parentCtxs.push(ctx);
       var savedClipDepth = this.clipDepth;
       this.clipDepth = null;
       this.ctx = mask.ctx;
       this.visit(child._mask, visitContainer, new RenderingContext(this.refreshStage));
       this.ctx = ctx;
       this.clipDepth = savedClipDepth;
       renderDisplayObject(child, maskee.ctx, context);
       if (child._isContainer) {
         this.ctx = maskee.ctx;
         visitContainer(child, this, context);
         this.ctx = ctx;
       }
+      context.parentCtxs.pop();
       this.clipEnd(clipInfo);
     } else {
       renderDisplayObject(child, ctx, context);
       if (child._isContainer) {
         visitContainer(child, this, context);
       }
     }
     ctx.restore();
     if (clippingMask) {
       ctx.fill();
     }
     context.isClippingMask = parentHasClippingMask;
     context.colorTransform = parentColorTransform;
   },
   clipStart: function (child) {
-    var m = child._parent._getConcatenatedTransform(true);
+    var m = child._parent._getConcatenatedTransform(null, true);
     var tx = m.tx / 20;
     var ty = m.ty / 20;
     var mask = CanvasCache.getCanvas(this.ctx.canvas);
     mask.ctx.setTransform(m.a, m.b, m.c, m.d, tx, ty);
     var maskee = CanvasCache.getCanvas(this.ctx.canvas);
     maskee.ctx.setTransform(m.a, m.b, m.c, m.d, tx, ty);
     var clipInfo = {
         ctx: this.ctx,
@@ -4460,16 +5423,17 @@ RenderingColorTransform.prototype = {
     return this.transform.join('|');
   }
 };
 function RenderingContext(refreshStage, invalidPath) {
   this.refreshStage = refreshStage === true;
   this.invalidPath = invalidPath;
   this.isClippingMask = false;
   this.colorTransform = new RenderingColorTransform();
+  this.parentCtxs = [];
 }
 function renderDisplayObject(child, ctx, context) {
   var m = child._currentTransform;
   if (m) {
     if (m.a * m.d == m.b * m.c) {
       ctx.closePath();
       ctx.rect(0, 0, 0, 0);
       ctx.clip();
@@ -4488,21 +5452,25 @@ function renderDisplayObject(child, ctx,
       var graphics = child._graphics;
       if (graphics._bitmap) {
         ctx.save();
         ctx.translate(child._bbox.xMin / 20, child._bbox.yMin / 20);
         context.colorTransform.setAlpha(ctx, true);
         ctx.drawImage(graphics._bitmap, 0, 0);
         ctx.restore();
       } else {
-        graphics.draw(ctx, context.isClippingMask, child.ratio, context.colorTransform);
+        var ratio = child.ratio;
+        if (ratio === undefined) {
+          ratio = 0;
+        }
+        graphics.draw(ctx, context.isClippingMask, ratio, context.colorTransform);
       }
     }
     if (child.draw) {
-      child.draw(ctx, child.ratio, context.colorTransform);
+      child.draw(ctx, child.ratio, context.colorTransform, context.parentCtxs);
     }
   } else {
     if (!child._invalid && !context.refreshStage) {
       return;
     }
     if (child.getBounds) {
       var b = child.getBounds(null);
       if (b && b.xMax - b.xMin > 0 && b.yMax - b.yMin > 0) {
@@ -4549,29 +5517,50 @@ function sampleEnd() {
     return;
   }
   samplesLeftPlusOne--;
   if (samplesLeftPlusOne === 1) {
     console.profileEnd('Sample');
   }
 }
 var timeline;
+var hudTimeline;
 function timelineEnter(name) {
   timeline && timeline.enter(name);
+  hudTimeline && hudTimeline.enter(name);
 }
 function timelineLeave(name) {
   timeline && timeline.leave(name);
+  hudTimeline && hudTimeline.leave(name);
 }
 function timelineWrapBroadcastMessage(domain, message) {
   timelineEnter(message);
   domain.broadcastMessage(message);
   timelineLeave(message);
 }
+function initializeHUD(stage, parentCanvas) {
+  var canvas = document.createElement('canvas');
+  var canvasContainer = document.createElement('div');
+  canvasContainer.appendChild(canvas);
+  canvasContainer.style.position = 'absolute';
+  canvasContainer.style.top = '0px';
+  canvasContainer.style.left = '0px';
+  canvasContainer.style.width = '100%';
+  canvasContainer.style.height = '150px';
+  canvasContainer.style.backgroundColor = 'rgba(0, 0, 0, 0.4)';
+  parentCanvas.parentElement.appendChild(canvasContainer);
+  hudTimeline = new Timeline(canvas);
+  hudTimeline.setFrameRate(stage._frameRate);
+  hudTimeline.refreshEvery(10);
+}
 function renderStage(stage, ctx, events) {
   var frameWidth, frameHeight;
+  if (!timeline && hud.value) {
+    initializeHUD(stage, ctx.canvas);
+  }
   function updateRenderTransform() {
     frameWidth = ctx.canvas.width;
     frameHeight = ctx.canvas.height;
     var scaleX = frameWidth / stage._stageWidth * 20;
     var scaleY = frameHeight / stage._stageHeight * 20;
     switch (stage._scaleMode) {
     case 'exactFit':
       break;
@@ -4672,32 +5661,19 @@ function renderStage(stage, ctx, events)
       });
     };
   }
   console.timeEnd('Initialize Renderer');
   console.timeEnd('Total');
   var firstRun = true;
   var frameCount = 0;
   var frameFPSAverage = new metrics.Average(120);
-  (function draw() {
-    var now = performance.now();
-    var renderFrame = now >= nextRenderAt;
-    if (renderFrame && events.onBeforeFrame) {
-      var e = {
-          cancel: false
-        };
-      events.onBeforeFrame(e);
-      renderFrame = !e.cancel;
-    }
-    if (renderFrame && renderDummyBalls) {
-      frameTime = now;
-      nextRenderAt = frameTime + maxDelay;
-      renderDummyBalls();
-      requestAnimationFrame(draw);
-      return;
+  function drawFrame(renderFrame, frameRequested) {
+    if (!skipFrameDraw.value) {
+      frameRequested = true;
     }
     sampleStart();
     var refreshStage = false;
     if (stage._invalid) {
       updateRenderTransform();
       stage._invalid = false;
       refreshStage = true;
     }
@@ -4706,104 +5682,142 @@ function renderStage(stage, ctx, events)
       stage._mouseMoved = false;
       mouseMoved = stage._mouseOver;
     } else {
       stage._handleMouseButtons();
     }
     if (renderFrame || refreshStage || mouseMoved) {
       FrameCounter.clear();
       var frameStartTime = performance.now();
+      timelineEnter('frame');
       traceRenderer.value && appendToFrameTerminal('Begin Frame #' + frameCount++, 'purple');
       var domain = avm2.systemDomain;
       if (renderFrame) {
-        frameTime = now;
-        maxDelay = 1000 / stage._frameRate;
-        if (!turboMode.value) {
-          while (nextRenderAt < now) {
-            nextRenderAt += maxDelay;
-          }
-        }
-        timelineEnter('EVENTS');
+        timelineEnter('events');
         if (firstRun) {
           firstRun = false;
         } else {
           enableAdvanceFrame.value && timelineWrapBroadcastMessage(domain, 'advanceFrame');
           enableEnterFrame.value && timelineWrapBroadcastMessage(domain, 'enterFrame');
           enableConstructChildren.value && timelineWrapBroadcastMessage(domain, 'constructChildren');
         }
         timelineWrapBroadcastMessage(domain, 'frameConstructed');
         timelineWrapBroadcastMessage(domain, 'executeFrame');
         timelineWrapBroadcastMessage(domain, 'exitFrame');
-        timelineLeave('EVENTS');
+        timelineLeave('events');
       }
       if (stage._deferRenderEvent) {
         stage._deferRenderEvent = false;
         domain.broadcastMessage('render', 'render');
       }
-      if (isCanvasVisible(ctx.canvas) && (refreshStage || renderFrame)) {
+      if (isCanvasVisible(ctx.canvas) && (refreshStage || renderFrame) && frameRequested) {
         var invalidPath = null;
-        if (!disablePreVisitor.value) {
-          traceRenderer.value && frameWriter.enter('> Pre Visitor');
-          timelineEnter('PRE');
-          invalidPath = stage._processInvalidRegions(true);
-          timelineLeave('PRE');
-          traceRenderer.value && frameWriter.leave('< Pre Visitor');
-        } else {
-          stage._processInvalidRegions(false);
-        }
-        if (!disableRenderVisitor.value) {
-          timelineEnter('RENDER');
-          traceRenderer.value && frameWriter.enter('> Render Visitor');
+        traceRenderer.value && frameWriter.enter('> Invalidation');
+        timelineEnter('invalidate');
+        invalidPath = stage._processInvalidations(refreshStage);
+        timelineLeave('invalidate');
+        traceRenderer.value && frameWriter.leave('< Invalidation');
+        if (!disableRenderVisitor.value && !invalidPath.isEmpty) {
+          timelineEnter('render');
+          traceRenderer.value && frameWriter.enter('> Rendering');
           new RenderVisitor(stage, ctx, invalidPath, refreshStage).start();
-          traceRenderer.value && frameWriter.leave('< Render Visitor');
-          timelineLeave('RENDER');
+          traceRenderer.value && frameWriter.leave('< Rendering');
+          timelineLeave('render');
         }
         if (showQuadTree.value) {
           ctx.strokeStyle = 'green';
           renderQuadTree(ctx, stage._qtree);
         }
         if (invalidPath && !refreshStage && showRedrawRegions.value) {
           ctx.strokeStyle = 'red';
           invalidPath.draw(ctx);
           ctx.stroke();
         }
       }
       if (mouseMoved && !disableMouseVisitor.value) {
-        renderFrame && timelineEnter('MOUSE');
-        traceRenderer.value && frameWriter.enter('> Mouse Visitor');
+        renderFrame && timelineEnter('mouse');
+        traceRenderer.value && frameWriter.enter('> Mouse Handling');
         stage._handleMouse();
-        traceRenderer.value && frameWriter.leave('< Mouse Visitor');
-        renderFrame && timelineLeave('MOUSE');
+        traceRenderer.value && frameWriter.leave('< Mouse Handling');
+        renderFrame && timelineLeave('mouse');
         ctx.canvas.style.cursor = stage._cursor;
       }
-      if (renderFrame && events.onAfterFrame) {
-        events.onAfterFrame();
-      }
       if (traceRenderer.value) {
         frameWriter.enter('> Frame Counters');
         for (var name in FrameCounter.counts) {
           frameWriter.writeLn(name + ': ' + FrameCounter.counts[name]);
         }
         frameWriter.leave('< Frame Counters');
         var frameElapsedTime = performance.now() - frameStartTime;
         var frameFPS = 1000 / frameElapsedTime;
         frameFPSAverage.push(frameFPS);
         traceRenderer.value && appendToFrameTerminal('End Frame Time: ' + frameElapsedTime.toFixed(2) + ' (' + frameFPS.toFixed(2) + ' fps, ' + frameFPSAverage.average().toFixed(2) + ' average fps)', 'purple');
       }
+      timelineLeave('frame');
     } else {
       traceRenderer.value && appendToFrameTerminal('Skip Frame', 'black');
     }
     sampleEnd();
+  }
+  var frameRequested = true;
+  var skipNextFrameDraw = false;
+  (function draw() {
+    var now = performance.now();
+    var renderFrame = true;
+    if (events.onBeforeFrame) {
+      var e = {
+          cancel: false
+        };
+      events.onBeforeFrame(e);
+      renderFrame = !e.cancel;
+    }
+    frameTime = now;
+    if (renderFrame && renderDummyBalls) {
+      renderDummyBalls();
+      return;
+    }
+    drawFrame(renderFrame, frameRequested && !skipNextFrameDraw);
+    frameRequested = false;
+    maxDelay = 1000 / stage._frameRate;
+    if (!turboMode.value) {
+      nextRenderAt += maxDelay;
+      var wasLate = false;
+      while (nextRenderAt < now) {
+        wasLate = true;
+        nextRenderAt += maxDelay;
+      }
+      if (wasLate && !skipNextFrameDraw) {
+        skipNextFrameDraw = true;
+        traceRenderer.value && appendToFrameTerminal('Skip Frame Draw', 'red');
+      } else {
+        skipNextFrameDraw = false;
+      }
+    } else {
+      nextRenderAt = now;
+    }
+    if (renderFrame && events.onAfterFrame) {
+      events.onAfterFrame();
+    }
     if (renderingTerminated) {
       if (events.onTerminated) {
         events.onTerminated();
       }
       return;
     }
-    requestAnimationFrame(draw);
+    setTimeout(draw, Math.max(0, nextRenderAt - performance.now()));
+  }());
+  (function frame() {
+    if (renderingTerminated) {
+      return;
+    }
+    if (stage._invalid || stage._mouseMoved) {
+      drawFrame(false, true);
+    }
+    frameRequested = true;
+    requestAnimationFrame(frame);
   }());
 }
 var tagHandler = function (global) {
     function defineShape($bytes, $stream, $, swfVersion, tagCode) {
       $ || ($ = {});
       $.id = readUi16($bytes, $stream);
       var $0 = $.bbox = {};
       bbox($bytes, $stream, $0, swfVersion, tagCode);
@@ -6459,17 +7473,17 @@ BodyParser.prototype = {
       finalBlock = progressInfo.bytesLoaded >= progressInfo.bytesTotal;
     }
     var readStartTime = performance.now();
     readTags(swf, stream, swfVersion, finalBlock, options.onprogress);
     swf.parseTime += performance.now() - readStartTime;
     var read = stream.pos;
     buffer.removeHead(read);
     this.totalRead += read;
-    if (this.totalRead >= this.length && options.oncomplete) {
+    if (options.oncomplete && swf.tags[swf.tags.length - 1].finalTag) {
       options.oncomplete(swf);
     }
   }
 };
 SWF.parseAsync = function swf_parseAsync(options) {
   var buffer = new HeadTailBuffer();
   var pipe = {
       push: function (data, progressInfo) {
@@ -6566,16 +7580,17 @@ SWF.parse = function (buffer, options) {
     options = {};
   var pipe = SWF.parseAsync(options);
   var bytes = new Uint8Array(buffer);
   var progressInfo = {
       bytesLoaded: bytes.length,
       bytesTotal: bytes.length
     };
   pipe.push(bytes, progressInfo);
+  pipe.close();
 };
 var $RELEASE = false;
 var isWorker = typeof window === 'undefined';
 if (isWorker && !true) {
   importScripts.apply(null, [
     '../../lib/DataView.js/DataView.js',
     '../flash/util.js',
     'config.js',
@@ -8970,23 +9985,39 @@ function popManyInto(src, count, dst) {
   extendBuiltin(Ap, 'pushUnique', function (v) {
     for (var i = 0, j = this.length; i < j; i++) {
       if (this[i] === v) {
         return;
       }
     }
     this.push(v);
   });
-  extendBuiltin(Ap, 'unique', function () {
-    var unique = [];
-    for (var i = 0; i < this.length; i++) {
-      unique.pushUnique(this[i]);
-    }
-    return unique;
-  });
+  var uniquesMap;
+  if (typeof Map !== 'undefined' && (uniquesMap = new Map()).clear) {
+    extendBuiltin(Ap, 'unique', function () {
+      var unique = [];
+      for (var i = 0; i < this.length; i++) {
+        if (uniquesMap.has(this[i])) {
+          continue;
+        }
+        unique.push(this[i]);
+        uniquesMap.set(this[i], true);
+      }
+      uniquesMap.clear();
+      return unique;
+    });
+  } else {
+    extendBuiltin(Ap, 'unique', function () {
+      var unique = [];
+      for (var i = 0; i < this.length; i++) {
+        unique.pushUnique(this[i]);
+      }
+      return unique;
+    });
+  }
   extendBuiltin(Ap, 'replace', function (x, y) {
     if (x === y) {
       return 0;
     }
     var count = 0;
     for (var i = 0; i < this.length; i++) {
       if (this[i] === x) {
         this[i] = y;
@@ -9503,17 +10534,17 @@ function base64ArrayBuffer(arrayBuffer) 
   return base64;
 }
 var PURPLE = '\x1b[94m';
 var YELLOW = '\x1b[93m';
 var GREEN = '\x1b[92m';
 var RED = '\x1b[91m';
 var ENDC = '\x1b[0m';
 var IndentingWriter = function () {
-    var consoleOutFn = console.info.bind(console);
+    var consoleOutFn = inBrowser ? console.info.bind(console) : print;
     function indentingWriter(suppressOutput, outFn) {
       this.tab = '  ';
       this.padding = '';
       this.suppressOutput = suppressOutput;
       this.out = outFn || consoleOutFn;
     }
     indentingWriter.prototype.writeLn = function writeLn(str) {
       if (!this.suppressOutput) {
@@ -10310,34 +11341,47 @@ var Errors = {
     InvalidEnumError: {
       code: 2008,
       message: 'Parameter %1 must be one of the accepted values.'
     },
     ArgumentError: {
       code: 2015,
       message: 'Invalid BitmapData.'
     },
+    CompressedDataError: {
+      code: 2058,
+      message: 'There was an error decompressing the data.'
+    },
+    SocketConnectError: {
+      code: 2011,
+      message: 'Socket connection failed to %1:%2.'
+    },
     CantAddSelfError: {
       code: 2024,
       message: 'An object cannot be added as a child of itself.'
     },
     NotAChildError: {
       code: 2025,
       message: 'The supplied DisplayObject must be a child of the caller.'
+    },
+    ExternalInterfaceNotAvailableError: {
+      code: 2067,
+      message: 'The ExternalInterface is not available in this container. ExternalInterface requires Internet Explorer ActiveX, Firefox, Mozilla 1.7.5 and greater, or other browsers that support NPRuntime.'
     }
   };
 function getErrorMessage(index) {
   if (!debuggerMode.value) {
     return 'Error #' + index;
   }
   for (var k in Errors) {
     if (Errors[k].code == index) {
       return 'Error #' + index + ': ' + Errors[k].message;
     }
   }
+  return 'Error #' + index + ': (unknown)';
 }
 function formatErrorMessage(error) {
   var message = error.message;
   Array.prototype.slice.call(arguments, 1).forEach(function (x, i) {
     message = message.replace('%' + (i + 1), x);
   });
   return 'Error #' + error.code + ': ' + message;
 }
@@ -12688,16 +13732,17 @@ var ScriptInfo = function scriptInfo() {
       }
     };
     return scriptInfo;
   }();
 var AbcFile = function () {
     function abcFile(bytes, name) {
       Timer.start('Parse ABC');
       this.name = name;
+      this.env = {};
       var n, i;
       var stream = new AbcStream(bytes);
       checkMagic(stream);
       Timer.start('Parse constantPool');
       this.constantPool = new ConstantPool(stream, name);
       Timer.stop();
       Timer.start('Parse Method Infos');
       this.methods = [];
@@ -16478,18 +17523,16 @@ var Verifier = function () {
                   return Type.fromName(trait.methodInfo.returnType, domain).instanceType();
                 } else if (trait.isClass()) {
                   return Type.from(trait.classInfo, domain);
                 } else if (trait.isMethod()) {
                   return Type.from(trait.methodInfo, domain);
                 }
               } else if (obj.isDirectlyReadable() && mn instanceof Multiname) {
                 ti().propertyQName = Multiname.getPublicQualifiedName(mn.name);
-              } else if (obj === Type.Object && mn instanceof Multiname) {
-                ti().propertyQName = Multiname.getPublicQualifiedName(mn.name);
               }
               if (isNumericMultiname(mn)) {
                 if (obj.isIndexedReadable()) {
                   ti().isIndexedReadable = true;
                   if (obj.isVector()) {
                     return obj.parameter;
                   }
                 } else if (obj.isDirectlyReadable()) {
@@ -16772,16 +17815,18 @@ var Verifier = function () {
               break;
             case 87:
               push(Type.from(new Activation(this.methodInfo)));
               break;
             case 88:
               push(Type.Any);
               break;
             case 89:
+              popMultiname();
+              pop();
               push(Type.XMLList);
               break;
             case 90:
               push(Type.Any);
               break;
             case 93:
               push(findProperty(popMultiname(), true));
               break;
@@ -18530,17 +19575,17 @@ var Verifier = function () {
   };
   Node.prototype.toString = function (brief) {
     if (brief) {
       return nameOf(this);
     }
     var inputs = [];
     this.visitInputs(function (input) {
       inputs.push(nameOf(input));
-    }, true);
+    });
     var str = nameOf(this) + ' = ' + this.nodeName.toUpperCase();
     if (this.toStringDetails) {
       str += ' ' + this.toStringDetails();
     }
     if (inputs.length) {
       str += ' ' + inputs.join(', ');
     }
     return str;
@@ -20736,17 +21781,17 @@ var createName = function createName(nam
               multiname = buildMultiname(bc.index);
               object = pop();
               push(getSuper(savedScope(), object, multiname, bc.ti));
               break;
             case 5:
               value = pop();
               multiname = buildMultiname(bc.index);
               object = pop();
-              push(setSuper(savedScope(), object, multiname, value, bc.ti));
+              setSuper(savedScope(), object, multiname, value, bc.ti);
               break;
             case 241:
             case 240:
               break;
             case 64:
               push(callPure(createFunctionCallee, null, [
                 constant(methods[bc.index]),
                 topScope(),
@@ -23235,17 +24280,17 @@ function executeScript(script) {
   var abc = script.abc;
   true;
   var global = new Global(script);
   if (abc.applicationDomain.allowNatives) {
     global[Multiname.getPublicQualifiedName('unsafeJSNative')] = getNative;
   }
   script.executing = true;
   var scope = new Scope(null, script.global);
-  createFunction(script.init, scope).call(script.global);
+  createFunction(script.init, scope).call(script.global, false);
   script.executed = true;
 }
 function ensureScriptIsExecuted(script, reason) {
   if (!script.executed && !script.executing) {
     if (traceExecution.value >= 2) {
       print('Executing Script For: ' + reason);
     }
     executeScript(script);
@@ -24054,17 +25099,17 @@ var Class = function () {
         if (!buildClass) {
           unexpected('No native for ' + ci.native.cls);
         }
         if (!baseClass) {
           scope = new Scope(scope, Class);
         }
       }
       var classScope = new Scope(scope, null);
-      var instanceConstructor = createFunction(ii.init, classScope);
+      var instanceConstructor = createFunction(ii.init, classScope, false);
       var cls;
       if (isNativeClass) {
         cls = buildClass(domain, classScope, instanceConstructor, baseClass);
       } else {
         cls = new Class(className, instanceConstructor);
       }
       cls.className = className;
       cls.classInfo = classInfo;
@@ -25306,31 +26351,40 @@ function asCallSuper(scope, namespaces, 
   var method = openMethods[resolved];
   var result = method.apply(this, args);
   traceCallExecution.value > 0 && callWriter.leave('return ' + toSafeString(result));
   return result;
 }
 function asSetSuper(scope, namespaces, name, flags, value) {
   if (traceCallExecution.value) {
     var receiver = this.class ? this.class.className + ' ' : '';
-    callWriter.enter('set super ' + receiver + name + '(' + toSafeArrayString(args) + ') #' + callCounter.count(name));
+    callWriter.enter('set super ' + receiver + name + '(' + toSafeString(value) + ') #' + callCounter.count(name));
   }
   var baseClass = scope.object.baseClass;
   var resolved = baseClass.traitsPrototype.resolveMultinameProperty(namespaces, name, flags);
-  baseClass.traitsPrototype[VM_OPEN_SET_METHOD_PREFIX + resolved].call(this, value);
+  if (this[VM_SLOTS].byQN[resolved]) {
+    this.asSetProperty(namespaces, name, flags, value);
+  } else {
+    baseClass.traitsPrototype[VM_OPEN_SET_METHOD_PREFIX + resolved].call(this, value);
+  }
   traceCallExecution.value > 0 && callWriter.leave('');
 }
 function asGetSuper(scope, namespaces, name, flags) {
   if (traceCallExecution.value) {
     var receiver = this.class ? this.class.className + ' ' : '';
     callWriter.enter('get super ' + receiver + name + ' #' + callCounter.count(name));
   }
   var baseClass = scope.object.baseClass;
   var resolved = baseClass.traitsPrototype.resolveMultinameProperty(namespaces, name, flags);
-  var result = baseClass.traitsPrototype[VM_OPEN_GET_METHOD_PREFIX + resolved].call(this);
+  var result;
+  if (this[VM_SLOTS].byQN[resolved]) {
+    result = this.asGetProperty(namespaces, name, flags);
+  } else {
+    result = baseClass.traitsPrototype[VM_OPEN_GET_METHOD_PREFIX + resolved].call(this);
+  }
   traceCallExecution.value > 0 && callWriter.leave('return ' + toSafeString(result));
   return result;
 }
 function construct(constructor, args) {
   if (constructor.classInfo) {
     var qn = constructor.classInfo.instanceInfo.name.qualifiedName;
     if (qn === Multiname.String) {
       return String.apply(null, args);
@@ -26198,17 +27252,17 @@ function getTraitFunction(trait, scope, 
           warning('Calling undefined native method: ' + trait.kindName() + ' ' + mi.holder.name + '::' + Multiname.getQualifiedName(mi.name));
         };
       }(mi);
     }
   } else {
     if (traceExecution.value >= 2) {
       print('Creating Function For Trait: ' + trait.holder + ' ' + trait);
     }
-    fn = createFunction(mi, scope);
+    fn = createFunction(mi, scope, false);
     true;
   }
   if (traceExecution.value >= 3) {
     print('Made Function: ' + Multiname.getQualifiedName(mi.name));
   }
   return fn;
 }
 function makeQualifiedNameTraitMap(traits) {
@@ -26243,17 +27297,17 @@ function createClass(classInfo, baseClas
   domain.onMessage.notify1('classCreated', cls);
   if (cls.instanceConstructor && cls !== Class) {
     cls.verify();
   }
   if (baseClass && (Multiname.getQualifiedName(baseClass.classInfo.instanceInfo.name.name) === 'Proxy' || baseClass.isProxy)) {
     installProxyClassWrapper(cls);
     cls.isProxy = true;
   }
-  createFunction(classInfo.init, scope).call(cls);
+  createFunction(classInfo.init, scope, false).call(cls);
   if (sealConstTraits) {
     this.sealConstantTraits(cls, ci.traits);
   }
   return cls;
 }
 function sealConstantTraits(object, traits) {
   for (var i = 0, j = traits.length; i < j; i++) {
     var trait = traits[i];
@@ -26483,16 +27537,23 @@ VM_METHOD_OVERRIDES['com.midasplayer.deb
   console.log(msg);
 };
 VM_METHOD_OVERRIDES['com.midasplayer.engine.comm.DebugGameComm::getGameData'] = function () {
   return '<gamedata randomseed="554884453" version="1">\n<musicOn>true</musicOn>\n<soundOn>true</soundOn>\n<isShortGame>false</isShortGame>\n<booster_1>0</booster_1>\n<booster_2>0</booster_2>\n<booster_3>0</booster_3>\n<booster_4>0</booster_4>\n<booster_5>0</booster_5>\n<bestScore>0</bestScore>\n<bestChain>0</bestChain>\n<bestLevel>0</bestLevel>\n<bestCrushed>0</bestCrushed>\n<bestMixed>0</bestMixed>\n<text id="outro.crushed">Candy crushed</text>\n<text id="outro.bestever">best ever</text>\n<text id="outro.trophy.two">scored {0} in one game</text>\n<text id="outro.combo_color_color">All Clear Created</text>\n<text id="outro.trophy.one">crushed {0} candy in one game</text>\n<text id="outro.score">Score</text>\n<text id="outro.opengame">Please register to play the full game</text>\n<text id="outro.chain">Longest chain</text>\n<text id="outro.time">Game ends in {0} seconds</text>\n<text id="outro.combo_color_line">Super Stripes Created</text>\n<text id="game.nomoves">No more moves!</text>\n<text id="outro.combo_wrapper_line">Mega-Candy Created</text>\n<text id="intro.time">Game starts in {0} seconds</text>\n<text id="outro.now">now</text>\n<text id="outro.level">Level reached</text>\n<text id="outro.title">Game Over</text>\n<text id="intro.info1">Match 3 Candy of the same colour to crush them. Matching 4 or 5 in different formations generates special sweets that are extra tasty.</text>\n<text id="intro.info2">You can also combine the special sweets for additional effects by switching them with each other. Try these combinations for a taste you will not forget: </text>\n<text id="outro.combo_color_wrapper">Double Colour Bombs Created</text>\n<text id="outro.trophy.three">made {0} combined candy in one game</text>\n<text id="intro.title">Play like this:</text>\n</gamedata>';
 };
 VM_METHOD_OVERRIDES['com.antkarlov.Preloader::com.antkarlov:Preloader.isUrl'] = function () {
   return true;
 };
+VM_METHOD_OVERRIDES['static com.demonsters.debugger.MonsterDebugger::initialize'] = function () {
+};
+VM_METHOD_OVERRIDES['com.spilgames.api.core.tracking.TrackConfig::getTrackers'] = function () {
+  return [];
+};
+VM_METHOD_OVERRIDES['com.spilgames.api.components.TextFields.AutoFitTextFieldEx::com.spilgames.api.components.TextFields:AutoFitTextFieldEx.updateProperties'] = VM_METHOD_OVERRIDES['com.spilgames.api.components.TextFields.AutoFitTextFieldEx::com.spilgames.api.components.TextFields:AutoFitTextFieldEx.updateTextSize'] = function () {
+};
 function asCheckVectorSetNumericProperty(i, length, fixed) {
   if (i < 0 || i > length || i === length && fixed || !isNumeric(i)) {
     throwError('RangeError', Errors.OutOfRangeError, i, length);
   }
 }
 function asCheckVectorGetNumericProperty(i, length) {
   if (i < 0 || i >= length || !isNumeric(i)) {
     throwError('RangeError', Errors.OutOfRangeError, i, length);
@@ -27688,17 +28749,17 @@ var isXMLType, isXMLName, XMLParser;
           if (!isWs || isWhitespacePreserved()) {
             sink.text(resolveEntities(text), isWs);
           }
         }
         i = j;
       }
     }
     this.parseFromString = function (s, mimeType) {
-      var currentElement = new XML('element');
+      var currentElement = new XML('element', '', '', '');
       var elementsStack = [];
       parseXml(s, {
         beginElement: function (name, attrs, scope, isEmpty) {
           var parent = currentElement;
           elementsStack.push(parent);
           currentElement = createNode('element', name.namespace, name.localName, name.prefix);
           for (var i = 0; i < attrs.length; ++i) {
             var attr = createNode('attribute', attrs[i].name.namespace, attrs[i].name.localName, attrs[i].name.prefix);
@@ -28460,17 +29521,17 @@ var isXMLType, isXMLName, XMLParser;
         },
         insertChildAfter: function insertChildAfter(child1, child2) {
           notImplemented('XML.insertChildAfter');
         },
         insertChildBefore: function insertChildBefore(child1, child2) {
           notImplemented('XML.insertChildBefore');
         },
         localName: function localName() {
-          notImplemented('XML.localName');
+          return this.name.localName;
         },
         name: function name() {
           return this.name;
         },
         _namespace: function _namespace(prefix, argc) {
           somewhatImplemented('XML._namespace()');
           return this.name.uri;
         },
@@ -30657,232 +31718,36 @@ var natives = function () {
       return c;
     }
     function constant(x) {
       return function () {
         return x;
       };
     }
     function ByteArrayClass(runtime, scope, instanceConstructor, baseClass) {
-      var INITIAL_SIZE = 128;
-      var defaultObjectEncoding = 3;
-      function ByteArray(bytes) {
-        if (bytes instanceof ByteArray) {
-          return bytes;
-        }
-        var initData = bytes || this.symbol && this.symbol.data;
-        if (initData) {
-          this.a = new ArrayBuffer(initData.length);
-          this.length = initData.length;
-          new Uint8Array(this.a).set(initData);
-        } else {
-          this.a = new ArrayBuffer(INITIAL_SIZE);
-          this.length = 0;
-        }
-        this.position = 0;
-        this.cacheViews();
-        this.nativele = new Int8Array(new Int32Array([]).buffer)[0] === 1;
-        this.le = this.nativele;
-        this.objectEncoding = defaultObjectEncoding;
-      }
-      function throwEOFError() {
-        runtime.throwErrorFromVM('flash.errors.EOFError', 'End of file was encountered.');
-      }
-      function throwRangeError() {
-        var error = Errors.ParamRangeError;
-        runtime.throwErrorFromVM('RangeError', getErrorMessage(error.code), error.code);
-      }
-      function checkRange(x, min, max) {
-        if (x !== clamp(x, min, max)) {
-          throwRangeError();
-        }
-      }
-      function get(b, m, size) {
-        if (b.position + size > b.length) {
-          throwEOFError();
-        }
-        var v = b.view[m](b.position, b.le);
-        b.position += size;
-        return v;
-      }
-      function set(b, m, size, v) {
-        var len = b.position + size;
-        b.ensureCapacity(len);
-        b.view[m](b.position, v, b.le);
-        b.position = len;
-        if (len > b.length) {
-          b.length = len;
-        }
-      }
-      var c = new Class('ByteArray', ByteArray, C(ByteArray));
+      var BA = function () {
+        ByteArray.call(this);
+      };
+      var BAp = BA.prototype = Object.create(ByteArray.prototype);
+      var c = new Class('ByteArray', BA, C(BA));
       c.extendBuiltin(baseClass);
-      var BAp = ByteArray.prototype;
       BAp.asGetNumericProperty = function (i) {
         if (i >= this.length) {
           return undefined;
         }
         return this.uint8v[i];
       };
       BAp.asSetNumericProperty = function (i, v) {
         var len = i + 1;
         this.ensureCapacity(len);
         this.uint8v[i] = v;
         if (len > this.length) {
           this.length = len;
         }
       };
-      BAp.cacheViews = function cacheViews() {
-        var a = this.a;
-        this.int8v = new Int8Array(a);
-        this.uint8v = new Uint8Array(a);
-        this.view = new DataView(a);
-      };
-      BAp.getBytes = function getBytes() {
-        return new Uint8Array(this.a, 0, this.length);
-      };
-      BAp.ensureCapacity = function ensureCapacity(size) {
-        var origa = this.a;
-        if (origa.byteLength < size) {
-          var newSize = origa.byteLength;
-          while (newSize < size) {
-            newSize *= 2;
-          }
-          var copya = new ArrayBuffer(newSize);
-          var origv = this.int8v;
-          this.a = copya;
-          this.cacheViews();
-          this.int8v.set(origv);
-        }
-      };
-      BAp.clear = function clear() {
-        this.length = 0;
-        this.position = 0;
-      };
-      BAp.readBoolean = function readBoolean() {
-        if (this.position + 1 > this.length) {
-          throwEOFError();
-        }
-        return this.int8v[this.position++] !== 0;
-      };
-      BAp.readByte = function readByte() {
-        if (this.position + 1 > this.length) {
-          throwEOFError();
-        }
-        return this.int8v[this.position++];
-      };
-      BAp.readUnsignedByte = function readUnsignedByte() {
-        if (this.position + 1 > this.length) {
-          throwEOFError();
-        }
-        return this.uint8v[this.position++];
-      };
-      BAp.readBytes = function readBytes(bytes, offset, length) {
-        var pos = this.position;
-        if (pos + length > this.length) {
-          throwEOFError();
-        }
-        if (bytes.length < offset + length) {
-          bytes.ensureCapacity(offset + length);
-          bytes.length = offset + length;
-        }
-        bytes.int8v.set(new Int8Array(this.a, pos, length), offset);
-        this.position += length;
-      };
-      BAp.writeBoolean = function writeBoolean(v) {
-        var len = this.position + 1;
-        this.ensureCapacity(len);
-        this.int8v[this.position++] = v ? 1 : 0;
-        if (len > this.length) {
-          this.length = len;
-        }
-      };
-      BAp.writeByte = function writeByte(v) {
-        var len = this.position + 1;
-        this.ensureCapacity(len);
-        this.int8v[this.position++] = v;
-        if (len > this.length) {
-          this.length = len;
-        }
-      };
-      BAp.writeUnsignedByte = function writeUnsignedByte(v) {
-        var len = this.position + 1;
-        this.ensureCapacity(len);
-        this.uint8v[this.position++] = v;
-        if (len > this.length) {
-          this.length = len;
-        }
-      };
-      BAp.writeRawBytes = function writeRawBytes(bytes) {
-        var len = this.position + bytes.length;
-        this.ensureCapacity(len);
-        this.int8v.set(bytes, this.position);
-        this.position = len;
-        if (len > this.length) {
-          this.length = len;
-        }
-      };
-      BAp.readRawBytes = function readRawBytes() {
-        return new Int8Array(this.a, 0, this.length);
-      };
-      BAp.writeBytes = function writeBytes(bytes, offset, length) {
-        if (arguments.length < 2) {
-          offset = 0;
-        }
-        if (arguments.length < 3) {
-          length = 0;
-        }
-        checkRange(offset, 0, bytes.length);
-        checkRange(offset + length, 0, bytes.length);
-        if (length === 0) {
-          length = bytes.length - offset;
-        }
-        this.writeRawBytes(new Int8Array(bytes.a, offset, length));
-      };
-      BAp.readDouble = function readDouble() {
-        return get(this, 'getFloat64', 8);
-      };
-      BAp.readFloat = function readFloat() {
-        return get(this, 'getFloat32', 4);
-      };
-      BAp.readInt = function readInt() {
-        return get(this, 'getInt32', 4);
-      };
-      BAp.readShort = function readShort() {
-        return get(this, 'getInt16', 2);
-      };
-      BAp.readUnsignedInt = function readUnsignedInt() {
-        return get(this, 'getUint32', 4);
-      };
-      BAp.readUnsignedShort = function readUnsignedShort() {
-        return get(this, 'getUint16', 2);
-      };
-      BAp.readObject = function readObject() {
-        return AMFUtils.encodings[this.objectEncoding].read(this);
-      };
-      BAp.writeDouble = function writeDouble(v) {
-        set(this, 'setFloat64', 8, v);
-      };
-      BAp.writeFloat = function writeFloat(v) {
-        set(this, 'setFloat32', 4, v);
-      };
-      BAp.writeInt = function writeInt(v) {
-        set(this, 'setInt32', 4, v);
-      };
-      BAp.writeShort = function writeShort(v) {
-        set(this, 'setInt16', 2, v);
-      };
-      BAp.writeUnsignedInt = function writeUnsignedInt(v) {
-        set(this, 'setUint32', 4, v);
-      };
-      BAp.writeUnsignedShort = function writeUnsignedShort(v) {
-        set(this, 'setUint16', 2, v);
-      };
-      BAp.writeObject = function readObject(v) {
-        return AMFUtils.encodings[this.objectEncoding].write(this, v);
-      };
       BAp.readUTF = function readUTF() {
         return this.readUTFBytes(this.readShort());
       };
       BAp.readUTFBytes = function readUTFBytes(length) {
         var pos = this.position;
         if (pos + length > this.length) {
           throwEOFError();
         }
@@ -30940,52 +31805,58 @@ var natives = function () {
           objectEncoding: {
             get: function () {
               return this.objectEncoding;
             },
             set: function (v) {
               this.objectEncoding = v;
             }
           },
-          readBytes: BAp.readBytes,
           writeBytes: BAp.writeBytes,
           writeBoolean: BAp.writeBoolean,
           writeByte: BAp.writeByte,
           writeShort: BAp.writeShort,
           writeInt: BAp.writeInt,
           writeUnsignedInt: BAp.writeUnsignedInt,
           writeFloat: BAp.writeFloat,
           writeDouble: BAp.writeDouble,
           writeMultiByte: BAp.writeMultiByte,
+          writeObject: function writeObject(v) {
+            return AMFUtils.encodings[this.objectEncoding].write(this, v);
+          },
           writeUTF: BAp.writeUTF,