Merge m-c to inbound
authorWes Kocher <wkocher@mozilla.com>
Thu, 09 Jan 2014 17:05:12 -0800
changeset 162904 fb5e82e717088d07490213376a2eaa3b47e9d153
parent 162903 b02827c3d07d584b1d58ec9e72999db5c050f89b (current diff)
parent 162816 37516445a0b58a662a5fcd8734db0b1c8b633be9 (diff)
child 162905 5fc335a9b782c6e8b4235a1964ed3bd0310bcc3d
push id25975
push userryanvm@gmail.com
push dateFri, 10 Jan 2014 19:46:47 +0000
treeherderautoland@e89afc241513 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone29.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge m-c to inbound
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
--- 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/b2g/components/FilePicker.js
+++ b/b2g/components/FilePicker.js
@@ -27,17 +27,17 @@ const VIDEO_FILTERS = ['video/mpeg', 'vi
                        'video/quicktime', 'video/webm', 'video/x-matroska',
                        'video/x-ms-wmv', 'video/x-flv'];
 const AUDIO_FILTERS = ['audio/basic', 'audio/L24', 'audio/mp4',
                        'audio/mpeg', 'audio/ogg', 'audio/vorbis',
                        'audio/vnd.rn-realaudio', 'audio/vnd.wave',
                        'audio/webm'];
 
 Cu.import('resource://gre/modules/XPCOMUtils.jsm');
-Cu.import("resource://gre/modules/FileUtils.jsm");
+Cu.import("resource://gre/modules/osfile.jsm");
 
 XPCOMUtils.defineLazyServiceGetter(this, 'cpmm',
                                    '@mozilla.org/childprocessmessagemanager;1',
                                    'nsIMessageSender');
 
 function FilePicker() {
 }
 
@@ -186,20 +186,18 @@ FilePicker.prototype = {
     if (!name &&
         (data.result.blob instanceof this.mParent.File) &&
         data.result.blob.name) {
       name = data.result.blob.name;
     }
 
     // Let's try to remove the full path and take just the filename.
     if (name) {
-      let file = new FileUtils.File(data.result.blob.name);
-      if (file && file.leafName) {
-        name = file.leafName;
-      }
+      let names = OS.Path.split(name);
+      name = names.components[names.components.length - 1];
     }
 
     // the fallback is a filename composed by 'blob' + extension.
     if (!name) {
       name = 'blob';
       if (data.result.blob.type) {
         let mimeSvc = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService);
         let mimeInfo = mimeSvc.getFromTypeAndExtension(data.result.blob.type, '');
new file mode 100644
--- /dev/null
+++ b/b2g/components/test/mochitest/filepicker_path_handler_chrome.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';
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+
+// use ppmm to handle file-picker message.
+let ppmm = Cc['@mozilla.org/parentprocessmessagemanager;1']
+             .getService(Ci.nsIMessageListenerManager);
+
+let pickResult = null;
+
+function processPickMessage(message) {
+  let sender = message.target.QueryInterface(Ci.nsIMessageSender);
+  // reply FilePicker's message
+  sender.sendAsyncMessage('file-picked', pickResult);
+  // notify caller
+  sendAsyncMessage('file-picked-posted', { type: 'file-picked-posted' });
+}
+
+function updatePickResult(result) {
+  pickResult = result;
+  sendAsyncMessage('pick-result-updated', { type: 'pick-result-updated' });
+}
+
+ppmm.addMessageListener('file-picker', processPickMessage);
+// use update-pick-result to change the expected pick result.
+addMessageListener('update-pick-result', updatePickResult);
--- a/b2g/components/test/mochitest/mochitest.ini
+++ b/b2g/components/test/mochitest/mochitest.ini
@@ -1,7 +1,9 @@
 [DEFAULT]
 run-if = toolkit == "gonk"
 support-files =
   permission_handler_chrome.js
   SandboxPromptTest.html
+  filepicker_path_handler_chrome.js
 
 [test_sandbox_permission.html]
+[test_filepicker_path.html]
new file mode 100644
--- /dev/null
+++ b/b2g/components/test/mochitest/test_filepicker_path.html
@@ -0,0 +1,135 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=949944
+-->
+<head>
+<meta charset="utf-8">
+<title>Permission Prompt Test</title>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+</head>
+<body onload="processTestCase()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=949944"> [B2G][Helix][Browser][Wallpaper] use new File([blob], filename) to return a blob with filename when picking</a>
+<script type="application/javascript">
+
+'use strict';
+
+var testCases = [
+  // case 1: returns blob with name
+  { pickedResult: { success: true,
+                    result: {
+                              type: 'text/plain',
+                              blob: new Blob(['1234567890'],
+                                             { type: 'text/plain' }),
+                              name: 'test1.txt'
+                            }
+                },
+    fileName: 'test1.txt' },
+  // case 2: returns blob without name
+  { pickedResult: { success: true,
+                    result: {
+                              type: 'text/plain',
+                              blob: new Blob(['1234567890'],
+                                             { type: 'text/plain' })
+                            }
+                },
+    fileName: 'blob.txt' },
+  // case 3: returns blob with full path name
+  { pickedResult: { success: true,
+                    result: {
+                              type: 'text/plain',
+                              blob: new Blob(['1234567890'],
+                                             { type: 'text/plain' }),
+                              name: '/full/path/test3.txt'
+                            }
+                },
+    fileName: 'test3.txt' },
+  // case 4: returns blob relative path name
+  { pickedResult: { success: true,
+                    result: {
+                              type: 'text/plain',
+                              blob: new Blob(['1234567890'],
+                                             { type: 'text/plain' }),
+                              name: 'relative/path/test4.txt'
+                            }
+                },
+    fileName: 'test4.txt' },
+  // case 5: returns file with name
+  { pickedResult: { success: true,
+                    result: {
+                              type: 'text/plain',
+                              blob: new File(['1234567890'],
+                                             'useless-name.txt',
+                                             { type: 'text/plain' }),
+                              name: 'test5.txt'
+                            }
+                },
+    fileName: 'test5.txt'},
+  // case 6: returns file without name. This case may fail because we
+  //         need to make sure the DOMFile can be sent through
+  //         sendAsyncMessage API.
+  { pickedResult: { success: true,
+                    result: {
+                              type: 'text/plain',
+                              blob: new File(['1234567890'],
+                                             'test6.txt',
+                                             { type: 'text/plain' })
+                            }
+                },
+    todo: true,
+    fileName: 'test6.txt'}
+];
+
+var chromeJS = SimpleTest.getTestFileURL('filepicker_path_handler_chrome.js');
+var chromeScript = SpecialPowers.loadChromeScript(chromeJS);
+var activeTestCase;
+
+chromeScript.addMessageListener('pick-result-updated', handleMessage);
+chromeScript.addMessageListener('file-picked-posted', handleMessage);
+
+// handle messages returned from chromeScript
+function handleMessage(data) {
+  var fileInput = document.getElementById('fileInput');
+  switch (data.type) {
+    case 'pick-result-updated':
+      fileInput.click();
+      break;
+    case 'file-picked-posted':
+      if (activeTestCase.todo) {
+        todo_is(fileInput.value, activeTestCase.fileName,
+                'DOMFile should be able to send through message.');
+      } else {
+        is(fileInput.value, activeTestCase.fileName);
+      }
+      processTestCase();
+      break;
+  }
+}
+
+function processTestCase() {
+  if (!testCases.length) {
+    SimpleTest.finish();
+    return;
+  }
+  activeTestCase = testCases.shift();
+  var expectedResult = activeTestCase.pickedResult;
+  if (navigator.userAgent.indexOf('Windows') > -1 &&
+      expectedResult.result.name) {
+    // If we run at a window box, we need to translate the path from '/' to '\\'
+    var name = expectedResult.result.name;
+    name = name.replace('/', '\\');
+    // If the name is an absolute path, we need to prepend drive letter.
+    if (name.startsWith('\\')) {
+      name = 'C:' + name;
+    }
+    // update the expected name.
+    expectedResult.result.name = name
+  }
+  chromeScript.sendAsyncMessage('update-pick-result', expectedResult);
+}
+
+</script>
+<input type="file" id="fileInput">
+</body>
+</html>
\ No newline at end of file
--- a/b2g/config/gaia.json
+++ b/b2g/config/gaia.json
@@ -1,4 +1,4 @@
 {
-    "revision": "6e971c6a9f947d301a0401fcbc87141b8eba23b6", 
+    "revision": "f1f4304e9f2fe7bcf79b1e6ead334706c119ac4a", 
     "repo_path": "/integration/gaia-central"
 }
--- 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;
-        }