WebKit Bugzilla
Attachment 361980 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-20190213184903.patch (text/plain), 92.44 KB, created by
Matt Baker
on 2019-02-13 18:49:06 PST
(
hide
)
Description:
Patch
Filename:
MIME Type:
Creator:
Matt Baker
Created:
2019-02-13 18:49:06 PST
Size:
92.44 KB
patch
obsolete
>Subversion Revision: 241489 >diff --git a/Source/WebInspectorUI/ChangeLog b/Source/WebInspectorUI/ChangeLog >index e68601baaddc5306462b7023e4545170c25d1cef..fa76e8e6115b9bea3ffeccb8c9e6662e43f14277 100644 >--- a/Source/WebInspectorUI/ChangeLog >+++ b/Source/WebInspectorUI/ChangeLog >@@ -1,3 +1,120 @@ >+2019-02-13 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 represented 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): >+ (get return): >+ Add utilities previously supplied by IndexSet and used by SelectionController. >+ >+ * UserInterface/Controllers/SelectionController.js: >+ (WI.SelectionController): >+ (WI.SelectionController.prototype.get lastSelectedItem): >+ (WI.SelectionController.prototype.get selectedItems): >+ (WI.SelectionController.prototype.set allowsMultipleSelection): >+ (WI.SelectionController.prototype.hasSelectedItem): >+ (WI.SelectionController.prototype.selectItem): >+ (WI.SelectionController.prototype.deselectItem): >+ (WI.SelectionController.prototype.selectAll): >+ (WI.SelectionController.prototype.deselectAll): >+ (WI.SelectionController.prototype.willRemoveSelectedItems): >+ (WI.SelectionController.prototype.reset): >+ (WI.SelectionController.prototype.handleKeyDown): >+ (WI.SelectionController.prototype.handleItemMouseDown): >+ (WI.SelectionController.prototype._deselectAllAndSelect): >+ (WI.SelectionController.prototype._selectItemsFromArrowKey): >+ (WI.SelectionController.prototype._firstSelectableItem): >+ (WI.SelectionController.prototype._lastSelectableItem): >+ (WI.SelectionController.prototype._previousSelectableItem): >+ (WI.SelectionController.prototype._nextSelectableItem): >+ (WI.SelectionController.prototype._updateSelectedItems): >+ (WI.SelectionController.prototype._addRange): >+ (WI.SelectionController.prototype._deleteRange): >+ (WI.SelectionController.prototype.get numberOfItems): 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._nextSelectableIndex): Deleted. >+ (WI.SelectionController.prototype._previousSelectableIndex): Deleted. >+ >+ * UserInterface/Main.html: >+ * UserInterface/Test.html: >+ Remove IndexSet. >+ >+ * UserInterface/Views/CookieStorageContentView.js: >+ (WI.CookieStorageContentView.prototype.tableIndexForRepresentedObject): >+ (WI.CookieStorageContentView.prototype.tableRepresentedObjectForIndex): >+ >+ * UserInterface/Views/DOMTreeOutline.js: >+ (WI.DOMTreeOutline.prototype.objectForSelection): >+ >+ * UserInterface/Views/NetworkTableContentView.js: >+ (WI.NetworkTableContentView.prototype.tableIndexForRepresentedObject): >+ (WI.NetworkTableContentView.prototype.tableRepresentedObjectForIndex): >+ >+ * 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.selectionControllerFirstSelectableItem): >+ (WI.Table.prototype.selectionControllerLastSelectableItem): >+ (WI.Table.prototype.selectionControllerPreviousSelectableItem): >+ (WI.Table.prototype.selectionControllerNextSelectableItem): >+ (WI.Table.prototype._handleMouseDown): >+ (WI.Table.prototype._removeRows): >+ (WI.Table.prototype._indexForRepresentedObject): >+ (WI.Table.prototype._representedObjectForIndex): >+ (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.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.getCachedTreeElement): >+ (WI.TreeOutline.prototype.selectionControllerSelectionDidChange): >+ (WI.TreeOutline.prototype.selectionControllerFirstSelectableItem): >+ (WI.TreeOutline.prototype.selectionControllerLastSelectableItem): >+ (WI.TreeOutline.prototype.selectionControllerPreviousSelectableItem): >+ (WI.TreeOutline.prototype.selectionControllerNextSelectableItem): >+ (WI.TreeOutline.prototype.objectForSelection): >+ (WI.TreeOutline._generateStyleRulesIfNeeded): >+ (WI.TreeOutline.prototype.selectionControllerNextSelectableIndex): Deleted. >+ (WI.TreeOutline.prototype.selectionControllerPreviousSelectableIndex): Deleted. >+ (WI.TreeOutline._generateStyleRulesIfNeeded._indexesForSubtree.numberOfElementsInSubtree): Deleted. >+ > 2019-02-13 Chris Dumez <cdumez@apple.com> > > Unreviewed, update localizable strings. >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..4f28dccdb38d44e8082eaf2aece973c4949f257e 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) >+ { >+ return this.size === other.size && this.isSubsetOf(other); >+ } >+}); >+ >+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, "firstValue", >+{ >+ get() >+ { >+ return this.values().next().value; >+ } >+}); >+ > Object.defineProperty(Set.prototype, "intersects", > { > value(other) >@@ -1456,6 +1489,12 @@ Object.defineProperty(Array.prototype, "binaryIndexOf", > { > value(value, comparator) > { >+ function defaultComparator(a, b) >+ { >+ return a - b; >+ } >+ comparator = comparator || defaultComparator; >+ > var index = this.lowerBound(value, comparator); > return index < this.length && comparator(value, this[index]) === 0 ? index : -1; > } >diff --git a/Source/WebInspectorUI/UserInterface/Controllers/SelectionController.js b/Source/WebInspectorUI/UserInterface/Controllers/SelectionController.js >index 75cbcf50bd3dacab08e451f11831c94e4de25caa..19e0cc6ab9eb06e12968a9da16d66d6ea10bfae1 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,35 @@ > > WI.SelectionController = class SelectionController extends WI.Object > { >- constructor(delegate) >+ constructor(delegate, comparator) > { > super(); > >+ console.assert(typeof comparator === "function"); > console.assert(delegate); >+ >+ this._comparator = comparator; > this._delegate = delegate; > > this._allowsEmptySelection = true; > this._allowsMultipleSelection = false; >- this._lastSelectedIndex = NaN; >- this._shiftAnchorIndex = NaN; >- this._selectedIndexes = new WI.IndexSet; >+ this._lastSelectedItem = null; >+ this._shiftAnchorItem = null; >+ this._selectedItems = 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.selectionControllerFirstSelectableItem, "SelectionController delegate must implement selectionControllerFirstSelectableItem."); >+ console.assert(this._delegate.selectionControllerLastSelectableItem, "SelectionController delegate must implement selectionControllerLastSelectableItem."); >+ console.assert(this._delegate.selectionControllerNextSelectableItem, "SelectionController delegate must implement selectionControllerNextSelectableItem."); >+ console.assert(this._delegate.selectionControllerPreviousSelectableItem, "SelectionController delegate must implement selectionControllerPreviousSelectableItem."); >+ console.assert(this._delegate.selectionControllerSelectionDidChange, "SelectionController delegate must implement selectionControllerSelectionDidChange."); > } > > // Public > > get delegate() { return this._delegate; } >- get lastSelectedItem() { return this._lastSelectedIndex; } >- get selectedItems() { return this._selectedIndexes; } >+ get lastSelectedItem() { return this._lastSelectedItem; } >+ get selectedItems() { return this._selectedItems; } > > get allowsEmptySelection() { return this._allowsEmptySelection; } > set allowsEmptySelection(flag) { this._allowsEmptySelection = flag; } >@@ -67,225 +72,134 @@ 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])); >- } >- } >- >- get numberOfItems() >- { >- return this._delegate.selectionControllerNumberOfItems(this); >+ if (this._selectedItems.size > 1) >+ this._updateSelectedItems(new Set([this._lastSelectedItem])); > } > >- hasSelectedItem(index) >+ hasSelectedItem(item) > { >- return this._selectedIndexes.has(index); >+ return this._selectedItems.has(item); > } > >- selectItem(index, extendSelection = false) >+ selectItem(item, extendSelection = false) > { >+ console.assert(item, "Invalid item for selection."); > 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.hasSelectedItem(item)) { > if (!extendSelection) >- this._deselectAllAndSelect(index); >+ this._deselectAllAndSelect(item); > return; > } > >- let newSelectedItems = extendSelection ? this._selectedIndexes.copy() : new WI.IndexSet; >- newSelectedItems.add(index); >+ this._lastSelectedItem = item; >+ this._shiftAnchorItem = null; > >- this._shiftAnchorIndex = NaN; >- this._lastSelectedIndex = index; >+ let newItems = new Set(extendSelection ? this._selectedItems : null); >+ newItems.add(item); > >- this._updateSelectedItems(newSelectedItems); >+ this._updateSelectedItems(newItems); > } > >- deselectItem(index) >+ deselectItem(item) > { >- console.assert(index >= 0 && index < this.numberOfItems); >+ console.assert(item, "Invalid item for selection."); > >- if (!this.hasSelectedItem(index)) >+ if (!this.hasSelectedItem(item)) > return; > >- if (!this._allowsEmptySelection && this._selectedIndexes.size === 1) >+ if (!this._allowsEmptySelection && this._selectedItems.size === 1) > return; > >- let newSelectedItems = this._selectedIndexes.copy(); >- newSelectedItems.delete(index); >+ let newItems = new Set(this._selectedItems); >+ newItems.delete(item); > >- if (this._shiftAnchorIndex === index) >- this._shiftAnchorIndex = NaN; >+ if (this._lastSelectedItem === item) { >+ this._lastSelectedItem = null; > >- if (this._lastSelectedIndex === index) { >- this._lastSelectedIndex = NaN; >- if (newSelectedItems.size) { >+ if (newItems.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 previous = this._previousSelectableItem(item); >+ let next = this._nextSelectableItem(item); >+ >+ while (!this.hasSelectedItem(previous) && !this.hasSelectedItem(next)) { >+ previous = this._previousSelectableItem(previous); >+ next = this._nextSelectableItem(next); > } >+ >+ if (this.hasSelectedItem(previous)) >+ this._lastSelectedItem = previous; >+ else if (this.hasSelectedItem(next)) >+ this._lastSelectedItem = next; > } > } > >- this._updateSelectedItems(newSelectedItems); >+ if (this._shiftAnchorItem === item) >+ this._shiftAnchorItem = null; >+ >+ this._updateSelectedItems(newItems); > } > > selectAll() > { >- if (!this.numberOfItems || !this._allowsMultipleSelection) >+ if (!this._allowsMultipleSelection) > return; > >- if (this._selectedIndexes.size === this.numberOfItems) >- return; >+ this._lastSelectedItem = this._lastSelectableItem(); > >- let newSelectedItems = new WI.IndexSet; >- newSelectedItems.addRange(0, this.numberOfItems); >+ let newItems = new Set; >+ this._addRange(newItems, this._firstSelectableItem(), this._lastSelectedItem); > >- this._lastSelectedIndex = newSelectedItems.lastIndex; >- if (isNaN(this._shiftAnchorIndex)) >- this._shiftAnchorIndex = this._lastSelectedIndex; >+ if (!this._shiftAnchorItem) >+ this._shiftAnchorItem = this._lastSelectedItem; > >- this._updateSelectedItems(newSelectedItems); >+ this._updateSelectedItems(newItems); > } > > deselectAll() > { >- const index = NaN; >- this._deselectAllAndSelect(index); >+ this._deselectAllAndSelect(null); > } > >- removeSelectedItems() >+ willRemoveSelectedItems() > { >- let numberOfSelectedItems = this._selectedIndexes.size; >- if (!numberOfSelectedItems) >+ if (!this._selectedItems.size) > return; > >+ let orderedSelection = Array.from(this._selectedItems).sort(this._comparator); >+ > // Try selecting the item following the selection. >- let lastSelectedIndex = this._selectedIndexes.lastIndex; >- let indexToSelect = this._nextSelectableIndex(lastSelectedIndex); >- if (isNaN(indexToSelect)) { >+ let lastSelectedItem = orderedSelection.lastValue; >+ let itemToSelect = this._nextSelectableItem(lastSelectedItem); >+ if (!itemToSelect) { > // 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 { >+ itemToSelect = orderedSelection[0]; >+ while (itemToSelect && this.hasSelectedItem(itemToSelect)) >+ itemToSelect = this._nextSelectableItem(itemToSelect); >+ >+ if (!itemToSelect || this.hasSelectedItem(itemToSelect)) { > // If the selection contains no holes, try selecting the item > // preceding the selection. >- indexToSelect = firstSelectedIndex > 0 ? this._previousSelectableIndex(firstSelectedIndex) : NaN; >+ itemToSelect = this._previousSelectableItem(orderedSelection[0]); > } > } > >- this._deselectAllAndSelect(indexToSelect); >+ this._deselectAllAndSelect(itemToSelect); > } > > 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; >- } >- >- didRemoveItems(indexes) >- { >- if (!indexes) >- return; >- >- console.assert(indexes instanceof WI.IndexSet); >- >- if (!indexes.size || !this._selectedIndexes.size) >- return; >- >- let firstRemovedIndex = indexes.firstIndex; >- if (this._selectedIndexes.lastIndex < firstRemovedIndex) >- 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 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._lastSelectedItem = null; >+ this._shiftAnchorItem = null; >+ this._selectedItems.clear(); > } > > handleKeyDown(event) > { >- if (!this.numberOfItems) >- return false; >- > if (event.key === "a" && event.commandOrControlKey) { > this.selectAll(); > return true; >@@ -305,41 +219,44 @@ WI.SelectionController = class SelectionController extends WI.Object > return false; > } > >- handleItemMouseDown(index, event) >+ handleItemMouseDown(item, event) > { >+ console.assert(item, "Invalid item for selection."); >+ > if (event.button !== 0 || event.ctrlKey) > return; > > // 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.hasSelectedItem(item)) >+ this.deselectItem(item); > else >- this.selectItem(index, this._allowsMultipleSelection); >+ this.selectItem(item, this._allowsMultipleSelection); > return; > } > > let shiftExtendSelection = this._allowsMultipleSelection && event.shiftKey; > if (!shiftExtendSelection) { >- this.selectItem(index); >+ this.selectItem(item); > return; > } > >- let newSelectedItems = this._selectedIndexes.copy(); >+ let newItems = new Set(this._selectedItems); > > // 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 (!newItems.size) { >+ this._lastSelectedItem = item; >+ this._shiftAnchorItem = this._firstSelectableItem(); >+ >+ this._addRange(newItems, this._shiftAnchorItem, this._lastSelectedItem); >+ this._updateSelectedItems(newItems); > return; > } > >- if (isNaN(this._shiftAnchorIndex)) >- this._shiftAnchorIndex = this._lastSelectedIndex; >+ if (!this._shiftAnchorItem) >+ this._shiftAnchorItem = this._lastSelectedItem; > > // Shift-clicking will add to or delete from the current selection, or > // pivot the selection around the anchor (a delete followed by an add). >@@ -347,107 +264,136 @@ WI.SelectionController = class SelectionController extends WI.Object > // 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. > >- function normalizeRange(startIndex, endIndex) { >- return startIndex > endIndex ? [endIndex, startIndex] : [startIndex, endIndex]; >- } >+ let sortItemPair = (a, b) => { >+ return [a, b].sort(this._comparator); >+ }; > >- if (this._shiftAnchorIndex !== this._lastSelectedIndex) { >- let [startIndex, endIndex] = normalizeRange(this._shiftAnchorIndex, this._lastSelectedIndex); >- newSelectedItems.deleteRange(startIndex, endIndex - startIndex + 1); >+ if (this._shiftAnchorItem !== this._lastSelectedItem) { >+ let [startItem, endItem] = sortItemPair(this._shiftAnchorItem, this._lastSelectedItem); >+ this._deleteRange(newItems, startItem, endItem); > } > >- let [startIndex, endIndex] = normalizeRange(this._shiftAnchorIndex, index); >- newSelectedItems.addRange(startIndex, endIndex - startIndex + 1); >+ let [startItem, endItem] = sortItemPair(this._shiftAnchorItem, item); >+ this._addRange(newItems, startItem, endItem); > >- this._lastSelectedIndex = index; >+ this._lastSelectedItem = item; > >- this._updateSelectedItems(newSelectedItems); >+ this._updateSelectedItems(newItems); > } > > // Private > >- _deselectAllAndSelect(index) >+ _deselectAllAndSelect(item) > { >- if (!this._selectedIndexes.size) >+ if (!this._selectedItems.size) > return; > >- if (this._selectedIndexes.size === 1 && this._selectedIndexes.firstIndex === index) >+ if (this._selectedItems.size === 1 && this.hasSelectedItem(item)) > return; > >- this._shiftAnchorIndex = NaN; >- this._lastSelectedIndex = index; >+ this._lastSelectedItem = item; >+ this._shiftAnchorItem = null; > >- let newSelectedItems = new WI.IndexSet; >- if (!isNaN(index)) >- newSelectedItems.add(index); >+ let newItems = new Set; >+ if (item) >+ newItems.add(item); > >- this._updateSelectedItems(newSelectedItems); >+ this._updateSelectedItems(newItems); > } > > _selectItemsFromArrowKey(goingUp, shiftKey) > { >- if (!this._selectedIndexes.size) { >- let index = goingUp ? this.numberOfItems - 1 : 0; >- this.selectItem(index); >+ if (!this._selectedItems.size) { >+ this.selectItem(goingUp ? this._lastSelectableItem() : this._firstSelectableItem()); > return; > } > >- let index = goingUp ? this._previousSelectableIndex(this._lastSelectedIndex) : this._nextSelectableIndex(this._lastSelectedIndex); >- if (isNaN(index)) >+ let item = goingUp ? this._previousSelectableItem(this._lastSelectedItem) : this._nextSelectableItem(this._lastSelectedItem); >+ if (!item) > return; > > let extendSelection = shiftKey && this._allowsMultipleSelection; >- if (!extendSelection || !this.hasSelectedItem(index)) { >- this.selectItem(index, extendSelection); >+ if (!extendSelection || !this.hasSelectedItem(item)) { >+ this.selectItem(item, 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); >+ let priorItem = goingUp ? this._nextSelectableItem(this._lastSelectedItem) : this._previousSelectableItem(this._lastSelectedItem); >+ if (priorItem && !this.hasSelectedItem(priorItem)) { >+ this.deselectItem(this._lastSelectedItem); > 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); >+ while (item) { >+ if (!this.hasSelectedItem(item)) { >+ this.selectItem(item, extendSelection); > break; > } > >- this._lastSelectedIndex = index; >- index = goingUp ? this._previousSelectableIndex(index) : this._nextSelectableIndex(index); >+ this._lastSelectedItem = item; >+ item = goingUp ? this._previousSelectableItem(item) : this._nextSelectableItem(item); > } > } > >- _nextSelectableIndex(index) >+ _firstSelectableItem() > { >- return this._delegate.selectionControllerNextSelectableIndex(this, index); >+ return this._delegate.selectionControllerFirstSelectableItem(this); > } > >- _previousSelectableIndex(index) >+ _lastSelectableItem() > { >- return this._delegate.selectionControllerPreviousSelectableIndex(this, index); >+ return this._delegate.selectionControllerLastSelectableItem(this); > } > >- _updateSelectedItems(indexes) >+ _previousSelectableItem(item) > { >- if (this._selectedIndexes.equals(indexes)) >- return; >+ return this._delegate.selectionControllerPreviousSelectableItem(this, item); >+ } >+ >+ _nextSelectableItem(item) >+ { >+ return this._delegate.selectionControllerNextSelectableItem(this, item); >+ } > >- let oldSelectedIndexes = this._selectedIndexes.copy(); >- this._selectedIndexes = indexes; >+ _updateSelectedItems(items) >+ { >+ let oldSelectedItems = this._selectedItems; >+ this._selectedItems = items; > > if (this._suppressSelectionDidChange || !this._delegate.selectionControllerSelectionDidChange) > return; > >- let deselectedItems = oldSelectedIndexes.difference(indexes); >- let selectedItems = indexes.difference(oldSelectedIndexes); >- this._delegate.selectionControllerSelectionDidChange(this, deselectedItems, selectedItems); >+ let deselectedItems = oldSelectedItems.difference(items); >+ let selectedItems = items.difference(oldSelectedItems); >+ if (deselectedItems.size || selectedItems.size) >+ this._delegate.selectionControllerSelectionDidChange(this, deselectedItems, selectedItems); >+ } >+ >+ _addRange(items, firstItem, lastItem) >+ { >+ let current = firstItem; >+ while (current) { >+ items.add(current); >+ if (current === lastItem) >+ break; >+ current = this._nextSelectableItem(current); >+ } >+ } >+ >+ _deleteRange(items, firstItem, lastItem) >+ { >+ let current = firstItem; >+ while (current) { >+ items.delete(current); >+ if (current === lastItem) >+ break; >+ current = this._nextSelectableItem(current); >+ } > } > }; >diff --git a/Source/WebInspectorUI/UserInterface/Main.html b/Source/WebInspectorUI/UserInterface/Main.html >index 40ddf8395158d5df9546ca4befd877868f15313b..ceb24265e9886b643ab66c1490fcdfbe79799b96 100644 >--- a/Source/WebInspectorUI/UserInterface/Main.html >+++ b/Source/WebInspectorUI/UserInterface/Main.html >@@ -276,7 +276,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/CookieStorageContentView.js b/Source/WebInspectorUI/UserInterface/Views/CookieStorageContentView.js >index 3aa2d9e0e311d9a4ed3a6891efb5920a620c7ef9..ad3f7f7e8418348e106c994411e1f9118cba5194 100644 >--- a/Source/WebInspectorUI/UserInterface/Views/CookieStorageContentView.js >+++ b/Source/WebInspectorUI/UserInterface/Views/CookieStorageContentView.js >@@ -75,6 +75,18 @@ WI.CookieStorageContentView = class CookieStorageContentView extends WI.ContentV > > // Table dataSource > >+ tableIndexForRepresentedObject(table, object) >+ { >+ return this._cookies.indexOf(object); >+ } >+ >+ tableRepresentedObjectForIndex(table, index) >+ { >+ if (index < 0 || index >= this._cookies.length) >+ return null; >+ return this._cookies[index]; >+ } >+ > tableNumberOfRows(table) > { > return this._cookies.length; >diff --git a/Source/WebInspectorUI/UserInterface/Views/DOMTreeOutline.js b/Source/WebInspectorUI/UserInterface/Views/DOMTreeOutline.js >index 7bfa538bceb19a1526079c9bb4055aaa2e16b7f7..ed7827368956806f9db298fee4e5073056f4ba86 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.willRemoveSelectedItems(); > > let levelMap = new Map; > >@@ -319,6 +319,20 @@ WI.DOMTreeOutline = class DOMTreeOutline extends WI.TreeOutline > return true; > } > >+ // Protected >+ >+ objectForSelection(treeElement) >+ { >+ if (treeElement instanceof WI.DOMTreeElement && treeElement.isCloseTag()) { >+ if (!treeElement.__closeTagProxyObject) { >+ treeElement.__closeTagProxyObject = {treeElement}; >+ } >+ return treeElement.__closeTagProxyObject; >+ } >+ >+ return super.objectForSelection(treeElement); >+ } >+ > // Private > > _revealAndSelectNode(node, omitFocus) >diff --git a/Source/WebInspectorUI/UserInterface/Views/NetworkTableContentView.js b/Source/WebInspectorUI/UserInterface/Views/NetworkTableContentView.js >index 43815af0131df9945095ce2187507f8f581c64ba..466258df56f3f522072d079daee3671fc888aef4 100644 >--- a/Source/WebInspectorUI/UserInterface/Views/NetworkTableContentView.js >+++ b/Source/WebInspectorUI/UserInterface/Views/NetworkTableContentView.js >@@ -327,6 +327,18 @@ WI.NetworkTableContentView = class NetworkTableContentView extends WI.ContentVie > > // Table dataSource > >+ tableIndexForRepresentedObject(table, object) >+ { >+ return this._rowIndexForRepresentedObject(object); >+ } >+ >+ tableRepresentedObjectForIndex(table, index) >+ { >+ if (index < 0 || index >= this._filteredEntries.length) >+ return null; >+ return this._filteredEntries[index]; >+ } >+ > tableNumberOfRows(table) > { > return this._filteredEntries.length; >diff --git a/Source/WebInspectorUI/UserInterface/Views/Table.js b/Source/WebInspectorUI/UserInterface/Views/Table.js >index 0f7c99baad357126ce9d3d073b37bdb5207865ed..e9bd53a4c1b047afc112470c75ff59a7ca8bf847 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) => this._indexForRepresentedObject(a) - this._indexForRepresentedObject(b)); > > this._resizers = []; > this._currentResizer = null; >@@ -113,6 +113,9 @@ WI.Table = class Table extends WI.View > this._visibleRowIndexStart = NaN; > this._visibleRowIndexEnd = NaN; > >+ console.assert(this._dataSource.tableIndexForRepresentedObject, "Table data source must implement tableIndexForRepresentedObject."); >+ console.assert(this._dataSource.tableRepresentedObjectForIndex, "Table data source must implement tableRepresentedObjectForIndex."); >+ > console.assert(this._delegate.tablePopulateCell, "Table delegate must implement tablePopulateCell."); > } > >@@ -125,12 +128,17 @@ WI.Table = class Table extends WI.View > > get selectedRow() > { >- return this._selectionController.lastSelectedItem; >+ let item = this._selectionController.lastSelectedItem; >+ let index = this._indexForRepresentedObject(item); >+ return index >= 0 ? index : NaN; > } > > get selectedRows() > { >- return Array.from(this._selectionController.selectedItems); >+ let rowIndexes = []; >+ for (let item of this._selectionController.selectedItems) >+ rowIndexes.push(this._indexForRepresentedObject(item)); >+ return rowIndexes; > } > > get scrollContainer() { return this._scrollContainerElement; } >@@ -236,7 +244,7 @@ WI.Table = class Table extends WI.View > > isRowSelected(rowIndex) > { >- return this._selectionController.hasSelectedItem(rowIndex); >+ return this._selectionController.hasSelectedItem(this._representedObjectForIndex(rowIndex)); > } > > reloadData() >@@ -313,12 +321,12 @@ WI.Table = class Table extends WI.View > > selectRow(rowIndex, extendSelection = false) > { >- this._selectionController.selectItem(rowIndex, extendSelection); >+ this._selectionController.selectItem(this._representedObjectForIndex(rowIndex), extendSelection); > } > > deselectRow(rowIndex) > { >- this._selectionController.deselectItem(rowIndex); >+ this._selectionController.deselectItem(this._representedObjectForIndex(rowIndex)); > } > > selectAll() >@@ -338,20 +346,24 @@ WI.Table = class Table extends WI.View > if (this.isRowSelected(rowIndex)) > this.deselectRow(rowIndex); > >- let rowIndexes = new WI.IndexSet([rowIndex]); >- this._removeRows(rowIndexes); >+ this._removeRows([rowIndex]); > } > > removeSelectedRows() > { >+ let selectedItems = this._selectionController.selectedItems; >+ if (!selectedItems.size) >+ return; >+ >+ let oldIndexes = []; >+ for (let item of selectedItems) >+ oldIndexes.push(this._indexForRepresentedObject(item)); >+ > // 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.willRemoveSelectedItems(); > >- this._selectionController.removeSelectedItems(); >- >- if (!oldSelectedItems.equals(this._selectionController.selectedItems)) >- this._removeRows(oldSelectedItems); >+ this._removeRows(oldIndexes); > } > > revealRow(rowIndex) >@@ -597,13 +609,22 @@ WI.Table = class Table extends WI.View > > selectionControllerSelectionDidChange(controller, deselectedItems, selectedItems) > { >- if (deselectedItems.size) >- this._toggleSelectedRowStyle(deselectedItems, false); >- if (selectedItems.size) >- this._toggleSelectedRowStyle(selectedItems, true); >+ for (let item of deselectedItems) { >+ let rowIndex = this._indexForRepresentedObject(item); >+ let row = this._cachedRows.get(rowIndex); >+ if (row) >+ row.classList.toggle("selected", false); >+ } >+ >+ for (let item of selectedItems) { >+ let rowIndex = this._indexForRepresentedObject(item); >+ let row = this._cachedRows.get(rowIndex); >+ if (row) >+ row.classList.toggle("selected", true); >+ } > > if (selectedItems.size === 1) { >- let rowIndex = selectedItems.firstIndex; >+ let rowIndex = this._indexForRepresentedObject(selectedItems.firstValue); > if (!this._isRowVisible(rowIndex)) > this.revealRow(rowIndex); > } >@@ -612,23 +633,30 @@ WI.Table = class Table extends WI.View > this._delegate.tableSelectionDidChange(this); > } > >- selectionControllerNumberOfItems(controller) >+ selectionControllerFirstSelectableItem(controller) >+ { >+ return this._representedObjectForIndex(0); >+ } >+ >+ selectionControllerLastSelectableItem(controller) > { >- return this.numberOfRows; >+ return this._representedObjectForIndex(this.numberOfRows - 1); > } > >- selectionControllerNextSelectableIndex(controller, index) >+ selectionControllerPreviousSelectableItem(controller, item) > { >- if (index >= this.numberOfRows - 1) >- return NaN; >- return index + 1; >+ let index = this._indexForRepresentedObject(item); >+ console.assert(index >= 0 && index < this.numberOfRows); >+ >+ return index > 0 ? this._representedObjectForIndex(index - 1) : null; > } > >- selectionControllerPreviousSelectableIndex(controller, index) >+ selectionControllerNextSelectableItem(controller, item) > { >- if (index <= 0) >- return NaN; >- return index - 1; >+ let index = this._indexForRepresentedObject(item); >+ console.assert(index >= 0 && index < this.numberOfRows); >+ >+ return index < this.numberOfRows - 1 ? this._representedObjectForIndex(index + 1) : null; > } > > // Resizer delegate >@@ -1295,7 +1323,7 @@ WI.Table = class Table extends WI.View > return; > } > >- this._selectionController.handleItemMouseDown(rowIndex, event); >+ this._selectionController.handleItemMouseDown(this._representedObjectForIndex(rowIndex), event); > } > > _handleContextMenu(event) >@@ -1387,8 +1415,12 @@ WI.Table = class Table extends WI.View > } > }; > >- for (let index = rowIndexes.firstIndex; index <= rowIndexes.lastIndex; ++index) { >- if (rowIndexes.has(index)) { >+ rowIndexes.sort((a, b) => a - b); >+ >+ let lastIndex = rowIndexes.lastValue; >+ >+ for (let index = rowIndexes[0]; index <= lastIndex; ++index) { >+ if (rowIndexes.binaryIndexOf(index) >= 0) { > let row = this._cachedRows.get(index); > if (row) { > this._cachedRows.delete(index); >@@ -1405,27 +1437,26 @@ 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, rowIndexes); > console.assert(this._cachedNumberOfRows === this._dataSource.tableNumberOfRows(this), "Table data source should update after removing rows."); > } > } > >- _toggleSelectedRowStyle(rowIndexes, flag) >+ _indexForRepresentedObject(object) > { >- for (let index of rowIndexes) { >- let row = this._cachedRows.get(index); >- if (row) >- row.classList.toggle("selected", flag); >- } >+ return this.dataSource.tableIndexForRepresentedObject(this, object); >+ } >+ >+ _representedObjectForIndex(index) >+ { >+ return this.dataSource.tableRepresentedObjectForIndex(this, index); > } > }; > >diff --git a/Source/WebInspectorUI/UserInterface/Views/TreeOutline.js b/Source/WebInspectorUI/UserInterface/Views/TreeOutline.js >index 9f21a2c55a78e12673bf49d9570ec5a0485f6a9b..e56e737773479116a95f17cdcbaa7fb992453492 100644 >--- a/Source/WebInspectorUI/UserInterface/Views/TreeOutline.js >+++ b/Source/WebInspectorUI/UserInterface/Views/TreeOutline.js >@@ -56,7 +56,54 @@ 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); >+ } >+ >+ // 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; >@@ -103,16 +150,14 @@ WI.TreeOutline = class TreeOutline extends WI.Object > > get selectedTreeElement() > { >- let selectedIndex = this._selectionController.lastSelectedItem; >- return this._treeElementAtIndex(selectedIndex) || null; >+ return this.getCachedTreeElement(this._selectionController.lastSelectedItem); > } > > set selectedTreeElement(treeElement) > { >- if (treeElement) { >- let index = this._indexOfTreeElement(treeElement); >- this._selectionController.selectItem(index); >- } else >+ if (treeElement) >+ this._selectionController.selectItem(this.objectForSelection(treeElement)); >+ else > this._selectionController.deselectAll(); > } > >@@ -120,8 +165,8 @@ WI.TreeOutline = class TreeOutline extends WI.Object > { > if (this.allowsMultipleSelection) { > let treeElements = []; >- for (let index of this._selectionController.selectedItems) >- treeElements.push(this._treeElementAtIndex(index)); >+ for (let representedObject of this._selectionController.selectedItems) >+ treeElements.push(this.getCachedTreeElement(representedObject)); > return treeElements; > } > >@@ -323,13 +368,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 +387,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 +423,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 +431,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 +449,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) >@@ -447,6 +477,9 @@ WI.TreeOutline = class TreeOutline extends WI.Object > if (!representedObject) > return null; > >+ if (representedObject.treeElement) >+ return representedObject.treeElement; >+ > if (representedObject.__treeElementIdentifier) { > // If this representedObject has a tree element identifier, and it is a known TreeElement > // in our tree we can just return that tree element. >@@ -806,25 +839,25 @@ WI.TreeOutline = class TreeOutline extends WI.Object > { > 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 representedObject of deselectedItems) { >+ let treeElement = this.getCachedTreeElement(representedObject); >+ if (!treeElement) >+ continue; >+ >+ 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 representedObject of selectedItems) { >+ let treeElement = this.getCachedTreeElement(representedObject); >+ if (!treeElement) >+ continue; >+ >+ if (treeElement.listItemElement) >+ treeElement.listItemElement.classList.add("selected"); >+ const omitFocus = true; >+ treeElement.select(omitFocus); > } > > let selectedTreeElement = this.selectedTreeElement; >@@ -843,44 +876,71 @@ WI.TreeOutline = class TreeOutline extends WI.Object > this._processingSelectionChange = false; > } > >- selectionControllerNextSelectableIndex(controller, index) >+ selectionControllerFirstSelectableItem(controller) > { >- let treeElement = this._treeElementAtIndex(index); >+ let firstChild = this.children[0]; >+ if (firstChild.selectable) >+ return firstChild.representedObject; >+ return this.selectionControllerNextSelectableItem(controller, firstChild.representedObject); >+ } >+ >+ selectionControllerLastSelectableItem(controller) >+ { >+ let treeElement = this.children.lastValue; >+ while (treeElement.expanded && treeElement.children.length) >+ treeElement = treeElement.children.lastValue; >+ >+ let item = this.objectForSelection(treeElement); >+ if (treeElement.selectable) >+ return item; >+ return this.selectionControllerPreviousSelectableItem(controller, item); >+ } >+ >+ selectionControllerPreviousSelectableItem(controller, item) >+ { >+ let treeElement = this.getCachedTreeElement(item); >+ console.assert(treeElement, "Missing TreeElement for representedObject.", item); > if (!treeElement) >- return NaN; >+ return null; > > const skipUnrevealed = true; > const stayWithin = null; > const dontPopulate = true; > >- while (treeElement = treeElement.traverseNextTreeElement(skipUnrevealed, stayWithin, dontPopulate)) { >+ while (treeElement = treeElement.traversePreviousTreeElement(skipUnrevealed, stayWithin, dontPopulate)) { > if (treeElement.selectable) >- return this._indexOfTreeElement(treeElement); >+ return this.objectForSelection(treeElement); > } > >- return NaN; >+ return null; > } > >- selectionControllerPreviousSelectableIndex(controller, index) >+ selectionControllerNextSelectableItem(controller, item) > { >- let treeElement = this._treeElementAtIndex(index); >+ let treeElement = this.getCachedTreeElement(item); >+ console.assert(treeElement, "Missing TreeElement for representedObject.", item); > if (!treeElement) >- return NaN; >+ return null; > > const skipUnrevealed = true; > const stayWithin = null; > const dontPopulate = true; > >- while (treeElement = treeElement.traversePreviousTreeElement(skipUnrevealed, stayWithin, dontPopulate)) { >+ while (treeElement = treeElement.traverseNextTreeElement(skipUnrevealed, stayWithin, dontPopulate)) { > if (treeElement.selectable) >- return this._indexOfTreeElement(treeElement); >+ return this.objectForSelection(treeElement); > } > >- return NaN; >+ return null; > } > > // Protected > >+ objectForSelection(treeElement) >+ { >+ return treeElement.representedObject; >+ } >+ > selectTreeElementInternal(treeElement, suppressNotification = false, selectedByUser = false) > { > if (this._processingSelectionChange) >@@ -1022,54 +1082,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.handleItemMouseDown(this.objectForSelection(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 +1099,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 f900e4e18d0f95e8b3383206dccf04580a81213f..14caa31d0516def7386b610caba7bb87e2f76137 100644 >--- a/LayoutTests/ChangeLog >+++ b/LayoutTests/ChangeLog >@@ -1,3 +1,24 @@ >+2019-02-13 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!). >+ >+ * inspector/table/resources/table-utilities.js: >+ (TestPage.registerInitializer.InspectorTest.TableDataSource.prototype.tableIndexForRepresentedObject): >+ (TestPage.registerInitializer.InspectorTest.TableDataSource.prototype.tableRepresentedObjectForIndex): >+ (TestPage.registerInitializer.InspectorTest.TableDataSource): >+ New Table data source methods. >+ >+ * 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: >+ Remove IndexSet tests and update tests for Set utilities to include new >+ helper methods `equals` and `difference`, and `firstValue`. >+ > 2019-02-13 Youenn Fablet <youenn@apple.com> > > getUserMedia with an ideal deviceId constraint doesn't always select the correct device >diff --git a/LayoutTests/inspector/table/resources/table-utilities.js b/LayoutTests/inspector/table/resources/table-utilities.js >index 183533fd53d06de05c4cce511e4131baa27a449a..2de9d74bfcf6ce91623d5f8344315bf6a4ef7284 100644 >--- a/LayoutTests/inspector/table/resources/table-utilities.js >+++ b/LayoutTests/inspector/table/resources/table-utilities.js >@@ -12,6 +12,16 @@ TestPage.registerInitializer(() => { > { > return this._items.length; > } >+ >+ tableIndexForRepresentedObject(table, object) >+ { >+ return this._items.indexOf(object); >+ } >+ >+ tableRepresentedObjectForIndex(table, index) >+ { >+ return this._items[index]; >+ } > }; > > InspectorTest.TableDelegate = class TableDelegate >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..b8da910bbeab3daa45c578bc7feeb03b5b77e9db 100644 >--- a/LayoutTests/inspector/unit-tests/set-utilities-expected.txt >+++ b/LayoutTests/inspector/unit-tests/set-utilities-expected.txt >@@ -16,3 +16,28 @@ 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]. >+ >+ >+-- Running test case: Set.prototype.firstValue >+PASS: Set with values [] should have firstValue equal to undefined. >+PASS: Set with values [1,2,3] should have firstValue equal to 1. >+ >diff --git a/LayoutTests/inspector/unit-tests/set-utilities.html b/LayoutTests/inspector/unit-tests/set-utilities.html >index bc6758af68d077f98a1757fb694b1cb6e77cdbbe..db7140c12a423b6663e673e5a37ac4ff9acccd6f 100644 >--- a/LayoutTests/inspector/unit-tests/set-utilities.html >+++ b/LayoutTests/inspector/unit-tests/set-utilities.html >@@ -59,6 +59,85 @@ 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.addTestCase({ >+ name: "Set.prototype.firstValue", >+ test() { >+ function testFirstValue(values) { >+ InspectorTest.expectEqual(new Set(values).firstValue, values[0], `Set with values [${values}] should have firstValue equal to ${values[0]}.`); >+ } >+ >+ testFirstValue([]); >+ testFirstValue([1, 2, 3]); >+ } >+ }); >+ > 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