Bug 848731 - Deleting nodes or container frames now resets the markup, highlighter, css views accordingly. r=paul
☠☠ backed out by 4667fc2ca737 ☠ ☠
authorPatrick Brosset <pbrosset@mozilla.com>
Tue, 01 Oct 2013 08:14:07 -0400
changeset 149454 09cca9f3210390ef5c6ef51ff78202309a65188b
parent 149453 6682ed134766597270c1a33fb563c8970ed4499c
child 149455 f4e88e5f24ca1d3540c1f9320c63c395e3febd57
push id25389
push userryanvm@gmail.com
push dateTue, 01 Oct 2013 20:35:30 +0000
treeherdermozilla-central@4364824a4cab [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerspaul
bugs848731
milestone27.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 848731 - Deleting nodes or container frames now resets the markup, highlighter, css views accordingly. r=paul
browser/devtools/inspector/breadcrumbs.js
browser/devtools/inspector/inspector-panel.js
browser/devtools/inspector/selection.js
browser/devtools/inspector/test/browser.ini
browser/devtools/inspector/test/browser_inspector_bug_848731_reset_selection_on_delete.html
browser/devtools/inspector/test/browser_inspector_bug_848731_reset_selection_on_delete.js
browser/devtools/markupview/markup-view.js
--- a/browser/devtools/inspector/breadcrumbs.js
+++ b/browser/devtools/inspector/breadcrumbs.js
@@ -97,16 +97,17 @@ HTMLBreadcrumbs.prototype = {
     this.container.addEventListener("underflow", this.onscrollboxreflow, false);
     this.container.addEventListener("overflow", this.onscrollboxreflow, false);
 
     this.update = this.update.bind(this);
     this.updateSelectors = this.updateSelectors.bind(this);
     this.selection.on("new-node-front", this.update);
     this.selection.on("pseudoclass", this.updateSelectors);
     this.selection.on("attribute-changed", this.updateSelectors);
+    this.inspector.on("markupmutation", this.update);
     this.update();
   },
 
   /**
    * Include in a promise's then() chain to reject the chain
    * when the breadcrumbs' selection has changed while the promise
    * was outstanding.
    */
@@ -344,16 +345,17 @@ HTMLBreadcrumbs.prototype = {
   /**
    * Remove nodes and delete properties.
    */
   destroy: function BC_destroy()
   {
     this.selection.off("new-node-front", this.update);
     this.selection.off("pseudoclass", this.updateSelectors);
     this.selection.off("attribute-changed", this.updateSelectors);
+    this.inspector.off("markupmutation", this.update);
 
     this.container.removeEventListener("underflow", this.onscrollboxreflow, false);
     this.container.removeEventListener("overflow", this.onscrollboxreflow, false);
     this.onscrollboxreflow = null;
 
     this.empty();
     this.container.removeEventListener("mousedown", this, true);
     this.container.removeEventListener("keypress", this, true);
@@ -606,17 +608,17 @@ HTMLBreadcrumbs.prototype = {
       button.appendChild(this.prettyPrintNodeAsXUL(crumb.node));
       button.setAttribute("tooltiptext", this.prettyPrintNodeAsText(crumb.node));
     }
   },
 
   /**
    * Update the breadcrumbs display when a new node is selected.
    */
-  update: function BC_update()
+  update: function BC_update(reason)
   {
     this.inspector.hideNodeMenu();
 
     let cmdDispatcher = this.chromeDoc.commandDispatcher;
     this.hadFocus = (cmdDispatcher.focusedElement &&
                      cmdDispatcher.focusedElement.parentNode == this.container);
 
     if (!this.selection.isConnected()) {
@@ -627,17 +629,18 @@ HTMLBreadcrumbs.prototype = {
     if (!this.selection.isElementNode()) {
       this.setCursor(-1); // no selection
       return;
     }
 
     let idx = this.indexOf(this.selection.nodeFront);
 
     // Is the node already displayed in the breadcrumbs?
-    if (idx > -1) {
+    // (and there are no mutations that need re-display of the crumbs)
+    if (idx > -1 && reason !== "markupmutation") {
       // Yes. We select it.
       this.setCursor(idx);
     } else {
       // No. Is the breadcrumbs display empty?
       if (this.nodeHierarchy.length > 0) {
         // No. We drop all the element that are not direct ancestors
         // of the selection
         let parent = this.selection.nodeFront.parentNode();
@@ -661,14 +664,14 @@ HTMLBreadcrumbs.prototype = {
       // Make sure the selected node and its neighbours are visible.
       this.scroll();
       this.inspector.emit("breadcrumbs-updated", this.selection.nodeFront);
       doneUpdating();
     }).then(null, err => {
       doneUpdating(this.selection.nodeFront);
       this.selectionGuardEnd(err);
     });
-  },
+  }
 }
 
 XPCOMUtils.defineLazyGetter(this, "DOMUtils", function () {
   return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
 });
--- a/browser/devtools/inspector/inspector-panel.js
+++ b/browser/devtools/inspector/inspector-panel.js
@@ -427,17 +427,19 @@ InspectorPanel.prototype = {
                                                                      node) {
     if (this.breadcrumbs.indexOf(node) == -1) {
       // only clear locks if we'd have to update breadcrumbs
       this.clearPseudoClasses();
     }
   },
 
   /**
-   * When a node is deleted, select its parent node.
+   * When a node is deleted, select its parent node or the defaultNode if no
+   * parent is found (may happen when deleting an iframe inside which the
+   * node was selected).
    */
   onDetached: function InspectorPanel_onDetached(event, parentNode) {
     this.cancelLayoutChange();
     this.breadcrumbs.cutAfter(this.breadcrumbs.indexOf(parentNode));
     this.selection.setNodeFront(parentNode ? parentNode : this._defaultNode, "detached");
   },
 
   /**
--- a/browser/devtools/inspector/selection.js
+++ b/browser/devtools/inspector/selection.js
@@ -1,14 +1,16 @@
 /* -*- Mode: Javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+"use strict";
+
 const {Cu, Ci} = require("chrome");
 let EventEmitter = require("devtools/shared/event-emitter");
 
 /**
  * API
  *
  *   new Selection(walker=null, node=null, track={attributes,detached});
  *   destroy()
@@ -71,58 +73,64 @@ Selection.prototype = {
   _walker: null,
   _node: null,
 
   _onMutations: function(mutations) {
     let attributeChange = false;
     let pseudoChange = false;
     let detached = false;
     let parentNode = null;
+
     for (let m of mutations) {
       if (!attributeChange && m.type == "attributes") {
         attributeChange = true;
       }
       if (m.type == "childList") {
-        if (!detached && this.isNode() && !this.isConnected()) {
-          parentNode = m.target;
+        if (!detached && !this.isConnected()) {
+          if (this.isNode()) {
+            parentNode = m.target;
+          }
           detached = true;
         }
       }
-      if (m.type == "pseudoClassLock"){
+      if (m.type == "pseudoClassLock") {
         pseudoChange = true;
       }
     }
 
-    if (attributeChange)
+    // Fire our events depending on what changed in the mutations array
+    if (attributeChange) {
       this.emit("attribute-changed");
-    if (pseudoChange)
+    }
+    if (pseudoChange) {
       this.emit("pseudoclass");
+    }
     if (detached) {
       this.emit("detached", parentNode ? parentNode.rawNode() : null);
       this.emit("detached-front", parentNode);
     }
   },
 
-  destroy: function SN_destroy() {
+  destroy: function() {
     this.setNode(null);
     this.setWalker(null);
   },
 
   setWalker: function(walker) {
     if (this._walker) {
       this._walker.off("mutations", this._onMutations);
     }
     this._walker = walker;
     if (this._walker) {
       this._walker.on("mutations", this._onMutations);
     }
   },
 
   // Not remote-safe
-  setNode: function SN_setNode(value, reason="unknown") {
+  setNode: function(value, reason="unknown") {
     if (value) {
       value = this._walker.frontForRawNode(value);
     }
     this.setNodeFront(value, reason);
   },
 
   // Not remote-safe
   get node() {
@@ -163,41 +171,41 @@ Selection.prototype = {
   get documentFront() {
     return this._walker.document(this._nodeFront);
   },
 
   get nodeFront() {
     return this._nodeFront;
   },
 
-  isRoot: function SN_isRootNode() {
+  isRoot: function() {
     return this.isNode() &&
            this.isConnected() &&
            this._nodeFront.isDocumentElement;
   },
 
-  isNode: function SN_isNode() {
+  isNode: function() {
     if (!this._nodeFront) {
       return false;
     }
 
     // As long as tools are still accessing node.rawNode(),
     // this needs to stay here.
     if (this._node && Cu.isDeadWrapper(this._node)) {
       return false;
     }
 
     return true;
   },
 
-  isLocal: function SN_nsLocal() {
+  isLocal: function() {
     return !!this._node;
   },
 
-  isConnected: function SN_isConnected() {
+  isConnected: function() {
     let node = this._nodeFront;
     if (!node || !node.actorID) {
       return false;
     }
 
     // As long as there are still tools going around
     // accessing node.rawNode, this needs to stay.
     let rawNode = node.rawNode();
@@ -215,63 +223,63 @@ Selection.prototype = {
       if (node === this._walker.rootNode) {
         return true;
       }
       node = node.parentNode();
     };
     return false;
   },
 
-  isHTMLNode: function SN_isHTMLNode() {
+  isHTMLNode: function() {
     let xhtml_ns = "http://www.w3.org/1999/xhtml";
     return this.isNode() && this.node.namespaceURI == xhtml_ns;
   },
 
   // Node type
 
-  isElementNode: function SN_isElementNode() {
+  isElementNode: function() {
     return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.ELEMENT_NODE;
   },
 
-  isAttributeNode: function SN_isAttributeNode() {
+  isAttributeNode: function() {
     return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.ATTRIBUTE_NODE;
   },
 
-  isTextNode: function SN_isTextNode() {
+  isTextNode: function() {
     return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.TEXT_NODE;
   },
 
-  isCDATANode: function SN_isCDATANode() {
+  isCDATANode: function() {
     return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.CDATA_SECTION_NODE;
   },
 
-  isEntityRefNode: function SN_isEntityRefNode() {
+  isEntityRefNode: function() {
     return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.ENTITY_REFERENCE_NODE;
   },
 
-  isEntityNode: function SN_isEntityNode() {
+  isEntityNode: function() {
     return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.ENTITY_NODE;
   },
 
-  isProcessingInstructionNode: function SN_isProcessingInstructionNode() {
+  isProcessingInstructionNode: function() {
     return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.PROCESSING_INSTRUCTION_NODE;
   },
 
-  isCommentNode: function SN_isCommentNode() {
+  isCommentNode: function() {
     return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.PROCESSING_INSTRUCTION_NODE;
   },
 
-  isDocumentNode: function SN_isDocumentNode() {
+  isDocumentNode: function() {
     return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.DOCUMENT_NODE;
   },
 
-  isDocumentTypeNode: function SN_isDocumentTypeNode() {
+  isDocumentTypeNode: function() {
     return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.DOCUMENT_TYPE_NODE;
   },
 
-  isDocumentFragmentNode: function SN_isDocumentFragmentNode() {
+  isDocumentFragmentNode: function() {
     return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.DOCUMENT_FRAGMENT_NODE;
   },
 
-  isNotationNode: function SN_isNotationNode() {
+  isNotationNode: function() {
     return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.NOTATION_NODE;
   },
-}
+};
--- a/browser/devtools/inspector/test/browser.ini
+++ b/browser/devtools/inspector/test/browser.ini
@@ -11,16 +11,18 @@ support-files = head.js
 [browser_inspector_bug_674871.js]
 [browser_inspector_bug_699308_iframe_navigation.js]
 [browser_inspector_bug_817558_delete_node.js]
 [browser_inspector_bug_831693_combinator_suggestions.js]
 [browser_inspector_bug_831693_input_suggestion.js]
 [browser_inspector_bug_831693_search_suggestions.html]
 [browser_inspector_bug_835722_infobar_reappears.js]
 [browser_inspector_bug_840156_destroy_after_navigation.js]
+[browser_inspector_bug_848731_reset_selection_on_delete.js]
+[browser_inspector_bug_848731_reset_selection_on_delete.html]
 [browser_inspector_changes.js]
 [browser_inspector_cmd_inspect.html]
 [browser_inspector_cmd_inspect.js]
 [browser_inspector_dead_node_exception.html]
 [browser_inspector_dead_node_exception.js]
 [browser_inspector_destroyselection.html]
 [browser_inspector_destroyselection.js]
 [browser_inspector_highlighter.js]
new file mode 100644
--- /dev/null
+++ b/browser/devtools/inspector/test/browser_inspector_bug_848731_reset_selection_on_delete.html
@@ -0,0 +1,14 @@
+<!doctype html>
+<html lang="en">
+<head>
+  <meta charset="utf-8">
+  <title>node delete - reset selection - test</title>
+</head>
+<body>
+  <ul id="deleteChildren">
+    <li id="deleteManually">Delete me via the inspector</li>
+    <li id="deleteAutomatically">Delete me via javascript</li>
+  </ul>
+  <iframe id="deleteIframe" src="data:text/html,%3C!DOCTYPE%20html%3E%3Chtml%20lang%3D%22en%22%3E%3Cbody%3E%3Cp%20id%3D%22deleteInIframe%22%3EDelete my container iframe%3C%2Fp%3E%3C%2Fbody%3E%3C%2Fhtml%3E"></iframe>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/browser/devtools/inspector/test/browser_inspector_bug_848731_reset_selection_on_delete.js
@@ -0,0 +1,140 @@
+/* -*- Mode: Javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Test that when nodes are being deleted in the page, the current selection
+// and therefore the markup view, css rule view, computed view, font view,
+// box model view, and breadcrumbs, reset accordingly to show the right node
+
+const TEST_PAGE = "http://mochi.test:8888/browser/browser/devtools/inspector/test/browser_inspector_bug_848731_reset_selection_on_delete.html";
+
+function test() {
+  let inspector, toolbox;
+
+  waitForExplicitFinish();
+
+  // Create a tab, load test HTML, wait for load and start the tests
+  gBrowser.selectedTab = gBrowser.addTab();
+  gBrowser.selectedBrowser.addEventListener("load", function onload() {
+    gBrowser.selectedBrowser.removeEventListener("load", onload, true);
+    waitForFocus(function() {
+      openInspector((aInspector, aToolbox) => {
+        inspector = aInspector;
+        toolbox = aToolbox;
+        startTests();
+      });
+    }, content);
+  }, true);
+  content.location = TEST_PAGE;
+
+  function startTests() {
+    testManuallyDeleteSelectedNode();
+  }
+
+  function getContainerForRawNode(rawNode) {
+    let front = inspector.markup.walker.frontForRawNode(rawNode);
+    let container = inspector.markup.getContainer(front);
+    return container;
+  }
+
+  // 1 - select a node, right click, hit delete, verify that its parent is selected
+  // and that all other tools are fine
+  function testManuallyDeleteSelectedNode() {
+    // Select our div
+    let div = content.document.getElementById("deleteManually");
+    inspector.selection.setNode(div);
+    inspector.once("inspector-updated", () => {
+      is(inspector.selection.node, div);
+
+      // Get the node container in the markup view
+      let container = getContainerForRawNode(div);
+
+      // Simulate right-click
+      EventUtils.synthesizeMouse(container.tagLine, 2, 2,
+        {type: "contextmenu", button: 2}, inspector.panelWin);
+
+      // And react to the popup shown event
+      let contextMenu = inspector.panelDoc.getElementById("inspectorPopupSet");
+      contextMenu.addEventListener("popupshown", function contextShown() {
+        contextMenu.removeEventListener("popupshown", contextShown, false);
+
+        // Click on the delete sub-menu item
+        inspector.panelDoc.getElementById("node-menu-delete").click();
+
+        // Once updated, make sure eveything is in place, and move on
+        inspector.once("inspector-updated", () => {
+          let parent = content.document.getElementById("deleteChildren");
+          assertNodeSelectedAndPanelsUpdated(parent, "ul#deleteChildren");
+          testAutomaticallyDeleteSelectedNode();
+        });
+      }, false);
+    });
+  }
+
+  // 2 - select a node, delete it via javascript, verify the same things as 1
+  function testAutomaticallyDeleteSelectedNode() {
+    // Select our second div
+    let div = content.document.getElementById("deleteAutomatically");
+    inspector.selection.setNode(div);
+    inspector.once("inspector-updated", () => {
+      is(inspector.selection.node, div);
+
+      // Now remove that div via javascript
+      let parent = content.document.getElementById("deleteChildren");
+      parent.removeChild(div);
+
+      // Once updated, make sure eveything is in place, and move on
+      inspector.once("inspector-updated", () => {
+        assertNodeSelectedAndPanelsUpdated(parent, "ul#deleteChildren");
+        testDeleteSelectedNodeContainerFrame();
+      });
+    });
+  }
+
+  // 3 - select a node inside an iframe, delete the iframe via javascript, verify that the default node is selected
+  // and that all other tools are fine
+  function testDeleteSelectedNodeContainerFrame() {
+    // Select our third test element, inside the iframe
+    let iframe = content.document.getElementById("deleteIframe");
+    let div = iframe.contentDocument.getElementById("deleteInIframe");
+    inspector.selection.setNode(div);
+    inspector.once("inspector-updated", () => {
+      is(inspector.selection.node, div);
+
+      // Now remove that parent iframe via javascript
+      let parent = content.document.body;
+      parent.removeChild(iframe);
+
+      // Once updated, make sure eveything is in place, and move on
+      inspector.once("inspector-updated", () => {
+        assertNodeSelectedAndPanelsUpdated(parent, "body");
+        endTests();
+      });
+    });
+  }
+
+  function endTests() {
+    executeSoon(() => {
+      toolbox.destroy();
+      toolbox = inspector = null;
+      gBrowser.removeCurrentTab();
+      finish();
+    });
+  }
+
+  function assertNodeSelectedAndPanelsUpdated(node, crumbLabel) {
+    // Right node selected?
+    is(inspector.selection.nodeFront, getNodeFront(node),
+      "The right node is selected");
+
+    // breadcrumbs updated?
+    let breadcrumbs = inspector.panelDoc.getElementById("inspector-breadcrumbs");
+    is(breadcrumbs.querySelector("button[checked=true]").textContent, crumbLabel,
+      "The right breadcrumb is selected");
+
+    // Highlighter is shown?
+    ok(!inspector.highlighter.isHidden(), "The highlighter is shown");
+  }
+}
--- a/browser/devtools/markupview/markup-view.js
+++ b/browser/devtools/markupview/markup-view.js
@@ -23,17 +23,19 @@ const promise = require("sdk/core/promis
 
 Cu.import("resource://gre/modules/devtools/LayoutHelpers.jsm");
 Cu.import("resource://gre/modules/devtools/Templater.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 
 loader.lazyGetter(this, "DOMParser", function() {
  return Cc["@mozilla.org/xmlextras/domparser;1"].createInstance(Ci.nsIDOMParser);
 });
-loader.lazyGetter(this, "AutocompletePopup", () => require("devtools/shared/autocomplete-popup").AutocompletePopup);
+loader.lazyGetter(this, "AutocompletePopup", () => {
+  return require("devtools/shared/autocomplete-popup").AutocompletePopup
+});
 
 /**
  * Vocabulary for the purposes of this file:
  *
  * MarkupContainer - the structure that holds an editor and its
  *  immediate children in the markup panel.
  * Node - A content node.
  * object.elt - A UI element in the markup panel.
@@ -43,18 +45,17 @@ loader.lazyGetter(this, "AutocompletePop
  * The markup tree.  Manages the mapping of nodes to MarkupContainers,
  * updating based on mutations, and the undo/redo bindings.
  *
  * @param Inspector aInspector
  *        The inspector we're watching.
  * @param iframe aFrame
  *        An iframe in which the caller has kindly loaded markup-view.xhtml.
  */
-function MarkupView(aInspector, aFrame, aControllerWindow)
-{
+function MarkupView(aInspector, aFrame, aControllerWindow) {
   this._inspector = aInspector;
   this.walker = this._inspector.walker;
   this._frame = aFrame;
   this.doc = this._frame.contentDocument;
   this._elt = this.doc.querySelector("#root");
 
   this.layoutHelpers = new LayoutHelpers(this.doc.defaultView);
 
@@ -96,30 +97,28 @@ function MarkupView(aInspector, aFrame, 
   this._initPreview();
 }
 
 exports.MarkupView = MarkupView;
 
 MarkupView.prototype = {
   _selectedContainer: null,
 
-  template: function MT_template(aName, aDest, aOptions={stack: "markup-view.xhtml"})
-  {
+  template: function(aName, aDest, aOptions={stack: "markup-view.xhtml"}) {
     let node = this.doc.getElementById("template-" + aName).cloneNode(true);
     node.removeAttribute("id");
     template(node, aDest, aOptions);
     return node;
   },
 
   /**
    * Get the MarkupContainer object for a given node, or undefined if
    * none exists.
    */
-  getContainer: function MT_getContainer(aNode)
-  {
+  getContainer: function(aNode) {
     return this._containers.get(aNode);
   },
 
   _handlePrefChange: function(event, data) {
     if (data.pref == "devtools.defaultColorUnit") {
       this.update();
     }
   },
@@ -143,36 +142,34 @@ MarkupView.prototype = {
 
     // Recursively update each node starting with documentElement.
     updateChildren(documentElement);
   },
 
   /**
    * Highlight the inspector selected node.
    */
-  _onNewSelection: function MT__onNewSelection()
-  {
+  _onNewSelection: function() {
     let done = this._inspector.updating("markup-view");
     if (this._inspector.selection.isNode()) {
       this.showNode(this._inspector.selection.nodeFront, true).then(() => {
         this.markNodeAsSelected(this._inspector.selection.nodeFront);
         done();
       });
     } else {
       this.unmarkSelectedNode();
       done();
     }
   },
 
   /**
    * Create a TreeWalker to find the next/previous
    * node for selection.
    */
-  _selectionWalker: function MT__seletionWalker(aStart)
-  {
+  _selectionWalker: function(aStart) {
     let walker = this.doc.createTreeWalker(
       aStart || this._elt,
       Ci.nsIDOMNodeFilter.SHOW_ELEMENT,
       function(aElement) {
         if (aElement.container &&
             aElement.container.elt === aElement &&
             aElement.container.visible) {
           return Ci.nsIDOMNodeFilter.FILTER_ACCEPT;
@@ -182,18 +179,17 @@ MarkupView.prototype = {
     );
     walker.currentNode = this._selectedContainer.elt;
     return walker;
   },
 
   /**
    * Key handling.
    */
-  _onKeyDown: function MT__KeyDown(aEvent)
-  {
+  _onKeyDown: function(aEvent) {
     let handled = true;
 
     // Ignore keystrokes that originated in editors.
     if (aEvent.target.tagName.toLowerCase() === "input" ||
         aEvent.target.tagName.toLowerCase() === "textarea") {
       return;
     }
 
@@ -281,18 +277,17 @@ MarkupView.prototype = {
       aEvent.preventDefault();
     }
   },
 
   /**
    * Delete a node from the DOM.
    * This is an undoable action.
    */
-  deleteNode: function MC__deleteNode(aNode)
-  {
+  deleteNode: function(aNode) {
     if (aNode.isDocumentElement ||
         aNode.nodeType == Ci.nsIDOMNode.DOCUMENT_TYPE_NODE) {
       return;
     }
 
     let container = this._containers.get(aNode);
 
     // Retain the node so we can undo this...
@@ -310,17 +305,17 @@ MarkupView.prototype = {
         this.walker.insertBefore(aNode, parent, sibling);
       });
     }).then(null, console.error);
   },
 
   /**
    * If an editable item is focused, select its container.
    */
-  _onFocus: function MC__onFocus(aEvent) {
+  _onFocus: function(aEvent) {
     let parent = aEvent.target;
     while (!parent.container) {
       parent = parent.parentNode;
     }
     if (parent) {
       this.navigate(parent.container, true);
     }
   },
@@ -329,18 +324,17 @@ MarkupView.prototype = {
    * Handle a user-requested navigation to a given MarkupContainer,
    * updating the inspector's currently-selected node.
    *
    * @param MarkupContainer aContainer
    *        The container we're navigating to.
    * @param aIgnoreFocus aIgnoreFocus
    *        If falsy, keyboard focus will be moved to the container too.
    */
-  navigate: function MT__navigate(aContainer, aIgnoreFocus)
-  {
+  navigate: function(aContainer, aIgnoreFocus) {
     if (!aContainer) {
       return;
     }
 
     let node = aContainer.node;
     this.markNodeAsSelected(node);
     this._inspector.selection.setNodeFront(node, "treepanel");
     // This event won't be fired if the node is the same. But the highlighter
@@ -357,18 +351,17 @@ MarkupView.prototype = {
    * Make sure a node is included in the markup tool.
    *
    * @param DOMNode aNode
    *        The node in the content document.
    * @param boolean aFlashNode
    *        Whether the newly imported node should be flashed
    * @returns MarkupContainer The MarkupContainer object for this element.
    */
-  importNode: function MT_importNode(aNode, aFlashNode)
-  {
+  importNode: function(aNode, aFlashNode) {
     if (!aNode) {
       return null;
     }
 
     if (this._containers.has(aNode)) {
       return this._containers.get(aNode);
     }
 
@@ -390,18 +383,17 @@ MarkupView.prototype = {
 
     return container;
   },
 
 
   /**
    * Mutation observer used for included nodes.
    */
-  _mutationObserver: function MT__mutationObserver(aMutations)
-  {
+  _mutationObserver: function(aMutations) {
     for (let mutation of aMutations) {
       let type = mutation.type;
       let target = mutation.target;
 
       if (mutation.type === "documentUnload") {
         // Treat this as a childList change of the child (maybe the protocol
         // should do this).
         type = "childList";
@@ -432,18 +424,17 @@ MarkupView.prototype = {
       this._inspector.emit("markupmutation");
     });
   },
 
   /**
    * Given a list of mutations returned by the mutation observer, flash the
    * corresponding containers to attract attention.
    */
-  _flashMutatedNodes: function MT_flashMutatedNodes(aMutations)
-  {
+  _flashMutatedNodes: function(aMutations) {
     let addedOrEditedContainers = new Set();
     let removedContainers = new Set();
 
     for (let {type, target, added, removed} of aMutations) {
       let container = this._containers.get(target);
 
       if (container) {
         if (type === "attributes" || type === "characterData") {
@@ -476,18 +467,17 @@ MarkupView.prototype = {
       container.flashMutation();
     }
   },
 
   /**
    * Make sure the given node's parents are expanded and the
    * node is scrolled on to screen.
    */
-  showNode: function MT_showNode(aNode, centered)
-  {
+  showNode: function(aNode, centered) {
     let container = this.importNode(aNode);
     let parent = aNode;
     while ((parent = parent.parentNode())) {
       this.importNode(parent);
       this.expandNode(parent);
     }
 
     return this._waitForChildren().then(() => {
@@ -496,39 +486,36 @@ MarkupView.prototype = {
       // Why is this not working?
       this.layoutHelpers.scrollIntoViewIfNeeded(this._containers.get(aNode).editor.elt, centered);
     });
   },
 
   /**
    * Expand the container's children.
    */
-  _expandContainer: function MT__expandContainer(aContainer)
-  {
+  _expandContainer: function(aContainer) {
     return this._updateChildren(aContainer, {expand: true}).then(() => {
       aContainer.expanded = true;
     });
   },
 
   /**
    * Expand the node's children.
    */
-  expandNode: function MT_expandNode(aNode)
-  {
+  expandNode: function(aNode) {
     let container = this._containers.get(aNode);
     this._expandContainer(container);
   },
 
   /**
    * Expand the entire tree beneath a container.
    *
    * @param aContainer The container to expand.
    */
-  _expandAll: function MT_expandAll(aContainer)
-  {
+  _expandAll: function(aContainer) {
     return this._expandContainer(aContainer).then(() => {
       let child = aContainer.children.firstChild;
       let promises = [];
       while (child) {
         promises.push(this._expandAll(child.container));
         child = child.nextSibling;
       }
       return promise.all(promises);
@@ -536,45 +523,41 @@ MarkupView.prototype = {
   },
 
   /**
    * Expand the entire tree beneath a node.
    *
    * @param aContainer The node to expand, or null
    *        to start from the top.
    */
-  expandAll: function MT_expandAll(aNode)
-  {
+  expandAll: function(aNode) {
     aNode = aNode || this._rootNode;
     return this._expandAll(this._containers.get(aNode));
   },
 
   /**
    * Collapse the node's children.
    */
-  collapseNode: function MT_collapseNode(aNode)
-  {
+  collapseNode: function(aNode) {
     let container = this._containers.get(aNode);
     container.expanded = false;
   },
 
-  setNodeExpanded: function(aNode, aExpanded)
-  {
+  setNodeExpanded: function(aNode, aExpanded) {
     if (aExpanded) {
       this.expandNode(aNode);
     } else {
       this.collapseNode(aNode);
     }
   },
 
   /**
    * Mark the given node selected.
    */
-  markNodeAsSelected: function MT_markNodeAsSelected(aNode)
-  {
+  markNodeAsSelected: function(aNode) {
     let container = this._containers.get(aNode);
     if (this._selectedContainer === container) {
       return false;
     }
     if (this._selectedContainer) {
       this._selectedContainer.selected = false;
     }
     this._selectedContainer = container;
@@ -584,18 +567,17 @@ MarkupView.prototype = {
 
     return true;
   },
 
   /**
    * Make sure that every ancestor of the selection are updated
    * and included in the list of visible children.
    */
-  _ensureVisible: function(node)
-  {
+  _ensureVisible: function(node) {
     while (node) {
       let container = this._containers.get(node);
       let parent = node.parentNode();
       if (!container.elt.parentNode) {
         let parentContainer = this._containers.get(parent);
         parentContainer.childrenDirty = true;
         this._updateChildren(parentContainer, {expand: node});
       }
@@ -603,29 +585,27 @@ MarkupView.prototype = {
       node = parent;
     }
     return this._waitForChildren();
   },
 
   /**
    * Unmark selected node (no node selected).
    */
-  unmarkSelectedNode: function MT_unmarkSelectedNode()
-  {
+  unmarkSelectedNode: function() {
     if (this._selectedContainer) {
       this._selectedContainer.selected = false;
       this._selectedContainer = null;
     }
   },
 
   /**
    * Called when the markup panel initiates a change on a node.
    */
-  nodeChanged: function MT_nodeChanged(aNode)
-  {
+  nodeChanged: function(aNode) {
     if (aNode === this._inspector.selection.nodeFront) {
       this._inspector.change("markupview");
     }
   },
 
   /**
    * Check if the current selection is a descendent of the container.
    * if so, make sure it's among the visible set for the container,
@@ -662,18 +642,17 @@ MarkupView.prototype = {
    *
    * @param MarkupContainer aContainer
    *        The markup container whose children need updating
    * @param Object options
    *        Options are {expand:boolean,flash:boolean}
    * @return a promise that will be resolved when the children are ready
    * (which may be immediately).
    */
-  _updateChildren: function(aContainer, options)
-  {
+  _updateChildren: function(aContainer, options) {
     let expand = options && options.expand;
     let flash = options && options.flash;
 
     aContainer.hasChildren = aContainer.node.hasChildren;
 
     if (!this._queuedChildUpdates) {
       this._queuedChildUpdates = new Map();
     }
@@ -767,138 +746,143 @@ MarkupView.prototype = {
       return promise.resolve(undefined);
     }
     return promise.all([updatePromise for (updatePromise of this._queuedChildUpdates.values())]);
   },
 
   /**
    * Return a list of the children to display for this container.
    */
-  _getVisibleChildren: function MV__getVisibleChildren(aContainer, aCentered)
-  {
+  _getVisibleChildren: function(aContainer, aCentered) {
     let maxChildren = aContainer.maxChildren || this.maxChildren;
     if (maxChildren == -1) {
       maxChildren = undefined;
     }
 
     return this.walker.children(aContainer.node, {
       maxNodes: maxChildren,
       center: aCentered
     });
   },
 
   /**
    * Tear down the markup panel.
    */
-  destroy: function MT_destroy()
-  {
+  destroy: function() {
     gDevTools.off("pref-changed", this._handlePrefChange);
 
     this.undo.destroy();
     delete this.undo;
 
     this.popup.destroy();
     delete this.popup;
 
     this._frame.removeEventListener("focus", this._boundFocus, false);
     delete this._boundFocus;
 
     if (this._boundUpdatePreview) {
-      this._frame.contentWindow.removeEventListener("scroll", this._boundUpdatePreview, true);
+      this._frame.contentWindow.removeEventListener("scroll",
+        this._boundUpdatePreview, true);
       delete this._boundUpdatePreview;
     }
 
     if (this._boundResizePreview) {
-      this._frame.contentWindow.removeEventListener("resize", this._boundResizePreview, true);
-      this._frame.contentWindow.removeEventListener("overflow", this._boundResizePreview, true);
-      this._frame.contentWindow.removeEventListener("underflow", this._boundResizePreview, true);
+      this._frame.contentWindow.removeEventListener("resize",
+        this._boundResizePreview, true);
+      this._frame.contentWindow.removeEventListener("overflow",
+        this._boundResizePreview, true);
+      this._frame.contentWindow.removeEventListener("underflow",
+        this._boundResizePreview, true);
       delete this._boundResizePreview;
     }
 
-    this._frame.contentWindow.removeEventListener("keydown", this._boundKeyDown, false);
+    this._frame.contentWindow.removeEventListener("keydown",
+      this._boundKeyDown, false);
     delete this._boundKeyDown;
 
     this._inspector.selection.off("new-node-front", this._boundOnNewSelection);
     delete this._boundOnNewSelection;
 
     this.walker.off("mutations", this._boundMutationObserver)
     delete this._boundMutationObserver;
 
     delete this._elt;
 
     delete this._containers;
   },
 
   /**
    * Initialize the preview panel.
    */
-  _initPreview: function MT_initPreview()
-  {
+  _initPreview: function() {
     if (!Services.prefs.getBoolPref("devtools.inspector.markupPreview")) {
       return;
     }
 
     this._previewBar = this.doc.querySelector("#previewbar");
     this._preview = this.doc.querySelector("#preview");
     this._viewbox = this.doc.querySelector("#viewbox");
 
     this._previewBar.classList.remove("disabled");
 
     this._previewWidth = this._preview.getBoundingClientRect().width;
 
     this._boundResizePreview = this._resizePreview.bind(this);
-    this._frame.contentWindow.addEventListener("resize", this._boundResizePreview, true);
-    this._frame.contentWindow.addEventListener("overflow", this._boundResizePreview, true);
-    this._frame.contentWindow.addEventListener("underflow", this._boundResizePreview, true);
+    this._frame.contentWindow.addEventListener("resize",
+      this._boundResizePreview, true);
+    this._frame.contentWindow.addEventListener("overflow",
+      this._boundResizePreview, true);
+    this._frame.contentWindow.addEventListener("underflow",
+      this._boundResizePreview, true);
 
     this._boundUpdatePreview = this._updatePreview.bind(this);
-    this._frame.contentWindow.addEventListener("scroll", this._boundUpdatePreview, true);
+    this._frame.contentWindow.addEventListener("scroll",
+      this._boundUpdatePreview, true);
     this._updatePreview();
   },
 
-
   /**
    * Move the preview viewbox.
    */
-  _updatePreview: function MT_updatePreview()
-  {
+  _updatePreview: function() {
     let win = this._frame.contentWindow;
 
     if (win.scrollMaxY == 0) {
       this._previewBar.classList.add("disabled");
       return;
     }
 
     this._previewBar.classList.remove("disabled");
 
     let ratio = this._previewWidth / PREVIEW_AREA;
     let width = ratio * win.innerWidth;
 
     let height = ratio * (win.scrollMaxY + win.innerHeight);
     let scrollTo
     if (height >= win.innerHeight) {
       scrollTo = -(height - win.innerHeight) * (win.scrollY / win.scrollMaxY);
-      this._previewBar.setAttribute("style", "height:" + height + "px;transform:translateY(" + scrollTo + "px)");
+      this._previewBar.setAttribute("style", "height:" + height +
+        "px;transform:translateY(" + scrollTo + "px)");
     } else {
       this._previewBar.setAttribute("style", "height:100%");
     }
 
     let bgSize = ~~width + "px " + ~~height + "px";
     this._preview.setAttribute("style", "background-size:" + bgSize);
 
     let height = ~~(win.innerHeight * ratio) + "px";
     let top = ~~(win.scrollY * ratio) + "px";
-    this._viewbox.setAttribute("style", "height:" + height + ";transform: translateY(" + top + ")");
+    this._viewbox.setAttribute("style", "height:" + height +
+      ";transform: translateY(" + top + ")");
   },
 
   /**
    * Hide the preview while resizing, to avoid slowness.
    */
-  _resizePreview: function MT_resizePreview()
-  {
+  _resizePreview: function() {
     let win = this._frame.contentWindow;
     this._previewBar.classList.add("hide");
     win.clearTimeout(this._resizePreviewTimeout);
 
     win.setTimeout(function() {
       this._updatePreview();
       this._previewBar.classList.remove("hide");
     }.bind(this), 1000);
@@ -1117,22 +1101,24 @@ MarkupContainer.prototype = {
   set highlighted(aValue) {
     this.highlighter.classList.remove("flash-out");
     this._highlighted = aValue;
     if (aValue) {
       if (!this.selected) {
         this.highlighter.classList.add("theme-bg-darker");
       }
       if (this.closeTagLine) {
-        this.closeTagLine.querySelector(".highlighter").classList.add("theme-bg-darker");
+        this.closeTagLine.querySelector(".highlighter").classList.add(
+          "theme-bg-darker");
       }
     } else {
       this.highlighter.classList.remove("theme-bg-darker");
       if (this.closeTagLine) {
-        this.closeTagLine.querySelector(".highlighter").classList.remove("theme-bg-darker");
+        this.closeTagLine.querySelector(".highlighter").classList.remove(
+          "theme-bg-darker");
       }
     }
   },
 
   /**
    * True if the container is visible in the markup tree.
    */
   get visible() {
@@ -1199,49 +1185,46 @@ RootContainer.prototype = {
   hasChildren: true,
   expanded: true,
   update: function() {}
 };
 
 /**
  * Creates an editor for simple nodes.
  */
-function GenericEditor(aContainer, aNode)
-{
+function GenericEditor(aContainer, aNode) {
   this.elt = aContainer.doc.createElement("span");
   this.elt.className = "editor";
   this.elt.textContent = aNode.nodeName;
 }
 
 /**
  * Creates an editor for a DOCTYPE node.
  *
  * @param MarkupContainer aContainer The container owning this editor.
  * @param DOMNode aNode The node being edited.
  */
-function DoctypeEditor(aContainer, aNode)
-{
+function DoctypeEditor(aContainer, aNode) {
   this.elt = aContainer.doc.createElement("span");
   this.elt.className = "editor comment";
   this.elt.textContent = '<!DOCTYPE ' + aNode.name +
      (aNode.publicId ? ' PUBLIC "' +  aNode.publicId + '"': '') +
      (aNode.systemId ? ' "' + aNode.systemId + '"' : '') +
      '>';
 }
 
 /**
  * Creates a simple text editor node, used for TEXT and COMMENT
  * nodes.
  *
  * @param MarkupContainer aContainer The container owning this editor.
  * @param DOMNode aNode The node being edited.
  * @param string aTemplate The template id to use to build the editor.
  */
-function TextEditor(aContainer, aNode, aTemplate)
-{
+function TextEditor(aContainer, aNode, aTemplate) {
   this.node = aNode;
   this._selected = false;
 
   aContainer.markup.template(aTemplate, this);
 
   editableField({
     element: this.value,
     stopOnReturn: true,
@@ -1277,18 +1260,17 @@ TextEditor.prototype = {
   set selected(aValue) {
     if (aValue === this._selected) {
       return;
     }
     this._selected = aValue;
     this.update();
   },
 
-  update: function TE_update()
-  {
+  update: function() {
     if (!this.selected || !this.node.incompleteValue) {
       let text = this.node.shortValue;
       // XXX: internationalize the elliding
       if (this.node.incompleteValue) {
         text += "…";
       }
       this.value.textContent = text;
     } else {
@@ -1307,18 +1289,17 @@ TextEditor.prototype = {
 };
 
 /**
  * Creates an editor for an Element node.
  *
  * @param MarkupContainer aContainer The container owning this editor.
  * @param Element aNode The node being edited.
  */
-function ElementEditor(aContainer, aNode)
-{
+function ElementEditor(aContainer, aNode) {
   this.doc = aContainer.doc;
   this.undo = aContainer.undo;
   this.template = aContainer.markup.template.bind(aContainer.markup);
   this.container = aContainer;
   this.markup = this.container.markup;
   this.node = aNode;
 
   this.attrs = {};
@@ -1381,18 +1362,17 @@ function ElementEditor(aContainer, aNode
 
   this.update();
 }
 
 ElementEditor.prototype = {
   /**
    * Update the state of the editor from the node.
    */
-  update: function EE_update(parseColors=true)
-  {
+  update: function(parseColors=true) {
     let attrs = this.node.attributes;
     if (!attrs) {
       return;
     }
 
     // Hide all the attribute editors, they'll be re-shown if they're
     // still applicable.  Don't update attributes that are being
     // actively edited.
@@ -1415,18 +1395,17 @@ ElementEditor.prototype = {
       }
     }
   },
 
   _startModifyingAttributes: function() {
     return this.node.startModifyingAttributes();
   },
 
-  _createAttribute: function EE_createAttribute(aAttr, aBefore = null)
-  {
+  _createAttribute: function(aAttr, aBefore = null) {
     // Create the template editor, which will save some variables here.
     let data = {
       attrName: aAttr.name,
     };
     this.template("attribute", data);
     var {attr, inner, name, val} = data;
 
     // Double quotes need to be handled specially to prevent DOMParser failing.
@@ -1492,17 +1471,16 @@ ElementEditor.prototype = {
             undoMods.apply();
           })
         } catch(ex) {
           console.error(ex);
         }
       }
     });
 
-
     // Figure out where we should place the attribute.
     let before = aBefore;
     if (aAttr.name == "id") {
       before = this.attrList.firstChild;
     } else if (aAttr.name == "class") {
       let idNode = this.attrs["id"];
       before = idNode ? idNode.nextSibling : this.attrList.firstChild;
     }
@@ -1534,46 +1512,44 @@ ElementEditor.prototype = {
    * Parse a user-entered attribute string and apply the resulting
    * attributes to the node.  This operation is undoable.
    *
    * @param string aValue the user-entered value.
    * @param Element aAttrNode the attribute editor that created this
    *        set of attributes, used to place new attributes where the
    *        user put them.
    */
-  _applyAttributes: function EE__applyAttributes(aValue, aAttrNode, aDoMods, aUndoMods)
-  {
+  _applyAttributes: function(aValue, aAttrNode, aDoMods, aUndoMods) {
     let attrs = parseAttributeValues(aValue, this.doc);
     for (let attr of attrs) {
       // Create an attribute editor next to the current attribute if needed.
       this._createAttribute(attr, aAttrNode ? aAttrNode.nextSibling : null);
       this._saveAttribute(attr.name, aUndoMods);
       aDoMods.setAttribute(attr.name, attr.value);
     }
   },
 
   /**
    * Saves the current state of the given attribute into an attribute
    * modification list.
    */
-  _saveAttribute: function(aName, aUndoMods)
-  {
+  _saveAttribute: function(aName, aUndoMods) {
     let node = this.node;
     if (node.hasAttribute(aName)) {
       let oldValue = node.getAttribute(aName);
       aUndoMods.setAttribute(aName, oldValue);
     } else {
       aUndoMods.removeAttribute(aName);
     }
   },
 
   /**
    * Called when the tag name editor has is done editing.
    */
-  onTagEdit: function EE_onTagEdit(aVal, aCommit) {
+  onTagEdit: function(aVal, aCommit) {
     if (!aCommit || aVal == this.rawNode.tagName) {
       return;
     }
 
     // Create a new element with the same attributes as the
     // current element and prepare to replace the current node
     // with it.
     try {
@@ -1618,17 +1594,18 @@ ElementEditor.prototype = {
           this.markup.navigate(this.container);
         }
       });
     }).then(null, console.error);
   }
 };
 
 function nodeDocument(node) {
-  return node.ownerDocument || (node.nodeType == Ci.nsIDOMNode.DOCUMENT_NODE ? node : null);
+  return node.ownerDocument ||
+    (node.nodeType == Ci.nsIDOMNode.DOCUMENT_NODE ? node : null);
 }
 
 function truncateString(str, maxLength) {
   if (str.length <= maxLength) {
     return str;
   }
 
   return str.substring(0, Math.ceil(maxLength / 2)) +
@@ -1643,20 +1620,22 @@ function truncateString(str, maxLength) 
  * @param  {HTMLDocument} doc
  *         A document that can be used to test valid attributes.
  * @return {Array}
  *         An array of attribute names and their values.
  */
 function parseAttributeValues(attr, doc) {
   attr = attr.trim();
 
+  let parse = DOMParser.parseFromString;
+
   // Handle bad user inputs by appending a " or ' if it fails to parse without them.
-  let el = DOMParser.parseFromString("<div " + attr + "></div>", "text/html").body.childNodes[0] ||
-           DOMParser.parseFromString("<div " + attr + "\"></div>", "text/html").body.childNodes[0] ||
-           DOMParser.parseFromString("<div " + attr + "'></div>", "text/html").body.childNodes[0];
+  let el = parse("<div " + attr + "></div>", "text/html").body.childNodes[0] ||
+           parse("<div " + attr + "\"></div>", "text/html").body.childNodes[0] ||
+           parse("<div " + attr + "'></div>", "text/html").body.childNodes[0];
   let div = doc.createElement("div");
 
   let attributes = [];
   for (let attribute of el.attributes) {
     // Try to set on an element in the document, throws exception on bad input.
     // Prevents InvalidCharacterError - "String contains an invalid character".
     try {
       div.setAttribute(attribute.name, attribute.value);