WebKit Bugzilla
Attachment 361719 Details for
Bug 193605
: Web Inspector: Frontend performance is very slow reloading theverge.com - 50% of time in TreeOutline _indexOfTreeElement
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Requests
|
Help
|
New Account
|
Log In
Remember
[x]
|
Forgot Password
Login:
[x]
[patch]
Patch
bug-193605-20190211153528.patch (text/plain), 87.46 KB, created by
Matt Baker
on 2019-02-11 15:35:28 PST
(
hide
)
Description:
Patch
Filename:
MIME Type:
Creator:
Matt Baker
Created:
2019-02-11 15:35:28 PST
Size:
87.46 KB
patch
obsolete
>Subversion Revision: 241256 >diff --git a/Source/WebInspectorUI/ChangeLog b/Source/WebInspectorUI/ChangeLog >index 35f37ac904ea7784a3128bd2cb1c7edddee57e1f..b3b88f85f302d9c04355b2b1feb65a1d18b89d2c 100644 >--- a/Source/WebInspectorUI/ChangeLog >+++ b/Source/WebInspectorUI/ChangeLog >@@ -1,3 +1,116 @@ >+2019-02-11 Matt Baker <mattbaker@apple.com> >+ >+ Web Inspector: Frontend performance is very slow reloading theverge.com - 50% of time in TreeOutline _indexOfTreeElement >+ https://bugs.webkit.org/show_bug.cgi?id=193605 >+ <rdar://problem/47403986> >+ >+ Reviewed by NOBODY (OOPS!). >+ >+ SelectionController should track an unordered Set of generic objects >+ instead of an ordered set of indexes. This eliminates the costly and >+ error-prone updates needed to keep the selected indexes in sync as items >+ are added and removed from TreeOutline (and Table, to a far lesser extent). >+ >+ The SelectionController interface is largely the same. Class and delegate >+ methods have been renamed to reflect the change from indexes to objects. >+ SelectionController tracks selected items in selection order. For the >+ operations that rely on objects being in insertion order, the controller >+ uses a comparator function provided at construction time. >+ >+ * UserInterface/Base/IndexSet.js: Removed. >+ No longer used. SelectionController now uses a plain Set. >+ >+ * UserInterface/Base/Utilities.js: >+ (value): >+ Add utilities previously supplied by IndexSet and used by SelectionController. >+ >+ * UserInterface/Controllers/SelectionController.js: >+ (WI.SelectionController): >+ (WI.SelectionController.prototype.get lastSelectedObject): >+ (WI.SelectionController.prototype.get selectedObjects): >+ (WI.SelectionController.prototype.set allowsMultipleSelection): >+ (WI.SelectionController.prototype.isSelected): >+ (WI.SelectionController.prototype.select): >+ (WI.SelectionController.prototype.deselect): >+ (WI.SelectionController.prototype.selectAll): >+ (WI.SelectionController.prototype.deselectAll): >+ (WI.SelectionController.prototype.willRemoveSelectedObjects): >+ (WI.SelectionController.prototype.reset): >+ (WI.SelectionController.prototype.didRemoveObjects): >+ (WI.SelectionController.prototype.handleKeyDown): >+ (WI.SelectionController.prototype.handleObjectMouseDown): >+ (WI.SelectionController.prototype._deselectAllAndSelect): >+ (WI.SelectionController.prototype._selectObjectsFromArrowKey): >+ (WI.SelectionController.prototype._firstSelectableObject): >+ (WI.SelectionController.prototype._lastSelectableObject): >+ (WI.SelectionController.prototype._previousSelectableObject): >+ (WI.SelectionController.prototype._nextSelectableObject): >+ (WI.SelectionController.prototype._updateSelectedObjects): >+ (WI.SelectionController.prototype._addObjectRange): >+ (WI.SelectionController.prototype._deleteObjectRange): >+ (WI.SelectionController.prototype.get lastSelectedItem): Deleted. >+ (WI.SelectionController.prototype.get selectedItems): Deleted. >+ (WI.SelectionController.prototype.get numberOfItems): Deleted. >+ (WI.SelectionController.prototype.hasSelectedItem): Deleted. >+ (WI.SelectionController.prototype.selectItem): Deleted. >+ (WI.SelectionController.prototype.deselectItem): Deleted. >+ (WI.SelectionController.prototype.removeSelectedItems): Deleted. >+ (WI.SelectionController.prototype.didInsertItem): Deleted. >+ (WI.SelectionController.prototype.didRemoveItems): Deleted. >+ (WI.SelectionController.prototype.handleItemMouseDown.normalizeRange): Deleted. >+ (WI.SelectionController.prototype.handleItemMouseDown): Deleted. >+ (WI.SelectionController.prototype._selectItemsFromArrowKey): Deleted. >+ (WI.SelectionController.prototype._nextSelectableIndex): Deleted. >+ (WI.SelectionController.prototype._previousSelectableIndex): Deleted. >+ (WI.SelectionController.prototype._updateSelectedItems): Deleted. >+ >+ * UserInterface/Main.html: >+ * UserInterface/Test.html: >+ Remove IndexSet. >+ >+ * UserInterface/Views/DOMTreeOutline.js: >+ >+ * UserInterface/Views/Table.js: >+ (WI.Table): >+ (WI.Table.prototype.get selectedRow): >+ (WI.Table.prototype.get selectedRows): >+ (WI.Table.prototype.isRowSelected): >+ (WI.Table.prototype.selectRow): >+ (WI.Table.prototype.deselectRow): >+ (WI.Table.prototype.removeRow): >+ (WI.Table.prototype.removeSelectedRows): >+ (WI.Table.prototype.selectionControllerSelectionDidChange): >+ (WI.Table.prototype.selectionControllerFirstSelectableObject): >+ (WI.Table.prototype.selectionControllerLastSelectableObject): >+ (WI.Table.prototype.selectionControllerNextSelectableObject): >+ (WI.Table.prototype.selectionControllerPreviousSelectableObject): >+ (WI.Table.prototype._handleMouseDown): >+ (WI.Table.prototype._removeRows): >+ (WI.Table.prototype.selectionControllerNumberOfItems): Deleted. >+ (WI.Table.prototype.selectionControllerNextSelectableIndex): Deleted. >+ (WI.Table.prototype.selectionControllerPreviousSelectableIndex): Deleted. >+ (WI.Table.prototype._toggleSelectedRowStyle): Deleted. >+ >+ * UserInterface/Views/TreeOutline.js: >+ (WI.TreeOutline.): >+ (WI.TreeOutline.compareSiblings): >+ (WI.TreeOutline): >+ (WI.TreeOutline.prototype.get selectedTreeElement): >+ (WI.TreeOutline.prototype.set selectedTreeElement): >+ (WI.TreeOutline.prototype.get selectedTreeElements): >+ (WI.TreeOutline.prototype.removeChildAtIndex): >+ (WI.TreeOutline.prototype.removeChildren): >+ (WI.TreeOutline.prototype._rememberTreeElement): >+ (WI.TreeOutline.prototype.selectionControllerSelectionDidChange): >+ (WI.TreeOutline.prototype.selectionControllerFirstSelectableObject): >+ (WI.TreeOutline.prototype.selectionControllerLastSelectableObject): >+ (WI.TreeOutline.prototype.selectionControllerNextSelectableObject): >+ (WI.TreeOutline.prototype.selectionControllerPreviousSelectableObject): >+ (WI.TreeOutline._generateStyleRulesIfNeeded): >+ (WI.TreeOutline.prototype.selectionControllerNextSelectableIndex): Deleted. >+ (WI.TreeOutline.prototype.selectionControllerPreviousSelectableIndex): Deleted. >+ (WI.TreeOutline._generateStyleRulesIfNeeded._indexesForSubtree.numberOfElementsInSubtree): Deleted. >+ > 2019-02-08 Devin Rousso <drousso@apple.com> > > Web Inspector: Audit: show keyboard shortcut in export tooltip >diff --git a/Source/WebInspectorUI/UserInterface/Base/IndexSet.js b/Source/WebInspectorUI/UserInterface/Base/IndexSet.js >deleted file mode 100644 >index 960432ce557dda42f3db716751dfedb31ab2271d..0000000000000000000000000000000000000000 >--- a/Source/WebInspectorUI/UserInterface/Base/IndexSet.js >+++ /dev/null >@@ -1,236 +0,0 @@ >-/* >- * Copyright (C) 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 >- * are met: >- * 1. Redistributions of source code must retain the above copyright >- * notice, this list of conditions and the following disclaimer. >- * 2. Redistributions in binary form must reproduce the above copyright >- * notice, this list of conditions and the following disclaimer in the >- * documentation and/or other materials provided with the distribution. >- * >- * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' >- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, >- * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR >- * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS >- * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR >- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF >- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS >- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN >- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) >- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF >- * THE POSSIBILITY OF SUCH DAMAGE. >- */ >- >-WI.IndexSet = class IndexSet >-{ >- constructor(values) >- { >- console.assert(!values || Array.isArray(values)); >- >- this._indexes = []; >- >- if (values) { >- for (let value of values.slice().sort((a, b) => a - b)) { >- if (value === this._indexes.lastValue) >- continue; >- if (this._validateIndex(value)) >- this._indexes.push(value); >- } >- } >- } >- >- // Public >- >- get size() { return this._indexes.length; } >- >- get firstIndex() >- { >- return this._indexes.length ? this._indexes[0] : NaN; >- } >- >- get lastIndex() >- { >- return this._indexes.length ? this._indexes.lastValue : NaN; >- } >- >- add(value) >- { >- if (!this._validateIndex(value)) >- return; >- >- let index = this._indexes.lowerBound(value); >- if (this._indexes[index] === value) >- return; >- >- this._indexes.insertAtIndex(value, index); >- } >- >- delete(value) >- { >- if (!this._validateIndex(value)) >- return false; >- >- if (!this.size) >- return false; >- >- let index = this._indexes.lowerBound(value); >- if (index === this._indexes.length) >- return false; >- this._indexes.splice(index, 1); >- return true; >- } >- >- has(value) >- { >- if (!this.size) >- return false; >- >- let index = this._indexes.lowerBound(value); >- return this._indexes[index] === value; >- } >- >- addRange(startIndex, count) >- { >- if (!this._validateIndex(startIndex)) >- return; >- >- console.assert(count > 0); >- if (count <= 0) >- return; >- >- if (count === 1) { >- this.add(startIndex); >- return; >- } >- >- let range = new Array(count); >- for (let i = 0; i < count; ++i) >- range[i] = startIndex + i; >- >- if (!this.size || (this.firstIndex >= range[0] && this.lastIndex <= range.lastValue)) { >- this._indexes = range; >- return; >- } >- >- let start = this._indexes.lowerBound(startIndex); >- let numberToDelete = this._indexes.upperBound(range.lastValue) - start; >- this._indexes.splice(start, numberToDelete, ...range); >- } >- >- deleteRange(startIndex, count) >- { >- if (!this._validateIndex(startIndex)) >- return; >- >- console.assert(count > 0); >- if (count <= 0) >- return; >- >- if (!this.size) >- return; >- >- if (count === 1) { >- this.delete(startIndex); >- return; >- } >- >- let lastIndex = startIndex + count - 1; >- if (this.firstIndex >= startIndex && this.lastIndex <= lastIndex) { >- this.clear(); >- return; >- } >- >- let start = this._indexes.lowerBound(startIndex); >- let numberToDelete = this._indexes.upperBound(lastIndex) - start; >- this._indexes.splice(start, numberToDelete); >- } >- >- clear() >- { >- this._indexes = []; >- } >- >- copy() >- { >- let indexSet = new WI.IndexSet; >- indexSet._indexes = this._indexes.slice(); >- return indexSet; >- } >- >- equals(indexSet) >- { >- console.assert(indexSet instanceof WI.IndexSet); >- if (!(indexSet instanceof WI.IndexSet)) >- return false; >- >- if (indexSet === this) >- return true; >- >- return Array.shallowEqual(this._indexes, indexSet._indexes); >- } >- >- difference(indexSet) >- { >- console.assert(indexSet instanceof WI.IndexSet); >- >- if (indexSet === this) >- return new WI.IndexSet; >- >- let result = new WI.IndexSet; >- result._indexes = this._indexes.filter((value) => !indexSet.has(value)); >- return result; >- } >- >- indexGreaterThan(value) >- { >- const following = true; >- return this._indexClosestTo(value, following); >- } >- >- indexLessThan(value) >- { >- const following = false; >- return this._indexClosestTo(value, following); >- } >- >- [Symbol.iterator]() >- { >- return this._indexes[Symbol.iterator](); >- } >- >- // Private >- >- _indexClosestTo(value, following) >- { >- if (!this.size) >- return NaN; >- >- if (this.size === 1) { >- if (following) >- return this.firstIndex > value ? this.firstIndex : NaN; >- return this.firstIndex < value ? this.firstIndex : NaN; >- } >- >- let offset = following ? 1 : -1; >- let position = this._indexes.lowerBound(value + offset); >- >- let closestIndex = this._indexes[position]; >- if (closestIndex === undefined) >- return NaN; >- >- if (value === closestIndex) >- return NaN; >- >- if (!following && closestIndex > value) >- return NaN; >- return closestIndex; >- } >- >- _validateIndex(value) >- { >- console.assert(Number.isInteger(value) && value >= 0, "Index must be a non-negative integer."); >- return Number.isInteger(value) && value >= 0; >- } >-}; >diff --git a/Source/WebInspectorUI/UserInterface/Base/Utilities.js b/Source/WebInspectorUI/UserInterface/Base/Utilities.js >index 93a4f9ea904b6a9b0cd3e55a7c3e71b8ef9059b6..eab7c86d36ee6dda96f122c29376fbd3e07f98a0 100644 >--- a/Source/WebInspectorUI/UserInterface/Base/Utilities.js >+++ b/Source/WebInspectorUI/UserInterface/Base/Utilities.js >@@ -118,6 +118,39 @@ Object.defineProperty(Map.prototype, "take", > } > }); > >+Object.defineProperty(Set.prototype, "equals", >+{ >+ value(other) >+ { >+ if (this.size !== other.size) >+ return false; >+ >+ for (let item of this) { >+ if (!other.has(item)) >+ return false; >+ } >+ >+ return true; >+ } >+}); >+ >+Object.defineProperty(Set.prototype, "difference", >+{ >+ value(other) >+ { >+ if (other === this) >+ return new Set; >+ >+ let result = new Set; >+ for (let item of this) { >+ if (!other.has(item)) >+ result.add(item); >+ } >+ >+ return result; >+ } >+}); >+ > Object.defineProperty(Set.prototype, "intersects", > { > value(other) >diff --git a/Source/WebInspectorUI/UserInterface/Controllers/SelectionController.js b/Source/WebInspectorUI/UserInterface/Controllers/SelectionController.js >index 75cbcf50bd3dacab08e451f11831c94e4de25caa..b52ca86ab23de41ad404128599965669bb40622c 100644 >--- a/Source/WebInspectorUI/UserInterface/Controllers/SelectionController.js >+++ b/Source/WebInspectorUI/UserInterface/Controllers/SelectionController.js >@@ -1,5 +1,5 @@ > /* >- * Copyright (C) 2018 Apple Inc. All Rights Reserved. >+ * Copyright (C) 2018, 2019 Apple Inc. All Rights Reserved. > * > * Redistribution and use in source and binary forms, with or without > * modification, are permitted provided that the following conditions >@@ -25,30 +25,34 @@ > > WI.SelectionController = class SelectionController extends WI.Object > { >- constructor(delegate) >+ constructor(delegate, comparator) > { > super(); > > console.assert(delegate); >+ >+ this._comparator = comparator; >+ if (!this._comparator) >+ this._comparator = (a, b) => a - b; >+ > this._delegate = delegate; > > this._allowsEmptySelection = true; > this._allowsMultipleSelection = false; >- this._lastSelectedIndex = NaN; >- this._shiftAnchorIndex = NaN; >- this._selectedIndexes = new WI.IndexSet; >+ this._lastSelectedObject = null; >+ this._shiftAnchorObject = null; >+ this._selectedObjects = new Set; > this._suppressSelectionDidChange = false; > >- console.assert(this._delegate.selectionControllerNumberOfItems, "SelectionController delegate must implement selectionControllerNumberOfItems."); >- console.assert(this._delegate.selectionControllerNextSelectableIndex, "SelectionController delegate must implement selectionControllerNextSelectableIndex."); >- console.assert(this._delegate.selectionControllerPreviousSelectableIndex, "SelectionController delegate must implement selectionControllerPreviousSelectableIndex."); >+ console.assert(this._delegate.selectionControllerNextSelectableObject, "SelectionController delegate must implement selectionControllerNextSelectableObject."); >+ console.assert(this._delegate.selectionControllerPreviousSelectableObject, "SelectionController delegate must implement selectionControllerPreviousSelectableObject."); > } > > // Public > > get delegate() { return this._delegate; } >- get lastSelectedItem() { return this._lastSelectedIndex; } >- get selectedItems() { return this._selectedIndexes; } >+ get lastSelectedObject() { return this._lastSelectedObject; } >+ get selectedObjects() { return this._selectedObjects; } > > get allowsEmptySelection() { return this._allowsEmptySelection; } > set allowsEmptySelection(flag) { this._allowsEmptySelection = flag; } >@@ -67,225 +71,147 @@ WI.SelectionController = class SelectionController extends WI.Object > if (this._allowsMultipleSelection) > return; > >- if (this._selectedIndexes.size > 1) { >- console.assert(this._lastSelectedIndex >= 0); >- this._updateSelectedItems(new WI.IndexSet([this._lastSelectedIndex])); >+ if (this._selectedObjects.size > 1) { >+ let newObjects = new Set; >+ newObjects.add(this._lastSelectedObject); >+ this._updateSelectedObjects(newObjects); > } > } > >- get numberOfItems() >- { >- return this._delegate.selectionControllerNumberOfItems(this); >- } >- >- hasSelectedItem(index) >+ isSelected(object) > { >- return this._selectedIndexes.has(index); >+ return this._selectedObjects.has(object); > } > >- selectItem(index, extendSelection = false) >+ select(object, extendSelection = false) > { > console.assert(!extendSelection || this._allowsMultipleSelection, "Cannot extend selection with multiple selection disabled."); >- console.assert(index >= 0 && index < this.numberOfItems); > > if (!this._allowsMultipleSelection) > extendSelection = false; > >- if (this.hasSelectedItem(index)) { >+ if (this.isSelected(object)) { > if (!extendSelection) >- this._deselectAllAndSelect(index); >+ this._deselectAllAndSelect(object); > return; > } > >- let newSelectedItems = extendSelection ? this._selectedIndexes.copy() : new WI.IndexSet; >- newSelectedItems.add(index); >+ this._lastSelectedObject = object; >+ this._shiftAnchorObject = null; > >- this._shiftAnchorIndex = NaN; >- this._lastSelectedIndex = index; >+ let newObjects = new Set(extendSelection ? this._selectedObjects : null); >+ newObjects.add(object); > >- this._updateSelectedItems(newSelectedItems); >+ this._updateSelectedObjects(newObjects); > } > >- deselectItem(index) >+ deselect(object) > { >- console.assert(index >= 0 && index < this.numberOfItems); >- >- if (!this.hasSelectedItem(index)) >+ if (!this.isSelected(object)) > return; > >- if (!this._allowsEmptySelection && this._selectedIndexes.size === 1) >+ if (!this._allowsEmptySelection && this._selectedObjects.size === 1) > return; > >- let newSelectedItems = this._selectedIndexes.copy(); >- newSelectedItems.delete(index); >- >- if (this._shiftAnchorIndex === index) >- this._shiftAnchorIndex = NaN; >- >- if (this._lastSelectedIndex === index) { >- this._lastSelectedIndex = NaN; >- if (newSelectedItems.size) { >- // Find selected item closest to deselected item. >- let preceding = newSelectedItems.indexLessThan(index); >- let following = newSelectedItems.indexGreaterThan(index); >- >- if (isNaN(preceding)) >- this._lastSelectedIndex = following; >- else if (isNaN(following)) >- this._lastSelectedIndex = preceding; >- else { >- if ((following - index) < (index - preceding)) >- this._lastSelectedIndex = following; >- else >- this._lastSelectedIndex = preceding; >+ let newObjects = new Set(this._selectedObjects); >+ newObjects.delete(object); >+ >+ if (this._lastSelectedObject === object) { >+ this._lastSelectedObject = null; >+ >+ if (newObjects.size) { >+ // Find selected object closest to deselected object. >+ let previous = this._previousSelectableObject(object); >+ let next = this._nextSelectableObject(object); >+ >+ while (!this.isSelected(previous) && !this.isSelected(next)) { >+ previous = this._previousSelectableObject(previous); >+ next = this._nextSelectableObject(next); > } >+ >+ if (this.isSelected(previous)) >+ this._lastSelectedObject = previous; >+ else if (this.isSelected(next)) >+ this._lastSelectedObject = next; > } > } > >- this._updateSelectedItems(newSelectedItems); >+ if (this._shiftAnchorObject === object) >+ this._shiftAnchorObject = null; >+ >+ this._updateSelectedObjects(newObjects); > } > > selectAll() > { >- if (!this.numberOfItems || !this._allowsMultipleSelection) >- return; >- >- if (this._selectedIndexes.size === this.numberOfItems) >+ if (!this._allowsMultipleSelection) > return; > >- let newSelectedItems = new WI.IndexSet; >- newSelectedItems.addRange(0, this.numberOfItems); >+ let newObjects = new Set; >+ this._addObjectRange(newObjects, this._firstSelectableObject(), null); > >- this._lastSelectedIndex = newSelectedItems.lastIndex; >- if (isNaN(this._shiftAnchorIndex)) >- this._shiftAnchorIndex = this._lastSelectedIndex; >+ this._lastSelectedObject = Array.from(newObjects).lastValue; >+ if (!this._shiftAnchorObject) >+ this._shiftAnchorObject = this._lastSelectedObject; > >- this._updateSelectedItems(newSelectedItems); >+ this._updateSelectedObjects(newObjects); > } > > deselectAll() > { >- const index = NaN; >- this._deselectAllAndSelect(index); >+ this._deselectAllAndSelect(null); > } > >- removeSelectedItems() >+ willRemoveSelectedObjects() > { >- let numberOfSelectedItems = this._selectedIndexes.size; >- if (!numberOfSelectedItems) >+ if (!this._selectedObjects.size) > return; > >- // Try selecting the item following the selection. >- let lastSelectedIndex = this._selectedIndexes.lastIndex; >- let indexToSelect = this._nextSelectableIndex(lastSelectedIndex); >- if (isNaN(indexToSelect)) { >- // If no item exists after the last item in the selection, try selecting >- // a deselected item (hole) within the selection. >- let firstSelectedIndex = this._selectedIndexes.firstIndex; >- if (lastSelectedIndex - firstSelectedIndex > numberOfSelectedItems) { >- indexToSelect = this._nextSelectableIndex(firstSelectedIndex); >- while (this._selectedIndexes.has(indexToSelect)) >- indexToSelect = this._nextSelectableIndex(firstSelectedIndex); >- } else { >- // If the selection contains no holes, try selecting the item >+ let orderedSelection = Array.from(this._selectedObjects).sort(this._comparator); >+ >+ // Try selecting the object following the selection. >+ let lastSelectedObject = orderedSelection.lastValue; >+ let objectToSelect = this._nextSelectableObject(lastSelectedObject); >+ if (!objectToSelect) { >+ // If no object exists after the last object in the selection, try selecting >+ // a deselected object (hole) within the selection. >+ objectToSelect = orderedSelection[0]; >+ while (objectToSelect && this.isSelected(objectToSelect)) >+ objectToSelect = this._nextSelectableObject(objectToSelect); >+ >+ if (!objectToSelect || this.isSelected(objectToSelect)) { >+ // If the selection contains no holes, try selecting the object > // preceding the selection. >- indexToSelect = firstSelectedIndex > 0 ? this._previousSelectableIndex(firstSelectedIndex) : NaN; >+ objectToSelect = this._previousSelectableObject(orderedSelection[0]); > } > } > >- this._deselectAllAndSelect(indexToSelect); >+ this._deselectAllAndSelect(objectToSelect); > } > > reset() > { >- this._shiftAnchorIndex = NaN; >- this._lastSelectedIndex = NaN; >- this._selectedIndexes.clear(); >- } >- >- didInsertItem(index) >- { >- let current = this._selectedIndexes.lastIndex; >- while (current >= index) { >- this._selectedIndexes.delete(current); >- this._selectedIndexes.add(current + 1); >- >- current = this._selectedIndexes.indexLessThan(current); >- } >- >- if (this._lastSelectedIndex >= index) >- this._lastSelectedIndex += 1; >- if (this._shiftAnchorIndex >= index) >- this._shiftAnchorIndex += 1; >+ this._lastSelectedObject = null; >+ this._shiftAnchorObject = null; >+ this._selectedObjects.clear(); > } > >- didRemoveItems(indexes) >+ didRemoveObjects(objects) > { >- if (!indexes) >- return; >- >- console.assert(indexes instanceof WI.IndexSet); >- >- if (!indexes.size || !this._selectedIndexes.size) >- return; >+ console.assert(objects instanceof Set); > >- let firstRemovedIndex = indexes.firstIndex; >- if (this._selectedIndexes.lastIndex < firstRemovedIndex) >+ if (!objects.size || !this._selectedObjects.size) > return; > >- let newSelectedIndexes = new WI.IndexSet; >- >- let lastRemovedIndex = indexes.lastIndex; >- if (this._selectedIndexes.firstIndex < lastRemovedIndex) { >- let removedCount = 0; >- let removedIndex = firstRemovedIndex; >- >- this._suppressSelectionDidChange = true; >- >- // Adjust the selected indexes that are in the range between the >- // first and last removed index (inclusive). >- for (let current = this._selectedIndexes.firstIndex; current < lastRemovedIndex; current = this._selectedIndexes.indexGreaterThan(current)) { >- if (this.hasSelectedItem(current)) { >- this.deselectItem(current); >- removedCount++; >- continue; >- } >- >- while (removedIndex < current) { >- removedCount++; >- removedIndex = indexes.indexGreaterThan(removedIndex); >- } >- >- let newIndex = current - removedCount; >- newSelectedIndexes.add(newIndex); >- >- if (this._lastSelectedIndex === current) >- this._lastSelectedIndex = newIndex; >- if (this._shiftAnchorIndex === current) >- this._shiftAnchorIndex = newIndex; >- } >- >- this._suppressSelectionDidChange = false; >- } >+ let newObjects = new Set(this._selectedObjects); >+ for (let object of objects) >+ newObjects.delete(object); > >- let removedCount = indexes.size; >- let current = lastRemovedIndex; >- while (current = this._selectedIndexes.indexGreaterThan(current)) >- newSelectedIndexes.add(current - removedCount); >- >- if (this._lastSelectedIndex > lastRemovedIndex) >- this._lastSelectedIndex -= removedCount; >- if (this._shiftAnchorIndex > lastRemovedIndex) >- this._shiftAnchorIndex -= removedCount; >- >- this._selectedIndexes = newSelectedIndexes; >+ this._updateSelectedObjects(newObjects); > } > > handleKeyDown(event) > { >- if (!this.numberOfItems) >- return false; >- > if (event.key === "a" && event.commandOrControlKey) { > this.selectAll(); > return true; >@@ -295,7 +221,7 @@ WI.SelectionController = class SelectionController extends WI.Object > return false; > > if (event.keyIdentifier === "Up" || event.keyIdentifier === "Down") { >- this._selectItemsFromArrowKey(event.keyIdentifier === "Up", event.shiftKey); >+ this._selectObjectsFromArrowKey(event.keyIdentifier === "Up", event.shiftKey); > > event.preventDefault(); > event.stopPropagation(); >@@ -305,7 +231,7 @@ WI.SelectionController = class SelectionController extends WI.Object > return false; > } > >- handleItemMouseDown(index, event) >+ handleObjectMouseDown(object, event) > { > if (event.button !== 0 || event.ctrlKey) > return; >@@ -313,141 +239,174 @@ WI.SelectionController = class SelectionController extends WI.Object > // Command (macOS) or Control (Windows) key takes precedence over shift > // whether or not multiple selection is enabled, so handle it first. > if (event.commandOrControlKey) { >- if (this.hasSelectedItem(index)) >- this.deselectItem(index); >+ if (this.isSelected(object)) >+ this.deselect(object); > else >- this.selectItem(index, this._allowsMultipleSelection); >+ this.select(object, this._allowsMultipleSelection); > return; > } > > let shiftExtendSelection = this._allowsMultipleSelection && event.shiftKey; > if (!shiftExtendSelection) { >- this.selectItem(index); >+ this.select(object); > return; > } > >- let newSelectedItems = this._selectedIndexes.copy(); >+ let newObjects = new Set(this._selectedObjects); > > // Shift-clicking when nothing is selected should cause the first item > // through the clicked item to be selected. >- if (!newSelectedItems.size) { >- this._shiftAnchorIndex = 0; >- this._lastSelectedIndex = index; >- newSelectedItems.addRange(0, index + 1); >- this._updateSelectedItems(newSelectedItems); >+ if (!newObjects.size) { >+ this._lastSelectedObject = object; >+ this._shiftAnchorObject = this._firstSelectableObject(); >+ >+ this._addObjectRange(newObjects, this._firstSelectableObject(), object); >+ this._updateSelectedObjects(newObjects); > return; > } > >- if (isNaN(this._shiftAnchorIndex)) >- this._shiftAnchorIndex = this._lastSelectedIndex; >+ if (!this._shiftAnchorObject) >+ this._shiftAnchorObject = this._lastSelectedObject; > > // Shift-clicking will add to or delete from the current selection, or > // pivot the selection around the anchor (a delete followed by an add). >- // We could check for all three cases, and add or delete only those items >+ // We could check for all three cases, and add or delete only those objects > // that are necessary, but it is simpler to throw out the previous shift- >- // selected range and add the new range between the anchor and clicked item. >+ // selected range and add the new range between the anchor and clicked object. > >- function normalizeRange(startIndex, endIndex) { >- return startIndex > endIndex ? [endIndex, startIndex] : [startIndex, endIndex]; >+ let sortObjectPair = (object1, object2) => { >+ return [object1, object2].sort(this._comparator); > } > >- if (this._shiftAnchorIndex !== this._lastSelectedIndex) { >- let [startIndex, endIndex] = normalizeRange(this._shiftAnchorIndex, this._lastSelectedIndex); >- newSelectedItems.deleteRange(startIndex, endIndex - startIndex + 1); >+ if (this._shiftAnchorObject !== this._lastSelectedObject) { >+ let [startObject, endObject] = sortObjectPair(this._shiftAnchorObject, this._lastSelectedObject); >+ this._deleteObjectRange(newObjects, startObject, endObject); > } > >- let [startIndex, endIndex] = normalizeRange(this._shiftAnchorIndex, index); >- newSelectedItems.addRange(startIndex, endIndex - startIndex + 1); >+ let [startObject, endObject] = sortObjectPair(this._shiftAnchorObject, object); >+ this._addObjectRange(newObjects, startObject, endObject); > >- this._lastSelectedIndex = index; >+ this._lastSelectedObject = object; > >- this._updateSelectedItems(newSelectedItems); >+ this._updateSelectedObjects(newObjects); > } > > // Private > >- _deselectAllAndSelect(index) >+ _deselectAllAndSelect(object) > { >- if (!this._selectedIndexes.size) >+ if (!this._selectedObjects.size) > return; > >- if (this._selectedIndexes.size === 1 && this._selectedIndexes.firstIndex === index) >+ if (this._selectedObjects.size === 1 && this.isSelected(object)) > return; > >- this._shiftAnchorIndex = NaN; >- this._lastSelectedIndex = index; >+ this._lastSelectedObject = object; >+ this._shiftAnchorObject = null; > >- let newSelectedItems = new WI.IndexSet; >- if (!isNaN(index)) >- newSelectedItems.add(index); >+ let newSelectedObjects = new Set; >+ if (object) >+ newSelectedObjects.add(object); > >- this._updateSelectedItems(newSelectedItems); >+ this._updateSelectedObjects(newSelectedObjects); > } > >- _selectItemsFromArrowKey(goingUp, shiftKey) >+ _selectObjectsFromArrowKey(goingUp, shiftKey) > { >- if (!this._selectedIndexes.size) { >- let index = goingUp ? this.numberOfItems - 1 : 0; >- this.selectItem(index); >+ if (!this._selectedObjects.size) { >+ let object = goingUp ? this._lastSelectableObject() : this._firstSelectableObject(); >+ this.select(object); > return; > } > >- let index = goingUp ? this._previousSelectableIndex(this._lastSelectedIndex) : this._nextSelectableIndex(this._lastSelectedIndex); >- if (isNaN(index)) >+ let object = goingUp ? this._previousSelectableObject(this._lastSelectedObject) : this._nextSelectableObject(this._lastSelectedObject); >+ if (!object) > return; > > let extendSelection = shiftKey && this._allowsMultipleSelection; >- if (!extendSelection || !this.hasSelectedItem(index)) { >- this.selectItem(index, extendSelection); >+ if (!extendSelection || !this.isSelected(object)) { >+ this.select(object, extendSelection); > return; > } > >- // 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 priorIndex = goingUp ? this._nextSelectableIndex(this._lastSelectedIndex) : this._previousSelectableIndex(this._lastSelectedIndex); >- if (!this.hasSelectedItem(priorIndex)) { >- this.deselectItem(this._lastSelectedIndex); >+ // Since the object in the direction of movement is selected, we are either >+ // extending the selection into the object, or deselecting. Determine which >+ // by checking whether the object opposite the anchor object is selected. >+ let priorObject = goingUp ? this._nextSelectableObject(this._lastSelectedObject) : this._previousSelectableObject(this._lastSelectedObject); >+ if (!this.isSelected(priorObject)) { >+ this.deselect(this._lastSelectedObject); > return; > } > >- // The selection is being extended into the item; make it the new >- // anchor item then continue searching in the direction of movement >- // for an unselected item to select. >- while (!isNaN(index)) { >- if (!this.hasSelectedItem(index)) { >- this.selectItem(index, extendSelection); >+ // The selection is being extended into the object; make it the new >+ // anchor object then continue searching in the direction of movement >+ // for an unselected object to select. >+ while (object) { >+ if (!this.isSelected(object)) { >+ this.select(object, extendSelection); > break; > } > >- this._lastSelectedIndex = index; >- index = goingUp ? this._previousSelectableIndex(index) : this._nextSelectableIndex(index); >+ this._lastSelectedObject = object; >+ object = goingUp ? this._previousSelectableObject(object) : this._nextSelectableObject(object); > } > } > >- _nextSelectableIndex(index) >+ _firstSelectableObject() > { >- return this._delegate.selectionControllerNextSelectableIndex(this, index); >+ return this._delegate.selectionControllerFirstSelectableObject(this); > } > >- _previousSelectableIndex(index) >+ _lastSelectableObject() > { >- return this._delegate.selectionControllerPreviousSelectableIndex(this, index); >+ return this._delegate.selectionControllerLastSelectableObject(this); > } > >- _updateSelectedItems(indexes) >+ _previousSelectableObject(object) > { >- if (this._selectedIndexes.equals(indexes)) >+ return this._delegate.selectionControllerPreviousSelectableObject(this, object); >+ } >+ >+ _nextSelectableObject(object) >+ { >+ return this._delegate.selectionControllerNextSelectableObject(this, object); >+ } >+ >+ _updateSelectedObjects(newObjects) >+ { >+ if (this._selectedObjects.equals(newObjects)) > return; > >- let oldSelectedIndexes = this._selectedIndexes.copy(); >- this._selectedIndexes = indexes; >+ let oldSelectedObjects = new Set(this._selectedObjects); >+ this._selectedObjects = newObjects; > > if (this._suppressSelectionDidChange || !this._delegate.selectionControllerSelectionDidChange) > return; > >- let deselectedItems = oldSelectedIndexes.difference(indexes); >- let selectedItems = indexes.difference(oldSelectedIndexes); >- this._delegate.selectionControllerSelectionDidChange(this, deselectedItems, selectedItems); >+ let deselected = oldSelectedObjects.difference(newObjects); >+ let selected = newObjects.difference(oldSelectedObjects); >+ this._delegate.selectionControllerSelectionDidChange(this, deselected, selected); >+ } >+ >+ _addObjectRange(objects, firstObject, lastObject) >+ { >+ let current = firstObject; >+ while (current) { >+ objects.add(current); >+ if (current === lastObject) >+ break; >+ current = this._nextSelectableObject(current); >+ } >+ } >+ >+ _deleteObjectRange(objects, firstObject, lastObject) >+ { >+ let current = firstObject; >+ while (current) { >+ objects.delete(current); >+ if (current === lastObject) >+ break; >+ current = this._nextSelectableObject(current); >+ } > } > }; >diff --git a/Source/WebInspectorUI/UserInterface/Main.html b/Source/WebInspectorUI/UserInterface/Main.html >index c97d00d2bbf2b3ef1894dac3e5c7a64f837922d3..a1ab0d12d71d495b79f7e1af31dc38fa00854b09 100644 >--- a/Source/WebInspectorUI/UserInterface/Main.html >+++ b/Source/WebInspectorUI/UserInterface/Main.html >@@ -275,7 +275,6 @@ > <script src="Base/WebInspector.js"></script> > <script src="Base/Platform.js"></script> > <script src="Base/DebuggableType.js"></script> >- <script src="Base/IndexSet.js"></script> > <script src="Base/LinkedList.js"></script> > <script src="Base/ListMultimap.js"></script> > <script src="Base/Object.js"></script> >diff --git a/Source/WebInspectorUI/UserInterface/Test.html b/Source/WebInspectorUI/UserInterface/Test.html >index 06a0d84a8a330472ae04c7529fd92babd97737f5..3c474c54aed0d188ece5654ed661f51dec2b2e18 100644 >--- a/Source/WebInspectorUI/UserInterface/Test.html >+++ b/Source/WebInspectorUI/UserInterface/Test.html >@@ -38,7 +38,6 @@ > <script src="Base/WebInspector.js"></script> > <script src="Base/Platform.js"></script> > <script src="Base/DebuggableType.js"></script> >- <script src="Base/IndexSet.js"></script> > <script src="Base/LinkedList.js"></script> > <script src="Base/ListMultimap.js"></script> > <script src="Base/Object.js"></script> >diff --git a/Source/WebInspectorUI/UserInterface/Views/DOMTreeOutline.js b/Source/WebInspectorUI/UserInterface/Views/DOMTreeOutline.js >index 7bfa538bceb19a1526079c9bb4055aaa2e16b7f7..332b9b74e4250bf4d71893cd3ed478c217c5b3f0 100644 >--- a/Source/WebInspectorUI/UserInterface/Views/DOMTreeOutline.js >+++ b/Source/WebInspectorUI/UserInterface/Views/DOMTreeOutline.js >@@ -285,7 +285,7 @@ WI.DOMTreeOutline = class DOMTreeOutline extends WI.TreeOutline > return false; > > let selectedTreeElements = this.selectedTreeElements; >- this._selectionController.removeSelectedItems(); >+ this._selectionController.willRemoveSelectedObjects(); > > let levelMap = new Map; > >diff --git a/Source/WebInspectorUI/UserInterface/Views/Table.js b/Source/WebInspectorUI/UserInterface/Views/Table.js >index 0f7c99baad357126ce9d3d073b37bdb5207865ed..b5c43198a2202d43581d1f7438c4eb98f5445968 100644 >--- a/Source/WebInspectorUI/UserInterface/Views/Table.js >+++ b/Source/WebInspectorUI/UserInterface/Views/Table.js >@@ -87,7 +87,7 @@ WI.Table = class Table extends WI.View > this._columnWidths = null; // Calculated in _resizeColumnsAndFiller. > this._fillerHeight = 0; // Calculated in _resizeColumnsAndFiller. > >- this._selectionController = new WI.SelectionController(this); >+ this._selectionController = new WI.SelectionController(this, (a, b) => a.__index - b.__index); > > this._resizers = []; > this._currentResizer = null; >@@ -125,12 +125,16 @@ WI.Table = class Table extends WI.View > > get selectedRow() > { >- return this._selectionController.lastSelectedItem; >+ let row = this._selectionController.lastSelectedObject; >+ return row ? row.__index : NaN; > } > > get selectedRows() > { >- return Array.from(this._selectionController.selectedItems); >+ let rowIndexes = []; >+ for (let row of this._selectionController.selectedObjects) >+ rowIndexes.push(row.__index); >+ return rowIndexes; > } > > get scrollContainer() { return this._scrollContainerElement; } >@@ -236,7 +240,7 @@ WI.Table = class Table extends WI.View > > isRowSelected(rowIndex) > { >- return this._selectionController.hasSelectedItem(rowIndex); >+ return this._selectionController.isSelected(this._cachedRows.get(rowIndex)); > } > > reloadData() >@@ -313,12 +317,12 @@ WI.Table = class Table extends WI.View > > selectRow(rowIndex, extendSelection = false) > { >- this._selectionController.selectItem(rowIndex, extendSelection); >+ this._selectionController.select(this._cachedRows.get(rowIndex), extendSelection); > } > > deselectRow(rowIndex) > { >- this._selectionController.deselectItem(rowIndex); >+ this._selectionController.deselect(this._cachedRows.get(rowIndex)); > } > > selectAll() >@@ -335,23 +339,28 @@ WI.Table = class Table extends WI.View > { > console.assert(rowIndex >= 0 && rowIndex < this.numberOfRows); > >+ let row = this._cachedRows.get(rowIndex); >+ > if (this.isRowSelected(rowIndex)) >- this.deselectRow(rowIndex); >+ this.deselect(row); > >- let rowIndexes = new WI.IndexSet([rowIndex]); >- this._removeRows(rowIndexes); >+ this._removeRows([row]); > } > > removeSelectedRows() > { >+ let selectedObjects = this._selectionController.selectedObjects; >+ if (!selectedObjects.size) >+ return; >+ >+ let oldObjects = new Set(selectedObjects); >+ > // Change the selection before removing rows. This matches the behavior > // of macOS Finder (in list and column modes) when removing selected items. >- let oldSelectedItems = this._selectionController.selectedItems.copy(); >- >- this._selectionController.removeSelectedItems(); >+ this._selectionController.willRemoveSelectedObjects(); > >- if (!oldSelectedItems.equals(this._selectionController.selectedItems)) >- this._removeRows(oldSelectedItems); >+ if (!oldObjects.equals(this._selectionController.selectedObjects)) >+ this._removeRows(Array.from(oldObjects)); > } > > revealRow(rowIndex) >@@ -595,15 +604,16 @@ WI.Table = class Table extends WI.View > > // SelectionController delegate > >- selectionControllerSelectionDidChange(controller, deselectedItems, selectedItems) >+ selectionControllerSelectionDidChange(controller, deselectedObjects, selectedObjects) > { >- if (deselectedItems.size) >- this._toggleSelectedRowStyle(deselectedItems, false); >- if (selectedItems.size) >- this._toggleSelectedRowStyle(selectedItems, true); >+ for (let row of deselectedObjects) >+ row.classList.toggle("selected", false); > >- if (selectedItems.size === 1) { >- let rowIndex = selectedItems.firstIndex; >+ for (let row of selectedObjects) >+ row.classList.toggle("selected", true); >+ >+ if (selectedObjects.size === 1) { >+ let rowIndex = Array.from(selectedObjects)[0].__index; > if (!this._isRowVisible(rowIndex)) > this.revealRow(rowIndex); > } >@@ -612,23 +622,32 @@ WI.Table = class Table extends WI.View > this._delegate.tableSelectionDidChange(this); > } > >- selectionControllerNumberOfItems(controller) >+ selectionControllerFirstSelectableObject() > { >- return this.numberOfRows; >+ // FIXME: needs to work for uncached rows >+ return this._cachedRows.get(0); > } > >- selectionControllerNextSelectableIndex(controller, index) >+ selectionControllerLastSelectableObject() > { >- if (index >= this.numberOfRows - 1) >- return NaN; >- return index + 1; >+ // FIXME: needs to work for uncached rows >+ return this._cachedRows.get(this.numberOfRows - 1); >+ } >+ >+ selectionControllerNextSelectableObject(controller, object) >+ { >+ let index = object.__index; >+ console.assert(index >= 0 && index < this.numberOfRows); >+ >+ return this._cachedRows.get(index + 1) || null; > } > >- selectionControllerPreviousSelectableIndex(controller, index) >+ selectionControllerPreviousSelectableObject(controller, object) > { >- if (index <= 0) >- return NaN; >- return index - 1; >+ let index = object.__index; >+ console.assert(index >= 0 && index < this.numberOfRows); >+ >+ return this._cachedRows.get(index - 1) || null; > } > > // Resizer delegate >@@ -1295,7 +1314,7 @@ WI.Table = class Table extends WI.View > return; > } > >- this._selectionController.handleItemMouseDown(rowIndex, event); >+ this._selectionController.handleObjectMouseDown(row, event); > } > > _handleContextMenu(event) >@@ -1374,7 +1393,7 @@ WI.Table = class Table extends WI.View > } > } > >- _removeRows(rowIndexes) >+ _removeRows(rows) > { > let removed = 0; > >@@ -1387,8 +1406,20 @@ WI.Table = class Table extends WI.View > } > }; > >- for (let index = rowIndexes.firstIndex; index <= rowIndexes.lastIndex; ++index) { >- if (rowIndexes.has(index)) { >+ let firstIndex = NaN; >+ let lastIndex = NaN; >+ for (let row of rows) { >+ let index = row.__index; >+ if (isNaN(firstIndex) || firstIndex > index) >+ firstIndex = index; >+ if (isNaN(lastIndex) || lastIndex < index) >+ lastIndex = index; >+ } >+ >+ let removedIndexes = new Set(rows.map((row) => row.__index)); >+ >+ for (let index = firstIndex; index <= lastIndex; ++index) { >+ if (removedIndexes.has(index)) { > let row = this._cachedRows.get(index); > if (row) { > this._cachedRows.delete(index); >@@ -1405,28 +1436,17 @@ WI.Table = class Table extends WI.View > if (!removed) > return; > >- for (let index = rowIndexes.lastIndex + 1; index < this._cachedNumberOfRows; ++index) >+ for (let index = lastIndex + 1; index < this._cachedNumberOfRows; ++index) > adjustRowAtIndex(index); > > this._cachedNumberOfRows -= removed; > console.assert(this._cachedNumberOfRows >= 0); > >- this._selectionController.didRemoveItems(rowIndexes); >- > if (this._delegate.tableDidRemoveRows) { >- this._delegate.tableDidRemoveRows(this, Array.from(rowIndexes)); >+ this._delegate.tableDidRemoveRows(this, Array.from(removedIndexes)); > console.assert(this._cachedNumberOfRows === this._dataSource.tableNumberOfRows(this), "Table data source should update after removing rows."); > } > } >- >- _toggleSelectedRowStyle(rowIndexes, flag) >- { >- for (let index of rowIndexes) { >- let row = this._cachedRows.get(index); >- if (row) >- row.classList.toggle("selected", flag); >- } >- } > }; > > WI.Table.SortOrder = { >diff --git a/Source/WebInspectorUI/UserInterface/Views/TreeOutline.js b/Source/WebInspectorUI/UserInterface/Views/TreeOutline.js >index 9f21a2c55a78e12673bf49d9570ec5a0485f6a9b..35d4bcbe915c561a264a0a1ed025fda76d1838ae 100644 >--- a/Source/WebInspectorUI/UserInterface/Views/TreeOutline.js >+++ b/Source/WebInspectorUI/UserInterface/Views/TreeOutline.js >@@ -56,7 +56,43 @@ WI.TreeOutline = class TreeOutline extends WI.Object > > this._cachedNumberOfDescendents = 0; > this._previousSelectedTreeElement = null; >- this._selectionController = new WI.SelectionController(this); >+ >+ let comparator = (a, b) => { >+ function getLevel(treeElement) { >+ let level = 0; >+ while (treeElement = treeElement.parent) >+ level++; >+ return level; >+ } >+ >+ function 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) { >+ a = a.parent; >+ aLevel--; >+ } >+ while (bLevel > aLevel) { >+ 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; >@@ -103,27 +139,21 @@ WI.TreeOutline = class TreeOutline extends WI.Object > > get selectedTreeElement() > { >- let selectedIndex = this._selectionController.lastSelectedItem; >- return this._treeElementAtIndex(selectedIndex) || null; >+ return this._selectionController.lastSelectedObject; > } > > set selectedTreeElement(treeElement) > { >- if (treeElement) { >- let index = this._indexOfTreeElement(treeElement); >- this._selectionController.selectItem(index); >- } else >+ if (treeElement) >+ this._selectionController.select(treeElement); >+ else > this._selectionController.deselectAll(); > } > > get selectedTreeElements() > { >- if (this.allowsMultipleSelection) { >- let treeElements = []; >- for (let index of this._selectionController.selectedItems) >- treeElements.push(this._treeElementAtIndex(index)); >- return treeElements; >- } >+ if (this.allowsMultipleSelection) >+ return Array.from(this._selectionController.selectedObjects); > > let selectedTreeElement = this.selectedTreeElement; > if (selectedTreeElement) >@@ -323,13 +353,10 @@ WI.TreeOutline = class TreeOutline extends WI.Object > parent.select(true, false); > } > >- let removedIndexes = null; >- > let treeOutline = child.treeOutline; > if (treeOutline) { > treeOutline._forgetTreeElement(child); > treeOutline._forgetChildrenRecursive(child); >- removedIndexes = treeOutline._indexesForSubtree(child); > } > > if (child.previousSibling) >@@ -345,10 +372,8 @@ WI.TreeOutline = class TreeOutline extends WI.Object > child.nextSibling = null; > child.previousSibling = null; > >- if (treeOutline) { >- treeOutline._selectionController.didRemoveItems(removedIndexes); >+ if (treeOutline) > treeOutline.dispatchEventToListeners(WI.TreeOutline.Event.ElementRemoved, {element: child}); >- } > } > > removeChild(child, suppressOnDeselect, suppressSelectSibling) >@@ -383,8 +408,6 @@ WI.TreeOutline = class TreeOutline extends WI.Object > treeOutline._forgetChildrenRecursive(child); > } > >- let removedIndexes = treeOutline._indexesForSubtree(child); >- > child._detach(); > child.treeOutline = null; > child.parent = null; >@@ -393,10 +416,8 @@ WI.TreeOutline = class TreeOutline extends WI.Object > > this.children.shift(); > >- if (treeOutline) { >- treeOutline._selectionController.didRemoveItems(removedIndexes); >+ if (treeOutline) > treeOutline.dispatchEventToListeners(WI.TreeOutline.Event.ElementRemoved, {element: child}); >- } > } > } > >@@ -413,12 +434,6 @@ WI.TreeOutline = class TreeOutline extends WI.Object > // add the element > elements.push(element); > this._cachedNumberOfDescendents++; >- >- let index = this._indexOfTreeElement(element); >- if (index >= 0) { >- console.assert(!element.selected, "TreeElement should not be selected before being inserted."); >- this._selectionController.didInsertItem(index); >- } > } > > _forgetTreeElement(element) >@@ -802,29 +817,21 @@ WI.TreeOutline = class TreeOutline extends WI.Object > return this._cachedNumberOfDescendents; > } > >- selectionControllerSelectionDidChange(controller, deselectedItems, selectedItems) >+ selectionControllerSelectionDidChange(controller, deselectedObjects, selectedObjects) > { > this._processingSelectionChange = true; > >- for (let index of deselectedItems) { >- let treeElement = this._treeElementAtIndex(index); >- console.assert(treeElement, "Missing TreeElement for deselected index " + index); >- if (treeElement) { >- if (treeElement.listItemElement) >- treeElement.listItemElement.classList.remove("selected"); >- treeElement.deselect(); >- } >+ for (let treeElement of deselectedObjects) { >+ if (treeElement.listItemElement) >+ treeElement.listItemElement.classList.remove("selected"); >+ treeElement.deselect(); > } > >- for (let index of selectedItems) { >- let treeElement = this._treeElementAtIndex(index); >- console.assert(treeElement, "Missing TreeElement for selected index " + index); >- if (treeElement) { >- if (treeElement.listItemElement) >- treeElement.listItemElement.classList.add("selected"); >- const omitFocus = true; >- treeElement.select(omitFocus); >- } >+ for (let treeElement of selectedObjects) { >+ if (treeElement.listItemElement) >+ treeElement.listItemElement.classList.add("selected"); >+ const omitFocus = true; >+ treeElement.select(omitFocus); > } > > let selectedTreeElement = this.selectedTreeElement; >@@ -843,11 +850,22 @@ WI.TreeOutline = class TreeOutline extends WI.Object > this._processingSelectionChange = false; > } > >- selectionControllerNextSelectableIndex(controller, index) >+ selectionControllerFirstSelectableObject(controller) > { >- let treeElement = this._treeElementAtIndex(index); >- if (!treeElement) >- return NaN; >+ return this.selectionControllerNextSelectableObject(controller, this); >+ } >+ >+ selectionControllerLastSelectableObject(controller) >+ { >+ // TODO: used by SelectionController when: >+ // 1) hitting the up arrow key when nothing is selected >+ // 2) Doing a select all >+ return null; >+ } >+ >+ selectionControllerNextSelectableObject(controller, object) >+ { >+ let treeElement = object; > > const skipUnrevealed = true; > const stayWithin = null; >@@ -855,17 +873,15 @@ WI.TreeOutline = class TreeOutline extends WI.Object > > while (treeElement = treeElement.traverseNextTreeElement(skipUnrevealed, stayWithin, dontPopulate)) { > if (treeElement.selectable) >- return this._indexOfTreeElement(treeElement); >+ return treeElement; > } > >- return NaN; >+ return null; > } > >- selectionControllerPreviousSelectableIndex(controller, index) >+ selectionControllerPreviousSelectableObject(controller, object) > { >- let treeElement = this._treeElementAtIndex(index); >- if (!treeElement) >- return NaN; >+ let treeElement = object; > > const skipUnrevealed = true; > const stayWithin = null; >@@ -873,10 +889,10 @@ WI.TreeOutline = class TreeOutline extends WI.Object > > while (treeElement = treeElement.traversePreviousTreeElement(skipUnrevealed, stayWithin, dontPopulate)) { > if (treeElement.selectable) >- return this._indexOfTreeElement(treeElement); >+ return treeElement; > } > >- return NaN; >+ return null; > } > > // Protected >@@ -1014,7 +1030,7 @@ WI.TreeOutline = class TreeOutline extends WI.Object > if (!treeElement.canSelectOnMouseDown(event)) > return; > >- if (this.allowsRepeatSelection && treeElement.selected && this._selectionController.selectedItems.size === 1) { >+ if (this.allowsRepeatSelection && treeElement.selected && this._selectionController.selectedObjects.size === 1) { > // Special case for dispatching a selection event for an already selected > // item in single-selection mode. > this._itemWasSelectedByUser = true; >@@ -1022,54 +1038,11 @@ WI.TreeOutline = class TreeOutline extends WI.Object > return; > } > >- let index = this._indexOfTreeElement(treeElement); >- if (isNaN(index)) >- return; >- > this._itemWasSelectedByUser = true; >- this._selectionController.handleItemMouseDown(index, event); >+ this._selectionController.handleObjectMouseDown(treeElement, event); > this._itemWasSelectedByUser = false; > } > >- _indexOfTreeElement(treeElement) >- { >- const skipUnrevealed = false; >- const stayWithin = null; >- const dontPopulate = true; >- >- let index = 0; >- let current = this.children[0]; >- while (current) { >- if (treeElement === current) >- return index; >- >- current = current.traverseNextTreeElement(skipUnrevealed, stayWithin, dontPopulate); >- ++index; >- } >- >- console.assert(false, "Unable to get index for tree element.", treeElement); >- return NaN; >- } >- >- _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; >@@ -1082,34 +1055,6 @@ WI.TreeOutline = class TreeOutline extends WI.Object > > this.dispatchEventToListeners(WI.TreeOutline.Event.SelectionDidChange, {selectedByUser}); > } >- >- _indexesForSubtree(treeElement) >- { >- let treeOutline = treeElement.treeOutline; >- if (!treeOutline) >- return null; >- >- function numberOfElementsInSubtree(treeElement) { >- let elements = treeElement.root ? Array.from(treeElement.children) : [treeElement]; >- let count = 0; >- while (elements.length) { >- let child = elements.pop(); >- if (child.hidden) >- continue; >- >- count++; >- elements = elements.concat(child.children); >- } >- return count; >- } >- >- let firstChild = treeElement.root ? treeElement.children[0] : treeElement; >- let startIndex = treeOutline._indexOfTreeElement(firstChild); >- let count = numberOfElementsInSubtree(treeElement); >- let indexes = new WI.IndexSet; >- indexes.addRange(startIndex, count); >- return indexes; >- } > }; > > WI.TreeOutline._styleElement = null; >diff --git a/LayoutTests/ChangeLog b/LayoutTests/ChangeLog >index b793e2c2adc3304a80a6454d752ff45c077128bc..9abbb5d68c9c079b3b8bc02eb391bcd3f76220cc 100644 >--- a/LayoutTests/ChangeLog >+++ b/LayoutTests/ChangeLog >@@ -1,3 +1,19 @@ >+2019-02-11 Matt Baker <mattbaker@apple.com> >+ >+ Web Inspector: Frontend performance is very slow reloading theverge.com - 50% of time in TreeOutline _indexOfTreeElement >+ https://bugs.webkit.org/show_bug.cgi?id=193605 >+ <rdar://problem/47403986> >+ >+ Reviewed by NOBODY (OOPS!). >+ >+ Remove IndexSet tests and update tests for Set utilities to include new >+ helper methods `equals` and `difference`. >+ >+ * inspector/unit-tests/index-set-expected.txt: Removed. >+ * inspector/unit-tests/index-set.html: Removed. >+ * inspector/unit-tests/set-utilities-expected.txt: >+ * inspector/unit-tests/set-utilities.html: >+ > 2019-02-10 Commit Queue <commit-queue@webkit.org> > > Unreviewed, rolling out r241167. >diff --git a/LayoutTests/inspector/unit-tests/index-set-expected.txt b/LayoutTests/inspector/unit-tests/index-set-expected.txt >deleted file mode 100644 >index f56a4740cc6a3f1721a7b97c552ac2ab1a61c3e8..0000000000000000000000000000000000000000 >--- a/LayoutTests/inspector/unit-tests/index-set-expected.txt >+++ /dev/null >@@ -1,153 +0,0 @@ >-Tests for WI.IndexSet. >- >- >-== Running test suite: IndexSet >--- Running test case: IndexSet.constructor >-PASS: size should be zero. >-PASS: firstIndex should be NaN. >-PASS: lastIndex should be NaN. >-PASS: Should be []. >- >--- Running test case: IndexSet.constructor array >-Initialize IndexSet with an array. >-PASS: size should be 5. >-PASS: firstIndex should be 0. >-PASS: lastIndex should be 100. >-PASS: Should be [0,1,5,50,100]. >-Initialize IndexSet with an array containing duplicate indexes. >-PASS: size should be 5. >-PASS: Should be [0,1,5,50,100]. >- >--- Running test case: IndexSet.constructor invalid >-PASS: size should be zero. >- >--- Running test case: IndexSet.prototype.clear >-PASS: size should be zero. >-PASS: firstIndex should be NaN. >-PASS: lastIndex should be NaN. >-PASS: Should be []. >- >--- Running test case: IndexSet.prototype.add >-PASS: size should be 1. >-PASS: has should return true. >- >--- Running test case: IndexSet.prototype.add duplicate >-PASS: size should be 1. >- >--- Running test case: IndexSet.prototype.add invalid >-PASS: size should be zero. >- >--- Running test case: IndexSet.prototype.delete >-Given an IndexSet with values [1,2,3]: >-PASS: delete 3 should succeed. >-PASS: has 3 should return false. >- >--- Running test case: IndexSet.prototype.delete nonexistent >-Given an IndexSet with values [1,2,3]: >-PASS: delete 4 should fail. >- >--- Running test case: IndexSet.prototype.delete invalid >--- Running test case: IndexSet.prototype[Symbol.iterator] >-Given an IndexSet with values [20,1,10,2]: >-1 >-2 >-10 >-20 >- >--- Running test case: IndexSet.prototype.indexGreaterThan >-Given an IndexSet with values [1,2]: >-PASS: Index greater than 0 should be 1. >-PASS: Index greater than 1 should be 2. >-PASS: Index greater than 2 should be NaN. >-PASS: Index greater than 3 should be NaN. >- >--- Running test case: IndexSet.prototype.indexLessThan >-Given an IndexSet with values [1,2]: >-PASS: Index less than 0 should be NaN. >-PASS: Index less than 1 should be NaN. >-PASS: Index less than 2 should be 1. >-PASS: Index less than 3 should be 2. >- >--- Running test case: IndexSet.prototype.copy >-PASS: Copy and original should be different objects. >-PASS: Copy and original should have the same values. >- >--- Running test case: IndexSet.prototype.addRange >-Given an IndexSet with values []: >-Add range to an empty IndexSet. >-PASS: Should be [1,2,3] after adding [1,2,3]. >- >-Given an IndexSet with values [10,11,12]: >-Add range before the beginning. >-PASS: Should be [0,1,2,10,11,12] after adding [0,1,2]. >- >-Given an IndexSet with values [1,2,3]: >-Add range after the end. >-PASS: Should be [1,2,3,10,11,12] after adding [10,11,12]. >- >-Given an IndexSet with values [1,5]: >-Add range in the middle. >-PASS: Should be [1,2,3,4,5] after adding [2,3,4]. >- >-Given an IndexSet with values [1,3,5]: >-Add range overlapping the middle. >-PASS: Should be [1,2,3,4,5] after adding [2,3,4]. >- >-Given an IndexSet with values [3,4,5]: >-Add range overlapping the beginning. >-PASS: Should be [1,2,3,4,5] after adding [1,2,3]. >- >-Given an IndexSet with values [1,2,3]: >-Add range overlapping the end. >-PASS: Should be [1,2,3,4,5] after adding [3,4,5]. >- >- >--- Running test case: IndexSet.prototype.deleteRange >-Given an IndexSet with values []: >-Remove range from an empty IndexSet. >-PASS: Should be [] after removing [1,2,3]. >- >-Given an IndexSet with values [10,11,12]: >-Remove range before the beginning. >-PASS: Should be [10,11,12] after removing [0,1,2]. >- >-Given an IndexSet with values [0,1,2]: >-Remove range after the end. >-PASS: Should be [0,1,2] after removing [10,11,12]. >- >-Given an IndexSet with values [0,1,2,3]: >-Remove range in the middle. >-PASS: Should be [0,3] after removing [1,2]. >- >-Given an IndexSet with values [1,3,5]: >-Remove range overlapping the middle. >-PASS: Should be [1,5] after removing [2,3,4]. >- >-Given an IndexSet with values [1,2,3]: >-Remove range overlapping the beginning. >-PASS: Should be [3] after removing [0,1,2]. >- >-Given an IndexSet with values [1,2,3]: >-Remove range overlapping the end. >-PASS: Should be [1] after removing [2,3,4]. >- >- >--- Running test case: IndexSet.prototype.equals >-PASS: Should trivially equal itself. >-PASS: Copy and original should be equal. >-PASS: Modified copy and original should not be equal. >- >--- Running test case: IndexSet.prototype.difference >-Given an IndexSet with values [], and another IndexSet with values []: >-PASS: Difference between the first and second IndexSet should be []. >- >-Given an IndexSet with values [1,2,3], and another IndexSet with values []: >-PASS: Difference between the first and second IndexSet should be [1,2,3]. >- >-Given an IndexSet with values [], and another IndexSet with values [1,2,3]: >-PASS: Difference between the first and second IndexSet should be []. >- >-Given an IndexSet with values [1,2,3], and another IndexSet with values [2,3,4]: >-PASS: Difference between the first and second IndexSet should be [1]. >- >- >diff --git a/LayoutTests/inspector/unit-tests/index-set.html b/LayoutTests/inspector/unit-tests/index-set.html >deleted file mode 100644 >index 185e1ad49a11bcc0e2740d02f01496f4e268f0b0..0000000000000000000000000000000000000000 >--- a/LayoutTests/inspector/unit-tests/index-set.html >+++ /dev/null >@@ -1,424 +0,0 @@ >-<!doctype html> >-<html> >-<head> >-<script src="../../http/tests/inspector/resources/inspector-test.js"></script> >-<script> >-function test() >-{ >- let suite = InspectorTest.createSyncSuite("IndexSet"); >- >- function createIndexSet(values = []) { >- if (!Array.isArray(values)) >- values = [values]; >- InspectorTest.log(`Given an IndexSet with values [${values}]:`); >- >- return new WI.IndexSet(values); >- } >- >- function rangeToArray(startIndex, count) { >- let result = []; >- for (let i = 0; i < count; ++i) >- result.push(startIndex + i); >- return result; >- } >- >- suite.addTestCase({ >- name: "IndexSet.constructor", >- test() { >- let indexSet = new WI.IndexSet; >- InspectorTest.expectEqual(indexSet.size, 0, "size should be zero."); >- InspectorTest.expectThat(isNaN(indexSet.firstIndex), "firstIndex should be NaN."); >- InspectorTest.expectThat(isNaN(indexSet.lastIndex), "lastIndex should be NaN."); >- InspectorTest.expectShallowEqual(Array.from(indexSet), [], "Should be []."); >- >- return true; >- } >- }); >- >- suite.addTestCase({ >- name: "IndexSet.constructor array", >- test() { >- const values = [5, 1, 0, 100, 50]; >- const sortedValues = values.slice().sort((a, b) => a - b); >- >- InspectorTest.log("Initialize IndexSet with an array."); >- { >- let indexSet = new WI.IndexSet(values); >- InspectorTest.expectEqual(indexSet.size, values.length, `size should be ${values.length}.`); >- InspectorTest.expectEqual(indexSet.firstIndex, sortedValues[0], `firstIndex should be ${sortedValues[0]}.`); >- InspectorTest.expectEqual(indexSet.lastIndex, sortedValues.lastValue, `lastIndex should be ${sortedValues.lastValue}.`); >- InspectorTest.expectShallowEqual(Array.from(indexSet), sortedValues, `Should be [${sortedValues}].`); >- } >- >- InspectorTest.log("Initialize IndexSet with an array containing duplicate indexes."); >- { >- let indexSet = new WI.IndexSet(values.concat(values)); >- InspectorTest.expectEqual(indexSet.size, values.length, `size should be ${values.length}.`); >- InspectorTest.expectShallowEqual(Array.from(indexSet), sortedValues, `Should be [${sortedValues}].`); >- } >- >- return true; >- } >- }); >- >- suite.addTestCase({ >- name: "IndexSet.constructor invalid", >- test() { >- let indexSet = new WI.IndexSet([-1, 1.5, "abc"]); >- InspectorTest.expectEqual(indexSet.size, 0, "size should be zero."); >- return true; >- } >- }); >- >- suite.addTestCase({ >- name: "IndexSet.prototype.clear", >- test() { >- let indexSet = new WI.IndexSet([1, 2, 3]); >- indexSet.add(42); >- indexSet.clear(); >- InspectorTest.expectEqual(indexSet.size, 0, "size should be zero."); >- InspectorTest.expectThat(isNaN(indexSet.firstIndex), "firstIndex should be NaN."); >- InspectorTest.expectThat(isNaN(indexSet.lastIndex), "lastIndex should be NaN."); >- InspectorTest.expectShallowEqual(Array.from(indexSet), [], "Should be []."); >- >- return true; >- } >- }); >- >- suite.addTestCase({ >- name: "IndexSet.prototype.add", >- test() { >- let indexSet = new WI.IndexSet; >- indexSet.add(42); >- InspectorTest.expectEqual(indexSet.size, 1, "size should be 1."); >- InspectorTest.expectThat(indexSet.has(42), "has should return true."); >- >- return true; >- } >- }); >- >- suite.addTestCase({ >- name: "IndexSet.prototype.add duplicate", >- test() { >- let indexSet = new WI.IndexSet; >- indexSet.add(42); >- indexSet.add(42); >- InspectorTest.expectEqual(indexSet.size, 1, "size should be 1."); >- >- return true; >- } >- }); >- >- suite.addTestCase({ >- name: "IndexSet.prototype.add invalid", >- test() { >- let indexSet = new WI.IndexSet; >- indexSet.add(-1); >- indexSet.add(1.5); >- indexSet.add("abc"); >- InspectorTest.expectEqual(indexSet.size, 0, "size should be zero."); >- >- return true; >- } >- }); >- >- suite.addTestCase({ >- name: "IndexSet.prototype.delete", >- test() { >- let indexSet = createIndexSet([1, 2, 3]); >- const indexToDelete = indexSet.lastIndex; >- let wasDeleted = indexSet.delete(indexToDelete); >- InspectorTest.expectThat(wasDeleted, `delete ${indexToDelete} should succeed.`); >- InspectorTest.expectFalse(indexSet.has(indexToDelete), `has ${indexToDelete} should return false.`); >- >- return true; >- } >- }); >- >- suite.addTestCase({ >- name: "IndexSet.prototype.delete nonexistent", >- test() { >- let indexSet = createIndexSet([1, 2, 3]); >- const indexToDelete = indexSet.lastIndex + 1; >- let wasDeleted = indexSet.delete(indexToDelete); >- InspectorTest.expectFalse(wasDeleted, `delete ${indexToDelete} should fail.`); >- >- return true; >- } >- }); >- >- suite.addTestCase({ >- name: "IndexSet.prototype.delete invalid", >- test() { >- let indexSet = new WI.IndexSet; >- indexSet.delete(-1); >- >- return true; >- } >- }); >- >- suite.addTestCase({ >- name: "IndexSet.prototype[Symbol.iterator]", >- test() { >- let indexSet = createIndexSet([20, 1, 10, 2]); >- for (let index of indexSet) >- InspectorTest.log(index); >- >- return true; >- } >- }); >- >- suite.addTestCase({ >- name: "IndexSet.prototype.indexGreaterThan", >- test() { >- let indexSet = createIndexSet([1, 2]); >- const {firstIndex, lastIndex} = indexSet; >- const indexBefore = firstIndex - 1; >- const indexAfter = lastIndex + 1; >- InspectorTest.expectEqual(indexSet.indexGreaterThan(indexBefore), firstIndex, `Index greater than ${indexBefore} should be ${firstIndex}.`); >- InspectorTest.expectEqual(indexSet.indexGreaterThan(firstIndex), lastIndex, `Index greater than ${firstIndex} should be ${lastIndex}.`); >- InspectorTest.expectThat(isNaN(indexSet.indexGreaterThan(lastIndex)), `Index greater than ${lastIndex} should be NaN.`); >- InspectorTest.expectThat(isNaN(indexSet.indexGreaterThan(indexAfter)), `Index greater than ${indexAfter} should be NaN.`); >- >- return true; >- } >- }); >- >- suite.addTestCase({ >- name: "IndexSet.prototype.indexLessThan", >- test() { >- let indexSet = createIndexSet([1, 2]); >- const {firstIndex, lastIndex} = indexSet; >- const indexBefore = firstIndex - 1; >- const indexAfter = lastIndex + 1; >- >- InspectorTest.expectThat(isNaN(indexSet.indexLessThan(indexBefore)), `Index less than ${indexBefore} should be NaN.`); >- InspectorTest.expectThat(isNaN(indexSet.indexLessThan(firstIndex)), `Index less than ${firstIndex} should be NaN.`); >- InspectorTest.expectEqual(indexSet.indexLessThan(lastIndex), firstIndex, `Index less than ${lastIndex} should be ${firstIndex}.`); >- InspectorTest.expectEqual(indexSet.indexLessThan(indexAfter), lastIndex, `Index less than ${indexAfter} should be ${lastIndex}.`); >- >- return true; >- } >- }); >- >- suite.addTestCase({ >- name: "IndexSet.prototype.copy", >- test() { >- let original = new WI.IndexSet([1, 2, 3]); >- let copied = original.copy(); >- InspectorTest.expectNotEqual(copied, original, "Copy and original should be different objects."); >- InspectorTest.expectShallowEqual(Array.from(copied), Array.from(original), "Copy and original should have the same values."); >- >- return true; >- } >- }); >- >- suite.addTestCase({ >- name: "IndexSet.prototype.addRange", >- test() { >- function testAddRange({description, initialValues, startIndex, count, expectedValues}) { >- let indexSet = createIndexSet(initialValues || []); >- >- InspectorTest.log(description); >- indexSet.addRange(startIndex, count); >- InspectorTest.expectShallowEqual(Array.from(indexSet), expectedValues, `Should be [${expectedValues}] after adding [${rangeToArray(startIndex, count)}].`); >- InspectorTest.log(""); >- } >- >- testAddRange({ >- description: "Add range to an empty IndexSet.", >- initialValues: [], >- startIndex: 1, >- count: 3, >- expectedValues: [1, 2, 3], >- }); >- >- testAddRange({ >- description: "Add range before the beginning.", >- initialValues: [10, 11, 12], >- startIndex: 0, >- count: 3, >- expectedValues: [0, 1, 2, 10, 11, 12], >- }); >- >- testAddRange({ >- description: "Add range after the end.", >- initialValues: [1, 2, 3], >- startIndex: 10, >- count: 3, >- expectedValues: [1, 2, 3, 10, 11, 12], >- }); >- >- testAddRange({ >- description: "Add range in the middle.", >- initialValues: [1, 5], >- startIndex: 2, >- count: 3, >- expectedValues: [1, 2, 3, 4, 5], >- }); >- >- testAddRange({ >- description: "Add range overlapping the middle.", >- initialValues: [1, 3, 5], >- startIndex: 2, >- count: 3, >- expectedValues: [1, 2, 3, 4, 5], >- }); >- >- testAddRange({ >- description: "Add range overlapping the beginning.", >- initialValues: [3, 4, 5], >- startIndex: 1, >- count: 3, >- expectedValues: [1, 2, 3, 4, 5], >- }); >- >- testAddRange({ >- description: "Add range overlapping the end.", >- initialValues: [1, 2, 3], >- startIndex: 3, >- count: 3, >- expectedValues: [1, 2, 3, 4, 5], >- }); >- >- return true; >- } >- }); >- >- suite.addTestCase({ >- name: "IndexSet.prototype.deleteRange", >- test() { >- function testDeleteRange({description, initialValues, startIndex, count, expectedValues}) { >- let indexSet = createIndexSet(initialValues || []); >- >- InspectorTest.log(description); >- indexSet.deleteRange(startIndex, count); >- InspectorTest.expectShallowEqual(Array.from(indexSet), expectedValues, `Should be [${expectedValues}] after removing [${rangeToArray(startIndex, count)}].`); >- InspectorTest.log(""); >- } >- >- testDeleteRange({ >- description: "Remove range from an empty IndexSet.", >- initialValues: [], >- startIndex: 1, >- count: 3, >- expectedValues: [], >- }); >- >- testDeleteRange({ >- description: "Remove range before the beginning.", >- initialValues: [10, 11, 12], >- startIndex: 0, >- count: 3, >- expectedValues: [10, 11, 12], >- }); >- >- testDeleteRange({ >- description: "Remove range after the end.", >- initialValues: [0, 1, 2], >- startIndex: 10, >- count: 3, >- expectedValues: [0, 1, 2], >- }); >- >- testDeleteRange({ >- description: "Remove range in the middle.", >- initialValues: [0, 1, 2, 3], >- startIndex: 1, >- count: 2, >- expectedValues: [0, 3], >- }); >- >- testDeleteRange({ >- description: "Remove range overlapping the middle.", >- initialValues: [1, 3, 5], >- startIndex: 2, >- count: 3, >- expectedValues: [1, 5], >- }); >- >- testDeleteRange({ >- description: "Remove range overlapping the beginning.", >- initialValues: [1, 2, 3], >- startIndex: 0, >- count: 3, >- expectedValues: [3], >- }); >- >- testDeleteRange({ >- description: "Remove range overlapping the end.", >- initialValues: [1, 2, 3], >- startIndex: 2, >- count: 3, >- expectedValues: [1], >- }); >- >- return true; >- } >- }); >- >- suite.addTestCase({ >- name: "IndexSet.prototype.equals", >- test() { >- let original = new WI.IndexSet([1, 2, 3]); >- let copied = original.copy(); >- InspectorTest.expectThat(original.equals(original), "Should trivially equal itself."); >- InspectorTest.expectThat(original.equals(copied), "Copy and original should be equal."); >- >- copied.delete(1); >- InspectorTest.expectFalse(original.equals(copied), "Modified copy and original should not be equal."); >- >- return true; >- } >- }); >- >- suite.addTestCase({ >- name: "IndexSet.prototype.difference", >- test() { >- function testDifference({values1, values2, expectedDifference}) { >- let indexSet1 = new WI.IndexSet(values1); >- let indexSet2 = new WI.IndexSet(values2); >- >- InspectorTest.log(`Given an IndexSet with values [${values1}], and another IndexSet with values [${values2}]:`); >- >- let difference = indexSet1.difference(indexSet2); >- InspectorTest.expectShallowEqual(Array.from(difference), expectedDifference, `Difference between the first and second IndexSet should be [${expectedDifference}].`); >- InspectorTest.log(""); >- } >- >- testDifference({ >- values1: [], >- values2: [], >- expectedDifference: [], >- }); >- >- testDifference({ >- values1: [1, 2, 3], >- values2: [], >- expectedDifference: [1, 2, 3], >- }); >- >- testDifference({ >- values1: [], >- values2: [1, 2, 3], >- expectedDifference: [], >- }); >- >- testDifference({ >- values1: [1, 2, 3], >- values2: [2, 3, 4], >- expectedDifference: [1], >- }); >- >- return true; >- } >- }); >- >- suite.runTestCasesAndFinish(); >-} >-</script> >-</head> >-<body onLoad="runTest()"> >- <p>Tests for WI.IndexSet.</p> >-</body> >-</html> >diff --git a/LayoutTests/inspector/unit-tests/set-utilities-expected.txt b/LayoutTests/inspector/unit-tests/set-utilities-expected.txt >index 99cd31f9ca5ceccb8bc959b29434c49e74640b4a..0432bf1c82b685aa7bd0397c7cc4f145e2ac1fd3 100644 >--- a/LayoutTests/inspector/unit-tests/set-utilities-expected.txt >+++ b/LayoutTests/inspector/unit-tests/set-utilities-expected.txt >@@ -16,3 +16,24 @@ PASS: a set should not be a subset of another set with different values. > PASS: a set should be a subset of another set with same and additional values. > PASS: a set should not be a subset of another set with same and different values. > >+-- Running test case: Set.prototype.equals >+PASS: an empty set should be equal to another empty set. >+PASS: a set should be equal to another set with the same values. >+PASS: a set should be equal to another set with the same values in a different order. >+PASS: a set should not be a equal to another set with different values. >+PASS: a set should not be equal to another set with same and different values. >+ >+-- Running test case: Set.prototype.difference >+Given a Set with values [], and another Set with values []: >+PASS: Set difference should be []. >+ >+Given a Set with values [1,2,3], and another Set with values []: >+PASS: Set difference should be [1,2,3]. >+ >+Given a Set with values [], and another Set with values [1,2,3]: >+PASS: Set difference should be []. >+ >+Given a Set with values [1,2,3], and another Set with values [2,3,4]: >+PASS: Set difference should be [1]. >+ >+ >diff --git a/LayoutTests/inspector/unit-tests/set-utilities.html b/LayoutTests/inspector/unit-tests/set-utilities.html >index bc6758af68d077f98a1757fb694b1cb6e77cdbbe..80c2ee7fd3f096fdfa44309dbc676faab4d0f343 100644 >--- a/LayoutTests/inspector/unit-tests/set-utilities.html >+++ b/LayoutTests/inspector/unit-tests/set-utilities.html >@@ -59,6 +59,73 @@ function test() > } > }); > >+ suite.addTestCase({ >+ name: "Set.prototype.equals", >+ test() { >+ function testTrue(a, b, message) { >+ InspectorTest.expectThat((new Set(a)).equals(new Set(b)), message); >+ } >+ >+ function testFalse(a, b, message) { >+ InspectorTest.expectFalse((new Set(a)).equals(new Set(b)), message); >+ } >+ >+ const object1 = {a: 1}; >+ const object2 = {b: 2}; >+ const object3 = {c: 3}; >+ >+ testTrue([], [], "an empty set should be equal to another empty set."); >+ testTrue([1, "a", object1], [1, "a", object1], "a set should be equal to another set with the same values."); >+ testTrue([1, "a", object1], [object1, 1, "a"], "a set should be equal to another set with the same values in a different order."); >+ testFalse([1, "a", object1], [2, "b", object2], "a set should not be a equal to another set with different values."); >+ testFalse([1, 2, "a", "b", object1, object2], [1, 3, "a", "c", object1, object3], "a set should not be equal to another set with same and different values."); >+ >+ return true; >+ } >+ }); >+ >+ suite.addTestCase({ >+ name: "Set.prototype.difference", >+ test() { >+ function testDifference({aValues, bValues, expectedDifference}) { >+ let a = new Set(aValues); >+ let b = new Set(bValues); >+ >+ InspectorTest.log(`Given a Set with values [${aValues}], and another Set with values [${bValues}]:`); >+ >+ let difference = a.difference(b); >+ InspectorTest.expectThat(difference.equals(new Set(expectedDifference)), `Set difference should be [${expectedDifference}].`); >+ InspectorTest.log(""); >+ } >+ >+ testDifference({ >+ aValues: [], >+ bValues: [], >+ expectedDifference: [], >+ }); >+ >+ testDifference({ >+ aValues: [1, 2, 3], >+ bValues: [], >+ expectedDifference: [1, 2, 3], >+ }); >+ >+ testDifference({ >+ aValues: [], >+ bValues: [1, 2, 3], >+ expectedDifference: [], >+ }); >+ >+ testDifference({ >+ aValues: [1, 2, 3], >+ bValues: [2, 3, 4], >+ expectedDifference: [1], >+ }); >+ >+ return true; >+ } >+ }); >+ > suite.runTestCasesAndFinish(); > } > </script>
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Formatted Diff
|
Diff
Attachments on
bug 193605
:
359577
|
361719
|
361722
|
361731
|
361739
|
361740
|
361822
|
361832
|
361835
|
361845
|
361955
|
361980
|
362078
|
362079
|
362190
|
362222