Changeset 241652 in webkit


Ignore:
Timestamp:
Feb 17, 2019 12:49:38 PM (5 years ago)
Author:
Matt Baker
Message:

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 Devin Rousso.

Source/WebInspectorUI:

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.removeSelectedItems):
(WI.SelectionController.prototype.reset):
(WI.SelectionController.prototype.didRemoveItems):
(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.didInsertItem): 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.

LayoutTests:

  • 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.

Location:
trunk
Files:
3 deleted
14 edited

Legend:

Unmodified
Added
Removed
  • trunk/LayoutTests/ChangeLog

    r241647 r241652  
     12019-02-17  Matt Baker  <mattbaker@apple.com>
     2
     3        Web Inspector: Frontend performance is very slow reloading theverge.com - 50% of time in TreeOutline _indexOfTreeElement
     4        https://bugs.webkit.org/show_bug.cgi?id=193605
     5        <rdar://problem/47403986>
     6
     7        Reviewed by Devin Rousso.
     8
     9        * inspector/table/resources/table-utilities.js:
     10        (TestPage.registerInitializer.InspectorTest.TableDataSource.prototype.tableIndexForRepresentedObject):
     11        (TestPage.registerInitializer.InspectorTest.TableDataSource.prototype.tableRepresentedObjectForIndex):
     12        (TestPage.registerInitializer.InspectorTest.TableDataSource):
     13        New Table data source methods.
     14
     15        * inspector/unit-tests/index-set-expected.txt: Removed.
     16        * inspector/unit-tests/index-set.html: Removed.
     17        * inspector/unit-tests/set-utilities-expected.txt:
     18        * inspector/unit-tests/set-utilities.html:
     19        Remove IndexSet tests and update tests for Set utilities to include new
     20        helper methods `equals` and `difference`, and `firstValue`.
     21
    1222019-02-16  Zalan Bujtas  <zalan@apple.com>
    223
  • trunk/LayoutTests/inspector/table/resources/table-utilities.js

    r237495 r241652  
    1212        {
    1313            return this._items.length;
     14        }
     15
     16        tableIndexForRepresentedObject(table, object)
     17        {
     18            return this._items.indexOf(object);
     19        }
     20
     21        tableRepresentedObjectForIndex(table, index)
     22        {
     23            return this._items[index];
    1424        }
    1525    };
  • trunk/LayoutTests/inspector/unit-tests/set-utilities-expected.txt

    r237436 r241652  
    1717PASS: a set should not be a subset of another set with same and different values.
    1818
     19-- Running test case: Set.prototype.equals
     20PASS: an empty set should be equal to another empty set.
     21PASS: a set should be equal to another set with the same values.
     22PASS: a set should be equal to another set with the same values in a different order.
     23PASS: a set should not be a equal to another set with different values.
     24PASS: a set should not be equal to another set with same and different values.
     25
     26-- Running test case: Set.prototype.difference
     27Given a Set with values [], and another Set with values []:
     28PASS: Set difference should be [].
     29
     30Given a Set with values [1,2,3], and another Set with values []:
     31PASS: Set difference should be [1,2,3].
     32
     33Given a Set with values [], and another Set with values [1,2,3]:
     34PASS: Set difference should be [].
     35
     36Given a Set with values [1,2,3], and another Set with values [2,3,4]:
     37PASS: Set difference should be [1].
     38
     39
     40-- Running test case: Set.prototype.firstValue
     41PASS: Set with values [] should have firstValue equal to undefined.
     42PASS: Set with values [1,2,3] should have firstValue equal to 1.
     43
  • trunk/LayoutTests/inspector/unit-tests/set-utilities.html

    r237436 r241652  
    6060    });
    6161
     62    suite.addTestCase({
     63        name: "Set.prototype.equals",
     64        test() {
     65            function testTrue(a, b, message) {
     66                InspectorTest.expectThat((new Set(a)).equals(new Set(b)), message);
     67            }
     68
     69            function testFalse(a, b, message) {
     70                InspectorTest.expectFalse((new Set(a)).equals(new Set(b)), message);
     71            }
     72
     73            const object1 = {a: 1};
     74            const object2 = {b: 2};
     75            const object3 = {c: 3};
     76
     77            testTrue([], [], "an empty set should be equal to another empty set.");
     78            testTrue([1, "a", object1], [1, "a", object1], "a set should be equal to another set with the same values.");
     79            testTrue([1, "a", object1], [object1, 1, "a"], "a set should be equal to another set with the same values in a different order.");
     80            testFalse([1, "a", object1], [2, "b", object2], "a set should not be a equal to another set with different values.");
     81            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.");
     82
     83            return true;
     84        }
     85    });
     86
     87    suite.addTestCase({
     88        name: "Set.prototype.difference",
     89        test() {
     90            function testDifference({aValues, bValues, expectedDifference}) {
     91                let a = new Set(aValues);
     92                let b = new Set(bValues);
     93
     94                InspectorTest.log(`Given a Set with values [${aValues}], and another Set with values [${bValues}]:`);
     95
     96                let difference = a.difference(b);
     97                InspectorTest.expectThat(difference.equals(new Set(expectedDifference)), `Set difference should be [${expectedDifference}].`);
     98                InspectorTest.log("");
     99            }
     100
     101            testDifference({
     102                aValues: [],
     103                bValues: [],
     104                expectedDifference: [],
     105            });
     106
     107            testDifference({
     108                aValues: [1, 2, 3],
     109                bValues: [],
     110                expectedDifference: [1, 2, 3],
     111            });
     112
     113            testDifference({
     114                aValues: [],
     115                bValues: [1, 2, 3],
     116                expectedDifference: [],
     117            });
     118
     119            testDifference({
     120                aValues: [1, 2, 3],
     121                bValues: [2, 3, 4],
     122                expectedDifference: [1],
     123            });
     124
     125            return true;
     126        }
     127    });
     128
     129    suite.addTestCase({
     130        name: "Set.prototype.firstValue",
     131        test() {
     132            function testFirstValue(values) {
     133                InspectorTest.expectEqual(new Set(values).firstValue, values[0], `Set with values [${values}] should have firstValue equal to ${values[0]}.`);
     134            }
     135
     136            testFirstValue([]);
     137            testFirstValue([1, 2, 3]);
     138        }
     139    });
     140
    62141    suite.runTestCasesAndFinish();
    63142}
  • trunk/Source/WebInspectorUI/ChangeLog

    r241643 r241652  
     12019-02-17  Matt Baker  <mattbaker@apple.com>
     2
     3        Web Inspector: Frontend performance is very slow reloading theverge.com - 50% of time in TreeOutline _indexOfTreeElement
     4        https://bugs.webkit.org/show_bug.cgi?id=193605
     5        <rdar://problem/47403986>
     6
     7        Reviewed by Devin Rousso.
     8
     9        SelectionController should track an unordered Set of represented objects
     10        instead of an ordered set of indexes. This eliminates the costly and
     11        error-prone updates needed to keep the selected indexes in sync as items
     12        are added and removed from TreeOutline (and Table, to a far lesser extent).
     13
     14        The SelectionController interface is largely the same. Class and delegate
     15        methods have been renamed to reflect the change from indexes to objects.
     16        SelectionController tracks selected items in selection order. For the
     17        operations that rely on objects being in insertion order, the controller
     18        uses a comparator function provided at construction time.
     19
     20        * UserInterface/Base/IndexSet.js: Removed.
     21        No longer used. SelectionController now uses a plain Set.
     22
     23        * UserInterface/Base/Utilities.js:
     24        (value):
     25        (get return):
     26        Add utilities previously supplied by IndexSet and used by SelectionController.
     27
     28        * UserInterface/Controllers/SelectionController.js:
     29        (WI.SelectionController):
     30        (WI.SelectionController.prototype.get lastSelectedItem):
     31        (WI.SelectionController.prototype.get selectedItems):
     32        (WI.SelectionController.prototype.set allowsMultipleSelection):
     33        (WI.SelectionController.prototype.hasSelectedItem):
     34        (WI.SelectionController.prototype.selectItem):
     35        (WI.SelectionController.prototype.deselectItem):
     36        (WI.SelectionController.prototype.selectAll):
     37        (WI.SelectionController.prototype.deselectAll):
     38        (WI.SelectionController.prototype.removeSelectedItems):
     39        (WI.SelectionController.prototype.reset):
     40        (WI.SelectionController.prototype.didRemoveItems):
     41        (WI.SelectionController.prototype.handleKeyDown):
     42        (WI.SelectionController.prototype.handleItemMouseDown):
     43        (WI.SelectionController.prototype._deselectAllAndSelect):
     44        (WI.SelectionController.prototype._selectItemsFromArrowKey):
     45        (WI.SelectionController.prototype._firstSelectableItem):
     46        (WI.SelectionController.prototype._lastSelectableItem):
     47        (WI.SelectionController.prototype._previousSelectableItem):
     48        (WI.SelectionController.prototype._nextSelectableItem):
     49        (WI.SelectionController.prototype._updateSelectedItems):
     50        (WI.SelectionController.prototype._addRange):
     51        (WI.SelectionController.prototype._deleteRange):
     52        (WI.SelectionController.prototype.get numberOfItems): Deleted.
     53        (WI.SelectionController.prototype.didInsertItem): Deleted.
     54        (WI.SelectionController.prototype.handleItemMouseDown.normalizeRange): Deleted.
     55        (WI.SelectionController.prototype._nextSelectableIndex): Deleted.
     56        (WI.SelectionController.prototype._previousSelectableIndex): Deleted.
     57
     58        * UserInterface/Main.html:
     59        * UserInterface/Test.html:
     60        Remove IndexSet.
     61
     62        * UserInterface/Views/CookieStorageContentView.js:
     63        (WI.CookieStorageContentView.prototype.tableIndexForRepresentedObject):
     64        (WI.CookieStorageContentView.prototype.tableRepresentedObjectForIndex):
     65
     66        * UserInterface/Views/DOMTreeOutline.js:
     67        (WI.DOMTreeOutline.prototype.objectForSelection):
     68
     69        * UserInterface/Views/NetworkTableContentView.js:
     70        (WI.NetworkTableContentView.prototype.tableIndexForRepresentedObject):
     71        (WI.NetworkTableContentView.prototype.tableRepresentedObjectForIndex):
     72
     73        * UserInterface/Views/Table.js:
     74        (WI.Table):
     75        (WI.Table.prototype.get selectedRow):
     76        (WI.Table.prototype.get selectedRows):
     77        (WI.Table.prototype.isRowSelected):
     78        (WI.Table.prototype.selectRow):
     79        (WI.Table.prototype.deselectRow):
     80        (WI.Table.prototype.removeRow):
     81        (WI.Table.prototype.removeSelectedRows):
     82        (WI.Table.prototype.selectionControllerSelectionDidChange):
     83        (WI.Table.prototype.selectionControllerFirstSelectableItem):
     84        (WI.Table.prototype.selectionControllerLastSelectableItem):
     85        (WI.Table.prototype.selectionControllerPreviousSelectableItem):
     86        (WI.Table.prototype.selectionControllerNextSelectableItem):
     87        (WI.Table.prototype._handleMouseDown):
     88        (WI.Table.prototype._removeRows):
     89        (WI.Table.prototype._indexForRepresentedObject):
     90        (WI.Table.prototype._representedObjectForIndex):
     91        (WI.Table.prototype.selectionControllerNumberOfItems): Deleted.
     92        (WI.Table.prototype.selectionControllerNextSelectableIndex): Deleted.
     93        (WI.Table.prototype.selectionControllerPreviousSelectableIndex): Deleted.
     94        (WI.Table.prototype._toggleSelectedRowStyle): Deleted.
     95
     96        * UserInterface/Views/TreeOutline.js:
     97        (WI.TreeOutline.compareSiblings):
     98        (WI.TreeOutline):
     99        (WI.TreeOutline.prototype.get selectedTreeElement):
     100        (WI.TreeOutline.prototype.set selectedTreeElement):
     101        (WI.TreeOutline.prototype.get selectedTreeElements):
     102        (WI.TreeOutline.prototype.removeChildAtIndex):
     103        (WI.TreeOutline.prototype.removeChildren):
     104        (WI.TreeOutline.prototype._rememberTreeElement):
     105        (WI.TreeOutline.prototype.getCachedTreeElement):
     106        (WI.TreeOutline.prototype.selectionControllerSelectionDidChange):
     107        (WI.TreeOutline.prototype.selectionControllerFirstSelectableItem):
     108        (WI.TreeOutline.prototype.selectionControllerLastSelectableItem):
     109        (WI.TreeOutline.prototype.selectionControllerPreviousSelectableItem):
     110        (WI.TreeOutline.prototype.selectionControllerNextSelectableItem):
     111        (WI.TreeOutline.prototype.objectForSelection):
     112        (WI.TreeOutline._generateStyleRulesIfNeeded):
     113        (WI.TreeOutline.prototype.selectionControllerNextSelectableIndex): Deleted.
     114        (WI.TreeOutline.prototype.selectionControllerPreviousSelectableIndex): Deleted.
     115        (WI.TreeOutline._generateStyleRulesIfNeeded._indexesForSubtree.numberOfElementsInSubtree): Deleted.
     116
    11172019-02-15  Joseph Pecoraro  <pecoraro@apple.com>
    2118
  • trunk/Source/WebInspectorUI/UserInterface/Base/Utilities.js

    r240824 r241652  
    119119});
    120120
     121Object.defineProperty(Set.prototype, "equals",
     122{
     123    value(other)
     124    {
     125        return this.size === other.size && this.isSubsetOf(other);
     126    }
     127});
     128
     129Object.defineProperty(Set.prototype, "difference",
     130{
     131    value(other)
     132    {
     133        if (other === this)
     134            return new Set;
     135
     136        let result = new Set;
     137        for (let item of this) {
     138            if (!other.has(item))
     139                result.add(item);
     140        }
     141
     142        return result;
     143    }
     144});
     145
     146Object.defineProperty(Set.prototype, "firstValue",
     147{
     148    get()
     149    {
     150        return this.values().next().value;
     151    }
     152});
     153
    121154Object.defineProperty(Set.prototype, "intersects",
    122155{
     
    14571490    value(value, comparator)
    14581491    {
     1492        function defaultComparator(a, b)
     1493        {
     1494            return a - b;
     1495        }
     1496        comparator = comparator || defaultComparator;
     1497
    14591498        var index = this.lowerBound(value, comparator);
    14601499        return index < this.length && comparator(value, this[index]) === 0 ? index : -1;
  • trunk/Source/WebInspectorUI/UserInterface/Controllers/SelectionController.js

    r240594 r241652  
    11/*
    2  * Copyright (C) 2018 Apple Inc. All Rights Reserved.
     2 * Copyright (C) 2018, 2019 Apple Inc. All Rights Reserved.
    33 *
    44 * Redistribution and use in source and binary forms, with or without
     
    2626WI.SelectionController = class SelectionController extends WI.Object
    2727{
    28     constructor(delegate)
     28    constructor(delegate, comparator)
    2929    {
    3030        super();
    3131
    3232        console.assert(delegate);
     33        console.assert(typeof comparator === "function");
     34
    3335        this._delegate = delegate;
     36        this._comparator = comparator;
    3437
    3538        this._allowsEmptySelection = true;
    3639        this._allowsMultipleSelection = false;
    37         this._lastSelectedIndex = NaN;
    38         this._shiftAnchorIndex = NaN;
    39         this._selectedIndexes = new WI.IndexSet;
     40        this._lastSelectedItem = null;
     41        this._shiftAnchorItem = null;
     42        this._selectedItems = new Set;
    4043        this._suppressSelectionDidChange = false;
    4144
    42         console.assert(this._delegate.selectionControllerNumberOfItems, "SelectionController delegate must implement selectionControllerNumberOfItems.");
    43         console.assert(this._delegate.selectionControllerNextSelectableIndex, "SelectionController delegate must implement selectionControllerNextSelectableIndex.");
    44         console.assert(this._delegate.selectionControllerPreviousSelectableIndex, "SelectionController delegate must implement selectionControllerPreviousSelectableIndex.");
     45        console.assert(this._delegate.selectionControllerFirstSelectableItem, "SelectionController delegate must implement selectionControllerFirstSelectableItem.");
     46        console.assert(this._delegate.selectionControllerLastSelectableItem, "SelectionController delegate must implement selectionControllerLastSelectableItem.");
     47        console.assert(this._delegate.selectionControllerNextSelectableItem, "SelectionController delegate must implement selectionControllerNextSelectableItem.");
     48        console.assert(this._delegate.selectionControllerPreviousSelectableItem, "SelectionController delegate must implement selectionControllerPreviousSelectableItem.");
    4549    }
    4650
     
    4852
    4953    get delegate() { return this._delegate; }
    50     get lastSelectedItem() { return this._lastSelectedIndex; }
    51     get selectedItems() { return this._selectedIndexes; }
     54    get lastSelectedItem() { return this._lastSelectedItem; }
     55    get selectedItems() { return this._selectedItems; }
    5256
    5357    get allowsEmptySelection() { return this._allowsEmptySelection; }
     
    6872            return;
    6973
    70         if (this._selectedIndexes.size > 1) {
    71             console.assert(this._lastSelectedIndex >= 0);
    72             this._updateSelectedItems(new WI.IndexSet([this._lastSelectedIndex]));
    73         }
    74     }
    75 
    76     get numberOfItems()
    77     {
    78         return this._delegate.selectionControllerNumberOfItems(this);
    79     }
    80 
    81     hasSelectedItem(index)
    82     {
    83         return this._selectedIndexes.has(index);
    84     }
    85 
    86     selectItem(index, extendSelection = false)
    87     {
     74        if (this._selectedItems.size > 1)
     75            this._updateSelectedItems(new Set([this._lastSelectedItem]));
     76    }
     77
     78    hasSelectedItem(item)
     79    {
     80        return this._selectedItems.has(item);
     81    }
     82
     83    selectItem(item, extendSelection = false)
     84    {
     85        console.assert(item, "Invalid item for selection.");
    8886        console.assert(!extendSelection || this._allowsMultipleSelection, "Cannot extend selection with multiple selection disabled.");
    89         console.assert(index >= 0 && index < this.numberOfItems);
    9087
    9188        if (!this._allowsMultipleSelection)
    9289            extendSelection = false;
    9390
    94         if (this.hasSelectedItem(index)) {
     91        if (this.hasSelectedItem(item)) {
    9592            if (!extendSelection)
    96                 this._deselectAllAndSelect(index);
    97             return;
    98         }
    99 
    100         let newSelectedItems = extendSelection ? this._selectedIndexes.copy() : new WI.IndexSet;
    101         newSelectedItems.add(index);
    102 
    103         this._shiftAnchorIndex = NaN;
    104         this._lastSelectedIndex = index;
    105 
    106         this._updateSelectedItems(newSelectedItems);
    107     }
    108 
    109     deselectItem(index)
    110     {
    111         console.assert(index >= 0 && index < this.numberOfItems);
    112 
    113         if (!this.hasSelectedItem(index))
    114             return;
    115 
    116         if (!this._allowsEmptySelection && this._selectedIndexes.size === 1)
    117             return;
    118 
    119         let newSelectedItems = this._selectedIndexes.copy();
    120         newSelectedItems.delete(index);
    121 
    122         if (this._shiftAnchorIndex === index)
    123             this._shiftAnchorIndex = NaN;
    124 
    125         if (this._lastSelectedIndex === index) {
    126             this._lastSelectedIndex = NaN;
    127             if (newSelectedItems.size) {
     93                this._deselectAllAndSelect(item);
     94            return;
     95        }
     96
     97        this._lastSelectedItem = item;
     98        this._shiftAnchorItem = null;
     99
     100        let newItems = new Set(extendSelection ? this._selectedItems : null);
     101        newItems.add(item);
     102
     103        this._updateSelectedItems(newItems);
     104    }
     105
     106    deselectItem(item)
     107    {
     108        console.assert(item, "Invalid item for selection.");
     109
     110        if (!this.hasSelectedItem(item))
     111            return;
     112
     113        if (!this._allowsEmptySelection && this._selectedItems.size === 1)
     114            return;
     115
     116        let newItems = new Set(this._selectedItems);
     117        newItems.delete(item);
     118
     119        if (this._lastSelectedItem === item) {
     120            this._lastSelectedItem = null;
     121
     122            if (newItems.size) {
    128123                // Find selected item closest to deselected item.
    129                 let preceding = newSelectedItems.indexLessThan(index);
    130                 let following = newSelectedItems.indexGreaterThan(index);
    131 
    132                 if (isNaN(preceding))
    133                     this._lastSelectedIndex = following;
    134                 else if (isNaN(following))
    135                     this._lastSelectedIndex = preceding;
    136                 else {
    137                     if ((following - index) < (index - preceding))
    138                         this._lastSelectedIndex = following;
    139                     else
    140                         this._lastSelectedIndex = preceding;
     124                let previous = item;
     125                let next = item;
     126                while (!this._lastSelectedItem && previous && next) {
     127                    previous = this._previousSelectableItem(previous);
     128                    if (this.hasSelectedItem(previous)) {
     129                        this._lastSelectedItem = previous;
     130                        break;
     131                    }
     132
     133                    next = this._nextSelectableItem(next);
     134                    if (this.hasSelectedItem(next)) {
     135                        this._lastSelectedItem = next;
     136                        break;
     137                    }
    141138                }
    142139            }
    143140        }
    144141
    145         this._updateSelectedItems(newSelectedItems);
     142        if (this._shiftAnchorItem === item)
     143            this._shiftAnchorItem = null;
     144
     145        this._updateSelectedItems(newItems);
    146146    }
    147147
    148148    selectAll()
    149149    {
    150         if (!this.numberOfItems || !this._allowsMultipleSelection)
    151             return;
    152 
    153         if (this._selectedIndexes.size === this.numberOfItems)
    154             return;
    155 
    156         let newSelectedItems = new WI.IndexSet;
    157         newSelectedItems.addRange(0, this.numberOfItems);
    158 
    159         this._lastSelectedIndex = newSelectedItems.lastIndex;
    160         if (isNaN(this._shiftAnchorIndex))
    161             this._shiftAnchorIndex = this._lastSelectedIndex;
    162 
    163         this._updateSelectedItems(newSelectedItems);
     150        if (!this._allowsMultipleSelection)
     151            return;
     152
     153        this._lastSelectedItem = this._lastSelectableItem();
     154
     155        let newItems = new Set;
     156        this._addRange(newItems, this._firstSelectableItem(), this._lastSelectedItem);
     157
     158        if (!this._shiftAnchorItem)
     159            this._shiftAnchorItem = this._lastSelectedItem;
     160
     161        this._updateSelectedItems(newItems);
    164162    }
    165163
    166164    deselectAll()
    167165    {
    168         const index = NaN;
    169         this._deselectAllAndSelect(index);
     166        this._deselectAllAndSelect(null);
    170167    }
    171168
    172169    removeSelectedItems()
    173170    {
    174         let numberOfSelectedItems = this._selectedIndexes.size;
    175         if (!numberOfSelectedItems)
    176             return;
     171        if (!this._selectedItems.size)
     172            return;
     173
     174        let orderedSelection = Array.from(this._selectedItems).sort(this._comparator);
    177175
    178176        // Try selecting the item following the selection.
    179         let lastSelectedIndex = this._selectedIndexes.lastIndex;
    180         let indexToSelect = this._nextSelectableIndex(lastSelectedIndex);
    181         if (isNaN(indexToSelect)) {
     177        let lastSelectedItem = orderedSelection.lastValue;
     178        let itemToSelect = this._nextSelectableItem(lastSelectedItem);
     179        if (!itemToSelect) {
    182180            // If no item exists after the last item in the selection, try selecting
    183181            // a deselected item (hole) within the selection.
    184             let firstSelectedIndex = this._selectedIndexes.firstIndex;
    185             if (lastSelectedIndex - firstSelectedIndex > numberOfSelectedItems) {
    186                 indexToSelect = this._nextSelectableIndex(firstSelectedIndex);
    187                 while (this._selectedIndexes.has(indexToSelect))
    188                     indexToSelect = this._nextSelectableIndex(firstSelectedIndex);
    189             } else {
     182            itemToSelect = orderedSelection[0];
     183            while (itemToSelect && this.hasSelectedItem(itemToSelect))
     184                itemToSelect = this._nextSelectableItem(itemToSelect);
     185
     186            if (!itemToSelect || this.hasSelectedItem(itemToSelect)) {
    190187                // If the selection contains no holes, try selecting the item
    191188                // preceding the selection.
    192                 indexToSelect = firstSelectedIndex > 0 ? this._previousSelectableIndex(firstSelectedIndex) : NaN;
     189                itemToSelect = this._previousSelectableItem(orderedSelection[0]);
    193190            }
    194191        }
    195192
    196         this._deselectAllAndSelect(indexToSelect);
     193        this._deselectAllAndSelect(itemToSelect);
    197194    }
    198195
    199196    reset()
    200197    {
    201         this._shiftAnchorIndex = NaN;
    202         this._lastSelectedIndex = NaN;
    203         this._selectedIndexes.clear();
    204     }
    205 
    206     didInsertItem(index)
    207     {
    208         let current = this._selectedIndexes.lastIndex;
    209         while (current >= index) {
    210             this._selectedIndexes.delete(current);
    211             this._selectedIndexes.add(current + 1);
    212 
    213             current = this._selectedIndexes.indexLessThan(current);
    214         }
    215 
    216         if (this._lastSelectedIndex >= index)
    217             this._lastSelectedIndex += 1;
    218         if (this._shiftAnchorIndex >= index)
    219             this._shiftAnchorIndex += 1;
    220     }
    221 
    222     didRemoveItems(indexes)
    223     {
    224         if (!indexes)
    225             return;
    226 
    227         console.assert(indexes instanceof WI.IndexSet);
    228 
    229         if (!indexes.size || !this._selectedIndexes.size)
    230             return;
    231 
    232         let firstRemovedIndex = indexes.firstIndex;
    233         if (this._selectedIndexes.lastIndex < firstRemovedIndex)
    234             return;
    235 
    236         let newSelectedIndexes = new WI.IndexSet;
    237 
    238         let lastRemovedIndex = indexes.lastIndex;
    239         if (this._selectedIndexes.firstIndex < lastRemovedIndex) {
    240             let removedCount = 0;
    241             let removedIndex = firstRemovedIndex;
    242 
    243             this._suppressSelectionDidChange = true;
    244 
    245             // Adjust the selected indexes that are in the range between the
    246             // first and last removed index (inclusive).
    247             for (let current = this._selectedIndexes.firstIndex; current < lastRemovedIndex; current = this._selectedIndexes.indexGreaterThan(current)) {
    248                 if (this.hasSelectedItem(current)) {
    249                     this.deselectItem(current);
    250                     removedCount++;
    251                     continue;
    252                 }
    253 
    254                 while (removedIndex < current) {
    255                     removedCount++;
    256                     removedIndex = indexes.indexGreaterThan(removedIndex);
    257                 }
    258 
    259                 let newIndex = current - removedCount;
    260                 newSelectedIndexes.add(newIndex);
    261 
    262                 if (this._lastSelectedIndex === current)
    263                     this._lastSelectedIndex = newIndex;
    264                 if (this._shiftAnchorIndex === current)
    265                     this._shiftAnchorIndex = newIndex;
    266             }
    267 
    268             this._suppressSelectionDidChange = false;
    269         }
    270 
    271         let removedCount = indexes.size;
    272         let current = lastRemovedIndex;
    273         while (current = this._selectedIndexes.indexGreaterThan(current))
    274             newSelectedIndexes.add(current - removedCount);
    275 
    276         if (this._lastSelectedIndex > lastRemovedIndex)
    277             this._lastSelectedIndex -= removedCount;
    278         if (this._shiftAnchorIndex > lastRemovedIndex)
    279             this._shiftAnchorIndex -= removedCount;
    280 
    281         this._selectedIndexes = newSelectedIndexes;
     198        this._lastSelectedItem = null;
     199        this._shiftAnchorItem = null;
     200        this._selectedItems.clear();
     201    }
     202
     203    didRemoveItems(items)
     204    {
     205        console.assert(items instanceof Set);
     206
     207        if (!items.size || !this._selectedItems.size)
     208           return;
     209
     210        this._updateSelectedItems(this._selectedItems.difference(items));
    282211    }
    283212
    284213    handleKeyDown(event)
    285214    {
    286         if (!this.numberOfItems)
    287             return false;
    288 
    289215        if (event.key === "a" && event.commandOrControlKey) {
    290216            this.selectAll();
     
    306232    }
    307233
    308     handleItemMouseDown(index, event)
    309     {
     234    handleItemMouseDown(item, event)
     235    {
     236        console.assert(item, "Invalid item for selection.");
     237
    310238        if (event.button !== 0 || event.ctrlKey)
    311239            return;
     
    314242        // whether or not multiple selection is enabled, so handle it first.
    315243        if (event.commandOrControlKey) {
    316             if (this.hasSelectedItem(index))
    317                 this.deselectItem(index);
     244            if (this.hasSelectedItem(item))
     245                this.deselectItem(item);
    318246            else
    319                 this.selectItem(index, this._allowsMultipleSelection);
     247                this.selectItem(item, this._allowsMultipleSelection);
    320248            return;
    321249        }
     
    323251        let shiftExtendSelection = this._allowsMultipleSelection && event.shiftKey;
    324252        if (!shiftExtendSelection) {
    325             this.selectItem(index);
    326             return;
    327         }
    328 
    329         let newSelectedItems = this._selectedIndexes.copy();
     253            this.selectItem(item);
     254            return;
     255        }
     256
     257        let newItems = new Set(this._selectedItems);
    330258
    331259        // Shift-clicking when nothing is selected should cause the first item
    332260        // through the clicked item to be selected.
    333         if (!newSelectedItems.size) {
    334             this._shiftAnchorIndex = 0;
    335             this._lastSelectedIndex = index;
    336             newSelectedItems.addRange(0, index + 1);
    337             this._updateSelectedItems(newSelectedItems);
    338             return;
    339         }
    340 
    341         if (isNaN(this._shiftAnchorIndex))
    342             this._shiftAnchorIndex = this._lastSelectedIndex;
     261        if (!newItems.size) {
     262            this._lastSelectedItem = item;
     263            this._shiftAnchorItem = this._firstSelectableItem();
     264
     265            this._addRange(newItems, this._shiftAnchorItem, this._lastSelectedItem);
     266            this._updateSelectedItems(newItems);
     267            return;
     268        }
     269
     270        if (!this._shiftAnchorItem)
     271            this._shiftAnchorItem = this._lastSelectedItem;
    343272
    344273        // Shift-clicking will add to or delete from the current selection, or
     
    348277        // selected range and add the new range between the anchor and clicked item.
    349278
    350         function normalizeRange(startIndex, endIndex) {
    351             return startIndex > endIndex ? [endIndex, startIndex] : [startIndex, endIndex];
    352         }
    353 
    354         if (this._shiftAnchorIndex !== this._lastSelectedIndex) {
    355             let [startIndex, endIndex] = normalizeRange(this._shiftAnchorIndex, this._lastSelectedIndex);
    356             newSelectedItems.deleteRange(startIndex, endIndex - startIndex + 1);
    357         }
    358 
    359         let [startIndex, endIndex] = normalizeRange(this._shiftAnchorIndex, index);
    360         newSelectedItems.addRange(startIndex, endIndex - startIndex + 1);
    361 
    362         this._lastSelectedIndex = index;
    363 
    364         this._updateSelectedItems(newSelectedItems);
     279        let sortItemPair = (a, b) => {
     280            return [a, b].sort(this._comparator);
     281        };
     282
     283        if (this._shiftAnchorItem !== this._lastSelectedItem) {
     284            let [startItem, endItem] = sortItemPair(this._shiftAnchorItem, this._lastSelectedItem);
     285            this._deleteRange(newItems, startItem, endItem);
     286        }
     287
     288        let [startItem, endItem] = sortItemPair(this._shiftAnchorItem, item);
     289        this._addRange(newItems, startItem, endItem);
     290
     291        this._lastSelectedItem = item;
     292
     293        this._updateSelectedItems(newItems);
    365294    }
    366295
    367296    // Private
    368297
    369     _deselectAllAndSelect(index)
    370     {
    371         if (!this._selectedIndexes.size)
    372             return;
    373 
    374         if (this._selectedIndexes.size === 1 && this._selectedIndexes.firstIndex === index)
    375             return;
    376 
    377         this._shiftAnchorIndex = NaN;
    378         this._lastSelectedIndex = index;
    379 
    380         let newSelectedItems = new WI.IndexSet;
    381         if (!isNaN(index))
    382             newSelectedItems.add(index);
    383 
    384         this._updateSelectedItems(newSelectedItems);
     298    _deselectAllAndSelect(item)
     299    {
     300        if (!this._selectedItems.size)
     301            return;
     302
     303        if (this._selectedItems.size === 1 && this.hasSelectedItem(item))
     304            return;
     305
     306        this._lastSelectedItem = item;
     307        this._shiftAnchorItem = null;
     308
     309        let newItems = new Set;
     310        if (item)
     311            newItems.add(item);
     312
     313        this._updateSelectedItems(newItems);
    385314    }
    386315
    387316    _selectItemsFromArrowKey(goingUp, shiftKey)
    388317    {
    389         if (!this._selectedIndexes.size) {
    390             let index = goingUp ? this.numberOfItems - 1 : 0;
    391             this.selectItem(index);
    392             return;
    393         }
    394 
    395         let index = goingUp ? this._previousSelectableIndex(this._lastSelectedIndex) : this._nextSelectableIndex(this._lastSelectedIndex);
    396         if (isNaN(index))
     318        if (!this._selectedItems.size) {
     319            this.selectItem(goingUp ? this._lastSelectableItem() : this._firstSelectableItem());
     320            return;
     321        }
     322
     323        let item = goingUp ? this._previousSelectableItem(this._lastSelectedItem) : this._nextSelectableItem(this._lastSelectedItem);
     324        if (!item)
    397325            return;
    398326
    399327        let extendSelection = shiftKey && this._allowsMultipleSelection;
    400         if (!extendSelection || !this.hasSelectedItem(index)) {
    401             this.selectItem(index, extendSelection);
     328        if (!extendSelection || !this.hasSelectedItem(item)) {
     329            this.selectItem(item, extendSelection);
    402330            return;
    403331        }
     
    406334        // extending the selection into the item, or deselecting. Determine which
    407335        // by checking whether the item opposite the anchor item is selected.
    408         let priorIndex = goingUp ? this._nextSelectableIndex(this._lastSelectedIndex) : this._previousSelectableIndex(this._lastSelectedIndex);
    409         if (!this.hasSelectedItem(priorIndex)) {
    410             this.deselectItem(this._lastSelectedIndex);
     336        let priorItem = goingUp ? this._nextSelectableItem(this._lastSelectedItem) : this._previousSelectableItem(this._lastSelectedItem);
     337        if (!priorItem || !this.hasSelectedItem(priorItem)) {
     338            this.deselectItem(this._lastSelectedItem);
    411339            return;
    412340        }
     
    415343        // anchor item then continue searching in the direction of movement
    416344        // for an unselected item to select.
    417         while (!isNaN(index)) {
    418             if (!this.hasSelectedItem(index)) {
    419                 this.selectItem(index, extendSelection);
     345        while (item) {
     346            if (!this.hasSelectedItem(item)) {
     347                this.selectItem(item, extendSelection);
    420348                break;
    421349            }
    422350
    423             this._lastSelectedIndex = index;
    424             index = goingUp ? this._previousSelectableIndex(index) : this._nextSelectableIndex(index);
    425         }
    426     }
    427 
    428     _nextSelectableIndex(index)
    429     {
    430         return this._delegate.selectionControllerNextSelectableIndex(this, index);
    431     }
    432 
    433     _previousSelectableIndex(index)
    434     {
    435         return this._delegate.selectionControllerPreviousSelectableIndex(this, index);
    436     }
    437 
    438     _updateSelectedItems(indexes)
    439     {
    440         if (this._selectedIndexes.equals(indexes))
    441             return;
    442 
    443         let oldSelectedIndexes = this._selectedIndexes.copy();
    444         this._selectedIndexes = indexes;
     351            this._lastSelectedItem = item;
     352            item = goingUp ? this._previousSelectableItem(item) : this._nextSelectableItem(item);
     353        }
     354    }
     355
     356    _firstSelectableItem()
     357    {
     358        return this._delegate.selectionControllerFirstSelectableItem(this);
     359    }
     360
     361    _lastSelectableItem()
     362    {
     363        return this._delegate.selectionControllerLastSelectableItem(this);
     364    }
     365
     366    _previousSelectableItem(item)
     367    {
     368        return this._delegate.selectionControllerPreviousSelectableItem(this, item);
     369    }
     370
     371    _nextSelectableItem(item)
     372    {
     373        return this._delegate.selectionControllerNextSelectableItem(this, item);
     374    }
     375
     376    _updateSelectedItems(items)
     377    {
     378        let oldSelectedItems = this._selectedItems;
     379        this._selectedItems = items;
    445380
    446381        if (this._suppressSelectionDidChange || !this._delegate.selectionControllerSelectionDidChange)
    447382            return;
    448383
    449         let deselectedItems = oldSelectedIndexes.difference(indexes);
    450         let selectedItems = indexes.difference(oldSelectedIndexes);
    451         this._delegate.selectionControllerSelectionDidChange(this, deselectedItems, selectedItems);
     384        let deselectedItems = oldSelectedItems.difference(items);
     385        let selectedItems = items.difference(oldSelectedItems);
     386        if (deselectedItems.size || selectedItems.size)
     387            this._delegate.selectionControllerSelectionDidChange(this, deselectedItems, selectedItems);
     388    }
     389
     390    _addRange(items, firstItem, lastItem)
     391    {
     392        let current = firstItem;
     393        while (current) {
     394            items.add(current);
     395            if (current === lastItem)
     396                break;
     397            current = this._nextSelectableItem(current);
     398        }
     399
     400        console.assert(!lastItem || items.has(lastItem), "End of range could not be reached.");
     401    }
     402
     403    _deleteRange(items, firstItem, lastItem)
     404    {
     405        let current = firstItem;
     406        while (current) {
     407            items.delete(current);
     408            if (current === lastItem)
     409                break;
     410            current = this._nextSelectableItem(current);
     411        }
     412
     413        console.assert(!lastItem || !items.has(lastItem), "End of range could not be reached.");
    452414    }
    453415};
  • trunk/Source/WebInspectorUI/UserInterface/Main.html

    r241325 r241652  
    277277    <script src="Base/Platform.js"></script>
    278278    <script src="Base/DebuggableType.js"></script>
    279     <script src="Base/IndexSet.js"></script>
    280279    <script src="Base/LinkedList.js"></script>
    281280    <script src="Base/ListMultimap.js"></script>
  • trunk/Source/WebInspectorUI/UserInterface/Test.html

    r240457 r241652  
    3939    <script src="Base/Platform.js"></script>
    4040    <script src="Base/DebuggableType.js"></script>
    41     <script src="Base/IndexSet.js"></script>
    4241    <script src="Base/LinkedList.js"></script>
    4342    <script src="Base/ListMultimap.js"></script>
  • trunk/Source/WebInspectorUI/UserInterface/Views/CookieStorageContentView.js

    r239226 r241652  
    7676    // Table dataSource
    7777
     78    tableIndexForRepresentedObject(table, object)
     79    {
     80        let index = this._cookies.indexOf(object);
     81        console.assert(index >= 0);
     82        return index;
     83    }
     84
     85    tableRepresentedObjectForIndex(table, index)
     86    {
     87        console.assert(index >= 0 && index < this._cookies.length);
     88        return this._cookies[index];
     89    }
     90
    7891    tableNumberOfRows(table)
    7992    {
  • trunk/Source/WebInspectorUI/UserInterface/Views/DOMTreeOutline.js

    r240639 r241652  
    320320    }
    321321
     322    // Protected
     323
     324    objectForSelection(treeElement)
     325    {
     326        if (treeElement instanceof WI.DOMTreeElement && treeElement.isCloseTag()) {
     327            // SelectionController requires every selectable item to be unique.
     328            // The DOMTreeElement for a close tag has the same represented object
     329            // as it's parent (the open tag). Return a proxy object associated
     330            // with the tree element for the close tag so it can be selected.
     331            if (!treeElement.__closeTagProxyObject)
     332                treeElement.__closeTagProxyObject = {__proxyObjectTreeElement: treeElement};
     333            return treeElement.__closeTagProxyObject;
     334        }
     335
     336        return super.objectForSelection(treeElement);
     337    }
     338
    322339    // Private
    323340
  • trunk/Source/WebInspectorUI/UserInterface/Views/NetworkTableContentView.js

    r241633 r241652  
    328328    // Table dataSource
    329329
     330    tableIndexForRepresentedObject(table, object)
     331    {
     332        return this._filteredEntries.indexOf(object);
     333    }
     334
     335    tableRepresentedObjectForIndex(table, index)
     336    {
     337        console.assert(index >=0 && index < this._filteredEntries.length);
     338        return this._filteredEntries[index];
     339    }
     340
    330341    tableNumberOfRows(table)
    331342    {
  • trunk/Source/WebInspectorUI/UserInterface/Views/Table.js

    r240347 r241652  
    8888        this._fillerHeight = 0; // Calculated in _resizeColumnsAndFiller.
    8989
    90         this._selectionController = new WI.SelectionController(this);
     90        this._selectionController = new WI.SelectionController(this, (a, b) => this._indexForRepresentedObject(a) - this._indexForRepresentedObject(b));
    9191
    9292        this._resizers = [];
     
    114114        this._visibleRowIndexEnd = NaN;
    115115
     116        console.assert(this._dataSource.tableIndexForRepresentedObject, "Table data source must implement tableIndexForRepresentedObject.");
     117        console.assert(this._dataSource.tableRepresentedObjectForIndex, "Table data source must implement tableRepresentedObjectForIndex.");
     118
    116119        console.assert(this._delegate.tablePopulateCell, "Table delegate must implement tablePopulateCell.");
    117120    }
     
    126129    get selectedRow()
    127130    {
    128         return this._selectionController.lastSelectedItem;
     131        let item = this._selectionController.lastSelectedItem;
     132        let index = this._indexForRepresentedObject(item);
     133        return index >= 0 ? index : NaN;
    129134    }
    130135
    131136    get selectedRows()
    132137    {
    133         return Array.from(this._selectionController.selectedItems);
     138        let rowIndexes = [];
     139        for (let item of this._selectionController.selectedItems)
     140            rowIndexes.push(this._indexForRepresentedObject(item));
     141        return rowIndexes;
    134142    }
    135143
     
    237245    isRowSelected(rowIndex)
    238246    {
    239         return this._selectionController.hasSelectedItem(rowIndex);
     247        return this._selectionController.hasSelectedItem(this._representedObjectForIndex(rowIndex));
    240248    }
    241249
     
    314322    selectRow(rowIndex, extendSelection = false)
    315323    {
    316         this._selectionController.selectItem(rowIndex, extendSelection);
     324        this._selectionController.selectItem(this._representedObjectForIndex(rowIndex), extendSelection);
    317325    }
    318326
    319327    deselectRow(rowIndex)
    320328    {
    321         this._selectionController.deselectItem(rowIndex);
     329        this._selectionController.deselectItem(this._representedObjectForIndex(rowIndex));
    322330    }
    323331
     
    339347            this.deselectRow(rowIndex);
    340348
    341         let rowIndexes = new WI.IndexSet([rowIndex]);
    342         this._removeRows(rowIndexes);
     349        this._removeRows(new Set([this._representedObjectForIndex(rowIndex)]));
    343350    }
    344351
    345352    removeSelectedRows()
    346353    {
     354        let selectedItems = this._selectionController.selectedItems;
     355        if (!selectedItems.size)
     356            return;
     357
    347358        // Change the selection before removing rows. This matches the behavior
    348359        // of macOS Finder (in list and column modes) when removing selected items.
    349         let oldSelectedItems = this._selectionController.selectedItems.copy();
    350 
    351360        this._selectionController.removeSelectedItems();
    352361
    353         if (!oldSelectedItems.equals(this._selectionController.selectedItems))
    354             this._removeRows(oldSelectedItems);
     362        this._removeRows(selectedItems);
    355363    }
    356364
     
    598606    selectionControllerSelectionDidChange(controller, deselectedItems, selectedItems)
    599607    {
    600         if (deselectedItems.size)
    601             this._toggleSelectedRowStyle(deselectedItems, false);
    602         if (selectedItems.size)
    603             this._toggleSelectedRowStyle(selectedItems, true);
     608        for (let item of deselectedItems) {
     609            let rowIndex = this._indexForRepresentedObject(item);
     610            let row = this._cachedRows.get(rowIndex);
     611            if (row)
     612                row.classList.toggle("selected", false);
     613        }
     614
     615        for (let item of selectedItems) {
     616            let rowIndex = this._indexForRepresentedObject(item);
     617            let row = this._cachedRows.get(rowIndex);
     618            if (row)
     619                row.classList.toggle("selected", true);
     620        }
    604621
    605622        if (selectedItems.size === 1) {
    606             let rowIndex = selectedItems.firstIndex;
     623            let rowIndex = this._indexForRepresentedObject(selectedItems.firstValue);
    607624            if (!this._isRowVisible(rowIndex))
    608625                this.revealRow(rowIndex);
     
    613630    }
    614631
    615     selectionControllerNumberOfItems(controller)
    616     {
    617         return this.numberOfRows;
    618     }
    619 
    620     selectionControllerNextSelectableIndex(controller, index)
    621     {
    622         if (index >= this.numberOfRows - 1)
    623             return NaN;
    624         return index + 1;
    625     }
    626 
    627     selectionControllerPreviousSelectableIndex(controller, index)
    628     {
    629         if (index <= 0)
    630             return NaN;
    631         return index - 1;
     632    selectionControllerFirstSelectableItem(controller)
     633    {
     634        return this._representedObjectForIndex(0);
     635    }
     636
     637    selectionControllerLastSelectableItem(controller)
     638    {
     639        return this._representedObjectForIndex(this.numberOfRows - 1);
     640    }
     641
     642    selectionControllerPreviousSelectableItem(controller, item)
     643    {
     644        let index = this._indexForRepresentedObject(item);
     645        console.assert(index >= 0 && index < this.numberOfRows);
     646
     647        return index > 0 ? this._representedObjectForIndex(index - 1) : null;
     648    }
     649
     650    selectionControllerNextSelectableItem(controller, item)
     651    {
     652        let index = this._indexForRepresentedObject(item);
     653        console.assert(index >= 0 && index < this.numberOfRows);
     654
     655        return index < this.numberOfRows - 1 ? this._representedObjectForIndex(index + 1) : null;
    632656    }
    633657
     
    12961320        }
    12971321
    1298         this._selectionController.handleItemMouseDown(rowIndex, event);
     1322        this._selectionController.handleItemMouseDown(this._representedObjectForIndex(rowIndex), event);
    12991323    }
    13001324
     
    13751399    }
    13761400
    1377     _removeRows(rowIndexes)
     1401    _removeRows(representedObjects)
    13781402    {
    13791403        let removed = 0;
     
    13881412        };
    13891413
    1390         for (let index = rowIndexes.firstIndex; index <= rowIndexes.lastIndex; ++index) {
    1391             if (rowIndexes.has(index)) {
     1414        let rowIndexes = [];
     1415        for (let object of representedObjects)
     1416            rowIndexes.push(this._indexForRepresentedObject(object));
     1417
     1418        rowIndexes.sort((a, b) => a - b);
     1419
     1420        let lastIndex = rowIndexes.lastValue;
     1421        for (let index = rowIndexes[0]; index <= lastIndex; ++index) {
     1422            if (rowIndexes.binaryIndexOf(index) >= 0) {
    13921423                let row = this._cachedRows.get(index);
    13931424                if (row) {
     
    14061437            return;
    14071438
    1408         for (let index = rowIndexes.lastIndex + 1; index < this._cachedNumberOfRows; ++index)
     1439        for (let index = lastIndex + 1; index < this._cachedNumberOfRows; ++index)
    14091440            adjustRowAtIndex(index);
    14101441
     
    14121443        console.assert(this._cachedNumberOfRows >= 0);
    14131444
    1414         this._selectionController.didRemoveItems(rowIndexes);
     1445        this._selectionController.didRemoveItems(representedObjects);
    14151446
    14161447        if (this._delegate.tableDidRemoveRows) {
    1417             this._delegate.tableDidRemoveRows(this, Array.from(rowIndexes));
     1448            this._delegate.tableDidRemoveRows(this, rowIndexes);
    14181449            console.assert(this._cachedNumberOfRows === this._dataSource.tableNumberOfRows(this), "Table data source should update after removing rows.");
    14191450        }
    14201451    }
    14211452
    1422     _toggleSelectedRowStyle(rowIndexes, flag)
    1423     {
    1424         for (let index of rowIndexes) {
    1425             let row = this._cachedRows.get(index);
    1426             if (row)
    1427                 row.classList.toggle("selected", flag);
    1428         }
     1453    _indexForRepresentedObject(object)
     1454    {
     1455        return this.dataSource.tableIndexForRepresentedObject(this, object);
     1456    }
     1457
     1458    _representedObjectForIndex(index)
     1459    {
     1460        return this.dataSource.tableRepresentedObjectForIndex(this, index);
    14291461    }
    14301462};
  • trunk/Source/WebInspectorUI/UserInterface/Views/TreeOutline.js

    r241003 r241652  
    5757        this._cachedNumberOfDescendents = 0;
    5858        this._previousSelectedTreeElement = null;
    59         this._selectionController = new WI.SelectionController(this);
     59
     60        let comparator = (a, b) => {
     61            function getLevel(treeElement) {
     62                let level = 0;
     63                while (treeElement = treeElement.parent)
     64                    level++;
     65                return level;
     66            }
     67
     68            function compareSiblings(s, t) {
     69                return s.parent.children.indexOf(s) - s.parent.children.indexOf(t);
     70            }
     71
     72            // Translate represented objects to TreeElements, which have the
     73            // hierarchical information needed to perform the comparison.
     74            a = this.getCachedTreeElement(a);
     75            b = this.getCachedTreeElement(b);
     76            if (!a || !b)
     77                return 0;
     78
     79            if (a.parent === b.parent)
     80                return compareSiblings(a, b);
     81
     82            let aLevel = getLevel(a);
     83            let bLevel = getLevel(b);
     84            while (aLevel > bLevel) {
     85                if (a.parent === b)
     86                    return 1;
     87                a = a.parent;
     88                aLevel--;
     89            }
     90            while (bLevel > aLevel) {
     91                if (b.parent === a)
     92                    return -1;
     93                b = b.parent;
     94                bLevel--;
     95            }
     96
     97            while (a.parent !== b.parent) {
     98                a = a.parent;
     99                b = b.parent;
     100            }
     101
     102            console.assert(a.parent === b.parent, "Missing common ancestor for TreeElements.", a, b);
     103            return compareSiblings(a, b);
     104        };
     105
     106        this._selectionController = new WI.SelectionController(this, comparator);
    60107
    61108        this._itemWasSelectedByUser = false;
     
    104151    get selectedTreeElement()
    105152    {
    106         let selectedIndex = this._selectionController.lastSelectedItem;
    107         return this._treeElementAtIndex(selectedIndex) || null;
     153        return this.getCachedTreeElement(this._selectionController.lastSelectedItem);
    108154    }
    109155
    110156    set selectedTreeElement(treeElement)
    111157    {
    112         if (treeElement) {
    113             let index = this._indexOfTreeElement(treeElement);
    114             this._selectionController.selectItem(index);
    115         } else
     158        if (treeElement)
     159            this._selectionController.selectItem(this.objectForSelection(treeElement));
     160        else
    116161            this._selectionController.deselectAll();
    117162    }
     
    121166        if (this.allowsMultipleSelection) {
    122167            let treeElements = [];
    123             for (let index of this._selectionController.selectedItems)
    124                 treeElements.push(this._treeElementAtIndex(index));
     168            for (let representedObject of this._selectionController.selectedItems)
     169                treeElements.push(this.getCachedTreeElement(representedObject));
    125170            return treeElements;
    126171        }
     
    324369        }
    325370
    326         let removedIndexes = null;
    327 
    328371        let treeOutline = child.treeOutline;
    329372        if (treeOutline) {
    330373            treeOutline._forgetTreeElement(child);
    331374            treeOutline._forgetChildrenRecursive(child);
    332             removedIndexes = treeOutline._indexesForSubtree(child);
    333375        }
    334376
     
    346388        child.previousSibling = null;
    347389
    348         if (treeOutline) {
    349             treeOutline._selectionController.didRemoveItems(removedIndexes);
     390        if (treeOutline)
    350391            treeOutline.dispatchEventToListeners(WI.TreeOutline.Event.ElementRemoved, {element: child});
    351         }
    352392    }
    353393
     
    384424            }
    385425
    386             let removedIndexes = treeOutline._indexesForSubtree(child);
    387 
    388426            child._detach();
    389427            child.treeOutline = null;
     
    394432            this.children.shift();
    395433
    396             if (treeOutline) {
    397                 treeOutline._selectionController.didRemoveItems(removedIndexes);
     434            if (treeOutline)
    398435                treeOutline.dispatchEventToListeners(WI.TreeOutline.Event.ElementRemoved, {element: child});
    399             }
    400436        }
    401437    }
     
    414450        elements.push(element);
    415451        this._cachedNumberOfDescendents++;
    416 
    417         let index = this._indexOfTreeElement(element);
    418         if (index >= 0) {
    419             console.assert(!element.selected, "TreeElement should not be selected before being inserted.");
    420             this._selectionController.didInsertItem(index);
    421         }
    422452    }
    423453
     
    447477        if (!representedObject)
    448478            return null;
     479
     480        // SelectionController requires every selectable object to be unique.
     481        // A TreeOutline subclass where multiple TreeElements may be associated
     482        // with one represented object can override objectForSelection, and return
     483        // a proxy object that is associated with a single TreeElement.
     484        if (representedObject.__proxyObjectTreeElement)
     485            return representedObject.__proxyObjectTreeElement;
    449486
    450487        if (representedObject.__treeElementIdentifier) {
     
    807844        this._processingSelectionChange = true;
    808845
    809         for (let index of deselectedItems) {
    810             let treeElement = this._treeElementAtIndex(index);
    811             console.assert(treeElement, "Missing TreeElement for deselected index " + index);
    812             if (treeElement) {
    813                 if (treeElement.listItemElement)
    814                     treeElement.listItemElement.classList.remove("selected");
    815                 treeElement.deselect();
    816             }
    817         }
    818 
    819         for (let index of selectedItems) {
    820             let treeElement = this._treeElementAtIndex(index);
    821             console.assert(treeElement, "Missing TreeElement for selected index " + index);
    822             if (treeElement) {
    823                 if (treeElement.listItemElement)
    824                     treeElement.listItemElement.classList.add("selected");
    825                 const omitFocus = true;
    826                 treeElement.select(omitFocus);
    827             }
     846        for (let representedObject of deselectedItems) {
     847            let treeElement = this.getCachedTreeElement(representedObject);
     848            if (!treeElement)
     849                continue;
     850
     851            if (treeElement.listItemElement)
     852                treeElement.listItemElement.classList.remove("selected");
     853
     854            treeElement.deselect();
     855        }
     856
     857        for (let representedObject of selectedItems) {
     858            let treeElement = this.getCachedTreeElement(representedObject);
     859            if (!treeElement)
     860                continue;
     861
     862            if (treeElement.listItemElement)
     863                treeElement.listItemElement.classList.add("selected");
     864
     865            const omitFocus = true;
     866            treeElement.select(omitFocus);
    828867        }
    829868
     
    844883    }
    845884
    846     selectionControllerNextSelectableIndex(controller, index)
    847     {
    848         let treeElement = this._treeElementAtIndex(index);
     885    selectionControllerFirstSelectableItem(controller)
     886    {
     887        let firstChild = this.children[0];
     888        if (firstChild.selectable)
     889            return firstChild.representedObject;
     890        return this.selectionControllerNextSelectableItem(controller, firstChild.representedObject);
     891    }
     892
     893    selectionControllerLastSelectableItem(controller)
     894    {
     895        let treeElement = this.children.lastValue;
     896        while (treeElement.expanded && treeElement.children.length)
     897            treeElement = treeElement.children.lastValue;
     898
     899        let item = this.objectForSelection(treeElement);
     900        if (treeElement.selectable)
     901            return item;
     902        return this.selectionControllerPreviousSelectableItem(controller, item);
     903    }
     904
     905    selectionControllerPreviousSelectableItem(controller, item)
     906    {
     907        let treeElement = this.getCachedTreeElement(item);
     908        console.assert(treeElement, "Missing TreeElement for representedObject.", item);
    849909        if (!treeElement)
    850             return NaN;
     910            return null;
    851911
    852912        const skipUnrevealed = true;
     
    854914        const dontPopulate = true;
    855915
    856         while (treeElement = treeElement.traverseNextTreeElement(skipUnrevealed, stayWithin, dontPopulate)) {
     916        while (treeElement = treeElement.traversePreviousTreeElement(skipUnrevealed, stayWithin, dontPopulate)) {
    857917            if (treeElement.selectable)
    858                 return this._indexOfTreeElement(treeElement);
    859         }
    860 
    861         return NaN;
    862     }
    863 
    864     selectionControllerPreviousSelectableIndex(controller, index)
    865     {
    866         let treeElement = this._treeElementAtIndex(index);
     918                return this.objectForSelection(treeElement);
     919        }
     920
     921        return null;
     922    }
     923
     924    selectionControllerNextSelectableItem(controller, item)
     925    {
     926        let treeElement = this.getCachedTreeElement(item);
     927        console.assert(treeElement, "Missing TreeElement for representedObject.", item);
    867928        if (!treeElement)
    868             return NaN;
     929            return null;
    869930
    870931        const skipUnrevealed = true;
     
    872933        const dontPopulate = true;
    873934
    874         while (treeElement = treeElement.traversePreviousTreeElement(skipUnrevealed, stayWithin, dontPopulate)) {
     935        while (treeElement = treeElement.traverseNextTreeElement(skipUnrevealed, stayWithin, dontPopulate)) {
    875936            if (treeElement.selectable)
    876                 return this._indexOfTreeElement(treeElement);
    877         }
    878 
    879         return NaN;
     937                return this.objectForSelection(treeElement);
     938        }
     939
     940        return null;
    880941    }
    881942
    882943    // Protected
     944
     945    objectForSelection(treeElement)
     946    {
     947        return treeElement.representedObject;
     948    }
    883949
    884950    selectTreeElementInternal(treeElement, suppressNotification = false, selectedByUser = false)
     
    10231089        }
    10241090
    1025         let index = this._indexOfTreeElement(treeElement);
    1026         if (isNaN(index))
    1027             return;
    1028 
    10291091        this._itemWasSelectedByUser = true;
    1030         this._selectionController.handleItemMouseDown(index, event);
     1092        this._selectionController.handleItemMouseDown(this.objectForSelection(treeElement), event);
    10311093        this._itemWasSelectedByUser = false;
    1032     }
    1033 
    1034     _indexOfTreeElement(treeElement)
    1035     {
    1036         const skipUnrevealed = false;
    1037         const stayWithin = null;
    1038         const dontPopulate = true;
    1039 
    1040         let index = 0;
    1041         let current = this.children[0];
    1042         while (current) {
    1043             if (treeElement === current)
    1044                 return index;
    1045 
    1046             current = current.traverseNextTreeElement(skipUnrevealed, stayWithin, dontPopulate);
    1047             ++index;
    1048         }
    1049 
    1050         console.assert(false, "Unable to get index for tree element.", treeElement);
    1051         return NaN;
    1052     }
    1053 
    1054     _treeElementAtIndex(index)
    1055     {
    1056         const skipUnrevealed = false;
    1057         const stayWithin = null;
    1058         const dontPopulate = true;
    1059 
    1060         let current = 0;
    1061         let treeElement = this.children[0];
    1062         while (treeElement) {
    1063             if (current === index)
    1064                 return treeElement;
    1065 
    1066             treeElement = treeElement.traverseNextTreeElement(skipUnrevealed, stayWithin, dontPopulate);
    1067             ++current;
    1068         }
    1069 
    1070         return null;
    10711094    }
    10721095
     
    10821105
    10831106        this.dispatchEventToListeners(WI.TreeOutline.Event.SelectionDidChange, {selectedByUser});
    1084     }
    1085 
    1086     _indexesForSubtree(treeElement)
    1087     {
    1088         let treeOutline = treeElement.treeOutline;
    1089         if (!treeOutline)
    1090             return null;
    1091 
    1092         function numberOfElementsInSubtree(treeElement) {
    1093             let elements = treeElement.root ? Array.from(treeElement.children) : [treeElement];
    1094             let count = 0;
    1095             while (elements.length) {
    1096                 let child = elements.pop();
    1097                 if (child.hidden)
    1098                     continue;
    1099 
    1100                 count++;
    1101                 elements = elements.concat(child.children);
    1102             }
    1103             return count;
    1104         }
    1105 
    1106         let firstChild = treeElement.root ? treeElement.children[0] : treeElement;
    1107         let startIndex = treeOutline._indexOfTreeElement(firstChild);
    1108         let count = numberOfElementsInSubtree(treeElement);
    1109         let indexes = new WI.IndexSet;
    1110         indexes.addRange(startIndex, count);
    1111         return indexes;
    11121107    }
    11131108};
Note: See TracChangeset for help on using the changeset viewer.