Diff
Modified: trunk/LayoutTests/ChangeLog (260612 => 260613)
--- trunk/LayoutTests/ChangeLog 2020-04-24 00:52:00 UTC (rev 260612)
+++ trunk/LayoutTests/ChangeLog 2020-04-24 01:05:34 UTC (rev 260613)
@@ -1,3 +1,13 @@
+2020-04-23 Devin Rousso <[email protected]>
+
+ Web Insspector: Storage: cannot select multiple local storage entries
+ https://bugs.webkit.org/show_bug.cgi?id=210876
+
+ Reviewed by Brian Burg.
+
+ * inspector/tree-outline/selection-controller-tree-comparator.html: Added.
+ * inspector/tree-outline/selection-controller-tree-comparator-expected.txt: Added.
+
2020-04-23 Alex Christensen <[email protected]>
Allow credentials for same-origin css mask images
Added: trunk/LayoutTests/inspector/tree-outline/selection-controller-tree-comparator-expected.txt (0 => 260613)
--- trunk/LayoutTests/inspector/tree-outline/selection-controller-tree-comparator-expected.txt (rev 0)
+++ trunk/LayoutTests/inspector/tree-outline/selection-controller-tree-comparator-expected.txt 2020-04-24 01:05:34 UTC (rev 260613)
@@ -0,0 +1,50 @@
+Tests for WI.SelectionController.createTreeComparator.
+
+
+== Running test suite: SelectionController.createTreeComparator
+-- Running test case: SelectionController.createTreeComparator.ValidTree
+Creating tree...
+Shuffling tree...
+Sorting tree...
+
+ root
+ 1
+ 1 > 1
+ 1 > 1 > 1
+ 1 > 1 > 2
+ 1 > 1 > 3
+ 1 > 2
+ 1 > 2 > 1
+ 1 > 2 > 2
+ 1 > 2 > 3
+ 1 > 3
+ 1 > 3 > 1
+ 1 > 3 > 2
+ 1 > 3 > 3
+ 2
+ 2 > 1
+ 2 > 1 > 1
+ 2 > 1 > 2
+ 2 > 1 > 3
+ 2 > 2
+ 2 > 2 > 1
+ 2 > 2 > 2
+ 2 > 2 > 3
+ 2 > 3
+ 2 > 3 > 1
+ 2 > 3 > 2
+ 2 > 3 > 3
+ 3
+ 3 > 1
+ 3 > 1 > 1
+ 3 > 1 > 2
+ 3 > 1 > 3
+ 3 > 2
+ 3 > 2 > 1
+ 3 > 2 > 2
+ 3 > 2 > 3
+ 3 > 3
+ 3 > 3 > 1
+ 3 > 3 > 2
+ 3 > 3 > 3
+
Added: trunk/LayoutTests/inspector/tree-outline/selection-controller-tree-comparator.html (0 => 260613)
--- trunk/LayoutTests/inspector/tree-outline/selection-controller-tree-comparator.html (rev 0)
+++ trunk/LayoutTests/inspector/tree-outline/selection-controller-tree-comparator.html 2020-04-24 01:05:34 UTC (rev 260613)
@@ -0,0 +1,161 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src=""
+<script>
+function test()
+{
+ function itemForRepresentedObject(x) {
+ return x;
+ }
+
+ function createTree(data) {
+ let node = {
+ ...data,
+ parent: null,
+ children: data.children || [],
+ };
+
+ node.children = node.children.map((childData) => {
+ let childNode = createTree(childData);
+ childNode.parent = node;
+ return childNode;
+ });
+
+ return node;
+ }
+
+ function flatten(node) {
+ return [node, ...node.children.flatMap(flatten)];
+ }
+
+ function shuffle(array) {
+ let shuffled = [];
+ while (array.length)
+ shuffled.push(array.splice(Math.floor(Math.random() * array.length), 1)[0]);
+ return shuffled;
+ }
+
+ let suite = InspectorTest.createSyncSuite("SelectionController.createTreeComparator");
+
+ suite.addTestCase({
+ name: "SelectionController.createTreeComparator.ValidTree",
+ description: "Check that createTreeComparator works with a valid tree.",
+ test() {
+ let comparator = WI.SelectionController.createTreeComparator(itemForRepresentedObject);
+
+ InspectorTest.log("Creating tree...");
+ let root = createTree({
+ id: "root",
+ children: [
+ {
+ id: "1",
+ children: [
+ {
+ id: "1 > 1",
+ children: [
+ {id: "1 > 1 > 1"},
+ {id: "1 > 1 > 2"},
+ {id: "1 > 1 > 3"},
+ ],
+ },
+ {
+ id: "1 > 2",
+ children: [
+ {id: "1 > 2 > 1"},
+ {id: "1 > 2 > 2"},
+ {id: "1 > 2 > 3"},
+ ],
+ },
+ {
+ id: "1 > 3",
+ children: [
+ {id: "1 > 3 > 1"},
+ {id: "1 > 3 > 2"},
+ {id: "1 > 3 > 3"},
+ ],
+ },
+
+ ],
+ },
+ {
+ id: "2",
+ children: [
+ {
+ id: "2 > 1",
+ children: [
+ {id: "2 > 1 > 1"},
+ {id: "2 > 1 > 2"},
+ {id: "2 > 1 > 3"},
+ ],
+ },
+ {
+ id: "2 > 2",
+ children: [
+ {id: "2 > 2 > 1"},
+ {id: "2 > 2 > 2"},
+ {id: "2 > 2 > 3"},
+ ],
+ },
+ {
+ id: "2 > 3",
+ children: [
+ {id: "2 > 3 > 1"},
+ {id: "2 > 3 > 2"},
+ {id: "2 > 3 > 3"},
+ ],
+ },
+ ],
+ },
+ {
+ id: "3",
+ children: [
+ {
+ id: "3 > 1",
+ children: [
+ {id: "3 > 1 > 1"},
+ {id: "3 > 1 > 2"},
+ {id: "3 > 1 > 3"},
+ ],
+ },
+ {
+ id: "3 > 2",
+ children: [
+ {id: "3 > 2 > 1"},
+ {id: "3 > 2 > 2"},
+ {id: "3 > 2 > 3"},
+ ],
+ },
+ {
+ id: "3 > 3",
+ children: [
+ {id: "3 > 3 > 1"},
+ {id: "3 > 3 > 2"},
+ {id: "3 > 3 > 3"},
+ ],
+ },
+ ],
+ },
+ ],
+ });
+
+ InspectorTest.log("Shuffling tree...");
+ let nodes = shuffle(flatten(root));
+
+ InspectorTest.log("Sorting tree...");
+ nodes.sort(comparator);
+
+ InspectorTest.newline();
+ for (let node of nodes)
+ InspectorTest.log(" " + node.id);
+ },
+ });
+
+ suite.runTestCasesAndFinish();
+}
+</script>
+</head>
+<body _onLoad_="runTest()">
+ <p>Tests for WI.SelectionController.createTreeComparator.</p>
+</body>
+</html>
Modified: trunk/Source/WebInspectorUI/ChangeLog (260612 => 260613)
--- trunk/Source/WebInspectorUI/ChangeLog 2020-04-24 00:52:00 UTC (rev 260612)
+++ trunk/Source/WebInspectorUI/ChangeLog 2020-04-24 01:05:34 UTC (rev 260613)
@@ -1,5 +1,85 @@
2020-04-23 Devin Rousso <[email protected]>
+ Web Insspector: Storage: cannot select multiple local storage entries
+ https://bugs.webkit.org/show_bug.cgi?id=210876
+
+ Reviewed by Brian Burg.
+
+ Support multiple selection using `WI.DataGrid`.
+
+ * UserInterface/Views/DataGrid.js:
+ (WI.DataGrid):
+ (WI.DataGrid.prototype.get allowsMultipleSelection): Added.
+ (WI.DataGrid.prototype.set allowsMultipleSelection): Added.
+ (WI.DataGrid.prototype.get selectedNode):
+ (WI.DataGrid.prototype.set selectedNode):
+ (WI.DataGrid.prototype.get selectedDataGridNodes): Added.
+ (WI.DataGrid.prototype._keyDown):
+ (WI.DataGrid.prototype.selectNodes):
+ (WI.DataGrid.prototype._mouseDownInDataTable):
+ (WI.DataGrid.prototype._contextMenuInDataTable):
+ (WI.DataGrid.prototype.handleCopyEvent):
+ (WI.DataGrid.prototype._copyRow):
+ (WI.DataGrid.prototype._copyTable):
+ (WI.DataGrid.prototype._hasCopyableData):
+ (WI.DataGrid.prototype.selectDataGridNodeInternal): Added.
+ (WI.DataGrid.prototype.deselectDataGridNodeInternal): Added.
+ (WI.DataGrid.prototype._dispatchSelectedNodeChangedEvent): Added.
+ (WI.DataGrid.prototype.dataGridNodeForSelectionItem): Added.
+ (WI.DataGrid.prototype.selectionItemForDataGridNode): Added.
+ (WI.DataGrid.prototype.selectionControllerSelectionDidChange): Added.
+ (WI.DataGrid.prototype.selectionControllerFirstSelectableItem): Added.
+ (WI.DataGrid.prototype.selectionControllerLastSelectableItem): Added.
+ (WI.DataGrid.prototype.selectionControllerPreviousSelectableItem): Added.
+ (WI.DataGrid.prototype.selectionControllerNextSelectableItem): Added.
+ * UserInterface/Views/DataGridNode.js:
+ (WI.DataGridNode.prototype.select):
+ (WI.DataGridNode.prototype.deselect):
+ Replace `selectedNode` with a `WI.SelectionController` that behaves like a `WI.TreeOutline`.
+ Use the `WI.SelectionController.Operation` to ensure that `WI.PlaceholderDataGridNode` are
+ not selected unless directly chosen (i.e. not during shift selection or ⌘A). Add logic such
+ that `WI.PlaceholderDataGridNode` are not copied. Prefer `_rows` instead of `children` as
+ the latter is not sorted/filtered.
+
+ * UserInterface/Controllers/SelectionController.js:
+ (WI.SelectionController.createTreeComparator): Added.
+ (WI.SelectionController.createListComparator): Added.
+ Create `static` helper functions for common comparators.
+
+ (WI.SelectionController.prototype.deselectItem):
+ (WI.SelectionController.prototype.selectAll):
+ (WI.SelectionController.prototype.removeSelectedItems):
+ (WI.SelectionController.prototype.handleItemMouseDown):
+ (WI.SelectionController.prototype._selectItemsFromArrowKey):
+ (WI.SelectionController.prototype._firstSelectableItem):
+ (WI.SelectionController.prototype._lastSelectableItem):
+ (WI.SelectionController.prototype._previousSelectableItem):
+ (WI.SelectionController.prototype._nextSelectableItem):
+ (WI.SelectionController.prototype._addRange):
+ (WI.SelectionController.prototype._deleteRange):
+ Introduce a `WI.SelectionController.Operation` which is used to tell the `_delegate` about
+ why it's being asked for information.
+
+ * UserInterface/Views/DOMStorageContentView.js:
+ (WI.DOMStorageContentView):
+ (WI.DOMStorageContentView.prototype._deleteCallback):
+ Support multiple selection, including deleting multiple rows at once.
+
+ * UserInterface/Views/Table.js:
+ (WI.Table):
+ * UserInterface/Views/TreeOutline.js:
+ (WI.TreeOutline):
+ (WI.TreeOutline.prototype.selectionControllerNumberOfItems): Deleted.
+ Removed unused `selectionControllerNumberOfItems`.
+
+ * UserInterface/Views/ProfileView.js:
+ (WI.ProfileView):
+ (WI.ProfileView.prototype._dataGridNodeSelected):
+ Maintain a `_selectedDataGridNode` so that `oldSelectedNode` doesn't have to be included
+ when dispatching `WI.DataGrid.Event.SelectedNodeChanged`.
+
+2020-04-23 Devin Rousso <[email protected]>
+
Web Inspector: REGRESSION: Elements: Styles: color functions are missing swatches
https://bugs.webkit.org/show_bug.cgi?id=210930
Modified: trunk/Source/WebInspectorUI/UserInterface/Controllers/SelectionController.js (260612 => 260613)
--- trunk/Source/WebInspectorUI/UserInterface/Controllers/SelectionController.js 2020-04-24 00:52:00 UTC (rev 260612)
+++ trunk/Source/WebInspectorUI/UserInterface/Controllers/SelectionController.js 2020-04-24 01:05:34 UTC (rev 260613)
@@ -48,6 +48,64 @@
console.assert(this._delegate.selectionControllerPreviousSelectableItem, "SelectionController delegate must implement selectionControllerPreviousSelectableItem.");
}
+ // Static
+
+ static createTreeComparator(itemForRepresentedObject)
+ {
+ return (a, b) => {
+ a = itemForRepresentedObject(a);
+ b = itemForRepresentedObject(b);
+ if (!a || !b)
+ return 0;
+
+ let getLevel = (item) => {
+ let level = 0;
+ while (item = item.parent)
+ level++;
+ return level;
+ };
+
+ let compareSiblings = (s, t) => {
+ return s.parent.children.indexOf(s) - s.parent.children.indexOf(t);
+ };
+
+ if (a.parent === b.parent)
+ return compareSiblings(a, b);
+
+ let aLevel = getLevel(a);
+ let bLevel = getLevel(b);
+ while (aLevel > bLevel) {
+ if (a.parent === b)
+ return 1;
+ a = a.parent;
+ aLevel--;
+ }
+ while (bLevel > aLevel) {
+ if (b.parent === a)
+ return -1;
+ b = b.parent;
+ bLevel--;
+ }
+
+ while (a.parent !== b.parent) {
+ a = a.parent;
+ b = b.parent;
+ }
+
+ console.assert(a.parent === b.parent, "Missing common ancestor.", a, b);
+ return compareSiblings(a, b);
+ };
+ }
+
+ static createListComparator(indexForRepresentedObject)
+ {
+ console.assert(indexForRepresentedObject);
+
+ return (a, b) => {
+ return indexForRepresentedObject(a) - indexForRepresentedObject(b);
+ };
+ }
+
// Public
get delegate() { return this._delegate; }
@@ -129,17 +187,21 @@
this._lastSelectedItem = null;
if (newItems.size) {
+ console.assert(this._allowsMultipleSelection);
+
+ const operation = WI.SelectionController.Operation.Extend;
+
// Find selected item closest to deselected item.
let previous = item;
let next = item;
while (!this._lastSelectedItem && previous && next) {
- previous = this._previousSelectableItem(previous);
+ previous = this._previousSelectableItem(previous, operation);
if (this.hasSelectedItem(previous)) {
this._lastSelectedItem = previous;
break;
}
- next = this._nextSelectableItem(next);
+ next = this._nextSelectableItem(next, operation);
if (this.hasSelectedItem(next)) {
this._lastSelectedItem = next;
break;
@@ -159,10 +221,10 @@
if (!this._allowsMultipleSelection)
return;
- this.reset();
+ const operation = WI.SelectionController.Operation.Extend;
let newItems = new Set;
- this._addRange(newItems, this._firstSelectableItem(), this._lastSelectableItem());
+ this._addRange(newItems, this._firstSelectableItem(operation), this._lastSelectableItem(operation));
this.selectItems(newItems);
}
@@ -176,22 +238,24 @@
if (!this._selectedItems.size)
return;
+ let operation = this._allowsMultipleSelection ? WI.SelectionController.Operation.Extend : WI.SelectionController.Operation.Direct;
+
let orderedSelection = Array.from(this._selectedItems).sort(this._comparator);
// Try selecting the item preceding the selection.
let firstSelectedItem = orderedSelection[0];
- let itemToSelect = this._previousSelectableItem(firstSelectedItem);
+ let itemToSelect = this._previousSelectableItem(firstSelectedItem, operation);
if (!itemToSelect) {
// If no item exists before the first item in the selection, try selecting
// a deselected item (hole) within the selection.
itemToSelect = firstSelectedItem;
while (itemToSelect && this.hasSelectedItem(itemToSelect))
- itemToSelect = this._nextSelectableItem(itemToSelect);
+ itemToSelect = this._nextSelectableItem(itemToSelect, operation);
if (!itemToSelect || this.hasSelectedItem(itemToSelect)) {
// If the selection contains no holes, try selecting the item
// following the selection.
- itemToSelect = this._nextSelectableItem(orderedSelection.lastValue);
+ itemToSelect = this._nextSelectableItem(orderedSelection.lastValue, operation);
}
}
@@ -265,7 +329,7 @@
// through the clicked item to be selected.
if (!newItems.size) {
this._lastSelectedItem = item;
- this._shiftAnchorItem = this._firstSelectableItem();
+ this._shiftAnchorItem = this._firstSelectableItem(WI.SelectionController.Operation.Extend);
this._addRange(newItems, this._shiftAnchorItem, this._lastSelectedItem);
this._updateSelectedItems(newItems);
@@ -320,16 +384,18 @@
_selectItemsFromArrowKey(goingUp, shiftKey)
{
+ let extendSelection = shiftKey && this._allowsMultipleSelection;
+ let operation = extendSelection ? WI.SelectionController.Operation.Extend : WI.SelectionController.Operation.Direct;
+
if (!this._selectedItems.size) {
- this.selectItem(goingUp ? this._lastSelectableItem() : this._firstSelectableItem());
+ this.selectItem(goingUp ? this._lastSelectableItem(operation) : this._firstSelectableItem(operation));
return;
}
- let item = goingUp ? this._previousSelectableItem(this._lastSelectedItem) : this._nextSelectableItem(this._lastSelectedItem);
+ let item = goingUp ? this._previousSelectableItem(this._lastSelectedItem, operation) : this._nextSelectableItem(this._lastSelectedItem, operation);
if (!item)
return;
- let extendSelection = shiftKey && this._allowsMultipleSelection;
if (!extendSelection || !this.hasSelectedItem(item)) {
this.selectItem(item, extendSelection);
return;
@@ -338,7 +404,7 @@
// Since the item in the direction of movement is selected, we are either
// extending the selection into the item, or deselecting. Determine which
// by checking whether the item opposite the anchor item is selected.
- let priorItem = goingUp ? this._nextSelectableItem(this._lastSelectedItem) : this._previousSelectableItem(this._lastSelectedItem);
+ let priorItem = goingUp ? this._nextSelectableItem(this._lastSelectedItem, operation) : this._previousSelectableItem(this._lastSelectedItem, operation);
if (!priorItem || !this.hasSelectedItem(priorItem)) {
this.deselectItem(this._lastSelectedItem);
return;
@@ -354,28 +420,28 @@
}
this._lastSelectedItem = item;
- item = goingUp ? this._previousSelectableItem(item) : this._nextSelectableItem(item);
+ item = goingUp ? this._previousSelectableItem(item, operation) : this._nextSelectableItem(item, operation);
}
}
- _firstSelectableItem()
+ _firstSelectableItem(operation)
{
- return this._delegate.selectionControllerFirstSelectableItem(this);
+ return this._delegate.selectionControllerFirstSelectableItem(this, operation);
}
- _lastSelectableItem()
+ _lastSelectableItem(operation)
{
- return this._delegate.selectionControllerLastSelectableItem(this);
+ return this._delegate.selectionControllerLastSelectableItem(this, operation);
}
- _previousSelectableItem(item)
+ _previousSelectableItem(item, operation)
{
- return this._delegate.selectionControllerPreviousSelectableItem(this, item);
+ return this._delegate.selectionControllerPreviousSelectableItem(this, item, operation);
}
- _nextSelectableItem(item)
+ _nextSelectableItem(item, operation)
{
- return this._delegate.selectionControllerNextSelectableItem(this, item);
+ return this._delegate.selectionControllerNextSelectableItem(this, item, operation);
}
_updateSelectedItems(items)
@@ -394,12 +460,16 @@
_addRange(items, firstItem, lastItem)
{
+ console.assert(this._allowsMultipleSelection);
+
+ const operation = WI.SelectionController.Operation.Extend;
+
let current = firstItem;
while (current) {
items.add(current);
if (current === lastItem)
break;
- current = this._nextSelectableItem(current);
+ current = this._nextSelectableItem(current, operation);
}
console.assert(!lastItem || items.has(lastItem), "End of range could not be reached.");
@@ -407,14 +477,23 @@
_deleteRange(items, firstItem, lastItem)
{
+ console.assert(this._allowsMultipleSelection);
+
+ const operation = WI.SelectionController.Operation.Extend;
+
let current = firstItem;
while (current) {
items.delete(current);
if (current === lastItem)
break;
- current = this._nextSelectableItem(current);
+ current = this._nextSelectableItem(current, operation);
}
console.assert(!lastItem || !items.has(lastItem), "End of range could not be reached.");
}
};
+
+WI.SelectionController.Operation = {
+ Direct: Symbol("selection-operation-direct"),
+ Extend: Symbol("selection-operation-extend"),
+};
Modified: trunk/Source/WebInspectorUI/UserInterface/Views/DOMStorageContentView.js (260612 => 260613)
--- trunk/Source/WebInspectorUI/UserInterface/Views/DOMStorageContentView.js 2020-04-24 00:52:00 UTC (rev 260612)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/DOMStorageContentView.js 2020-04-24 01:05:34 UTC (rev 260613)
@@ -48,6 +48,7 @@
});
this._dataGrid.sortOrder = WI.DataGrid.SortOrder.Ascending;
this._dataGrid.sortColumnIdentifier = "key";
+ this._dataGrid.allowsMultipleSelection = true;
this._dataGrid.createSettings("dom-storage-content-view");
this._dataGrid.addEventListener(WI.DataGrid.Event.SortChanged, this._sortDataGrid, this);
this.addSubview(this._dataGrid);
@@ -193,13 +194,14 @@
this._dataGrid.sortNodesImmediately(comparator);
}
- _deleteCallback(node)
+ _deleteCallback()
{
- if (!node || node.isPlaceholderNode)
- return;
-
- this._dataGrid.removeChild(node);
- this.representedObject.removeItem(node.data["key"]);
+ for (let dataGridNode of this._dataGrid.selectedDataGridNodes) {
+ if (dataGridNode.isPlaceholderNode)
+ continue;
+ this._dataGrid.removeChild(dataGridNode);
+ this.representedObject.removeItem(dataGridNode.data["key"]);
+ }
}
_editingCallback(editingNode, columnIdentifier, oldText, newText, moveDirection)
Modified: trunk/Source/WebInspectorUI/UserInterface/Views/DataGrid.js (260612 => 260613)
--- trunk/Source/WebInspectorUI/UserInterface/Views/DataGrid.js 2020-04-24 00:52:00 UTC (rev 260612)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/DataGrid.js 2020-04-24 01:05:34 UTC (rev 260613)
@@ -44,7 +44,6 @@
this._rows = [];
this.children = [];
- this.selectedNode = null;
this.expandNodesWhenArrowing = false;
this.root = true;
this.hasChildren = false;
@@ -68,6 +67,13 @@
this._filterDelegate = null;
this._filterDidModifyNodeWhileProcessingItems = false;
+ let itemForRepresentedObject = this.dataGridNodeForSelectionItem.bind(this);
+ let selectionComparator = WI.SelectionController.createTreeComparator(itemForRepresentedObject);
+ this._selectionController = new WI.SelectionController(this, selectionComparator);
+
+ this._processingSelectionChange = false;
+ this._suppressNextSelectionDidChangeEvent = false;
+
this.element.className = "data-grid";
this.element.tabIndex = 0;
this.element.addEventListener("keydown", this._keyDown.bind(this), false);
@@ -318,6 +324,45 @@
this._updateScrollListeners();
}
+ get allowsMultipleSelection()
+ {
+ return this._selectionController.allowsMultipleSelection;
+ }
+
+ set allowsMultipleSelection(flag)
+ {
+ this._selectionController.allowsMultipleSelection = flag;
+ }
+
+ get selectedNode()
+ {
+ return this.dataGridNodeForSelectionItem(this._selectionController.lastSelectedItem);
+ }
+
+ set selectedNode(dataGridNode)
+ {
+ if (dataGridNode)
+ this._selectionController.selectItem(this.selectionItemForDataGridNode(dataGridNode));
+ else
+ this._selectionController.deselectAll();
+ }
+
+ get selectedDataGridNodes()
+ {
+ if (this.allowsMultipleSelection) {
+ let selectedDataGridNodes = [];
+ for (let item of this._selectionController.selectedItems)
+ selectedDataGridNodes.push(this.dataGridNodeForSelectionItem(item));
+ return selectedDataGridNodes;
+ }
+
+ let selectedNode = this.selectedNode;
+ if (selectedNode)
+ return [selectedNode];
+
+ return [];
+ }
+
get filterText() { return this._filterText; }
set filterText(x)
@@ -1366,66 +1411,72 @@
_keyDown(event)
{
- if (!this.selectedNode || event.shiftKey || event.metaKey || event.ctrlKey || this._editing)
+ if (this._editing)
return;
- let isRTL = WI.resolvedLayoutDirection() === WI.LayoutDirection.RTL;
+ let isRTL = WI.resolveLayoutDirectionForElement(this.element) === WI.LayoutDirection.RTL;
+ let expandKeyIdentifier = isRTL ? "Left" : "Right";
+ let collapseKeyIdentifier = isRTL ? "Right" : "Left";
var handled = false;
var nextSelectedNode;
- if (event.keyIdentifier === "Up" && !event.altKey) {
- nextSelectedNode = this.selectedNode.traversePreviousNode(true);
- while (nextSelectedNode && !nextSelectedNode.selectable)
- nextSelectedNode = nextSelectedNode.traversePreviousNode(true);
- handled = nextSelectedNode ? true : false;
- } else if (event.keyIdentifier === "Down" && !event.altKey) {
- nextSelectedNode = this.selectedNode.traverseNextNode(true);
- while (nextSelectedNode && !nextSelectedNode.selectable)
- nextSelectedNode = nextSelectedNode.traverseNextNode(true);
- handled = nextSelectedNode ? true : false;
- } else if ((!isRTL && event.keyIdentifier === "Left") || (isRTL && event.keyIdentifier === "Right")) {
- if (this.selectedNode.expanded) {
- if (event.altKey)
- this.selectedNode.collapseRecursively();
- else
- this.selectedNode.collapse();
- handled = true;
- } else if (this.selectedNode.parent && !this.selectedNode.parent.root) {
- handled = true;
- if (this.selectedNode.parent.selectable) {
- nextSelectedNode = this.selectedNode.parent;
- handled = nextSelectedNode ? true : false;
- } else if (this.selectedNode.parent)
- this.selectedNode.parent.collapse();
- }
- } else if ((!isRTL && event.keyIdentifier === "Right") || (isRTL && event.keyIdentifier === "Left")) {
- if (!this.selectedNode.revealed) {
- this.selectedNode.reveal();
- handled = true;
- } else if (this.selectedNode.hasChildren) {
- handled = true;
+
+ if (this.selectedNode) {
+ if (event.keyIdentifier === collapseKeyIdentifier) {
if (this.selectedNode.expanded) {
- nextSelectedNode = this.selectedNode.children[0];
- handled = nextSelectedNode ? true : false;
- } else {
if (event.altKey)
- this.selectedNode.expandRecursively();
+ this.selectedNode.collapseRecursively();
else
- this.selectedNode.expand();
+ this.selectedNode.collapse();
+ handled = true;
+ } else if (this.selectedNode.parent && !this.selectedNode.parent.root) {
+ handled = true;
+ if (this.selectedNode.parent.selectable) {
+ nextSelectedNode = this.selectedNode.parent;
+ while (nextSelectedNode && !nextSelectedNode.selectable)
+ nextSelectedNode = nextSelectedNode.parent;
+ handled = !!nextSelectedNode;
+ } else if (this.selectedNode.parent)
+ this.selectedNode.parent.collapse();
}
+ } else if (event.keyIdentifier === expandKeyIdentifier) {
+ if (!this.selectedNode.revealed) {
+ this.selectedNode.reveal();
+ handled = true;
+ } else if (this.selectedNode.hasChildren) {
+ handled = true;
+ if (this.selectedNode.expanded) {
+ nextSelectedNode = this.selectedNode.children[0];
+ while (nextSelectedNode && !nextSelectedNode.selectable)
+ nextSelectedNode = nextSelectedNode.nextSibling;
+ handled = !!nextSelectedNode;
+ } else {
+ if (event.altKey)
+ this.selectedNode.expandRecursively();
+ else
+ this.selectedNode.expand();
+ }
+ }
+ } else if (event.keyCode === 8 /* Backspace */ || event.keyCode === 46 /* Delete */) {
+ if (this._deleteCallback) {
+ handled = true;
+ this._deleteCallback(this.selectedNode);
+ }
+ } else if (isEnterKey(event)) {
+ if (this._editCallback) {
+ handled = true;
+ this._startEditing(this.selectedNode.element.children[0]);
+ }
}
- } else if (event.keyCode === 8 || event.keyCode === 46) {
- if (this._deleteCallback) {
- handled = true;
- this._deleteCallback(this.selectedNode);
- }
- } else if (isEnterKey(event)) {
- if (this._editCallback) {
- handled = true;
- this._startEditing(this.selectedNode.element.children[0]);
- }
}
+ if (!handled) {
+ handled = this._selectionController.handleKeyDown(event);
+
+ if (handled)
+ nextSelectedNode = this.selectedNode;
+ }
+
if (nextSelectedNode) {
nextSelectedNode.reveal();
nextSelectedNode.select();
@@ -1462,6 +1513,24 @@
// This is the root, do nothing.
}
+ selectNodes(nodes)
+ {
+ if (!nodes.length)
+ return;
+
+ if (nodes.length === 1) {
+ this.selectedNode = nodes[0];
+ return;
+ }
+
+ console.assert(this.allowsMultipleSelection, "Cannot select multiple DataGridNode with multiple selection disabled.");
+ if (!this.allowsMultipleSelection)
+ return;
+
+ let selectableObjects = nodes.map((node) => this.selectionItemForDataGridNode(node));
+ this._selectionController.selectItems(new Set(selectableObjects));
+ }
+
dataGridNodeFromNode(target)
{
var rowElement = target.closest("tr");
@@ -1578,23 +1647,16 @@
_mouseDownInDataTable(event)
{
- var gridNode = this.dataGridNodeFromNode(event.target);
- if (!gridNode) {
- if (this.selectedNode)
- this.selectedNode.deselect();
+ let dataGridNode = this.dataGridNodeFromNode(event.target);
+ if (!dataGridNode) {
+ this._selectionController.deselectAll();
return;
}
- if (!gridNode.selectable || gridNode.isEventWithinDisclosureTriangle(event))
+ if (!dataGridNode.selectable || dataGridNode.isEventWithinDisclosureTriangle(event))
return;
- if (event.metaKey) {
- if (gridNode.selected)
- gridNode.deselect();
- else
- gridNode.select();
- } else
- gridNode.select();
+ this._selectionController.handleItemMouseDown(this.selectionItemForDataGridNode(dataGridNode), event);
}
_contextMenuInHeader(event)
@@ -1666,7 +1728,7 @@
if (gridNode) {
if (gridNode.selectable && gridNode.copyable && !gridNode.isEventWithinDisclosureTriangle(event)) {
- contextMenu.appendItem(WI.UIString("Copy Row"), this._copyRow.bind(this, event.target));
+ contextMenu.appendItem(WI.UIString("Copy Row"), this._copyRow.bind(this, gridNode));
contextMenu.appendItem(WI.UIString("Copy Table"), this._copyTable.bind(this));
if (this.dataGrid._editCallback) {
@@ -1746,22 +1808,30 @@
handleCopyEvent(event)
{
- if (!this.selectedNode || !window.getSelection().isCollapsed)
+ if (!window.getSelection().isCollapsed)
return;
- var copyText = this._copyTextForDataGridNode(this.selectedNode);
- event.clipboardData.setData("text/plain", copyText);
+ let copyData = [];
+ for (let dataGridNode of this.selectedDataGridNodes) {
+ if (!dataGridNode.copyable || dataGridNode.isPlaceholderNode)
+ continue;
+ copyData.push(this._copyTextForDataGridNode(dataGridNode));
+ }
+
+ if (!copyData.length)
+ return;
+
+ event.clipboardData.setData("text/plain", copyData.join("\n"));
event.stopPropagation();
event.preventDefault();
}
- _copyRow(target)
+ _copyRow(dataGridNode)
{
- var gridNode = this.dataGridNodeFromNode(target);
- if (!gridNode)
+ if (!dataGridNode.copyable || dataGridNode.isPlaceholderNode)
return;
- var copyText = this._copyTextForDataGridNode(gridNode);
+ let copyText = this._copyTextForDataGridNode(dataGridNode);
InspectorFrontendHost.copyText(copyText);
}
@@ -1769,19 +1839,28 @@
{
let copyData = [];
copyData.push(this._copyTextForDataGridHeaders());
- for (let gridNode of this.children) {
- if (!gridNode.copyable)
+ for (let dataGridNode of this._rows) {
+ if (!dataGridNode.copyable || dataGridNode.isPlaceholderNode)
continue;
- copyData.push(this._copyTextForDataGridNode(gridNode));
+ copyData.push(this._copyTextForDataGridNode(dataGridNode));
}
+ if (!copyData.length)
+ return;
+
InspectorFrontendHost.copyText(copyData.join("\n"));
}
_hasCopyableData()
{
- let gridNode = this.children[0];
- return gridNode && gridNode.selectable && gridNode.copyable;
+ const skipHidden = true;
+ const stayWithin = null;
+ const dontPopulate = true;
+
+ let dataGridNode = this._rows[0];
+ while (dataGridNode && (!dataGridNode.selectable || !dataGridNode.copyable || dataGridNode.isPlaceholderNode))
+ dataGridNode = dataGridNode.traverseNextNode(skipHidden, stayWithin, dontPopulate);
+ return !!dataGridNode;
}
resizerDragStarted(resizer)
@@ -1904,6 +1983,36 @@
this._applyFilterToNodesTask.start();
}
+ selectDataGridNodeInternal(dataGridNode, suppressSelectedEvent)
+ {
+ if (this._processingSelectionChange)
+ return;
+
+ this._suppressNextSelectionDidChangeEvent = suppressSelectedEvent;
+
+ this._selectionController.selectItem(this.selectionItemForDataGridNode(dataGridNode));
+ }
+
+ deselectDataGridNodeInternal(dataGridNode, suppressDeselectedEvent)
+ {
+ if (this._processingSelectionChange)
+ return;
+
+ this._suppressNextSelectionDidChangeEvent = suppressDeselectedEvent;
+
+ this._selectionController.deselectItem(this.selectionItemForDataGridNode(dataGridNode));
+ }
+
+ _dispatchSelectedNodeChangedEvent()
+ {
+ if (this._suppressNextSelectionDidChangeEvent) {
+ this._suppressNextSelectionDidChangeEvent = false;
+ return;
+ }
+
+ this.dispatchEventToListeners(WI.DataGrid.Event.SelectedNodeChanged);
+ }
+
// YieldableTask delegate
yieldableTaskWillProcessItem(task, node)
@@ -1927,6 +2036,95 @@
{
this._applyFilterToNodesTask = null;
}
+
+ // SelectionController delegate
+
+ dataGridNodeForSelectionItem(item)
+ {
+ console.assert(!item || item instanceof WI.DataGridNode);
+ return item;
+ }
+
+ selectionItemForDataGridNode(dataGridNode)
+ {
+ console.assert(!dataGridNode || dataGridNode instanceof WI.DataGridNode);
+ return dataGridNode;
+ }
+
+ selectionControllerSelectionDidChange(selectionController, deselectedItems, selectedItems)
+ {
+ this._processingSelectionChange = true;
+
+ for (let item of deselectedItems) {
+ let dataGridNode = this.dataGridNodeForSelectionItem(item);
+ dataGridNode?.deselect();
+ }
+
+ for (let item of selectedItems) {
+ let dataGridNode = this.dataGridNodeForSelectionItem(item);
+ dataGridNode?.select();
+ }
+
+ this._dispatchSelectedNodeChangedEvent();
+
+ this._processingSelectionChange = false;
+ }
+
+ selectionControllerFirstSelectableItem(controller, operation)
+ {
+ let firstChild = this._rows[0];
+ let item = this.selectionItemForDataGridNode(firstChild);
+ if (firstChild.selectable && (!firstChild.isPlaceholderNode || operation === WI.SelectionController.Operation.Direct))
+ return item;
+ return this.selectionControllerNextSelectableItem(controller, item, operation);
+ }
+
+ selectionControllerLastSelectableItem(controller, operation)
+ {
+ let lastChild = this._rows.lastValue;
+ while (lastChild.expanded && lastChild.children.length)
+ lastChild = lastChild.children.lastValue;
+
+ let item = this.selectionItemForDataGridNode(lastChild);
+ if (lastChild.selectable && (!lastChild.isPlaceholderNode || operation === WI.SelectionController.Operation.Direct))
+ return item;
+ return this.selectionControllerPreviousSelectableItem(controller, item, operation);
+ }
+
+ selectionControllerPreviousSelectableItem(controller, item, operation)
+ {
+ let dataGridNode = this.dataGridNodeForSelectionItem(item);
+ console.assert(dataGridNode, "Missing DataGridNode for selection item.", item);
+ if (!dataGridNode)
+ return null;
+
+ const skipUnrevealed = true;
+ const dontPopulate = true;
+ while (dataGridNode = dataGridNode.traversePreviousNode(skipUnrevealed, dontPopulate)) {
+ if (dataGridNode.selectable && (!dataGridNode.isPlaceholderNode || operation === WI.SelectionController.Operation.Direct))
+ return this.selectionItemForDataGridNode(dataGridNode);
+ }
+
+ return null;
+ }
+
+ selectionControllerNextSelectableItem(controller, item, operation)
+ {
+ let dataGridNode = this.dataGridNodeForSelectionItem(item);
+ console.assert(dataGridNode, "Missing DataGridNode for selection item.", item);
+ if (!dataGridNode)
+ return null;
+
+ const skipUnrevealed = true;
+ const stayWithin = null;
+ const dontPopulate = true;
+ while (dataGridNode = dataGridNode.traverseNextNode(skipUnrevealed, stayWithin, dontPopulate)) {
+ if (dataGridNode.selectable && (!dataGridNode.isPlaceholderNode || operation === WI.SelectionController.Operation.Direct))
+ return this.selectionItemForDataGridNode(dataGridNode);
+ }
+
+ return null;
+ }
};
WI.DataGrid.Event = {
Modified: trunk/Source/WebInspectorUI/UserInterface/Views/DataGridNode.js (260612 => 260613)
--- trunk/Source/WebInspectorUI/UserInterface/Views/DataGridNode.js 2020-04-24 00:52:00 UTC (rev 260612)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/DataGridNode.js 2020-04-24 01:05:34 UTC (rev 260613)
@@ -566,18 +566,10 @@
if (!this.dataGrid || !this.selectable || this.selected)
return;
- let oldSelectedNode = this.dataGrid.selectedNode;
- if (oldSelectedNode)
- oldSelectedNode.deselect(true);
-
this._selected = true;
- this.dataGrid.selectedNode = this;
+ this._element?.classList.add("selected");
- if (this._element)
- this._element.classList.add("selected");
-
- if (!suppressSelectedEvent)
- this.dataGrid.dispatchEventToListeners(WI.DataGrid.Event.SelectedNodeChanged, {oldSelectedNode});
+ this.dataGrid.selectDataGridNodeInternal(this, suppressSelectedEvent);
}
revealAndSelect(suppressSelectedEvent)
@@ -588,17 +580,13 @@
deselect(suppressDeselectedEvent)
{
- if (!this.dataGrid || this.dataGrid.selectedNode !== this || !this.selected)
+ if (!this.dataGrid || !this.selectable || !this.selected)
return;
this._selected = false;
- this.dataGrid.selectedNode = null;
+ this._element?.classList.remove("selected");
- if (this._element)
- this._element.classList.remove("selected");
-
- if (!suppressDeselectedEvent)
- this.dataGrid.dispatchEventToListeners(WI.DataGrid.Event.SelectedNodeChanged, {oldSelectedNode: this});
+ this.dataGrid.deselectDataGridNodeInternal(this, suppressDeselectedEvent);
}
traverseNextNode(skipHidden, stayWithin, dontPopulate, info)
Modified: trunk/Source/WebInspectorUI/UserInterface/Views/ProfileView.js (260612 => 260613)
--- trunk/Source/WebInspectorUI/UserInterface/Views/ProfileView.js 2020-04-24 00:52:00 UTC (rev 260612)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/ProfileView.js 2020-04-24 01:05:34 UTC (rev 260613)
@@ -69,6 +69,8 @@
this._dataGrid.sortOrder = WI.DataGrid.SortOrder.Descending;
this._dataGrid.createSettings("profile-view");
+ this._selectedDataGridNode = null;
+
// Currently we create a new ProfileView for each CallingContextTree, so
// to share state between them, use a common shared data object.
this._sharedData = extraArguments;
@@ -211,18 +213,19 @@
_dataGridNodeSelected(event)
{
- let oldSelectedNode = event.data.oldSelectedNode;
- if (oldSelectedNode) {
- this._removeGuidanceElement(WI.ProfileView.GuidanceType.Selected, oldSelectedNode);
- oldSelectedNode.forEachChildInSubtree((node) => this._removeGuidanceElement(WI.ProfileView.GuidanceType.Selected, node));
+ if (this._selectedDataGridNode) {
+ this._removeGuidanceElement(WI.ProfileView.GuidanceType.Selected, this._selectedDataGridNode);
+ this._selectedDataGridNode.forEachChildInSubtree((node) => this._removeGuidanceElement(WI.ProfileView.GuidanceType.Selected, node));
}
- let newSelectedNode = this._dataGrid.selectedNode;
- if (newSelectedNode) {
- this._removeGuidanceElement(WI.ProfileView.GuidanceType.Selected, newSelectedNode);
- newSelectedNode.forEachChildInSubtree((node) => this._appendGuidanceElement(WI.ProfileView.GuidanceType.Selected, node, newSelectedNode));
+ this._selectedDataGridNode = this._dataGrid.selectedNode;
- this._sharedData.selectedNodeHash = newSelectedNode.callingContextTreeNode.hash;
+
+ if (this._selectedDataGridNode) {
+ this._removeGuidanceElement(WI.ProfileView.GuidanceType.Selected, this._selectedDataGridNode);
+ this._selectedDataGridNode.forEachChildInSubtree((node) => this._appendGuidanceElement(WI.ProfileView.GuidanceType.Selected, node, this._selectedDataGridNode));
+
+ this._sharedData.selectedNodeHash = this._selectedDataGridNode.callingContextTreeNode.hash;
}
}
Modified: trunk/Source/WebInspectorUI/UserInterface/Views/Table.js (260612 => 260613)
--- trunk/Source/WebInspectorUI/UserInterface/Views/Table.js 2020-04-24 00:52:00 UTC (rev 260612)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/Table.js 2020-04-24 01:05:34 UTC (rev 260613)
@@ -86,7 +86,8 @@
this._columnWidths = null; // Calculated in _resizeColumnsAndFiller.
this._fillerHeight = 0; // Calculated in _resizeColumnsAndFiller.
- this._selectionController = new WI.SelectionController(this, (a, b) => this._indexForRepresentedObject(a) - this._indexForRepresentedObject(b));
+ let selectionComparator = WI.SelectionController.createListComparator(this._indexForRepresentedObject.bind(this));
+ this._selectionController = new WI.SelectionController(this, selectionComparator);
this._resizers = [];
this._currentResizer = null;
Modified: trunk/Source/WebInspectorUI/UserInterface/Views/TreeOutline.js (260612 => 260613)
--- trunk/Source/WebInspectorUI/UserInterface/Views/TreeOutline.js 2020-04-24 00:52:00 UTC (rev 260612)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/TreeOutline.js 2020-04-24 01:05:34 UTC (rev 260613)
@@ -57,54 +57,10 @@
this._cachedNumberOfDescendants = 0;
- let comparator = (a, b) => {
- function getLevel(treeElement) {
- let level = 0;
- while (treeElement = treeElement.parent)
- level++;
- return level;
- }
+ let itemForRepresentedObject = this.getCachedTreeElement.bind(this);
+ let selectionComparator = WI.SelectionController.createTreeComparator(itemForRepresentedObject);
+ this._selectionController = new WI.SelectionController(this, selectionComparator);
- function compareSiblings(s, t) {
- return s.parent.children.indexOf(s) - s.parent.children.indexOf(t);
- }
-
- // Translate represented objects to TreeElements, which have the
- // hierarchical information needed to perform the comparison.
- a = this.getCachedTreeElement(a);
- b = this.getCachedTreeElement(b);
- if (!a || !b)
- return 0;
-
- if (a.parent === b.parent)
- return compareSiblings(a, b);
-
- let aLevel = getLevel(a);
- let bLevel = getLevel(b);
- while (aLevel > bLevel) {
- if (a.parent === b)
- return 1;
- a = a.parent;
- aLevel--;
- }
- while (bLevel > aLevel) {
- if (b.parent === a)
- return -1;
- b = b.parent;
- bLevel--;
- }
-
- while (a.parent !== b.parent) {
- a = a.parent;
- b = b.parent;
- }
-
- console.assert(a.parent === b.parent, "Missing common ancestor for TreeElements.", a, b);
- return compareSiblings(a, b);
- };
-
- this._selectionController = new WI.SelectionController(this, comparator);
-
this._itemWasSelectedByUser = false;
this._processingSelectionChange = false;
this._suppressNextSelectionDidChangeEvent = false;
@@ -770,11 +726,6 @@
// SelectionController delegate
- selectionControllerNumberOfItems(controller)
- {
- return this._cachedNumberOfDescendants;
- }
-
selectionControllerSelectionDidChange(controller, deselectedItems, selectedItems)
{
this._processingSelectionChange = true;