Changeset 237746 in webkit


Ignore:
Timestamp:
Nov 2, 2018 12:39:01 PM (5 years ago)
Author:
Matt Baker
Message:

Web Inspector: support multiple selection/deletion of cookie records
https://bugs.webkit.org/show_bug.cgi?id=66381
<rdar://problem/19281525>

Reviewed by Devin Rousso.

  • Localizations/en.lproj/localizedStrings.js:
  • UserInterface/Views/CookieStorageContentView.js:

(WI.CookieStorageContentView):
(WI.CookieStorageContentView.prototype.get scrollableElements):
(WI.CookieStorageContentView.prototype.tableNumberOfRows):
(WI.CookieStorageContentView.prototype.tableSortChanged):
(WI.CookieStorageContentView.prototype.tableCellContextMenuClicked):
(WI.CookieStorageContentView.prototype.tableDidRemoveRows):
(WI.CookieStorageContentView.prototype.tablePopulateCell):
(WI.CookieStorageContentView.prototype.initialLayout):
(WI.CookieStorageContentView.prototype._generateSortComparator):
(WI.CookieStorageContentView.prototype._refreshButtonClicked):
(WI.CookieStorageContentView.prototype._reloadCookies):
(WI.CookieStorageContentView.prototype._updateSort):
(WI.CookieStorageContentView.prototype._handleTableKeyDown):
(WI.CookieStorageContentView.prototype.update): Deleted.
(WI.CookieStorageContentView.prototype._rebuildTable): Deleted.
(WI.CookieStorageContentView.prototype._sortDataGrid.localeCompare): Deleted.
(WI.CookieStorageContentView.prototype._sortDataGrid.numberCompare): Deleted.
(WI.CookieStorageContentView.prototype._sortDataGrid.expiresCompare): Deleted.
(WI.CookieStorageContentView.prototype._sortDataGrid): Deleted.
(WI.CookieStorageContentView.prototype._deleteCallback): Deleted.
Replace DataGrid with Table. The content view serves as the table
delegate and data source, and handles delete and backspace key events
to allow deleting the selected cookies. Cookies may also be deleted from
the table context menu and a new button in the navigation bar.

  • UserInterface/Views/Table.js:

(WI.Table.prototype.isRowSelected):
(WI.Table.prototype.selectRow):
(WI.Table.prototype.deselectRow):
(WI.Table.prototype.removeRow):
(WI.Table.prototype._getOrCreateRow):
(WI.Table.prototype._handleMouseDown):
(WI.Table.prototype._removeRows):
(WI.Table.prototype._isRowSelected): Deleted.
Make isSelectedRow public. It is more convenient and efficient than
calling Table.prototype.selectedRows.includes(rowIndex).

Location:
trunk/Source/WebInspectorUI
Files:
4 edited

Legend:

Unmodified
Added
Removed
  • trunk/Source/WebInspectorUI/ChangeLog

    r237720 r237746  
     12018-11-02  Matt Baker  <mattbaker@apple.com>
     2
     3        Web Inspector: support multiple selection/deletion of cookie records
     4        https://bugs.webkit.org/show_bug.cgi?id=66381
     5        <rdar://problem/19281525>
     6
     7        Reviewed by Devin Rousso.
     8
     9        * Localizations/en.lproj/localizedStrings.js:
     10
     11        * UserInterface/Views/CookieStorageContentView.js:
     12        (WI.CookieStorageContentView):
     13        (WI.CookieStorageContentView.prototype.get scrollableElements):
     14        (WI.CookieStorageContentView.prototype.tableNumberOfRows):
     15        (WI.CookieStorageContentView.prototype.tableSortChanged):
     16        (WI.CookieStorageContentView.prototype.tableCellContextMenuClicked):
     17        (WI.CookieStorageContentView.prototype.tableDidRemoveRows):
     18        (WI.CookieStorageContentView.prototype.tablePopulateCell):
     19        (WI.CookieStorageContentView.prototype.initialLayout):
     20        (WI.CookieStorageContentView.prototype._generateSortComparator):
     21        (WI.CookieStorageContentView.prototype._refreshButtonClicked):
     22        (WI.CookieStorageContentView.prototype._reloadCookies):
     23        (WI.CookieStorageContentView.prototype._updateSort):
     24        (WI.CookieStorageContentView.prototype._handleTableKeyDown):
     25        (WI.CookieStorageContentView.prototype.update): Deleted.
     26        (WI.CookieStorageContentView.prototype._rebuildTable): Deleted.
     27        (WI.CookieStorageContentView.prototype._sortDataGrid.localeCompare): Deleted.
     28        (WI.CookieStorageContentView.prototype._sortDataGrid.numberCompare): Deleted.
     29        (WI.CookieStorageContentView.prototype._sortDataGrid.expiresCompare): Deleted.
     30        (WI.CookieStorageContentView.prototype._sortDataGrid): Deleted.
     31        (WI.CookieStorageContentView.prototype._deleteCallback): Deleted.
     32        Replace DataGrid with Table. The content view serves as the table
     33        delegate and data source, and handles delete and backspace key events
     34        to allow deleting the selected cookies. Cookies may also be deleted from
     35        the table context menu and a new button in the navigation bar.
     36
     37        * UserInterface/Views/Table.js:
     38        (WI.Table.prototype.isRowSelected):
     39        (WI.Table.prototype.selectRow):
     40        (WI.Table.prototype.deselectRow):
     41        (WI.Table.prototype.removeRow):
     42        (WI.Table.prototype._getOrCreateRow):
     43        (WI.Table.prototype._handleMouseDown):
     44        (WI.Table.prototype._removeRows):
     45        (WI.Table.prototype._isRowSelected): Deleted.
     46        Make `isSelectedRow` public. It is more convenient and efficient than
     47        calling `Table.prototype.selectedRows.includes(rowIndex)`.
     48
    1492018-11-01  Devin Rousso  <drousso@apple.com>
    250
  • trunk/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js

    r237708 r237746  
    376376localizedStrings["Expanded"] = "Expanded";
    377377localizedStrings["Experimental"] = "Experimental";
    378 localizedStrings["Expires"] = "Expires";
    379378localizedStrings["Export"] = "Export";
    380379localizedStrings["Export HAR"] = "Export HAR";
     
    442441localizedStrings["HTML"] = "HTML";
    443442localizedStrings["HTML Attributes"] = "HTML Attributes";
    444 localizedStrings["HTTP"] = "HTTP";
    445443localizedStrings["Headers"] = "Headers";
    446444localizedStrings["Headers:"] = "Headers:";
     
    747745localizedStrings["Run %d"] = "Run %d";
    748746localizedStrings["Running the “%s“ audit"] = "Running the “%s“ audit";
    749 localizedStrings["Same-Site"] = "Same-Site";
    750747localizedStrings["Samples"] = "Samples";
    751748localizedStrings["Save File"] = "Save File";
  • trunk/Source/WebInspectorUI/UserInterface/Views/CookieStorageContentView.js

    r236766 r237746  
    3232        this.element.classList.add("cookie-storage");
    3333
     34        this._cookies = [];
     35        this._sortComparator = null;
     36        this._table = null;
     37
    3438        this._refreshButtonNavigationItem = new WI.ButtonNavigationItem("cookie-storage-refresh", WI.UIString("Refresh"), "Images/ReloadFull.svg", 13, 13);
    3539        this._refreshButtonNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._refreshButtonClicked, this);
    36 
    37         this.update();
    3840    }
    3941
     
    4345    {
    4446        return [this._refreshButtonNavigationItem];
    45     }
    46 
    47     update()
    48     {
    49         PageAgent.getCookies().then((payload) => {
    50             this._cookies = this._filterCookies(payload.cookies);
    51             this._rebuildTable();
    52         }).catch((error) => {
    53             console.error("Could not fetch cookies: ", error);
    54         });
    5547    }
    5648
     
    6355    get scrollableElements()
    6456    {
    65         if (!this._dataGrid)
     57        if (!this._table)
    6658            return [];
    67         return [this._dataGrid.scrollContainer];
     59        return [this._table.scrollContainer];
     60    }
     61
     62    // Table dataSource
     63
     64    tableNumberOfRows(table)
     65    {
     66        return this._cookies.length;
     67    }
     68
     69    tableSortChanged(table)
     70    {
     71        this._generateSortComparator();
     72
     73        if (!this._sortComparator)
     74            return;
     75
     76        this._updateSort();
     77        this._table.reloadData();
     78    }
     79
     80    // Table delegate
     81
     82    tableCellContextMenuClicked(table, cell, column, rowIndex, event)
     83    {
     84        let contextMenu = WI.ContextMenu.createFromEvent(event);
     85
     86        contextMenu.appendSeparator();
     87        contextMenu.appendItem(WI.UIString("Delete"), () => {
     88            if (table.isRowSelected(rowIndex))
     89                table.removeSelectedRows();
     90            else
     91                table.removeRow(rowIndex);
     92        });
     93        contextMenu.appendSeparator();
     94    }
     95
     96    tableDidRemoveRows(table, rowIndexes)
     97    {
     98        if (!rowIndexes.length)
     99            return;
     100
     101        for (let i = rowIndexes.length - 1; i >= 0; --i) {
     102            let rowIndex = rowIndexes[i];
     103            let cookie = this._cookies[rowIndex];
     104            console.assert(cookie, "Missing cookie for row " + rowIndex);
     105            if (!cookie)
     106                continue;
     107
     108            this._cookies.splice(rowIndex, 1);
     109
     110            // FIXME: <https://bugs.webkit.org/b/189533> add a WI.Cookie.url property
     111            // once we switch over to using model objects instead of raw payload data.
     112            let cookieURL = (cookie.secure ? "https://" : "http://") + cookie.domain + cookie.path;
     113            PageAgent.deleteCookie(cookie.name, cookieURL);
     114        }
     115    }
     116
     117    tablePopulateCell(table, cell, column, rowIndex)
     118    {
     119        let cookie = this._cookies[rowIndex];
     120
     121        const checkmark = "\u2713";
     122
     123        switch (column.identifier) {
     124        case "name":
     125            cell.textContent = cookie.name;
     126            break;
     127        case "value":
     128            cell.textContent = cookie.value;
     129            break;
     130        case "domain":
     131            cell.textContent = cookie.domain || emDash;
     132            break;
     133        case "path":
     134            cell.textContent = cookie.path || emDash;
     135            break;
     136        case "expires":
     137            cell.textContent = cookie.expires ? new Date(cookie.expires).toLocaleString() : WI.UIString("Session");
     138            break;
     139        case "size":
     140            cell.textContent = Number.bytesToString(cookie.size);
     141            break;
     142        case "secure":
     143            cell.textContent = cookie.secure ? checkmark : zeroWidthSpace;
     144            break;
     145        case "httpOnly":
     146            cell.textContent = cookie.httpOnly ? checkmark : zeroWidthSpace;
     147            break;
     148        case "sameSite":
     149            cell.textContent = cookie.sameSite === WI.Cookie.SameSiteType.None ? emDash : WI.Cookie.displayNameForSameSiteType(cookie.sameSite);
     150            break;
     151        }
     152
     153        return cell;
     154    }
     155
     156    // Protected
     157
     158    initialLayout()
     159    {
     160        super.initialLayout();
     161
     162        this._table = new WI.Table("cookies-table", this, this, 20);
     163        this._table.allowsMultipleSelection = true;
     164
     165        this._nameColumn = new WI.TableColumn("name", WI.UIString("Name"), {
     166            minWidth: 70,
     167            maxWidth: 300,
     168            initialWidth: 200,
     169            resizeType: WI.TableColumn.ResizeType.Locked,
     170        });
     171
     172        this._valueColumn = new WI.TableColumn("value", WI.UIString("Value"), {
     173            minWidth: 100,
     174            maxWidth: 600,
     175            initialWidth: 200,
     176            hideable: false,
     177        });
     178
     179        this._domainColumn = new WI.TableColumn("domain", WI.unlocalizedString("Domain"), {
     180            minWidth: 100,
     181            maxWidth: 200,
     182            initialWidth: 120,
     183        });
     184
     185        this._pathColumn = new WI.TableColumn("path", WI.unlocalizedString("Path"), {
     186            minWidth: 50,
     187            maxWidth: 300,
     188            initialWidth: 100,
     189        });
     190
     191        this._expiresColumn = new WI.TableColumn("expires", WI.unlocalizedString("Expires"), {
     192            minWidth: 100,
     193            maxWidth: 200,
     194            initialWidth: 150,
     195        });
     196
     197        this._sizeColumn = new WI.TableColumn("size", WI.unlocalizedString("Size"), {
     198            minWidth: 50,
     199            maxWidth: 80,
     200            initialWidth: 65,
     201            align: "right",
     202        });
     203
     204        this._secureColumn = new WI.TableColumn("secure", WI.unlocalizedString("Secure"), {
     205            minWidth: 70,
     206            maxWidth: 70,
     207            align: "center",
     208        });
     209
     210        this._httpOnlyColumn = new WI.TableColumn("httpOnly", WI.unlocalizedString("HttpOnly"), {
     211            minWidth: 80,
     212            maxWidth: 80,
     213            align: "center",
     214        });
     215
     216        this._sameSiteColumn = new WI.TableColumn("sameSite", WI.unlocalizedString("SameSite"), {
     217            minWidth: 40,
     218            maxWidth: 80,
     219            initialWidth: 70,
     220            align: "center",
     221        });
     222
     223        this._table.addColumn(this._nameColumn);
     224        this._table.addColumn(this._valueColumn);
     225        this._table.addColumn(this._domainColumn);
     226        this._table.addColumn(this._pathColumn);
     227        this._table.addColumn(this._expiresColumn);
     228        this._table.addColumn(this._sizeColumn);
     229        this._table.addColumn(this._secureColumn);
     230        this._table.addColumn(this._httpOnlyColumn);
     231        this._table.addColumn(this._sameSiteColumn);
     232
     233        if (!this._table.sortColumnIdentifier) {
     234            this._table.sortOrder = WI.Table.SortOrder.Ascending;
     235            this._table.sortColumnIdentifier = "name";
     236         }
     237
     238        this.addSubview(this._table);
     239
     240        this._table.element.addEventListener("keydown", this._handleTableKeyDown.bind(this));
     241
     242        this._reloadCookies();
    68243    }
    69244
    70245    // Private
    71 
    72     _rebuildTable()
    73     {
    74         // FIXME <https://webkit.org/b/151400>: If there are no cookies, add placeholder explanatory text.
    75         if (!this._dataGrid) {
    76             var columns = {name: {}, value: {}, domain: {}, path: {}, expires: {}, size: {}, http: {}, secure: {}, sameSite: {}};
    77 
    78             columns.name.title = WI.UIString("Name");
    79             columns.name.sortable = true;
    80             columns.name.width = "24%";
    81             columns.name.locked = true;
    82 
    83             columns.value.title = WI.UIString("Value");
    84             columns.value.sortable = true;
    85             columns.value.width = "34%";
    86             columns.value.locked = true;
    87 
    88             columns.domain.title = WI.UIString("Domain");
    89             columns.domain.sortable = true;
    90             columns.domain.width = "6%";
    91 
    92             columns.path.title = WI.UIString("Path");
    93             columns.path.sortable = true;
    94             columns.path.width = "6%";
    95 
    96             columns.expires.title = WI.UIString("Expires");
    97             columns.expires.sortable = true;
    98             columns.expires.width = "6%";
    99 
    100             columns.size.title = WI.UIString("Size");
    101             columns.size.aligned = "right";
    102             columns.size.sortable = true;
    103             columns.size.width = "6%";
    104 
    105             columns.http.title = WI.UIString("HTTP");
    106             columns.http.aligned = "centered";
    107             columns.http.sortable = true;
    108             columns.http.width = "6%";
    109 
    110             columns.secure.title = WI.UIString("Secure");
    111             columns.secure.aligned = "centered";
    112             columns.secure.sortable = true;
    113             columns.secure.width = "6%";
    114 
    115             columns.sameSite.title = WI.UIString("Same-Site");
    116             columns.sameSite.sortable = true;
    117             columns.sameSite.width = "6%";
    118 
    119             this._dataGrid = new WI.DataGrid(columns, null, this._deleteCallback.bind(this));
    120             this._dataGrid.columnChooserEnabled = true;
    121             this._dataGrid.addEventListener(WI.DataGrid.Event.SortChanged, this._sortDataGrid, this);
    122             this._dataGrid.sortColumnIdentifier = "name";
    123             this._dataGrid.createSettings("cookie-storage-content-view");
    124 
    125             this.addSubview(this._dataGrid);
    126             this._dataGrid.updateLayout();
    127         }
    128 
    129         console.assert(this._dataGrid);
    130         this._dataGrid.removeChildren();
    131 
    132         for (let cookie of this._cookies) {
    133             const checkmark = "\u2713";
    134             var data = {
    135                 name: cookie.name,
    136                 value: cookie.value,
    137                 domain: cookie.domain || "",
    138                 path: cookie.path || "",
    139                 expires: "",
    140                 size: Number.bytesToString(cookie.size),
    141                 http: cookie.httpOnly ? checkmark : "",
    142                 secure: cookie.secure ? checkmark : "",
    143                 sameSite: cookie.sameSite && cookie.sameSite !== WI.Cookie.SameSiteType.None ? WI.Cookie.displayNameForSameSiteType(cookie.sameSite) : "",
    144             };
    145 
    146             if (cookie.type !== WI.CookieType.Request)
    147                 data["expires"] = cookie.session ? WI.UIString("Session") : new Date(cookie.expires).toLocaleString();
    148 
    149             var node = new WI.DataGridNode(data);
    150             node.cookie = cookie;
    151 
    152             this._dataGrid.appendChild(node);
    153         }
    154     }
    155246
    156247    _filterCookies(cookies)
     
    177268    }
    178269
    179     _sortDataGrid()
    180     {
    181         function localeCompare(field, nodeA, nodeB)
    182         {
    183             return (nodeA.data[field] + "").extendedLocaleCompare(nodeB.data[field] + "");
    184         }
    185 
    186         function numberCompare(field, nodeA, nodeB)
    187         {
    188             return nodeA.cookie[field] - nodeB.cookie[field];
    189         }
    190 
    191         function expiresCompare(nodeA, nodeB)
    192         {
    193             if (nodeA.cookie.session !== nodeB.cookie.session)
    194                 return nodeA.cookie.session ? -1 : 1;
    195 
    196             if (nodeA.cookie.session)
    197                 return 0;
    198 
    199             return nodeA.cookie.expires - nodeB.cookie.expires;
    200         }
    201 
    202         var comparator;
    203         switch (this._dataGrid.sortColumnIdentifier) {
    204             case "value": comparator = localeCompare.bind(this, "value"); break;
    205             case "domain": comparator = localeCompare.bind(this, "domain"); break;
    206             case "path": comparator = localeCompare.bind(this, "path"); break;
    207             case "expires": comparator = expiresCompare; break;
    208             case "size": comparator = numberCompare.bind(this, "size"); break;
    209             case "http": comparator = localeCompare.bind(this, "http"); break;
    210             case "secure": comparator = localeCompare.bind(this, "secure"); break;
    211             case "sameSite": comparator = localeCompare.bind(this, "sameSite"); break;
    212             case "name":
    213             default: comparator = localeCompare.bind(this, "name"); break;
    214         }
    215 
    216         console.assert(comparator);
    217         this._dataGrid.sortNodes(comparator);
    218     }
    219 
    220     _deleteCallback(node)
    221     {
    222         if (!node || !node.cookie)
    223             return;
    224 
    225         var cookie = node.cookie;
    226         var cookieURL = (cookie.secure ? "https://" : "http://") + cookie.domain + cookie.path;
    227         PageAgent.deleteCookie(cookie.name, cookieURL);
    228 
    229         this.update();
     270    _generateSortComparator()
     271    {
     272        let sortColumnIdentifier = this._table.sortColumnIdentifier;
     273        if (!sortColumnIdentifier) {
     274            this._sortComparator = null;
     275            return;
     276        }
     277
     278        let comparator = null;
     279
     280        switch (sortColumnIdentifier) {
     281        case "name":
     282        case "value":
     283        case "domain":
     284        case "path":
     285        case "sameSite":
     286            comparator = (a, b) => (a[sortColumnIdentifier] || "").extendedLocaleCompare(b[sortColumnIdentifier] || "");
     287            break;
     288
     289        case "size":
     290        case "httpOnly":
     291        case "secure":
     292            comparator = (a, b) => a[sortColumnIdentifier] - b[sortColumnIdentifier];
     293            break;
     294
     295        case "expires":
     296            comparator = (a, b) => {
     297                if (!a.expires)
     298                    return 1;
     299                if (!b.expires)
     300                    return -1;
     301                return a.expires - b.expires;
     302            };
     303            break;
     304
     305        default:
     306            console.assert("Unexpected sort column", sortColumnIdentifier);
     307            return;
     308        }
     309
     310        let reverseFactor = this._table.sortOrder === WI.Table.SortOrder.Ascending ? 1 : -1;
     311        this._sortComparator = (a, b) => reverseFactor * comparator(a, b);
    230312    }
    231313
    232314    _refreshButtonClicked(event)
    233315    {
    234         this.update();
     316        this._reloadCookies();
     317    }
     318
     319    _reloadCookies()
     320    {
     321        PageAgent.getCookies().then((payload) => {
     322            this._cookies = this._filterCookies(payload.cookies);
     323            this._updateSort();
     324            this._table.reloadData();
     325        }).catch((error) => {
     326            console.error("Could not fetch cookies: ", error);
     327        });
     328    }
     329
     330    _updateSort()
     331    {
     332        if (!this._sortComparator)
     333            return;
     334
     335        this._cookies.sort(this._sortComparator);
     336    }
     337
     338    _handleTableKeyDown(event)
     339    {
     340        if (event.keyCode === WI.KeyboardShortcut.Key.Backspace.keyCode || event.keyCode === WI.KeyboardShortcut.Key.Delete.keyCode)
     341            this._table.removeSelectedRows();
    235342    }
    236343};
  • trunk/Source/WebInspectorUI/UserInterface/Views/Table.js

    r237495 r237746  
    236236    }
    237237
     238    isRowSelected(rowIndex)
     239    {
     240        return this._selectedRows.has(rowIndex);
     241    }
     242
    238243    resize()
    239244    {
     
    322327        console.assert(rowIndex >= 0 && rowIndex < this.numberOfRows);
    323328
    324         if (this._isRowSelected(rowIndex)) {
     329        if (this.isRowSelected(rowIndex)) {
    325330            if (!extendSelection)
    326331                this._deselectAllAndSelect(rowIndex);
     
    347352        console.assert(rowIndex >= 0 && rowIndex < this.numberOfRows);
    348353
    349         if (!this._isRowSelected(rowIndex))
     354        if (!this.isRowSelected(rowIndex))
    350355            return;
    351356
     
    391396        console.assert(rowIndex >= 0 && rowIndex < this.numberOfRows);
    392397
    393         if (this._isRowSelected(rowIndex))
     398        if (this.isRowSelected(rowIndex))
    394399            this.deselectRow(rowIndex);
    395400
     
    798803        row.__index = rowIndex;
    799804        row.__widthGeneration = 0;
    800         if (this._isRowSelected(rowIndex))
     805        if (this.isRowSelected(rowIndex))
    801806            row.classList.add("selected");
    802807
     
    13221327        let rowIndex = row.__index;
    13231328
    1324         if (this._isRowSelected(rowIndex)) {
     1329        if (this.isRowSelected(rowIndex)) {
    13251330            if (event.metaKey)
    13261331                this.deselectRow(rowIndex)
     
    14531458            }
    14541459
    1455             if (this._isRowSelected(index)) {
     1460            if (this.isRowSelected(index)) {
    14561461                this._selectedRows.delete(index);
    14571462                this._selectedRows.add(newIndex);
     
    14941499    }
    14951500
    1496     _isRowSelected(rowIndex)
    1497     {
    1498         return this._selectedRows.has(rowIndex);
    1499     }
    1500 
    15011501    _notifySelectionDidChange()
    15021502    {
Note: See TracChangeset for help on using the changeset viewer.