Allow deletion of Trust Tokens

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 <szuend@chromium.org>
Reviewed-by: Sigurd Schneider <sigurds@chromium.org>
Reviewed-by: Jack Franklin <jacktfranklin@chromium.org>
This commit is contained in:
Simon Zünd
2021-03-12 10:24:18 +00:00
committed by Commit Bot
parent 189ac95f38
commit cf83ad89eb
9 changed files with 120 additions and 12 deletions
+1
View File
@@ -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",
+1
View File
@@ -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",
+1
View File
@@ -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"
}
+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2.91 4.233" height="16" width="11"><path d="M.265 1.323h2.38L2.25 3.969H.661M2.381.53h-.529V.264h-.794v.264H.53a.27.27 0 00-.264.265v.264H2.646V.794A.27.27 0 002.38.529"/></svg>

After

Width:  |  Height:  |  Size: 230 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2.91 4.233" height="16" width="11"><path d="M.265 1.323h2.38L2.25 3.969H.661M2.381.53h-.529V.264h-.794v.264H.53a.27.27 0 0 0-.264.265v.264h2.38V.794A.27.27 0 0 0 2.38.529"/></svg>

After

Width:  |  Height:  |  Size: 232 B

+3
View File
@@ -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"
},
+3
View File
@@ -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": "Îśŝúêŕ"
},
+73 -3
View File
@@ -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`
<style>
.delete-button {
width: 16px;
height: 16px;
background: transparent;
overflow: hidden;
border: none;
padding: 0;
outline: none;
cursor: pointer;
}
.delete-button:hover devtools-icon {
--icon-color: var(--color-text-primary);
}
.delete-button:focus devtools-icon {
--icon-color: var(--color-text-secondary);
}
.button-container {
display: block;
text-align: center;
}
</style>
<!-- Wrap the button in a container, otherwise we can't center it inside the column. -->
<span class="button-container">
<button class="delete-button"
title=${i18nString(UIStrings.deleteTrustTokens, {PH1: issuer as string})}
@click=${(): void => this.deleteClickHandler(issuer as string)}>
<devtools-icon .data=${
{iconName: 'trash_bin_icon', color: 'var(--color-text-secondary)', width: '9px', height: '14px'} as
Components.Icon.IconWithName}>
</devtools-icon>
</button>
</span>`;
// clang-format on
}
}
function removeTrailingSlash(s: string): string {
@@ -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<Resources.TrustTokensView.TrustTokensView> {
async function renderTrustTokensView(
tokens: Protocol.Storage.TrustTokens[],
deleteClickHandler: (issuer: string) => void = () => {}): Promise<Resources.TrustTokensView.TrustTokensView> {
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<string> = 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');
});
});