From cf83ad89eb6c8fa903d29919a711784aeb7557e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20Z=C3=BCnd?= Date: Fri, 12 Mar 2021 10:58:00 +0100 Subject: [PATCH] Allow deletion of Trust Tokens MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This CL adds a new column to the Trust Tokens table in the Application panel. The column contains a delete button for each row, that can be used to delete all Trust Tokens issued by the corresponding issuer. Screenshot: https://imgur.com/a/gkfXcQ7 R=jacktfranklin@chromium.org, sigurds@chromium.org Bug: chromium:1126824 Change-Id: I816221ed130a9c00f6ea2ee69f5eec5734464d7d Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/2748085 Commit-Queue: Simon Zünd Reviewed-by: Sigurd Schneider Reviewed-by: Jack Franklin --- devtools_grd_files.gni | 1 + devtools_image_files.gni | 1 + front_end/Images/src/optimize_svg.hashes | 1 + front_end/Images/src/trash_bin_icon.svg | 1 + front_end/Images/trash_bin_icon.svg | 1 + front_end/i18n/locales/en-US.json | 3 + front_end/i18n/locales/en-XL.json | 3 + front_end/resources/TrustTokensView.ts | 76 ++++++++++++++++++- .../resources/TrustTokensView_test.ts | 45 ++++++++--- 9 files changed, 120 insertions(+), 12 deletions(-) create mode 100644 front_end/Images/src/trash_bin_icon.svg create mode 100644 front_end/Images/trash_bin_icon.svg diff --git a/devtools_grd_files.gni b/devtools_grd_files.gni index 2763039f1d..5577d41557 100644 --- a/devtools_grd_files.gni +++ b/devtools_grd_files.gni @@ -111,6 +111,7 @@ grd_files_release_sources = [ "front_end/Images/toolbarResizerVertical.png", "front_end/Images/touchCursor.png", "front_end/Images/touchCursor_2x.png", + "front_end/Images/trash_bin_icon.svg", "front_end/Images/treeoutlineTriangles.svg", "front_end/Images/warning_icon.svg", "front_end/Images/whatsnew.avif", diff --git a/devtools_image_files.gni b/devtools_image_files.gni index 0ff97b812c..7a30443881 100644 --- a/devtools_image_files.gni +++ b/devtools_image_files.gni @@ -102,6 +102,7 @@ devtools_image_files = [ "toolbarResizerVertical.png", "touchCursor_2x.png", "touchCursor.png", + "trash_bin_icon.svg", "treeoutlineTriangles.svg", "warning_icon.svg", "whatsnew.avif", diff --git a/front_end/Images/src/optimize_svg.hashes b/front_end/Images/src/optimize_svg.hashes index b226e170ac..e32a40a2ab 100644 --- a/front_end/Images/src/optimize_svg.hashes +++ b/front_end/Images/src/optimize_svg.hashes @@ -56,6 +56,7 @@ "smallIcons.svg": "10719c45b1004a4c4dd236e590deded1", "sources_panel_icon.svg": "48769389e8622535a65ccfcb755e74a1", "switcherIcon.svg": "796f5ed707e44fc587858a0c76c448eb", + "trash_bin_icon.svg": "de378e55632db9bbc3da1a3b7dc32acb", "treeoutlineTriangles.svg": "aa6c8f8df9ae7c50f14397131808e764", "warning_icon.svg": "4d6d7934fb97ecb318fb8b9122b1a750" } \ No newline at end of file diff --git a/front_end/Images/src/trash_bin_icon.svg b/front_end/Images/src/trash_bin_icon.svg new file mode 100644 index 0000000000..91e16eab5f --- /dev/null +++ b/front_end/Images/src/trash_bin_icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/front_end/Images/trash_bin_icon.svg b/front_end/Images/trash_bin_icon.svg new file mode 100644 index 0000000000..b1f96abbe0 --- /dev/null +++ b/front_end/Images/trash_bin_icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/front_end/i18n/locales/en-US.json b/front_end/i18n/locales/en-US.json index 585c7862bd..4d91bb6f5d 100644 --- a/front_end/i18n/locales/en-US.json +++ b/front_end/i18n/locales/en-US.json @@ -7469,6 +7469,9 @@ "resources/TrustTokensView.ts | allStoredTrustTokensAvailableIn": { "message": "All stored Trust Tokens available in this browser instance." }, + "resources/TrustTokensView.ts | deleteTrustTokens": { + "message": "Delete all stored Trust Tokens issued by {PH1}." + }, "resources/TrustTokensView.ts | issuer": { "message": "Issuer" }, diff --git a/front_end/i18n/locales/en-XL.json b/front_end/i18n/locales/en-XL.json index ff2bcedc12..1283204ffe 100644 --- a/front_end/i18n/locales/en-XL.json +++ b/front_end/i18n/locales/en-XL.json @@ -7469,6 +7469,9 @@ "resources/TrustTokensView.ts | allStoredTrustTokensAvailableIn": { "message": "Âĺl̂ śt̂ór̂éd̂ T́r̂úŝt́ T̂ók̂én̂ś âv́âíl̂áb̂ĺê ín̂ t́ĥíŝ b́r̂óŵśêŕ îńŝt́âńĉé." }, + "resources/TrustTokensView.ts | deleteTrustTokens": { + "message": "D̂él̂ét̂é âĺl̂ śt̂ór̂éd̂ T́r̂úŝt́ T̂ók̂én̂ś îśŝúêd́ b̂ý {PH1}." + }, "resources/TrustTokensView.ts | issuer": { "message": "Îśŝúêŕ" }, diff --git a/front_end/resources/TrustTokensView.ts b/front_end/resources/TrustTokensView.ts index aa7a674aea..dba9fb50d5 100644 --- a/front_end/resources/TrustTokensView.ts +++ b/front_end/resources/TrustTokensView.ts @@ -33,6 +33,13 @@ const UIStrings = { * @description Text shown instead of a table when the table would be empty. */ noTrustTokensStored: 'No Trust Tokens are currently stored.', + /** + * @description Each row in the Trust Token table has a delete button. This is the text shown + * when hovering over this button. The placeholder is a normal URL, indicating the site which + * provided the Trust Tokens that will be deleted when the button is clicked. + * @example {https://google.com} PH1 + */ + deleteTrustTokens: 'Delete all stored Trust Tokens issued by {PH1}.', }; const str_ = i18n.i18n.registerUIStrings('resources/TrustTokensView.ts', UIStrings); const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_); @@ -78,7 +85,12 @@ class TrustTokensViewWidgetWrapper extends UI.ThrottledWidget.ThrottledWidget { return; } const {tokens} = await mainTarget.storageAgent().invoke_getTrustTokens(); - this.trustTokensView.data = {tokens}; + this.trustTokensView.data = { + tokens, + deleteClickHandler: (issuer: string): void => { + mainTarget.storageAgent().invoke_clearTrustTokens({issuerOrigin: issuer}); + }, + }; this.update(); } @@ -86,11 +98,13 @@ class TrustTokensViewWidgetWrapper extends UI.ThrottledWidget.ThrottledWidget { export interface TrustTokensViewData { tokens: Protocol.Storage.TrustTokens[]; + deleteClickHandler: (issuerOrigin: string) => void; } export class TrustTokensView extends HTMLElement { private readonly shadow = this.attachShadow({mode: 'open'}); private tokens: Protocol.Storage.TrustTokens[] = []; + private deleteClickHandler: (issuerOrigin: string) => void = () => {}; connectedCallback(): void { this.render(); @@ -98,6 +112,7 @@ export class TrustTokensView extends HTMLElement { set data(data: TrustTokensViewData) { this.tokens = data.tokens; + this.deleteClickHandler = data.deleteClickHandler; this.render(); } @@ -150,7 +165,7 @@ export class TrustTokensView extends HTMLElement { { id: 'issuer', title: i18nString(UIStrings.issuer), - widthWeighting: 2, + widthWeighting: 10, hideable: false, visible: true, sortable: true, @@ -158,11 +173,19 @@ export class TrustTokensView extends HTMLElement { { id: 'count', title: i18nString(UIStrings.storedTokenCount), - widthWeighting: 1, + widthWeighting: 5, hideable: false, visible: true, sortable: true, }, + { + id: 'delete-button', + title: '', + widthWeighting: 1, + hideable: false, + visible: true, + sortable: false, + }, ], rows: this.buildRowsFromTokens(), initialSort: { @@ -181,11 +204,58 @@ export class TrustTokensView extends HTMLElement { const tokens = this.tokens.filter(token => token.count > 0); return tokens.map(token => ({ cells: [ + { + columnId: 'delete-button', + value: removeTrailingSlash(token.issuerOrigin), + renderer: this.deleteButtonRenderer.bind(this), + }, {columnId: 'issuer', value: removeTrailingSlash(token.issuerOrigin)}, {columnId: 'count', value: token.count}, ], })); } + + private deleteButtonRenderer(issuer: Components.DataGridUtils.CellValue): LitHtml.TemplateResult { + // clang-format off + return LitHtml.html` + + + + + `; + // clang-format on + } } function removeTrailingSlash(s: string): string { diff --git a/test/unittests/front_end/resources/TrustTokensView_test.ts b/test/unittests/front_end/resources/TrustTokensView_test.ts index ab21e2d52c..e02beaafac 100644 --- a/test/unittests/front_end/resources/TrustTokensView_test.ts +++ b/test/unittests/front_end/resources/TrustTokensView_test.ts @@ -6,17 +6,18 @@ import * as Coordinator from '../../../../front_end/render_coordinator/render_co import * as Resources from '../../../../front_end/resources/resources.js'; import * as Components from '../../../../front_end/ui/components/components.js'; import {assertElement, assertShadowRoot, getElementWithinComponent, renderElementIntoDOM} from '../helpers/DOMHelpers.js'; -import {getValuesOfAllBodyRows} from '../ui/components/DataGridHelpers.js'; +import {getCellByIndexes, getValuesOfAllBodyRows} from '../ui/components/DataGridHelpers.js'; const coordinator = Coordinator.RenderCoordinator.RenderCoordinator.instance(); const {assert} = chai; -async function renderTrustTokensView(tokens: Protocol.Storage.TrustTokens[]): - Promise { +async function renderTrustTokensView( + tokens: Protocol.Storage.TrustTokens[], + deleteClickHandler: (issuer: string) => void = () => {}): Promise { const component = new Resources.TrustTokensView.TrustTokensView(); renderElementIntoDOM(component); - component.data = {tokens}; + component.data = {tokens, deleteClickHandler}; // The data-grid's renderer is scheduled, so we need to wait until the coordinator // is done before we can test against it. @@ -43,8 +44,8 @@ describe('TrustTokensView', () => { const dataGridShadowRoot = getInternalDataGridShadowRoot(component); const rowValues = getValuesOfAllBodyRows(dataGridShadowRoot); assert.deepEqual(rowValues, [ - ['bar.org', '7'], - ['foo.com', '42'], + ['bar.org', '7', ''], + ['foo.com', '42', ''], ]); }); @@ -56,7 +57,7 @@ describe('TrustTokensView', () => { const dataGridShadowRoot = getInternalDataGridShadowRoot(component); const rowValues = getValuesOfAllBodyRows(dataGridShadowRoot); - assert.deepEqual(rowValues, [['foo.com', '42']]); + assert.deepEqual(rowValues, [['foo.com', '42', '']]); }); it('removes trailing slashes from issuer origins', async () => { @@ -68,8 +69,8 @@ describe('TrustTokensView', () => { const dataGridShadowRoot = getInternalDataGridShadowRoot(component); const rowValues = getValuesOfAllBodyRows(dataGridShadowRoot); assert.deepEqual(rowValues, [ - ['example.com', '20'], - ['sub.domain.org', '14'], + ['example.com', '20', ''], + ['sub.domain.org', '14', ''], ]); }); @@ -83,4 +84,30 @@ describe('TrustTokensView', () => { const noTrustTokensElement = component.shadowRoot.querySelector('div.no-tt-message'); assertElement(noTrustTokensElement, HTMLDivElement); }); + + it('calls the delete handler with the right issuer when the delete button is clicked in a row', async () => { + // Create a Promise that resolves with the issuer for which the delete button was clicked. + let resolveDeleteButtonPromise: (issuer: string) => void; + const deleteButtonClicked: Promise = new Promise(resolve => { + resolveDeleteButtonPromise = resolve; + }); + + const component = await renderTrustTokensView( + [ + {issuerOrigin: 'bar.org', count: 42}, + {issuerOrigin: 'foo.com', count: 7}, + ], + (issuer: string) => { + resolveDeleteButtonPromise(issuer); + }); + + const dataGridShadowRoot = getInternalDataGridShadowRoot(component); + const deleteCell = getCellByIndexes(dataGridShadowRoot, {column: 2, row: 1}); + const button = deleteCell.querySelector('button'); + assertElement(button, HTMLButtonElement); + button.click(); + + const actualIssuer = await deleteButtonClicked; + assert.strictEqual(actualIssuer, 'bar.org'); + }); });