Diff
Modified: trunk/Source/WebInspectorUI/ChangeLog (238598 => 238599)
--- trunk/Source/WebInspectorUI/ChangeLog 2018-11-28 02:16:25 UTC (rev 238598)
+++ trunk/Source/WebInspectorUI/ChangeLog 2018-11-28 02:55:42 UTC (rev 238599)
@@ -1,3 +1,68 @@
+2018-11-27 Matt Baker <[email protected]>
+
+ Web Inspector: TreeOutline should re-use multiple-selection logic from Table
+ https://bugs.webkit.org/show_bug.cgi?id=191483
+ <rdar://problem/45953305>
+
+ Reviewed by Devin Rousso.
+
+ Update TreeOutline to use SelectionController. Adopting SelectionController
+ in TreeOutline is not as straightforward as it was in Table. Selected items
+ are tracked by index, and TreeElement lacks an explicit index. As a consequence
+ TreeElement indexes are calcualted as needed and cached. The cache is cleared
+ whenever an element is added or removed.
+
+ * UserInterface/Controllers/SelectionController.js:
+ (WI.SelectionController.prototype.didInsertItem):
+ (WI.SelectionController.prototype.didRemoveItem):
+ (WI.SelectionController.prototype.handleKeyDown):
+ Drive-by syntax error fix.
+ (WI.SelectionController.prototype._adjustIndexesAfter):
+ (WI.SelectionController):
+
+ * UserInterface/Views/DOMTreeElement.js:
+ (WI.DOMTreeElement.prototype.canSelectOnMouseDown):
+ (WI.DOMTreeElement.prototype.selectOnMouseDown): Deleted.
+
+ * UserInterface/Views/DOMTreeOutline.js:
+ (WI.DOMTreeOutline.prototype._onmousedown):
+ Item selection is now handled by SelectionController.
+
+ * UserInterface/Views/ShaderProgramTreeElement.js:
+ (WI.ShaderProgramTreeElement.prototype.canSelectOnMouseDown):
+ (WI.ShaderProgramTreeElement.prototype.selectOnMouseDown): Deleted.
+
+ * UserInterface/Views/TreeElement.js:
+ (WI.TreeElement.prototype.canSelectOnMouseDown):
+ (WI.TreeElement.prototype._attach):
+ (WI.TreeElement.prototype.select):
+ (WI.TreeElement.prototype.deselect):
+ Route item selection through the parent TreeOutline, in order to go though
+ the TreeOutline's SelectionController.
+
+ (WI.TreeElement.treeElementMouseDown): Deleted.
+ Moved handler to TreeOutline, which owns the SelectionController that
+ needs to respond to mouse events.
+
+ * UserInterface/Views/TreeOutline.js:
+ (WI.TreeOutline):
+ (WI.TreeOutline.prototype.get allowsMultipleSelection):
+ (WI.TreeOutline.prototype.set allowsMultipleSelection):
+ (WI.TreeOutline.prototype.get selectedTreeElement):
+ (WI.TreeOutline.prototype.set selectedTreeElement):
+ (WI.TreeOutline.prototype.insertChild):
+ (WI.TreeOutline.prototype.removeChildAtIndex):
+ (WI.TreeOutline.prototype._rememberTreeElement):
+ (WI.TreeOutline.prototype._forgetTreeElement):
+ (WI.TreeOutline.prototype._treeKeyDown):
+ (WI.TreeOutline.prototype.selectionControllerNumberOfItems):
+ (WI.TreeOutline.prototype.selectionControllerSelectionDidChange):
+ (WI.TreeOutline.prototype.selectionControllerNextSelectableIndex):
+ (WI.TreeOutline.prototype.selectionControllerPreviousSelectableIndex):
+ (WI.TreeOutline.prototype.selectTreeElementInternal):
+ (WI.TreeOutline._generateStyleRulesIfNeeded._indexOfTreeElement.previousElement):
+ (WI.TreeOutline._generateStyleRulesIfNeeded):
+
2018-11-27 Nikita Vasilyev <[email protected]>
Web Inspector: Experimental Computed panel is unreadable in Dark Mode
Modified: trunk/Source/WebInspectorUI/UserInterface/Controllers/SelectionController.js (238598 => 238599)
--- trunk/Source/WebInspectorUI/UserInterface/Controllers/SelectionController.js 2018-11-28 02:16:25 UTC (rev 238598)
+++ trunk/Source/WebInspectorUI/UserInterface/Controllers/SelectionController.js 2018-11-28 02:55:42 UTC (rev 238599)
@@ -192,15 +192,17 @@
this._selectedIndexes.clear();
}
+ didInsertItem(index)
+ {
+ this._adjustIndexesAfter(index - 1, 1);
+ }
+
didRemoveItem(index)
{
if (this.hasSelectedItem(index))
this.deselectItem(index);
- while (index = this._selectedIndexes.indexGreaterThan(index)) {
- this._selectedIndexes.delete(index);
- this._selectedIndexes.add(index - 1);
- }
+ this._adjustIndexesAfter(index, -1);
}
handleKeyDown(event)
@@ -208,7 +210,7 @@
if (!this.numberOfItems)
return false;
- if (event.key === "a" && event.commandOrControlKey()) {
+ if (event.key === "a" && event.commandOrControlKey) {
this.selectAll();
return true;
}
@@ -372,4 +374,12 @@
let selectedItems = indexes.difference(oldSelectedIndexes);
this._delegate.selectionControllerSelectionDidChange(this, deselectedItems, selectedItems);
}
+
+ _adjustIndexesAfter(index, delta)
+ {
+ while (index = this._selectedIndexes.indexGreaterThan(index)) {
+ this._selectedIndexes.delete(index);
+ this._selectedIndexes.add(index + delta);
+ }
+ }
};
Modified: trunk/Source/WebInspectorUI/UserInterface/Views/DOMTreeElement.js (238598 => 238599)
--- trunk/Source/WebInspectorUI/UserInterface/Views/DOMTreeElement.js 2018-11-28 02:16:25 UTC (rev 238598)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/DOMTreeElement.js 2018-11-28 02:55:42 UTC (rev 238599)
@@ -669,16 +669,16 @@
return true;
}
- selectOnMouseDown(event)
+ canSelectOnMouseDown(event)
{
- super.selectOnMouseDown(event);
-
if (this._editing)
- return;
+ return false;
// Prevent selecting the nearest word on double click.
if (event.detail >= 2)
- event.preventDefault();
+ return false;
+
+ return true;
}
ondblclick(event)
Modified: trunk/Source/WebInspectorUI/UserInterface/Views/DOMTreeOutline.js (238598 => 238599)
--- trunk/Source/WebInspectorUI/UserInterface/Views/DOMTreeOutline.js 2018-11-28 02:16:25 UTC (rev 238598)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/DOMTreeOutline.js 2018-11-28 02:55:42 UTC (rev 238599)
@@ -298,8 +298,6 @@
event.preventDefault();
return;
}
-
- element.select();
}
_onmousemove(event)
Modified: trunk/Source/WebInspectorUI/UserInterface/Views/ShaderProgramTreeElement.js (238598 => 238599)
--- trunk/Source/WebInspectorUI/UserInterface/Views/ShaderProgramTreeElement.js 2018-11-28 02:16:25 UTC (rev 238598)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/ShaderProgramTreeElement.js 2018-11-28 02:55:42 UTC (rev 238599)
@@ -48,12 +48,9 @@
this.element.addEventListener("mouseout", this._handleMouseOut.bind(this));
}
- selectOnMouseDown(event)
+ canSelectOnMouseDown(event)
{
- if (this._statusElement.contains(event.target))
- return;
-
- super.selectOnMouseDown(event);
+ return !this._statusElement.contains(event.target);
}
// Private
Modified: trunk/Source/WebInspectorUI/UserInterface/Views/TreeElement.js (238598 => 238599)
--- trunk/Source/WebInspectorUI/UserInterface/Views/TreeElement.js 2018-11-28 02:16:25 UTC (rev 238598)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/TreeElement.js 2018-11-28 02:55:42 UTC (rev 238599)
@@ -187,6 +187,12 @@
this.expand();
}
+ canSelectOnMouseDown(event)
+ {
+ // Overridden by subclasses if needed.
+ return true;
+ }
+
_fireDidChange()
{
if (this.treeOutline)
@@ -239,7 +245,6 @@
if (this.selected)
this._listItemNode.classList.add("selected");
- this._listItemNode.addEventListener("mousedown", WI.TreeElement.treeElementMouseDown);
this._listItemNode.addEventListener("click", WI.TreeElement.treeElementToggled);
this._listItemNode.addEventListener("dblclick", WI.TreeElement.treeElementDoubleClicked);
@@ -279,20 +284,6 @@
this.treeOutline.soon.updateVirtualizedElements();
}
- static treeElementMouseDown(event)
- {
- var element = event.currentTarget;
- if (!element || !element.treeElement || !element.treeElement.selectable)
- return;
-
- if (element.treeElement.isEventWithinDisclosureTriangle(event)) {
- event.preventDefault();
- return;
- }
-
- element.treeElement.selectOnMouseDown(event);
- }
-
static treeElementToggled(event)
{
let element = event.currentTarget;
@@ -526,30 +517,12 @@
treeOutline.processingSelectionChange = true;
- // Prevent dispatching a SelectionDidChange event for the deselected element if
- // it will be dispatched for the selected element.
- if (!suppressOnSelect)
- suppressOnDeselect = true;
+ this.selected = true;
+ treeOutline.selectTreeElementInternal(this, suppressOnSelect, selectedByUser);
- let deselectedElement = treeOutline.selectedTreeElement;
- if (!this.selected) {
- if (treeOutline.selectedTreeElement)
- treeOutline.selectedTreeElement.deselect(suppressOnDeselect);
+ if (!suppressOnSelect && this.onselect)
+ this.onselect(this, selectedByUser);
- this.selected = true;
- treeOutline.selectedTreeElement = this;
-
- if (this._listItemNode)
- this._listItemNode.classList.add("selected");
- }
-
- if (!suppressOnSelect) {
- if (this.onselect)
- this.onselect(this, selectedByUser);
-
- treeOutline.dispatchEventToListeners(WI.TreeOutline.Event.SelectionDidChange, {selectedByUser});
- }
-
treeOutline.processingSelectionChange = false;
let treeOutlineGroup = WI.TreeOutlineGroup.groupForTreeOutline(treeOutline);
@@ -571,18 +544,11 @@
return false;
this.selected = false;
- this.treeOutline.selectedTreeElement = null;
+ this.treeOutline.selectTreeElementInternal(null, suppressOnDeselect);
- if (this._listItemNode)
- this._listItemNode.classList.remove("selected");
+ if (!suppressOnDeselect && this.ondeselect)
+ this.ondeselect(this);
- if (!suppressOnDeselect) {
- if (this.ondeselect)
- this.ondeselect(this);
-
- this.treeOutline.dispatchEventToListeners(WI.TreeOutline.Event.SelectionDidChange);
- }
-
return true;
}
Modified: trunk/Source/WebInspectorUI/UserInterface/Views/TreeOutline.js (238598 => 238599)
--- trunk/Source/WebInspectorUI/UserInterface/Views/TreeOutline.js 2018-11-28 02:16:25 UTC (rev 238598)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/TreeOutline.js 2018-11-28 02:55:42 UTC (rev 238599)
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2007, 2013, 2015 Apple Inc. All rights reserved.
+ * Copyright (C) 2007-2018 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
@@ -37,7 +37,6 @@
this.element.addEventListener("contextmenu", this._handleContextmenu.bind(this));
this.children = [];
- this.selectedTreeElement = null;
this._childrenListNode = this.element;
this._childrenListNode.removeChildren();
this._knownTreeElements = [];
@@ -55,6 +54,14 @@
this._customIndent = false;
this._selectable = selectable;
+ this._cachedNumberOfDescendents = 0;
+ this._selectionController = new WI.SelectionController(this);
+ this._treeElementIndexCache = new Map;
+
+ this._itemWasSelectedByUser = false;
+ this._processingSelectionControllerSelectionDidChange = false;
+ this._suppressNextSelectionDidChangeEvent = false;
+
this._virtualizedVisibleTreeElements = null;
this._virtualizedAttachedTreeElements = null;
this._virtualizedScrollContainer = null;
@@ -64,6 +71,7 @@
this._childrenListNode.tabIndex = 0;
this._childrenListNode.addEventListener("keydown", this._treeKeyDown.bind(this), true);
+ this._childrenListNode.addEventListener("mousedown", this._handleMouseDown.bind(this));
WI.TreeOutline._generateStyleRulesIfNeeded();
@@ -73,6 +81,28 @@
// Public
+ get allowsMultipleSelection()
+ {
+ return this._selectionController.allowsMultipleSelection;
+ }
+
+ set allowsMultipleSelection(flag)
+ {
+ this._selectionController.allowsMultipleSelection = flag;
+ }
+
+ get selectedTreeElement()
+ {
+ let selectedIndex = this._selectionController.lastSelectedItem;
+ return this._treeElementAtIndex(selectedIndex) || null;
+ }
+
+ set selectedTreeElement(treeElement)
+ {
+ let index = this._indexOfTreeElement(treeElement);
+ this._selectionController.selectItem(index);
+ }
+
get hidden()
{
return this._hidden;
@@ -242,6 +272,9 @@
if (isFirstChild && this.expanded)
this.expand();
+
+ let insertionIndex = this.treeOutline._indexOfTreeElement(child.previousSibling) || 0;
+ this.treeOutline._selectionController.didInsertItem(insertionIndex);
}
removeChildAtIndex(childIndex, suppressOnDeselect, suppressSelectSibling)
@@ -272,6 +305,7 @@
if (treeOutline) {
treeOutline._forgetTreeElement(child);
treeOutline._forgetChildrenRecursive(child);
+ treeOutline._selectionController.didRemoveItem(childIndex);
}
child._detach();
@@ -378,6 +412,9 @@
_rememberTreeElement(element)
{
+ this._treeElementIndexCache.clear();
+ this._cachedNumberOfDescendents++;
+
if (!this._knownTreeElements[element.identifier])
this._knownTreeElements[element.identifier] = [];
@@ -392,6 +429,9 @@
_forgetTreeElement(element)
{
+ this._treeElementIndexCache.clear();
+ this._cachedNumberOfDescendents--;
+
if (this.selectedTreeElement === element) {
element.deselect(true);
this.selectedTreeElement = null;
@@ -523,7 +563,7 @@
if (event.target !== this._childrenListNode)
return;
- if (!this.selectedTreeElement || event.shiftKey || event.metaKey || event.ctrlKey)
+ if (!this.selectedTreeElement || event.commandOrControlKey)
return;
let isRTL = WI.resolvedLayoutDirection() === WI.LayoutDirection.RTL;
@@ -530,17 +570,8 @@
var handled = false;
var nextSelectedElement;
- if (event.keyIdentifier === "Up" && !event.altKey) {
- nextSelectedElement = this.selectedTreeElement.traversePreviousTreeElement(true);
- while (nextSelectedElement && !nextSelectedElement.selectable)
- nextSelectedElement = nextSelectedElement.traversePreviousTreeElement(true);
- handled = nextSelectedElement ? true : false;
- } else if (event.keyIdentifier === "Down" && !event.altKey) {
- nextSelectedElement = this.selectedTreeElement.traverseNextTreeElement(true);
- while (nextSelectedElement && !nextSelectedElement.selectable)
- nextSelectedElement = nextSelectedElement.traverseNextTreeElement(true);
- handled = nextSelectedElement ? true : false;
- } else if ((!isRTL && event.keyIdentifier === "Left") || (isRTL && event.keyIdentifier === "Right")) {
+
+ if ((!isRTL && event.keyIdentifier === "Left") || (isRTL && event.keyIdentifier === "Right")) {
if (this.selectedTreeElement.expanded) {
if (event.altKey)
this.selectedTreeElement.collapseRecursively();
@@ -563,7 +594,12 @@
handled = true;
} else if (this.selectedTreeElement.hasChildren) {
handled = true;
- if (!this.selectedTreeElement.expanded) {
+ if (this.selectedTreeElement.expanded) {
+ nextSelectedElement = this.selectedTreeElement.children[0];
+ while (nextSelectedElement && !nextSelectedElement.selectable)
+ nextSelectedElement = nextSelectedElement.nextSibling;
+ handled = nextSelectedElement ? true : false;
+ } else {
if (event.altKey)
this.selectedTreeElement.expandRecursively();
else
@@ -587,6 +623,9 @@
handled = this.treeOutline.onspace(this.selectedTreeElement);
}
+ if (!handled)
+ handled = this._selectionController.handleKeyDown(event);
+
if (nextSelectedElement) {
nextSelectedElement.reveal();
nextSelectedElement.select(false, true);
@@ -757,8 +796,96 @@
this._virtualizedScrollContainer.scrollTop = (firstItem + extraRows) * this._virtualizedTreeItemHeight;
}
+ // SelectionController delegate
+
+ selectionControllerNumberOfItems(controller)
+ {
+ return this._cachedNumberOfDescendents;
+ }
+
+ selectionControllerSelectionDidChange(controller, deselectedItems, selectedItems)
+ {
+ this._processingSelectionControllerSelectionDidChange = true;
+
+ for (let index of deselectedItems) {
+ let treeElement = this._treeElementAtIndex(index);
+ console.assert(treeElement, "Missing TreeElement for deselected index " + index);
+ if (treeElement) {
+ treeElement.listItemElement.classList.remove("selected");
+ if (!this._suppressNextSelectionDidChangeEvent)
+ treeElement.deselect();
+ }
+ }
+
+ for (let index of selectedItems) {
+ let treeElement = this._treeElementAtIndex(index);
+ console.assert(treeElement, "Missing TreeElement for selected index " + index);
+ if (treeElement) {
+ treeElement.listItemElement.classList.add("selected");
+ if (!this._suppressNextSelectionDidChangeEvent)
+ treeElement.select();
+ }
+ }
+
+ this._processingSelectionControllerSelectionDidChange = false;
+
+ this._dispatchSelectionDidChangeEvent();
+ }
+
+ selectionControllerNextSelectableIndex(controller, index)
+ {
+ let treeElement = this._treeElementAtIndex(index);
+ if (!treeElement)
+ return NaN;
+
+ const skipUnrevealed = true;
+ const stayWithin = null;
+ const dontPopulate = true;
+
+ while (treeElement = treeElement.traverseNextTreeElement(skipUnrevealed, stayWithin, dontPopulate)) {
+ if (treeElement.selectable)
+ return this._indexOfTreeElement(treeElement);
+ }
+
+ return NaN;
+ }
+
+ selectionControllerPreviousSelectableIndex(controller, index)
+ {
+ let treeElement = this._treeElementAtIndex(index);
+ if (!treeElement)
+ return NaN;
+
+ const skipUnrevealed = true;
+ const stayWithin = null;
+ const dontPopulate = true;
+
+ while (treeElement = treeElement.traversePreviousTreeElement(skipUnrevealed, stayWithin, dontPopulate)) {
+ if (treeElement.selectable)
+ return this._indexOfTreeElement(treeElement);
+ }
+
+ return NaN;
+ }
+
// Protected
+ selectTreeElementInternal(treeElement, suppressNotification = false, selectedByUser = false)
+ {
+ if (this._processingSelectionControllerSelectionDidChange)
+ return;
+
+ this._itemWasSelectedByUser = selectedByUser;
+ this._suppressNextSelectionDidChangeEvent = suppressNotification;
+
+ if (this.allowsRepeatSelection && this.selectedTreeElement === treeElement) {
+ this._dispatchSelectionDidChangeEvent();
+ return;
+ }
+
+ this.selectedTreeElement = treeElement;
+ }
+
treeElementFromEvent(event)
{
let scrollContainer = this.element.parentElement;
@@ -855,6 +982,93 @@
let contextMenu = WI.ContextMenu.createFromEvent(event);
this.populateContextMenu(contextMenu, event, treeElement);
}
+
+ _handleMouseDown(event)
+ {
+ let treeElement = this.treeElementFromEvent(event);
+ if (!treeElement || !treeElement.selectable)
+ return;
+
+ if (treeElement.isEventWithinDisclosureTriangle(event)) {
+ event.preventDefault();
+ return;
+ }
+
+ if (!treeElement.canSelectOnMouseDown(event)) {
+ event.preventDefault();
+ return;
+ }
+
+ let index = this._indexOfTreeElement(treeElement);
+ if (isNaN(index))
+ return;
+
+ this._selectionController.handleItemMouseDown(index, event);
+ }
+
+ _indexOfTreeElement(treeElement)
+ {
+ function previousElement(element) {
+ if (element.previousSibling) {
+ element = element.previousSibling;
+ if (element.children.length)
+ element = element.children.lastValue;
+ } else
+ element = element.parent && element.parent.root ? null : element.parent;
+ return element;
+ }
+
+ let index = 0;
+ let current = treeElement;
+ while (current) {
+ let closestIndex = this._treeElementIndexCache.get(current);
+ if (!isNaN(closestIndex)) {
+ index += closestIndex;
+ break;
+ }
+
+ current = previousElement(current);
+ if (current)
+ index++;
+ }
+
+ if (!this._treeElementIndexCache.has(treeElement))
+ this._treeElementIndexCache.set(treeElement, index);
+
+ return index;
+ }
+
+ _treeElementAtIndex(index)
+ {
+ const skipUnrevealed = false;
+ const stayWithin = null;
+ const dontPopulate = true;
+
+ let current = 0;
+ let treeElement = this.children[0];
+ while (treeElement) {
+ if (current === index)
+ return treeElement;
+
+ treeElement = treeElement.traverseNextTreeElement(skipUnrevealed, stayWithin, dontPopulate);
+ ++current;
+ }
+
+ return null;
+ }
+
+ _dispatchSelectionDidChangeEvent()
+ {
+ let selectedByUser = this._itemWasSelectedByUser;
+ this._itemWasSelectedByUser = false;
+
+ if (this._suppressNextSelectionDidChangeEvent) {
+ this._suppressNextSelectionDidChangeEvent = false;
+ return;
+ }
+
+ this.dispatchEventToListeners(WI.TreeOutline.Event.SelectionDidChange, {selectedByUser});
+ }
};
WI.TreeOutline._styleElement = null;