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');
+ });
});