keep going
authorDave Camp <dcamp@mozilla.com>
Sun, 21 Jul 2013 10:12:37 -0700
changeset 16 2f71eb90cf95f8944ba6cf67834c92e3bb46f5b1
parent 15 707fea7a45e804f73efbc0afe60a85be356b71bf
child 17 482f78b4d3460c9a0a8eaafaa5cae39393e49f0c
push id17
push userdcamp@campd.org
push dateSun, 21 Jul 2013 17:12:42 +0000
keep going
android-browser.diff
copy-html.diff
inspector-panel-default-node.diff
inspector-remote-delete-node.diff
promise-return.diff
remote-edit-attributes.diff
remote-edit-value.diff
remote-markup.diff
search-box-remote.diff
series
style-actor.diff
walker-reload.diff
warning-fixes.diff
window-targets.diff
--- a/android-browser.diff
+++ b/android-browser.diff
@@ -1,22 +1,22 @@
 # HG changeset patch
 # User Dave Camp <dcamp@mozilla.com>
 # Date 1372178155 25200
 #      Tue Jun 25 09:35:55 2013 -0700
-# Node ID aad4f3eb5b6c8fee5ab2e94c17c784d4ac9bcc7d
-# Parent  00ddc3161ebd3ab028c7344c198696f9f0c0d27b
+# Node ID db733687ce263d6c9683464df3ca65d632d52687
+# Parent  4502bf53830e85ec59f874d2856695ab53de8a40
 Bug 888492 - Use the tab actor's _browser rather than _tabbrowser for load notifications. r=jwalker
 
 diff --git a/toolkit/devtools/server/actors/inspector.js b/toolkit/devtools/server/actors/inspector.js
 --- a/toolkit/devtools/server/actors/inspector.js
 +++ b/toolkit/devtools/server/actors/inspector.js
-@@ -1984,17 +1984,17 @@ var InspectorActor = protocol.ActorClass
-   getWalker: method(function(options={}) {
+@@ -2034,17 +2034,17 @@ var InspectorActor = protocol.ActorClass
      let deferred = promise.defer();
+     this._walkerPromise = deferred.promise;
  
      let window = this.window;
  
      var domReady = () => {
        let tabActor = this.tabActor;
        window.removeEventListener("DOMContentLoaded", domReady, true);
 -      deferred.resolve(WalkerActor(this.conn, window.document, tabActor._tabbrowser, options));
 +      deferred.resolve(WalkerActor(this.conn, window.document, tabActor._browser, options));
deleted file mode 100644
--- a/copy-html.diff
+++ /dev/null
@@ -1,58 +0,0 @@
-# HG changeset patch
-# User Dave Camp <dcamp@mozilla.com>
-# Date 1371477175 25200
-#      Mon Jun 17 06:52:55 2013 -0700
-# Node ID a5aab8dafa96ddb3551c06ffcd701ba122a63bce
-# Parent  c62a389a817a1fdf7126044c2ac535a6c91723aa
-imported patch copy-html.diff
-
-diff --git a/browser/devtools/inspector/inspector-panel.js b/browser/devtools/inspector/inspector-panel.js
---- a/browser/devtools/inspector/inspector-panel.js
-+++ b/browser/devtools/inspector/inspector-panel.js
-@@ -630,34 +630,38 @@ InspectorPanel.prototype = {
-   /**
-    * Copy the innerHTML of the selected Node to the clipboard.
-    */
-   copyInnerHTML: function InspectorPanel_copyInnerHTML()
-   {
-     if (!this.selection.isNode()) {
-       return;
-     }
--    let toCopy = this.selection.node.innerHTML;
--    if (toCopy) {
--      clipboardHelper.copyString(toCopy);
--    }
-+    this._copyLongStr(this.walker.innerHTML(this.selection.nodeFront));
-   },
- 
-   /**
-    * Copy the outerHTML of the selected Node to the clipboard.
-    */
-   copyOuterHTML: function InspectorPanel_copyOuterHTML()
-   {
-     if (!this.selection.isNode()) {
-       return;
-     }
--    let toCopy = this.selection.node.outerHTML;
--    if (toCopy) {
--      clipboardHelper.copyString(toCopy);
--    }
-+
-+    this._copyLongStr(this.walker.outerHTML(this.selection.nodeFront));
-+  },
-+
-+  _copyLongStr: function(promise) {
-+    return promise.then(longstr => {
-+      return longstr.string().then(toCopy => {
-+        longstr.release().then(null, console.error);
-+        clipboardHelper.copyString(toCopy);
-+      });
-+    }).then(null, console.error);
-   },
- 
-   /**
-    * Copy a unique selector of the selected Node to the clipboard.
-    */
-   copyUniqueSelector: function InspectorPanel_copyUniqueSelector()
-   {
-     if (!this.selection.isNode()) {
deleted file mode 100644
--- a/inspector-panel-default-node.diff
+++ /dev/null
@@ -1,85 +0,0 @@
-# HG changeset patch
-# User Dave Camp <dcamp@mozilla.com>
-# Date 1371477174 25200
-#      Mon Jun 17 06:52:54 2013 -0700
-# Node ID c62a389a817a1fdf7126044c2ac535a6c91723aa
-# Parent  dc0232e8d114bb05987546bfcebdeee2f082b671
-imported patch inspector-panel-default-node.diff
-
-diff --git a/browser/devtools/inspector/inspector-panel.js b/browser/devtools/inspector/inspector-panel.js
---- a/browser/devtools/inspector/inspector-panel.js
-+++ b/browser/devtools/inspector/inspector-panel.js
-@@ -152,24 +152,34 @@ InspectorPanel.prototype = {
- 
-     return deferred.promise;
-   },
- 
-   /**
-    * Return a promise that will resolve to the default node for selection.
-    */
-   _getDefaultNodeForSelection : function() {
-+    if (this._defaultNode) {
-+      return this._defaultNode;
-+    }
-+    let walker = this.walker;
-     // if available set body node as default selected node
-     // else set documentElement
--    return this.walker.querySelector(this.walker.rootNode, "body").then(front => {
-+    return walker.querySelector(this.walker.rootNode, "body").then(front => {
-       if (front) {
-         return front;
-       }
-       return this.walker.documentElement(this.walker.rootNode);
--    });
-+    }).then(node => {
-+      if (walker !== this.walker) {
-+        promise.reject(null);
-+      }
-+      this._defaultNode = node;
-+      return node;
-+    })
-   },
- 
-   /**
-    * Selection object (read only)
-    */
-   get selection() {
-     return this._selection;
-   },
-@@ -271,16 +281,17 @@ InspectorPanel.prototype = {
- 
-   /**
-    * Reset the inspector on navigate away.
-    */
-   onNavigatedAway: function InspectorPanel_onNavigatedAway(event, payload) {
-     let newWindow = payload._navPayload || payload;
-     this.walker.release().then(null, console.error);
-     this.walker = null;
-+    this._defaultNode = null;
-     this.selection.setNodeFront(null);
-     this.selection.setWalker(null);
-     this._destroyMarkup();
-     this.isDirty = false;
- 
-     this.target.inspector.getWalker().then(walker => {
-       if (this._destroyPromise) {
-         walker.release().then(null, console.error);
-@@ -386,17 +397,17 @@ InspectorPanel.prototype = {
-   },
- 
-   /**
-    * When a node is deleted, select its parent node.
-    */
-   onDetached: function InspectorPanel_onDetached(event, parentNode) {
-     this.cancelLayoutChange();
-     this.breadcrumbs.cutAfter(this.breadcrumbs.indexOf(parentNode));
--    this.selection.setNodeFront(parentNode, "detached");
-+    this.selection.setNodeFront(parentNode ? parentNode : this._defaultNode, "detached");
-   },
- 
-   /**
-    * Destroy the inspector.
-    */
-   destroy: function InspectorPanel__destroy() {
-     if (this._destroyPromise) {
-       return this._destroyPromise;
deleted file mode 100644
--- a/inspector-remote-delete-node.diff
+++ /dev/null
@@ -1,132 +0,0 @@
-# HG changeset patch
-# User Dave Camp <dcamp@mozilla.com>
-# Date 1371477175 25200
-#      Mon Jun 17 06:52:55 2013 -0700
-# Node ID 00ddc3161ebd3ab028c7344c198696f9f0c0d27b
-# Parent  f2cb449dbdfbb5f006856d91943417c22bbd230e
-imported patch inspector-remote-delete-node.diff
-
-diff --git a/browser/devtools/inspector/inspector-panel.js b/browser/devtools/inspector/inspector-panel.js
---- a/browser/devtools/inspector/inspector-panel.js
-+++ b/browser/devtools/inspector/inspector-panel.js
-@@ -685,18 +685,17 @@ InspectorPanel.prototype = {
-     }
- 
-     // If the markup panel is active, use the markup panel to delete
-     // the node, making this an undoable action.
-     if (this.markup) {
-       this.markup.deleteNode(this.selection.nodeFront);
-     } else {
-       // remove the node from content
--      let parent = this.selection.node.parentNode;
--      parent.removeChild(this.selection.node);
-+      this.walker.removeNode(this.selection.nodeFront);
-     }
-   },
- 
-   /**
-    * Schedule a low-priority change event for things like paint
-    * and resize.
-    */
-   scheduleLayoutChange: function Inspector_scheduleLayoutChange()
-diff --git a/browser/devtools/inspector/test/browser_inspector_menu.js b/browser/devtools/inspector/test/browser_inspector_menu.js
---- a/browser/devtools/inspector/test/browser_inspector_menu.js
-+++ b/browser/devtools/inspector/test/browser_inspector_menu.js
-@@ -98,17 +98,17 @@ 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.selection.once("detached", deleteTest);
-+    inspector.once("markupmutation", deleteTest);
- 
-     let commandEvent = document.createEvent("XULCommandEvent");
-     commandEvent.initCommandEvent("command", true, true, window, 0, false, false,
-                                   false, false, null);
-     deleteNode.dispatchEvent(commandEvent);
-   }
- 
-   function deleteTest() {
-diff --git a/browser/devtools/markupview/markup-view.js b/browser/devtools/markupview/markup-view.js
---- a/browser/devtools/markupview/markup-view.js
-+++ b/browser/devtools/markupview/markup-view.js
-@@ -225,38 +225,38 @@ MarkupView.prototype = {
-   },
- 
-   /**
-    * Delete a node from the DOM.
-    * This is an undoable action.
-    */
-   deleteNode: function MC__deleteNode(aNode)
-   {
--    aNode = aNode.rawNode();
--    if (!aNode) {
--      return;
--    }
--    let doc = nodeDocument(aNode);
--    if (aNode === doc ||
--        aNode === doc.documentElement ||
-+    if (aNode.isDocumentElement ||
-         aNode.nodeType == Ci.nsIDOMNode.DOCUMENT_TYPE_NODE) {
-       return;
-     }
- 
--    let parentNode = aNode.parentNode;
--    let sibling = aNode.nextSibling;
-+    let container = this._containers.get(aNode);
- 
--    this.undo.do(function() {
--      if (aNode.selected) {
--        this.navigate(this._containers.get(parentNode));
--      }
--      parentNode.removeChild(aNode);
--    }.bind(this), function() {
--      parentNode.insertBefore(aNode, sibling);
--    });
-+    // Retain the node so we can undo this...
-+    this.walker.retainNode(aNode).then(() => {
-+      let parent = aNode.parentNode();
-+      let sibling = null;
-+      this.undo.do(() => {
-+        if (container.selected) {
-+          this.navigate(this._containers.get(parent));
-+        }
-+        this.walker.removeNode(aNode).then(nextSibling => {
-+          sibling = nextSibling;
-+        });
-+      }, () => {
-+        this.walker.insertBefore(aNode, parent, sibling);
-+      });
-+    }).then(null, console.error);
-   },
- 
-   /**
-    * If an editable item is focused, select its container.
-    */
-   _onFocus: function MC__onFocus(aEvent) {
-     let parent = aEvent.target;
-     while (!parent.container) {
-@@ -588,16 +588,19 @@ MarkupView.prototype = {
-     // centered node.
-     let centered = this._checkSelectionVisible(aContainer);
- 
-     // Children aren't updated yet, but clear the childrenDirty flag anyway.
-     // If the dirty flag is re-set while we're fetching we'll need to fetch
-     // again.
-     aContainer.childrenDirty = false;
-     let updatePromise = this._getVisibleChildren(aContainer, centered).then(children => {
-+      if (!this._containers) {
-+        return promise.reject("markup view destroyed");
-+      }
-       this._queuedChildUpdates.delete(aContainer);
- 
-       // If children are dirty, we got a change notification for this node
-       // while the request was in progress, we need to do it again.
-       if (aContainer.childrenDirty) {
-         return this._updateChildren(aContainer, centered);
-       }
- 
new file mode 100644
--- /dev/null
+++ b/promise-return.diff
@@ -0,0 +1,76 @@
+# HG changeset patch
+# User Dave Camp <dcamp@mozilla.com>
+# Date 1374270262 25200
+#      Fri Jul 19 14:44:22 2013 -0700
+# Node ID 05edf651c461763fb584de9a6b9ac87c1f71ec3f
+# Parent  a3432a61411e4585061f69e75b0a12d7324ecb11
+[mq]: promise-return.diff
+
+diff --git a/toolkit/devtools/server/main.js b/toolkit/devtools/server/main.js
+--- a/toolkit/devtools/server/main.js
++++ b/toolkit/devtools/server/main.js
+@@ -644,16 +644,18 @@ function DebuggerServerConnection(aPrefi
+ {
+   this._prefix = aPrefix;
+   this._transport = aTransport;
+   this._transport.hooks = this;
+   this._nextID = 1;
+ 
+   this._actorPool = new ActorPool(this);
+   this._extraPools = [];
++
++  this._actorResponses = new Map;
+ }
+ 
+ DebuggerServerConnection.prototype = {
+   _prefix: null,
+   get prefix() { return this._prefix },
+ 
+   _transport: null,
+   get transport() { return this._transport },
+@@ -817,29 +819,32 @@ DebuggerServerConnection.prototype = {
+     }
+ 
+     if (!ret) {
+       // This should become an error once we've converted every user
+       // of this to promises in bug 794078.
+       return;
+     }
+ 
+-    resolve(ret)
+-      .then(null, (e) => {
+-        return this._unknownError(
+-          "error occurred while processing '" + aPacket.type,
+-          e);
+-      })
+-      .then(function (aResponse) {
+-        if (!aResponse.from) {
+-          aResponse.from = aPacket.to;
+-        }
+-        return aResponse;
+-      })
+-      .then(this.transport.send.bind(this.transport));
++    let pendingResponse = this._actorResponses.get(actor.actorID) || promise.resolve(null);
++
++    let response = pendingResponse.then(() => {
++      return resolve(ret);
++    }).then(null, (e) => {
++      return this._unknownError(
++        "error occurred while processing '" + aPacket.type,
++        e);
++    }).then(function (aResponse) {
++      if (!aResponse.from) {
++        aResponse.from = aPacket.to;
++      }
++      return aResponse;
++    }).then(this.transport.send.bind(this.transport));
++    this._actorResponses.set(actorID, response);
++    return response;
+   },
+ 
+   /**
+    * Called by DebuggerTransport when the underlying stream is closed.
+    *
+    * @param aStatus nsresult
+    *        The status code that corresponds to the reason for closing
+    *        the stream.
deleted file mode 100644
--- a/remote-edit-attributes.diff
+++ /dev/null
@@ -1,325 +0,0 @@
-# HG changeset patch
-# User Dave Camp <dcamp@mozilla.com>
-# Date 1371477175 25200
-#      Mon Jun 17 06:52:55 2013 -0700
-# Node ID 6155b1a4a5a5b04ba68ea367f36721c00bf3f307
-# Parent 78398f24d7703ef98e71055b3d9db45731a55c31
-Bug 886035 - Port markup view attribute editing to the inspector actor. r=paul
-
-diff --git a/browser/devtools/markupview/markup-view.js b/browser/devtools/markupview/markup-view.js
---- a/browser/devtools/markupview/markup-view.js
-+++ b/browser/devtools/markupview/markup-view.js
-@@ -1101,47 +1101,53 @@ function ElementEditor(aContainer, aNode
-     this.template("elementContentSummary", this);
-   }
- 
-   // Create the closing tag
-   this.template("elementClose", this);
- 
-   this.rawNode = aNode.rawNode();
- 
--  // Make the tag name editable (unless this is a document element)
--  if (this.rawNode) {
--    if (!aNode.isDocumentElement) {
--      this.tag.setAttribute("tabindex", "0");
--      editableField({
--        element: this.tag,
--        trigger: "dblclick",
--        stopOnReturn: true,
--        done: this.onTagEdit.bind(this),
--      });
--    }
--
--    // Make the new attribute space editable.
-+  // Make the tag name editable (unless this is a remote node or
-+  // a document element)
-+  if (this.rawNode && !aNode.isDocumentElement) {
-+    this.tag.setAttribute("tabindex", "0");
-     editableField({
--      element: this.newAttr,
-+      element: this.tag,
-       trigger: "dblclick",
-       stopOnReturn: true,
--      done: function EE_onNew(aVal, aCommit) {
--        if (!aCommit) {
--          return;
--        }
--
--        try {
--          this._applyAttributes(aVal);
--        } catch (x) {
--          return;
--        }
--      }.bind(this)
-+      done: this.onTagEdit.bind(this),
-     });
-   }
- 
-+  // Make the new attribute space editable.
-+  editableField({
-+    element: this.newAttr,
-+    trigger: "dblclick",
-+    stopOnReturn: true,
-+    done: (aVal, aCommit) => {
-+      if (!aCommit) {
-+        return;
-+      }
-+
-+      try {
-+        let doMods = this._startModifyingAttributes();
-+        let undoMods = this._startModifyingAttributes();
-+        this._applyAttributes(aVal, null, doMods, undoMods);
-+        this.undo.do(() => {
-+          doMods.apply();
-+        }, function() {
-+          undoMods.apply();
-+        });
-+      } catch(x) {
-+        console.log(x);
-+      }
-+    }
-+  });
-+
-   let tagName = this.node.nodeName.toLowerCase();
-   this.tag.textContent = tagName;
-   this.closeTag.textContent = tagName;
- 
-   this.update();
- }
- 
- ElementEditor.prototype = {
-@@ -1170,16 +1176,20 @@ ElementEditor.prototype = {
-     for (let i = 0; i < attrs.length; i++) {
-       let attr = this._createAttribute(attrs[i]);
-       if (!attr.inplaceEditor) {
-         attr.style.removeProperty("display");
-       }
-     }
-   },
- 
-+  _startModifyingAttributes: function() {
-+    return this.node.startModifyingAttributes();
-+  },
-+
-   _createAttribute: function EE_createAttribute(aAttr, aBefore)
-   {
-     if (this.attrs.indexOf(aAttr.name) !== -1) {
-       var attr = this.attrs[aAttr.name];
-       var name = attr.querySelector(".attrname");
-       var val = attr.querySelector(".attrvalue");
-     } else {
-       // Create the template editor, which will save some variables here.
-@@ -1194,59 +1204,61 @@ ElementEditor.prototype = {
-       if (aAttr.name == "id") {
-         before = this.attrList.firstChild;
-       } else if (aAttr.name == "class") {
-         let idNode = this.attrs["id"];
-         before = idNode ? idNode.nextSibling : this.attrList.firstChild;
-       }
-       this.attrList.insertBefore(attr, before);
- 
--      if (this.rawNode) {
-+      // Make the attribute editable.
-+      editableField({
-+        element: inner,
-+        trigger: "dblclick",
-+        stopOnReturn: true,
-+        selectAll: false,
-+        start: (aEditor, aEvent) => {
-+          // If the editing was started inside the name or value areas,
-+          // select accordingly.
-+          if (aEvent && aEvent.target === name) {
-+            aEditor.input.setSelectionRange(0, name.textContent.length);
-+          } else if (aEvent && aEvent.target === val) {
-+            let length = val.textContent.length;
-+            let editorLength = aEditor.input.value.length;
-+            let start = editorLength - (length + 1);
-+            aEditor.input.setSelectionRange(start, start + length);
-+          } else {
-+            aEditor.input.select();
-+          }
-+        },
-+        done: (aVal, aCommit) => {
-+          if (!aCommit) {
-+            return;
-+          }
- 
--        // Make the attribute editable.
--        editableField({
--          element: inner,
--          trigger: "dblclick",
--          stopOnReturn: true,
--          selectAll: false,
--          start: function EE_editAttribute_start(aEditor, aEvent) {
--            // If the editing was started inside the name or value areas,
--            // select accordingly.
--            if (aEvent && aEvent.target === name) {
--              aEditor.input.setSelectionRange(0, name.textContent.length);
--            } else if (aEvent && aEvent.target === val) {
--              let length = val.textContent.length;
--              let editorLength = aEditor.input.value.length;
--              let start = editorLength - (length + 1);
--              aEditor.input.setSelectionRange(start, start + length);
--            } else {
--              aEditor.input.select();
--            }
--          },
--          done: function EE_editAttribute_done(aVal, aCommit) {
--            if (!aCommit) {
--              return;
--            }
-+          let doMods = this._startModifyingAttributes();
-+          let undoMods = this._startModifyingAttributes();
- 
--            this.undo.startBatch();
--
--            // Remove the attribute stored in this editor and re-add any attributes
--            // parsed out of the input element. Restore original attribute if
--            // parsing fails.
--            this._removeAttribute(this.rawNode, aAttr.name);
--            try {
--              this._applyAttributes(aVal, attr);
--              this.undo.endBatch();
--            } catch (e) {
--              this.undo.endBatch();
--              this.undo.undo();
--            }
--          }.bind(this)
--        });
--      }
-+          // Remove the attribute stored in this editor and re-add any attributes
-+          // parsed out of the input element. Restore original attribute if
-+          // parsing fails.
-+          try {
-+            this._saveAttribute(aAttr.name, undoMods);
-+            doMods.removeAttribute(aAttr.name);
-+            this._applyAttributes(aVal, attr, doMods, undoMods);
-+            this.undo.do(() => {
-+              doMods.apply();
-+            }, () => {
-+              undoMods.apply();
-+            })
-+          } catch(ex) {
-+            console.error(ex);
-+          }
-+        }
-+      });
- 
-       this.attrs[aAttr.name] = attr;
-     }
- 
-     name.textContent = aAttr.name;
-     val.textContent = aAttr.value;
- 
-     return attr;
-@@ -1256,94 +1268,45 @@ ElementEditor.prototype = {
-    * Parse a user-entered attribute string and apply the resulting
-    * attributes to the node.  This operation is undoable.
-    *
-    * @param string aValue the user-entered value.
-    * @param Element aAttrNode the attribute editor that created this
-    *        set of attributes, used to place new attributes where the
-    *        user put them.
-    */
--  _applyAttributes: function EE__applyAttributes(aValue, aAttrNode)
-+  _applyAttributes: function EE__applyAttributes(aValue, aAttrNode, aDoMods, aUndoMods)
-   {
-     let attrs = escapeAttributeValues(aValue);
- 
--    this.undo.startBatch();
-+    for (let attr of attrs) {
-+      // Create an attribute editor next to the current attribute if needed.
-+      this._createAttribute(attr, aAttrNode ? aAttrNode.nextSibling : null);
- 
--    for (let attr of attrs) {
--      let attribute = {
--        name: attr.name,
--        value: attr.value
--      };
--      // Create an attribute editor next to the current attribute if needed.
--      this._createAttribute(attribute, aAttrNode ? aAttrNode.nextSibling : null);
--      this._setAttribute(this.rawNode, attr.name, attr.value);
--    }
--
--    this.undo.endBatch();
--  },
--
--  /**
--   * Helper function for _setAttribute and _removeAttribute,
--   * returns a function that puts an attribute back the way it was.
--   */
--  _restoreAttribute: function EE_restoreAttribute(aNode, aName)
--  {
--    if (aNode.hasAttribute(aName)) {
--      let oldValue = aNode.getAttribute(aName);
--      return function() {
--        aNode.setAttribute(aName, oldValue);
--        this.markup.nodeChanged(aNode);
--      }.bind(this);
--    } else {
--      return function() {
--        aNode.removeAttribute(aName);
--        this.markup.nodeChanged(aNode);
--      }.bind(this);
-+      this._saveAttribute(attr.name, aUndoMods);
-+      aDoMods.setAttribute(attr.name, attr.value);
-     }
-   },
- 
-   /**
--   * Sets an attribute.  This operation is undoable.
-+   * Saves the current state of the given attribute into an attribute
-+   * modification list.
-    */
--  _setAttribute: function EE_setAttribute(aNode, aName, aValue)
-+  _saveAttribute: function(aName, aUndoMods)
-   {
--    this.undo.do(function() {
--      aNode.setAttribute(aName, aValue);
--      this.markup.nodeChanged(aNode);
--    }.bind(this), this._restoreAttribute(aNode, aName));
-+    let node = this.node;
-+    if (node.hasAttribute(aName)) {
-+      let oldValue = node.getAttribute(aName);
-+      aUndoMods.setAttribute(aName, oldValue);
-+    } else {
-+      aUndoMods.removeAttribute(aName);
-+    }
-   },
- 
-   /**
--   * Removes an attribute.  This operation is undoable.
--   */
--  _removeAttribute: function EE_removeAttribute(aNode, aName)
--  {
--    this.undo.do(function() {
--      aNode.removeAttribute(aName);
--      this.markup.nodeChanged(aNode);
--    }.bind(this), this._restoreAttribute(aNode, aName));
--  },
--
--  /**
--   * Handler for the new attribute editor.
--   */
--  _onNewAttribute: function EE_onNewAttribute(aValue, aCommit)
--  {
--    if (!aValue || !aCommit) {
--      return;
--    }
--
--    this._setAttribute(this.rawNode, aValue, "");
--    let attr = this._createAttribute({ name: aValue, value: ""});
--    attr.style.removeAttribute("display");
--    attr.querySelector("attrvalue").click();
--  },
--
--
--  /**
-    * Called when the tag name editor has is done editing.
-    */
-   onTagEdit: function EE_onTagEdit(aVal, aCommit) {
-     if (!aCommit || aVal == this.rawNode.tagName) {
-       return;
-     }
- 
-     // Create a new element with the same attributes as the
deleted file mode 100644
--- a/remote-edit-value.diff
+++ /dev/null
@@ -1,76 +0,0 @@
-# HG changeset patch
-# User Dave Camp <dcamp@mozilla.com>
-# Date 1371477175 25200
-#      Mon Jun 17 06:52:55 2013 -0700
-# Node ID dc0232e8d114bb05987546bfcebdeee2f082b671
-# Parent 9c16b8a6b6cec44b4eac86c486ca441380dd13bd
-Bug 886033 - Port markup view text value editing to inspector actor. r=paul
-
-diff --git a/browser/devtools/markupview/markup-view.js b/browser/devtools/markupview/markup-view.js
---- a/browser/devtools/markupview/markup-view.js
-+++ b/browser/devtools/markupview/markup-view.js
-@@ -1003,38 +1003,42 @@ function DoctypeEditor(aContainer, aNode
-  */
- function TextEditor(aContainer, aNode, aTemplate)
- {
-   this.node = aNode;
-   this._selected = false;
- 
-   aContainer.markup.template(aTemplate, this);
- 
--  let rawNode = aNode.rawNode();
--  if (rawNode) {
--    editableField({
--      element: this.value,
--      stopOnReturn: true,
--      trigger: "dblclick",
--      multiline: true,
--      done: function TE_done(aVal, aCommit) {
--        if (!aCommit) {
--          return;
--        }
--        let oldValue = rawNode.nodeValue;
--        aContainer.undo.do(function() {
--          rawNode.nodeValue = aVal;
--          aContainer.markup.nodeChanged(this.node);
--        }.bind(this), function() {
--          rawNode.nodeValue = oldValue;
--          aContainer.markup.nodeChanged(this.node);
--        }.bind(this));
--      }.bind(this)
--    });
--  }
-+  editableField({
-+    element: this.value,
-+    stopOnReturn: true,
-+    trigger: "dblclick",
-+    multiline: true,
-+    done: (aVal, aCommit) => {
-+      if (!aCommit) {
-+        return;
-+      }
-+      this.node.getNodeValue().then(longstr => {
-+        longstr.string().then(oldValue => {
-+          longstr.release().then(null, console.error);
-+
-+          aContainer.undo.do(() => {
-+            this.node.setNodeValue(aVal).then(() => {
-+              aContainer.markup.nodeChanged(this.node);
-+            });
-+          }, () => {
-+            this.node.setNodeValue(oldValue).then(() => {
-+              aContainer.markup.nodeChanged(this.node);
-+            })
-+          });
-+        });
-+      });
-+    }
-+  });
- 
-   this.update();
- }
- 
- TextEditor.prototype = {
-   get selected() this._selected,
-   set selected(aValue) {
-     if (aValue === this._selected) {
deleted file mode 100644
--- a/remote-markup.diff
+++ /dev/null
@@ -1,2330 +0,0 @@
-# HG changeset patch
-# User Dave Camp <dcamp@mozilla.com>
-# Date 1370924326 25200
-#      Mon Jun 10 21:18:46 2013 -0700
-# Node ID 78398f24d7703ef98e71055b3d9db45731a55c31
-# Parent  20848adc99809d3d24ae2425732017fd8d3ecb25
-Bug 886032 - Port the markup view to the inspector actor (readonly). r=paul
-
-diff --git a/browser/devtools/inspector/inspector-panel.js b/browser/devtools/inspector/inspector-panel.js
---- a/browser/devtools/inspector/inspector-panel.js
-+++ b/browser/devtools/inspector/inspector-panel.js
-@@ -136,17 +136,17 @@ InspectorPanel.prototype = {
- 
-       // All the components are initialized. Let's select a node.
-       this._selection.setNodeFront(defaultSelection);
- 
-       if (this.highlighter) {
-         this.highlighter.unlock();
-       }
- 
--      this.markup.expandNode(this.selection.node);
-+      this.markup.expandNode(this.selection.nodeFront);
- 
-       this.emit("ready");
-       deferred.resolve(this);
-     }.bind(this));
- 
-     this.setupSearchBox();
-     this.setupSidebar();
- 
-@@ -292,17 +292,17 @@ InspectorPanel.prototype = {
-       this._getDefaultNodeForSelection().then(defaultNode => {
-         if (this._destroyPromise) {
-           return;
-         }
-         this.selection.setNodeFront(defaultNode, "navigateaway");
- 
-         this._initMarkup();
-         this.once("markuploaded", () => {
--          this.markup.expandNode(this.selection.node);
-+          this.markup.expandNode(this.selection.nodeFront);
-           this.setupSearchBox();
-         });
-       });
-     });
-   },
- 
-   /**
-    * When a new node is selected.
-@@ -663,27 +663,24 @@ InspectorPanel.prototype = {
-    * Delete the selected node.
-    */
-   deleteNode: function IUI_deleteNode() {
-     if (!this.selection.isNode() ||
-          this.selection.isRoot()) {
-       return;
-     }
- 
--    let toDelete = this.selection.node;
--
--    let parent = this.selection.node.parentNode;
--
-     // If the markup panel is active, use the markup panel to delete
-     // the node, making this an undoable action.
-     if (this.markup) {
--      this.markup.deleteNode(toDelete);
-+      this.markup.deleteNode(this.selection.nodeFront);
-     } else {
-       // remove the node from content
--      parent.removeChild(toDelete);
-+      let parent = this.selection.node.parentNode;
-+      parent.removeChild(this.selection.node);
-     }
-   },
- 
-   /**
-    * Schedule a low-priority change event for things like paint
-    * and resize.
-    */
-   scheduleLayoutChange: function Inspector_scheduleLayoutChange()
-diff --git a/browser/devtools/inspector/test/browser_inspector_bug_817558_delete_node.js b/browser/devtools/inspector/test/browser_inspector_bug_817558_delete_node.js
---- a/browser/devtools/inspector/test/browser_inspector_bug_817558_delete_node.js
-+++ b/browser/devtools/inspector/test/browser_inspector_bug_817558_delete_node.js
-@@ -22,30 +22,31 @@ function test()
-     node = iframe.contentDocument.querySelector("span");
-     openInspector(runTests);
-   }
- 
-   function runTests(aInspector)
-   {
-     inspector = aInspector;
-     inspector.selection.setNode(node);
-+    inspector.once("inspector-updated", () => {
-+      let parentNode = node.parentNode;
-+      parentNode.removeChild(node);
- 
--    let parentNode = node.parentNode;
--    parentNode.removeChild(node);
-+      let tmp = {};
-+      Cu.import("resource:///modules/devtools/LayoutHelpers.jsm", tmp);
-+      ok(!tmp.LayoutHelpers.isNodeConnected(node), "Node considered as disconnected.");
- 
--    let tmp = {};
--    Cu.import("resource:///modules/devtools/LayoutHelpers.jsm", tmp);
--    ok(!tmp.LayoutHelpers.isNodeConnected(node), "Node considered as disconnected.");
--
--    // Wait for the selection to process the mutation
--    inspector.walker.on("mutations", () => {
--      is(inspector.selection.node, parentNode, "parent of selection got selected");
--      finishUp();
-+      // Wait for the inspector to process the mutation
-+      inspector.once("inspector-updated", () => {
-+        is(inspector.selection.node, parentNode, "parent of selection got selected");
-+        finishUp();
-+      });
-     });
--  }
-+  };
- 
-   function finishUp() {
-     node = null;
-     gBrowser.removeCurrentTab();
-     finish();
-   }
- }
- 
-diff --git a/browser/devtools/inspector/test/browser_inspector_initialization.js b/browser/devtools/inspector/test/browser_inspector_initialization.js
---- a/browser/devtools/inspector/test/browser_inspector_initialization.js
-+++ b/browser/devtools/inspector/test/browser_inspector_initialization.js
-@@ -67,36 +67,47 @@ function startInspectorTests(toolbox)
- 
- 
- function testHighlighter(node)
- {
-   ok(isHighlighting(), "Highlighter is highlighting");
-   is(getHighlitNode(), node, "Right node is highlighted");
- }
- 
-+let callNo = 0;
- function testMarkupView(node)
- {
-   let i = getActiveInspector();
--  is(i.markup._selectedContainer.node, node, "Right node is selected in the markup view");
-+  try {
-+    is(i.markup._selectedContainer.node.rawNode(), node, "Right node is selected in the markup view");
-+  } catch(ex) { console.error(ex); }
- }
- 
- function testBreadcrumbs(node)
- {
-   let b = getActiveInspector().breadcrumbs;
-   let expectedText = b.prettyPrintNodeAsText(getNodeFront(node));
-   let button = b.container.querySelector("button[checked=true]");
-   ok(button, "A crumbs is checked=true");
-   is(button.getAttribute("tooltiptext"), expectedText, "Crumb refers to the right node");
- }
- 
- function _clickOnInspectMenuItem(node) {
-   document.popupNode = node;
-   var contentAreaContextMenu = document.getElementById("contentAreaContextMenu");
-   var contextMenu = new nsContextMenu(contentAreaContextMenu);
--  return contextMenu.inspectNode();
-+  var promise = devtools.require("sdk/core/promise");
-+  var deferred = promise.defer();
-+  contextMenu.inspectNode().then(() => {
-+    let i = getActiveInspector();
-+    i.once("inspector-updated", () => {
-+      deferred.resolve(undefined);
-+    });
-+  });
-+  return deferred.promise;
- }
- 
- function runContextMenuTest()
- {
-   salutation = doc.getElementById("salutation");
-   _clickOnInspectMenuItem(salutation).then(testInitialNodeIsSelected);
- }
- 
-diff --git a/browser/devtools/inspector/test/browser_inspector_menu.js b/browser/devtools/inspector/test/browser_inspector_menu.js
---- a/browser/devtools/inspector/test/browser_inspector_menu.js
-+++ b/browser/devtools/inspector/test/browser_inspector_menu.js
-@@ -48,31 +48,33 @@ function test() {
- 
-       checkElementMenuItems();
-     });
-   }
- 
-   function checkElementMenuItems() {
-     info("Checking context menu entries for p tag");
-     inspector.selection.setNode(doc.querySelector("p"));
--    let tag = getMarkupTagNodeContaining("p");
-+    inspector.once("inspector-updated", () => {
-+      let tag = getMarkupTagNodeContaining("p");
- 
--    // Right-click p tag
--    contextMenuClick(tag);
-+      // Right-click p tag
-+      contextMenuClick(tag);
- 
--    checkEnabled("node-menu-copyinner");
--    checkEnabled("node-menu-copyouter");
--    checkEnabled("node-menu-copyuniqueselector");
--    checkEnabled("node-menu-delete");
-+      checkEnabled("node-menu-copyinner");
-+      checkEnabled("node-menu-copyouter");
-+      checkEnabled("node-menu-copyuniqueselector");
-+      checkEnabled("node-menu-delete");
- 
--    for (let name of ["hover", "active", "focus"]) {
--      checkEnabled("node-menu-pseudo-" + name);
--    }
-+      for (let name of ["hover", "active", "focus"]) {
-+        checkEnabled("node-menu-pseudo-" + name);
-+      }
- 
--    testCopyInnerMenu();
-+      testCopyInnerMenu();
-+    });
-   }
- 
-   function testCopyInnerMenu() {
-     let copyInner = inspector.panelDoc.getElementById("node-menu-copyinner");
-     ok(copyInner, "the popup menu has a copy inner html menu item");
- 
-     waitForClipboard("This is some example text",
-                      function() { copyInner.doCommand(); },
-diff --git a/browser/devtools/markupview/markup-view.js b/browser/devtools/markupview/markup-view.js
---- a/browser/devtools/markupview/markup-view.js
-+++ b/browser/devtools/markupview/markup-view.js
-@@ -10,16 +10,17 @@ const {Cc, Cu, Ci} = require("chrome");
- const PAGE_SIZE = 10;
- 
- const PREVIEW_AREA = 700;
- const DEFAULT_MAX_CHILDREN = 100;
- 
- let {UndoStack} = require("devtools/shared/undo");
- let EventEmitter = require("devtools/shared/event-emitter");
- let {editableField, InplaceEditor} = require("devtools/shared/inplace-editor");
-+let promise = require("sdk/core/promise");
- 
- Cu.import("resource:///modules/devtools/LayoutHelpers.jsm");
- Cu.import("resource://gre/modules/devtools/Templater.jsm");
- Cu.import("resource://gre/modules/Services.jsm");
- 
- /**
-  * Vocabulary for the purposes of this file:
-  *
-@@ -36,35 +37,37 @@ Cu.import("resource://gre/modules/Servic
-  * @param Inspector aInspector
-  *        The inspector we're watching.
-  * @param iframe aFrame
-  *        An iframe in which the caller has kindly loaded markup-view.xhtml.
-  */
- 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");
- 
-   try {
-     this.maxChildren = Services.prefs.getIntPref("devtools.markup.pagesize");
-   } catch(ex) {
-     this.maxChildren = DEFAULT_MAX_CHILDREN;
-   }
- 
-   this.undo = new UndoStack();
-   this.undo.installController(aControllerWindow);
- 
-   this._containers = new WeakMap();
- 
--  this._observer = new this.doc.defaultView.MutationObserver(this._mutationObserver.bind(this));
-+  this._boundMutationObserver = this._mutationObserver.bind(this);
-+  this.walker.on("mutations", this._boundMutationObserver)
- 
-   this._boundOnNewSelection = this._onNewSelection.bind(this);
--  this._inspector.selection.on("new-node", this._boundOnNewSelection);
-+  this._inspector.selection.on("new-node-front", this._boundOnNewSelection);
-   this._onNewSelection();
- 
-   this._boundKeyDown = this._onKeyDown.bind(this);
-   this._frame.contentWindow.addEventListener("keydown", this._boundKeyDown, false);
- 
-   this._boundFocus = this._onFocus.bind(this);
-   this._frame.addEventListener("focus", this._boundFocus, false);
- 
-@@ -93,35 +96,41 @@ MarkupView.prototype = {
-     return this._containers.get(aNode);
-   },
- 
-   /**
-    * Highlight the inspector selected node.
-    */
-   _onNewSelection: function MT__onNewSelection()
-   {
-+    let done = this._inspector.updating("markup-view");
-     if (this._inspector.selection.isNode()) {
--      this.showNode(this._inspector.selection.node, true);
--      this.markNodeAsSelected(this._inspector.selection.node);
-+      this.showNode(this._inspector.selection.nodeFront, true).then(() => {
-+        this.markNodeAsSelected(this._inspector.selection.nodeFront);
-+        done();
-+      });
-     } else {
-       this.unmarkSelectedNode();
-+      done();
-     }
-   },
- 
-   /**
-    * Create a TreeWalker to find the next/previous
-    * node for selection.
-    */
-   _selectionWalker: function MT__seletionWalker(aStart)
-   {
-     let walker = this.doc.createTreeWalker(
-       aStart || this._elt,
-       Ci.nsIDOMNodeFilter.SHOW_ELEMENT,
-       function(aElement) {
--        if (aElement.container && aElement.container.visible) {
-+        if (aElement.container &&
-+            aElement.container.elt === aElement &&
-+            aElement.container.visible) {
-           return Ci.nsIDOMNodeFilter.FILTER_ACCEPT;
-         }
-         return Ci.nsIDOMNodeFilter.FILTER_SKIP;
-       }
-     );
-     walker.currentNode = this._selectedContainer.elt;
-     return walker;
-   },
-@@ -140,31 +149,32 @@ MarkupView.prototype = {
-     }
- 
-     switch(aEvent.keyCode) {
-       case Ci.nsIDOMKeyEvent.DOM_VK_DELETE:
-       case Ci.nsIDOMKeyEvent.DOM_VK_BACK_SPACE:
-         this.deleteNode(this._selectedContainer.node);
-         break;
-       case Ci.nsIDOMKeyEvent.DOM_VK_HOME:
--        this.navigate(this._containers.get(this._rootNode.firstChild));
-+        let rootContainer = this._containers.get(this._rootNode);
-+        this.navigate(rootContainer.children.firstChild.container);
-         break;
-       case Ci.nsIDOMKeyEvent.DOM_VK_LEFT:
-         if (this._selectedContainer.expanded) {
-           this.collapseNode(this._selectedContainer.node);
-         } else {
-           let parent = this._selectionWalker().parentNode();
-           if (parent) {
-             this.navigate(parent.container);
-           }
-         }
-         break;
-       case Ci.nsIDOMKeyEvent.DOM_VK_RIGHT:
-         if (!this._selectedContainer.expanded) {
--          this.expandNode(this._selectedContainer.node);
-+          this._expandContainer(this._selectedContainer);
-         } else {
-           let next = this._selectionWalker().nextNode();
-           if (next) {
-             this.navigate(next.container);
-           }
-         }
-         break;
-       case Ci.nsIDOMKeyEvent.DOM_VK_UP:
-@@ -215,16 +225,20 @@ MarkupView.prototype = {
-   },
- 
-   /**
-    * Delete a node from the DOM.
-    * This is an undoable action.
-    */
-   deleteNode: function MC__deleteNode(aNode)
-   {
-+    aNode = aNode.rawNode();
-+    if (!aNode) {
-+      return;
-+    }
-     let doc = nodeDocument(aNode);
-     if (aNode === doc ||
-         aNode === doc.documentElement ||
-         aNode.nodeType == Ci.nsIDOMNode.DOCUMENT_TYPE_NODE) {
-       return;
-     }
- 
-     let parentNode = aNode.parentNode;
-@@ -264,136 +278,129 @@ MarkupView.prototype = {
-    */
-   navigate: function MT__navigate(aContainer, aIgnoreFocus)
-   {
-     if (!aContainer) {
-       return;
-     }
- 
-     let node = aContainer.node;
--    this.showNode(node, false);
--
--    this._inspector.selection.setNode(node, "treepanel");
-+    this.markNodeAsSelected(node);
-+    this._inspector.selection.setNodeFront(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.
-    *
-    * @param DOMNode aNode
-    *        The node in the content document.
--   *
-    * @returns MarkupContainer The MarkupContainer object for this element.
-    */
--  importNode: function MT_importNode(aNode, aExpand)
-+  importNode: function MT_importNode(aNode)
-   {
-     if (!aNode) {
-       return null;
-     }
- 
-     if (this._containers.has(aNode)) {
-       return this._containers.get(aNode);
-     }
- 
--    this._observer.observe(aNode, {
--      attributes: true,
--      childList: true,
--      characterData: true,
--    });
--
--    let walker = documentWalker(aNode);
--    let parent = walker.parentNode();
--    if (parent) {
--      var container = new MarkupContainer(this, aNode);
--    } else {
-+    if (aNode === this.walker.rootNode) {
-       var container = new RootContainer(this, aNode);
-       this._elt.appendChild(container.elt);
--
--      if (this._rootNode) {
--        this._rootNode.removeEventListener("load", this, true);
--      }
--
-       this._rootNode = aNode;
--      aNode.addEventListener("load", this, true);
-+    } else {
-+      var container = new MarkupContainer(this, aNode);
-     }
- 
-     this._containers.set(aNode, container);
--    // FIXME: set an expando to prevent the the wrapper from disappearing
--    // See bug 819131 for details.
--    aNode.__preserveHack = true;
--    container.expanded = aExpand;
-+    container.childrenDirty = true;
- 
--    container.childrenDirty = true;
-     this._updateChildren(container);
- 
--    if (parent) {
--      this.importNode(parent, true);
--    }
-     return container;
-   },
- 
--  handleEvent: function MT_handleEvent(aEvent) {
--    // Fake a childList mutation here.
--    this._mutationObserver([{target: aEvent.target, type: "childList"}]);
--  },
- 
-   /**
-    * Mutation observer used for included nodes.
-    */
-   _mutationObserver: function MT__mutationObserver(aMutations)
-   {
-     for (let mutation of aMutations) {
--      let container = this._containers.get(mutation.target);
-+      let type = mutation.type;
-+      let target = mutation.target;
-+
-+      if (mutation.type === "documentUnload") {
-+        // Treat this as a childList change of the child (maybe the protocol
-+        // should do this).
-+        type = "childList"
-+        target = mutation.targetParent;
-+        if (!target) {
-+          continue;
-+        }
-+      }
-+
-+      let container = this._containers.get(target);
-       if (!container) {
--        // Container might not exist if this came from a load event for an iframe
-+        // Container might not exist if this came from a load event for a node
-         // we're not viewing.
-         continue;
-       }
--      if (mutation.type === "attributes" || mutation.type === "characterData") {
-+      if (type === "attributes" || type === "characterData") {
-         container.update();
--      } else if (mutation.type === "childList") {
-+      } else if (type === "childList") {
-         container.childrenDirty = true;
-         this._updateChildren(container);
-       }
-     }
--    this._inspector.emit("markupmutation");
-+    this._waitForChildren().then(() => {
-+      this._inspector.emit("markupmutation");
-+    });
-   },
- 
-+
-   /**
-    * Make sure the given node's parents are expanded and the
-    * node is scrolled on to screen.
-    */
-   showNode: function MT_showNode(aNode, centered)
-   {
-     let container = this.importNode(aNode);
--    this._updateChildren(container);
--    let walker = documentWalker(aNode);
--    let parent;
--    while (parent = walker.parentNode()) {
--      this._updateChildren(this.getContainer(parent));
-+    let parent = aNode;
-+    while ((parent = parent.parentNode())) {
-+      this.importNode(parent);
-       this.expandNode(parent);
-     }
--    LayoutHelpers.scrollIntoViewIfNeeded(this._containers.get(aNode).editor.elt, centered);
-+
-+    return this._waitForChildren().then(() => {
-+      return this._ensureVisible(aNode);
-+    }).then(() => {
-+      // Why is this not working?
-+      LayoutHelpers.scrollIntoViewIfNeeded(this._containers.get(aNode).editor.elt, centered);
-+    });
-   },
- 
-   /**
-    * Expand the container's children.
-    */
-   _expandContainer: function MT__expandContainer(aContainer)
-   {
--    if (aContainer.hasChildren && !aContainer.expanded) {
-+    return this._updateChildren(aContainer, true).then(() => {
-       aContainer.expanded = true;
--      this._updateChildren(aContainer);
--    }
-+    })
-   },
- 
-   /**
-    * Expand the node's children.
-    */
-   expandNode: function MT_expandNode(aNode)
-   {
-     let container = this._containers.get(aNode);
-@@ -402,45 +409,57 @@ MarkupView.prototype = {
- 
-   /**
-    * Expand the entire tree beneath a container.
-    *
-    * @param aContainer The container to expand.
-    */
-   _expandAll: function MT_expandAll(aContainer)
-   {
--    this._expandContainer(aContainer);
--    let child = aContainer.children.firstChild;
--    while (child) {
--      this._expandAll(child.container);
--      child = child.nextSibling;
--    }
-+    return this._expandContainer(aContainer).then(() => {
-+      let child = aContainer.children.firstChild;
-+      let promises = [];
-+      while (child) {
-+        promises.push(this._expandAll(child.container));
-+        child = child.nextSibling;
-+      }
-+      return promise.all(promises);
-+    }).then(null, console.error);
-   },
- 
-   /**
-    * Expand the entire tree beneath a node.
-    *
-    * @param aContainer The node to expand, or null
-    *        to start from the top.
-    */
-   expandAll: function MT_expandAll(aNode)
-   {
-     aNode = aNode || this._rootNode;
--    this._expandAll(this._containers.get(aNode));
-+    return this._expandAll(this._containers.get(aNode));
-   },
- 
-   /**
-    * Collapse the node's children.
-    */
-   collapseNode: function MT_collapseNode(aNode)
-   {
-     let container = this._containers.get(aNode);
-     container.expanded = false;
-   },
- 
-+  setNodeExpanded: function(aNode, aExpanded)
-+  {
-+    if (aExpanded) {
-+      this.expandNode(aNode);
-+    } else {
-+      this.collapseNode(aNode);
-+    }
-+  },
-+
-   /**
-    * Mark the given node selected.
-    */
-   markNodeAsSelected: function MT_markNodeAsSelected(aNode)
-   {
-     let container = this._containers.get(aNode);
-     if (this._selectedContainer === container) {
-       return false;
-@@ -448,40 +467,37 @@ MarkupView.prototype = {
-     if (this._selectedContainer) {
-       this._selectedContainer.selected = false;
-     }
-     this._selectedContainer = container;
-     if (aNode) {
-       this._selectedContainer.selected = true;
-     }
- 
--    this._ensureSelectionVisible();
--
-     return true;
-   },
- 
-   /**
-    * Make sure that every ancestor of the selection are updated
-    * and included in the list of visible children.
-    */
--  _ensureSelectionVisible: function MT_ensureSelectionVisible()
-+  _ensureVisible: function(node)
-   {
--    let node = this._selectedContainer.node;
--    let walker = documentWalker(node);
-     while (node) {
-       let container = this._containers.get(node);
--      let parent = walker.parentNode();
-+      let parent = node.parentNode();
-       if (!container.elt.parentNode) {
-         let parentContainer = this._containers.get(parent);
-         parentContainer.childrenDirty = true;
-         this._updateChildren(parentContainer, node);
-       }
- 
-       node = parent;
-     }
-+    return this._waitForChildren();
-   },
- 
-   /**
-    * Unmark selected node (no node selected).
-    */
-   unmarkSelectedNode: function MT_unmarkSelectedNode()
-   {
-     if (this._selectedContainer) {
-@@ -496,190 +512,199 @@ MarkupView.prototype = {
-   nodeChanged: function MT_nodeChanged(aNode)
-   {
-     if (aNode === this._inspector.selection) {
-       this._inspector.change("markupview");
-     }
-   },
- 
-   /**
-+   * Check if the current selection is a descendent of the container.
-+   * if so, make sure it's among the visible set for the container,
-+   * and set the dirty flag if needed.
-+   * @returns The node that should be made visible, if any.
-+   */
-+  _checkSelectionVisible: function(aContainer) {
-+    let centered = null;
-+    let node = this._inspector.selection.nodeFront;
-+    while (node) {
-+      if (node.parentNode() === aContainer.node) {
-+        centered = node;
-+        break;
-+      }
-+      node = node.parentNode();
-+    }
-+
-+    return centered;
-+  },
-+
-+  /**
-    * Make sure all children of the given container's node are
-    * imported and attached to the container in the right order.
--   * @param aCentered If provided, this child will be included
--   *        in the visible subset, and will be roughly centered
--   *        in that list.
-+   *
-+   * Children need to be updated only in the following circumstances:
-+   * a) We just imported this node and have never seen its children.
-+   *    container.childrenDirty will be set by importNode in this case.
-+   * b) We received a childList mutation on the node.
-+   *    container.childrenDirty will be set in that case too.
-+   * c) We have changed the selection, and the path to that selection
-+   *    wasn't loaded in a previous children request (because we only
-+   *    grab a subset).
-+   *    container.childrenDirty should be set in that case too!
-+   *
-+   * This method returns a promise that will be resolved when the children
-+   * are ready (which may be immediately).
-    */
--  _updateChildren: function MT__updateChildren(aContainer, aCentered)
-+  _updateChildren: function(aContainer, aExpand)
-   {
--    if (!aContainer.childrenDirty) {
--      return false;
-+    aContainer.hasChildren = aContainer.node.hasChildren;
-+
-+    if (!this._queuedChildUpdates) {
-+      this._queuedChildUpdates = new Map();
-     }
- 
--    // Get a tree walker pointing at the first child of the node.
--    let treeWalker = documentWalker(aContainer.node);
--    let child = treeWalker.firstChild();
--    aContainer.hasChildren = !!child;
--
--    if (!aContainer.expanded) {
--      return;
-+    if (this._queuedChildUpdates.has(aContainer)) {
-+      return this._queuedChildUpdates.get(aContainer);
-     }
- 
--    aContainer.childrenDirty = false;
--
--    let children = this._getVisibleChildren(aContainer, aCentered);
--    let fragment = this.doc.createDocumentFragment();
--
--    for (child of children.children) {
--      let container = this.importNode(child, false);
--      fragment.appendChild(container.elt);
-+    if (!aContainer.childrenDirty) {
-+      return promise.resolve(aContainer);
-     }
- 
--    while (aContainer.children.firstChild) {
--      aContainer.children.removeChild(aContainer.children.firstChild);
-+    if (!aContainer.hasChildren) {
-+      while (aContainer.children.firstChild) {
-+        aContainer.children.removeChild(aContainer.children.firstChild);
-+      }
-+      aContainer.childrenDirty = false;
-+      return promise.resolve(aContainer);
-     }
- 
--    if (!(children.hasFirst && children.hasLast)) {
--      let data = {
--        showing: this.strings.GetStringFromName("markupView.more.showing"),
--        showAll: this.strings.formatStringFromName(
--                  "markupView.more.showAll",
--                  [aContainer.node.children.length.toString()], 1),
--        allButtonClick: function() {
--          aContainer.maxChildren = -1;
--          aContainer.childrenDirty = true;
--          this._updateChildren(aContainer);
--        }.bind(this)
--      };
--
--      if (!children.hasFirst) {
--        let span = this.template("more-nodes", data);
--        fragment.insertBefore(span, fragment.firstChild);
--      }
--      if (!children.hasLast) {
--        let span = this.template("more-nodes", data);
--        fragment.appendChild(span);
--      }
-+    // If we're not expanded (or asked to update anyway), we're done for
-+    // now.  Note that this will leave the childrenDirty flag set, so when
-+    // expanded we'll refresh the child list.
-+    if (!(aContainer.expanded || aExpand)) {
-+      return promise.resolve(aContainer);
-     }
- 
--    aContainer.children.appendChild(fragment);
-+    // We're going to issue a children request, make sure it includes the
-+    // centered node.
-+    let centered = this._checkSelectionVisible(aContainer);
- 
--    return true;
-+    // Children aren't updated yet, but clear the childrenDirty flag anyway.
-+    // If the dirty flag is re-set while we're fetching we'll need to fetch
-+    // again.
-+    aContainer.childrenDirty = false;
-+    let updatePromise = this._getVisibleChildren(aContainer, centered).then(children => {
-+      this._queuedChildUpdates.delete(aContainer);
-+
-+      // If children are dirty, we got a change notification for this node
-+      // while the request was in progress, we need to do it again.
-+      if (aContainer.childrenDirty) {
-+        return this._updateChildren(aContainer, centered);
-+      }
-+
-+      let fragment = this.doc.createDocumentFragment();
-+
-+      for (let child of children.nodes) {
-+        let container = this.importNode(child);
-+        fragment.appendChild(container.elt);
-+      }
-+
-+      while (aContainer.children.firstChild) {
-+        aContainer.children.removeChild(aContainer.children.firstChild);
-+      }
-+
-+      if (!(children.hasFirst && children.hasLast)) {
-+        let data = {
-+          showing: this.strings.GetStringFromName("markupView.more.showing"),
-+          showAll: this.strings.formatStringFromName(
-+                    "markupView.more.showAll",
-+                    [aContainer.node.numChildren.toString()], 1),
-+          allButtonClick: () => {
-+            aContainer.maxChildren = -1;
-+            aContainer.childrenDirty = true;
-+            this._updateChildren(aContainer);
-+          }
-+        };
-+
-+        if (!children.hasFirst) {
-+          let span = this.template("more-nodes", data);
-+          fragment.insertBefore(span, fragment.firstChild);
-+        }
-+        if (!children.hasLast) {
-+          let span = this.template("more-nodes", data);
-+          fragment.appendChild(span);
-+        }
-+      }
-+
-+      aContainer.children.appendChild(fragment);
-+      return aContainer;
-+    }).then(null, console.error);
-+    this._queuedChildUpdates.set(aContainer, updatePromise);
-+    return updatePromise;
-+  },
-+
-+  _waitForChildren: function() {
-+    if (!this._queuedChildUpdates) {
-+      return promise.resolve(undefined);
-+    }
-+    return promise.all([updatePromise for (updatePromise of this._queuedChildUpdates.values())]);
-   },
- 
-   /**
-    * Return a list of the children to display for this container.
-    */
-   _getVisibleChildren: function MV__getVisibleChildren(aContainer, aCentered)
-   {
-     let maxChildren = aContainer.maxChildren || this.maxChildren;
-     if (maxChildren == -1) {
--      maxChildren = Number.MAX_VALUE;
--    }
--    let firstChild = documentWalker(aContainer.node).firstChild();
--    let lastChild = documentWalker(aContainer.node).lastChild();
--
--    if (!firstChild) {
--      // No children, we're done.
--      return { hasFirst: true, hasLast: true, children: [] };
-+      maxChildren = undefined;
-     }
- 
--    // By default try to put the selected child in the middle of the list.
--    let start = aCentered || firstChild;
--
--    // Start by reading backward from the starting point....
--    let nodes = [];
--    let backwardWalker = documentWalker(start);
--    if (backwardWalker.previousSibling()) {
--      let backwardCount = Math.floor(maxChildren / 2);
--      let backwardNodes = this._readBackward(backwardWalker, backwardCount);
--      nodes = backwardNodes;
--    }
--
--    // Then read forward by any slack left in the max children...
--    let forwardWalker = documentWalker(start);
--    let forwardCount = maxChildren - nodes.length;
--    nodes = nodes.concat(this._readForward(forwardWalker, forwardCount));
--
--    // If there's any room left, it means we've run all the way to the end.
--    // In that case, there might still be more items at the front.
--    let remaining = maxChildren - nodes.length;
--    if (remaining > 0 && nodes[0] != firstChild) {
--      let firstNodes = this._readBackward(backwardWalker, remaining);
--
--      // Then put it all back together.
--      nodes = firstNodes.concat(nodes);
--    }
--
--    return {
--      hasFirst: nodes[0] == firstChild,
--      hasLast: nodes[nodes.length - 1] == lastChild,
--      children: nodes
--    };
--  },
--
--  _readForward: function MV__readForward(aWalker, aCount)
--  {
--    let ret = [];
--    let node = aWalker.currentNode;
--    do {
--      ret.push(node);
--      node = aWalker.nextSibling();
--    } while (node && --aCount);
--    return ret;
--  },
--
--  _readBackward: function MV__readBackward(aWalker, aCount)
--  {
--    let ret = [];
--    let node = aWalker.currentNode;
--    do {
--      ret.push(node);
--      node = aWalker.previousSibling();
--    } while(node && --aCount);
--    ret.reverse();
--    return ret;
-+    return this.walker.children(aContainer.node, {
-+      maxNodes: maxChildren,
-+      center: aCentered
-+    });
-   },
- 
-   /**
-    * Tear down the markup panel.
-    */
-   destroy: function MT_destroy()
-   {
-     this.undo.destroy();
-     delete this.undo;
- 
-     this._frame.removeEventListener("focus", this._boundFocus, false);
-     delete this._boundFocus;
- 
--    this._frame.contentWindow.removeEventListener("scroll", this._boundUpdatePreview, true);
--    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._boundUpdatePreview;
-+    if (this._boundUpdatePreview) {
-+      this._frame.contentWindow.removeEventListener("scroll", this._boundUpdatePreview, true);
-+      delete this._boundUpdatePreview;
-+    }
-+
-+    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._frame.contentWindow.removeEventListener("keydown", this._boundKeyDown, false);
-     delete this._boundKeyDown;
- 
--    this._inspector.selection.off("new-node", this._boundOnNewSelection);
-+    this._inspector.selection.off("new-node-front", this._boundOnNewSelection);
-     delete this._boundOnNewSelection;
- 
-+    this.walker.off("mutations", this._boundMutationObserver)
-+    delete this._boundMutationObserver;
-+
-     delete this._elt;
- 
-     delete this._containers;
--    this._observer.disconnect();
--    delete this._observer;
--
--    if (this._rootNode) {
--      try {
--        this._rootNode.removeEventListener("load", this, true);
--      } catch(e) {
--        // this._rootNode might be a dead object.
--      }
--      delete this._rootNode;
--    }
-   },
- 
-   /**
-    * Initialize the preview panel.
-    */
-   _initPreview: function MT_initPreview()
-   {
-     if (!Services.prefs.getBoolPref("devtools.inspector.markupPreview")) {
-@@ -789,25 +814,22 @@ function MarkupContainer(aMarkupView, aN
- 
-   // The template will fill the following properties
-   this.elt = null;
-   this.expander = null;
-   this.codeBox = null;
-   this.children = null;
-   this.markup.template("container", this);
-   this.elt.container = this;
-+  this.children.container = this;
- 
-   this.expander.addEventListener("click", function() {
-     this.markup.navigate(this);
- 
--    if (this.expanded) {
--      this.markup.collapseNode(this.node);
--    } else {
--      this.markup.expandNode(this.node);
--    }
-+    this.markup.setNodeExpanded(this.node, !this.expanded);
-   }.bind(this));
- 
-   this.codeBox.insertBefore(this.editor.elt, this.children);
- 
-   this.editor.elt.addEventListener("mousedown", function(evt) {
-     this.markup.navigate(this);
-   }.bind(this), false);
- 
-@@ -820,20 +842,21 @@ function MarkupContainer(aMarkupView, aN
-   }
- 
-   if (this.editor.closeElt) {
-     this.editor.closeElt.addEventListener("mousedown", function(evt) {
-       this.markup.navigate(this);
-     }.bind(this), false);
-     this.codeBox.appendChild(this.editor.closeElt);
-   }
--
- }
- 
- MarkupContainer.prototype = {
-+  toString: function() { return "[MarkupContainer for " + this.node + "]" },
-+
-   /**
-    * True if the current node has children.  The MarkupView
-    * will set this attribute for the MarkupContainer.
-    */
-   _hasChildren: false,
- 
-   get hasChildren() {
-     return this._hasChildren;
-@@ -843,16 +866,20 @@ MarkupContainer.prototype = {
-     this._hasChildren = aValue;
-     if (aValue) {
-       this.expander.style.visibility = "visible";
-     } else {
-       this.expander.style.visibility = "hidden";
-     }
-   },
- 
-+  parentContainer: function() {
-+    return this.elt.parentNode ? this.elt.parentNode.container : null;
-+  },
-+
-   /**
-    * True if the node has been visually expanded in the tree.
-    */
-   get expanded() {
-     return this.children.hasAttribute("expanded");
-   },
- 
-   set expanded(aValue) {
-@@ -885,16 +912,17 @@ MarkupContainer.prototype = {
-   _selected: false,
- 
-   get selected() {
-     return this._selected;
-   },
- 
-   set selected(aValue) {
-     this._selected = aValue;
-+    this.editor.selected = aValue;
-     if (this._selected) {
-       this.editor.elt.classList.add("theme-selected");
-       if (this.editor.closeElt) {
-         this.editor.closeElt.classList.add("theme-selected");
-       }
-     } else {
-       this.editor.elt.classList.remove("theme-selected");
-       if (this.editor.closeElt) {
-@@ -928,18 +956,20 @@ MarkupContainer.prototype = {
- 
- /**
-  * Dummy container node used for the root document element.
-  */
- function RootContainer(aMarkupView, aNode)
- {
-   this.doc = aMarkupView.doc;
-   this.elt = this.doc.createElement("ul");
-+  this.elt.container = this;
-   this.children = this.elt;
-   this.node = aNode;
-+  this.toString = function() { return "[root container]"}
- }
- 
- /**
-  * Creates an editor for simple nodes.
-  */
- function GenericEditor(aContainer, aNode)
- {
-   this.elt = aContainer.doc.createElement("span");
-@@ -969,46 +999,77 @@ function DoctypeEditor(aContainer, aNode
-  *
-  * @param MarkupContainer aContainer The container owning this editor.
-  * @param DOMNode aNode The node being edited.
-  * @param string aTemplate The template id to use to build the editor.
-  */
- function TextEditor(aContainer, aNode, aTemplate)
- {
-   this.node = aNode;
-+  this._selected = false;
- 
-   aContainer.markup.template(aTemplate, this);
- 
--  editableField({
--    element: this.value,
--    stopOnReturn: true,
--    trigger: "dblclick",
--    multiline: true,
--    done: function TE_done(aVal, aCommit) {
--      if (!aCommit) {
--        return;
--      }
--      let oldValue = this.node.nodeValue;
--      aContainer.undo.do(function() {
--        this.node.nodeValue = aVal;
--        aContainer.markup.nodeChanged(this.node);
--      }.bind(this), function() {
--        this.node.nodeValue = oldValue;
--        aContainer.markup.nodeChanged(this.node);
--      }.bind(this));
--    }.bind(this)
--  });
-+  let rawNode = aNode.rawNode();
-+  if (rawNode) {
-+    editableField({
-+      element: this.value,
-+      stopOnReturn: true,
-+      trigger: "dblclick",
-+      multiline: true,
-+      done: function TE_done(aVal, aCommit) {
-+        if (!aCommit) {
-+          return;
-+        }
-+        let oldValue = rawNode.nodeValue;
-+        aContainer.undo.do(function() {
-+          rawNode.nodeValue = aVal;
-+          aContainer.markup.nodeChanged(this.node);
-+        }.bind(this), function() {
-+          rawNode.nodeValue = oldValue;
-+          aContainer.markup.nodeChanged(this.node);
-+        }.bind(this));
-+      }.bind(this)
-+    });
-+  }
- 
-   this.update();
- }
- 
- TextEditor.prototype = {
-+  get selected() this._selected,
-+  set selected(aValue) {
-+    if (aValue === this._selected) {
-+      return;
-+    }
-+    this._selected = aValue;
-+    this.update();
-+  },
-+
-   update: function TE_update()
-   {
--    this.value.textContent = this.node.nodeValue;
-+    if (!this.selected || !this.node.incompleteValue) {
-+      let text = this.node.shortValue;
-+      // XXX: internationalize the elliding
-+      if (this.node.incompleteValue) {
-+        text += "…";
-+      }
-+      this.value.textContent = text;
-+    } else {
-+      let longstr = null;
-+      this.node.getNodeValue().then(ret => {
-+        longstr = ret;
-+        return longstr.string();
-+      }).then(str => {
-+        longstr.release().then(null, console.error);
-+        if (this.selected) {
-+          this.value.textContent = str;
-+        }
-+      }).then(null, console.error);
-+    }
-   }
- };
- 
- /**
-  * Creates an editor for an Element node.
-  *
-  * @param MarkupContainer aContainer The container owning this editor.
-  * @param Element aNode The node being edited.
-@@ -1030,53 +1091,57 @@ function ElementEditor(aContainer, aNode
-   this.attrList = null;
-   this.newAttr = null;
-   this.summaryElt = null;
-   this.closeElt = null;
- 
-   // Create the main editor
-   this.template("element", this);
- 
--  if (this.node.firstChild || this.node.textContent.length > 0) {
-+  if (this.node.hasChildren) {
-     // Create the summary placeholder
-     this.template("elementContentSummary", this);
-   }
- 
-   // Create the closing tag
-   this.template("elementClose", this);
- 
-+  this.rawNode = aNode.rawNode();
-+
-   // Make the tag name editable (unless this is a document element)
--  if (aNode != aNode.ownerDocument.documentElement) {
--    this.tag.setAttribute("tabindex", "0");
-+  if (this.rawNode) {
-+    if (!aNode.isDocumentElement) {
-+      this.tag.setAttribute("tabindex", "0");
-+      editableField({
-+        element: this.tag,
-+        trigger: "dblclick",
-+        stopOnReturn: true,
-+        done: this.onTagEdit.bind(this),
-+      });
-+    }
-+
-+    // Make the new attribute space editable.
-     editableField({
--      element: this.tag,
-+      element: this.newAttr,
-       trigger: "dblclick",
-       stopOnReturn: true,
--      done: this.onTagEdit.bind(this),
-+      done: function EE_onNew(aVal, aCommit) {
-+        if (!aCommit) {
-+          return;
-+        }
-+
-+        try {
-+          this._applyAttributes(aVal);
-+        } catch (x) {
-+          return;
-+        }
-+      }.bind(this)
-     });
-   }
- 
--  // Make the new attribute space editable.
--  editableField({
--    element: this.newAttr,
--    trigger: "dblclick",
--    stopOnReturn: true,
--    done: function EE_onNew(aVal, aCommit) {
--      if (!aCommit) {
--        return;
--      }
--
--      try {
--        this._applyAttributes(aVal);
--      } catch (x) {
--        return;
--      }
--    }.bind(this)
--  });
--
-   let tagName = this.node.nodeName.toLowerCase();
-   this.tag.textContent = tagName;
-   this.closeTag.textContent = tagName;
- 
-   this.update();
- }
- 
- ElementEditor.prototype = {
-@@ -1129,56 +1194,59 @@ ElementEditor.prototype = {
-       if (aAttr.name == "id") {
-         before = this.attrList.firstChild;
-       } else if (aAttr.name == "class") {
-         let idNode = this.attrs["id"];
-         before = idNode ? idNode.nextSibling : this.attrList.firstChild;
-       }
-       this.attrList.insertBefore(attr, before);
- 
--      // Make the attribute editable.
--      editableField({
--        element: inner,
--        trigger: "dblclick",
--        stopOnReturn: true,
--        selectAll: false,
--        start: function EE_editAttribute_start(aEditor, aEvent) {
--          // If the editing was started inside the name or value areas,
--          // select accordingly.
--          if (aEvent && aEvent.target === name) {
--            aEditor.input.setSelectionRange(0, name.textContent.length);
--          } else if (aEvent && aEvent.target === val) {
--            let length = val.textContent.length;
--            let editorLength = aEditor.input.value.length;
--            let start = editorLength - (length + 1);
--            aEditor.input.setSelectionRange(start, start + length);
--          } else {
--            aEditor.input.select();
--          }
--        },
--        done: function EE_editAttribute_done(aVal, aCommit) {
--          if (!aCommit) {
--            return;
--          }
-+      if (this.rawNode) {
- 
--          this.undo.startBatch();
-+        // Make the attribute editable.
-+        editableField({
-+          element: inner,
-+          trigger: "dblclick",
-+          stopOnReturn: true,
-+          selectAll: false,
-+          start: function EE_editAttribute_start(aEditor, aEvent) {
-+            // If the editing was started inside the name or value areas,
-+            // select accordingly.
-+            if (aEvent && aEvent.target === name) {
-+              aEditor.input.setSelectionRange(0, name.textContent.length);
-+            } else if (aEvent && aEvent.target === val) {
-+              let length = val.textContent.length;
-+              let editorLength = aEditor.input.value.length;
-+              let start = editorLength - (length + 1);
-+              aEditor.input.setSelectionRange(start, start + length);
-+            } else {
-+              aEditor.input.select();
-+            }
-+          },
-+          done: function EE_editAttribute_done(aVal, aCommit) {
-+            if (!aCommit) {
-+              return;
-+            }
- 
--          // Remove the attribute stored in this editor and re-add any attributes
--          // parsed out of the input element. Restore original attribute if
--          // parsing fails.
--          this._removeAttribute(this.node, aAttr.name);
--          try {
--            this._applyAttributes(aVal, attr);
--            this.undo.endBatch();
--          } catch (e) {
--            this.undo.endBatch();
--            this.undo.undo();
--          }
--        }.bind(this)
--      });
-+            this.undo.startBatch();
-+
-+            // Remove the attribute stored in this editor and re-add any attributes
-+            // parsed out of the input element. Restore original attribute if
-+            // parsing fails.
-+            this._removeAttribute(this.rawNode, aAttr.name);
-+            try {
-+              this._applyAttributes(aVal, attr);
-+              this.undo.endBatch();
-+            } catch (e) {
-+              this.undo.endBatch();
-+              this.undo.undo();
-+            }
-+          }.bind(this)
-+        });
-+      }
- 
-       this.attrs[aAttr.name] = attr;
-     }
- 
-     name.textContent = aAttr.name;
-     val.textContent = aAttr.value;
- 
-     return attr;
-@@ -1201,17 +1269,17 @@ ElementEditor.prototype = {
- 
-     for (let attr of attrs) {
-       let attribute = {
-         name: attr.name,
-         value: attr.value
-       };
-       // Create an attribute editor next to the current attribute if needed.
-       this._createAttribute(attribute, aAttrNode ? aAttrNode.nextSibling : null);
--      this._setAttribute(this.node, attr.name, attr.value);
-+      this._setAttribute(this.rawNode, attr.name, attr.value);
-     }
- 
-     this.undo.endBatch();
-   },
- 
-   /**
-    * Helper function for _setAttribute and _removeAttribute,
-    * returns a function that puts an attribute back the way it was.
-@@ -1258,187 +1326,93 @@ ElementEditor.prototype = {
-    * Handler for the new attribute editor.
-    */
-   _onNewAttribute: function EE_onNewAttribute(aValue, aCommit)
-   {
-     if (!aValue || !aCommit) {
-       return;
-     }
- 
--    this._setAttribute(this.node, aValue, "");
-+    this._setAttribute(this.rawNode, aValue, "");
-     let attr = this._createAttribute({ name: aValue, value: ""});
-     attr.style.removeAttribute("display");
-     attr.querySelector("attrvalue").click();
-   },
- 
- 
-   /**
-    * Called when the tag name editor has is done editing.
-    */
-   onTagEdit: function EE_onTagEdit(aVal, aCommit) {
--    if (!aCommit || aVal == this.node.tagName) {
-+    if (!aCommit || aVal == this.rawNode.tagName) {
-       return;
-     }
- 
-     // Create a new element with the same attributes as the
-     // current element and prepare to replace the current node
-     // with it.
-     try {
--      var newElt = nodeDocument(this.node).createElement(aVal);
-+      var newElt = nodeDocument(this.rawNode).createElement(aVal);
-     } catch(x) {
-       // Failed to create a new element with that tag name, ignore
-       // the change.
-       return;
-     }
- 
--    let attrs = this.node.attributes;
-+    let attrs = this.rawNode.attributes;
- 
-     for (let i = 0 ; i < attrs.length; i++) {
-       newElt.setAttribute(attrs[i].name, attrs[i].value);
-     }
-+    let newFront = this.markup.walker.frontForRawNode(newElt);
-+    let newContainer = this.markup.importNode(newFront);
- 
--    function swapNodes(aOld, aNew) {
--      while (aOld.firstChild) {
--        aNew.appendChild(aOld.firstChild);
-+    // Retain the two nodes we care about here so we can undo.
-+    let walker = this.markup.walker;
-+    promise.all([
-+      walker.retainNode(newFront), walker.retainNode(this.node)
-+    ]).then(() => {
-+      function swapNodes(aOld, aNew) {
-+        aOld.parentNode.insertBefore(aNew, aOld);
-+        while (aOld.firstChild) {
-+          aNew.appendChild(aOld.firstChild);
-+        }
-+        aOld.parentNode.removeChild(aOld);
-       }
--      aOld.parentNode.insertBefore(aNew, aOld);
--      aOld.parentNode.removeChild(aOld);
--    }
- 
--    let markup = this.container.markup;
--
--    // Queue an action to swap out the element.
--    this.undo.do(function() {
--      swapNodes(this.node, newElt);
--
--      // Make sure the new node is imported and is expanded/selected
--      // the same as the current node.
--      let newContainer = markup.importNode(newElt, this.container.expanded);
--      newContainer.expanded = this.container.expanded;
--      if (this.container.selected) {
--        markup.navigate(newContainer);
--      }
--    }.bind(this), function() {
--      swapNodes(newElt, this.node);
--
--      let newContainer = markup._containers.get(newElt);
--      this.container.expanded = newContainer.expanded;
--      if (newContainer.selected) {
--        markup.navigate(this.container);
--      }
--    }.bind(this));
--  },
-+      this.undo.do(() => {
-+        swapNodes(this.rawNode, newElt);
-+        this.markup.setNodeExpanded(newFront, this.container.expanded);
-+        if (this.container.selected) {
-+          this.markup.navigate(newContainer);
-+        }
-+      }, () => {
-+        swapNodes(newElt, this.rawNode);
-+        this.markup.setNodeExpanded(this.node, newContainer.expanded);
-+        if (newContainer.selected) {
-+          this.markup.navigate(this.container);
-+        }
-+      });
-+    }).then(null, console.error);
-+  }
- }
- 
- 
- 
- RootContainer.prototype = {
-   hasChildren: true,
-   expanded: true,
-   update: function RC_update() {}
- };
- 
--function documentWalker(node) {
--  return new DocumentWalker(node, Ci.nsIDOMNodeFilter.SHOW_ALL, whitespaceTextFilter);
--}
--
- function nodeDocument(node) {
-   return node.ownerDocument || (node.nodeType == Ci.nsIDOMNode.DOCUMENT_NODE ? node : null);
- }
- 
- /**
-- * Similar to a TreeWalker, except will dig in to iframes and it doesn't
-- * implement the good methods like previousNode and nextNode.
-- *
-- * See TreeWalker documentation for explanations of the methods.
-- */
--function DocumentWalker(aNode, aShow, aFilter)
--{
--  let doc = nodeDocument(aNode);
--  this.walker = doc.createTreeWalker(nodeDocument(aNode), aShow, aFilter);
--  this.walker.currentNode = aNode;
--  this.filter = aFilter;
--}
--
--DocumentWalker.prototype = {
--  get node() this.walker.node,
--  get whatToShow() this.walker.whatToShow,
--  get expandEntityReferences() this.walker.expandEntityReferences,
--  get currentNode() this.walker.currentNode,
--  set currentNode(aVal) this.walker.currentNode = aVal,
--
--  /**
--   * Called when the new node is in a different document than
--   * the current node, creates a new treewalker for the document we've
--   * run in to.
--   */
--  _reparentWalker: function DW_reparentWalker(aNewNode) {
--    if (!aNewNode) {
--      return null;
--    }
--    let doc = nodeDocument(aNewNode);
--    let walker = doc.createTreeWalker(doc,
--      this.whatToShow, this.filter, this.expandEntityReferences);
--    walker.currentNode = aNewNode;
--    this.walker = walker;
--    return aNewNode;
--  },
--
--  parentNode: function DW_parentNode()
--  {
--    let currentNode = this.walker.currentNode;
--    let parentNode = this.walker.parentNode();
--
--    if (!parentNode) {
--      if (currentNode && currentNode.nodeType == Ci.nsIDOMNode.DOCUMENT_NODE
--          && currentNode.defaultView) {
--        let embeddingFrame = currentNode.defaultView.frameElement;
--        if (embeddingFrame) {
--          return this._reparentWalker(embeddingFrame);
--        }
--      }
--      return null;
--    }
--
--    return parentNode;
--  },
--
--  firstChild: function DW_firstChild()
--  {
--    let node = this.walker.currentNode;
--    if (!node)
--      return;
--    if (node.contentDocument) {
--      return this._reparentWalker(node.contentDocument);
--    } else if (node.getSVGDocument) {
--      return this._reparentWalker(node.getSVGDocument());
--    }
--    return this.walker.firstChild();
--  },
--
--  lastChild: function DW_lastChild()
--  {
--    let node = this.walker.currentNode;
--    if (!node)
--      return;
--    if (node.contentDocument) {
--      return this._reparentWalker(node.contentDocument);
--    } else if (node.getSVGDocument) {
--      return this._reparentWalker(node.getSVGDocument());
--    }
--    return this.walker.lastChild();
--  },
--
--  previousSibling: function DW_previousSibling() this.walker.previousSibling(),
--  nextSibling: function DW_nextSibling() this.walker.nextSibling(),
--
--  // XXX bug 785143: not doing previousNode or nextNode, which would sure be useful.
--};
--
--/**
-  * Properly escape attribute values.
-  *
-  * @param  {String} attr
-  *         The attributes for which the values are to be escaped.
-  * @return {Array}
-  *         An array of attribute names and their escaped values.
-  */
- function escapeAttributeValues(attr) {
-diff --git a/browser/devtools/markupview/test/browser_inspector_markup_edit.js b/browser/devtools/markupview/test/browser_inspector_markup_edit.js
---- a/browser/devtools/markupview/test/browser_inspector_markup_edit.js
-+++ b/browser/devtools/markupview/test/browser_inspector_markup_edit.js
-@@ -73,42 +73,41 @@ function test() {
-       before: function() {
-         assertAttributes(doc.querySelector("#node1"), {
-           id: "node1",
-           class: "node1"
-         });
-       },
-       execute: function(after) {
-         inspector.once("markupmutation", after);
--        let editor = markup.getContainer(doc.querySelector("#node1")).editor;
-+        let editor = getContainerForRawNode(markup, doc.querySelector("#node1")).editor;
-         let attr = editor.attrs["class"].querySelector(".editable");
-         editField(attr, 'class="changednode1"');
-       },
-       after: function() {
-         assertAttributes(doc.querySelector("#node1"), {
-           id: "node1",
-           class: "changednode1"
-         });
-       }
-     },
--
-     {
-       desc: 'Try changing an attribute to a quote (") - this should result ' +
-             'in it being set to an empty string',
-       before: function() {
-         assertAttributes(doc.querySelector("#node22"), {
-           id: "node22",
-           class: "unchanged"
-         });
-       },
-       execute: function(after) {
--        let editor = markup.getContainer(doc.querySelector("#node22")).editor;
-+        let editor = getContainerForRawNode(markup, doc.querySelector("#node22")).editor;
-         let attr = editor.attrs["class"].querySelector(".editable");
-         editField(attr, 'class="""');
--        executeSoon(after);
-+        inspector.once("markupmutation", after);
-       },
-       after: function() {
-         assertAttributes(doc.querySelector("#node22"), {
-           id: "node22",
-           class: ""
-         });
-       }
-     },
-@@ -118,17 +117,17 @@ function test() {
-       before: function() {
-         assertAttributes(doc.querySelector("#node4"), {
-           id: "node4",
-           class: "node4"
-         });
-       },
-       execute: function(after) {
-         inspector.once("markupmutation", after);
--        let editor = markup.getContainer(doc.querySelector("#node4")).editor;
-+        let editor = getContainerForRawNode(markup, doc.querySelector("#node4")).editor;
-         let attr = editor.attrs["class"].querySelector(".editable");
-         editField(attr, '');
-       },
-       after: function() {
-         assertAttributes(doc.querySelector("#node4"), {
-           id: "node4",
-         });
-       }
-@@ -138,17 +137,17 @@ function test() {
-       desc: "Add an attribute by clicking the empty space after a node",
-       before: function() {
-         assertAttributes(doc.querySelector("#node14"), {
-           id: "node14",
-         });
-       },
-       execute: function(after) {
-         inspector.once("markupmutation", after);
--        let editor = markup.getContainer(doc.querySelector("#node14")).editor;
-+        let editor = getContainerForRawNode(markup, doc.querySelector("#node14")).editor;
-         let attr = editor.newAttr;
-         editField(attr, 'class="newclass" style="color:green"');
-       },
-       after: function() {
-         assertAttributes(doc.querySelector("#node14"), {
-           id: "node14",
-           class: "newclass",
-           style: "color:green"
-@@ -161,20 +160,20 @@ function test() {
-             'clicking the empty space after a node - this should result ' +
-             'in it being set to an empty string',
-       before: function() {
-         assertAttributes(doc.querySelector("#node23"), {
-           id: "node23",
-         });
-       },
-       execute: function(after) {
--        let editor = markup.getContainer(doc.querySelector("#node23")).editor;
-+        let editor = getContainerForRawNode(markup, doc.querySelector("#node23")).editor;
-         let attr = editor.newAttr;
-         editField(attr, 'class="newclass" style="""');
--        executeSoon(after);
-+        inspector.once("markupmutation", after);
-       },
-       after: function() {
-         assertAttributes(doc.querySelector("#node23"), {
-           id: "node23",
-           class: "newclass",
-           style: ""
-         });
-       }
-@@ -183,20 +182,20 @@ function test() {
-     {
-       desc: "Try add attributes by adding to an existing attribute's entry",
-       before: function() {
-         assertAttributes(doc.querySelector("#node24"), {
-           id: "node24",
-         });
-       },
-       execute: function(after) {
--        let editor = markup.getContainer(doc.querySelector("#node24")).editor;
-+        let editor = getContainerForRawNode(markup, doc.querySelector("#node24")).editor;
-         let attr = editor.attrs["id"].querySelector(".editable");
-         editField(attr, attr.textContent + ' class="""');
--        executeSoon(after);
-+        inspector.once("markupmutation", after);
-       },
-       after: function() {
-         assertAttributes(doc.querySelector("#node24"), {
-           id: "node24",
-           class: ""
-         });
-       }
-     },
-@@ -205,17 +204,17 @@ function test() {
-       desc: "Edit text",
-       before: function() {
-         let node = doc.querySelector('.node6').firstChild;
-         is(node.nodeValue, "line6", "Text should be unchanged");
-       },
-       execute: function(after) {
-         inspector.once("markupmutation", after);
-         let node = doc.querySelector('.node6').firstChild;
--        let editor = markup.getContainer(node).editor;
-+        let editor = getContainerForRawNode(markup, node).editor;
-         let field = editor.elt.querySelector("pre");
-         editField(field, "New text");
-       },
-       after: function() {
-         let node = doc.querySelector('.node6').firstChild;
-         is(node.nodeValue, "New text", "Text should be changed.");
-       },
-     },
-@@ -224,17 +223,17 @@ function test() {
-       desc: "Add an attribute value containing < > &uuml; \" & '",
-       before: function() {
-         assertAttributes(doc.querySelector("#node25"), {
-           id: "node25",
-         });
-       },
-       execute: function(after) {
-         inspector.once("markupmutation", after);
--        let editor = markup.getContainer(doc.querySelector("#node25")).editor;
-+        let editor = getContainerForRawNode(markup, doc.querySelector("#node25")).editor;
-         let attr = editor.newAttr;
-         editField(attr, 'src="somefile.html?param1=<a>&param2=&uuml;"bl\'ah"');
-       },
-       after: function() {
-         assertAttributes(doc.querySelector("#node25"), {
-           id: "node25",
-           src: "somefile.html?param1=&lt;a&gt;&param2=&uuml;&quot;bl&apos;ah"
-         });
-@@ -255,122 +254,120 @@ function test() {
-     var target = TargetFactory.forTab(gBrowser.selectedTab);
-     gDevTools.showToolbox(target, "inspector").then(function(toolbox) {
-       inspector = toolbox.getCurrentPanel();
-       startTests();
-     });
-   }
- 
-   function startTests() {
--    let startNode = doc.documentElement.cloneNode();
-     markup = inspector.markup;
--    markup.expandAll();
-+    markup.expandAll().then(() => {
- 
--    let cursor = 0;
-+      let cursor = 0;
- 
--    function nextEditTest() {
--      executeSoon(function() {
--        if (cursor >= edits.length) {
--          addAttributes();
--        } else {
--          let step = edits[cursor++];
--          info("START " + step.desc);
--          if (step.setup) {
--            step.setup();
-+      function nextEditTest() {
-+        executeSoon(function() {
-+          if (cursor >= edits.length) {
-+            addAttributes();
-+          } else {
-+            let step = edits[cursor++];
-+            info("START " + step.desc);
-+            if (step.setup) {
-+              step.setup();
-+            }
-+            step.before();
-+            info("before execute");
-+            step.execute(function() {
-+              info("after execute");
-+              step.after();
-+              ok(markup.undo.canUndo(), "Should be able to undo.");
-+              markup.undo.undo();
-+              inspector.once("markupmutation", () => {
-+                step.before();
-+                ok(markup.undo.canRedo(), "Should be able to redo.");
-+                markup.undo.redo();
-+                inspector.once("markupmutation", () => {
-+                  step.after();
-+                  info("END " + step.desc);
-+                  nextEditTest();
-+                });
-+              });
-+            });
-           }
--          step.before();
--          info("before execute");
--          step.execute(function() {
--            info("after execute");
--            step.after();
--            ok(markup.undo.canUndo(), "Should be able to undo.");
--            markup.undo.undo();
--            step.before();
--            ok(markup.undo.canRedo(), "Should be able to redo.");
--            markup.undo.redo();
--            step.after();
--            info("END " + step.desc);
--            nextEditTest();
--          });
--        }
--      });
--    }
--    nextEditTest();
-+        });
-+      }
-+      nextEditTest();
-+    });
-   }
- 
-   function addAttributes() {
-     let 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",
-         });
- 
--        /**
--         * XXX: disabled until the remote markup view is enabled
--         * is(inspector.highlighter.nodeInfo.classesBox.textContent, "",
--         *  "No classes in the infobar before edit.");
--         */
-+        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 = markup.getContainer(doc.querySelector("#node18")).editor;
-+        let editor = getContainerForRawNode(markup, doc.querySelector("#node18")).editor;
-         let attr = editor.attrs["id"].querySelector(".editable");
-         editField(attr, attr.textContent + ' class="newclass" style="color:green"');
-       },
-       after: function() {
-         assertAttributes(doc.querySelector("#node18"), {
-           id: "node18",
-           class: "newclass",
-           style: "color:green"
-         });
- 
--        /**
--         * XXX: disabled until the remote markup view is enabled
--         *is(inspector.highlighter.nodeInfo.classesBox.textContent, ".newclass",
--         *  "Correct classes in the infobar after edit.");
--         */
-+        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",
-       setup: function() {
-         inspector.selection.setNode(doc.querySelector("#retag-me"));
-       },
-       before: function() {
-         let node = doc.querySelector("#retag-me");
--        let container = markup.getContainer(node);
-+        let container = getContainerForRawNode(markup, node);
- 
-         is(node.tagName, "DIV", "retag-me should be a div.");
-         ok(container.selected, "retag-me should be selected.");
-         ok(container.expanded, "retag-me should be expanded.");
-         is(doc.querySelector("#retag-me-2").parentNode, node,
-           "retag-me-2 should be a child of the old element.");
-       },
-       execute: function(after) {
-         inspector.once("markupmutation", after);
-         let node = doc.querySelector("#retag-me");
--        let editor = markup.getContainer(node).editor;
-+        let editor = getContainerForRawNode(markup, node).editor;
-         let field = editor.tag;
-         editField(field, "p");
-       },
-       after: function() {
-         let node = doc.querySelector("#retag-me");
--        let container = markup.getContainer(node);
-+        let container = getContainerForRawNode(markup, node);
-         is(node.tagName, "P", "retag-me should be a p.");
-         ok(container.selected, "retag-me should be selected.");
-         ok(container.expanded, "retag-me should be expanded.");
-         is(doc.querySelector("#retag-me-2").parentNode, node,
-           "retag-me-2 should be a child of the new element.");
-       }
-     };
-     testAsyncSetup(test, removeElementWithDelete);
-@@ -394,49 +391,51 @@ function test() {
-     };
-     testAsyncExecute(test, finishUp);
-   }
- 
-   function testAsyncExecute(test, callback) {
-     info("START " + test.desc);
- 
-     test.before();
--    inspector.selection.once("new-node", function BIMET_testAsyncExecNewNode() {
-+    inspector.once("inspector-updated", function BIMET_testAsyncExecNewNode() {
-       test.executeCont();
--      test.after();
--      undoRedo(test, callback);
-+      inspector.once("markupmutation", () => {
-+        test.after();
-+        undoRedo(test, callback);
-+      });
-     });
-     executeSoon(function BIMET_setNode1() {
-       test.execute();
-     });
-   }
- 
-   function testAsyncSetup(test, callback) {
-     info("START " + test.desc);
- 
--    inspector.selection.once("new-node", function BIMET_testAsyncSetupNewNode() {
-+    inspector.once("inspector-updated", function BIMET_testAsyncSetupNewNode() {
-       test.before();
-       test.execute(function() {
-         test.after();
-         undoRedo(test, callback);
-       });
-     });
-     executeSoon(function BIMET_setNode2() {
-       test.setup();
-     });
-   }
- 
-   function undoRedo(test, callback) {
-     ok(markup.undo.canUndo(), "Should be able to undo.");
-     markup.undo.undo();
--    executeSoon(function() {
-+    inspector.once("markupmutation", () => {
-       test.before();
-       ok(markup.undo.canRedo(), "Should be able to redo.");
-       markup.undo.redo();
--      executeSoon(function() {
-+      inspector.once("markupmutation", () => {
-         test.after();
-         info("END " + test.desc);
-         callback();
-       });
-     });
-   }
- 
-   function finishUp() {
-diff --git a/browser/devtools/markupview/test/browser_inspector_markup_mutation.js b/browser/devtools/markupview/test/browser_inspector_markup_mutation.js
---- a/browser/devtools/markupview/test/browser_inspector_markup_mutation.js
-+++ b/browser/devtools/markupview/test/browser_inspector_markup_mutation.js
-@@ -5,16 +5,20 @@ http://creativecommons.org/publicdomain/
-  * Tests that various mutations to the dom update the markup tool correctly.
-  * The test for comparing the markup tool to the real dom is a bit weird:
-  * - Select the text in the markup tool
-  * - Parse that as innerHTML in a document we've created for the purpose.
-  * - Remove extraneous whitespace in that tree
-  * - Compare it to the real dom with isEqualNode.
-  */
- 
-+function fail(err) {
-+  ok(false, err)
-+}
-+
- function test() {
-   waitForExplicitFinish();
- 
-   // Will hold the doc we're viewing
-   let contentTab;
-   let doc;
- 
-   // Holds the MarkupTool object we're testing.
-@@ -40,20 +44,23 @@ function test() {
-         node.parentNode.removeChild(node);
-       }
-     }
-   }
- 
-   // Verify that the markup in the tool is the same as the markup in the document.
-   function checkMarkup()
-   {
--    markup.expandAll();
-+    return markup.expandAll().then(checkMarkup2);
-+  }
- 
-+  function checkMarkup2()
-+  {
-     let contentNode = doc.querySelector("body");
--    let panelNode = markup._containers.get(contentNode).elt;
-+    let panelNode = getContainerForRawNode(markup, contentNode).elt;
-     let parseNode = parseDoc.querySelector("body");
- 
-     // Grab the text from the markup panel...
-     let sel = panelNode.ownerDocument.defaultView.getSelection();
-     sel.selectAllChildren(panelNode);
- 
-     // Parse it
-     parseNode.outerHTML = sel;
-@@ -149,30 +156,32 @@ function test() {
-     gDevTools.showToolbox(target, "inspector").then(function(toolbox) {
-       inspector = toolbox.getCurrentPanel();
-       startTests();
-     });
-   }
- 
-   function startTests() {
-     markup = inspector.markup;
--    checkMarkup();
--    nextStep(0);
-+    checkMarkup().then(() => {
-+      nextStep(0);
-+    }).then(null, fail);
-   }
- 
-   function nextStep(cursor) {
-     if (cursor >= mutations.length) {
-       finishUp();
-       return;
-     }
-     mutations[cursor]();
-     inspector.once("markupmutation", function() {
-       executeSoon(function() {
--        checkMarkup();
--        nextStep(cursor + 1);
-+        checkMarkup().then(() => {
-+          nextStep(cursor + 1);
-+        }).then(null, fail);
-       });
-     });
-   }
- 
-   function finishUp() {
-     doc = inspector = null;
-     gBrowser.removeTab(contentTab);
-     gBrowser.removeTab(parseTab);
-diff --git a/browser/devtools/markupview/test/browser_inspector_markup_navigation.js b/browser/devtools/markupview/test/browser_inspector_markup_navigation.js
---- a/browser/devtools/markupview/test/browser_inspector_markup_navigation.js
-+++ b/browser/devtools/markupview/test/browser_inspector_markup_navigation.js
-@@ -79,17 +79,17 @@ function test() {
-   }, true);
- 
-   content.location = "http://mochi.test:8888/browser/browser/devtools/markupview/test/browser_inspector_markup_navigation.html";
- 
-   function setupTest() {
-     var target = TargetFactory.forTab(gBrowser.selectedTab);
-     gDevTools.showToolbox(target, "inspector").then(function(toolbox) {
-       inspector = toolbox.getCurrentPanel();
--      startNavigation();
-+      inspector.once("inspector-updated", startNavigation);
-     });
-   }
- 
-   function startNavigation() {
-     nextStep(0);
-   }
- 
-   function nextStep(cursor) {
-@@ -121,31 +121,31 @@ function test() {
-       case "pagedown":
-         EventUtils.synthesizeKey("VK_PAGE_DOWN", {});
-         break;
-       case "home":
-         EventUtils.synthesizeKey("VK_HOME", {});
-         break;
-     }
- 
--    executeSoon(function BIMNT_newNode() {
-+    inspector.markup._waitForChildren().then(() => executeSoon(function BIMNT_newNode() {
-       let node = inspector.selection.node;
- 
-       if (className == "*comment*") {
-         is(node.nodeType, Node.COMMENT_NODE, "[" + cursor + "] should be a comment after moving " + key);
-       } else if (className == "*text*") {
-         is(node.nodeType, Node.TEXT_NODE, "[" + cursor + "] should be text after moving " + key);
-       } else if (className == "*doctype*") {
-         is(node.nodeType, Node.DOCUMENT_TYPE_NODE, "[" + cursor + "] should be doctype after moving " + key);
-       } else {
-         is(node.className, className, "[" + cursor + "] right node selected: " + className + " after moving " + key);
-       }
- 
-       nextStep(cursor + 1);
--    });
-+    }));
-   }
- 
-   function finishUp() {
-     doc = inspector = null;
-     gBrowser.removeCurrentTab();
-     finish();
-   }
- }
-diff --git a/browser/devtools/markupview/test/browser_inspector_markup_subset.js b/browser/devtools/markupview/test/browser_inspector_markup_subset.js
---- a/browser/devtools/markupview/test/browser_inspector_markup_subset.js
-+++ b/browser/devtools/markupview/test/browser_inspector_markup_subset.js
-@@ -20,36 +20,44 @@ function test() {
- 
-   let inspector;
- 
-   // Holds the MarkupTool object we're testing.
-   let markup;
- 
-   function assertChildren(expected)
-   {
--    let container = markup.getContainer(doc.querySelector("body"));
-+    let container = getContainerForRawNode(markup, doc.querySelector("body"));
-     let found = [];
-     for (let child of container.children.children) {
-       if (child.classList.contains("more-nodes")) {
-         found += "*more*";
-       } else {
-         found += child.container.node.getAttribute("id");
-       }
-     }
--    is(expected, found, "Got the expected children.");
-+    is(found, expected, "Got the expected children.");
-   }
- 
-   function forceReload()
-   {
--    let container = markup.getContainer(doc.querySelector("body"));
-+    let container = getContainerForRawNode(markup, doc.querySelector("body"));
-     container.childrenDirty = true;
-   }
- 
-   let selections = [
-     {
-+      desc: "Select the last item",
-+      selector: "#z",
-+      before: function() {},
-+      after: function() {
-+        assertChildren("*more*vwxyz");
-+      }
-+    },
-+    {
-       desc: "Select the first item",
-       selector: "#a",
-       before: function() {
-       },
-       after: function() {
-         assertChildren("abcde*more*");
-       }
-     },
-@@ -92,55 +100,48 @@ function test() {
-     doc = content.document;
-     waitForFocus(setupTest, content);
-   }, true);
-   content.location = "http://mochi.test:8888/browser/browser/devtools/markupview/test/browser_inspector_markup_subset.html";
- 
-   function setupTest() {
-     var target = TargetFactory.forTab(gBrowser.selectedTab);
-     let toolbox = gDevTools.showToolbox(target, "inspector").then(function(toolbox) {
--      toolbox.once("inspector-selected", function SE_selected(id, aInspector) {
--        inspector = aInspector;
--        markup = inspector.markup;
--        runNextSelection();
--      });
--    });
--  }
--
--  function runTests() {
--    inspector.selection.once("new-node", startTests);
--    executeSoon(function() {
--      inspector.selection.setNode(doc.body);
-+      inspector = toolbox.getCurrentPanel();
-+      markup = inspector.markup;
-+      inspector.once("inspector-updated", runNextSelection);
-     });
-   }
- 
-   function runNextSelection() {
-     let selection = selections.shift();
-     if (!selection) {
-       clickMore();
-       return;
-     }
- 
-     info(selection.desc);
-     selection.before();
--    inspector.selection.once("new-node", function() {
-+    inspector.once("inspector-updated", function() {
-       selection.after();
-       runNextSelection();
-     });
-     inspector.selection.setNode(doc.querySelector(selection.selector));
-   }
- 
-   function clickMore() {
-     info("Check that clicking more loads the whole thing.");
-     // Make sure that clicking the "more" button loads all the nodes.
--    let container = markup.getContainer(doc.querySelector("body"));
-+    let container = getContainerForRawNode(markup, doc.querySelector("body"));
-     let button = container.elt.querySelector("button");
-     button.click();
--    assertChildren("abcdefghijklmnopqrstuvwxyz");
--    finishUp();
-+    markup._waitForChildren().then(() => {
-+      assertChildren("abcdefghijklmnopqrstuvwxyz");
-+      finishUp();
-+    });
-   }
- 
-   function finishUp() {
-     doc = inspector = null;
-     gBrowser.removeCurrentTab();
-     finish();
-   }
- }
-diff --git a/browser/devtools/markupview/test/head.js b/browser/devtools/markupview/test/head.js
---- a/browser/devtools/markupview/test/head.js
-+++ b/browser/devtools/markupview/test/head.js
-@@ -11,8 +11,44 @@ let TargetFactory = devtools.TargetFacto
- function clearUserPrefs()
- {
-   Services.prefs.clearUserPref("devtools.inspector.htmlPanelOpen");
-   Services.prefs.clearUserPref("devtools.inspector.sidebarOpen");
-   Services.prefs.clearUserPref("devtools.inspector.activeSidebar");
- }
- 
- registerCleanupFunction(clearUserPrefs);
-+
-+Services.prefs.setBoolPref("devtools.debugger.log", true);
-+SimpleTest.registerCleanupFunction(() => {
-+  Services.prefs.clearUserPref("devtools.debugger.log");
-+});
-+
-+function getContainerForRawNode(markupView, rawNode) {
-+  let front = markupView.walker.frontForRawNode(rawNode);
-+  let container = markupView.getContainer(front);
-+  return container;
-+}
-+
-+
-+Services.prefs.setBoolPref("devtools.debugger.log", true);
-+SimpleTest.registerCleanupFunction(() => {
-+  Services.prefs.clearUserPref("devtools.debugger.log");
-+});
-+
-+function getContainerForRawNode(markupView, rawNode) {
-+  let front = markupView.walker.frontForRawNode(rawNode);
-+  let container = markupView.getContainer(front);
-+  return container;
-+}
-+
-+
-+Services.prefs.setBoolPref("devtools.debugger.log", true);
-+SimpleTest.registerCleanupFunction(() => {
-+  Services.prefs.clearUserPref("devtools.debugger.log");
-+});
-+
-+function getContainerForRawNode(markupView, rawNode) {
-+  let front = markupView.walker.frontForRawNode(rawNode);
-+  let container = markupView.getContainer(front);
-+  return container;
-+}
-+
deleted file mode 100644
--- a/search-box-remote.diff
+++ /dev/null
@@ -1,482 +0,0 @@
-# HG changeset patch
-# User Dave Camp <dcamp@mozilla.com>
-# Date 1370924326 25200
-#      Mon Jun 10 21:18:46 2013 -0700
-# Node ID f2cb449dbdfbb5f006856d91943417c22bbd230e
-# Parent  a5aab8dafa96ddb3551c06ffcd701ba122a63bce
-imported patch search-box-remote.diff
-* * *
-imported patch search-fixes-2.diff
-* * *
-imported patch selector-search-fix.diff
-
-diff --git a/browser/devtools/inspector/inspector-panel.js b/browser/devtools/inspector/inspector-panel.js
---- a/browser/devtools/inspector/inspector-panel.js
-+++ b/browser/devtools/inspector/inspector-panel.js
-@@ -219,28 +219,29 @@ InspectorPanel.prototype = {
-    */
-   setupSearchBox: function InspectorPanel_setupSearchBox() {
-     let searchDoc;
-     if (this.target.isLocalTab) {
-       searchDoc = this.browser.contentDocument;
-     } else if (this.target.window) {
-       searchDoc = this.target.window.document;
-     } else {
--      return;
-+      searchDoc = null;
-     }
-     // Initiate the selectors search object.
--    let setNodeFunction = function(node) {
--      this.selection.setNode(node, "selectorsearch");
-+    let setNodeFunction = function(eventName, node) {
-+      this.selection.setNodeFront(node, "selectorsearch");
-     }.bind(this);
-     if (this.searchSuggestions) {
-       this.searchSuggestions.destroy();
-       this.searchSuggestions = null;
-     }
-     this.searchBox = this.panelDoc.getElementById("inspector-searchbox");
--    this.searchSuggestions = new SelectorSearch(searchDoc, this.searchBox, setNodeFunction);
-+    this.searchSuggestions = new SelectorSearch(this, searchDoc, this.searchBox);
-+    this.searchSuggestions.on("node-selected", setNodeFunction);
-   },
- 
-   /**
-    * Build the sidebar.
-    */
-   setupSidebar: function InspectorPanel_setupSidebar() {
-     let tabbox = this.panelDoc.querySelector("#inspector-sidebar");
-     this.sidebar = new ToolSidebar(tabbox, this, "inspector");
-diff --git a/browser/devtools/inspector/selector-search.js b/browser/devtools/inspector/selector-search.js
---- a/browser/devtools/inspector/selector-search.js
-+++ b/browser/devtools/inspector/selector-search.js
-@@ -1,39 +1,42 @@
- /* 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} = require("chrome");
-+const EventEmitter = require("devtools/shared/event-emitter");
-+const promise = require("sdk/core/promise");
- 
- loader.lazyGetter(this, "AutocompletePopup", () => {
-   return Cu.import("resource:///modules/devtools/AutocompletePopup.jsm", {}).AutocompletePopup;
- });
- 
- // Maximum number of selector suggestions shown in the panel.
- const MAX_SUGGESTIONS = 15;
- 
- /**
-  * Converts any input box on a page to a CSS selector search and suggestion box.
-  *
-  * @constructor
-+ * @param InspectorPanel aInspector
-+ *        The InspectorPanel whose `walker` attribute should be used for
-+ *        document traversal.
-  * @param nsIDOMDocument aContentDocument
-- *        The content document which inspector is attached to.
-+ *        The content document which inspector is attached to, or null if
-+ *        a remote document.
-  * @param nsiInputElement aInputNode
-  *        The input element to which the panel will be attached and from where
-  *        search input will be taken.
-- * @param Function aCallback
-- *        The method to callback when a search is available.
-- *        This method is called with the matched node as the first argument.
-  */
--function SelectorSearch(aContentDocument, aInputNode, aCallback) {
-+function SelectorSearch(aInspector, aContentDocument, aInputNode) {
-+  this.inspector = aInspector;
-   this.doc = aContentDocument;
--  this.callback = aCallback;
-   this.searchBox = aInputNode;
-   this.panelDoc = this.searchBox.ownerDocument;
- 
-   // initialize variables.
-   this._lastSearched = null;
-   this._lastValidSearch = "";
-   this._lastToLastValidSearch = null;
-   this._searchResults = null;
-@@ -57,22 +60,30 @@ function SelectorSearch(aContentDocument
-     onClick: this._onListBoxKeypress,
-     onKeypress: this._onListBoxKeypress,
-   };
-   this.searchPopup = new AutocompletePopup(this.panelDoc, options);
- 
-   // event listeners.
-   this.searchBox.addEventListener("command", this._onHTMLSearch, true);
-   this.searchBox.addEventListener("keypress", this._onSearchKeypress, true);
-+
-+  // For testing, we need to be able to wait for the most recent node request
-+  // to finish.  Tests can watch this promise for that.
-+  this._lastQuery = promise.resolve(null);
-+
-+  EventEmitter.decorate(this);
- }
- 
- exports.SelectorSearch = SelectorSearch;
- 
- SelectorSearch.prototype = {
- 
-+  get walker() this.inspector.walker,
-+
-   // The possible states of the query.
-   States: {
-     CLASS: "class",
-     ID: "id",
-     TAG: "tag",
-   },
- 
-   // The current state of the query.
-@@ -163,101 +174,122 @@ SelectorSearch.prototype = {
-     this.searchBox.removeEventListener("keypress", this._onSearchKeypress, true);
-     this.searchPopup.destroy();
-     this.searchPopup = null;
-     this.searchBox = null;
-     this.doc = null;
-     this.panelDoc = null;
-     this._searchResults = null;
-     this._searchSuggestions = null;
--    this.callback = null;
-+    EventEmitter.decorate(this);
-+  },
-+
-+  _selectResult: function(index) {
-+    return this._searchResults.item(index).then(node => {
-+      this.emit("node-selected", node);
-+    });
-   },
- 
-   /**
-    * The command callback for the input box. This function is automatically
-    * invoked as the user is typing if the input box type is search.
-    */
-   _onHTMLSearch: function SelectorSearch__onHTMLSearch() {
-     let query = this.searchBox.value;
-     if (query == this._lastSearched) {
-       return;
-     }
-     this._lastSearched = query;
-+    this._searchResults = null;
-     this._searchIndex = 0;
- 
-     if (query.length == 0) {
-       this._lastValidSearch = "";
-       this.searchBox.removeAttribute("filled");
-       this.searchBox.classList.remove("devtools-no-search-result");
-       if (this.searchPopup.isOpen) {
-         this.searchPopup.hidePopup();
-       }
-       return;
-     }
- 
-     this.searchBox.setAttribute("filled", true);
--    try {
--      this._searchResults = this.doc.querySelectorAll(query);
--    }
--    catch (ex) {
--      this._searchResults = [];
--    }
--    if (this._searchResults.length > 0) {
--      this._lastValidSearch = query;
--      // Even though the selector matched atleast one node, there is still
--      // possibility of suggestions.
--      if (query.match(/[\s>+]$/)) {
--        // If the query has a space or '>' at the end, create a selector to match
--        // the children of the selector inside the search box by adding a '*'.
--        this._lastValidSearch += "*";
--      }
--      else if (query.match(/[\s>+][\.#a-zA-Z][\.#>\s+]*$/)) {
--        // If the query is a partial descendant selector which does not matches
--        // any node, remove the last incomplete part and add a '*' to match
--        // everything. For ex, convert 'foo > b' to 'foo > *' .
--        let lastPart = query.match(/[\s>+][\.#a-zA-Z][^>\s+]*$/)[0];
--        this._lastValidSearch = query.slice(0, -1 * lastPart.length + 1) + "*";
-+    let queryList = null;
-+
-+    this._lastQuery = this.walker.querySelectorAll(this.walker.rootNode, query).then(list => {
-+      return list;
-+    }, (err) => {
-+      // Failures are ok here, just use a null item list;
-+      return null;
-+    }).then(queryList => {
-+      // Value has changed since we started this request, we're done.
-+      if (query != this.searchBox.value) {
-+        if (queryList) {
-+          queryList.release();
-+        }
-+        return promise.reject(null);
-       }
- 
--      if (!query.slice(-1).match(/[\.#\s>+]/)) {
--        // Hide the popup if we have some matching nodes and the query is not
--        // ending with [.# >] which means that the selector is not at the
--        // beginning of a new class, tag or id.
--        if (this.searchPopup.isOpen) {
--          this.searchPopup.hidePopup();
-+      this._searchResults = queryList;
-+      if (this._searchResults && this._searchResults.length > 0) {
-+        this._lastValidSearch = query;
-+        // Even though the selector matched atleast one node, there is still
-+        // possibility of suggestions.
-+        if (query.match(/[\s>+]$/)) {
-+          // If the query has a space or '>' at the end, create a selector to match
-+          // the children of the selector inside the search box by adding a '*'.
-+          this._lastValidSearch += "*";
-         }
-+        else if (query.match(/[\s>+][\.#a-zA-Z][\.#>\s+]*$/)) {
-+          // If the query is a partial descendant selector which does not matches
-+          // any node, remove the last incomplete part and add a '*' to match
-+          // everything. For ex, convert 'foo > b' to 'foo > *' .
-+          let lastPart = query.match(/[\s>+][\.#a-zA-Z][^>\s+]*$/)[0];
-+          this._lastValidSearch = query.slice(0, -1 * lastPart.length + 1) + "*";
-+        }
-+
-+        if (!query.slice(-1).match(/[\.#\s>+]/)) {
-+          // Hide the popup if we have some matching nodes and the query is not
-+          // ending with [.# >] which means that the selector is not at the
-+          // beginning of a new class, tag or id.
-+          if (this.searchPopup.isOpen) {
-+            this.searchPopup.hidePopup();
-+          }
-+        }
-+        else {
-+          this.showSuggestions();
-+        }
-+        this.searchBox.classList.remove("devtools-no-search-result");
-+
-+        return this._selectResult(0);
-       }
-       else {
-+        if (query.match(/[\s>+]$/)) {
-+          this._lastValidSearch = query + "*";
-+        }
-+        else if (query.match(/[\s>+][\.#a-zA-Z][\.#>\s+]*$/)) {
-+          let lastPart = query.match(/[\s+>][\.#a-zA-Z][^>\s+]*$/)[0];
-+          this._lastValidSearch = query.slice(0, -1 * lastPart.length + 1) + "*";
-+        }
-+        this.searchBox.classList.add("devtools-no-search-result");
-         this.showSuggestions();
-       }
--      this.searchBox.classList.remove("devtools-no-search-result");
--      this.callback(this._searchResults[0]);
--    }
--    else {
--      if (query.match(/[\s>+]$/)) {
--        this._lastValidSearch = query + "*";
--      }
--      else if (query.match(/[\s>+][\.#a-zA-Z][\.#>\s+]*$/)) {
--        let lastPart = query.match(/[\s+>][\.#a-zA-Z][^>\s+]*$/)[0];
--        this._lastValidSearch = query.slice(0, -1 * lastPart.length + 1) + "*";
--      }
--      this.searchBox.classList.add("devtools-no-search-result");
--      this.showSuggestions();
--    }
-+      return undefined;
-+    });
-   },
- 
-   /**
-    * Handles keypresses inside the input box.
-    */
-   _onSearchKeypress: function SelectorSearch__onSearchKeypress(aEvent) {
-     let query = this.searchBox.value;
-     switch(aEvent.keyCode) {
-       case aEvent.DOM_VK_ENTER:
-       case aEvent.DOM_VK_RETURN:
--        if (query == this._lastSearched) {
-+        if (query == this._lastSearched && this._searchResults) {
-           this._searchIndex = (this._searchIndex + 1) % this._searchResults.length;
-         }
-         else {
-           this._onHTMLSearch();
-           return;
-         }
-         break;
- 
-@@ -310,18 +342,18 @@ SelectorSearch.prototype = {
-         return;
- 
-       default:
-         return;
-     }
- 
-     aEvent.preventDefault();
-     aEvent.stopPropagation();
--    if (this._searchResults.length > 0) {
--      this.callback(this._searchResults[this._searchIndex]);
-+    if (this._searchResults && this._searchResults.length > 0) {
-+      this._lastQuery = this._selectResult(this._searchIndex);
-     }
-   },
- 
-   /**
-    * Handles keypress and mouse click on the suggestions richlistbox.
-    */
-   _onListBoxKeypress: function SelectorSearch__onListBoxKeypress(aEvent) {
-     switch(aEvent.keyCode || aEvent.button) {
-@@ -437,16 +469,19 @@ SelectorSearch.prototype = {
-     }
-   },
- 
-   /**
-    * Suggests classes,ids and tags based on the user input as user types in the
-    * searchbox.
-    */
-   showSuggestions: function SelectorSearch_showSuggestions() {
-+    if (!this.walker.isLocal()) {
-+      return;
-+    }
-     let query = this.searchBox.value;
-     if (this._lastValidSearch != "" &&
-         this._lastToLastValidSearch != this._lastValidSearch) {
-       this._searchSuggestions = {
-         ids: new Map(),
-         classes: new Map(),
-         tags: new Map(),
-       };
-diff --git a/browser/devtools/inspector/test/browser_inspector_bug_650804_search.js b/browser/devtools/inspector/test/browser_inspector_bug_650804_search.js
---- a/browser/devtools/inspector/test/browser_inspector_bug_650804_search.js
-+++ b/browser/devtools/inspector/test/browser_inspector_bug_650804_search.js
-@@ -63,16 +63,17 @@ function test()
-   {
-     openInspector(startTest);
-   }
- 
-   function startTest(aInspector)
-   {
-     inspector = aInspector;
-     inspector.selection.setNode($("b1"));
-+
-     searchBox =
-       inspector.panelWin.document.getElementById("inspector-searchbox");
- 
-     focusSearchBoxUsingShortcut(inspector.panelWin, function() {
-       searchBox.addEventListener("command", checkState, true);
-       searchBox.addEventListener("keypress", checkState, true);
-       checkStateAndMoveOn(0);
-     });
-@@ -90,25 +91,28 @@ function test()
-     info("pressing key " + key + " to get id " + id);
-     EventUtils.synthesizeKey(key, {}, inspector.panelWin);
-   }
- 
-   function checkState(event) {
-     if (event.type == "keypress" && keypressStates.indexOf(state) == -1) {
-       return;
-     }
--    executeSoon(function() {
--      let [key, id, isValid] = keyStates[state];
--      info(inspector.selection.node.id + " is selected with text " +
--           inspector.searchBox.value);
--      is(inspector.selection.node, $(id),
--         "Correct node is selected for state " + state);
--      is(!searchBox.classList.contains("devtools-no-search-result"), isValid,
--         "Correct searchbox result state for state " + state);
--      checkStateAndMoveOn(state + 1);
-+
-+    inspector.searchSuggestions._lastQuery.then(() => {
-+      executeSoon(() => {
-+        let [key, id, isValid] = keyStates[state];
-+        info(inspector.selection.node.id + " is selected with text " +
-+             inspector.searchBox.value);
-+        is(inspector.selection.node, $(id),
-+           "Correct node is selected for state " + state);
-+        is(!searchBox.classList.contains("devtools-no-search-result"), isValid,
-+           "Correct searchbox result state for state " + state);
-+        checkStateAndMoveOn(state + 1);
-+      });
-     });
-   }
- 
-   function finishUp() {
-     searchBox = null;
-     gBrowser.removeCurrentTab();
-     finish();
-   }
-diff --git a/browser/devtools/inspector/test/browser_inspector_bug_831693_combinator_suggestions.js b/browser/devtools/inspector/test/browser_inspector_bug_831693_combinator_suggestions.js
---- a/browser/devtools/inspector/test/browser_inspector_bug_831693_combinator_suggestions.js
-+++ b/browser/devtools/inspector/test/browser_inspector_bug_831693_combinator_suggestions.js
-@@ -55,16 +55,17 @@ function test()
-   function setupTest()
-   {
-     openInspector(startTest);
-   }
- 
-   function startTest(aInspector)
-   {
-     inspector = aInspector;
-+
-     searchBox =
-       inspector.panelWin.document.getElementById("inspector-searchbox");
-     popup = inspector.searchSuggestions.searchPopup;
- 
-     focusSearchBoxUsingShortcut(inspector.panelWin, function() {
-       searchBox.addEventListener("command", checkState, true);
-       checkStateAndMoveOn(0);
-     });
-@@ -80,17 +81,17 @@ function test()
-     state = index;
- 
-     info("pressing key " + key + " to get suggestions " +
-          JSON.stringify(suggestions));
-     EventUtils.synthesizeKey(key, {}, inspector.panelWin);
-   }
- 
-   function checkState(event) {
--    executeSoon(function() {
-+    inspector.searchSuggestions._lastQuery.then(() => {
-       let [key, suggestions] = keyStates[state];
-       let actualSuggestions = popup.getItems();
-       is(popup._panel.state == "open" || popup._panel.state == "showing"
-          ? actualSuggestions.length: 0, suggestions.length,
-          "There are expected number of suggestions at " + state + "th step.");
-       actualSuggestions = actualSuggestions.reverse();
-       for (let i = 0; i < suggestions.length; i++) {
-         is(suggestions[i][0], actualSuggestions[i].label,
-diff --git a/browser/devtools/inspector/test/browser_inspector_bug_831693_input_suggestion.js b/browser/devtools/inspector/test/browser_inspector_bug_831693_input_suggestion.js
---- a/browser/devtools/inspector/test/browser_inspector_bug_831693_input_suggestion.js
-+++ b/browser/devtools/inspector/test/browser_inspector_bug_831693_input_suggestion.js
-@@ -82,17 +82,17 @@ function test()
-     state = index;
- 
-     info("pressing key " + key + " to get suggestions " +
-          JSON.stringify(suggestions));
-     EventUtils.synthesizeKey(key, {}, inspector.panelWin);
-   }
- 
-   function checkState(event) {
--    executeSoon(function() {
-+    inspector.searchSuggestions._lastQuery.then(() => {
-       let [key, suggestions] = keyStates[state];
-       let actualSuggestions = popup.getItems();
-       is(popup._panel.state == "open" || popup._panel.state == "showing"
-          ? actualSuggestions.length: 0, suggestions.length,
-          "There are expected number of suggestions at " + state + "th step.");
-       actualSuggestions = actualSuggestions.reverse();
-       for (let i = 0; i < suggestions.length; i++) {
-         is(suggestions[i][0], actualSuggestions[i].label,
-diff --git a/toolkit/devtools/server/actors/inspector.js b/toolkit/devtools/server/actors/inspector.js
---- a/toolkit/devtools/server/actors/inspector.js
-+++ b/toolkit/devtools/server/actors/inspector.js
-@@ -2038,17 +2038,17 @@ function nodeDocument(node) {
-  * Similar to a TreeWalker, except will dig in to iframes and it doesn't
-  * implement the good methods like previousNode and nextNode.
-  *
-  * See TreeWalker documentation for explanations of the methods.
-  */
- function DocumentWalker(aNode, aShow, aFilter, aExpandEntityReferences)
- {
-   let doc = nodeDocument(aNode);
--  this.walker = doc.createTreeWalker(nodeDocument(aNode),
-+  this.walker = doc.createTreeWalker(doc,
-     aShow, aFilter, aExpandEntityReferences);
-   this.walker.currentNode = aNode;
-   this.filter = aFilter;
- }
- 
- DocumentWalker.prototype = {
-   get node() this.walker.node,
-   get whatToShow() this.walker.whatToShow,
--- a/series
+++ b/series
@@ -1,14 +1,8 @@
-remote-markup.diff
-remote-edit-attributes.diff
-remote-edit-value.diff
-inspector-panel-default-node.diff
-copy-html.diff
-search-box-remote.diff
-inspector-remote-delete-node.diff
+walker-reload.diff
+warning-fixes.diff
+style-actor.diff
 android-browser.diff
-warning-fixes.diff
-walker-reload.diff
-style-actor.diff
 window-targets.diff
 inspector-retain-root.diff #+obsolete
 protocol-clientserver-marshallers.diff #+experimental
+promise-return.diff #+experimental
--- a/style-actor.diff
+++ b/style-actor.diff
@@ -1,14 +1,14 @@
 # HG changeset patch
 # User Dave Camp <dcamp@mozilla.com>
 # Date 1371506345 25200
 #      Mon Jun 17 14:59:05 2013 -0700
-# Node ID 50fc0e0dd0aae7f4d0436580dfa9c55a41b2650b
-# Parent  c0b89ed707e700d850f91c82e321b06878e96ee7
+# Node ID 496a475072d70e5f9fafc8d03dfd3d627ece92c9
+# Parent d09c94392c1a3093065309079d3d7071c0312782
 [mq]: style-actor.diff
 * * *
 [mq]: computed-view-remote.diff
 
 diff --git a/browser/devtools/inspector/inspector-panel.js b/browser/devtools/inspector/inspector-panel.js
 --- a/browser/devtools/inspector/inspector-panel.js
 +++ b/browser/devtools/inspector/inspector-panel.js
 @@ -40,22 +40,18 @@ function InspectorPanel(iframeWindow, to
@@ -31,17 +31,17 @@ diff --git a/browser/devtools/inspector/
        return this._getDefaultNodeForSelection();
      }).then(defaultSelection => {
        return this._deferredOpen(defaultSelection);
      }).then(null, console.error);
    },
  
    _deferredOpen: function(defaultSelection) {
      let deferred = promise.defer();
-@@ -317,24 +313,21 @@ InspectorPanel.prototype = {
+@@ -311,24 +307,21 @@ InspectorPanel.prototype = {
     * When a new node is selected.
     */
    onNewSelection: function InspectorPanel_onNewSelection() {
      this.cancelLayoutChange();
  
      // Wait for all the known tools to finish updating and then let the
      // client know.
      let selection = this.selection.nodeFront;
@@ -59,17 +59,17 @@ diff --git a/browser/devtools/inspector/
    },
  
    /**
     * Delay the "inspector-updated" notification while a tool
     * is updating itself.  Returns a function that must be
     * invoked when the tool is done updating with the node
     * that the tool is viewing.
     */
-@@ -408,16 +401,17 @@ InspectorPanel.prototype = {
+@@ -402,16 +395,17 @@ InspectorPanel.prototype = {
     */
    destroy: function InspectorPanel__destroy() {
      if (this._destroyPromise) {
        return this._destroyPromise;
      }
      if (this.walker) {
        this._destroyPromise = this.walker.release().then(null, console.error);
        delete this.walker;
@@ -5189,17 +5189,17 @@ diff --git a/toolkit/devtools/gcli/Templ
                  if (capture) {
                    node.removeAttribute('capture' + name.substring(2));
                  }
                }
                else {
 diff --git a/toolkit/devtools/server/actors/inspector.js b/toolkit/devtools/server/actors/inspector.js
 --- a/toolkit/devtools/server/actors/inspector.js
 +++ b/toolkit/devtools/server/actors/inspector.js
-@@ -47,16 +47,18 @@ const protocol = require("devtools/serve
+@@ -56,16 +56,18 @@ const protocol = require("devtools/serve
  const {Arg, Option, method, RetVal, types} = protocol;
  const {LongStringActor, ShortLongString} = require("devtools/server/actors/string");
  const promise = require("sdk/core/promise");
  const object = require("sdk/util/object");
  const events = require("sdk/event/core");
  const { Unknown } = require("sdk/platform/xpcom");
  const { Class } = require("sdk/core/heritage");
  
@@ -5208,17 +5208,17 @@ diff --git a/toolkit/devtools/server/act
  const PSEUDO_CLASSES = [":hover", ":active", ":focus"];
  
  Cu.import("resource://gre/modules/Services.jsm");
  
  exports.register = function(handle) {
    handle.addTabActor(InspectorActor, "inspectorActor");
  };
  
-@@ -120,31 +122,35 @@ var NodeActor = protocol.ActorClass({
+@@ -129,31 +131,35 @@ var NodeActor = protocol.ActorClass({
    /**
     * Instead of storing a connection object, the NodeActor gets its connection
     * from its associated walker.
     */
    get conn() this.walker.conn,
  
    // Returns the JSON representation of this object over the wire.
    form: function(detail) {
@@ -5245,17 +5245,17 @@ diff --git a/toolkit/devtools/server/act
        nodeName: this.rawNode.nodeName,
        numChildren: numChildren,
  
        // doctype attributes
        name: this.rawNode.name,
        publicId: this.rawNode.publicId,
        systemId: this.rawNode.systemId,
  
-@@ -167,16 +173,29 @@ var NodeActor = protocol.ActorClass({
+@@ -176,16 +182,29 @@ var NodeActor = protocol.ActorClass({
        } else {
          form.shortValue = this.rawNode.nodeValue;
        }
      }
  
      return form;
    },
  
@@ -5275,17 +5275,17 @@ diff --git a/toolkit/devtools/server/act
    writeAttrs: function() {
      if (!this.rawNode.attributes) {
        return undefined;
      }
      return [{namespace: attr.namespace, name: attr.name, value: attr.value }
              for (attr of this.rawNode.attributes)];
    },
  
-@@ -290,16 +309,20 @@ let NodeFront = protocol.FrontClass(Node
+@@ -299,16 +318,20 @@ let NodeFront = protocol.FrontClass(Node
        this._observer = null;
      }
  
      protocol.Front.prototype.destroy.call(this);
    },
  
    // Update the object given a form representation off the wire.
    form: function(form, detail, ctx) {
@@ -5296,40 +5296,89 @@ diff --git a/toolkit/devtools/server/act
      // Shallow copy of the form.  We could just store a reference, but
      // eventually we'll want to update some of the data.
      this._form = object.merge(form);
      this._form.attrs = this._form.attrs ? this._form.attrs.slice() : [];
  
      if (form.parent) {
        // Get the owner actor for this actor (the walker), and find the
        // parent node of this actor from it, creating a standin node if
-@@ -814,16 +837,23 @@ var WalkerActor = protocol.ActorClass({
-     actor.observer.observe(node, {
-       attributes: true,
-       characterData: true,
-       childList: true,
-       subtree: true
-     });
-   },
- 
+@@ -2054,33 +2077,71 @@ var InspectorActor = protocol.ActorClass
+     }
+ 
+     return this._walkerPromise;
+   }, {
+     request: {},
+     response: {
+       walker: RetVal("domwalker")
+     }
++  }),
++
 +  getNodeStyle: method(function() {
-+    return NodeStyleActor(this);
++    if (this._nodeStylePromise) {
++      return this._nodeStylePromise;
++    }
++
++    this._nodeStylePromise = this.getWalker().then(walker => {
++      return NodeStyleActor(walker);
++    });
++    return this._nodeStylePromise;
 +  }, {
 +    request: {},
 +    response: { nodeStyle: RetVal("nodestyle") }
+   })
+ });
+ 
+ /**
+  * Client side of the inspector actor, which is used to create
+  * inspector-related actors, including the walker.
+  */
+ var InspectorFront = exports.InspectorFront = protocol.FrontClass(InspectorActor, {
+   initialize: function(client, tabForm) {
+     protocol.Front.prototype.initialize.call(this, client);
+     this.actorID = tabForm.inspectorActor;
+ 
+     // XXX: This is the first actor type in its hierarchy to use the protocol
+     // library, so we're going to self-own on the client side for now.
+     client.addActorPool(this);
+     this.manage(this);
+-  }
++  },
++
++  getWalker: custom(function() {
++    return this._getWalker().then(walker => {
++      this.walker = walker;
++      return walker;
++    });
++  }, {
++    impl: "_getWalker"
 +  }),
 +
-   /**
-    * Return the document node that contains the given node,
-    * or the root node if no node is specified.
-    * @param NodeActor node
-    *        The node whose document is needed, or null to
-    *        return the root.
-    */
-   document: method(function(node) {
++  getNodeStyle: custom(function() {
++    this._getNodeStyle().then(nodeStyle => {
++      // We need a walker to understand node references from the
++      // node style.
++      if (this.walker) {
++        return nodeStyle;
++      }
++      return this.getWalker().then(() => {
++        return nodeStyle;
++      });
++    }
++  }, {
++    impl: "_getNodeStyle"
++  })
+ });
+ 
+ function documentWalker(node, whatToShow=Ci.nsIDOMNodeFilter.SHOW_ALL) {
+   return new DocumentWalker(node, whatToShow, whitespaceTextFilter, false);
+ }
+ 
+ // Exported for test purposes.
+ exports._documentWalker = documentWalker;
 diff --git a/toolkit/devtools/server/actors/styleeditor.js b/toolkit/devtools/server/actors/styleeditor.js
 --- a/toolkit/devtools/server/actors/styleeditor.js
 +++ b/toolkit/devtools/server/actors/styleeditor.js
 @@ -115,17 +115,17 @@ StyleEditorActor.prototype = {
    },
  
    /**
     * Get the BaseURI for the document.
@@ -5346,17 +5395,17 @@ diff --git a/toolkit/devtools/server/act
     * Adds load listeners to document.
     */
    onNewDocument: function() {
      // delete previous document's actors
 diff --git a/toolkit/devtools/server/actors/styles.js b/toolkit/devtools/server/actors/styles.js
 new file mode 100644
 --- /dev/null
 +++ b/toolkit/devtools/server/actors/styles.js
-@@ -0,0 +1,559 @@
+@@ -0,0 +1,553 @@
 +/* 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, Ci} = require("chrome");
 +const protocol = require("devtools/server/protocol");
@@ -5374,21 +5423,21 @@ new file mode 100644
 +// Predeclare the domnode actor type for use in requests.
 +types.addActorType("domnode");
 +
 +types.addDictType("appliedstyle", {
 +  rule: "domstylerule#actorid",
 +  inherited: "nullable:domnode#actorid"
 +});
 +
-+types.addLifetime("requestWalker", "requestWalker");
++types.addLifetime("walker", "walker");
 +
 +types.addDictType("matchedselector", {
 +  rule: "domstylerule#actorid",
-+  sourceElement: "nullable:requestWalker:domnode",
++  sourceElement: "nullable:walker:domnode",
 +  selector: "string",
 +  value: "string",
 +  status: "number"
 +});
 +
 +var NodeStyleActor = protocol.ActorClass({
 +  typeName: "nodestyle",
 +  initialize: function(walker) {
@@ -5400,17 +5449,16 @@ new file mode 100644
 +
 +  get conn() this.walker.conn,
 +
 +  // item can be a nsIDOMCSSRule or an Element.
 +  _styleRef: function(item) {
 +    if (this._refMap.has(item)) {
 +      return this._refMap.get(item);
 +    }
-+    dump("item: " + item + "\n");
 +    let actor = StyleRuleActor(this, item);
 +    this.manage(actor);
 +    this._refMap.set(item, actor);
 +
 +    return actor;
 +  },
 +
 +  _sheetRef: function(sheet) {
@@ -5519,18 +5567,16 @@ new file mode 100644
 +        selector: selectorInfo.selector.text,
 +        value: selectorInfo.value,
 +        status: selectorInfo.status
 +      });
 +    }
 +
 +    this.expandSets(rules, sheets);
 +
-+    this.requestWalker = walker;
-+
 +    return {
 +      matched: matched,
 +      rules: [...rules],
 +      sheets: [...sheets]
 +    }
 +  }, {
 +    request: {
 +      node: Arg(0, "domnode"),
@@ -5559,31 +5605,26 @@ new file mode 100644
 +
 +  getApplied: method(function(node, options) {
 +    let entries = [];
 +
 +    this.addElementRules(node.rawNode, undefined, options, entries);
 +
 +    if (options.inherited) {
 +      let parent = this.walker.parentNode(node);
-+      dump("TRYING TO ADD INHERITED RULES: " + parent + "\n");
 +      while (parent && parent.rawNode.nodeType != Ci.nsIDOMNode.DOCUMENT_NODE) {
-+        dump("TRYING TO ADD INHERITED RULES: " + parent + "\n");
 +        this.addElementRules(parent.rawNode, parent, options, entries);
 +        parent = this.walker.parentNode(parent);
 +      }
 +    }
 +
 +
 +    if (options.matchedSelectors) {
-+      dump("matched selectors provided except it isn't.");
 +      for (let entry of entries) {
-+        dump("Checking entry\n");
 +        if (entry.rule.type === ELEMENT_STYLE) {
-+          dump("element style!\n");
 +          continue;
 +        }
 +
 +        let domRule = entry.rule.rawRule;
 +        let selectors = CssLogic.getSelectors(domRule);
 +        let element = entry.inherited ? entry.inherited.rawNode : node.rawNode;
 +        entry.matchedSelectors = [];
 +        for (let i = 0; i < selectors.length; i++) {
@@ -5645,24 +5686,28 @@ new file mode 100644
 +    }
 +  },
 +});
 +exports.NodeStyleActor = NodeStyleActor;
 +
 +var NodeStyleFront = protocol.FrontClass(NodeStyleActor, {
 +  initialize: function(conn, form, ctx, detail) {
 +    protocol.Front.prototype.initialize.call(this, conn, form, ctx, detail);
++    this.inspector = this.parent();
 +  },
 +
 +  destroy: function() {
 +    protocol.Front.prototype.destroy.call(this);
 +  },
 +
++  get walker() {
++    return this.inspector.walker;
++  },
++
 +  getMatchedSelectors: protocol.custom(function(node, property) {
-+    this.requestWalker = node.parent();
 +    return this._getMatchedSelectors(node, property).then(ret => {
 +      return ret.matched;
 +    });
 +  }, {
 +    impl: "_getMatchedSelectors"
 +  }),
 +
 +  getApplied: protocol.custom(function(node, options={}) {
@@ -5732,17 +5777,16 @@ new file mode 100644
 +      this.rawRule = item;
 +      if (this.rawRule instanceof Ci.nsIDOMCSSStyleRule && this.rawRule.parentStyleSheet) {
 +        this.line = DOMUtils.getRuleLine(this.rawRule);
 +      }
 +
 +    } else {
 +      this.type = ELEMENT_STYLE;
 +      this.rawNode = item;
-+      dump("item: " + item + "\n");
 +      this.rawRule = {
 +        style: item.style,
 +        toString: function() "[element rule " + this.style + "]"
 +      }
 +    }
 +  },
 +
 +  get conn() this.nodeStyle.conn,
@@ -5764,17 +5808,16 @@ new file mode 100644
 +    if (this.rawRule.parentRule) {
 +      form.parentRule = this.nodeStyle._styleRef(this.rawRule.parentRule).actorID;
 +    }
 +    if (this.rawRule.parentStyleSheet) {
 +      form.parentStyleSheet = this.nodeStyle._sheetRef(this.rawRule.parentStyleSheet).actorID;
 +    }
 +
 +    if (this.type === ELEMENT_STYLE) {
-+      dump("raw node: " + this.rawNode + "\n");
 +      form.href = this.rawNode.ownerDocument.location.href;
 +    }
 +
 +    switch (this.type) {
 +      case Ci.nsIDOMCSSRule.STYLE_RULE:
 +        form.selectors = CssLogic.getSelectors(this.rawRule);
 +        /* fall through */
 +      case ELEMENT_STYLE:
--- a/walker-reload.diff
+++ b/walker-reload.diff
@@ -1,48 +1,50 @@
 # HG changeset patch
 # User Dave Camp <dcamp@mozilla.com>
 # Date 1374258100 25200
 #      Fri Jul 19 11:21:40 2013 -0700
-# Node ID c0b89ed707e700d850f91c82e321b06878e96ee7
-# Parent  193c735dba54ac6efcd67cbd39e33b37d3115fbe
+# Node ID 4502bf53830e85ec59f874d2856695ab53de8a40
+# Parent 05ff12c00114de0b595cab891fb42336753b8462
 imported patch walker-reload.diff
 
 diff --git a/browser/devtools/inspector/inspector-panel.js b/browser/devtools/inspector/inspector-panel.js
 --- a/browser/devtools/inspector/inspector-panel.js
 +++ b/browser/devtools/inspector/inspector-panel.js
-@@ -158,17 +158,26 @@ InspectorPanel.prototype = {
+@@ -151,24 +151,27 @@ InspectorPanel.prototype = {
+     this.setupSidebar();
+ 
+     return deferred.promise;
+   },
+ 
+   /**
+    * Return a promise that will resolve to the default node for selection.
     */
-   _getDefaultNodeForSelection : function() {
+-  _getDefaultNodeForSelection : function() {
++  _getDefaultNodeForSelection: function() {
      if (this._defaultNode) {
        return this._defaultNode;
      }
      let walker = this.walker;
++
      // if available set body node as default selected node
      // else set documentElement
 -    return walker.querySelector(this.walker.rootNode, "body").then(front => {
-+    let root;
-+    if (walker.rootNode.actorID) {
-+      root = promise.resolve(walker.rootNode);
-+    } else {
-+      root = walker.document();
-+    }
-+
-+    return root.then(rootNode => {
++    return walker.getRootNode().then(rootNode => {
 +      return walker.querySelector(rootNode, "body");
 +    }).then(front => {
        if (front) {
          return front;
        }
        return this.walker.documentElement(this.walker.rootNode);
      }).then(node => {
        if (walker !== this.walker) {
          promise.reject(null);
        }
-@@ -280,43 +289,31 @@ InspectorPanel.prototype = {
+@@ -280,43 +283,31 @@ InspectorPanel.prototype = {
      this.sidebar.show();
    },
  
    /**
     * Reset the inspector on navigate away.
     */
    onNavigatedAway: function InspectorPanel_onNavigatedAway(event, payload) {
      let newWindow = payload._navPayload || payload;
@@ -85,17 +87,17 @@ diff --git a/browser/devtools/inspector/
  
    /**
     * When a new node is selected.
     */
    onNewSelection: function InspectorPanel_onNewSelection() {
 diff --git a/toolkit/devtools/server/actors/inspector.js b/toolkit/devtools/server/actors/inspector.js
 --- a/toolkit/devtools/server/actors/inspector.js
 +++ b/toolkit/devtools/server/actors/inspector.js
-@@ -1567,16 +1567,24 @@ var WalkerActor = protocol.ActorClass({
+@@ -1576,16 +1576,24 @@ var WalkerActor = protocol.ActorClass({
          mutation.added = addedActors;
        }
        this.queueMutation(mutation);
      }
    },
  
    onFrameLoad: function(window) {
      let frame = window.frameElement;
@@ -110,17 +112,17 @@ diff --git a/toolkit/devtools/server/act
      let frameActor = this._refMap.get(frame);
      if (!frameActor) {
        return;
      }
  
      this.queueMutation({
        type: "frameLoad",
        target: frameActor.actorID,
-@@ -1627,16 +1635,21 @@ var WalkerActor = protocol.ActorClass({
+@@ -1636,16 +1644,21 @@ var WalkerActor = protocol.ActorClass({
      }
  
      let doc = window.document;
      let documentActor = this._refMap.get(doc);
      if (!documentActor) {
        return;
      }
  
@@ -132,39 +134,114 @@ diff --git a/toolkit/devtools/server/act
      this.queueMutation({
        type: "documentUnload",
        target: documentActor.actorID
      });
  
      let walker = documentWalker(doc);
      let parentNode = walker.parentNode();
      if (parentNode) {
-@@ -1778,16 +1791,21 @@ var WalkerFront = exports.WalkerFront = 
+@@ -1668,29 +1681,41 @@ var WalkerActor = protocol.ActorClass({
+ /**
+  * Client side of the DOM walker.
+  */
+ var WalkerFront = exports.WalkerFront = protocol.FrontClass(WalkerActor, {
+   // Set to true if cleanup should be requested after every mutation list.
+   autoCleanup: true,
+ 
+   initialize: function(client, form) {
++    this._rootNodeDeferred = promise.defer();
+     protocol.Front.prototype.initialize.call(this, client, form);
+     this._orphaned = new Set();
+     this._retainedOrphans = new Set();
+   },
+ 
+   destroy: function() {
+     protocol.Front.prototype.destroy.call(this);
+   },
  
+   // Update the object given a form representation off the wire.
+   form: function(json) {
+-    this.actorID = json.actorID;
++    this.actorID = json.actor;
+     this.rootNode = types.getType("domnode").read(json.root, this);
++    this._rootNodeDeferred.resolve(this.rootNode);
++  },
++
++  /**
++   * Clients can use walker.rootNode to get the current root node of the
++   * walker, but during a reload the root node might be null.  This
++   * method returns a promise that will resolve to the root node when it is
++   * set.
++   */
++  getRootNode: function() {
++    return this._rootNodeDeferred.promise;
+   },
+ 
+   /**
+    * When reading an actor form off the wire, we want to hook it up to its
+    * parent front.  The protocol guarantees that the parent will be seen
+    * by the client in either a previous or the current request.
+    * So if we've already seen this parent return it, otherwise create
+    * a bare-bones stand-in node.  The stand-in node will be updated
+@@ -1788,18 +1813,29 @@ var WalkerFront = exports.WalkerFront = 
    /**
     * Get any unprocessed mutation records and process them.
     */
    getMutations: protocol.custom(function(options={}) {
      return this._getMutations(options).then(mutations => {
        let emitMutations = [];
        for (let change of mutations) {
+         // The target is only an actorID, get the associated front.
+-        let targetID = change.target;
+-        let targetFront = this.get(targetID);
++        let targetID;
++        let targetFront;
++
 +        if (change.type === "newRoot") {
 +          this.rootNode = types.getType("domnode").read(change.target, this);
-+          continue;
++          this._rootNodeDeferred.resolve(this.rootNode);
++          targetID = this.rootNode.actorID;
++          targetFront = this.rootNode;
++        } else {
++          targetID = change.target;
++          targetFront = this.get(targetID);
 +        }
 +
-         // The target is only an actorID, get the associated front.
-         let targetID = change.target;
-         let targetFront = this.get(targetID);
          if (!targetFront) {
            console.trace("Got a mutation for an unexpected actor: " + targetID + ", please file a bug on bugzilla.mozilla.org!");
            continue;
          }
  
-@@ -1977,33 +1995,38 @@ var InspectorActor = protocol.ActorClass
+         let emittedMutation = object.merge(change, { target: targetFront });
+ 
+         if (change.type === "childList") {
+@@ -1843,16 +1879,21 @@ var WalkerFront = exports.WalkerFront = 
+           // document children, because we should have gotten a documentUnload
+           // first.
+           for (let child of targetFront.treeChildren()) {
+             if (child.nodeType === Ci.nsIDOMNode.DOCUMENT_NODE) {
+               console.trace("Got an unexpected frameLoad in the inspector, please file a bug on bugzilla.mozilla.org!");
+             }
+           }
+         } else if (change.type === "documentUnload") {
++          if (targetFront === this.rootNode) {
++            this.rootNode = null;
++            this._rootNodeDeferred = promise.defer();
++          }
++
+           // We try to give fronts instead of actorIDs, but these fronts need
+           // to be destroyed now.
+           emittedMutation.target = targetFront.actorID;
+           emittedMutation.targetParent = targetFront.parentNode();
+ 
+           // Release the document node and all of its children, even retained.
+           this._releaseFront(targetFront, true);
+         } else if (change.type === "unretained") {
+@@ -1986,33 +2027,38 @@ var InspectorActor = protocol.ActorClass
        return tabActor.browser;
      } else if (tabActor.browser instanceof Ci.nsIDOMElement) {
        return tabActor.browser.contentWindow;
      }
      return null;
    },
  
    getWalker: method(function(options={}) {
@@ -175,17 +252,17 @@ diff --git a/toolkit/devtools/server/act
      let deferred = promise.defer();
 +    this._walkerPromise = deferred.promise;
  
      let window = this.window;
  
      var domReady = () => {
        let tabActor = this.tabActor;
        window.removeEventListener("DOMContentLoaded", domReady, true);
-       deferred.resolve(WalkerActor(this.conn, window.document, tabActor._browser, options));
+       deferred.resolve(WalkerActor(this.conn, window.document, tabActor._tabbrowser, options));
      };
  
      if (window.document.readyState === "loading") {
        window.addEventListener("DOMContentLoaded", domReady, true);
      } else {
        domReady();
      }
  
@@ -194,8 +271,141 @@ diff --git a/toolkit/devtools/server/act
    }, {
      request: {},
      response: {
        walker: RetVal("domwalker")
      }
    })
  });
  
+diff --git a/toolkit/devtools/server/tests/mochitest/Makefile.in b/toolkit/devtools/server/tests/mochitest/Makefile.in
+--- a/toolkit/devtools/server/tests/mochitest/Makefile.in
++++ b/toolkit/devtools/server/tests/mochitest/Makefile.in
+@@ -18,16 +18,17 @@ MOCHITEST_CHROME_FILES	= \
+ 	test_inspector-changevalue.html \
+ 	test_inspector-insert.html \
+ 	test_inspector-mutations-attr.html \
+ 	test_inspector-mutations-childlist.html \
+ 	test_inspector-mutations-frameload.html \
+ 	test_inspector-mutations-value.html \
+ 	test_inspector-release.html \
+ 	test_inspector-remove.html \
++	test_inspector-reload.html \
+ 	test_inspector-retain.html \
+ 	test_inspector-pseudoclass-lock.html \
+ 	test_inspector-traversal.html \
+ 	test_unsafeDereference.html \
+ 	nonchrome_unsafeDereference.html \
+ 	$(NULL)
+ 
+ include $(topsrcdir)/config/rules.mk
+diff --git a/toolkit/devtools/server/tests/mochitest/inspector-helpers.js b/toolkit/devtools/server/tests/mochitest/inspector-helpers.js
+--- a/toolkit/devtools/server/tests/mochitest/inspector-helpers.js
++++ b/toolkit/devtools/server/tests/mochitest/inspector-helpers.js
+@@ -230,16 +230,20 @@ function isFrameLoad(change) {
+ function isUnretained(change) {
+   return change.type === "unretained";
+ }
+ 
+ function isChildList(change) {
+   return change.type === "childList";
+ }
+ 
++function isNewRoot(change) {
++  return change.type === "newRoot";
++}
++
+ // Make sure an iframe's src attribute changed and then
+ // strip that mutation out of the list.
+ function assertSrcChange(mutations) {
+   return assertAndStrip(mutations, "Should have had an iframe source change.", isSrcChange);
+ }
+ 
+ // Make sure there's an unload in the mutation list and strip
+ // that mutation out of the list
+diff --git a/toolkit/devtools/server/tests/mochitest/test_inspector-reload.html b/toolkit/devtools/server/tests/mochitest/test_inspector-reload.html
+new file mode 100644
+--- /dev/null
++++ b/toolkit/devtools/server/tests/mochitest/test_inspector-reload.html
+@@ -0,0 +1,83 @@
++<!DOCTYPE HTML>
++<html>
++<!--
++https://bugzilla.mozilla.org/show_bug.cgi?id=
++-->
++<head>
++  <meta charset="utf-8">
++  <title>Test for Bug </title>
++
++  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
++  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
++  <script type="application/javascript;version=1.8" src="inspector-helpers.js"></script>
++  <script type="application/javascript;version=1.8">
++Components.utils.import("resource://gre/modules/devtools/Loader.jsm");
++const Ci = Components.interfaces;
++const promise = devtools.require("sdk/core/promise");
++const inspector = devtools.require("devtools/server/actors/inspector");
++
++window.onload = function() {
++  SimpleTest.waitForExplicitFinish();
++  runNextTest();
++}
++
++var gInspectee = null;
++var gClient = null;
++var gWalker = null;
++
++addTest(function setup() {
++  let url = document.getElementById("inspectorContent").href;
++  attachURL(url, function(err, client, tab, doc) {
++    gInspectee = doc;
++    let {InspectorFront} = devtools.require("devtools/server/actors/inspector");
++    let inspector = InspectorFront(client, tab);
++    promiseDone(inspector.getWalker().then(walker => {
++      ok(walker, "getWalker() should return an actor.");
++      gClient = client;
++      gWalker = walker;
++      return inspector.getWalker();
++    }).then(walker => {
++      dump(walker.actorID + "\n");
++      ok(walker === gWalker, "getWalker() twice should return the same walker.");
++    }).then(runNextTest));
++  });
++});
++
++addTest(function testReload() {
++  let nodeFront;
++  let oldRootID = gWalker.rootNode.actorID;
++  // Load a node to populate the tree a bit.
++  promiseDone(gWalker.querySelector(gWalker.rootNode, "#a").then(front => {
++    gInspectee.defaultView.location.reload();
++    return waitForMutation(gWalker, isNewRoot);
++  }).then(() => {
++    ok(gWalker.rootNode.actorID != oldRootID, "Root node should have changed.");
++  }).then(() => {
++    // Make sure we can still access the document
++    return gWalker.querySelector(gWalker.rootNode, "#a");
++  }).then(front => {
++    ok(front.actorID, "Got a new actor ID");
++  }).then(runNextTest));
++});
++
++addTest(function cleanup() {
++  delete gWalker;
++  delete gInspectee;
++  delete gClient;
++  runNextTest();
++});
++
++
++  </script>
++</head>
++<body>
++<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a>
++<a id="inspectorContent" target="_blank" href="inspector-traversal-data.html">Test Document</a>
++<p id="display"></p>
++<div id="content" style="display: none">
++
++</div>
++<pre id="test">
++</pre>
++</body>
++</html>
--- a/warning-fixes.diff
+++ b/warning-fixes.diff
@@ -1,14 +1,14 @@
 # HG changeset patch
 # User Dave Camp <dcamp@mozilla.com>
 # Date 1371477174 25200
 #      Mon Jun 17 06:52:54 2013 -0700
-# Node ID 193c735dba54ac6efcd67cbd39e33b37d3115fbe
-# Parent  aad4f3eb5b6c8fee5ab2e94c17c784d4ac9bcc7d
+# Node ID 913ee6f167d93faa1014ae436f9018909d26e094
+# Parent  db733687ce263d6c9683464df3ca65d632d52687
 imported patch warning-fixes.diff
 
 diff --git a/browser/devtools/shared/inplace-editor.js b/browser/devtools/shared/inplace-editor.js
 --- a/browser/devtools/shared/inplace-editor.js
 +++ b/browser/devtools/shared/inplace-editor.js
 @@ -398,17 +398,17 @@ InplaceEditor.prototype = {
              --selStart;
            }
--- a/window-targets.diff
+++ b/window-targets.diff
@@ -1,14 +1,14 @@
 # HG changeset patch
 # User Dave Camp <dcamp@mozilla.com>
 # Date 1372178155 25200
 #      Tue Jun 25 09:35:55 2013 -0700
-# Node ID 2f0bf3ecb8b591aa233d7685323ec441ef1fc089
-# Parent e4edef1b0ea25d1cee3c49716ca00c7673377dd3
+# Node ID c91491dba5ec6cb90c3024bac1bb36678c2d18cd
+# Parent  496a475072d70e5f9fafc8d03dfd3d627ece92c9
 imported patch window-targets.diff
 * * *
 imported patch custom-windows.diff
 
 diff --git a/browser/devtools/framework/target.js b/browser/devtools/framework/target.js
 --- a/browser/devtools/framework/target.js
 +++ b/browser/devtools/framework/target.js
 @@ -77,17 +77,19 @@ exports.TargetFactory = {
@@ -201,17 +201,17 @@ diff --git a/browser/devtools/framework/
        attachTab();
      } else {
        // Remote chrome debugging doesn't need anything at this point.
        this._remote.resolve(null);
      }
 diff --git a/toolkit/devtools/server/actors/inspector.js b/toolkit/devtools/server/actors/inspector.js
 --- a/toolkit/devtools/server/actors/inspector.js
 +++ b/toolkit/devtools/server/actors/inspector.js
-@@ -695,17 +695,17 @@ let traversalMethod = {
+@@ -704,17 +704,17 @@ let traversalMethod = {
   */
  var ProgressListener = Class({
    extends: Unknown,
    interfaces: ["nsIWebProgressListener", "nsISupportsWeakReference"],
  
    initialize: function(webProgress) {
      Unknown.prototype.initialize.call(this);
      this.webProgress = webProgress;
@@ -220,17 +220,17 @@ diff --git a/toolkit/devtools/server/act
    },
  
    destroy: function() {
      this.webProgress.removeProgressListener(this);
    },
  
    onStateChange: makeInfallible(function stateChange(progress, request, flag, status) {
      let isWindow = flag & Ci.nsIWebProgressListener.STATE_IS_WINDOW;
-@@ -2034,20 +2034,27 @@ var InspectorActor = protocol.ActorClass
+@@ -2061,20 +2061,27 @@ var InspectorActor = protocol.ActorClass
        return this._walkerPromise;
      }
  
      let deferred = promise.defer();
      this._walkerPromise = deferred.promise;
  
      let window = this.window;