Bug 191977 - Web Inspector: Table selection should be handled by a SelectionController
Summary: Web Inspector: Table selection should be handled by a SelectionController
Status: RESOLVED FIXED
Alias: None
Product: WebKit
Classification: Unclassified
Component: Web Inspector (show other bugs)
Version: WebKit Nightly Build
Hardware: All All
: P3 Normal
Assignee: Matt Baker
URL:
Keywords: InRadar
Depends on:
Blocks: 191483
  Show dependency treegraph
 
Reported: 2018-11-26 12:36 PST by Matt Baker
Modified: 2018-11-27 11:41 PST (History)
6 users (show)

See Also:


Attachments
Patch (34.17 KB, patch)
2018-11-26 13:00 PST, Matt Baker
no flags Details | Formatted Diff | Diff
Archive of layout-test-results from ews102 for mac-sierra (2.51 MB, application/zip)
2018-11-26 14:02 PST, EWS Watchlist
no flags Details
Archive of layout-test-results from ews105 for mac-sierra-wk2 (3.49 MB, application/zip)
2018-11-26 14:14 PST, EWS Watchlist
no flags Details
Archive of layout-test-results from ews115 for mac-sierra (2.21 MB, application/zip)
2018-11-26 14:55 PST, EWS Watchlist
no flags Details
Patch (36.96 KB, patch)
2018-11-27 08:40 PST, Matt Baker
no flags Details | Formatted Diff | Diff
Patch (36.99 KB, patch)
2018-11-27 10:31 PST, Matt Baker
no flags Details | Formatted Diff | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Matt Baker 2018-11-26 12:36:58 PST
Summary:
Table selection should be handled by a SelectionController. This is a step toward adding multiple-selection to TreeOutline in https://webkit.org/b/191483.

Notes:
The controller and its delegate (a Table or TreeOutline):

class SelectionController
{
    constructor(delegate)

    get allowsMultipleSelection()
    set allowsMultipleSelection(flag)

    get lastSelectedItem()
    get selectedItems()

    get numberOfItems()

    hasSelectedItem(index)
    selectItem(index, extendSelection = false)
    deselectItem(index)

    selectAll()
    deselectAll()
    removeSelectedItems()

    clear()

    didInsertItem(index);
    didRemoveItem(index);

    handleKeyDown(event)
    handleItemMouseDown(index, event)    
}

class SelectionControllerDelegate
{
    // Required

    selectionControllerNumberOfItems(controller)
    selectionControllerNextSelectableIndex(controller, index)
    selectionControllerPreviousSelectableIndex(controller, index)

    // Optional

    selectionControllerSelectionDidChange(controller, deselectedItems, selectedItems)
}
Comment 1 Radar WebKit Bug Importer 2018-11-26 12:37:29 PST
<rdar://problem/46253093>
Comment 2 Matt Baker 2018-11-26 13:00:55 PST
Created attachment 355667 [details]
Patch
Comment 3 EWS Watchlist 2018-11-26 14:02:06 PST
Comment on attachment 355667 [details]
Patch

Attachment 355667 [details] did not pass mac-ews (mac):
Output: https://webkit-queues.webkit.org/results/10157964

New failing tests:
inspector/table/table-remove-rows.html
Comment 4 EWS Watchlist 2018-11-26 14:02:08 PST
Created attachment 355673 [details]
Archive of layout-test-results from ews102 for mac-sierra

The attached test failures were seen while running run-webkit-tests on the mac-ews.
Bot: ews102  Port: mac-sierra  Platform: Mac OS X 10.12.6
Comment 5 EWS Watchlist 2018-11-26 14:14:04 PST
Comment on attachment 355667 [details]
Patch

Attachment 355667 [details] did not pass mac-wk2-ews (mac-wk2):
Output: https://webkit-queues.webkit.org/results/10157983

New failing tests:
inspector/table/table-remove-rows.html
Comment 6 EWS Watchlist 2018-11-26 14:14:05 PST
Created attachment 355674 [details]
Archive of layout-test-results from ews105 for mac-sierra-wk2

The attached test failures were seen while running run-webkit-tests on the mac-wk2-ews.
Bot: ews105  Port: mac-sierra-wk2  Platform: Mac OS X 10.12.6
Comment 7 EWS Watchlist 2018-11-26 14:55:46 PST
Comment on attachment 355667 [details]
Patch

Attachment 355667 [details] did not pass mac-debug-ews (mac):
Output: https://webkit-queues.webkit.org/results/10158190

New failing tests:
inspector/table/table-remove-rows.html
Comment 8 EWS Watchlist 2018-11-26 14:55:47 PST
Created attachment 355678 [details]
Archive of layout-test-results from ews115 for mac-sierra

The attached test failures were seen while running run-webkit-tests on the mac-debug-ews.
Bot: ews115  Port: mac-sierra  Platform: Mac OS X 10.12.6
Comment 9 Matt Baker 2018-11-26 15:12:15 PST
Comment on attachment 355667 [details]
Patch

r-. Need to fix Table.prototype._removeRows.
Comment 10 Devin Rousso 2018-11-26 15:21:28 PST
Comment on attachment 355667 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=355667&action=review

r-, due to test failures (as discussed offline), and the lack of handling item addition/removal (e.g. `didInsertItem` and `didRemoveItem`)

Otherwise, looks good!  Was there a reason you decided on going forward with an index-based approach vs an object-based approach, like we discussed before Thanksgiving?  I think the object-based approach will be much more flexible for `WI.TreeOutline` and would likely require far less work to implement vs an index-based approach.

> Source/WebInspectorUI/UserInterface/Controllers/SelectionController.js:33
> +        this._delegate = delegate;

NIT: would this really be considered a delegate?  Perhaps "view" or something else like that to better explain that this is the "controlled object"?

> Source/WebInspectorUI/UserInterface/Controllers/SelectionController.js:42
> +        console.assert(this._delegate.selectionControllerNextSelectableIndex, "SelectionController delegate must implement selectionControllerNextSelectableIndex.");

`selectionControllerPreviousSelectableIndex`?

> Source/WebInspectorUI/UserInterface/Controllers/SelectionController.js:70
> +    get lastSelectedItem() { return this._lastSelectedIndex; }
> +    get selectedItems() { return this._selectedIndexes; }

Style: put all one-line getters at the top of the Public section

> Source/WebInspectorUI/UserInterface/Controllers/SelectionController.js:149
> +        this._lastSelectedIndex = newSelectedItems.lastIndex;

Should we also treat the last index as the anchor for shift-selection?  This seems to be how Finder does it.

> Source/WebInspectorUI/UserInterface/Controllers/SelectionController.js:187
> +    clear()

NIT: I'd prefer `reset`, as `clear` is too similar to "clear selection or "deselect all" in my mind

> Source/WebInspectorUI/UserInterface/Controllers/SelectionController.js:197
> +            return false;

Is there a reason this function needs to return a boolean?

> Source/WebInspectorUI/UserInterface/Controllers/SelectionController.js:223
> +        if (event.commandOrControlKey) {

Nice!

> Source/WebInspectorUI/UserInterface/Controllers/SelectionController.js:359
> +        let deselectedItems = oldSelectedIndexes.difference(indexes);
> +        let selectedItems = indexes.difference(oldSelectedIndexes);
> +        this._delegate.selectionControllerSelectionDidChange(this, deselectedItems, selectedItems);

I like this new approach!

> Source/WebInspectorUI/UserInterface/Views/Table.js:347
> +        if (!oldSelectedItems.equals(this._selectionController.selectedItems))

Why is this check needed?

> Source/WebInspectorUI/UserInterface/Views/Table.js:594
> +        if (index === this.numberOfRows - 1)

NIT: for sanity's sake, we should either add an assert or change this to be a `>=`

> Source/WebInspectorUI/UserInterface/Views/Table.js:601
> +        if (index === 0)

NIT: for sanity's sake, we should either add an assert or change this to be a `<=`

> Source/WebInspectorUI/UserInterface/Views/Table.js:1244
> +        this._selectionController.handleKeyDown(event);

This won't call `revealRow` when moving Up/Down.  I think that's an issue if we try to go Up/Down without a previous selection, as the item to select may be outside what's been cached.

Perhaps there should be a `selectionControllerRevealIndex` delegate call?
Comment 11 Matt Baker 2018-11-27 08:38:15 PST
Comment on attachment 355667 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=355667&action=review

>> Source/WebInspectorUI/UserInterface/Controllers/SelectionController.js:33
>> +        this._delegate = delegate;
> 
> NIT: would this really be considered a delegate?  Perhaps "view" or something else like that to better explain that this is the "controlled object"?

In practice it's a view, but delegate is very general which I like here.

>> Source/WebInspectorUI/UserInterface/Controllers/SelectionController.js:70
>> +    get selectedItems() { return this._selectedIndexes; }
> 
> Style: put all one-line getters at the top of the Public section

I'll change it here, but in general I don't think this should be a hard and fast rule. Sometimes it makes sense to group properties semantically.

>> Source/WebInspectorUI/UserInterface/Controllers/SelectionController.js:149
>> +        this._lastSelectedIndex = newSelectedItems.lastIndex;
> 
> Should we also treat the last index as the anchor for shift-selection?  This seems to be how Finder does it.

Nice observation. In fact, it looks like Finder only does this in the absence of an existing shift-selection anchor; I'll match that behavior.

>> Source/WebInspectorUI/UserInterface/Controllers/SelectionController.js:187
>> +    clear()
> 
> NIT: I'd prefer `reset`, as `clear` is too similar to "clear selection or "deselect all" in my mind

`reset` is better.

>> Source/WebInspectorUI/UserInterface/Controllers/SelectionController.js:197
>> +            return false;
> 
> Is there a reason this function needs to return a boolean?

There is. TreeOutline will use the return value. I should have added the return value in that patch, but let's just keep this.

>> Source/WebInspectorUI/UserInterface/Views/Table.js:1244
>> +        this._selectionController.handleKeyDown(event);
> 
> This won't call `revealRow` when moving Up/Down.  I think that's an issue if we try to go Up/Down without a previous selection, as the item to select may be outside what's been cached.
> 
> Perhaps there should be a `selectionControllerRevealIndex` delegate call?

Great catch. We can resolve this in Table, without extending the delegate:

 selectionControllerSelectionDidChange(controller, deselectedItems, selectedItems)
 {
     if (deselectedItems.size)
         this._toggleSelectedRowStyle(deselectedItems, false);
     if (selectedItems.size)
         this._toggleSelectedRowStyle(selectedItems, true);

+    if (selectedItems.size === 1) {
+        let rowIndex = selectedItems.firstIndex;
+        if (!this._isRowVisible(rowIndex))
+            this.revealRow(rowIndex);
+    }

     if (this._delegate.tableSelectionDidChange)
         this._delegate.tableSelectionDidChange(this);
 }
Comment 12 Matt Baker 2018-11-27 08:40:46 PST
Created attachment 355735 [details]
Patch
Comment 13 Matt Baker 2018-11-27 08:47:34 PST
(In reply to Devin Rousso from comment #10)
> Comment on attachment 355667 [details]
> Patch
> 
> View in context:
> https://bugs.webkit.org/attachment.cgi?id=355667&action=review
> 
> r-, due to test failures (as discussed offline), and the lack of handling
> item addition/removal (e.g. `didInsertItem` and `didRemoveItem`)
> 
> Otherwise, looks good!  Was there a reason you decided on going forward with
> an index-based approach vs an object-based approach, like we discussed
> before Thanksgiving?  I think the object-based approach will be much more
> flexible for `WI.TreeOutline` and would likely require far less work to
> implement vs an index-based approach.

An object-based approach has the advantage of being slightly simpler, in that `didRemoveItem` and `didInsertItem` aren't needed to keep SelectionController's stored indexes in synch with the view. This has the consequence that the selected items are no longer in order, however, which is an issue for copying rows from Table.

The index-based approach was also complete, and I didn't want to rock the boat at the 11th hour. ;)
Comment 14 Devin Rousso 2018-11-27 09:08:38 PST
(In reply to Matt Baker from comment #13)
> The index-based approach was also complete, and I didn't want to rock the boat at the 11th hour. ;)
No worries!  Was just wondering :)

I think an object-based approach will be more flexible, but in the spirit of getting things done, this works just as well =D
Comment 15 Devin Rousso 2018-11-27 09:29:27 PST
Comment on attachment 355735 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=355735&action=review

> Source/WebInspectorUI/UserInterface/Controllers/SelectionController.js:42
> +        console.assert(this._delegate.selectionControllerNextSelectableIndex, "SelectionController delegate must implement selectionControllerNextSelectableIndex.");

`selectionControllerPreviousSelectableIndex`?

> Source/WebInspectorUI/UserInterface/Controllers/SelectionController.js:195
> +    didRemoveItem(index)

What happened to `didInsertItem`?  Don't we need to do effectively the same logic but instead add one to each index?

> Source/WebInspectorUI/UserInterface/Controllers/SelectionController.js:200
> +        while (index = this._selectedIndexes.indexGreaterThan(index)) {

So this is relying on the falsey nature of `NaN` to return when there is no greater index?

> Source/WebInspectorUI/UserInterface/Controllers/SelectionController.js:225
> +        }

There should be a default return value after this.
Comment 16 Matt Baker 2018-11-27 10:31:19 PST
Created attachment 355747 [details]
Patch
Comment 17 Matt Baker 2018-11-27 10:33:15 PST
Comment on attachment 355735 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=355735&action=review

>> Source/WebInspectorUI/UserInterface/Controllers/SelectionController.js:195
>> +    didRemoveItem(index)
> 
> What happened to `didInsertItem`?  Don't we need to do effectively the same logic but instead add one to each index?

Individual items aren't inserted into Table, only removed. TreeOutline inserts individual items, so it will be added in https://webkit.org/b/191483.

>> Source/WebInspectorUI/UserInterface/Controllers/SelectionController.js:200
>> +        while (index = this._selectedIndexes.indexGreaterThan(index)) {
> 
> So this is relying on the falsey nature of `NaN` to return when there is no greater index?

Yep.
Comment 18 Devin Rousso 2018-11-27 10:59:36 PST
Comment on attachment 355747 [details]
Patch

r=me, nice work :)
Comment 19 WebKit Commit Bot 2018-11-27 11:41:24 PST
Comment on attachment 355747 [details]
Patch

Clearing flags on attachment: 355747

Committed r238563: <https://trac.webkit.org/changeset/238563>
Comment 20 WebKit Commit Bot 2018-11-27 11:41:26 PST
All reviewed patches have been landed.  Closing bug.