More work
authorDave Camp <dcamp@mozilla.com>
Sun, 23 Jun 2013 09:27:10 -0700
changeset 10 3141e7194a9a7b38bf6e9980ed3198bf965e510f
parent 9 5903dbb0a8f623cedd92fc3ccfbf99592d44f0c5
child 11 16a15e9f8241531c2a3f42f783e8d91632974359
push id11
push userdcamp@campd.org
push dateSun, 23 Jun 2013 16:27:14 +0000
More work
console-padend.diff
copy-html.diff
inspector-actor-docelement.diff
inspector-actor-forgiving-rawnode.diff
inspector-actor-innerhtml.diff
inspector-actor-markup-fixes.diff
inspector-actor-modify-attributes.diff
inspector-actor-modify-nodevalue.diff
inspector-actor-pseudoclass.diff
inspector-actor-queue-mutations.diff
inspector-actor-readystate.diff
inspector-actor-retain.diff
inspector-actor-sibling-traversal.diff
inspector-delete-node.diff
inspector-front-import-rawnode-parents.diff
inspector-front-islocal.diff
inspector-panel-default-node.diff
inspector-remote-breadcrumbs.diff
inspector-remote-delete-node.diff
inspector-remote-pseudoclass.diff
inspector-remote-target.diff
remote-edit-attributes.diff
remote-edit-value.diff
remote-markup.diff
search-box-remote.diff
series
style-actor.diff
warning-fixes.diff
deleted file mode 100644
--- a/console-padend.diff
+++ /dev/null
@@ -1,83 +0,0 @@
-# HG changeset patch
-# Parent 7da5909bde65e50ac90509a0e7b1752a3b25148f
-
-diff --git a/toolkit/devtools/Console.jsm b/toolkit/devtools/Console.jsm
---- a/toolkit/devtools/Console.jsm
-+++ b/toolkit/devtools/Console.jsm
-@@ -30,30 +30,32 @@ Cu.import("resource://gre/modules/Consol
- XPCOMUtils.defineLazyModuleGetter(this, "Services",
-                                   "resource://gre/modules/Services.jsm");
- 
- let gTimerRegistry = new Map();
- 
- /**
-  * String utility to ensure that strings are a specified length. Strings
-  * that are too long are truncated to the max length and the last char is
-- * set to "_". Strings that are too short are left padded with spaces.
-+ * set to "_". Strings that are too short are padded with spaces.
-  *
-  * @param {string} aStr
-  *        The string to format to the correct length
-  * @param {number} aMaxLen
-  *        The maximum allowed length of the returned string
-  * @param {number} aMinLen (optional)
-  *        The minimum allowed length of the returned string. If undefined,
-  *        then aMaxLen will be used
-  * @param {object} aOptions (optional)
-- *        An object allowing format customization. The only customization
-- *        allowed currently is 'truncate' which can take the value "start" to
-- *        truncate strings from the start as opposed to the end or "center" to
-- *        truncate strings in the center
-+ *        An object allowing format customization. Allowed customizations:
-+ *          'truncate' - can take the value "start" to truncate strings from
-+ *             the start as opposed to the end or "center" to truncate
-+ *             strings in the center.
-+ *          'align' - takes an alignment when padding is needed for MinLen,
-+ *             either "start" or "end".  Defaults to "start".
-  * @return {string}
-  *        The original string formatted to fit the specified lengths
-  */
- function fmt(aStr, aMaxLen, aMinLen, aOptions) {
-   if (aMinLen == null) {
-     aMinLen = aMaxLen;
-   }
-   if (aStr == null) {
-@@ -69,17 +71,18 @@ function fmt(aStr, aMaxLen, aMinLen, aOp
-       let end = aStr.substring((aStr.length - (aMaxLen / 2)) + 1);
-       return start + "_" + end;
-     }
-     else {
-       return aStr.substring(0, aMaxLen - 1) + "_";
-     }
-   }
-   if (aStr.length < aMinLen) {
--    return Array(aMinLen - aStr.length + 1).join(" ") + aStr;
-+    let padding = Array(aMinLen - aStr.length + 1).join(" ");
-+    aStr = (aOptions.align === "end") ? padding + aStr : aStr + padding;
-   }
-   return aStr;
- }
- 
- /**
-  * Utility to extract the constructor name of an object.
-  * Object.toString gives: "[object ?????]"; we want the "?????".
-  *
-@@ -339,17 +342,17 @@ function getStack(aFrame, aMaxDepth = 0)
-  *        Array of trace objects as created by parseStack()
-  * @return {string} Multi line report of the stack trace
-  */
- function formatTrace(aTrace) {
-   let reply = "";
-   aTrace.forEach(function(frame) {
-     reply += fmt(frame.filename, 20, 20, { truncate: "start" }) + " " +
-              fmt(frame.lineNumber, 5, 5) + " " +
--             fmt(frame.functionName, 75, 75, { truncate: "center" }) + "\n";
-+             fmt(frame.functionName, 0, 75, { truncate: "center" }) + "\n";
-   });
-   return reply;
- }
- 
- /**
-  * Create a new timer by recording the current time under the specified name.
-  *
-  * @param {string} aName
--- a/copy-html.diff
+++ b/copy-html.diff
@@ -1,10 +1,16 @@
 # HG changeset patch
-# Parent a58fb40fdea3d76e1e25fddb7a716ddb582f58f0
+# User Dave Camp <dcamp@mozilla.com>
+# Date 1371477175 25200
+#      Mon Jun 17 06:52:55 2013 -0700
+# Node ID 148959580da71de87f64db00b877f31381034562
+# Parent  12e30c6f2d124d7ca5b5184a46dc7d6b787fd266
+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
 @@ -631,34 +631,42 @@ InspectorPanel.prototype = {
    /**
     * Copy the innerHTML of the selected Node to the clipboard.
     */
    copyInnerHTML: function InspectorPanel_copyInnerHTML()
deleted file mode 100644
--- a/inspector-actor-docelement.diff
+++ /dev/null
@@ -1,46 +0,0 @@
-# HG changeset patch
-# Parent 8a31e6ec65839e0e78e22f3e1d28ca9b373ffbe1
-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
-@@ -140,16 +140,21 @@ var NodeActor = protocol.ActorClass({
-       publicId: this.rawNode.publicId,
-       systemId: this.rawNode.systemId,
- 
-       attrs: this.writeAttrs(),
- 
-       pseudoClassLocks: this.writePseudoClassLocks(),
-     };
- 
-+    if (this.rawNode.ownerDocument &&
-+        this.rawNode.ownerDocument.documentElement === this.rawNode) {
-+      form.isDocumentElement = true;
-+    }
-+
-     if (this.rawNode.nodeValue) {
-       // We only include a short version of the value if it's longer than
-       // gValueSummaryLength
-       if (this.rawNode.nodeValue.length > gValueSummaryLength) {
-         form.shortValue = this.rawNode.nodeValue.substring(0, gValueSummaryLength);
-         form.incompleteValue = true;
-       } else {
-         form.shortValue = this.rawNode.nodeValue;
-@@ -321,16 +326,18 @@ let NodeFront = protocol.FrontClass(Node
- 
-   get hasChildren() this._form.numChildren > 0,
-   get numChildren() this._form.numChildren,
- 
-   get tagName() this.nodeType === Ci.nsIDOMNode.ELEMENT_NODE ? this.nodeName : null,
-   get shortValue() this._form.shortValue,
-   get incompleteValue() !!this._form.incompleteValue,
- 
-+  get isDocumentElement() !!this._form.isDocumentElement,
-+
-   // doctype properties
-   get name() this._form.name,
-   get publicId() this._form.publicId,
-   get systemId() this._form.systemId,
- 
-   getAttribute: function(name) {
-     let attr = this._getAttribute(name);
-     return attr ? attr.value : null;
deleted file mode 100644
--- a/inspector-actor-forgiving-rawnode.diff
+++ /dev/null
@@ -1,58 +0,0 @@
-# HG changeset patch
-# User Dave Camp <dcamp@mozilla.com>
-# Date 1370924323 25200
-#      Mon Jun 10 21:18:43 2013 -0700
-# Node ID ecbacfcceb02c1377980df824ff47b7eca7f1063
-# Parent b9e8d9e36ad51f754fc6da74f619df6cddabd5a9
-imported patch target-inspector-changes.diff
-
-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
-@@ -418,21 +418,24 @@ let NodeFront = protocol.FrontClass(Node
- 
-   /**
-    * Get an nsIDOMNode for the given node front.  This only works locally,
-    * and is only intended as a stopgap during the transition to the remote
-    * protocol.  If you depend on this you're likely to break soon.
-    */
-   rawNode: function(rawNode) {
-     if (!this.conn._transport._serverConnection) {
--      throw new Error("Tried to use rawNode on a remote connection.");
-+      console.warn("Tried to use rawNode on a remote connection.");
-+      return null;
-     }
-     let actor = this.conn._transport._serverConnection.getActor(this.actorID);
-     if (!actor) {
--      throw new Error("Could not find client side for actor " + this.actorID);
-+      // Can happen if we try to get the raw node for an already-expired
-+      // actor.
-+      return null;
-     }
-     return actor.rawNode;
-   }
- });
- 
- /**
-  * Returned from any call that might return a node that isn't connected to root by
-  * nodes the child has seen, such as querySelector.
-@@ -1736,17 +1739,18 @@ var WalkerFront = exports.WalkerFront = 
-   isLocal: function() {
-     return !!this.conn._transport._serverConnection;
-   },
- 
-   // XXX hack during transition to remote inspector: get a proper NodeFront
-   // for a given local node.  Only works locally.
-   frontForRawNode: function(rawNode){
-     if (!this.isLocal()) {
--      throw Error("Tried to use frontForRawNode on a remote connection.");
-+      console.warn("Tried to use frontForRawNode on a remote connection.");
-+      return null;
-     }
-     let actor = this.conn._transport._serverConnection.getActor(this.actorID);
-     if (!actor) {
-       throw Error("Could not find client side for actor " + this.actorID);
-     }
-     let nodeActor = actor._ref(rawNode);
- 
-     // Pass the node through a read/write pair to create the client side actor.
deleted file mode 100644
--- a/inspector-actor-innerhtml.diff
+++ /dev/null
@@ -1,96 +0,0 @@
-# HG changeset patch
-# User Dave Camp <dcamp@mozilla.com>
-# Date 1370924324 25200
-#      Mon Jun 10 21:18:44 2013 -0700
-# Node ID 82bc29ea15b3c373dbec7ee0efc62d1911db4ea5
-# Parent  4520f54fb87f1f2f350a73011973680fa8c102ef
-imported patch inspector-innerhtml.diff
-
-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
-@@ -1297,16 +1297,44 @@ var WalkerActor = protocol.ActorClass({
-   }, {
-     request: {
-       node: Arg(0, "domnode", { optional: true }),
-     },
-     response: {}
-   }),
- 
-   /**
-+   * Get a node's innerHTML property.
-+   */
-+  innerHTML: method(function(node) {
-+    return LongStringActor(this.conn, node.rawNode.innerHTML);
-+  }, {
-+    request: {
-+      node: Arg(0, "domnode")
-+    },
-+    response: {
-+      value: RetVal("longstring")
-+    }
-+  }),
-+
-+  /**
-+   * Get a node's innerHTML property.
-+   */
-+  outerHTML: method(function(node) {
-+    return LongStringActor(this.conn, node.rawNode.outerHTML);
-+  }, {
-+    request: {
-+      node: Arg(0, "domnode")
-+    },
-+    response: {
-+      value: RetVal("longstring")
-+    }
-+  }),
-+
-+  /**
-    * Get any pending mutation records.  Must be called by the client after
-    * the `new-mutations` notification is received.  Returns an array of
-    * mutation records.
-    *
-    * Mutation records have a basic structure:
-    *
-    * {
-    *   type: attributes|characterData|childList,
-diff --git a/toolkit/devtools/server/tests/mochitest/test_inspector-traversal.html b/toolkit/devtools/server/tests/mochitest/test_inspector-traversal.html
---- a/toolkit/devtools/server/tests/mochitest/test_inspector-traversal.html
-+++ b/toolkit/devtools/server/tests/mochitest/test_inspector-traversal.html
-@@ -48,16 +48,36 @@ addTest(function testWalkerRoot() {
-   // actor as the getWalker returned.
-   promiseDone(gWalker.document().then(root => {
-     ok(root === gWalker.rootNode, "Re-fetching the document node should match the root document node.");
-     checkActorIDs.push(root.actorID);
-     assertOwnership();
-   }).then(runNextTest));
- });
- 
-+addTest(function testInnerHTML() {
-+  promiseDone(gWalker.documentElement().then(docElement => {
-+    return gWalker.innerHTML(docElement);
-+  }).then(longstring => {
-+    return longstring.string();
-+  }).then(innerHTML => {
-+    ok(innerHTML === gInspectee.documentElement.innerHTML, "innerHTML should match");
-+  }).then(runNextTest));
-+});
-+
-+addTest(function testOuterHTML() {
-+  promiseDone(gWalker.documentElement().then(docElement => {
-+    return gWalker.outerHTML(docElement);
-+  }).then(longstring => {
-+    return longstring.string();
-+  }).then(outerHTML => {
-+    ok(outerHTML === gInspectee.documentElement.outerHTML, "outerHTML should match");
-+  }).then(runNextTest));
-+});
-+
- addTest(function testQuerySelector() {
-   promiseDone(gWalker.querySelector(gWalker.rootNode, "#longlist").then(node => {
-     is(node.getAttribute("data-test"), "exists", "should have found the right node");
-     assertOwnership();
-   }).then(() => {
-     return gWalker.querySelector(gWalker.rootNode, "unknownqueryselector").then(node => {
-       ok(!node, "Should not find a node here.");
-       assertOwnership();
deleted file mode 100644
--- a/inspector-actor-markup-fixes.diff
+++ /dev/null
@@ -1,59 +0,0 @@
-# HG changeset patch
-# Parent d8ddd97cf06d971b86fbbe85be756b23e00dfdd8
-
-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
-@@ -122,23 +122,33 @@ var NodeActor = protocol.ActorClass({
-    * from its associated walker.
-    */
-   get conn() this.walker.conn,
- 
-   // Returns the JSON representation of this object over the wire.
-   form: function(detail) {
-     let parentNode = this.walker.parentNode(this);
- 
-+    // Estimate the number of children.
-+    let numChildren = this.rawNode.childNodes.length;
-+    if (numChildren === 0) {
-+      // This might be an iframe with virtual children.
-+      let walker = documentWalker(this.rawNode);
-+      if (walker.firstChild()) {
-+        numChildren = 1;
-+      }
-+    }
-+
-     let form = {
-       actor: this.actorID,
-       parent: parentNode ? parentNode.actorID : undefined,
-       nodeType: this.rawNode.nodeType,
-       namespaceURI: this.namespaceURI,
-       nodeName: this.rawNode.nodeName,
--      numChildren: this.rawNode.childNodes.length,
-+      numChildren: numChildren,
- 
-       // doctype attributes
-       name: this.rawNode.name,
-       publicId: this.rawNode.publicId,
-       systemId: this.rawNode.systemId,
- 
-       attrs: this.writeAttrs(),
- 
-@@ -1784,16 +1794,17 @@ var WalkerFront = exports.WalkerFront = 
-             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") {
-           // 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") {
-           // Retained orphans were force-released without the intervention of
-           // client (probably a navigated frame).
-           for (let released of change.nodes) {
-             let releasedFront = this.get(released);
deleted file mode 100644
--- a/inspector-actor-modify-attributes.diff
+++ /dev/null
@@ -1,299 +0,0 @@
-# HG changeset patch
-# User Dave Camp <dcamp@mozilla.com>
-# Date 1370924324 25200
-#      Mon Jun 10 21:18:44 2013 -0700
-# Node ID aa8731dc056cc52c0fa20078a3269f9f053962a9
-# Parent 479d31695ac872442a148328295e3880177d0e8a
-imported patch inspector-set-attributes.diff
-
-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
-@@ -1296,16 +1296,54 @@ var WalkerActor = protocol.ActorClass({
-   }, {
-     request: {
-       node: Arg(0, "domnode", { optional: true }),
-     },
-     response: {}
-   }),
- 
-   /**
-+   * Modify a node's attributes.  Passed an array of modifications
-+   * similar in format to "attributes" mutations.
-+   * {
-+   *   attributeName: <string>
-+   *   attributeNamespace: <optional string>
-+   *   newValue: <optional string> - If null or undefined, the attribute
-+   *     will be removed.
-+   * }
-+   *
-+   * Returns when the modifications have been made.  Mutations will
-+   * be queued for any changes made.
-+   */
-+  modifyAttributes: method(function(node, modifications) {
-+    let rawNode = node.rawNode;
-+    for (let change of modifications) {
-+      if (change.newValue == null) {
-+        if (change.attributeNamespace) {
-+          rawNode.removeAttributeNS(change.attributeNamespace, change.attributeName);
-+        } else {
-+          rawNode.removeAttribute(change.attributeName);
-+        }
-+      } else {
-+        if (change.attributeNamespace) {
-+          rawNode.setAttributeNS(change.attributeNamespace, change.attributeName, change.newValue);
-+        } else {
-+          rawNode.setAttribute(change.attributeName, change.newValue);
-+        }
-+      }
-+    }
-+  }, {
-+    request: {
-+      node: Arg(0, "domnode"),
-+      modifications: Arg(1, "array:json")
-+    },
-+    response: {}
-+  }),
-+
-+  /**
-    * Get a node's innerHTML property.
-    */
-   innerHTML: method(function(node) {
-     return LongStringActor(this.conn, node.rawNode.innerHTML);
-   }, {
-     request: {
-       node: Arg(0, "domnode")
-     },
-@@ -1637,16 +1675,23 @@ var WalkerFront = exports.WalkerFront = 
-   querySelector: protocol.custom(function(queryNode, selector) {
-     return this._querySelector(queryNode, selector).then(response => {
-       return response.node;
-     });
-   }, {
-     impl: "_querySelector"
-   }),
- 
-+  /**
-+   * Return a new AttributeModificationList for this node.
-+   */
-+  startModifyingAttributes: function(node) {
-+    return AttributeModificationList(this, node);
-+  },
-+
-   _releaseFront: function(node, force) {
-     if (node.retained && !force) {
-       node.reparent(null);
-       this._retainedOrphans.add(node);
-       return;
-     }
- 
-     if (node.retained) {
-@@ -1802,16 +1847,59 @@ var WalkerFront = exports.WalkerFront = 
-       this._orphaned.add(top);
-       walkerActor._orphaned.add(this.conn._transport._serverConnection.getActor(top.actorID));
-     }
-     return returnNode;
-   }
- });
- 
- /**
-+ * Convenience API for building a list of attribute modifications
-+ * for the `modifyAttributes` request.
-+ */
-+var AttributeModificationList = Class({
-+  initialize: function(walker, node) {
-+    this.walker = walker;
-+    this.node = node;
-+    this.modifications = [];
-+  },
-+
-+  apply: function() {
-+    let ret = this.walker.modifyAttributes(this.node, this.modifications);
-+    return ret;
-+  },
-+
-+  destroy: function() {
-+    this.walker = null;
-+    this.node = null;
-+    this.modification = null;
-+  },
-+
-+  setAttributeNS: function(ns, name, value) {
-+    this.modifications.push({
-+      attributeNamespace: ns,
-+      attributeName: name,
-+      newValue: value
-+    });
-+  },
-+
-+  setAttribute: function(name, value) {
-+    this.setAttributeNS(undefined, name, value);
-+  },
-+
-+  removeAttributeNS: function(ns, name) {
-+    this.setAttributeNS(ns, name, undefined);
-+  },
-+
-+  removeAttribute: function(name) {
-+    this.setAttributeNS(undefined, name, undefined);
-+  }
-+})
-+
-+/**
-  * Server side of the inspector actor, which is used to create
-  * inspector-related actors, including the walker.
-  */
- var InspectorActor = protocol.ActorClass({
-   typeName: "inspector",
-   initialize: function(conn, tabActor) {
-     protocol.Actor.prototype.initialize.call(this, conn);
-     this.tabActor = tabActor;
-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
-@@ -9,16 +9,17 @@ srcdir		= @srcdir@
- VPATH		= @srcdir@
- relativesrcdir	= @relativesrcdir@
- 
- include $(DEPTH)/config/autoconf.mk
- 
- MOCHITEST_CHROME_FILES	= \
- 	inspector-helpers.js \
- 	inspector-traversal-data.html \
-+	test_inspector-changeattrs.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-retain.html \
- 	test_inspector-pseudoclass-lock.html \
- 	test_inspector-traversal.html \
-diff --git a/toolkit/devtools/server/tests/mochitest/test_inspector-changeattrs.html b/toolkit/devtools/server/tests/mochitest/test_inspector-changeattrs.html
-new file mode 100644
---- /dev/null
-+++ b/toolkit/devtools/server/tests/mochitest/test_inspector-changeattrs.html
-@@ -0,0 +1,102 @@
-+<!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 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;
-+var checkActorIDs = [];
-+
-+function assertOwnership() {
-+  assertOwnershipTrees(gWalker);
-+}
-+
-+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;
-+    }).then(runNextTest));
-+  });
-+});
-+
-+addTest(function testChangeAttrs() {
-+  let attrNode = gInspectee.querySelector("#a");
-+  let attrFront;
-+  promiseDone(gWalker.querySelector(gWalker.rootNode, "#a").then(front => {
-+    attrFront = front;
-+    dump("attrFront is: " + attrFront + "\n");
-+    // Add a few attributes.
-+    let list = gWalker.startModifyingAttributes(attrFront);
-+    list.setAttribute("data-newattr", "newvalue");
-+    list.setAttribute("data-newattr2", "newvalue");
-+    return list.apply();
-+  }).then(() => {
-+    // We're only going to test that the change hit the document.
-+    // There are other tests that make sure changes are propagated
-+    // to the client.
-+    is(attrNode.getAttribute("data-newattr"), "newvalue", "Node should have the first new attribute");
-+    is(attrNode.getAttribute("data-newattr2"), "newvalue", "Node should have the second new attribute.");
-+  }).then(() => {
-+    // Change an attribute.
-+    let list = gWalker.startModifyingAttributes(attrFront);
-+    list.setAttribute("data-newattr", "changedvalue");
-+    return list.apply();
-+  }).then(() => {
-+    is(attrNode.getAttribute("data-newattr"), "changedvalue", "Node should have the changed first value.");
-+    is(attrNode.getAttribute("data-newattr2"), "newvalue", "Second value should remain unchanged.");
-+  }).then(() => {
-+    let list = gWalker.startModifyingAttributes(attrFront);
-+    list.removeAttribute("data-newattr2");
-+    return list.apply();
-+  }).then(() => {
-+    is(attrNode.getAttribute("data-newattr"), "changedvalue", "Node should have the changed first value.");
-+    ok(!attrNode.hasAttribute("data-newattr2"), "Second value should be removed.");
-+  }).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>
-diff --git a/toolkit/devtools/server/tests/mochitest/test_inspector-mutations-attr.html b/toolkit/devtools/server/tests/mochitest/test_inspector-mutations-attr.html
---- a/toolkit/devtools/server/tests/mochitest/test_inspector-mutations-attr.html
-+++ b/toolkit/devtools/server/tests/mochitest/test_inspector-mutations-attr.html
-@@ -45,17 +45,16 @@ addTest(setupAttrTest);
- addTest(testAddAttribute);
- addTest(testChangeAttribute);
- addTest(testRemoveAttribute);
- addTest(setupFrameAttrTest);
- addTest(testAddAttribute);
- addTest(testChangeAttribute);
- addTest(testRemoveAttribute);
- 
--
- function setupAttrTest() {
-   attrNode = gInspectee.querySelector("#a")
-   promiseDone(gWalker.querySelector(gWalker.rootNode, "#a").then(node => {
-     attrFront = node;
-   }).then(runNextTest));
- }
- 
- function setupFrameAttrTest() {
deleted file mode 100644
--- a/inspector-actor-modify-nodevalue.diff
+++ /dev/null
@@ -1,154 +0,0 @@
-# HG changeset patch
-# User Dave Camp <dcamp@mozilla.com>
-# Date 1370924324 25200
-#      Mon Jun 10 21:18:44 2013 -0700
-# Node ID 5f0cf3075c4dcc2d69e228a3ea1db724e0d1c101
-# Parent  aa8731dc056cc52c0fa20078a3269f9f053962a9
-imported patch inspector-set-nodevalue.diff
-
-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
-@@ -1335,16 +1335,32 @@ var WalkerActor = protocol.ActorClass({
-     request: {
-       node: Arg(0, "domnode"),
-       modifications: Arg(1, "array:json")
-     },
-     response: {}
-   }),
- 
-   /**
-+   * Modify a node's nodeValue.
-+   *
-+   * Returns when the modification has been made.  Mutations will be queued
-+   * for any changes made.
-+   */
-+  setNodeValue: method(function(node, string) {
-+    node.rawNode.nodeValue = string;
-+  }, {
-+    request: {
-+      node: Arg(0, "domnode"),
-+      value: Arg(1, "string")
-+    },
-+    response: {}
-+  }),
-+
-+  /**
-    * Get a node's innerHTML property.
-    */
-   innerHTML: method(function(node) {
-     return LongStringActor(this.conn, node.rawNode.innerHTML);
-   }, {
-     request: {
-       node: Arg(0, "domnode")
-     },
-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
-@@ -10,16 +10,17 @@ VPATH		= @srcdir@
- relativesrcdir	= @relativesrcdir@
- 
- include $(DEPTH)/config/autoconf.mk
- 
- MOCHITEST_CHROME_FILES	= \
- 	inspector-helpers.js \
- 	inspector-traversal-data.html \
- 	test_inspector-changeattrs.html \
-+	test_inspector-changevalue.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-retain.html \
- 	test_inspector-pseudoclass-lock.html \
- 	test_inspector-traversal.html \
-diff --git a/toolkit/devtools/server/tests/mochitest/test_inspector-changevalue.html b/toolkit/devtools/server/tests/mochitest/test_inspector-changevalue.html
-new file mode 100644
---- /dev/null
-+++ b/toolkit/devtools/server/tests/mochitest/test_inspector-changevalue.html
-@@ -0,0 +1,84 @@
-+<!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;
-+
-+function assertOwnership() {
-+  assertOwnershipTrees(gWalker);
-+}
-+
-+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;
-+    }).then(runNextTest));
-+  });
-+});
-+
-+addTest(function testChangeAttrs() {
-+  let contentNode = gInspectee.querySelector("#a").firstChild;
-+  let nodeFront;
-+  promiseDone(gWalker.querySelector(gWalker.rootNode, "#a").then(front => {
-+    // Get the text child
-+    return gWalker.children(front, { maxNodes: 1 });
-+  }).then(children => {
-+    nodeFront = children.nodes[0];
-+    is(nodeFront.nodeType, Ci.nsIDOMNode.TEXT_NODE);
-+    return nodeFront.setNodeValue("newvalue");
-+  }).then(() => {
-+    // We're only going to test that the change hit the document.
-+    // There are other tests that make sure changes are propagated
-+    // to the client.
-+    is(contentNode.nodeValue, "newvalue", "Node should have a new value.");
-+  }).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>
deleted file mode 100644
--- a/inspector-actor-pseudoclass.diff
+++ /dev/null
@@ -1,549 +0,0 @@
-# HG changeset patch
-# User Dave Camp <dcamp@mozilla.com>
-# Date 1370924321 25200
-#      Mon Jun 10 21:18:41 2013 -0700
-# Node ID eba8ce8bdd39a6ed813aa806572fb821b08bcf41
-# Parent dd2624fad51fb4c70259821e1af2b2dd26c1b93e
-Bug 877316 - Add pseudo-class lock functionality to the inspector actor. no-r
-
-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
- 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");
- 
-+const PSEUDO_CLASSES = [":hover", ":active", ":focus"];
-+
- Cu.import("resource://gre/modules/Services.jsm");
- 
- exports.register = function(handle) {
-   handle.addTabActor(InspectorActor, "inspectorActor");
- };
- 
- exports.unregister = function(handle) {
-   handle.removeTabActor(InspectorActor);
-@@ -133,17 +135,19 @@ var NodeActor = protocol.ActorClass({
-       nodeName: this.rawNode.nodeName,
-       numChildren: this.rawNode.childNodes.length,
- 
-       // doctype attributes
-       name: this.rawNode.name,
-       publicId: this.rawNode.publicId,
-       systemId: this.rawNode.systemId,
- 
--      attrs: this.writeAttrs()
-+      attrs: this.writeAttrs(),
-+
-+      pseudoClassLocks: this.writePseudoClassLocks(),
-     };
- 
-     if (this.rawNode.nodeValue) {
-       // We only include a short version of the value if it's longer than
-       // gValueSummaryLength
-       if (this.rawNode.nodeValue.length > gValueSummaryLength) {
-         form.shortValue = this.rawNode.nodeValue.substring(0, gValueSummaryLength);
-         form.incompleteValue = true;
-@@ -158,16 +162,30 @@ var NodeActor = protocol.ActorClass({
-   writeAttrs: function() {
-     if (!this.rawNode.attributes) {
-       return undefined;
-     }
-     return [{namespace: attr.namespace, name: attr.name, value: attr.value }
-             for (attr of this.rawNode.attributes)];
-   },
- 
-+  writePseudoClassLocks: function() {
-+    if (this.rawNode.nodeType !== Ci.nsIDOMNode.ELEMENT_NODE) {
-+      return undefined;
-+    }
-+    let ret = undefined;
-+    for (let pseudo of PSEUDO_CLASSES) {
-+      if (DOMUtils.hasPseudoClassLock(this.rawNode, pseudo)) {
-+        ret = ret || [];
-+        ret.push(pseudo);
-+      }
-+    }
-+    return ret;
-+  },
-+
-   /**
-    * Returns a LongStringActor with the node's value.
-    */
-   getNodeValue: method(function() {
-     return new LongStringActor(this.conn, this.rawNode.nodeValue || "");
-   }, {
-     request: {},
-     response: {
-@@ -279,16 +297,18 @@ let NodeFront = protocol.FrontClass(Node
-           name: change.attributeName,
-           namespace: change.attributeNamespace,
-           value: change.newValue
-         });
-       }
-     } else if (change.type === "characterData") {
-       this._form.shortValue = change.newValue;
-       this._form.incompleteValue = change.incompleteValue;
-+    } else if (change.type === "pseudoClassLock") {
-+      this._form.pseudoClassLocks = change.pseudoClassLocks;
-     }
-   },
- 
-   // Some accessors to make NodeFront feel more like an nsIDOMNode
- 
-   get id() this.getAttribute("id"),
- 
-   get nodeType() this._form.nodeType,
-@@ -317,16 +337,21 @@ let NodeFront = protocol.FrontClass(Node
-   },
-   hasAttribute: function(name) {
-     this._cacheAttributes();
-     return (name in this._attrMap);
-   },
- 
-   get attributes() this._form.attrs,
- 
-+  get pseudoClassLocks() this._form.pseudoClassLocks || [],
-+  hasPseudoClassLock: function(pseudo) {
-+    return this.pseudoClassLocks.some(locked => locked === pseudo);
-+  },
-+
-   getNodeValue: protocol.custom(function() {
-     if (!this.incompleteValue) {
-       return delayedResolve(new ShortLongString(this.shortValue));
-     } else {
-       return this._getNodeValue();
-     }
-   }, {
-     impl: "_getNodeValue"
-@@ -633,16 +658,17 @@ var WalkerActor = protocol.ActorClass({
-    * @param DebuggerServerConnection conn
-    *    The server connection.
-    */
-   initialize: function(conn, document, webProgress, options) {
-     protocol.Actor.prototype.initialize.call(this, conn);
-     this.rootDoc = document;
-     this._refMap = new Map();
-     this._pendingMutations = [];
-+    this._activePseudoClassLocks = new Set();
- 
-     // Nodes which have been removed from the client's known
-     // ownership tree are considered "orphaned", and stored in
-     // this set.
-     this._orphaned = new Set();
- 
-     // The client can tell the walker that it is interested in a node
-     // even when it is orphaned with the `retainNode` method.  This
-@@ -671,25 +697,31 @@ var WalkerActor = protocol.ActorClass({
-     }
-   },
- 
-   toString: function() {
-     return "[WalkerActor " + this.actorID + "]";
-   },
- 
-   destroy: function() {
--    protocol.Actor.prototype.destroy.call(this);
-+    this.clearPseudoClassLocks();
-+    this._activePseudoClassLocks = null;
-     this.progressListener.destroy();
-     this.rootDoc = null;
-+    protocol.Actor.prototype.destroy.call(this);
-   },
- 
-   release: method(function() {}, { release: true }),
- 
-   unmanage: function(actor) {
-     if (actor instanceof NodeActor) {
-+      if (this._activePseudoClassLocks &&
-+          this._activePseudoClassLocks.has(actor)) {
-+        this.clearPsuedoClassLocks(actor);
-+      }
-       this._refMap.delete(actor.rawNode);
-     }
-     protocol.Actor.prototype.unmanage.call(this, actor);
-   },
- 
-   _ref: function(node) {
-     let actor = this._refMap.get(node);
-     if (actor) return actor;
-@@ -1127,16 +1159,141 @@ var WalkerActor = protocol.ActorClass({
-       selector: Arg(1)
-     },
-     response: {
-       list: RetVal("domnodelist")
-     }
-   }),
- 
-   /**
-+   * Add a pseudo-class lock to a node.
-+   *
-+   * @param NodeActor node
-+   * @param string pseudo
-+   *    A pseudoclass: ':hover', ':active', ':focus'
-+   * @param options
-+   *    Options object:
-+   *    `parents`: True if the pseudo-class should be added
-+   *      to parent nodes.
-+   *
-+   * @returns An empty packet.  A "pseudoClassLock" mutation will
-+   *    be queued for any changed nodes.
-+   */
-+  addPseudoClassLock: method(function(node, pseudo, options={}) {
-+    this._addPseudoClassLock(node, pseudo);
-+
-+    if (!options.parents) {
-+      return;
-+    }
-+
-+    let walker = documentWalker(node.rawNode);
-+    let cur;
-+    while ((cur = walker.parentNode())) {
-+      let curNode = this._ref(cur);
-+      this._addPseudoClassLock(curNode, pseudo);
-+    }
-+  }, {
-+    request: {
-+      node: Arg(0, "domnode"),
-+      pseudoClass: Arg(1),
-+      parents: Option(2)
-+    },
-+    response: {}
-+  }),
-+
-+  _queuePseudoClassMutation: function(node) {
-+    this.queueMutation({
-+      target: node.actorID,
-+      type: "pseudoClassLock",
-+      pseudoClassLocks: node.writePseudoClassLocks()
-+    });
-+  },
-+
-+  _addPseudoClassLock: function(node, pseudo) {
-+    if (node.rawNode.nodeType !== Ci.nsIDOMNode.ELEMENT_NODE) {
-+      return false;
-+    }
-+    DOMUtils.addPseudoClassLock(node.rawNode, pseudo);
-+    this._activePseudoClassLocks.add(node);
-+    this._queuePseudoClassMutation(node);
-+    return true;
-+  },
-+
-+  /**
-+   * Remove a pseudo-class lock from a node.
-+   *
-+   * @param NodeActor node
-+   * @param string pseudo
-+   *    A pseudoclass: ':hover', ':active', ':focus'
-+   * @param options
-+   *    Options object:
-+   *    `parents`: True if the pseudo-class should be removed
-+   *      from parent nodes.
-+   *
-+   * @returns An empty response.  "pseudoClassLock" mutations
-+   *    will be emitted for any changed nodes.
-+   */
-+  removePseudoClassLock: method(function(node, pseudo, options={}) {
-+    this._removePseudoClassLock(node, pseudo);
-+
-+    if (!options.parents) {
-+      return;
-+    }
-+
-+    let walker = documentWalker(node.rawNode);
-+    let cur;
-+    while ((cur = walker.parentNode())) {
-+      let curNode = this._ref(cur);
-+      this._removePseudoClassLock(curNode, pseudo);
-+    }
-+  }, {
-+    request: {
-+      node: Arg(0, "domnode"),
-+      pseudoClass: Arg(1),
-+      parents: Option(2)
-+    },
-+    response: {}
-+  }),
-+
-+  _removePseudoClassLock: function(node, pseudo) {
-+    if (node.rawNode.nodeType != Ci.nsIDOMNode.ELEMENT_NODE) {
-+      return false;
-+    }
-+    DOMUtils.removePseudoClassLock(node.rawNode, pseudo);
-+    if (!node.writePseudoClassLocks()) {
-+      this._activePseudoClassLocks.delete(node);
-+    }
-+    this._queuePseudoClassMutation(node);
-+    return true;
-+  },
-+
-+  /**
-+   * Clear all the pseudo-classes on a given node
-+   * or all nodes.
-+   */
-+  clearPseudoClassLocks: method(function(node) {
-+    if (node) {
-+      DOMUtils.clearPseudoClassLocks(node.rawNode);
-+      this._activePseudoClassLocks.delete(node);
-+      this._queuePseudoClassMutation(node);
-+    } else {
-+      for (let locked of this._activePseudoClassLocks) {
-+        DOMUtils.clearPseudoClassLocks(locked.rawNode);
-+        this._activePseudoClassLocks.delete(locked);
-+        this._queuePseudoClassMutation(locked);
-+      }
-+    }
-+  }, {
-+    request: {
-+      node: Arg(0, "domnode", { optional: true }),
-+    },
-+    response: {}
-+  }),
-+
-+  /**
-    * Get any pending mutation records.  Must be called by the client after
-    * the `new-mutations` notification is received.  Returns an array of
-    * mutation records.
-    *
-    * Mutation records have a basic structure:
-    *
-    * {
-    *   type: attributes|characterData|childList,
-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
-@@ -15,14 +15,15 @@ MOCHITEST_CHROME_FILES	= \
- 	inspector-helpers.js \
- 	inspector-traversal-data.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-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
-@@ -73,16 +73,28 @@ function attachURL(url, callback) {
-         });
-       });
-     }
-   }, false);
- 
-   return cleanup;
- }
- 
-+function promiseOnce(target, event) {
-+  let deferred = Promise.defer();
-+  target.on(event, (...args) => {
-+    if (args.length === 1) {
-+      deferred.resolve(args[0]);
-+    } else {
-+      deferred.resolve(args);
-+    }
-+  });
-+  return deferred.promise;
-+}
-+
- function sortOwnershipChildren(children) {
-   return children.sort((a, b) => a.name.localeCompare(b.name));
- }
- 
- function serverOwnershipSubtree(walker, node) {
-   let actor = walker._refMap.get(node);
-   if (!actor) {
-     return undefined;
-diff --git a/toolkit/devtools/server/tests/mochitest/test_inspector-pseudoclass-lock.html b/toolkit/devtools/server/tests/mochitest/test_inspector-pseudoclass-lock.html
-new file mode 100644
---- /dev/null
-+++ b/toolkit/devtools/server/tests/mochitest/test_inspector-pseudoclass-lock.html
-@@ -0,0 +1,177 @@
-+<!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 Promise = devtools.require("sdk/core/promise");
-+const inspector = devtools.require("devtools/server/actors/inspector");
-+const DOMUtils = Components.classes["@mozilla.org/inspector/dom-utils;1"].
-+                   getService(Components.interfaces.inIDOMUtils);
-+
-+const KNOWN_PSEUDOCLASSES = [':hover', ':active', ':focus']
-+
-+window.onload = function() {
-+  SimpleTest.waitForExplicitFinish();
-+  runNextTest();
-+}
-+
-+var gInspectee = null;
-+var gWalker = null;
-+var gClient = null;
-+var gCleanupConnection = null;
-+
-+function setup(callback) {
-+  let url = document.getElementById("inspectorContent").href;
-+  gCleanupConnection = 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 => {
-+      gClient = client;
-+      gWalker = walker;
-+    }).then(callback));
-+  });
-+}
-+
-+function teardown() {
-+  gWalker = null;
-+  gClient = null;
-+  gInspectee = null;
-+  if (gCleanupConnection) {
-+    gCleanupConnection();
-+    gCleanupConnection = null;
-+  }
-+}
-+
-+function checkChange(change, expectation) {
-+  is(change.type, "pseudoClassLock", "Expect a pseudoclass lock change.");
-+  let target = change.target;
-+  if (expectation.id)
-+    is(target.id, expectation.id, "Expect a change on node id " + expectation.id);
-+  if (expectation.nodeName)
-+    is(target.nodeName, expectation.nodeName, "Expect a change on node name " + expectation.nodeName);
-+
-+  is(target.pseudoClassLocks.length, expectation.pseudos.length,
-+     "Expect " + expectation.pseudos.length + " pseudoclass locks.");
-+  for (let pseudo of expectation.pseudos) {
-+    ok(target.hasPseudoClassLock(pseudo), "Expect lock: " + pseudo);
-+    ok(DOMUtils.hasPseudoClassLock(target.rawNode(), pseudo), "Expect lock in dom: " + pseudo);
-+  }
-+
-+  for (let pseudo of KNOWN_PSEUDOCLASSES) {
-+    if (!expectation.pseudos.some(expected => pseudo === expected)) {
-+      ok(!target.hasPseudoClassLock(pseudo), "Don't expect lock: " + pseudo);
-+      ok(!DOMUtils.hasPseudoClassLock(target.rawNode(), pseudo), "Don't expect lock in dom: " + pseudo);
-+
-+    }
-+  }
-+}
-+
-+function checkMutations(mutations, expectations) {
-+  is(mutations.length, expectations.length, "Should get the right number of mutations.");
-+  for (let i = 0; i < mutations.length; i++) {
-+    checkChange(mutations[i] , expectations[i]);
-+  }
-+}
-+
-+addTest(function testPseudoClassLock() {
-+  let contentNode;
-+  let nodeFront;
-+  setup(() => {
-+    contentNode = gInspectee.querySelector("#b");
-+    return promiseDone(gWalker.querySelector(gWalker.rootNode, "#b").then(front => {
-+      nodeFront = front;
-+      // Lock the pseudoclass alone, no parents.
-+      gWalker.addPseudoClassLock(nodeFront, ':active');
-+      // Expect a single pseudoClassLock mutation.
-+      return promiseOnce(gWalker, "mutations");
-+    }).then(mutations => {
-+      is(mutations.length, 1, "Should get one mutations");
-+      is(mutations[0].target, nodeFront, "Should be the node we tried to apply to");
-+      checkChange(mutations[0], {
-+        id: "b",
-+        nodeName: "DIV",
-+        pseudos: [":active"]
-+      });
-+    }).then(() => {
-+      // Now add :hover, this time with parents.
-+      gWalker.addPseudoClassLock(nodeFront, ':hover', {parents: true});
-+      return promiseOnce(gWalker, "mutations");
-+    }).then(mutations => {
-+      let expectedMutations = [{
-+        id: 'b',
-+        nodeName: 'DIV',
-+        pseudos: [':hover', ':active'],
-+      },
-+      {
-+        id: 'longlist',
-+        nodeName: 'DIV',
-+        pseudos: [':hover']
-+      },
-+      {
-+        nodeName: 'BODY',
-+        pseudos: [':hover']
-+      },
-+      {
-+        nodeName: 'HTML',
-+        pseudos: [':hover']
-+      }];
-+      checkMutations(mutations, expectedMutations);
-+    }).then(() => {
-+      // Now remove the :hover on all parents
-+      gWalker.removePseudoClassLock(nodeFront, ':hover', {parents: true});
-+      return promiseOnce(gWalker, "mutations");
-+    }).then(mutations => {
-+      let expectedMutations = [{
-+        id: 'b',
-+        nodeName: 'DIV',
-+        // Should still have :active on the original node.
-+        pseudos: [':active']
-+      },
-+      {
-+        id: 'longlist',
-+        nodeName: 'DIV',
-+        pseudos: []
-+      },
-+      {
-+        nodeName: 'BODY',
-+        pseudos: []
-+      },
-+      {
-+        nodeName: 'HTML',
-+        pseudos: []
-+      }];
-+      checkMutations(mutations, expectedMutations);
-+    }).then(() => {
-+      // Now shut down the walker and make sure that clears up the remaining lock.
-+      return gWalker.release();
-+    }).then(() => {
-+      ok(!DOMUtils.hasPseudoClassLock(contentNode, ':active'), "Pseudoclass should have been removed during destruction.");
-+      teardown();
-+    }).then(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>
deleted file mode 100644
--- a/inspector-actor-queue-mutations.diff
+++ /dev/null
@@ -1,118 +0,0 @@
-# HG changeset patch
-# User Dave Camp <dcamp@mozilla.com>
-# Date 1371007643 25200
-#      Tue Jun 11 20:27:23 2013 -0700
-# Node ID dbc8e30c05d208167df53f10427cca00af677dec
-# Parent 88cd07a112ae43a74d5f3cd15d25ca2cae5bfcdc
-Bug 878433 - r?
-
-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
-@@ -1134,27 +1134,39 @@ var WalkerActor = protocol.ActorClass({
-     request: {
-       cleanup: Option(0)
-     },
-     response: {
-       mutations: RetVal("array:dommutation")
-     }
-   }),
- 
-+  queueMutation: function(mutation) {
-+    if (!this.actorID) {
-+      // We've been destroyed, don't bother queueing this mutation.
-+      return;
-+    }
-+    // We only send the `new-mutations` notification once, until the client
-+    // fetches mutations with the `getMutations` packet.
-+    let needEvent = this._pendingMutations.length === 0;
-+
-+    this._pendingMutations.push(mutation);
-+
-+    if (needEvent) {
-+      events.emit(this, "new-mutations");
-+    }
-+  },
-+
-   /**
-    * Handles mutations from the DOM mutation observer API.
-    *
-    * @param array[MutationRecord] mutations
-    *    See https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver#MutationRecord
-    */
-   onMutations: function(mutations) {
--    // We only send the `new-mutations` notification once, until the client
--    // fetches mutations with the `getMutations` packet.
--    let needEvent = this._pendingMutations.length === 0;
--
-     for (let change of mutations) {
-       let targetActor = this._refMap.get(change.target);
-       if (!targetActor) {
-         continue;
-       }
-       let targetNode = change.target;
-       let mutation = {
-         type: change.type,
-@@ -1201,58 +1213,47 @@ var WalkerActor = protocol.ActorClass({
-           // to date.
-           this._orphaned.delete(addedActor);
-           addedActors.push(addedActor.actorID);
-         }
-         mutation.numChildren = change.target.childNodes.length;
-         mutation.removed = removedActors;
-         mutation.added = addedActors;
-       }
--      this._pendingMutations.push(mutation);
--    }
--    if (needEvent) {
--      events.emit(this, "new-mutations");
-+      this.queueMutation(mutation);
-     }
-   },
- 
-   onFrameLoad: function(window) {
-     let frame = window.frameElement;
-     let frameActor = this._refMap.get(frame);
-     if (!frameActor) {
-       return;
-     }
--    let needEvent = this._pendingMutations.length === 0;
--    this._pendingMutations.push({
-+
-+    this.queueMutation({
-       type: "frameLoad",
-       target: frameActor.actorID,
-       added: [],
-       removed: []
-     });
--
--    if (needEvent) {
--      events.emit(this, "new-mutations");
--    }
-   },
- 
-   onFrameUnload: function(window) {
-     let doc = window.document;
-     let documentActor = this._refMap.get(doc);
-     if (!documentActor) {
-       return;
-     }
- 
--    let needEvent = this._pendingMutations.length === 0;
--    this._pendingMutations.push({
-+    this.queueMutation({
-       type: "documentUnload",
-       target: documentActor.actorID
-     });
-     this.releaseNode(documentActor);
--    if (needEvent) {
--      events.emit(this, "new-mutations");
--    }
-   }
- });
- 
- /**
-  * 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.
deleted file mode 100644
--- a/inspector-actor-readystate.diff
+++ /dev/null
@@ -1,56 +0,0 @@
-# HG changeset patch
-# Parent 6e21fc2a61a895fcfc1b5a7fc08e6ccccf61b3c2
-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
-@@ -1775,26 +1775,46 @@ var WalkerFront = exports.WalkerFront = 
-  * Server side of the inspector actor, which is used to create
-  * inspector-related actors, including the walker.
-  */
- var InspectorActor = protocol.ActorClass({
-   typeName: "inspector",
-   initialize: function(conn, tabActor) {
-     protocol.Actor.prototype.initialize.call(this, conn);
-     this.tabActor = tabActor;
-+  },
-+
-+  get window() {
-+    let tabActor = this.tabActor;
-     if (tabActor.browser instanceof Ci.nsIDOMWindow) {
--      this.window = tabActor.browser;
-+      return tabActor.browser;
-     } else if (tabActor.browser instanceof Ci.nsIDOMElement) {
--      this.window = tabActor.browser.contentWindow;
-+      return tabActor.browser.contentWindow;
-     }
--    this.webProgress = tabActor._tabbrowser;
-+    return null;
-   },
- 
-   getWalker: method(function(options={}) {
--    return WalkerActor(this.conn, this.window.document, this.webProgress, options);
-+    let deferred = promise.defer();
-+
-+    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));
-+    };
-+
-+    if (window.document.readyState === "loading") {
-+      window.addEventListener("DOMContentLoaded", domReady, true);
-+    } else {
-+      domReady();
-+    }
-+
-+    return deferred.promise;
-   }, {
-     request: {},
-     response: {
-       walker: RetVal("domwalker")
-     }
-   })
- });
- 
deleted file mode 100644
--- a/inspector-actor-retain.diff
+++ /dev/null
@@ -1,839 +0,0 @@
-# HG changeset patch
-# User Dave Camp <dcamp@mozilla.com>
-# Date 1370924325 25200
-#      Mon Jun 10 21:18:45 2013 -0700
-# Node ID 9215e54470c9610389b34fda374278f24432072f
-# Parent 61eae06ca468e31af1a16267874a012652e04e9d
-Bug 881120 - r+, waiting on dependency
-
-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
-@@ -204,29 +204,28 @@ let NodeFront = protocol.FrontClass(Node
-   initialize: function(conn, form, detail, ctx) {
-     this._parent = null; // The parent node
-     this._child = null;  // The first child of this node.
-     this._next = null;   // The next sibling of this node.
-     this._prev = null;   // The previous sibling of this node.
-     protocol.Front.prototype.initialize.call(this, conn, form, detail, ctx);
-   },
- 
-+  /**
-+   * Destroy a node front.  The node must have been removed from the
-+   * ownership tree before this is called, unless the whole walker front
-+   * is being destroyed.
-+   */
-   destroy: function() {
-     // If an observer was added on this node, shut it down.
-     if (this.observer) {
-       this._observer.disconnect();
-       this._observer = null;
-     }
- 
--    // Disconnect this item and from the ownership tree and destroy
--    // all of its children.
--    this.reparent(null);
--    for (let child of this.treeChildren()) {
--      child.destroy();
--    }
-     protocol.Front.prototype.destroy.call(this);
-   },
- 
-   // Update the object given a form representation off the wire.
-   form: function(form, detail, ctx) {
-     // 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);
-@@ -640,16 +639,21 @@ var WalkerActor = protocol.ActorClass({
-     this._refMap = new Map();
-     this._pendingMutations = [];
- 
-     // Nodes which have been removed from the client's known
-     // ownership tree are considered "orphaned", and stored in
-     // this set.
-     this._orphaned = new Set();
- 
-+    // The client can tell the walker that it is interested in a node
-+    // even when it is orphaned with the `retainNode` method.  This
-+    // list contains orphaned nodes that were so retained.
-+    this._retainedOrphans = new Set();
-+
-     this.onMutations = this.onMutations.bind(this);
-     this.onFrameLoad = this.onFrameLoad.bind(this);
-     this.onFrameUnload = this.onFrameUnload.bind(this);
- 
-     this.progressListener = ProgressListener(webProgress);
- 
-     events.on(this.progressListener, "windowchange-start", this.onFrameUnload);
-     events.on(this.progressListener, "windowchange-stop", this.onFrameLoad);
-@@ -786,33 +790,85 @@ var WalkerActor = protocol.ActorClass({
-     let parent = walker.parentNode();
-     if (parent) {
-       return this._ref(parent);
-     }
-     return null;
-   },
- 
-   /**
-+   * Mark a node as 'retained'.
-+   *
-+   * A retained node is not released when `releaseNode` is called on its
-+   * parent, or when a parent is released with the `cleanup` option to
-+   * `getMutations`.
-+   *
-+   * When a retained node's parent is released, a retained mode is added to
-+   * the walker's "retained orphans" list.
-+   *
-+   * Retained nodes can be deleted by providing the `force` option to
-+   * `releaseNode`.  They will also be released when their document
-+   * has been destroyed.
-+   *
-+   * Retaining a node makes no promise about its children;  They can
-+   * still be removed by normal means.
-+   */
-+  retainNode: method(function(node) {
-+    node.retained = true;
-+  }, {
-+    request: { node: Arg(0, "domnode") },
-+    response: {}
-+  }),
-+
-+  /**
-+   * Remove the 'retained' mark from a node.  If the node was a
-+   * retained orphan, release it.
-+   */
-+  unretainNode: method(function(node) {
-+    node.retained = false;
-+    if (this._retainedOrphans.has(node)) {
-+      this._retainedOrphans.delete(node);
-+      this.releaseNode(node);
-+    }
-+  }, {
-+    request: { node: Arg(0, "domnode") },
-+    response: {},
-+  }),
-+
-+  /**
-    * Release actors for a node and all child nodes.
-    */
--  releaseNode: method(function(node) {
-+  releaseNode: method(function(node, options={}) {
-+    if (node.retained && !options.force) {
-+      this._retainedOrphans.add(node);
-+      return;
-+    }
-+
-+    if (node.retained) {
-+      // Forcing a retained node to go away.
-+      this._retainedOrphans.delete(node);
-+    }
-+
-     let walker = documentWalker(node.rawNode);
- 
-     let child = walker.firstChild();
-     while (child) {
-       let childActor = this._refMap.get(child);
-       if (childActor) {
--        this.releaseNode(childActor);
-+        this.releaseNode(childActor, options);
-       }
-       child = walker.nextSibling();
-     }
- 
-     node.destroy();
-   }, {
--    request: { node: Arg(0, "domnode") }
-+    request: {
-+      node: Arg(0, "domnode"),
-+      force: Option(1)
-+    }
-   }),
- 
-   /**
-    * Add any nodes between `node` and the walker's root node that have not
-    * yet been seen by the client.
-    */
-   ensurePathToRoot: function(node, newParents=new Set()) {
-     if (!node) {
-@@ -1119,16 +1175,18 @@ var WalkerActor = protocol.ActorClass({
-    * in the new set of children it needs to issue a `children` request.
-    */
-   getMutations: method(function(options={}) {
-     let pending = this._pendingMutations || [];
-     this._pendingMutations = [];
- 
-     if (options.cleanup) {
-       for (let node of this._orphaned) {
-+        // Release the orphaned node.  Nodes or children that have been
-+        // retained will be moved to this._retainedOrphans.
-         this.releaseNode(node);
-       }
-       this._orphaned = new Set();
-     }
- 
-     return pending;
-   }, {
-     request: {
-@@ -1232,41 +1290,79 @@ var WalkerActor = protocol.ActorClass({
-     this.queueMutation({
-       type: "frameLoad",
-       target: frameActor.actorID,
-       added: [],
-       removed: []
-     });
-   },
- 
-+  // Returns true if domNode is in window or a subframe.
-+  _childOfWindow: function(window, domNode) {
-+    let win = nodeDocument(domNode).defaultView;
-+    while (win) {
-+      if (win === window) {
-+        return true;
-+      }
-+      win = win.frameElement;
-+    }
-+    return false;
-+  },
-+
-   onFrameUnload: function(window) {
-+    // Any retained orphans that belong to this document
-+    // or its children need to be released, and a mutation sent
-+    // to notify of that.
-+    let releasedOrphans = [];
-+
-+    for (let retained of this._retainedOrphans) {
-+      if (Cu.isDeadWrapper(retained.rawNode) ||
-+          this._childOfWindow(window, retained.rawNode)) {
-+        this._retainedOrphans.delete(retained);
-+        releasedOrphans.push(retained.actorID);
-+        this.releaseNode(retained, { force: true });
-+      }
-+    }
-+
-+    if (releasedOrphans.length > 0) {
-+      this.queueMutation({
-+        target: this.rootNode.actorID,
-+        type: "unretained",
-+        nodes: releasedOrphans
-+      });
-+    }
-+
-     let doc = window.document;
-     let documentActor = this._refMap.get(doc);
-     if (!documentActor) {
-       return;
-     }
- 
-     this.queueMutation({
-       type: "documentUnload",
-       target: documentActor.actorID
-     });
--    this.releaseNode(documentActor);
-+
-+    // Need to force a release of this node, because those nodes can't
-+    // be accessed anymore.
-+    this.releaseNode(documentActor, { force: true });
-   }
- });
- 
- /**
-  * 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) {
-     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) {
-@@ -1286,34 +1382,96 @@ var WalkerFront = exports.WalkerFront = 
-     let front = this.get(id);
-     if (front) {
-       return front;
-     }
- 
-     return types.getType("domnode").read({ actor: id }, this, "standin");
-   },
- 
--  releaseNode: protocol.custom(function(node) {
-+  /**
-+   * See the documentation for WalkerActor.prototype.retainNode for
-+   * information on retained nodes.
-+   *
-+   * From the client's perspective, `retainNode` can fail if the node in
-+   * question is removed from the ownership tree before the `retainNode`
-+   * request reaches the server.  This can only happen if the client has
-+   * asked the server to release nodes but hasn't gotten a response
-+   * yet: Either a `releaseNode` request or a `getMutations` with `cleanup`
-+   * set is outstanding.
-+   *
-+   * If either of those requests is outstanding AND releases the retained
-+   * node, this request will fail with noSuchActor, but the ownership tree
-+   * will stay in a consistent state.
-+   *
-+   * Because the protocol guarantees that requests will be processed and
-+   * responses received in the order they were sent, we get the right
-+   * semantics by setting our local retained flag on the node only AFTER
-+   * a SUCCESSFUL retainNode call.
-+   */
-+  retainNode: protocol.custom(function(node) {
-+    return this._retainNode(node).then(() => {
-+      node.retained = true;
-+    });
-+  }, {
-+    impl: "_retainNode",
-+  }),
-+
-+  unretainNode: protocol.custom(function(node) {
-+    return this._unretainNode(node).then(() => {
-+      node.retained = false;
-+      if (this._retainedOrphans.has(node)) {
-+        this._retainedOrphans.delete(node);
-+        this._releaseFront(node);
-+      }
-+    });
-+  }, {
-+    impl: "_unretainNode"
-+  }),
-+
-+  releaseNode: protocol.custom(function(node, options={}) {
-     // NodeFront.destroy will destroy children in the ownership tree too,
-     // mimicking what the server will do here.
-     let actorID = node.actorID;
--    node.destroy();
-+    this._releaseFront(node, !!options.force);
-     return this._releaseNode({ actorID: actorID });
-   }, {
-     impl: "_releaseNode"
-   }),
- 
-   querySelector: protocol.custom(function(queryNode, selector) {
-     return this._querySelector(queryNode, selector).then(response => {
-       return response.node;
-     });
-   }, {
-     impl: "_querySelector"
-   }),
- 
-+  _releaseFront: function(node, force) {
-+    if (node.retained && !force) {
-+      node.reparent(null);
-+      this._retainedOrphans.add(node);
-+      return;
-+    }
-+
-+    if (node.retained) {
-+      // Forcing a removal.
-+      this._retainedOrphans.delete(node);
-+    }
-+
-+    // Release any children
-+    for (let child of node.treeChildren()) {
-+      this._releaseFront(child, force);
-+    }
-+
-+    // All children will have been removed from the node by this point.
-+    node.reparent(null);
-+    node.destroy();
-+  },
-+
-   /**
-    * 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.
-@@ -1370,27 +1528,38 @@ var WalkerFront = exports.WalkerFront = 
-             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") {
-           // We try to give fronts instead of actorIDs, but these fronts need
-           // to be destroyed now.
-           emittedMutation.target = targetFront.actorID;
--          targetFront.destroy();
-+
-+          // Release the document node and all of its children, even retained.
-+          this._releaseFront(targetFront, true);
-+        } else if (change.type === "unretained") {
-+          // Retained orphans were force-released without the intervention of
-+          // client (probably a navigated frame).
-+          for (let released of change.nodes) {
-+            let releasedFront = this.get(released);
-+            this._retainedOrphans.delete(released);
-+            this._releaseFront(releasedFront, true);
-+          }
-         } else {
-           targetFront.updateMutation(change);
-         }
- 
-         emitMutations.push(emittedMutation);
-       }
- 
-       if (options.cleanup) {
-         for (let node of this._orphaned) {
--          node.destroy();
-+          // This will move retained nodes to this._retainedOrphans.
-+          this._releaseFront(node);
-         }
-         this._orphaned = new Set();
-       }
- 
-       events.emit(this, "mutations", emitMutations);
-     });
-   }, {
-     impl: "_getMutations"
-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
-@@ -14,14 +14,15 @@ include $(DEPTH)/config/autoconf.mk
- MOCHITEST_CHROME_FILES	= \
- 	inspector-helpers.js \
- 	inspector-traversal-data.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-retain.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
-@@ -105,31 +105,33 @@ function serverOwnershipSubtree(walker, 
- }
- 
- function serverOwnershipTree(walker) {
-   let serverConnection = walker.conn._transport._serverConnection;
-   let serverWalker = serverConnection.getActor(walker.actorID);
- 
-   return {
-     root: serverOwnershipSubtree(serverWalker, serverWalker.rootDoc ),
--    orphaned: [serverOwnershipSubtree(serverWalker, o.rawNode) for (o of serverWalker._orphaned)]
-+    orphaned: [serverOwnershipSubtree(serverWalker, o.rawNode) for (o of serverWalker._orphaned)],
-+    retained: [serverOwnershipSubtree(serverWalker, o.rawNode) for (o of serverWalker._retainedOrphans)]
-   };
- }
- 
- function clientOwnershipSubtree(node) {
-   return {
-     name: node.actorID,
-     children: sortOwnershipChildren([clientOwnershipSubtree(child) for (child of node.treeChildren())])
-   }
- }
- 
- function clientOwnershipTree(walker) {
-   return {
-     root: clientOwnershipSubtree(walker.rootNode),
--    orphaned: [clientOwnershipSubtree(o) for (o of walker._orphaned)]
-+    orphaned: [clientOwnershipSubtree(o) for (o of walker._orphaned)],
-+    retained: [clientOwnershipSubtree(o) for (o of walker._retainedOrphans)]
-   }
- }
- 
- function ownershipTreeSize(tree) {
-   let size = 1;
-   for (let child of tree.children) {
-     size += ownershipTreeSize(child);
-   }
-@@ -156,26 +158,114 @@ function checkMissing(client, actorID) {
-     type: "request",
-   }, response => {
-     is(response.error, "noSuchActor", "node list actor should no longer be contactable.");
-     deferred.resolve(undefined);
-   });
-   return deferred.promise;
- }
- 
-+// Verify that an actorID is accessible both from the client library and the server.
-+function checkAvailable(client, actorID) {
-+  let deferred = Promise.defer();
-+  let front = client.getActor(actorID);
-+  ok(front, "Front should be accessible from the client for actorID: " + actorID);
-+
-+  let deferred = Promise.defer();
-+  client.request({
-+    to: actorID,
-+    type: "garbageAvailableTest",
-+  }, response => {
-+    is(response.error, "unrecognizedPacketType", "node list actor should be contactable.");
-+    deferred.resolve(undefined);
-+  });
-+  return deferred.promise;
-+}
-+
- function promiseDone(promise) {
-   promise.then(null, err => {
-     ok(false, "Promise failed: " + err);
-     if (err.stack) {
-       dump(err.stack);
-     }
-     SimpleTest.finish();
-   });
- }
- 
-+// Mutation list testing
-+
-+function isSrcChange(change) {
-+  return (change.type === "attributes" && change.attributeName === "src");
-+}
-+
-+function assertAndStrip(mutations, message, test) {
-+  let size = mutations.length;
-+  mutations = mutations.filter(test);
-+  ok((mutations.size != size), message);
-+  return mutations;
-+}
-+
-+function isSrcChange(change) {
-+  return change.type === "attributes" && change.attributeName === "src";
-+}
-+
-+function isUnload(change) {
-+  return change.type === "documentUnload";
-+}
-+
-+function isFrameLoad(change) {
-+  return change.type === "frameLoad";
-+}
-+
-+function isUnretained(change) {
-+  return change.type === "unretained";
-+}
-+
-+function isChildList(change) {
-+  return change.type === "childList";
-+}
-+
-+// 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
-+function assertUnload(mutations) {
-+  return assertAndStrip(mutations, "Should have had a document unload change.", isUnload);
-+}
-+
-+// Make sure there's a frame load in the mutation list and strip
-+// that mutation out of the list
-+function assertFrameLoad(mutations) {
-+  return assertAndStrip(mutations, "Should have had a frame load change.", isFrameLoad);
-+}
-+
-+// Load mutations aren't predictable, so keep accumulating mutations until
-+// the one we're looking for shows up.
-+function waitForMutation(walker, test, mutations=[]) {
-+  let deferred = Promise.defer();
-+  for (let change of mutations) {
-+    if (test(change)) {
-+      deferred.resolve(mutations);
-+    }
-+  }
-+
-+  walker.once("mutations", newMutations => {
-+    waitForMutation(walker, test, mutations.concat(newMutations)).then(finalMutations => {
-+      deferred.resolve(finalMutations);
-+    })
-+  });
-+
-+  return deferred.promise;
-+}
-+
-+
- var _tests = [];
- function addTest(test) {
-   _tests.push(test);
- }
- 
- function runNextTest() {
-   if (_tests.length == 0) {
-     SimpleTest.finish()
-diff --git a/toolkit/devtools/server/tests/mochitest/test_inspector-mutations-frameload.html b/toolkit/devtools/server/tests/mochitest/test_inspector-mutations-frameload.html
---- a/toolkit/devtools/server/tests/mochitest/test_inspector-mutations-frameload.html
-+++ b/toolkit/devtools/server/tests/mochitest/test_inspector-mutations-frameload.html
-@@ -62,77 +62,16 @@ function loadChildSelector(selector) {
-     return gWalker.children(frame);
-   }).then(children => {
-     return gWalker.querySelectorAll(children.nodes[0], selector);
-   }).then(nodeList => {
-     return nodeList.items();
-   });
- }
- 
--
--function isSrcChange(change) {
--  return (change.type === "attributes" && change.attributeName === "src");
--}
--
--function assertAndStrip(mutations, message, test) {
--  let size = mutations.length;
--  mutations = mutations.filter(test);
--  ok((mutations.size != size), message);
--  return mutations;
--}
--
--function isSrcChange(change) {
--  return change.type === "attributes" && change.attributeName === "src";
--}
--
--function isUnload(change) {
--  return change.type === "documentUnload";
--}
--
--function isFrameLoad(change) {
--  return change.type === "frameLoad";
--}
--
--// 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
--function assertUnload(mutations) {
--  return assertAndStrip(mutations, "Should have had a document unload change.", isUnload);
--}
--
--// Make sure there's a frame load in the mutation list and strip
--// that mutation out of the list
--function assertFrameLoad(mutations) {
--  return assertAndStrip(mutations, "Should have had a frame load change.", isFrameLoad);
--}
--
--// Load mutations aren't predictable, so keep accumulating mutations until
--// the one we're looking for shows up.
--function waitForMutation(walker, test, mutations=[]) {
--  let deferred = Promise.defer();
--  for (let change of mutations) {
--    if (test(change)) {
--      deferred.resolve(mutations);
--    }
--  }
--
--  walker.once("mutations", newMutations => {
--    waitForMutation(walker, test, mutations.concat(newMutations)).then(finalMutations => {
--      deferred.resolve(finalMutations);
--    })
--  });
--
--  return deferred.promise;
--}
--
- function getUnloadedDoc(mutations) {
-   for (let change of mutations) {
-     if (isUnload(change)) {
-       return change.target;
-     }
-   }
-   return null;
- }
-diff --git a/toolkit/devtools/server/tests/mochitest/test_inspector-retain.html b/toolkit/devtools/server/tests/mochitest/test_inspector-retain.html
-new file mode 100644
---- /dev/null
-+++ b/toolkit/devtools/server/tests/mochitest/test_inspector-retain.html
-@@ -0,0 +1,183 @@
-+<!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 Promise = devtools.require("sdk/core/promise");
-+const inspector = devtools.require("devtools/server/actors/inspector");
-+
-+window.onload = function() {
-+  SimpleTest.waitForExplicitFinish();
-+  runNextTest();
-+}
-+
-+var gWalker = null;
-+var gClient = null;
-+var gInspectee = null;
-+
-+function assertOwnership() {
-+  return assertOwnershipTrees(gWalker);
-+}
-+
-+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;
-+    }).then(runNextTest));
-+  });
-+});
-+
-+// Retain a node, and a second-order child (in another document, for kicks)
-+// Release the parent of the top item, which should cause one retained orphan.
-+
-+// Then unretain the top node, which should retain the orphan.
-+
-+// Then change the source of the iframe, which should kill that orphan.
-+
-+addTest(function testRetain() {
-+  let originalOwnershipSize = 0;
-+  let bodyFront = null;
-+  let frameFront = null;
-+  let childListFront = null;
-+  // Get the toplevel body element and retain it.
-+  promiseDone(gWalker.querySelector(gWalker.rootNode, "body").then(front => {
-+    bodyFront = front;
-+    return gWalker.retainNode(bodyFront);
-+  }).then(() => {
-+    // Get an element in the child frame and retain it.
-+    return gWalker.querySelector(gWalker.rootNode, "#childFrame");
-+  }).then(frame => {
-+    frameFront = frame;
-+    return gWalker.children(frame, { maxNodes: 1 }).then(children => {
-+      return children.nodes[0];
-+    });
-+  }).then(childDoc => {
-+    return gWalker.querySelector(childDoc, "#longlist");
-+  }).then(list => {
-+    childListFront = list;
-+    originalOwnershipSize = assertOwnership();
-+    // and rtain it.
-+    return gWalker.retainNode(childListFront);
-+  }).then(() => {
-+    // OK, try releasing the parent of the first retained.
-+    return gWalker.releaseNode(bodyFront.parentNode());
-+  }).then(() => {
-+    let size = assertOwnership();
-+    let clientTree = clientOwnershipTree(gWalker);
-+
-+    // That request should have freed the parent of the first retained
-+    // but moved the rest into the retained orphaned tree.
-+    is(ownershipTreeSize(clientTree.root) + ownershipTreeSize(clientTree.retained[0]) + 1,
-+       originalOwnershipSize,
-+       "Should have only lost one item overall.");
-+    is(gWalker._retainedOrphans.size, 1, "Should have retained one orphan");
-+    ok(gWalker._retainedOrphans.has(bodyFront), "Should have retained the expected node.");
-+  }).then(() => {
-+    // Unretain the body, which should promote the childListFront to a retained orphan.
-+    return gWalker.unretainNode(bodyFront);
-+  }).then(() => {
-+    assertOwnership();
-+    let clientTree = clientOwnershipTree(gWalker);
-+
-+    is(gWalker._retainedOrphans.size, 1, "Should still only have one retained orphan.");
-+    ok(!gWalker._retainedOrphans.has(bodyFront), "Should have dropped the body node.")
-+    ok(gWalker._retainedOrphans.has(childListFront), "Should have retained the child node.")
-+  }).then(() => {
-+    // Change the source of the iframe, which should kill the retained orphan.
-+    gInspectee.querySelector("#childFrame").src = "data:text/html,<html>new child</html>";
-+    return waitForMutation(gWalker, isUnretained);
-+  }).then(mutations => {
-+    assertOwnership();
-+    let clientTree = clientOwnershipTree(gWalker);
-+    is(gWalker._retainedOrphans.size, 0, "Should have no more retained orphans.");
-+
-+  }).then(runNextTest));
-+});
-+
-+// Get a hold of a node, remove it from the doc and retain it at the same time.
-+// We should always win that race (even though the mutation happens before the
-+// retain request), because we haven't issued `getMutations` yet.
-+addTest(function testWinRace() {
-+  let front = null;
-+  promiseDone(gWalker.querySelector(gWalker.rootNode, "#a").then(node => {
-+    front = node;
-+    let contentNode = gInspectee.querySelector("#a");
-+    contentNode.parentNode.removeChild(contentNode);
-+    // Now wait for that mutation and retain response to come in.
-+    return Promise.all([
-+      gWalker.retainNode(front),
-+      waitForMutation(gWalker, isChildList)
-+    ]);
-+  }).then(() => {
-+    assertOwnership();
-+    let clientTree = clientOwnershipTree(gWalker);
-+    is(gWalker._retainedOrphans.size, 1, "Should have a retained orphan.");
-+    ok(gWalker._retainedOrphans.has(front), "Should have retained our expected node.");
-+    return gWalker.unretainNode(front);
-+  }).then(() => {
-+    // Make sure we're clear for the next test.
-+    assertOwnership();
-+    let clientTree = clientOwnershipTree(gWalker);
-+    is(gWalker._retainedOrphans.size, 0, "Should have no more retained orphans.");
-+  }).then(runNextTest));
-+});
-+
-+// Same as above, but issue the request right after the 'new-mutations' event, so that
-+// we *lose* the race.
-+addTest(function testLoseRace() {
-+  let front = null;
-+  promiseDone(gWalker.querySelector(gWalker.rootNode, "#z").then(node => {
-+    front = node;
-+    gInspectee.querySelector("#z").parentNode = null;
-+    let contentNode = gInspectee.querySelector("#a");
-+    contentNode.parentNode.removeChild(contentNode);
-+    return promiseOnce(gWalker, "new-mutations");
-+  }).then(() => {
-+    // Verify that we have an outstanding request (no good way to tell that it's a
-+    // getMutations request, but there's nothing else it would be).
-+    is(gWalker._requests.length, 1, "Should have an outstanding request.");
-+    return gWalker.retainNode(front)
-+  }).then(() => { ok(false, "Request should not have succeeded!"); },
-+          (err) => {
-+    ok(err, "noSuchActor", "Should have lost the race.");
-+    let clientTree = clientOwnershipTree(gWalker);
-+    is(gWalker._retainedOrphans.size, 0, "Should have no more retained orphans.");
-+    // Don't re-throw the error.
-+  }).then(runNextTest));
-+});
-+
-+addTest(function cleanup() {
-+  delete gWalker;
-+  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>
deleted file mode 100644
--- a/inspector-actor-sibling-traversal.diff
+++ /dev/null
@@ -1,113 +0,0 @@
-# HG changeset patch
-# User Dave Camp <dcamp@mozilla.com>
-# Date 1370924298 25200
-#      Mon Jun 10 21:18:18 2013 -0700
-# Node ID ed630d04acf2f288cc5fcac8f245d2f11b5145fe
-# Parent ec657949abbc58a6a27bdfe404c0e0e765426adb
-No bug, fixes nextSibling/prevSibling on first/last nodes.
-
-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
-@@ -587,17 +587,17 @@ let nodeArrayMethod = {
- };
- 
- let traversalMethod = {
-   request: {
-     node: Arg(0, "domnode"),
-     whatToShow: Option(1)
-   },
-   response: {
--    node: RetVal("domnode")
-+    node: RetVal("domnode", {optional: true})
-   }
- }
- 
- /**
-  * We need to know when a document is navigating away so that we can kill
-  * the nodes underneath it.  We also need to know when a document is
-  * navigated to so that we can send a mutation event for the iframe node.
-  *
-@@ -1062,31 +1062,33 @@ var WalkerActor = protocol.ActorClass({
-    *
-    * @param object options
-    *    Named options:
-    *    `whatToShow`: A bitmask of node types that should be included.  See
-    *       https://developer.mozilla.org/en-US/docs/Web/API/NodeFilter.
-    */
-   nextSibling: method(function(node, options={}) {
-     let walker = documentWalker(node.rawNode, options.whatToShow || Ci.nsIDOMNodeFilter.SHOW_ALL);
--    return this._ref(walker.nextSibling());
-+    let sibling = walker.nextSibling();
-+    return sibling ? this._ref(sibling) : null;
-   }, traversalMethod),
- 
-   /**
-    * Get the previous sibling of a given node.  Getting nodes one at a time
-    * might be inefficient, be careful.
-    *
-    * @param object options
-    *    Named options:
-    *    `whatToShow`: A bitmask of node types that should be included.  See
-    *       https://developer.mozilla.org/en-US/docs/Web/API/NodeFilter.
-    */
-   previousSibling: method(function(node, options={}) {
-     let walker = documentWalker(node.rawNode, options.whatToShow || Ci.nsIDOMNodeFilter.SHOW_ALL);
--    return this._ref(walker.previousSibling());
-+    let sibling = walker.previousSibling();
-+    return sibling ? this._ref(sibling) : null;
-   }, traversalMethod),
- 
-   /**
-    * Helper function for the `children` method: Read forward in the sibling
-    * list into an array with `count` items, including the current node.
-    */
-   _readForward: function(walker, count)
-   {
-diff --git a/toolkit/devtools/server/tests/mochitest/test_inspector-traversal.html b/toolkit/devtools/server/tests/mochitest/test_inspector-traversal.html
---- a/toolkit/devtools/server/tests/mochitest/test_inspector-traversal.html
-+++ b/toolkit/devtools/server/tests/mochitest/test_inspector-traversal.html
-@@ -180,17 +180,42 @@ addTest(function testSiblings() {
-     return gWalker.siblings(a, { maxNodes: 5, center: a }).then(nodeArrayChecker(true, false, "abcde"));
-   }).then(() => {
-     return gWalker.siblings(gWalker.rootNode).then(response => {
-       ok(response.hasFirst && response.hasLast, "Has first and last.");
-       is(response.nodes.length, 1, "Has only the document element.");
-       ok(response.nodes[0] === gWalker.rootNode, "Document element is its own sibling.");
-     });
-   }).then(runNextTest));
--})
-+});
-+
-+addTest(function testNextSibling() {
-+  promiseDone(gWalker.querySelector(gWalker.rootNode, "#y").then(y => {
-+    is(y.id, "y", "Got the right node.");
-+    return gWalker.nextSibling(y);
-+  }).then(z => {
-+    is(z.id, "z", "nextSibling got the next node.");
-+    return gWalker.nextSibling(z);
-+  }).then(nothing => {
-+    is(nothing, null, "nextSibling on the last node returned null.");
-+  }).then(runNextTest));
-+});
-+
-+addTest(function testPreviousSibling() {
-+  promiseDone(gWalker.querySelector(gWalker.rootNode, "#b").then(b => {
-+    is(b.id, "b", "Got the right node.");
-+    return gWalker.previousSibling(b);
-+  }).then(a => {
-+    is(a.id, "a", "nextSibling got the next node.");
-+    return gWalker.previousSibling(a);
-+  }).then(nothing => {
-+    is(nothing, null, "previousSibling on the first node returned null.");
-+  }).then(runNextTest));
-+});
-+
- 
- addTest(function testFrameTraversal() {
-   promiseDone(gWalker.querySelector(gWalker.rootNode, "#childFrame").then(childFrame => {
-     return gWalker.children(childFrame);
-   }).then(children => {
-     let nodes = children.nodes;
-     ok(nodes.length, 1, "There should be only one child of the iframe");
-     is(nodes[0].nodeType, Node.DOCUMENT_NODE, "iframe child should be a document node");
--- a/inspector-delete-node.diff
+++ b/inspector-delete-node.diff
@@ -1,14 +1,20 @@
 # HG changeset patch
-# Parent b464872dc4b6325650220c08eba5de362dbcd290
+# User Dave Camp <dcamp@mozilla.com>
+# Date 1371477173 25200
+#      Mon Jun 17 06:52:53 2013 -0700
+# Node ID ac9d08208d5564b85f52667d768da9ea5d8b8892
+# Parent  8174f98e2cdcb3ff13e8a9d7228113e0d6c48076
+imported patch inspector-delete-node.diff
+
 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
-@@ -1387,16 +1387,48 @@ var WalkerActor = protocol.ActorClass({
+@@ -1376,16 +1376,48 @@ var WalkerActor = protocol.ActorClass({
      request: {
        node: Arg(0, "domnode")
      },
      response: {
        value: RetVal("longstring")
      }
    }),
  
deleted file mode 100644
--- a/inspector-front-import-rawnode-parents.diff
+++ /dev/null
@@ -1,47 +0,0 @@
-# HG changeset patch
-# Parent 7654bacc8ceb2ef47c8a466a501ec887ca56c85e
-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
-@@ -1742,25 +1742,37 @@ var WalkerFront = exports.WalkerFront = 
- 
-   // XXX hack during transition to remote inspector: get a proper NodeFront
-   // for a given local node.  Only works locally.
-   frontForRawNode: function(rawNode){
-     if (!this.isLocal()) {
-       console.warn("Tried to use frontForRawNode on a remote connection.");
-       return null;
-     }
--    let actor = this.conn._transport._serverConnection.getActor(this.actorID);
--    if (!actor) {
-+    let walkerActor = this.conn._transport._serverConnection.getActor(this.actorID);
-+    if (!walkerActor) {
-       throw Error("Could not find client side for actor " + this.actorID);
-     }
--    let nodeActor = actor._ref(rawNode);
-+    let nodeActor = walkerActor._ref(rawNode);
- 
-     // Pass the node through a read/write pair to create the client side actor.
-     let nodeType = types.getType("domnode");
--    return nodeType.read(nodeType.write(nodeActor, actor), this);
-+    let returnNode = nodeType.read(nodeType.write(nodeActor, walkerActor), this);
-+    let top = returnNode;
-+    let extras = walkerActor.parents(nodeActor);
-+    for (let extraActor of extras) {
-+      top = nodeType.read(nodeType.write(extraActor, walkerActor), this);
-+    }
-+
-+    if (top !== this.rootNode) {
-+      // Imported an already-orphaned node.
-+      this._orphaned.add(top);
-+      walkerActor._orphaned.add(this.conn._transport._serverConnection.getActor(top.actorID));
-+    }
-+    return returnNode;
-   }
- });
- 
- /**
-  * Server side of the inspector actor, which is used to create
-  * inspector-related actors, including the walker.
-  */
- var InspectorActor = protocol.ActorClass({
deleted file mode 100644
--- a/inspector-front-islocal.diff
+++ /dev/null
@@ -1,37 +0,0 @@
-# HG changeset patch
-# User Dave Camp <dcamp@mozilla.com>
-# Date 1370924323 25200
-#      Mon Jun 10 21:18:43 2013 -0700
-# Node ID 4520f54fb87f1f2f350a73011973680fa8c102ef
-# Parent 56096f636c37feda0eb4b4e97ca9b53a1bf9c5d9
-imported patch inspector-front-islocal.diff
-
-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
-@@ -1728,20 +1728,24 @@ var WalkerFront = exports.WalkerFront = 
-    * Handle the `new-mutations` notification by fetching the
-    * available mutation records.
-    */
-   onMutations: protocol.preEvent("new-mutations", function() {
-     // Fetch and process the mutations.
-     this.getMutations({cleanup: this.autoCleanup}).then(null, console.error);
-   }),
- 
-+  isLocal: function() {
-+    return !!this.conn._transport._serverConnection;
-+  },
-+
-   // XXX hack during transition to remote inspector: get a proper NodeFront
-   // for a given local node.  Only works locally.
-   frontForRawNode: function(rawNode){
--    if (!this.conn._transport._serverConnection) {
-+    if (!this.isLocal()) {
-       throw Error("Tried to use frontForRawNode on a remote connection.");
-     }
-     let actor = this.conn._transport._serverConnection.getActor(this.actorID);
-     if (!actor) {
-       throw Error("Could not find client side for actor " + this.actorID);
-     }
-     let nodeActor = actor._ref(rawNode);
- 
--- a/inspector-panel-default-node.diff
+++ b/inspector-panel-default-node.diff
@@ -1,10 +1,16 @@
 # HG changeset patch
-# Parent 5a3c75718c6b1fb4ef114d5fac982b03889af6d1
+# User Dave Camp <dcamp@mozilla.com>
+# Date 1371477174 25200
+#      Mon Jun 17 06:52:54 2013 -0700
+# Node ID 373fe12a4425767c349a811250ff5865ac49aaf8
+# Parent  70c5102898494af1ae84395936f013ba70671c0c
+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;
    },
  
--- a/inspector-remote-breadcrumbs.diff
+++ b/inspector-remote-breadcrumbs.diff
@@ -1,14 +1,14 @@
 # HG changeset patch
 # User Dave Camp <dcamp@mozilla.com>
 # Date 1370640819 25200
 #      Fri Jun 07 14:33:39 2013 -0700
-# Node ID 8b49065448a6038dad3171180ecb0ab2996e9a99
-# Parent 68d0be9dd4c70b190d96d293c841ec19880ae77e
+# Node ID 62edd1ed7d645fb006c49b7a204263a839701287
+# Parent  6f2e3b39842bf54cac95d049e5f2d11ea357dac8
 [mq]: inspector-remote-breadcrumbs.diff
 
 diff --git a/browser/devtools/inspector/breadcrumbs.js b/browser/devtools/inspector/breadcrumbs.js
 --- a/browser/devtools/inspector/breadcrumbs.js
 +++ b/browser/devtools/inspector/breadcrumbs.js
 @@ -7,29 +7,44 @@
  const {Cc, Cu, Ci} = require("chrome");
  
--- a/inspector-remote-delete-node.diff
+++ b/inspector-remote-delete-node.diff
@@ -1,10 +1,15 @@
 # HG changeset patch
-# Parent 5e9c5d11bc049b4c57a22918e259765d22bb5a6f
+# User Dave Camp <dcamp@mozilla.com>
+# Date 1371477175 25200
+#      Mon Jun 17 06:52:55 2013 -0700
+# Node ID 12e30c6f2d124d7ca5b5184a46dc7d6b787fd266
+# Parent  373fe12a4425767c349a811250ff5865ac49aaf8
+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
 @@ -681,18 +681,17 @@ InspectorPanel.prototype = {
      }
  
      // If the markup panel is active, use the markup panel to delete
--- a/inspector-remote-pseudoclass.diff
+++ b/inspector-remote-pseudoclass.diff
@@ -1,14 +1,14 @@
 # HG changeset patch
 # User Dave Camp <dcamp@mozilla.com>
 # Date 1370924326 25200
 #      Mon Jun 10 21:18:46 2013 -0700
-# Node ID 5724d3079ccef162c0e57fa4389c2f9ec711721d
-# Parent e3b507774acb53a8db95de4fcfb047275d04e69a
+# Node ID 719d686511efb88a27174ceee884aae98a14ac4a
+# Parent  62edd1ed7d645fb006c49b7a204263a839701287
 imported patch inspector-remote-pseudoclass.diff
 * * *
 imported patch pcl-fix-2.diff
 
 diff --git a/browser/devtools/inspector/breadcrumbs.js b/browser/devtools/inspector/breadcrumbs.js
 --- a/browser/devtools/inspector/breadcrumbs.js
 +++ b/browser/devtools/inspector/breadcrumbs.js
 @@ -138,23 +138,18 @@ HTMLBreadcrumbs.prototype = {
--- a/inspector-remote-target.diff
+++ b/inspector-remote-target.diff
@@ -1,14 +1,14 @@
 # HG changeset patch
 # User Dave Camp <dcamp@mozilla.com>
 # Date 1370640752 25200
 #      Fri Jun 07 14:32:32 2013 -0700
-# Node ID ee78b8f0ffa56f2fb92f250e49ad3f53492d60a3
-# Parent 85ead710c5590773b93e9839c8f296aa61fdb96c
+# Node ID 6f2e3b39842bf54cac95d049e5f2d11ea357dac8
+# Parent  ac9d08208d5564b85f52667d768da9ea5d8b8892
 [mq]: inspector-remote-target.diff
 
 diff --git a/browser/app/profile/firefox.js b/browser/app/profile/firefox.js
 --- a/browser/app/profile/firefox.js
 +++ b/browser/app/profile/firefox.js
 @@ -1054,16 +1054,17 @@ pref("devtools.toolbox.host", "bottom");
  pref("devtools.toolbox.selectedTool", "webconsole");
  pref("devtools.toolbox.toolbarSpec", '["paintflashing toggle","tilt toggle","scratchpad","resize toggle"]');
--- a/remote-edit-attributes.diff
+++ b/remote-edit-attributes.diff
@@ -1,10 +1,15 @@
 # HG changeset patch
-# Parent ff6e6c117e439d75d739c09c447c080b8d36273c
+# User Dave Camp <dcamp@mozilla.com>
+# Date 1371477175 25200
+#      Mon Jun 17 06:52:55 2013 -0700
+# Node ID a45d2d4ce81131a7ba129dd1fd444c719e9513a6
+# Parent  148959580da71de87f64db00b877f31381034562
+imported patch remote-edit-attributes.diff
 
 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
 @@ -1104,47 +1104,53 @@ function ElementEditor(aContainer, aNode
      this.template("elementContentSummary", this);
    }
  
@@ -88,17 +93,17 @@ diff --git a/browser/devtools/markupview
        let attr = this._createAttribute(attrs[i]);
        if (!attr.inplaceEditor) {
          attr.style.removeProperty("display");
        }
      }
    },
  
 +  _startModifyingAttributes: function() {
-+    return this.markup.walker.startModifyingAttributes(this.node);
++    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");
--- a/remote-edit-value.diff
+++ b/remote-edit-value.diff
@@ -1,10 +1,16 @@
 # HG changeset patch
-# Parent f2aafa2137962fc3319d2b6ddeb837a1c73c0af6
+# User Dave Camp <dcamp@mozilla.com>
+# Date 1371477175 25200
+#      Mon Jun 17 06:52:55 2013 -0700
+# Node ID 985632d429cbf80d9c70a376a6decda6f24e79c8
+# Parent  a45d2d4ce81131a7ba129dd1fd444c719e9513a6
+imported patch remote-edit-value.diff
+
 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
 @@ -1006,38 +1006,45 @@ function DoctypeEditor(aContainer, aNode
   */
  function TextEditor(aContainer, aNode, aTemplate)
  {
    this.node = aNode;
--- a/remote-markup.diff
+++ b/remote-markup.diff
@@ -1,14 +1,14 @@
 # HG changeset patch
 # User Dave Camp <dcamp@mozilla.com>
 # Date 1370924326 25200
 #      Mon Jun 10 21:18:46 2013 -0700
-# Node ID 327e1698efad6b10f6c9099aa65c87e0e4819532
-# Parent 500dd317824d2f0dbeb0b5544d23384fca41cd33
+# Node ID 2f16f9dd59e09f9349072d84c7f88fc8a01cebed
+# Parent  49fdfa8550af2362f7233cf7e321f5ad0bd4a851
 imported patch remote-markup.diff
 * * *
 imported patch markup-fixes.diff
 * * *
 [mq]: fucking-around.diff
 
 diff --git a/browser/devtools/inspector/inspector-panel.js b/browser/devtools/inspector/inspector-panel.js
 --- a/browser/devtools/inspector/inspector-panel.js
--- a/search-box-remote.diff
+++ b/search-box-remote.diff
@@ -1,24 +1,24 @@
 # HG changeset patch
 # User Dave Camp <dcamp@mozilla.com>
 # Date 1370924326 25200
 #      Mon Jun 10 21:18:46 2013 -0700
-# Node ID f307f02cb78c9958fd0ce05d4a9ef3dd60e9609b
-# Parent  5724d3079ccef162c0e57fa4389c2f9ec711721d
+# Node ID 49fdfa8550af2362f7233cf7e321f5ad0bd4a851
+# Parent  719d686511efb88a27174ceee884aae98a14ac4a
 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
-@@ -210,28 +210,29 @@ InspectorPanel.prototype = {
+@@ -209,28 +209,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 {
@@ -460,17 +460,17 @@ diff --git a/browser/devtools/inspector/
           ? 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
-@@ -1983,17 +1983,17 @@ function nodeDocument(node) {
+@@ -2030,17 +2030,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);
--- a/series
+++ b/series
@@ -1,28 +1,15 @@
-inspector-actor-queue-mutations.diff
-inspector-actor-retain.diff
-inspector-actor-pseudoclass.diff
-inspector-actor-sibling-traversal.diff
-inspector-front-islocal.diff
-inspector-actor-forgiving-rawnode.diff
-inspector-front-import-rawnode-parents.diff
-inspector-actor-readystate.diff
-inspector-actor-docelement.diff
-inspector-actor-innerhtml.diff
-inspector-actor-modify-attributes.diff
-inspector-actor-modify-nodevalue.diff
-inspector-actor-markup-fixes.diff
+inspector-delete-node.diff
 inspector-remote-target.diff
 inspector-remote-breadcrumbs.diff
 inspector-remote-pseudoclass.diff
-inspector-delete-node.diff
 search-box-remote.diff
 remote-markup.diff
 warning-fixes.diff
 inspector-panel-default-node.diff
-console-padend.diff
 inspector-remote-delete-node.diff
 copy-html.diff
 remote-edit-attributes.diff
 remote-edit-value.diff
+style-actor.diff
 inspector-retain-root.diff #+obsolete
 protocol-clientserver-marshallers.diff #+experimental
new file mode 100644
--- /dev/null
+++ b/style-actor.diff
@@ -0,0 +1,2756 @@
+# HG changeset patch
+# User Dave Camp <dcamp@mozilla.com>
+# Date 1371506345 25200
+#      Mon Jun 17 14:59:05 2013 -0700
+# Node ID b51219d9850aa1b1b68b1a2c0bd64ff2854f7124
+# Parent 985632d429cbf80d9c70a376a6decda6f24e79c8
+[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
+ exports.InspectorPanel = InspectorPanel;
+ 
+ InspectorPanel.prototype = {
+   /**
+    * open is effectively an asynchronous constructor
+    */
+   open: function InspectorPanel_open() {
+     return this.target.makeRemote().then(() => {
+-      return this.target.inspector.getWalker();
+-    }).then(walker => {
+-      if (this._destroyPromise) {
+-        walker.release().then(null, console.error);
+-      }
+-      this.walker = walker;
++      return this._getWalker();
++    }).then(() => {
+       return this._getDefaultNodeForSelection();
+     }).then(defaultSelection => {
+       return this._deferredOpen(defaultSelection);
+     }).then(null, console.error);
+   },
+ 
+   _deferredOpen: function(defaultSelection) {
+     let deferred = Promise.defer();
+@@ -275,37 +271,53 @@ InspectorPanel.prototype = {
+ 
+     let ruleViewTab = this.sidebar.getTab("ruleview");
+     ruleViewTab.addEventListener("mouseover", this.toggleHighlighter, false);
+     ruleViewTab.addEventListener("mouseout", this.toggleHighlighter, false);
+ 
+     this.sidebar.show();
+   },
+ 
++  _getWalker: function() {
++    return this.target.inspector.getWalker().then(walker => {
++      if (this._destroyPromise) {
++        walker.release().then(null, console.error);
++        promise.reject("inspector destroyed");
++        return;
++      }
++      return walker.getNodeStyle().then(nodeStyle => {
++        if (this._destroyPromise) {
++          walker.release().then(null, console.error);
++          promise.reject("inspector destroyed");
++          return;
++        }
++        this.walker = walker;
++        this.nodeStyle = nodeStyle;
++        if (this.selection) {
++          this.selection.setWalker(walker);
++        }
++      });
++    });
++  },
++
+   /**
+    * 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.nodeStyle = 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);
+-        return;
+-      }
+-
+-      this.walker = walker;
+-      this.selection.setWalker(walker);
++    this._getWalker().then(() => {
+       this._getDefaultNodeForSelection().then(defaultNode => {
+         if (this._destroyPromise) {
+           return;
+         }
+         this.selection.setNodeFront(defaultNode, "navigateaway");
+ 
+         this._initMarkup();
+         this.once("markuploaded", () => {
+@@ -320,24 +332,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;
++
+     let selfUpdate = this.updating("inspector-panel");
+     Services.tm.mainThread.dispatch(() => {
+-      try {
+-        selfUpdate(selection);
+-      } catch(ex) {
+-        console.error(ex);
+-      }
+-    }, Ci.nsIThread.DISPATCH_NORMAL);
++      selfUpdate(selection);
++    }, 0);
+   },
+ 
+   /**
+    * 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.
+    */
+@@ -411,16 +420,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;
++      delete this.nodeStyle;
+     } else {
+       this._destroyPromise = Promise.resolve(null);
+     }
+ 
+     this.cancelUpdate();
+     this.cancelLayoutChange();
+ 
+     if (this.browser) {
+diff --git a/browser/devtools/inspector/test/browser_inspector_changes.js b/browser/devtools/inspector/test/browser_inspector_changes.js
+--- a/browser/devtools/inspector/test/browser_inspector_changes.js
++++ b/browser/devtools/inspector/test/browser_inspector_changes.js
+@@ -35,64 +35,58 @@ function test() {
+       info("Computed View ready");
+       inspector.sidebar.select("computedview");
+ 
+       testDiv = doc.getElementById("testdiv");
+ 
+       testDiv.style.fontSize = "10px";
+ 
+       // Start up the style inspector panel...
+-      Services.obs.addObserver(stylePanelTests, "StyleInspector-populated", false);
++      inspector.once("computed-view-refreshed", stylePanelTests);
+ 
+       inspector.selection.setNode(testDiv);
+     });
+   }
+ 
+   function stylePanelTests()
+   {
+-    Services.obs.removeObserver(stylePanelTests, "StyleInspector-populated");
+-
+     let computedview = inspector.sidebar.getWindowForTab("computedview").computedview;
+     ok(computedview, "Style Panel has a cssHtmlTree");
+ 
+     let propView = getInspectorProp("font-size");
+     is(propView.value, "10px", "Style inspector should be showing the correct font size.");
+ 
+-    Services.obs.addObserver(stylePanelAfterChange, "StyleInspector-populated", false);
++    inspector.once("computed-view-refreshed", stylePanelAfterChange);
+ 
+     testDiv.style.fontSize = "15px";
+     inspector.emit("layout-change");
+   }
+ 
+   function stylePanelAfterChange()
+   {
+-    Services.obs.removeObserver(stylePanelAfterChange, "StyleInspector-populated");
+-
+     let propView = getInspectorProp("font-size");
+     is(propView.value, "15px", "Style inspector should be showing the new font size.");
+ 
+     stylePanelNotActive();
+   }
+ 
+   function stylePanelNotActive()
+   {
+     // Tests changes made while the style panel is not active.
+     inspector.sidebar.select("ruleview");
+ 
+     executeSoon(function() {
+-      Services.obs.addObserver(stylePanelAfterSwitch, "StyleInspector-populated", false);
++      inspector.once("computed-view-refreshed", stylePanelAfterSwitch);
+       testDiv.style.fontSize = "20px";
+       inspector.sidebar.select("computedview");
+     });
+   }
+ 
+   function stylePanelAfterSwitch()
+   {
+-    Services.obs.removeObserver(stylePanelAfterSwitch, "StyleInspector-populated");
+-
+     let propView = getInspectorProp("font-size");
+     is(propView.value, "20px", "Style inspector should be showing the newest font size.");
+ 
+     finishTest();
+   }
+ 
+   function finishTest()
+   {
+diff --git a/browser/devtools/inspector/test/browser_inspector_pseudoclass_lock.js b/browser/devtools/inspector/test/browser_inspector_pseudoclass_lock.js
+--- a/browser/devtools/inspector/test/browser_inspector_pseudoclass_lock.js
++++ b/browser/devtools/inspector/test/browser_inspector_pseudoclass_lock.js
+@@ -62,31 +62,35 @@ function selectNode(aInspector)
+ function performTests()
+ {
+   // toggle the class
+   inspector.togglePseudoClass(pseudo);
+ 
+   // Wait for the "pseudoclass" event so we know the
+   // inspector has been told of the pseudoclass lock change.
+   inspector.selection.once("pseudoclass", () => {
++    dump("GOT FIRST PSEUDOCLASS\n");
+     // Give the rule view time to update.
+-    executeSoon(() => {
++    inspector.once("rule-view-refreshed", () => {
++      dump("GOT FIRST RULE VIEW REFRESH\n");
+       testAdded();
+-
+-      // toggle the lock off and wait for the pseudoclass event again.
++      dump("about to toggle pseudo class\n");
++      // Change the pseudo class and give the rule view time to update.
+       inspector.togglePseudoClass(pseudo);
+       inspector.selection.once("pseudoclass", () => {
+-        // Give the rule view time to update.
+-        executeSoon(() => {
++        dump("GOT SECOND PSEUDOCLASS\n");
++        inspector.once("rule-view-refreshed", () => {
++          dump("GOT SECOND RULEVIEW REFRESH\n");
+           testRemoved();
+           testRemovedFromUI();
+ 
+           // toggle it back on
+           inspector.togglePseudoClass(pseudo);
+           inspector.selection.once("pseudoclass", () => {
++            dump("GOT THIRD PSEUDOCLASS\n");
+             testNavigate(() => {
+               // close the inspector
+               finishUp();
+             });
+           });
+         });
+       });
+     });
+diff --git a/browser/devtools/main.js b/browser/devtools/main.js
+--- a/browser/devtools/main.js
++++ b/browser/devtools/main.js
+@@ -91,17 +91,18 @@ Tools.inspector = {
+   ordinal: 2,
+   modifiers: osString == "Darwin" ? "accel,alt" : "accel,shift",
+   icon: "chrome://browser/skin/devtools/tool-inspector.png",
+   url: "chrome://browser/content/devtools/inspector/inspector.xul",
+   label: l10n("inspector.label", inspectorStrings),
+   tooltip: l10n("inspector.tooltip", inspectorStrings),
+ 
+   isTargetSupported: function(target) {
+-    return !target.isRemote;
++//    return !target.isRemote;
++return true;
+   },
+ 
+   build: function(iframeWindow, toolbox) {
+     let panel = new InspectorPanel(iframeWindow, toolbox);
+     return panel.open();
+   }
+ };
+ 
+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
+@@ -279,21 +279,25 @@ MarkupView.prototype = {
+   navigate: function MT__navigate(aContainer, aIgnoreFocus)
+   {
+     if (!aContainer) {
+       return;
+     }
+ 
+     let node = aContainer.node;
+     this.markNodeAsSelected(node);
++    let sameNode = (node === this._inspector.selection.nodeFront);
+     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 (sameNode) {
++      // 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.
+diff --git a/browser/devtools/styleinspector/computed-view.js b/browser/devtools/styleinspector/computed-view.js
+--- a/browser/devtools/styleinspector/computed-view.js
++++ b/browser/devtools/styleinspector/computed-view.js
+@@ -3,24 +3,27 @@
+ /* This Source Code Form is subject to the terms of the Mozilla Public
+  * License, v. 2.0. If a copy of the MPL was not distributed with this
+  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+ 
+ const {Cc, Ci, Cu} = require("chrome");
+ 
+ let ToolDefinitions = require("main").Tools;
+ let {CssLogic} = require("devtools/styleinspector/css-logic");
++let promise = require("sdk/core/promise");
++let {EventEmitter} = require("devtools/shared/event-emitter");
+ 
+ Cu.import("resource://gre/modules/Services.jsm");
+ Cu.import("resource://gre/modules/PluralForm.jsm");
+ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+ Cu.import("resource://gre/modules/devtools/Templater.jsm");
+ 
+ Cu.import("resource:///modules/devtools/gDevTools.jsm");
+ 
++
+ const FILTER_CHANGED_TIMEOUT = 300;
+ 
+ const HTML_NS = "http://www.w3.org/1999/xhtml";
+ const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+ 
+ /**
+  * Helper for long-running processes that should yield occasionally to
+  * the mainloop.
+@@ -85,16 +88,17 @@ UpdateProcess.prototype = {
+       this._runBatch();
+       this.schedule();
+     } catch(e) {
+       if (e instanceof StopIteration) {
+         this.onBatch();
+         this.onDone();
+         return;
+       }
++      console.error(e);
+       throw e;
+     }
+   },
+ 
+   _runBatch: function Y_runBatch()
+   {
+     let time = Date.now();
+     while(!this.canceled) {
+@@ -112,22 +116,22 @@ UpdateProcess.prototype = {
+ /**
+  * CssHtmlTree is a panel that manages the display of a table sorted by style.
+  * There should be one instance of CssHtmlTree per style display (of which there
+  * will generally only be one).
+  *
+  * @params {StyleInspector} aStyleInspector The owner of this CssHtmlTree
+  * @constructor
+  */
+-function CssHtmlTree(aStyleInspector)
++function CssHtmlTree(aStyleInspector, aNodeStyle)
+ {
+   this.styleWindow = aStyleInspector.window;
+   this.styleDocument = aStyleInspector.window.document;
+   this.styleInspector = aStyleInspector;
+-  this.cssLogic = aStyleInspector.cssLogic;
++  this.nodeStyle = aNodeStyle;
+   this.propertyViews = [];
+ 
+   let chromeReg = Cc["@mozilla.org/chrome/chrome-registry;1"].
+     getService(Ci.nsIXULChromeRegistry);
+   this.getRTLAttr = chromeReg.isLocaleRTL("global") ? "rtl" : "ltr";
+ 
+   // Create bound methods.
+   this.siFocusWindow = this.focusWindow.bind(this);
+@@ -139,16 +143,19 @@ function CssHtmlTree(aStyleInspector)
+   // Nodes used in templating
+   this.root = this.styleDocument.getElementById("root");
+   this.templateRoot = this.styleDocument.getElementById("templateRoot");
+   this.propertyContainer = this.styleDocument.getElementById("propertyContainer");
+ 
+   // No results text.
+   this.noResults = this.styleDocument.getElementById("noResults");
+ 
++
++  CssHtmlTree.processTemplate(this.templateRoot, this.root, this);
++
+   // The element that we're inspecting, and the document that it comes from.
+   this.viewedElement = null;
+   this.createStyleViews();
+ }
+ 
+ /**
+  * Memoized lookup of a l10n string from a string bundle.
+  * @param {string} aName The key to lookup.
+@@ -202,18 +209,16 @@ XPCOMUtils.defineLazyGetter(this, "clipb
+   return Cc["@mozilla.org/widget/clipboardhelper;1"].
+     getService(Ci.nsIClipboardHelper);
+ });
+ 
+ CssHtmlTree.prototype = {
+   // Cache the list of properties that match the selected element.
+   _matchedProperties: null,
+ 
+-  htmlComplete: false,
+-
+   // Used for cancelling timeouts in the style filter.
+   _filterChangedTimeout: null,
+ 
+   // The search filter
+   searchField: null,
+ 
+   // Reference to the "Include browser styles" checkbox.
+   includeBrowserStylesCheckbox: null,
+@@ -232,114 +237,128 @@ CssHtmlTree.prototype = {
+     return this.includeBrowserStylesCheckbox.checked;
+   },
+ 
+   /**
+    * Update the highlighted element. The CssHtmlTree panel will show the style
+    * information for the given element.
+    * @param {nsIDOMElement} aElement The highlighted node to get styles for.
+    */
+-  highlight: function CssHtmlTree_highlight(aElement)
+-  {
+-    this.viewedElement = aElement;
+-    this._matchedProperties = null;
+-
++  highlight: function(aElement) {
++    dump("Highlighting\n");
++    console.trace();
+     if (!aElement) {
+       if (this._refreshProcess) {
+         this._refreshProcess.cancel();
+       }
+-      return;
++      return promise.resolve(undefined)
+     }
+ 
+-    if (this.htmlComplete) {
+-      this.refreshSourceFilter();
+-      this.refreshPanel();
+-    } else {
+-      if (this._refreshProcess) {
+-        this._refreshProcess.cancel();
++    this.viewedElement = aElement;
++
++    this.refreshSourceFilter();
++    return this.refreshPanel();
++  },
++
++  _createPropertyViews: function()
++  {
++    if (this._createViewsPromise) {
++      return this._createViewsPromise;
++    }
++
++    let deferred = promise.defer();
++    this._createViewsPromise = deferred.promise;
++
++    this.refreshSourceFilter();
++    this.numVisibleProperties = 0;
++    let fragment = this.styleDocument.createDocumentFragment();
++
++    this._createViewsProcess = new UpdateProcess(this.styleWindow, CssHtmlTree.propertyNames, {
++      onItem: (aPropertyName) => {
++        // Per-item callback.
++        let propView = new PropertyView(this, aPropertyName);
++        fragment.appendChild(propView.buildMain());
++        fragment.appendChild(propView.buildSelectorContainer());
++
++        if (propView.visible) {
++          this.numVisibleProperties++;
++        }
++        propView.refreshMatchedSelectors();
++        this.propertyViews.push(propView);
++      },
++      onCancel: () => {
++        deferred.reject("_createPropertyViews cancelled");
++      },
++      onDone: () => {
++        // Completed callback.
++        this.propertyContainer.appendChild(fragment);
++        this.noResults.hidden = this.numVisibleProperties > 0;
++        deferred.resolve(undefined);
+       }
++    });
+ 
+-      CssHtmlTree.processTemplate(this.templateRoot, this.root, this);
+-
+-      // Refresh source filter ... this must be done after templateRoot has been
+-      // processed.
+-      this.refreshSourceFilter();
+-      this.numVisibleProperties = 0;
+-      let fragment = this.styleDocument.createDocumentFragment();
+-      this._refreshProcess = new UpdateProcess(this.styleWindow, CssHtmlTree.propertyNames, {
+-        onItem: function(aPropertyName) {
+-          // Per-item callback.
+-          let propView = new PropertyView(this, aPropertyName);
+-          fragment.appendChild(propView.buildMain());
+-          fragment.appendChild(propView.buildSelectorContainer());
+-
+-          if (propView.visible) {
+-            this.numVisibleProperties++;
+-          }
+-          propView.refreshMatchedSelectors();
+-          this.propertyViews.push(propView);
+-        }.bind(this),
+-        onDone: function() {
+-          // Completed callback.
+-          this.htmlComplete = true;
+-          this.propertyContainer.appendChild(fragment);
+-          this.noResults.hidden = this.numVisibleProperties > 0;
+-          this._refreshProcess = null;
+-
+-          // If a refresh was scheduled during the building, complete it.
+-          if (this._needsRefresh) {
+-            delete this._needsRefresh;
+-            this.refreshPanel();
+-          } else {
+-            Services.obs.notifyObservers(null, "StyleInspector-populated", null);
+-          }
+-        }.bind(this)});
+-
+-      this._refreshProcess.schedule();
+-    }
++    this._createViewsProcess.schedule();
++    return deferred.promise;
+   },
+ 
+   /**
+    * Refresh the panel content.
+    */
+   refreshPanel: function CssHtmlTree_refreshPanel()
+   {
++    dump("Refreshing panel...\n");
+     // If we're still in the process of creating the initial layout,
+     // leave it alone.
+-    if (!this.htmlComplete) {
++    return promise.all([
++      this._createPropertyViews(),
++      this.nodeStyle.getComputed(this.viewedElement, {
++        filter: this._sourceFilter,
++        onlyMatched: !this.includeBrowserStyles,
++        markMatched: true
++      })
++    ]).then(([createViews, computed]) => {
++      this._matchedProperties = new Set;
++      for (let name in computed) {
++        if (computed[name].matched) {
++          this._matchedProperties.add(name);
++        }
++      }
++      this._computed = computed;
++
+       if (this._refreshProcess) {
+-        this._needsRefresh = true;
++        this._refreshProcess.cancel();
+       }
+-      return;
+-    }
+ 
+-    if (this._refreshProcess) {
+-      this._refreshProcess.cancel();
+-    }
++      this.noResults.hidden = true;
+ 
+-    this.noResults.hidden = true;
++      // Reset visible property count
++      this.numVisibleProperties = 0;
+ 
+-    // Reset visible property count
+-    this.numVisibleProperties = 0;
++      // Reset zebra striping.
++      this._darkStripe = true;
+ 
+-    // Reset zebra striping.
+-    this._darkStripe = true;
++      let display = this.propertyContainer.style.display;
+ 
+-    let display = this.propertyContainer.style.display;
+-    this._refreshProcess = new UpdateProcess(this.styleWindow, this.propertyViews, {
+-      onItem: function(aPropView) {
+-        aPropView.refresh();
+-      }.bind(this),
+-      onDone: function() {
+-        this._refreshProcess = null;
+-        this.noResults.hidden = this.numVisibleProperties > 0;
+-        Services.obs.notifyObservers(null, "StyleInspector-populated", null);
+-      }.bind(this)
+-    });
+-    this._refreshProcess.schedule();
++      let deferred = promise.defer();
++      this._refreshProcess = new UpdateProcess(this.styleWindow, this.propertyViews, {
++        onItem: (aPropView) => {
++          aPropView.refresh();
++        },
++        onCancel: () => {
++          deferred.reject("refresh cancelled");
++        },
++        onDone: () => {
++          this._refreshProcess = null;
++          this.noResults.hidden = this.numVisibleProperties > 0;
++          this.styleInspector.inspector.emit("computed-view-refreshed");
++          deferred.resolve(undefined);
++        }
++      });
++      this._refreshProcess.schedule();
++    }).then(null, console.error);
+   },
+ 
+   /**
+    * Called when the user enters a search term.
+    *
+    * @param {Event} aEvent the DOM Event object.
+    */
+   filterChanged: function CssHtmlTree_filterChanged(aEvent)
+@@ -372,17 +391,17 @@ CssHtmlTree.prototype = {
+    * When includeBrowserStyles.checked is false we only display properties that
+    * have matched selectors and have been included by the document or one of the
+    * document's stylesheets. If .checked is false we display all properties
+    * including those that come from UA stylesheets.
+    */
+   refreshSourceFilter: function CssHtmlTree_setSourceFilter()
+   {
+     this._matchedProperties = null;
+-    this.cssLogic.sourceFilter = this.includeBrowserStyles ?
++    this._sourceFilter = this.includeBrowserStyles ?
+                                  CssLogic.FILTER.UA :
+                                  CssLogic.FILTER.ALL;
+   },
+ 
+   /**
+    * The CSS as displayed by the UI.
+    */
+   createStyleViews: function CssHtmlTree_createStyleViews()
+@@ -404,31 +423,28 @@ CssHtmlTree.prototype = {
+       } else {
+         CssHtmlTree.propertyNames.push(prop);
+       }
+     }
+ 
+     CssHtmlTree.propertyNames.sort();
+     CssHtmlTree.propertyNames.push.apply(CssHtmlTree.propertyNames,
+       mozProps.sort());
++
++    this._createPropertyViews();
+   },
+ 
+   /**
+-   * Get a list of properties that have matched selectors.
++   * Get a set of properties that have matched selectors.
+    *
+-   * @return {object} the object maps property names (keys) to booleans (values)
+-   * that tell if the given property has matched selectors or not.
++   * @return {Set} If a property name is in the set, it has matching selectors.
+    */
+   get matchedProperties()
+   {
+-    if (!this._matchedProperties) {
+-      this._matchedProperties =
+-        this.cssLogic.hasMatchedSelectors(CssHtmlTree.propertyNames);
+-    }
+-    return this._matchedProperties;
++    return this._matchedProperties || new Set;
+   },
+ 
+   /**
+    * Focus the window on mousedown.
+    *
+    * @param aEvent The event object
+    */
+   focusWindow: function si_focusWindow(aEvent)
+@@ -468,16 +484,19 @@ CssHtmlTree.prototype = {
+     delete this.viewedElement;
+ 
+     // Remove event listeners
+     this.includeBrowserStylesCheckbox.removeEventListener("command",
+       this.includeBrowserStylesChanged);
+     this.searchField.removeEventListener("command", this.filterChanged);
+ 
+     // Cancel tree construction
++    if (this._createViewsProcess) {
++      this._createViewsProcess.cancel();
++    }
+     if (this._refreshProcess) {
+       this._refreshProcess.cancel();
+     }
+ 
+     // Remove context menu
+     let outerDoc = this.styleInspector.outerIFrame.ownerDocument;
+     let menu = outerDoc.querySelector("#computed-view-context-menu");
+     if (menu) {
+@@ -512,21 +531,28 @@ CssHtmlTree.prototype = {
+ 
+     // The document in which we display the results (csshtmltree.xul).
+     delete this.styleDocument;
+ 
+     // The element that we're inspecting, and the document that it comes from.
+     delete this.propertyViews;
+     delete this.styleWindow;
+     delete this.styleDocument;
+-    delete this.cssLogic;
+     delete this.styleInspector;
+   },
+ };
+ 
++function PropertyInfo(aTree, aName) {
++  this.tree = aTree;
++  this.name = aName;
++}
++PropertyInfo.prototype = {
++  get value() this.tree._computed ? this.tree._computed[this.name].value : ""
++}
++
+ /**
+  * A container to give easy access to property data from the template engine.
+  *
+  * @constructor
+  * @param {CssHtmlTree} aTree the CssHtmlTree instance we are working with.
+  * @param {string} aName the CSS property name for which this PropertyView
+  * instance will render the rules.
+  */
+@@ -534,16 +560,17 @@ function PropertyView(aTree, aName)
+ {
+   this.tree = aTree;
+   this.name = aName;
+   this.getRTLAttr = aTree.getRTLAttr;
+ 
+   this.link = "https://developer.mozilla.org/CSS/" + aName;
+ 
+   this.templateMatchedSelectors = aTree.styleDocument.getElementById("templateMatchedSelectors");
++  this._propertyInfo = new PropertyInfo(aTree, aName);
+ }
+ 
+ PropertyView.prototype = {
+   // The parent element which contains the open attribute
+   element: null,
+ 
+   // Property header node
+   propertyHeader: null,
+@@ -580,25 +607,25 @@ PropertyView.prototype = {
+     return this.propertyInfo.value;
+   },
+ 
+   /**
+    * An easy way to access the CssPropertyInfo behind this PropertyView.
+    */
+   get propertyInfo()
+   {
+-    return this.tree.cssLogic.getPropertyInfo(this.name);
++    return this._propertyInfo;
+   },
+ 
+   /**
+    * Does the property have any matched selectors?
+    */
+   get hasMatchedSelectors()
+   {
+-    return this.name in this.tree.matchedProperties;
++    return this.tree.matchedProperties.has(this.name);
+   },
+ 
+   /**
+    * Should this property be visible?
+    */
+   get visible()
+   {
+     if (!this.tree.includeBrowserStyles && !this.hasMatchedSelectors) {
+@@ -733,34 +760,41 @@ PropertyView.prototype = {
+ 
+     if (hasMatchedSelectors) {
+       this.matchedExpander.classList.add("expandable");
+     } else {
+       this.matchedExpander.classList.remove("expandable");
+     }
+ 
+     if (this.matchedExpanded && hasMatchedSelectors) {
+-      CssHtmlTree.processTemplate(this.templateMatchedSelectors,
+-        this.matchedSelectorsContainer, this);
+-      this.matchedExpander.setAttribute("open", "");
++      this.tree.nodeStyle.getMatchedSelectors(this.tree.viewedElement, this.name).then(matched => {
++        if (!this.matchedExpanded) {
++          return;
++        }
++
++        this._matchedSelectorResponse = matched;
++        CssHtmlTree.processTemplate(this.templateMatchedSelectors,
++          this.matchedSelectorsContainer, this);
++        this.matchedExpander.setAttribute("open", "");
++      }).then(null, console.error);
+     } else {
+       this.matchedSelectorsContainer.innerHTML = "";
+       this.matchedExpander.removeAttribute("open");
+     }
+   },
+ 
+   /**
+    * Provide access to the matched SelectorViews that we are currently
+    * displaying.
+    */
+   get matchedSelectorViews()
+   {
+     if (!this._matchedSelectorViews) {
+       this._matchedSelectorViews = [];
+-      this.propertyInfo.matchedSelectors.forEach(
++      this._matchedSelectorResponse.forEach(
+         function matchedSelectorViews_convert(aSelectorInfo) {
+           this._matchedSelectorViews.push(new SelectorView(this.tree, aSelectorInfo));
+         }, this);
+     }
+ 
+     return this._matchedSelectorViews;
+   },
+ 
+@@ -797,16 +831,22 @@ PropertyView.prototype = {
+  * @param CssHtmlTree aTree, the owning CssHtmlTree
+  * @param aSelectorInfo
+  */
+ function SelectorView(aTree, aSelectorInfo)
+ {
+   this.tree = aTree;
+   this.selectorInfo = aSelectorInfo;
+   this._cacheStatusNames();
++
++  let rule = this.selectorInfo.rule;
++  if (rule && rule.parentStyleSheet) {
++    this.sheet = rule.parentStyleSheet;
++  }
++  this.source = CssLogic.shortSource(this.selectorInfo.sheet);
+ }
+ 
+ /**
+  * Decode for cssInfo.rule.status
+  * @see SelectorView.prototype._cacheStatusNames
+  * @see CssLogic.STATUS
+  */
+ SelectorView.STATUS_NAMES = [
+@@ -854,34 +894,29 @@ SelectorView.prototype = {
+   /**
+    * Get class name for selector depending on status
+    */
+   get statusClass()
+   {
+     return SelectorView.CLASS_NAMES[this.selectorInfo.status - 1];
+   },
+ 
+-  /**
+-   * A localized Get localized human readable info
+-   */
+-  text: function SelectorView_text(aElement) {
+-    let result = this.selectorInfo.selector.text;
+-    if (this.selectorInfo.elementStyle) {
+-      let source = this.selectorInfo.sourceElement;
+-      let inspector = this.tree.styleInspector.inspector;
++  get href()
++  {
++      return this.selectorInfo.href;
++  },
+ 
+-      if (inspector.selection.node == source) {
+-        result = "this";
+-      } else {
+-        result = CssLogic.getShortName(source);
+-      }
+-      result += ".style";
+-    }
++  get sourceText()
++  {
++    return this.selectorInfo.sourceText;
++  },
+ 
+-    return result;
++  get value()
++  {
++    return this.selectorInfo.value;
+   },
+ 
+   maybeOpenStyleEditor: function(aEvent)
+   {
+     let keyEvent = Ci.nsIDOMKeyEvent;
+     if (aEvent.keyCode == keyEvent.DOM_VK_RETURN) {
+       this.openStyleEditor();
+     }
+@@ -945,9 +980,9 @@ SelectorView.prototype = {
+         href = this.selectorInfo.sourceElement.ownerDocument.location.href;
+       }
+       viewSourceUtils.viewSource(href, null, contentDoc, line);
+     }
+   },
+ };
+ 
+ exports.CssHtmlTree = CssHtmlTree;
+-exports.PropertyView = PropertyView;
+\ No newline at end of file
++exports.PropertyView = PropertyView;
+diff --git a/browser/devtools/styleinspector/computedview.xhtml b/browser/devtools/styleinspector/computedview.xhtml
+--- a/browser/devtools/styleinspector/computedview.xhtml
++++ b/browser/devtools/styleinspector/computedview.xhtml
+@@ -93,22 +93,22 @@
+       -->
+       <div id="templateMatchedSelectors">
+         <loop foreach="selector in ${matchedSelectorViews}">
+           <p>
+             <span class="rule-link">
+               <a target="_blank" class="link theme-link"
+                   onclick="${selector.openStyleEditor}"
+                   onkeydown="${selector.maybeOpenStyleEditor}"
+-                  title="${selector.selectorInfo.href}"
+-                  tabindex="0">${selector.selectorInfo.source}</a>
++                  title="${selector.href}"
++                  tabindex="0">${selector.source}</a>
+             </span>
+             <span dir="ltr" class="rule-text ${selector.statusClass} theme-fg-color3" title="${selector.statusText}">
+-              ${selector.text(__element)}
+-              <span class="other-property-value theme-fg-color1">${selector.selectorInfo.value}</span>
++              ${selector.sourceText}
++              <span class="other-property-value theme-fg-color1">${selector.value}</span>
+             </span>
+           </p>
+         </loop>
+       </div>
+     </div>
+ 
+   </body>
+ </html>
+diff --git a/browser/devtools/styleinspector/css-logic.js b/browser/devtools/styleinspector/css-logic.js
+--- a/browser/devtools/styleinspector/css-logic.js
++++ b/browser/devtools/styleinspector/css-logic.js
+@@ -138,16 +138,18 @@ CssLogic.prototype = {
+     this._matchedSelectors = null;
+   },
+ 
+   /**
+    * Focus on a new element - remove the style caches.
+    *
+    * @param {nsIDOMElement} aViewedElement the element the user has highlighted
+    * in the Inspector.
++   *
++   * @returns a promise that will be resolved when CssLogic is up to date.
+    */
+   highlight: function CssLogic_highlight(aViewedElement)
+   {
+     if (!aViewedElement) {
+       this.viewedElement = null;
+       this.viewedDocument = null;
+       this._computedStyle = null;
+       this.reset();
+diff --git a/browser/devtools/styleinspector/rule-view.js b/browser/devtools/styleinspector/rule-view.js
+--- a/browser/devtools/styleinspector/rule-view.js
++++ b/browser/devtools/styleinspector/rule-view.js
+@@ -2,19 +2,21 @@
+ /* vim: set ts=2 et sw=2 tw=80: */
+ /* This Source Code Form is subject to the terms of the Mozilla Public
+  * License, v. 2.0. If a copy of the MPL was not distributed with this
+  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+ 
+ "use strict";
+ 
+ const {Cc, Ci, Cu} = require("chrome");
++const promise = require("sdk/core/promise");
+ 
+ let {CssLogic} = require("devtools/styleinspector/css-logic");
+ let {InplaceEditor, editableField, editableItem} = require("devtools/shared/inplace-editor");
++let {ELEMENT_STYLE} = require("devtools/server/actors/styles");
+ 
+ Cu.import("resource://gre/modules/Services.jsm");
+ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+ 
+ const HTML_NS = "http://www.w3.org/1999/xhtml";
+ const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+ 
+ /**
+@@ -29,16 +31,44 @@ const CSS_LINE_RE = /(?:[^;\(]*(?:\([^\)
+ const CSS_PROP_RE = /\s*([^:\s]*)\s*:\s*(.*?)\s*(?:! (important))?;?$/;
+ 
+ // Used to parse an external resource from a property value
+ const CSS_RESOURCE_RE = /url\([\'\"]?(.*?)[\'\"]?\)/;
+ 
+ const IOService = Cc["@mozilla.org/network/io-service;1"]
+                   .getService(Ci.nsIIOService);
+ 
++function promiseWarn(err) {
++  console.error(err);
++  return promise.reject(err);
++}
++
++function createDummyDocument() {
++  const { getDocShell, create: makeFrame } = require("sdk/frame/utils");
++
++  let frame = makeFrame(Services.appShell.hiddenDOMWindow.document, {
++    nodeName: "iframe",
++    namespaceURI: "http://www.w3.org/1999/xhtml",
++    allowJavascript: false,
++    allowPlugins: false,
++    allowAuth: false
++  });
++  let docShell = getDocShell(frame);
++  let eventTarget = docShell.chromeEventHandler;
++  docShell.createAboutBlankContentViewer(Cc["@mozilla.org/nullprincipal;1"].createInstance(Ci.nsIPrincipal));
++  let window = docShell.contentViewer.DOMDocument.defaultView;
++  window.location = "data:text/html,<html></html>";
++  let deferred = promise.defer()
++  eventTarget.addEventListener("DOMContentLoaded", function handler(event) {
++    eventTarget.removeEventListener("DOMContentLoaded", handler, false);
++    deferred.resolve(window.document);
++  }, false);
++  return deferred.promise;
++}
++
+ /**
+  * Our model looks like this:
+  *
+  * ElementStyle:
+  *   Responsible for keeping track of which properties are overridden.
+  *   Maintains a list of Rule objects that apply to the element.
+  * Rule:
+  *   Manages a single style declaration or rule.
+@@ -60,39 +90,43 @@ const IOService = Cc["@mozilla.org/netwo
+  *        The element whose style we are viewing.
+  * @param {object} aStore
+  *        The ElementStyle can use this object to store metadata
+  *        that might outlast the rule view, particularly the current
+  *        set of disabled properties.
+  *
+  * @constructor
+  */
+-function ElementStyle(aElement, aStore)
++function ElementStyle(aElement, aStore, aNodeStyle)
+ {
+   this.element = aElement;
+   this.store = aStore || {};
++  this.nodeStyle = aNodeStyle;
+ 
+   // We don't want to overwrite this.store.userProperties so we only create it
+   // if it doesn't already exist.
+   if (!("userProperties" in this.store)) {
+     this.store.userProperties = new UserProperties();
+   }
+ 
+   if (!("disabled" in this.store)) {
+     this.store.disabled = new WeakMap();
+   }
+ 
+   let doc = aElement.ownerDocument;
+ 
+   // To figure out how shorthand properties are interpreted by the
+   // engine, we will set properties on a dummy element and observe
+   // how their .style attribute reflects them as computed values.
+-  this.dummyElement = doc.createElementNS(this.element.namespaceURI,
+-                                          this.element.tagName);
+-  this.populate();
++  this.dummyElementPromise = createDummyDocument().then(document => {
++    this.dummyElement = document.createElementNS(this.element.namespaceURI,
++                                                 this.element.tagName);
++    this.dummyElement.setAttribute("style", "background:green");
++    document.documentElement.appendChild(this.dummyElement);
++  }).then(null, promiseWarn);
+ }
+ // We're exporting _ElementStyle for unit tests.
+ exports._ElementStyle = ElementStyle;
+ 
+ ElementStyle.prototype = {
+ 
+   // The element we're looking at.
+   element: null,
+@@ -110,114 +144,130 @@ ElementStyle.prototype = {
+     if (this.onChanged) {
+       this.onChanged();
+     }
+   },
+ 
+   /**
+    * Refresh the list of rules to be displayed for the active element.
+    * Upon completion, this.rules[] will hold a list of Rule objects.
++   *
++   * Returns a promise that will be resolved when the elementStyle is
++   * ready.
+    */
+   populate: function ElementStyle_populate()
+   {
+-    // Store the current list of rules (if any) during the population
+-    // process.  They will be reused if possible.
+-    this._refreshRules = this.rules;
++    let populated = this.nodeStyle.getApplied(this.element, {
++      inherited: true,
++      matchedSelectors: true
++    }).then(entries => {
++      dump("GOT ENTRIES: " + entries + "\n");
++      // Make sure the dummy element has been created before continuing...
++      return this.dummyElementPromise.then(() => {
++        dump("BUILT MY DUMMY ELEMENT\n");
++        if (this.populated != populated) {
++          dump("DON'T CARE ANYMORE\n");
++          // Don't care anymore.
++          return promise.reject("unused");
++        }
+ 
+-    this.rules = [];
++        dump("ENTRIES: " + entries.length + "\n");
+ 
+-    let element = this.element;
+-    do {
+-      this._addElementRules(element);
+-    } while ((element = element.parentNode) &&
+-             element.nodeType === Ci.nsIDOMNode.ELEMENT_NODE);
++        // Store the current list of rules (if any) during the population
++        // process.  They will be reused if possible.
++        this._refreshRules = this.rules;
+ 
+-    // Mark overridden computed styles.
+-    this.markOverridden();
++        dump("a\n");
+ 
+-    // We're done with the previous list of rules.
+-    delete this._refreshRules;
+-  },
++        this.rules = [];
+ 
+-  _addElementRules: function ElementStyle_addElementRules(aElement)
+-  {
+-    let inherited = aElement !== this.element ? aElement : null;
++        for (let entry of entries) {
++          dump("b\n");
++          this._maybeAddRule(entry);
++        }
+ 
+-    // Include the element's style first.
+-    this._maybeAddRule({
+-      style: aElement.style,
+-      selectorText: CssLogic.l10n("rule.sourceElement"),
+-      inherited: inherited
+-    });
+ 
+-    // Get the styles that apply to the element.
+-    var domRules = domUtils.getCSSStyleRules(aElement);
++        dump("c\n");
++        // Mark overridden computed styles.
++        this.markOverridden();
+ 
+-    // getCSStyleRules returns ordered from least-specific to
+-    // most-specific.
+-    for (let i = domRules.Count() - 1; i >= 0; i--) {
+-      let domRule = domRules.GetElementAt(i);
++        // We're done with the previous list of rules.
++        delete this._refreshRules;
+ 
+-      // XXX: Optionally provide access to system sheets.
+-      let contentSheet = CssLogic.isContentStylesheet(domRule.parentStyleSheet);
+-      if (!contentSheet) {
+-        continue;
+-      }
++        dump("About to continue the promise\n");
+ 
+-      if (domRule.type !== Ci.nsIDOMCSSRule.STYLE_RULE) {
+-        continue;
+-      }
+-
+-      this._maybeAddRule({
+-        domRule: domRule,
+-        inherited: inherited
++        return null;
+       });
+-    }
++    }).then(null, promiseWarn);
++    this.populated = populated;
++    return this.populated;
+   },
+ 
+   /**
+    * Add a rule if it's one we care about.  Filters out duplicates and
+    * inherited styles with no inherited properties.
+    *
+    * @param {object} aOptions
+    *        Options for creating the Rule, see the Rule constructor.
+    *
+    * @return {bool} true if we added the rule.
+    */
+   _maybeAddRule: function ElementStyle_maybeAddRule(aOptions)
+   {
+     // If we've already included this domRule (for example, when a
+     // common selector is inherited), ignore it.
+-    if (aOptions.domRule &&
+-        this.rules.some(function(rule) rule.domRule === aOptions.domRule)) {
++    if (aOptions.rule &&
++        this.rules.some(function(rule) rule.domRule === aOptions.rule)) {
++      dump("Ignoring " + aOptions.rule + " because we supposedly already have it.\n");
++      return false;
++    }
++
++    if (aOptions.system) {
+       return false;
+     }
+ 
+     let rule = null;
+ 
++    dump("rule: " + aOptions.rule + "\n");
++
+     // If we're refreshing and the rule previously existed, reuse the
+     // Rule object.
+-    for (let r of (this._refreshRules || [])) {
+-      if (r.matches(aOptions)) {
+-        rule = r;
+-        rule.refresh();
+-        break;
++    if (this._refreshRules) {
++      dump("refresh rules: " + this._refreshRules.length + "\n");
++      dump(this._refreshRules.toString() + "\n");
++      for (let r of this._refreshRules) {
++        dump("got an obj\n");
+       }
++      for (let r of this._refreshRules) {
++        dump("r\n");
++        if (r.matches(aOptions)) {
++          dump("found a match\n");
++          rule = r;
++          rule.refresh(aOptions);
++          dump("endr\n");
++          break;
++        }
++        dump("endr loop\n");
++      }
++      dump("loop over\n");
+     }
+ 
+     // If this is a new rule, create its Rule object.
+     if (!rule) {
++      dump("Creating rule\n");
+       rule = new Rule(this, aOptions);
+     }
++    dump("have a rule now\n");
+ 
+     // Ignore inherited rules with no properties.
+     if (aOptions.inherited && rule.textProps.length == 0) {
+       return false;
+     }
+ 
++    dump("pushing rule\n");
+     this.rules.push(rule);
+     return true;
+   },
+ 
+   /**
+    * Mark the properties listed in this.rules with an overridden flag
+    * if an earlier property overrides it.
+    */
+@@ -320,32 +370,30 @@ ElementStyle.prototype = {
+ 
+ /**
+  * A single style rule or declaration.
+  *
+  * @param {ElementStyle} aElementStyle
+  *        The ElementStyle to which this rule belongs.
+  * @param {object} aOptions
+  *        The information used to construct this rule.  Properties include:
+- *          domRule: the nsIDOMCSSStyleRule to view, if any.
+- *          style: the nsIDOMCSSStyleDeclaration to view.  If omitted,
+- *            the domRule's style will be used.
+- *          selectorText: selector text to display.  If omitted, the domRule's
+- *            selectorText will be used.
++ *          rule: A StyleRuleActor
+  *          inherited: An element this rule was inherited from.  If omitted,
+  *            the rule applies directly to the current element.
+  * @constructor
+  */
+ function Rule(aElementStyle, aOptions)
+ {
+   this.elementStyle = aElementStyle;
+-  this.domRule = aOptions.domRule || null;
+-  this.style = aOptions.style || this.domRule.style;
+-  this.selectorText = aOptions.selectorText || this.domRule.selectorText;
++  this.domRule = aOptions.rule || null;
++  this.style = aOptions.rule;
++  this.matchedSelectors = aOptions.matchedSelectors || [];
++
+   this.inherited = aOptions.inherited || null;
++  this._modificationDepth = 0;
+ 
+   if (this.domRule) {
+     let parentRule = this.domRule.parentRule;
+     if (parentRule && parentRule.type == Ci.nsIDOMCSSRule.MEDIA_RULE) {
+       this.mediaText = parentRule.media.mediaText;
+     }
+   }
+ 
+@@ -383,46 +431,49 @@ Rule.prototype = {
+         eltText += "#" + this.inherited.id;
+       }
+       this._inheritedSource =
+         CssLogic._strings.formatStringFromName("rule.inheritedFrom", [eltText], 1);
+     }
+     return this._inheritedSource;
+   },
+ 
++  get selectorText()
++  {
++    return this.domRule.selectors ? this.domRule.selectors.join(", ") : CssLogic.l10n("rule.sourceElement");
++  },
++
+   /**
+    * The rule's stylesheet.
+    */
+   get sheet()
+   {
+     return this.domRule ? this.domRule.parentStyleSheet : null;
+   },
+ 
+   /**
+    * The rule's line within a stylesheet
+    */
+   get ruleLine()
+   {
+-    if (!this.sheet) {
+-      // No stylesheet, no ruleLine
+-      return null;
+-    }
+-    return domUtils.getRuleLine(this.domRule);
++    return this.domRule ? this.domRule.ruleLine : null;
+   },
+ 
+   /**
+    * Returns true if the rule matches the creation options
+    * specified.
+    *
+    * @param {object} aOptions
+    *        Creation options.  See the Rule constructor for documentation.
+    */
+   matches: function Rule_matches(aOptions)
+   {
+-    return (this.style === (aOptions.style || aOptions.domRule.style));
++    dump("style: " + this.style + "\n");
++    dump("rule: " + aOptions.rule + "\n");
++    return (this.style === (aOptions.rule));
+   },
+ 
+   /**
+    * Create a new TextProperty to include in the rule.
+    *
+    * @param {string} aName
+    *        The text property name (such as "background" or "border-top").
+    * @param {string} aValue
+@@ -443,74 +494,82 @@ Rule.prototype = {
+    * computed styles.  Store disabled properties in the element
+    * style's store.  Will re-mark overridden properties.
+    *
+    * @param {string} [aName]
+    *        A text property name (such as "background" or "border-top") used
+    *        when calling from setPropertyValue & setPropertyName to signify
+    *        that the property should be saved in store.userProperties.
+    */
+-  applyProperties: function Rule_applyProperties(aName)
++  applyProperties: function Rule_applyProperties(aModifications, aName)
+   {
++    if (!aModifications) {
++      aModifications = this.style.startModifyingProperties();
++    }
+     let disabledProps = [];
+     let store = this.elementStyle.store;
+ 
+     for each (let prop in this.textProps) {
+       if (!prop.enabled) {
+         disabledProps.push({
+           name: prop.name,
+           value: prop.value,
+           priority: prop.priority
+         });
+         continue;
+       }
+ 
+-      this.style.setProperty(prop.name, prop.value, prop.priority);
++      aModifications.setProperty(prop.name, prop.value, prop.priority);
+ 
+       if (aName && prop.name == aName) {
+         store.userProperties.setProperty(
+           this.style, prop.name,
+-          this.style.getPropertyValue(prop.name),
++          null,
++//          this.style.getPropertyValue(prop.name),
+           prop.value);
+       }
+ 
+       // Refresh the property's priority from the style, to reflect
+       // any changes made during parsing.
+-      prop.priority = this.style.getPropertyPriority(prop.name);
++//      prop.priority = this.style.getPropertyPriority(prop.name);
+       prop.updateComputed();
+     }
+-    this.elementStyle._changed();
+ 
+-    // Store disabled properties in the disabled store.
+-    let disabled = this.elementStyle.store.disabled;
+-    if (disabledProps.length > 0) {
+-      disabled.set(this.style, disabledProps);
+-    } else {
+-      disabled.delete(this.style);
+-    }
++    return aModifications.apply().then(() => {
++      this.elementStyle._changed();
+ 
+-    this.elementStyle.markOverridden();
++      // Store disabled properties in the disabled store.
++      let disabled = this.elementStyle.store.disabled;
++      if (disabledProps.length > 0) {
++        disabled.set(this.style, disabledProps);
++      } else {
++        disabled.delete(this.style);
++      }
++
++      this.elementStyle.markOverridden();
++    }).then(null, promiseWarn);
+   },
+ 
+   /**
+    * Renames a property.
+    *
+    * @param {TextProperty} aProperty
+    *        The property to rename.
+    * @param {string} aName
+    *        The new property name (such as "background" or "border-top").
+    */
+   setPropertyName: function Rule_setPropertyName(aProperty, aName)
+   {
+     if (aName === aProperty.name) {
+       return;
+     }
+-    this.style.removeProperty(aProperty.name);
++    let modifications = this.style.startModifyingProperties();
++    modifications.removeProperty(aProperty.name);
+     aProperty.name = aName;
+-    this.applyProperties(aName);
++    this.applyProperties(modifications, aName);
+   },
+ 
+   /**
+    * Sets the value and priority of a property.
+    *
+    * @param {TextProperty} aProperty
+    *        The property to manipulate.
+    * @param {string} aValue
+@@ -520,42 +579,44 @@ Rule.prototype = {
+    */
+   setPropertyValue: function Rule_setPropertyValue(aProperty, aValue, aPriority)
+   {
+     if (aValue === aProperty.value && aPriority === aProperty.priority) {
+       return;
+     }
+     aProperty.value = aValue;
+     aProperty.priority = aPriority;
+-    this.applyProperties(aProperty.name);
++    this.applyProperties(null, aProperty.name);
+   },
+ 
+   /**
+    * Disables or enables given TextProperty.
+    */
+   setPropertyEnabled: function Rule_enableProperty(aProperty, aValue)
+   {
+     aProperty.enabled = !!aValue;
++    let modifications = this.style.startModifyingProperties();
+     if (!aProperty.enabled) {
+-      this.style.removeProperty(aProperty.name);
++      modifications.removeProperty(aProperty.name);
+     }
+-    this.applyProperties();
++    this.applyProperties(modifications);
+   },
+ 
+   /**
+    * Remove a given TextProperty from the rule and update the rule
+    * accordingly.
+    */
+   removeProperty: function Rule_removeProperty(aProperty)
+   {
+     this.textProps = this.textProps.filter(function(prop) prop != aProperty);
+-    this.style.removeProperty(aProperty);
++    let modifications = this.style.startModifyingProperties();
++    modifications.removeProperty(aProperty);
+     // Need to re-apply properties in case removing this TextProperty
+     // exposes another one.
+-    this.applyProperties();
++    this.applyProperties(modifications);
+   },
+ 
+   /**
+    * Get the list of TextProperties from the style.  Needs
+    * to parse the style's cssText.
+    */
+   _getTextProperties: function Rule_getTextProperties()
+   {
+@@ -603,18 +664,19 @@ Rule.prototype = {
+ 
+     return textProps;
+   },
+ 
+   /**
+    * Reread the current state of the rules and rebuild text
+    * properties as needed.
+    */
+-  refresh: function Rule_refresh()
++  refresh: function Rule_refresh(aOptions)
+   {
++    this.matchedSelectors = aOptions.matched || [];
+     let newTextProps = this._getTextProperties();
+ 
+     // Update current properties for each property present on the style.
+     // This will mark any touched properties with _visited so we
+     // can detect properties that weren't touched (because they were
+     // removed from the style).
+     // Also keep track of properties that didn't exist in the current set
+     // of properties.
+@@ -857,40 +919,45 @@ TextProperty.prototype = {
+  * property will be available with the user interface.
+  *
+  * @param {Document} aDoc
+  *        The document that will contain the rule view.
+  * @param {object} aStore
+  *        The CSS rule view can use this object to store metadata
+  *        that might outlast the rule view, particularly the current
+  *        set of disabled properties.
+- * @param {<iframe>} aOuterIFrame
+- *        The iframe containing the ruleview.
++ * @param {NodeStyleFront} aNodeStyle
++ *        The NodeStyleFront for communicating with the remote server.
+  * @constructor
+  */
+-function CssRuleView(aDoc, aStore)
++function CssRuleView(aDoc, aStore, aNodeStyle)
+ {
+   this.doc = aDoc;
+   this.store = aStore;
++  this.nodeStyle = aNodeStyle;
+   this.element = this.doc.createElementNS(HTML_NS, "div");
+   this.element.className = "ruleview devtools-monospace";
+   this.element.flex = 1;
+ 
+   this._boundCopy = this._onCopy.bind(this);
+   this.element.addEventListener("copy", this._boundCopy);
+ 
+   this._showEmpty();
+ }
+ 
+ exports.CssRuleView = CssRuleView;
+ 
+ CssRuleView.prototype = {
+   // The element that we're inspecting.
+   _viewedElement: null,
+ 
++  setNodeStyle: function(aNodeStyle) {
++    this.nodeStyle = aNodeStyle;
++  },
++
+   /**
+    * Return {bool} true if the rule view currently has an input editor visible.
+    */
+   get isEditing() {
+     return this.element.querySelectorAll(".styleinspector-propertyeditor").length > 0;
+   },
+ 
+   destroy: function CssRuleView_destroy()
+@@ -908,62 +975,79 @@ CssRuleView.prototype = {
+   /**
+    * Update the highlighted element.
+    *
+    * @param {nsIDOMElement} aElement
+    *        The node whose style rules we'll inspect.
+    */
+   highlight: function CssRuleView_highlight(aElement)
+   {
++    dump("ruleview highlighting\n");
+     if (this._viewedElement === aElement) {
+-      return;
++      dump("same element\n");
++      return promise.resolve(undefined);
+     }
+ 
+     this.clear();
+ 
+     if (this._elementStyle) {
+       delete this._elementStyle;
+     }
+ 
+     this._viewedElement = aElement;
+     if (!this._viewedElement) {
+       this._showEmpty();
+-      return;
++      return promise.resolve(undefined);
+     }
+ 
+-    this._elementStyle = new ElementStyle(aElement, this.store);
+-    this._elementStyle.onChanged = function() {
+-      this._changed();
+-    }.bind(this);
+-
+-    this._createEditors();
++    this._elementStyle = new ElementStyle(aElement, this.store, this.nodeStyle);
++    dump("Calling _populate\n");
++    return this._populate().then(() => {
++      this._elementStyle.onChanged = () => {
++        this._changed();
++      };
++    }).then(null, (err) => console.error);
+   },
+ 
+   /**
+    * Update the rules for the currently highlighted element.
+    */
+   nodeChanged: function CssRuleView_nodeChanged()
+   {
++    dump("NODE CHANGED\n");
+     // Ignore refreshes during editing or when no element is selected.
+     if (this.isEditing || !this._elementStyle) {
+-      return;
++      dump("editing or !elementstyle?");
++      return promise.resolve(null);
+     }
+ 
+     this._clearRules();
+ 
+     // Repopulate the element style.
+-    this._elementStyle.populate();
++    return this._populate();
++  },
+ 
+-    // Refresh the rule editors.
+-    this._createEditors();
++  _populate: function() {
++    dump("POPULATING\n");
++    let elementStyle = this._elementStyle;
++    return this._elementStyle.populate().then(() => {
++      dump("PROMISE DONE\n");
++      if (this._elementStyle != elementStyle) {
++        dump("ELEMENT CHANGED\n");
++        return promise.reject("element changed");
++      }
++      this._createEditors();
+ 
+-    // Notify anyone that cares that we refreshed.
+-    var evt = this.doc.createEvent("Events");
+-    evt.initEvent("CssRuleViewRefreshed", true, false);
+-    this.element.dispatchEvent(evt);
++      dump("DONE REFRESHING\n");
++      // Notify anyone that cares that we refreshed.
++      var evt = this.doc.createEvent("Events");
++      evt.initEvent("CssRuleViewRefreshed", true, false);
++      this.element.dispatchEvent(evt);
++      return undefined;
++    }).then(null, promiseWarn);
+   },
+ 
+   /**
+    * Show the user that the rule view has no node selected.
+    */
+   _showEmpty: function CssRuleView_showEmpty()
+   {
+     if (this.doc.getElementById("noResults") > 0) {
+@@ -1007,36 +1091,41 @@ CssRuleView.prototype = {
+     this.element.dispatchEvent(evt);
+   },
+ 
+   /**
+    * Creates editor UI for each of the rules in _elementStyle.
+    */
+   _createEditors: function CssRuleView_createEditors()
+   {
++    dump("starting to create editors...\n");
+     // Run through the current list of rules, attaching
+     // their editors in order.  Create editors if needed.
+     let lastInheritedSource = "";
+-    for each (let rule in this._elementStyle.rules) {
++    for (let rule of this._elementStyle.rules) {
++      if (rule.domRule.system) {
++        continue;
++      }
+ 
+       let inheritedSource = rule.inheritedSource;
+       if (inheritedSource != lastInheritedSource) {
+         let h2 = this.doc.createElementNS(HTML_NS, "div");
+         h2.className = "ruleview-rule-inheritance theme-gutter";
+         h2.textContent = inheritedSource;
+         lastInheritedSource = inheritedSource;
+         this.element.appendChild(h2);
+       }
+ 
+       if (!rule.editor) {
+         new RuleEditor(this, rule);
+       }
+ 
+       this.element.appendChild(rule.editor.element);
+     }
++    dump("Done creating editors\n");
+   },
+ 
+   /**
+    * Copy selected text from the rule view.
+    *
+    * @param {Event} aEvent
+    *        The event object.
+    */
+@@ -1091,16 +1180,17 @@ function RuleEditor(aRuleView, aRule)
+   this._newPropertyDestroy = this._newPropertyDestroy.bind(this);
+ 
+   this._create();
+ }
+ 
+ RuleEditor.prototype = {
+   _create: function RuleEditor_create()
+   {
++    dump("CREATING\n");
+     this.element = this.doc.createElementNS(HTML_NS, "div");
+     this.element.className = "ruleview-rule theme-separator";
+     this.element._ruleEditor = this;
+ 
+     // Give a relative position for the inplace editor's measurement
+     // span to be placed absolutely against.
+     this.element.style.position = "relative";
+ 
+@@ -1170,55 +1260,54 @@ RuleEditor.prototype = {
+       tabindex: "0",
+       textContent: "}"
+     });
+ 
+     // Create a property editor when the close brace is clicked.
+     editableItem({ element: this.closeBrace }, function(aElement) {
+       this.newProperty();
+     }.bind(this));
++    dump("DONE CREATING\n");
+   },
+ 
+   /**
+    * Update the rule editor with the contents of the rule.
+    */
+   populate: function RuleEditor_populate()
+   {
+     // Clear out existing viewers.
+     while (this.selectorText.hasChildNodes()) {
+       this.selectorText.removeChild(this.selectorText.lastChild);
+     }
+ 
+     // If selector text comes from a css rule, highlight selectors that
+     // actually match.  For custom selector text (such as for the 'element'
+     // style, just show the text directly.
+-    if (this.rule.domRule && this.rule.domRule.selectorText) {
+-      let selectors = CssLogic.getSelectors(this.rule.domRule);
+-      let element = this.rule.inherited || this.ruleView._viewedElement;
+-      for (let i = 0; i < selectors.length; i++) {
+-        let selector = selectors[i];
++    if (this.rule.domRule.type === ELEMENT_STYLE) {
++      this.selectorText.textContent = this.rule.selectorText;
++    } else {
++      this.rule.domRule.selectors.forEach((selector, i) => {
+         if (i != 0) {
+           createChild(this.selectorText, "span", {
+             class: "ruleview-selector-separator",
+             textContent: ", "
+           });
+         }
+         let cls;
+-        if (domUtils.selectorMatchesElement(element, this.rule.domRule, i)) {
++        dump("Comparing against: " + JSON.stringify(this.rule.matchedSelectors) + "\n");
++        if (this.rule.matchedSelectors.indexOf(selector) > -1) {
+           cls = "ruleview-selector-matched";
+         } else {
+           cls = "ruleview-selector-unmatched";
+         }
+         createChild(this.selectorText, "span", {
+           class: cls,
+           textContent: selector
+         });
+-      }
+-    } else {
+-      this.selectorText.textContent = this.rule.selectorText;
++      });
+     }
+ 
+     for (let prop of this.rule.textProps) {
+       if (!prop.editor) {
+         new TextPropertyEditor(this, prop);
+         this.propertyList.appendChild(prop.editor.element);
+       }
+     }
+diff --git a/browser/devtools/styleinspector/style-inspector.js b/browser/devtools/styleinspector/style-inspector.js
+--- a/browser/devtools/styleinspector/style-inspector.js
++++ b/browser/devtools/styleinspector/style-inspector.js
+@@ -10,35 +10,40 @@ let ToolDefinitions = require("main").To
+ 
+ Cu.import("resource://gre/modules/Services.jsm");
+ 
+ loader.lazyGetter(this, "gDevTools", () => Cu.import("resource:///modules/devtools/gDevTools.jsm", {}).gDevTools);
+ loader.lazyGetter(this, "RuleView", () => require("devtools/styleinspector/rule-view"));
+ loader.lazyGetter(this, "ComputedView", () => require("devtools/styleinspector/computed-view"));
+ loader.lazyGetter(this, "_strings", () => Services.strings
+   .createBundle("chrome://browser/locale/devtools/styleinspector.properties"));
+-loader.lazyGetter(this, "CssLogic", () => require("devtools/styleinspector/css-logic").CssLogic);
+ 
+ // This module doesn't currently export any symbols directly, it only
+ // registers inspector tools.
+ 
+ function RuleViewTool(aInspector, aWindow, aIFrame)
+ {
+   this.inspector = aInspector;
+   this.doc = aWindow.document;
+   this.outerIFrame = aIFrame;
+ 
+   this.view = new RuleView.CssRuleView(this.doc, null);
+   this.doc.documentElement.appendChild(this.view.element);
+ 
+-  this._changeHandler = function() {
++  this._changeHandler = () => {
+     this.inspector.markDirty();
+-  }.bind(this);
++  };
+ 
+-  this.view.element.addEventListener("CssRuleViewChanged", this._changeHandler)
++  this.view.element.addEventListener("CssRuleViewChanged", this._changeHandler);
++
++  this._refreshHandler = () => {
++    this.inspector.emit("rule-view-refreshed");
++  };
++
++  this.view.element.addEventListener("CssRuleViewRefreshed", this._refreshHandler);
+ 
+   this._cssLinkHandler = function(aEvent) {
+     let contentDoc = this.inspector.selection.document;
+     let rule = aEvent.detail.rule;
+     let line = rule.ruleLine || 0;
+     let styleSheet = rule.sheet;
+     let styleSheets = contentDoc.styleSheets;
+     let contentSheet = false;
+@@ -76,76 +81,83 @@ function RuleViewTool(aInspector, aWindo
+     }
+   }.bind(this);
+ 
+   this.view.element.addEventListener("CssRuleViewCSSLinkClicked",
+                                      this._cssLinkHandler);
+ 
+   this._onSelect = this.onSelect.bind(this);
+   this.inspector.selection.on("detached", this._onSelect);
+-  this.inspector.selection.on("new-node", this._onSelect);
++  this.inspector.selection.on("new-node-front", this._onSelect);
+   this.refresh = this.refresh.bind(this);
+   this.inspector.on("layout-change", this.refresh);
+   this.inspector.sidebar.on("ruleview-selected", this.refresh);
+   this.inspector.selection.on("pseudoclass", this.refresh);
+   if (this.inspector.highlighter) {
+     this.inspector.highlighter.on("locked", this._onSelect);
+   }
+ 
+   this.onSelect();
+ }
+ 
+ exports.RuleViewTool = RuleViewTool;
+ 
+ RuleViewTool.prototype = {
+   onSelect: function RVT_onSelect(aEvent) {
++    this.view.setNodeStyle(this.inspector.nodeStyle);
+     if (!this.inspector.selection.isConnected() ||
+         !this.inspector.selection.isElementNode()) {
+       this.view.highlight(null);
+       return;
+     }
+ 
+-    if (!aEvent || aEvent == "new-node") {
++    if (!aEvent || aEvent == "new-node-front") {
+       if (this.inspector.selection.reason == "highlighter") {
+         this.view.highlight(null);
+       } else {
+-        this.view.highlight(this.inspector.selection.node);
++        let done = this.inspector.updating("rule-view");
++        this.view.highlight(this.inspector.selection.nodeFront).then(done, done);
+       }
+     }
+ 
+     if (aEvent == "locked") {
+-      this.view.highlight(this.inspector.selection.node);
++      let done = this.inspector.updating("rule-view");
++      this.view.highlight(this.inspector.selection.nodeFront).then(done, done);
+     }
+   },
+ 
+   isActive: function RVT_isActive() {
+     return this.inspector.sidebar.getCurrentTabID() == "ruleview";
+   },
+ 
+   refresh: function RVT_refresh() {
++    dump("REFRESHING\n");
+     if (this.isActive()) {
+       this.view.nodeChanged();
+     }
+   },
+ 
+   destroy: function RVT_destroy() {
+     this.inspector.off("layout-change", this.refresh);
+     this.inspector.sidebar.off("ruleview-selected", this.refresh);
+     this.inspector.selection.off("pseudoclass", this.refresh);
+-    this.inspector.selection.off("new-node", this._onSelect);
++    this.inspector.selection.off("new-node-front", this._onSelect);
+     if (this.inspector.highlighter) {
+       this.inspector.highlighter.off("locked", this._onSelect);
+     }
+ 
+     this.view.element.removeEventListener("CssRuleViewCSSLinkClicked",
+       this._cssLinkHandler);
+ 
+     this.view.element.removeEventListener("CssRuleViewChanged",
+       this._changeHandler);
+ 
++    this.view.element.removeEventListener("CssRuleViewRefreshed",
++      this._refreshHandler);
++
+     this.doc.documentElement.removeChild(this.view.element);
+ 
+     this.view.destroy();
+ 
+     delete this.outerIFrame;
+     delete this.view;
+     delete this.doc;
+     delete this.inspector;
+@@ -153,79 +165,81 @@ RuleViewTool.prototype = {
+ }
+ 
+ function ComputedViewTool(aInspector, aWindow, aIFrame)
+ {
+   this.inspector = aInspector;
+   this.window = aWindow;
+   this.document = aWindow.document;
+   this.outerIFrame = aIFrame;
+-  this.cssLogic = new CssLogic();
+-  this.view = new ComputedView.CssHtmlTree(this);
++  this.view = new ComputedView.CssHtmlTree(this, aInspector.nodeStyle);
+ 
+   this._onSelect = this.onSelect.bind(this);
+   this.inspector.selection.on("detached", this._onSelect);
+-  this.inspector.selection.on("new-node", this._onSelect);
++  this.inspector.selection.on("new-node-front", this._onSelect);
+   if (this.inspector.highlighter) {
+     this.inspector.highlighter.on("locked", this._onSelect);
+   }
+   this.refresh = this.refresh.bind(this);
+   this.inspector.on("layout-change", this.refresh);
+   this.inspector.sidebar.on("computedview-selected", this.refresh);
+   this.inspector.selection.on("pseudoclass", this.refresh);
+ 
+-  this.cssLogic.highlight(null);
+   this.view.highlight(null);
+ 
+   this.onSelect();
+ }
+ 
+ exports.ComputedViewTool = ComputedViewTool;
+ 
+ ComputedViewTool.prototype = {
+   onSelect: function CVT_onSelect(aEvent)
+   {
+     if (!this.inspector.selection.isConnected() ||
+         !this.inspector.selection.isElementNode()) {
+       this.view.highlight(null);
+       return;
+     }
+ 
+-    if (!aEvent || aEvent == "new-node") {
++    if (!aEvent || aEvent == "new-node-front") {
+       if (this.inspector.selection.reason == "highlighter") {
++        dump("highlighter\n");
+         // FIXME: We should hide view's content
+       } else {
+-        this.cssLogic.highlight(this.inspector.selection.node);
+-        this.view.highlight(this.inspector.selection.node);
++        let done = this.inspector.updating("computed-view");
++        this.view.highlight(this.inspector.selection.nodeFront).then(() => {
++          done();
++        });
+       }
+     }
+ 
+     if (aEvent == "locked") {
+-      this.cssLogic.highlight(this.inspector.selection.node);
+-      this.view.highlight(this.inspector.selection.node);
++      let done = this.inspector.updating("computed-view");
++      this.view.highlight(this.inspector.selection.nodeFront).then(() => {
++        done();
++      });
+     }
+   },
+ 
+   isActive: function CVT_isActive() {
+     return this.inspector.sidebar.getCurrentTabID() == "computedview";
+   },
+ 
+   refresh: function CVT_refresh() {
+     if (this.isActive()) {
+-      this.cssLogic.highlight(this.inspector.selection.node);
+       this.view.refreshPanel();
+     }
+   },
+ 
+   destroy: function CVT_destroy(aContext)
+   {
+     this.inspector.off("layout-change", this.refresh);
+     this.inspector.sidebar.off("computedview-selected", this.refresh);
+     this.inspector.selection.off("pseudoclass", this.refresh);
+-    this.inspector.selection.off("new-node", this._onSelect);
++    this.inspector.selection.off("new-node-front", this._onSelect);
+     if (this.inspector.highlighter) {
+       this.inspector.highlighter.off("locked", this._onSelect);
+     }
+ 
+     this.view.destroy();
+     delete this.view;
+ 
+     delete this.outerIFrame;
+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
+ 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");
+ 
++const {NodeStyleActor} = require("devtools/server/actors/styles");
++
+ 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({
+   /**
+    * 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) {
++    if (detail === "actorid") {
++      return this.actorID;
++    }
++
+     let parentNode = this.walker.parentNode(this);
+ 
+     // Estimate the number of children.
+     let numChildren = this.rawNode.childNodes.length;
+     if (numChildren === 0 &&
+         (this.rawNode.contentDocument || this.rawNode.getSVGDocument)) {
+       // This might be an iframe with virtual children.
+       numChildren = 1;
+     }
+ 
+     let form = {
+       actor: this.actorID,
+       parent: parentNode ? parentNode.actorID : undefined,
+       nodeType: this.rawNode.nodeType,
+-      namespaceURI: this.namespaceURI,
++      namespaceURI: this.rawNode.namespaceURI,
+       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({
+       } else {
+         form.shortValue = this.rawNode.nodeValue;
+       }
+     }
+ 
+     return form;
+   },
+ 
++  /**
++   * Returns a best guess at the owner document for the node.
++   */
++  connectedDocument: function() {
++    let node = this;
++    while (node) {
++      if (node.nodeType === Ci.nsIDOMNode.DOCUMENT_NODE) {
++        return node;
++      }
++    }
++    return null;
++  },
++
+   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
+       this._observer = null;
+     }
+ 
+     protocol.Front.prototype.destroy.call(this);
+   },
+ 
+   // Update the object given a form representation off the wire.
+   form: function(form, detail, ctx) {
++    if (detail === "actorid") {
++      this.actorID = form;
++      return;
++    }
+     // 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
+     });
+   },
+ 
++  getNodeStyle: method(function() {
++    return NodeStyleActor(this);
++  }, {
++    request: {},
++    response: { nodeStyle: RetVal("nodestyle") }
++  }),
++
+   /**
+    * 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) {
+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,508 @@
++/* 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");
++const {Arg, Option, method, RetVal, types} = protocol;
++const events = require("sdk/event/core");
++const object = require("sdk/util/object");
++
++// XXX: Move css-logic into toolkit.
++loader.lazyGetter(this, "CssLogic", () => require("devtools/styleinspector/css-logic").CssLogic);
++loader.lazyGetter(this, "DOMUtils", () => Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils));
++
++const ELEMENT_STYLE = 100;
++exports.ELEMENT_STYLE = ELEMENT_STYLE;
++
++// 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.addDictType("matchedselector", {
++  rule: "domstylerule#actorid",
++  sourceElement: "nullable:requestWalker:domnode",
++  selector: "string",
++  value: "string",
++  status: "number"
++});
++
++var NodeStyleActor = protocol.ActorClass({
++  typeName: "nodestyle",
++  initialize: function(walker) {
++    protocol.Actor.prototype.initialize.call(this, null);
++    this.walker = walker;
++    this._refMap = new Map;
++    this.cssLogic = new CssLogic();
++  },
++
++  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);
++    }
++    let actor = StyleRuleActor(this, item);
++    this.manage(actor);
++    this._refMap.set(item, actor);
++
++    return actor;
++  },
++
++  _sheetRef: function(sheet) {
++    if (this._refMap.has(sheet)) {
++      return this._refMap.get(sheet);
++    }
++    let actor = StyleSheetActor(this, sheet);
++    this.manage(actor);
++    this._refMap.set(sheet, actor);
++
++    return actor;
++  },
++
++  getComputed: method(function(node, options) {
++    let win = node.rawNode.ownerDocument.defaultView;
++    let ret = Object.create(null);
++
++    this.cssLogic.sourceFilter = options.filter || CssLogic.FILTER.UA;
++    this.cssLogic.highlight(node.rawNode);
++    let computed = this.cssLogic._computedStyle;
++
++    Array.prototype.forEach.call(computed, name => {
++      ret[name] = {
++        value: computed.getPropertyValue(name),
++        priority: computed.getPropertyPriority(name) || undefined
++      };
++    });
++
++    if (options.markMatched || options.onlyMatched) {
++      let matched = this.cssLogic.hasMatchedSelectors(Object.keys(ret));
++      for (let key in ret) {
++        if (matched[key]) {
++          ret[key].matched = options.markMatched ? true : undefined
++        } else if (options.onlyMatched) {
++          delete ret[key];
++        }
++      }
++    }
++
++    return ret;
++  }, {
++    request: {
++      node: Arg(0, "domnode"),
++      markMatched: Option(1, "boolean"),
++      onlyMatched: Option(1, "boolean"),
++      filter: Option(1, "string"),
++
++    },
++    response: {
++      computed: RetVal("json")
++    }
++  }),
++
++  expandSets: function(ruleSet, sheetSet) {
++    // Sets include new items in their iteration.
++    for (let rule of ruleSet) {
++      if (rule.rawRule.parentRule) {
++        let parent = this._styleRef(rule.rawRule.parentRule);
++        if (!ruleSet.has(parent)) {
++          ruleSet.add(parent);
++        }
++      }
++      if (rule.rawRule.parentStyleSheet) {
++        let parent = this._sheetRef(rule.rawRule.parentStyleSheet);
++        if (!sheetSet.has(parent)) {
++          sheetSet.add(parent);
++        }
++      }
++    }
++
++    for (let sheet of sheetSet) {
++      if (sheet.rawSheet.parentStyleSheet) {
++        let parent = this._sheetRef(sheet.rawSheet.parentStyleSheet);
++        if (!sheetSet.has(parent)) {
++          sheetSet.add(parent);
++        }
++      }
++    }
++  },
++
++  getMatchedSelectors: method(function(node, property) {
++    this.cssLogic.highlight(node.rawNode);
++
++    let walker = node.parent();
++
++    let rules = new Set;
++    let sheets = new Set;
++
++    let matched = [];
++    let propInfo = this.cssLogic.getPropertyInfo(property);
++    for (let selectorInfo of propInfo.matchedSelectors) {
++      let cssRule = selectorInfo.selector._cssRule;
++      let domRule = cssRule._domRule || cssRule.sourceElement;
++      let rule;
++      if (domRule) {
++        rule = domRule ? this._styleRef(domRule) : undefined;
++        rules.add(rule);
++      }
++
++      let sourceElement = undefined;
++      if (selectorInfo.sourceElement) {
++        sourceElement = walker._ref(selectorInfo.sourceElement);
++      }
++      matched.push({
++        rule: rule,
++        href: cssRule.href,
++        sourceText: this.getSelectorSource(selectorInfo, node.rawNode),
++        sourceElement: sourceElement,
++        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"),
++      properties: Arg(1, "string")
++    },
++    response: RetVal(types.addDictType("matchedselectorresponse", {
++      rules: "array:domstylerule",
++      sheets: "array:domsheet",
++      matched: "array:matchedselector"
++    }))
++  }),
++
++  getSelectorSource: function(selectorInfo, relativeTo) {
++    let result = selectorInfo.selector.text;
++    if (selectorInfo.elementStyle) {
++      let source = selectorInfo.sourceElement;
++      if (source === relativeTo) {
++        return "this";
++      } else {
++        result = CssLogic.getShortName(source);
++      }
++      result += ".style"
++    }
++    return result;
++  },
++
++  getApplied: method(function(node, options) {
++    let entries = [];
++
++    this.addElementRules(node.rawNode, undefined, options, entries);
++
++    if (options.inherited) {
++      let parent = this.walker.parentNode(node);
++      while (parent && parent.type != Ci.nsIDOMNode.DOCUMENT_ELEMENT) {
++        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++) {
++          if (DOMUtils.selectorMatchesElement(element, domRule, i)) {
++            entry.matchedSelectors.push(selectors[i]);
++          }
++        }
++
++      }
++    }
++
++    let rules = new Set;
++    let sheets = new Set;
++    entries.forEach(entry => rules.add(entry.rule));
++    this.expandSets(rules, sheets);
++
++    return {
++      entries: entries,
++      rules: [...rules],
++      sheets: [...sheets]
++    }
++  }, {
++    request: {
++      node: Arg(0, "domnode"),
++      inherited: Option(1, "boolean"),
++      matchedSelectors: Option(1, "boolean")
++    },
++    response: RetVal(types.addDictType("appliedStylesReturn", {
++      entries: "array:appliedstyle",
++      rules: "array:domstylerule",
++      sheets: "array:domsheet"
++    }))
++  }),
++
++  addElementRules: function(element, inherited, options, rules)
++  {
++    let elementStyle = this._styleRef(element);
++    rules.push({
++      rule: elementStyle,
++      inherited: inherited,
++    });
++
++    // Get the styles that apply to the element.
++    let domRules = DOMUtils.getCSSStyleRules(element);
++
++    // getCSSStyleRules returns ordered from least-specific to
++    // most-specific.
++    for (let i = domRules.Count() - 1; i >= 0; i--) {
++      let domRule = domRules.GetElementAt(i);
++
++      let isSystem = !CssLogic.isContentStylesheet(domRule.parentStyleSheet);
++
++      let ruleActor = this._styleRef(domRule);
++      rules.push({
++        rule: ruleActor,
++        inherited: inherited,
++        system: isSystem || undefined
++      });
++    }
++  },
++});
++exports.NodeStyleActor = NodeStyleActor;
++
++var NodeStyleFront = protocol.FrontClass(NodeStyleActor, {
++  initialize: function(conn, form, ctx, detail) {
++    protocol.Front.prototype.initialize.call(this, conn, form, ctx, detail);
++  },
++
++  destroy: function() {
++    protocol.Front.prototype.destroy.call(this);
++  },
++
++  getMatchedSelectors: protocol.custom(function(node, property) {
++    this.requestWalker = node.walker;
++    return this._getMatchedSelectors(node, property).then(ret => {
++      return ret.matched;
++    });
++  }, {
++    impl: "_getMatchedSelectors"
++  }),
++
++  getApplied: protocol.custom(function(node, options={}) {
++    return this._getApplied(node, options).then(ret => {
++      return ret.entries;
++    });
++  }, {
++    impl: "_getApplied"
++  })
++});
++
++var StyleSheetActor = protocol.ActorClass({
++  typeName: "domsheet",
++
++  initialize: function(nodeStyle, sheet) {
++    protocol.Front.prototype.initialize.call(this);
++    this.nodeStyle = nodeStyle;
++    this.rawSheet = sheet;
++  },
++
++  get conn() this.nodeStyle.conn,
++
++  form: function(detail) {
++    if (detail === "actorid") {
++      return this.actorID;
++    }
++    return {
++      actor: this.actorID,
++      href: this.rawSheet.href || this.rawSheet.ownerNode.ownerDocument.location.toString(),
++      disabled: this.rawSheet.disabled ? true : undefined
++    }
++  }
++});
++
++var StyleSheetFront = protocol.FrontClass(StyleSheetActor, {
++  initialize: function(conn, form, ctx, detail) {
++    protocol.Front.prototype.initialize.call(this, conn, form, ctx, detail);
++  },
++
++  form: function(form, detail) {
++    if (detail === "actorid") {
++      this.actorID = form;
++      return;
++    }
++    this.actorID = form.actorID;
++    this._form = form;
++  },
++
++  get href() this._form.href,
++  get disabled() !!this._form.disabled
++})
++
++types.addActorType("domstylerule");
++var StyleRuleActor = protocol.ActorClass({
++  typeName: "domstylerule",
++  initialize: function(nodeStyle, item) {
++    protocol.Actor.prototype.initialize.call(this, null);
++    this.nodeStyle = nodeStyle;
++    if (item instanceof (Ci.nsIDOMCSSRule)) {
++      this.type = item.type;
++      this.rawRule = item;
++      if (this.rawRule instanceof Ci.nsIDOMCSSStyleRule && this.rawRule.parentStyleSheet) {
++        this.ruleLine = DOMUtils.getRuleLine(this.rawRule);
++      }
++    } else {
++      this.type = ELEMENT_STYLE;
++      this.rawRule = {
++        style: item,
++        toString: function() "[element rule " + this.style + "]"
++      }
++    }
++  },
++
++  get conn() this.nodeStyle.conn,
++  get marshallPool() this.nodeStyle,
++
++  toString: function() "[StyleRuleActor for " + this.rawRule + "]",
++
++  form: function(detail) {
++    if (detail === "actorid") {
++      return this.actorID;
++    }
++    let form = {
++      actor: this.actorID,
++      type: this.type,
++      ruleLine: this.ruleLine || undefined,
++      selectors: this.selectors,
++    };
++
++    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;
++    }
++
++    switch (this.type) {
++      case Ci.nsIDOMCSSRule.STYLE_RULE:
++        form.selectors = CssLogic.getSelectors(this.rawRule);
++        /* fall through */
++      case ELEMENT_STYLE:
++        form.cssText = this.rawRule.style.cssText || "";
++        break;
++      case Ci.nsIDOMCSSrule.CHARSET_RULE:
++        form.encoding = this.rawRule.encoding;
++        break;
++      case Ci.nsIDOMCSSRule.IMPORT_RULE:
++        form.href = this.rawRule.href;
++        break;
++      case Ci.nsIDOMCSSRule.MEDIA_RULE:
++        form.media = [];
++        for (let i = 0, n = this.media.length; i < n; i++) {
++          form.media.push(this.media.item(i));
++        }
++        break;
++    }
++
++    return form;
++  },
++
++  modifyProperties: method(function(modifications) {
++    for (let mod of modifications) {
++      if (mod.type === "set") {
++        this.rawRule.style.setProperty(mod.name, mod.value, mod.priority);
++      } else if (mod.type === "remove") {
++        this.rawRule.style.removeProperty(mod.name);
++      }
++    }
++    return this;
++  }, {
++    request: { modifications: Arg(0, "array:json") },
++    response: { rule: RetVal("domstylerule") }
++  })
++});
++
++var StyleRuleFront = protocol.FrontClass(StyleRuleActor, {
++  initialize: function(client, form, ctx, detail) {
++    protocol.Front.prototype.initialize.call(this, client, form, ctx, detail);
++  },
++
++  destroy: function() {
++    protocol.Front.prototype.destroy.call(this);
++  },
++
++  form: function(form, detail) {
++    if (detail === "actorid") {
++      this.actorID = form;
++      return;
++    }
++    this.actorID = form.actor;
++    this._form = form;
++  },
++
++  startModifyingProperties: function() {
++    return new RuleModificationList(this);
++  },
++
++  get type() this._form.type,
++  get ruleLine() this._form.ruleLine,
++  get cssText() {
++    return this._form.cssText;
++  },
++  get selectors() {
++    return this._form.selectors;
++  },
++  get parentRule() {
++    return this.parent().get(this._form.parentRule);
++  },
++  get parentStyleSheet() {
++    return this.parent().get(this._form.parentStyleSheet);
++  },
++});
++
++function RuleModificationList(rule) {
++  this.rule = rule;
++  this.modifications = [];
++}
++RuleModificationList.prototype = {
++  apply: function() {
++    return this.rule.modifyProperties(this.modifications);
++  },
++  setProperty: function(name, value, priority) {
++    this.modifications.push({
++      type: "set",
++      name: name,
++      value: value,
++      priority: priority
++    });
++  },
++  removeProperty: function(name) {
++    this.modifications.push({
++      type: "remove",
++      name: name
++    });
++  }
++};
++
+diff --git a/toolkit/devtools/server/protocol.js b/toolkit/devtools/server/protocol.js
+--- a/toolkit/devtools/server/protocol.js
++++ b/toolkit/devtools/server/protocol.js
+@@ -76,16 +76,18 @@ types.getType = function(type) {
+   // New type, see if it's a collection/lifetime type:
+   let sep = type.indexOf(":");
+   if (sep >= 0) {
+     let collection = type.substring(0, sep);
+     let subtype = types.getType(type.substring(sep + 1));
+ 
+     if (collection === "array") {
+       return types.addArrayType(subtype);
++    } else if (collection === "nullable") {
++      return types.addNullableType(subtype);
+     }
+ 
+     if (registeredLifetimes.has(collection)) {
+       return types.addLifetimeType(collection, subtype);
+     }
+ 
+     throw Error("Unknown collection type: " + collection);
+   }
+@@ -236,22 +238,23 @@ types.addActorType = function(name) {
+       // find the actor registered with this actorID.
+       if (ctx instanceof Actor) {
+         return ctx.conn.getActor(v);
+       }
+ 
+       // Reading a response on the client side, check for an
+       // existing front on the connection, and create the front
+       // if it isn't found.
+-      let front = ctx.conn.getActor(v.actor);
++      let actorID = (detail === "actorid") ? v : v.actor;
++      let front = ctx.conn.getActor(actorID);
+       if (front) {
+         front.form(v, detail, ctx);
+       } else {
+         front = new type.frontClass(ctx.conn, v, detail, ctx)
+-        front.actorID = v.actor;
++        front.actorID = actorID;
+         ctx.marshallPool().manage(front);
+       }
+       return front;
+     },
+     write: (v, ctx, detail) => {
+       // If returning a response from the server side, make sure
+       // the actor is added to a parent object and return its form.
+       if (v instanceof Actor) {
+@@ -267,16 +270,34 @@ types.addActorType = function(name) {
+   }, {
+     // We usually freeze types, but actor types are updated when clients are
+     // created, so don't freeze yet.
+     thawed: true
+   });
+   return type;
+ }
+ 
++types.addNullableType = function(subtype) {
++  subtype = types.getType(subtype);
++  return types.addType("nullable:" + subtype.name, {
++    read: (value, ctx) => {
++      if (value == null) {
++        return value;
++      }
++      return subtype.read(value, ctx);
++    },
++    write: (value, ctx) => {
++      if (value == null) {
++        return value;
++      }
++      return subtype.write(value, ctx);
++    }
++  });
++}
++
+ /**
+  * Register an actor detail type.  This is just like an actor type, but
+  * will pass a detail hint to the actor's form method during serialization/
+  * deserialization.
+  *
+  * This is called by getType() when passed an 'actorType#detail' string.
+  *
+  * @param string name
--- a/warning-fixes.diff
+++ b/warning-fixes.diff
@@ -1,10 +1,16 @@
 # HG changeset patch
-# Parent abf0da1d05d621b0bd58c6d028c0b92ae7aef8ef
+# User Dave Camp <dcamp@mozilla.com>
+# Date 1371477174 25200
+#      Mon Jun 17 06:52:54 2013 -0700
+# Node ID 70c5102898494af1ae84395936f013ba70671c0c
+# Parent  2f16f9dd59e09f9349072d84c7f88fc8a01cebed
+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;
            }
          }
        }