Web Inspector: DOM Debugger: descendant breakpoints should be able to be enabled...
authordrousso@apple.com <drousso@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 2 Jul 2019 16:15:55 +0000 (16:15 +0000)
committerdrousso@apple.com <drousso@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 2 Jul 2019 16:15:55 +0000 (16:15 +0000)
https://bugs.webkit.org/show_bug.cgi?id=199332

Reviewed by Matt Baker.

* UserInterface/Controllers/DOMDebuggerManager.js:
(WI.DOMDebuggerManager.prototype.get domBreakpoints):
(WI.DOMDebuggerManager.prototype.domBreakpointsForNode):
(WI.DOMDebuggerManager.prototype.domBreakpointsInSubtree): Added.
(WI.DOMDebuggerManager.prototype.removeDOMBreakpoint):
(WI.DOMDebuggerManager.prototype._detachDOMBreakpoint):
(WI.DOMDebuggerManager.prototype._detachBreakpointsForFrame):
(WI.DOMDebuggerManager.prototype._speculativelyResolveDOMBreakpointsForURL):
(WI.DOMDebuggerManager.prototype._resolveDOMBreakpoint):
Provide a way of getting a "summary" array of `DOMBreakpoint`s for all descendant nodes.
Rework the data structure for holding `DOMBreakpoint`s to use a `Multimap` so no duplicates
can be added (it uses a `Set` instead of an `Array`).

* UserInterface/Views/DOMTreeElement.js:
(WI.DOMTreeElement):
(WI.DOMTreeElement.prototype.get hasBreakpoint):
(WI.DOMTreeElement.prototype.set breakpointStatus):
(WI.DOMTreeElement.prototype.bindRevealDescendantBreakpointsMenuItemHandler): Added.
(WI.DOMTreeElement.prototype._subtreeBreakpointChanged): Added.
(WI.DOMTreeElement.prototype._updateBreakpointStatus):
(WI.DOMTreeElement.prototype._statusImageContextmenu):
(WI.DOMTreeElement.prototype.subtreeBreakpointCountDidChange): Deleted.
* UserInterface/Views/DOMTreeOutline.js:
(WI.DOMTreeOutline.prototype.populateContextMenu):
* UserInterface/Views/ContextMenuUtilities.js:
(WI.appendContextMenuItemsForDOMNode):
(WI.appendContextMenuItemsForDOMNodeBreakpoints):
Keep track of the actual descendant `DOMNodeTreeElement` that have breakpoints, rather than
just a count, so that the "Reveal Descendant Breakpoints" action is able to access them.
Change "Reveal Descendant Breakpoints" to reveal and select all descendant breakpoints
instead of just the first one.
Drive-by: don't remove specific (event) listener breakpoints when invoking the
          "Delete Descendant Breakpoints" action, as that's not obvious from the UI.

* UserInterface/Controllers/BreakpointPopoverController.js:
(WI.BreakpointPopoverController.prototype.appendContextMenuItems):
* UserInterface/Views/DOMBreakpointTreeElement.js:
(WI.DOMBreakpointTreeElement.prototype.populateContextMenu):
* UserInterface/Views/DOMNodeTreeElement.js:
(WI.DOMNodeTreeElement.prototype.populateContextMenu):
* UserInterface/Views/EventBreakpointTreeElement.js:
(WI.EventBreakpointTreeElement.prototype.populateContextMenu):
* UserInterface/Views/URLBreakpointTreeElement.js:
(WI.URLBreakpointTreeElement.prototype.populateContextMenu):
Remove the separator before "Delete Breakpoint" so all breakpoint actions are in the same section.

* Localizations/en.lproj/localizedStrings.js:
* UserInterface/Base/Multimap.js:
(Multimap.prototype.get size): Added.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@247053 268f45cc-cd09-0410-ab3c-d52691b4dbfc

12 files changed:
Source/WebInspectorUI/ChangeLog
Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js
Source/WebInspectorUI/UserInterface/Base/Multimap.js
Source/WebInspectorUI/UserInterface/Controllers/BreakpointPopoverController.js
Source/WebInspectorUI/UserInterface/Controllers/DOMDebuggerManager.js
Source/WebInspectorUI/UserInterface/Views/ContextMenuUtilities.js
Source/WebInspectorUI/UserInterface/Views/DOMBreakpointTreeElement.js
Source/WebInspectorUI/UserInterface/Views/DOMNodeTreeElement.js
Source/WebInspectorUI/UserInterface/Views/DOMTreeElement.js
Source/WebInspectorUI/UserInterface/Views/DOMTreeOutline.js
Source/WebInspectorUI/UserInterface/Views/EventBreakpointTreeElement.js
Source/WebInspectorUI/UserInterface/Views/URLBreakpointTreeElement.js

index ae2f193..2d21b5d 100644 (file)
@@ -1,3 +1,60 @@
+2019-07-02  Devin Rousso  <drousso@apple.com>
+
+        Web Inspector: DOM Debugger: descendant breakpoints should be able to be enabled/disabled/deleted from a collapsed parent
+        https://bugs.webkit.org/show_bug.cgi?id=199332
+
+        Reviewed by Matt Baker.
+
+        * UserInterface/Controllers/DOMDebuggerManager.js:
+        (WI.DOMDebuggerManager.prototype.get domBreakpoints):
+        (WI.DOMDebuggerManager.prototype.domBreakpointsForNode):
+        (WI.DOMDebuggerManager.prototype.domBreakpointsInSubtree): Added.
+        (WI.DOMDebuggerManager.prototype.removeDOMBreakpoint):
+        (WI.DOMDebuggerManager.prototype._detachDOMBreakpoint):
+        (WI.DOMDebuggerManager.prototype._detachBreakpointsForFrame):
+        (WI.DOMDebuggerManager.prototype._speculativelyResolveDOMBreakpointsForURL):
+        (WI.DOMDebuggerManager.prototype._resolveDOMBreakpoint):
+        Provide a way of getting a "summary" array of `DOMBreakpoint`s for all descendant nodes.
+        Rework the data structure for holding `DOMBreakpoint`s to use a `Multimap` so no duplicates
+        can be added (it uses a `Set` instead of an `Array`).
+
+        * UserInterface/Views/DOMTreeElement.js:
+        (WI.DOMTreeElement):
+        (WI.DOMTreeElement.prototype.get hasBreakpoint):
+        (WI.DOMTreeElement.prototype.set breakpointStatus):
+        (WI.DOMTreeElement.prototype.bindRevealDescendantBreakpointsMenuItemHandler): Added.
+        (WI.DOMTreeElement.prototype._subtreeBreakpointChanged): Added.
+        (WI.DOMTreeElement.prototype._updateBreakpointStatus):
+        (WI.DOMTreeElement.prototype._statusImageContextmenu):
+        (WI.DOMTreeElement.prototype.subtreeBreakpointCountDidChange): Deleted.
+        * UserInterface/Views/DOMTreeOutline.js:
+        (WI.DOMTreeOutline.prototype.populateContextMenu):
+        * UserInterface/Views/ContextMenuUtilities.js:
+        (WI.appendContextMenuItemsForDOMNode):
+        (WI.appendContextMenuItemsForDOMNodeBreakpoints):
+        Keep track of the actual descendant `DOMNodeTreeElement` that have breakpoints, rather than
+        just a count, so that the "Reveal Descendant Breakpoints" action is able to access them.
+        Change "Reveal Descendant Breakpoints" to reveal and select all descendant breakpoints
+        instead of just the first one.
+        Drive-by: don't remove specific (event) listener breakpoints when invoking the
+                  "Delete Descendant Breakpoints" action, as that's not obvious from the UI.
+
+        * UserInterface/Controllers/BreakpointPopoverController.js:
+        (WI.BreakpointPopoverController.prototype.appendContextMenuItems):
+        * UserInterface/Views/DOMBreakpointTreeElement.js:
+        (WI.DOMBreakpointTreeElement.prototype.populateContextMenu):
+        * UserInterface/Views/DOMNodeTreeElement.js:
+        (WI.DOMNodeTreeElement.prototype.populateContextMenu):
+        * UserInterface/Views/EventBreakpointTreeElement.js:
+        (WI.EventBreakpointTreeElement.prototype.populateContextMenu):
+        * UserInterface/Views/URLBreakpointTreeElement.js:
+        (WI.URLBreakpointTreeElement.prototype.populateContextMenu):
+        Remove the separator before "Delete Breakpoint" so all breakpoint actions are in the same section.
+
+        * Localizations/en.lproj/localizedStrings.js:
+        * UserInterface/Base/Multimap.js:
+        (Multimap.prototype.get size): Added.
+
 2019-07-02  Matt Baker  <mattbaker@apple.com>
 
         REGRESSION (r238563): Web Inspector: Selection is erratic when holding Up/Down on Network Table
index 23ca96c..224d449 100644 (file)
@@ -324,6 +324,7 @@ localizedStrings["Default"] = "Default";
 localizedStrings["Delete"] = "Delete";
 localizedStrings["Delete Breakpoint"] = "Delete Breakpoint";
 localizedStrings["Delete Breakpoints"] = "Delete Breakpoints";
+localizedStrings["Delete Descendant Breakpoints"] = "Delete Descendant Breakpoints";
 localizedStrings["Demo Audit"] = "Demo Audit";
 localizedStrings["Detach into separate window"] = "Detach into separate window";
 localizedStrings["Detached"] = "Detached";
@@ -333,6 +334,7 @@ localizedStrings["Diagnoses common accessibility problems affecting screen reade
 localizedStrings["Dimensions"] = "Dimensions";
 localizedStrings["Disable Breakpoint"] = "Disable Breakpoint";
 localizedStrings["Disable Breakpoints"] = "Disable Breakpoints";
+localizedStrings["Disable Descendant Breakpoints"] = "Disable Descendant Breakpoints";
 localizedStrings["Disable Event Listener"] = "Disable Event Listener";
 localizedStrings["Disable ICE Candidate Restrictions"] = "Disable ICE Candidate Restrictions";
 localizedStrings["Disable Program"] = "Disable Program";
@@ -404,6 +406,7 @@ localizedStrings["Elements"] = "Elements";
 localizedStrings["Emulate User Gesture"] = "Emulate User Gesture";
 localizedStrings["Enable Breakpoint"] = "Enable Breakpoint";
 localizedStrings["Enable Breakpoints"] = "Enable Breakpoints";
+localizedStrings["Enable Descendant Breakpoints"] = "Enable Descendant Breakpoints";
 localizedStrings["Enable Event Listener"] = "Enable Event Listener";
 localizedStrings["Enable Layers Tab"] = "Enable Layers Tab";
 localizedStrings["Enable New Tab Bar"] = "Enable New Tab Bar";
@@ -882,7 +885,7 @@ localizedStrings["Return string must be one of %s"] = "Return string must be one
 localizedStrings["Return type for anonymous function"] = "Return type for anonymous function";
 localizedStrings["Return type for function: %s"] = "Return type for function: %s";
 localizedStrings["Return value is not an object, string, or boolean"] = "Return value is not an object, string, or boolean";
-localizedStrings["Reveal Breakpoint"] = "Reveal Breakpoint";
+localizedStrings["Reveal Descendant Breakpoints"] = "Reveal Descendant Breakpoints";
 /* Open Elements tab and select this node in DOM tree */
 localizedStrings["Reveal in DOM Tree"] = "Reveal in DOM Tree";
 localizedStrings["Reveal in Debugger Tab"] = "Reveal in Debugger Tab";
index 500eac0..3d69eb0 100644 (file)
@@ -35,6 +35,11 @@ class Multimap
 
     // Public
 
+    get size()
+    {
+        return this._map.size;
+    }
+
     has(key, value)
     {
         let valueSet = this._map.get(key);
index 0230507..a23fef4 100644 (file)
@@ -88,10 +88,8 @@ WI.BreakpointPopoverController = class BreakpointPopoverController extends WI.Ob
         if (!breakpoint.autoContinue && !breakpoint.disabled && breakpoint.actions.length)
             contextMenu.appendItem(WI.UIString("Set to Automatically Continue"), toggleAutoContinue);
 
-        if (WI.debuggerManager.isBreakpointRemovable(breakpoint)) {
-            contextMenu.appendSeparator();
+        if (WI.debuggerManager.isBreakpointRemovable(breakpoint))
             contextMenu.appendItem(WI.UIString("Delete Breakpoint"), removeBreakpoint);
-        }
 
         if (breakpoint._sourceCodeLocation.hasMappedLocation()) {
             contextMenu.appendSeparator();
index 69ce6d1..dd88a63 100644 (file)
@@ -146,10 +146,8 @@ WI.DOMDebuggerManager = class DOMDebuggerManager extends WI.Object
         while (frames.length) {
             let frame = frames.shift();
             let domBreakpointNodeIdentifierMap = this._domBreakpointFrameIdentifierMap.get(frame.id);
-            if (domBreakpointNodeIdentifierMap) {
-                for (let breakpoints of domBreakpointNodeIdentifierMap.values())
-                    resolvedBreakpoints = resolvedBreakpoints.concat(breakpoints);
-            }
+            if (domBreakpointNodeIdentifierMap)
+                resolvedBreakpoints = resolvedBreakpoints.concat(Array.from(domBreakpointNodeIdentifierMap.values()));
 
             frames.push(...frame.childFrameCollection);
         }
@@ -178,7 +176,26 @@ WI.DOMDebuggerManager = class DOMDebuggerManager extends WI.Object
             return [];
 
         let breakpoints = domBreakpointNodeIdentifierMap.get(node.id);
-        return breakpoints ? breakpoints.slice() : [];
+        return breakpoints ? Array.from(breakpoints) : [];
+    }
+
+    domBreakpointsInSubtree(node)
+    {
+        console.assert(node instanceof WI.DOMNode);
+
+        let breakpoints = [];
+
+        if (node.children) {
+            let children = Array.from(node.children);
+            while (children.length) {
+                let child = children.pop();
+                if (child.children)
+                    children = children.concat(child.children);
+                breakpoints = breakpoints.concat(this.domBreakpointsForNode(child));
+            }
+        }
+
+        return breakpoints;
     }
 
     addDOMBreakpoint(breakpoint)
@@ -215,11 +232,6 @@ WI.DOMDebuggerManager = class DOMDebuggerManager extends WI.Object
             return;
         }
 
-        let nodeIdentifier = breakpoint.domNodeIdentifier;
-        console.assert(nodeIdentifier, "Cannot remove unresolved DOM breakpoint.");
-        if (!nodeIdentifier)
-            return;
-
         this._detachDOMBreakpoint(breakpoint);
 
         this._domBreakpointURLMap.delete(breakpoint.url);
@@ -227,7 +239,7 @@ WI.DOMDebuggerManager = class DOMDebuggerManager extends WI.Object
         if (!breakpoint.disabled) {
             // We should get the target associated with the nodeIdentifier of this breakpoint.
             let target = WI.assumingMainTarget();
-            target.DOMDebuggerAgent.removeDOMBreakpoint(nodeIdentifier, breakpoint.type);
+            target.DOMDebuggerAgent.removeDOMBreakpoint(breakpoint.domNodeIdentifier, breakpoint.type);
         }
 
         this.dispatchEventToListeners(WI.DOMDebuggerManager.Event.DOMBreakpointRemoved, {breakpoint});
@@ -406,17 +418,7 @@ WI.DOMDebuggerManager = class DOMDebuggerManager extends WI.Object
         if (!domBreakpointNodeIdentifierMap)
             return;
 
-        let breakpoints = domBreakpointNodeIdentifierMap.get(nodeIdentifier);
-        console.assert(breakpoints, "Missing DOM breakpoints for node.", node);
-        if (!breakpoints)
-            return;
-
-        breakpoints.remove(breakpoint, true);
-
-        if (breakpoints.length)
-            return;
-
-        domBreakpointNodeIdentifierMap.delete(nodeIdentifier);
+        domBreakpointNodeIdentifierMap.delete(nodeIdentifier, breakpoint);
 
         if (!domBreakpointNodeIdentifierMap.size)
             this._domBreakpointFrameIdentifierMap.delete(frameIdentifier);
@@ -430,10 +432,8 @@ WI.DOMDebuggerManager = class DOMDebuggerManager extends WI.Object
 
         this._domBreakpointFrameIdentifierMap.delete(frame.id);
 
-        for (let breakpoints of domBreakpointNodeIdentifierMap.values()) {
-            for (let breakpoint of breakpoints)
-                breakpoint.domNodeIdentifier = null;
-        }
+        for (let breakpoint of domBreakpointNodeIdentifierMap.values())
+            breakpoint.domNodeIdentifier = null;
     }
 
     _speculativelyResolveDOMBreakpointsForURL(url)
@@ -447,6 +447,14 @@ WI.DOMDebuggerManager = class DOMDebuggerManager extends WI.Object
                 continue;
 
             WI.domManager.pushNodeByPathToFrontend(breakpoint.path, (nodeIdentifier) => {
+                if (breakpoint.domNodeIdentifier) {
+                    // This breakpoint may have been resolved by a node being inserted before this
+                    // callback is invoked.  If so, the `nodeIdentifier` should match, so don't try
+                    // to resolve it again as it would've already been resolved.
+                    console.assert(breakpoint.domNodeIdentifier === nodeIdentifier);
+                    return;
+                }
+
                 if (nodeIdentifier)
                     this._resolveDOMBreakpoint(breakpoint, nodeIdentifier);
             });
@@ -463,15 +471,11 @@ WI.DOMDebuggerManager = class DOMDebuggerManager extends WI.Object
         let frameIdentifier = node.frame.id;
         let domBreakpointNodeIdentifierMap = this._domBreakpointFrameIdentifierMap.get(frameIdentifier);
         if (!domBreakpointNodeIdentifierMap) {
-            domBreakpointNodeIdentifierMap = new Map;
+            domBreakpointNodeIdentifierMap = new Multimap;
             this._domBreakpointFrameIdentifierMap.set(frameIdentifier, domBreakpointNodeIdentifierMap);
         }
 
-        let breakpoints = domBreakpointNodeIdentifierMap.get(nodeIdentifier);
-        if (breakpoints)
-            breakpoints.push(breakpoint);
-        else
-            domBreakpointNodeIdentifierMap.set(nodeIdentifier, [breakpoint]);
+        domBreakpointNodeIdentifierMap.add(nodeIdentifier, breakpoint);
 
         breakpoint.domNodeIdentifier = nodeIdentifier;
 
index 3e395c5..5c27e2b 100644 (file)
@@ -224,7 +224,7 @@ WI.appendContextMenuItemsForDOMNode = function(contextMenu, domNode, options = {
     if (WI.domDebuggerManager.supported && isElement && !domNode.isPseudoElement() && attached) {
         contextMenu.appendSeparator();
 
-        WI.appendContextMenuItemsForDOMNodeBreakpoints(contextMenu, domNode);
+        WI.appendContextMenuItemsForDOMNodeBreakpoints(contextMenu, domNode, options);
     }
 
     contextMenu.appendSeparator();
@@ -284,43 +284,64 @@ WI.appendContextMenuItemsForDOMNode = function(contextMenu, domNode, options = {
     contextMenu.appendSeparator();
 };
 
-WI.appendContextMenuItemsForDOMNodeBreakpoints = function(contextMenu, domNode, {allowEditing} = {})
+WI.appendContextMenuItemsForDOMNodeBreakpoints = function(contextMenu, domNode, options = {})
 {
     if (contextMenu.__domBreakpointItemsAdded)
         return;
 
     contextMenu.__domBreakpointItemsAdded = true;
 
-    let subMenu = contextMenu.appendSubMenuItem(WI.UIString("Break on"));
-
     let breakpoints = WI.domDebuggerManager.domBreakpointsForNode(domNode);
-    let keyValuePairs = breakpoints.map((breakpoint) => [breakpoint.type, breakpoint]);
-    let breakpointsByType = new Map(keyValuePairs);
+
+    contextMenu.appendSeparator();
+
+    let subMenu = contextMenu.appendSubMenuItem(WI.UIString("Break on"));
 
     for (let type of Object.values(WI.DOMBreakpoint.Type)) {
         let label = WI.DOMBreakpointTreeElement.displayNameForType(type);
-        let breakpoint = breakpointsByType.get(type);
+        let breakpoint = breakpoints.find((breakpoint) => breakpoint.type === type);
 
         subMenu.appendCheckboxItem(label, function() {
             if (breakpoint)
                 WI.domDebuggerManager.removeDOMBreakpoint(breakpoint);
             else
                 WI.domDebuggerManager.addDOMBreakpoint(new WI.DOMBreakpoint(domNode, type));
-        }, !!breakpoint, false);
+        }, !!breakpoint);
     }
 
-    if (allowEditing) {
-        contextMenu.appendSeparator();
+    contextMenu.appendSeparator();
 
+    if (breakpoints.length) {
         let shouldEnable = breakpoints.some((breakpoint) => breakpoint.disabled);
-        let label = shouldEnable ? WI.UIString("Enable Breakpoints") : WI.UIString("Disable Breakpoints");
-        contextMenu.appendItem(label, () => {
-            breakpoints.forEach((breakpoint) => breakpoint.disabled = !shouldEnable);
+        contextMenu.appendItem(shouldEnable ? WI.UIString("Enable Breakpoint") : WI.UIString("Disable Breakpoint"), () => {
+            for (let breakpoint of breakpoints)
+                breakpoint.disabled = !shouldEnable;
+        });
+
+        contextMenu.appendItem(WI.UIString("Delete Breakpoint"), () => {
+            for (let breakpoint of breakpoints)
+                WI.domDebuggerManager.removeDOMBreakpoint(breakpoint);
+        });
+
+        contextMenu.appendSeparator();
+    }
+
+    let subtreeBreakpoints = WI.domDebuggerManager.domBreakpointsInSubtree(domNode);
+    if (subtreeBreakpoints.length) {
+        if (options.revealDescendantBreakpointsMenuItemHandler)
+            contextMenu.appendItem(WI.UIString("Reveal Descendant Breakpoints"), options.revealDescendantBreakpointsMenuItemHandler);
+
+        let subtreeShouldEnable = subtreeBreakpoints.some((breakpoint) => breakpoint.disabled);
+        contextMenu.appendItem(subtreeShouldEnable ? WI.UIString("Enable Descendant Breakpoints") : WI.UIString("Disable Descendant Breakpoints"), () => {
+            for (let subtreeBreakpoint of subtreeBreakpoints)
+                subtreeBreakpoint.disabled = !subtreeShouldEnable;
         });
 
-        contextMenu.appendItem(WI.UIString("Delete Breakpoints"), function() {
-            WI.domDebuggerManager.removeDOMBreakpointsForNode(domNode);
-            WI.domManager.removeEventListenerBreakpointsForNode(domNode);
+        contextMenu.appendItem(WI.UIString("Delete Descendant Breakpoints"), () => {
+            for (let subtreeBreakpoint of subtreeBreakpoints)
+                WI.domDebuggerManager.removeDOMBreakpoint(subtreeBreakpoint);
         });
+
+        contextMenu.appendSeparator();
     }
 };
index 991d716..c0cc5a1 100644 (file)
@@ -126,7 +126,6 @@ WI.DOMBreakpointTreeElement = class DOMBreakpointTreeElement extends WI.GeneralT
         let label = breakpoint.disabled ? WI.UIString("Enable Breakpoint") : WI.UIString("Disable Breakpoint");
         contextMenu.appendItem(label, this._toggleBreakpoint.bind(this));
 
-        contextMenu.appendSeparator();
         contextMenu.appendItem(WI.UIString("Delete Breakpoint"), function() {
             WI.domDebuggerManager.removeDOMBreakpoint(breakpoint);
         });
index adaff3f..bf0d979 100644 (file)
@@ -55,9 +55,7 @@ WI.DOMNodeTreeElement = class DOMNodeTreeElement extends WI.GeneralTreeElement
     {
         contextMenu.appendSeparator();
 
-        WI.appendContextMenuItemsForDOMNodeBreakpoints(contextMenu, this.representedObject, {
-            allowEditing: true,
-        });
+        WI.appendContextMenuItemsForDOMNodeBreakpoints(contextMenu, this.representedObject);
 
         contextMenu.appendSeparator();
 
index 9b1b3b6..5f20a68 100644 (file)
@@ -45,7 +45,7 @@ WI.DOMTreeElement = class DOMTreeElement extends WI.TreeElement
         this._animatingHighlight = false;
         this._shouldHighlightAfterReveal = false;
         this._boundHighlightAnimationEnd = this._highlightAnimationEnd.bind(this);
-        this._subtreeBreakpointCount = 0;
+        this._subtreeBreakpointTreeElements = null;
 
         this._showGoToArrow = false;
         this._highlightedAttributes = new Set;
@@ -76,7 +76,7 @@ WI.DOMTreeElement = class DOMTreeElement extends WI.TreeElement
 
     get hasBreakpoint()
     {
-        return this._breakpointStatus !== WI.DOMTreeElement.BreakpointStatus.None || this._subtreeBreakpointCount > 0;
+        return this._breakpointStatus !== WI.DOMTreeElement.BreakpointStatus.None || (this._subtreeBreakpointTreeElements && this._subtreeBreakpointTreeElements.size);
     }
 
     get breakpointStatus()
@@ -103,11 +103,23 @@ WI.DOMTreeElement = class DOMTreeElement extends WI.TreeElement
 
         let parentElement = this.parent;
         while (parentElement && !parentElement.root) {
-            parentElement.subtreeBreakpointCountDidChange(increment);
+            parentElement._subtreeBreakpointChanged(this);
             parentElement = parentElement.parent;
         }
     }
 
+    bindRevealDescendantBreakpointsMenuItemHandler()
+    {
+        if (!this._subtreeBreakpointTreeElements || !this._subtreeBreakpointTreeElements.size)
+            return null;
+
+        let subtreeBreakpointTreeElements = Array.from(this._subtreeBreakpointTreeElements);
+        return () => {
+            for (let subtreeBreakpointTreeElement of subtreeBreakpointTreeElements)
+                subtreeBreakpointTreeElement.reveal();
+        };
+    }
+
     get closeTagTreeElement() { return this._closeTagTreeElement; }
 
     revealAndHighlight()
@@ -119,12 +131,6 @@ WI.DOMTreeElement = class DOMTreeElement extends WI.TreeElement
         this.reveal();
     }
 
-    subtreeBreakpointCountDidChange(increment)
-    {
-        this._subtreeBreakpointCount += increment;
-        this._updateBreakpointStatus();
-    }
-
     isCloseTag()
     {
         return this._elementCloseTag;
@@ -1872,6 +1878,21 @@ WI.DOMTreeElement = class DOMTreeElement extends WI.TreeElement
             event.preventDefault();
     }
 
+    _subtreeBreakpointChanged(treeElement)
+    {
+        if (treeElement.hasBreakpoint) {
+            if (!this._subtreeBreakpointTreeElements)
+                this._subtreeBreakpointTreeElements = new Set;
+            this._subtreeBreakpointTreeElements.add(treeElement);
+        } else {
+            this._subtreeBreakpointTreeElements.delete(treeElement);
+            if (!this._subtreeBreakpointTreeElements.size)
+                this._subtreeBreakpointTreeElements = null;
+        }
+
+        this._updateBreakpointStatus();
+    }
+
     _updateBreakpointStatus()
     {
         let listItemElement = this.listItemElement;
@@ -1879,7 +1900,7 @@ WI.DOMTreeElement = class DOMTreeElement extends WI.TreeElement
             return;
 
         let hasBreakpoint = this._breakpointStatus !== WI.DOMTreeElement.BreakpointStatus.None;
-        let hasSubtreeBreakpoints = !!this._subtreeBreakpointCount;
+        let hasSubtreeBreakpoints = this._subtreeBreakpointTreeElements && this._subtreeBreakpointTreeElements.size;
 
         if (!hasBreakpoint && !hasSubtreeBreakpoints) {
             if (this._statusImageElement)
@@ -1921,26 +1942,13 @@ WI.DOMTreeElement = class DOMTreeElement extends WI.TreeElement
 
     _statusImageContextmenu(event)
     {
-        let hasBreakpoint = this._breakpointStatus !== WI.DOMTreeElement.BreakpointStatus.None;
-        let hasSubtreeBreakpoints = !!this._subtreeBreakpointCount;
-        if (!hasBreakpoint && !hasSubtreeBreakpoints)
+        if (!this.hasBreakpoint)
             return;
 
         let contextMenu = WI.ContextMenu.createFromEvent(event);
-        if (hasBreakpoint) {
-            WI.appendContextMenuItemsForDOMNodeBreakpoints(contextMenu, this.representedObject, {
-                allowEditing: true,
-            });
-            return;
-        }
-
-        contextMenu.appendItem(WI.UIString("Reveal Breakpoint"), () => {
-            let breakpointTreeElement = this.selfOrDescendant((treeElement) => treeElement.breakpointStatus && treeElement.breakpointStatus !== WI.DOMTreeElement.BreakpointStatus.None);
-            console.assert(breakpointTreeElement, "Missing breakpoint descendant.", this);
-            if (!breakpointTreeElement)
-                return;
 
-            breakpointTreeElement.revealAndHighlight();
+        WI.appendContextMenuItemsForDOMNodeBreakpoints(contextMenu, this.representedObject, {
+            revealDescendantBreakpointsMenuItemHandler: this.bindRevealDescendantBreakpointsMenuItemHandler(),
         });
     }
 
index 404075d..0ddb0d9 100644 (file)
@@ -289,6 +289,10 @@ WI.DOMTreeOutline = class DOMTreeOutline extends WI.TreeOutline
             excludeRevealElement: this._excludeRevealElementContextMenu,
             copySubMenu: subMenus.copy,
         };
+
+        if (treeElement.bindRevealDescendantBreakpointsMenuItemHandler)
+            options.revealDescendantBreakpointsMenuItemHandler = treeElement.bindRevealDescendantBreakpointsMenuItemHandler();
+
         WI.appendContextMenuItemsForDOMNode(contextMenu, treeElement.representedObject, options);
 
         super.populateContextMenu(contextMenu, event, treeElement);
index 04ddda2..726803d 100644 (file)
@@ -114,8 +114,6 @@ WI.EventBreakpointTreeElement = class EventBreakpointTreeElement extends WI.Gene
         let label = breakpoint.disabled ? WI.UIString("Enable Breakpoint") : WI.UIString("Disable Breakpoint");
         contextMenu.appendItem(label, this._toggleBreakpoint.bind(this));
 
-        contextMenu.appendSeparator();
-
         contextMenu.appendItem(WI.UIString("Delete Breakpoint"), () => {
             if (breakpoint.eventListener)
                 WI.domManager.removeBreakpointForEventListener(breakpoint.eventListener);
index 05b1808..5413c32 100644 (file)
@@ -114,8 +114,6 @@ WI.URLBreakpointTreeElement = class URLBreakpointTreeElement extends WI.GeneralT
         let label = breakpoint.disabled ? WI.UIString("Enable Breakpoint") : WI.UIString("Disable Breakpoint");
         contextMenu.appendItem(label, this._toggleBreakpoint.bind(this));
 
-        contextMenu.appendSeparator();
-
         contextMenu.appendItem(WI.UIString("Delete Breakpoint"), () => {
             WI.domDebuggerManager.removeURLBreakpoint(breakpoint);
         });