Bug 817558 - [inspector] When the selection is deleted, we should select the parent, and make sure the breadcrumbs are updated, r=paul
authorGirish Sharma <scrapmachines@gmail.com>
Tue, 18 Dec 2012 02:28:40 +0530
changeset 122221 0248cb0ceeb6885e0b2b391a72199bdba32a0f7a
parent 122220 d055a17c13246bcfbc8d646e537eccefed77918f
child 122222 73bb1c39936345ec43fff094f23c6930084c005a
push idunknown
push userunknown
push dateunknown
reviewerspaul
bugs817558
milestone20.0a1
Bug 817558 - [inspector] When the selection is deleted, we should select the parent, and make sure the breadcrumbs are updated, r=paul
browser/devtools/inspector/Breadcrumbs.jsm
browser/devtools/inspector/Highlighter.jsm
browser/devtools/inspector/InspectorPanel.jsm
browser/devtools/inspector/Selection.jsm
browser/devtools/inspector/test/Makefile.in
browser/devtools/inspector/test/browser_inspector_bug_817558_delete_node.js
browser/devtools/layoutview/view.js
browser/devtools/tilt/TiltVisualizer.jsm
--- a/browser/devtools/inspector/Breadcrumbs.jsm
+++ b/browser/devtools/inspector/Breadcrumbs.jsm
@@ -78,17 +78,16 @@ HTMLBreadcrumbs.prototype = {
     }.bind(this);
 
     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", this.update);
-    this.selection.on("detached", this.update);
     this.selection.on("pseudoclass", this.updateSelectors);
     this.selection.on("attribute-changed", this.updateSelectors);
     this.update();
   },
 
   /**
    * Build a string that represents the node: tagName#id.class1.class2.
    *
@@ -291,17 +290,16 @@ HTMLBreadcrumbs.prototype = {
   {
     this.nodeHierarchy.forEach(function(crumb) {
       if (LayoutHelpers.isNodeConnected(crumb.node)) {
         DOMUtils.clearPseudoClassLocks(crumb.node);
       }
     });
 
     this.selection.off("new-node", this.update);
-    this.selection.off("detached", this.update);
     this.selection.off("pseudoclass", this.updateSelectors);
     this.selection.off("attribute-changed", this.updateSelectors);
 
     this.container.removeEventListener("underflow", this.onscrollboxreflow, false);
     this.container.removeEventListener("overflow", this.onscrollboxreflow, false);
     this.onscrollboxreflow = null;
 
     this.empty();
--- a/browser/devtools/inspector/Highlighter.jsm
+++ b/browser/devtools/inspector/Highlighter.jsm
@@ -128,17 +128,16 @@ Highlighter.prototype = {
 
     this.transitionDisabler = null;
     this.pageEventsMuter = null;
 
     this.unlock();
 
     this.selection.on("new-node", this.highlight);
     this.selection.on("new-node", this.updateInfobar);
-    this.selection.on("detached", this.highlight);
     this.selection.on("pseudoclass", this.updateInfobar);
     this.selection.on("attribute-changed", this.updateInfobar);
 
     this.onToolSelected = function(event, id) {
       if (id != "inspector") {
         this.chromeWin.clearTimeout(this.pageEventsMuter);
         this.detachMouseListeners();
         this.hide();
@@ -163,17 +162,16 @@ Highlighter.prototype = {
     this.inspectButton.removeEventListener("command", this.unlock);
     this.inspectButton = null;
 
     this.toolbox.off("select", this.onToolSelected);
     this.toolbox = null;
 
     this.selection.off("new-node", this.highlight);
     this.selection.off("new-node", this.updateInfobar);
-    this.selection.off("detached", this.highlight);
     this.selection.off("pseudoclass", this.updateInfobar);
     this.selection.off("attribute-changed", this.updateInfobar);
 
     this.detachMouseListeners();
     this.detachPageListeners();
 
     this.chromeWin.clearTimeout(this.transitionDisabler);
     this.chromeWin.clearTimeout(this.pageEventsMuter);
--- a/browser/devtools/inspector/InspectorPanel.jsm
+++ b/browser/devtools/inspector/InspectorPanel.jsm
@@ -63,16 +63,18 @@ InspectorPanel.prototype = {
     this._resetNodeMenu = this._resetNodeMenu.bind(this);
     this.nodemenu.addEventListener("popupshowing", this._setupNodeMenu, true);
     this.nodemenu.addEventListener("popuphiding", this._resetNodeMenu, true);
 
     // Create an empty selection
     this._selection = new Selection();
     this.onNewSelection = this.onNewSelection.bind(this);
     this.selection.on("new-node", this.onNewSelection);
+    this.onDetached = this.onDetached.bind(this);
+    this.selection.on("detached", this.onDetached);
 
     this.breadcrumbs = new HTMLBreadcrumbs(this);
 
     if (this.tabTarget) {
       this.browser = this.target.tab.linkedBrowser;
       this.scheduleLayoutChange = this.scheduleLayoutChange.bind(this);
       this.browser.addEventListener("resize", this.scheduleLayoutChange, true);
 
@@ -276,16 +278,25 @@ InspectorPanel.prototype = {
   /**
    * When a new node is selected.
    */
   onNewSelection: function InspectorPanel_onNewSelection() {
     this.cancelLayoutChange();
   },
 
   /**
+   * When a node is deleted, select its parent node.
+   */
+  onDetached: function InspectorPanel_onDetached(event, parentNode) {
+    this.cancelLayoutChange();
+    this.breadcrumbs.cutAfter(this.breadcrumbs.indexOf(parentNode));
+    this.selection.setNode(parentNode, "detached");
+  },
+
+  /**
    * Destroy the inspector.
    */
   destroy: function InspectorPanel__destroy() {
     if (this._destroyed) {
       return Promise.resolve(null);
     }
     this._destroyed = true;
 
@@ -310,16 +321,17 @@ InspectorPanel.prototype = {
     this.sidebar.off("select", this._setDefaultSidebar);
     this.sidebar.destroy();
     this.sidebar = null;
 
     this.nodemenu.removeEventListener("popupshowing", this._setupNodeMenu, true);
     this.nodemenu.removeEventListener("popuphiding", this._resetNodeMenu, true);
     this.breadcrumbs.destroy();
     this.selection.off("new-node", this.onNewSelection);
+    this.selection.off("detached", this.onDetached);
     this._destroyMarkup();
     this._selection.destroy();
     this._selection = null;
     this.panelWin.inspector = null;
     this.target = null;
     this.panelDoc = null;
     this.panelWin = null;
     this.breadcrumbs = null;
--- a/browser/devtools/inspector/Selection.jsm
+++ b/browser/devtools/inspector/Selection.jsm
@@ -65,31 +65,33 @@ this.Selection = function Selection(node
 }
 
 Selection.prototype = {
   _node: null,
 
   _onMutations: function(mutations) {
     let attributeChange = false;
     let detached = false;
+    let parentNode = null;
     for (let m of mutations) {
       if (!attributeChange && m.type == "attributes") {
         attributeChange = true;
       }
       if (m.type == "childList") {
         if (!detached && !this.isConnected()) {
+          parentNode = m.target;
           detached = true;
         }
       }
     }
 
     if (attributeChange)
       this.emit("attribute-changed");
     if (detached)
-      this.emit("detached");
+      this.emit("detached", parentNode);
   },
 
   _attachEvents: function SN__attachEvents() {
     if (!this.window || !this.isNode() || !this.track) {
       return;
     }
 
     if (this.track.attributes) {
--- a/browser/devtools/inspector/test/Makefile.in
+++ b/browser/devtools/inspector/test/Makefile.in
@@ -30,14 +30,15 @@ include $(topsrcdir)/config/rules.mk
 		browser_inspector_bug_566084_location_changed.js \
 		$(filter disabled-temporarily--bug-816990, browser_inspector_sidebarstate.js) \
 		browser_inspector_pseudoclass_lock.js \
 		browser_inspector_cmd_inspect.js \
 		browser_inspector_cmd_inspect.html \
 		browser_inspector_highlighter_autohide.js \
 		browser_inspector_changes.js \
 		browser_inspector_bug_674871.js \
+		browser_inspector_bug_817558_delete_node.js \
 		head.js \
 		helpers.js \
 		$(NULL)
 
 libs::	$(_BROWSER_FILES)
 	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/browser/$(relativesrcdir)
new file mode 100644
--- /dev/null
+++ b/browser/devtools/inspector/test/browser_inspector_bug_817558_delete_node.js
@@ -0,0 +1,50 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function test()
+{
+  waitForExplicitFinish();
+  //ignoreAllUncaughtExceptions();
+
+  let node, iframe, inspector;
+
+  gBrowser.selectedTab = gBrowser.addTab();
+  gBrowser.selectedBrowser.addEventListener("load", function onload() {
+    gBrowser.selectedBrowser.removeEventListener("load", onload, true);
+    waitForFocus(setupTest, content);
+  }, true);
+
+  content.location = "http://mochi.test:8888/browser/browser/devtools/inspector/test/browser_inspector_destroyselection.html";
+
+  function setupTest()
+  {
+    iframe = content.document.querySelector("iframe");
+    node = iframe.contentDocument.querySelector("span");
+    openInspector(runTests);
+  }
+
+  function runTests(aInspector)
+  {
+    inspector = aInspector;
+    inspector.selection.setNode(node);
+
+    let parentNode = node.parentNode;
+    parentNode.removeChild(node);
+
+    let tmp = {};
+    Cu.import("resource:///modules/devtools/LayoutHelpers.jsm", tmp);
+    ok(!tmp.LayoutHelpers.isNodeConnected(node), "Node considered as disconnected.");
+    executeSoon(function() {
+      is(inspector.selection.node, parentNode, "parent of selection got selected");
+
+      finishUp();
+    });
+  }
+
+  function finishUp() {
+    node = null;
+    gBrowser.removeCurrentTab();
+    finish();
+  }
+}
+
--- a/browser/devtools/layoutview/view.js
+++ b/browser/devtools/layoutview/view.js
@@ -30,17 +30,16 @@ function LayoutView(aInspector, aWindow)
 
 LayoutView.prototype = {
   init: function LV_init() {
     this.cssLogic = new CssLogic();
 
     this.update = this.update.bind(this);
     this.onNewNode = this.onNewNode.bind(this);
     this.onHighlighterLocked = this.onHighlighterLocked.bind(this);
-    this.inspector.selection.on("detached", this.onNewNode);
     this.inspector.selection.on("new-node", this.onNewNode);
     this.inspector.sidebar.on("layoutview-selected", this.onNewNode);
     if (this.inspector.highlighter) {
       this.inspector.highlighter.on("locked", this.onHighlighterLocked);
     }
 
     // Store for the different dimensions of the node.
     // 'selector' refers to the element that holds the value in view.xhtml;
@@ -96,17 +95,16 @@ LayoutView.prototype = {
   },
 
   /**
    * Destroy the nodes. Remove listeners.
    */
   destroy: function LV_destroy() {
     this.inspector.sidebar.off("layoutview-selected", this.onNewNode);
     this.inspector.selection.off("new-node", this.onNewNode);
-    this.inspector.selection.off("detached", this.onNewNode);
     if (this.browser) {
       this.browser.removeEventListener("MozAfterPaint", this.update, true);
     }
     if (this.inspector.highlighter) {
       this.inspector.highlighter.on("locked", this.onHighlighterLocked);
     }
     this.sizeHeadingLabel = null;
     this.sizeLabel = null;
--- a/browser/devtools/tilt/TiltVisualizer.jsm
+++ b/browser/devtools/tilt/TiltVisualizer.jsm
@@ -176,32 +176,30 @@ TiltVisualizer.prototype = {
 
     let target = TargetFactory.forTab(aTab);
     let toolbox = gDevTools.getToolbox(target);
     if (toolbox) {
       let panel = toolbox.getPanel("inspector");
       if (panel) {
         this.inspector = panel;
         this.inspector.selection.on("new-node", this.onNewNodeFromInspector);
-        this.inspector.selection.on("detached", this.onNewNodeFromInspector);
         this.onNewNodeFromInspector();
       }
     }
   },
 
   /**
    * Unregister inspector event listeners.
    */
   unbindInspector: function TV_unbindInspector()
   {
     this._browserTab = null;
 
     if (this.inspector) {
       this.inspector.selection.off("new-node", this.onNewNodeFromInspector);
-      this.inspector.selection.off("detached", this.onNewNodeFromInspector);
       this.inspector = null;
     }
 
     gDevTools.off("inspector-ready", this.onInspectorReady);
     gDevTools.off("toolbox-destroyed", this.onToolboxDestroyed);
 
     Services.obs.removeObserver(this.onNewNodeFromTilt,
                                 this.presenter.NOTIFICATIONS.HIGHLIGHTING);
@@ -212,31 +210,29 @@ TiltVisualizer.prototype = {
   /**
    * When a new inspector is started.
    */
   onInspectorReady: function TV_onInspectorReady(event, toolbox, panel)
   {
     if (toolbox.target.tab === this._browserTab) {
       this.inspector = panel;
       this.inspector.selection.on("new-node", this.onNewNodeFromInspector);
-      this.inspector.selection.on("detached", this.onNewNodeFromInspector);
       this.onNewNodeFromTilt();
     }
   },
 
   /**
    * When the toolbox, therefor the inspector, is closed.
    */
   onToolboxDestroyed: function TV_onToolboxDestroyed(event, tab)
   {
     if (tab === this._browserTab &&
         this.inspector) {
       if (this.inspector.selection) {
         this.inspector.selection.off("new-node", this.onNewNodeFromInspector);
-        this.inspector.selection.off("detached", this.onNewNodeFromInspector);
       }
       this.inspector = null;
     }
   },
 
   /**
    * When a new node is selected in the inspector.
    */