Simon Zünd | 30152ed | 2021-01-21 09:18:28 | [diff] [blame] | 1 | // Copyright 2020 The Chromium Authors. All rights reserved. |
| 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
Tim van der Lippe | bb352e6 | 2021-04-01 17:57:28 | [diff] [blame] | 5 | import * as i18n from '../../core/i18n/i18n.js'; |
Tim van der Lippe | e00b92f | 2021-03-31 16:52:17 | [diff] [blame] | 6 | import * as SDK from '../../core/sdk/sdk.js'; |
Tim van der Lippe | c59708f | 2021-03-31 15:07:19 | [diff] [blame] | 7 | import * as LitHtml from '../../third_party/lit-html/lit-html.js'; |
| 8 | import * as Components from '../../ui/components/components.js'; |
Tim van der Lippe | aa61faf | 2021-04-07 15:32:07 | [diff] [blame^] | 9 | import * as UI from '../../ui/legacy/legacy.js'; |
Simon Zünd | 30152ed | 2021-01-21 09:18:28 | [diff] [blame] | 10 | |
| 11 | import type {ResourcesPanel} from './ResourcesPanel.js'; |
| 12 | |
| 13 | import {ApplicationPanelTreeElement} from './ApplicationPanelTreeElement.js'; |
| 14 | |
Simon Zünd | 697fb0b | 2021-03-01 10:12:42 | [diff] [blame] | 15 | const UIStrings = { |
Vidal Guillermo Diazleal Ortega | 0a997bb | 2021-02-09 04:33:01 | [diff] [blame] | 16 | /** |
| 17 | *@description Hover text for an info icon in the Trust Token panel |
| 18 | */ |
| 19 | trustTokens: 'Trust Tokens', |
| 20 | /** |
| 21 | *@description Text for the issuer of an item |
| 22 | */ |
| 23 | issuer: 'Issuer', |
| 24 | /** |
| 25 | *@description Column header for Trust Token table |
| 26 | */ |
| 27 | storedTokenCount: 'Stored token count', |
| 28 | /** |
| 29 | *@description Hover text for an info icon in the Trust Token panel |
| 30 | */ |
| 31 | allStoredTrustTokensAvailableIn: 'All stored Trust Tokens available in this browser instance.', |
Simon Zünd | d4ba153 | 2021-02-10 08:35:49 | [diff] [blame] | 32 | /** |
| 33 | * @description Text shown instead of a table when the table would be empty. |
| 34 | */ |
| 35 | noTrustTokensStored: 'No Trust Tokens are currently stored.', |
Simon Zünd | cf83ad8 | 2021-03-12 09:58:00 | [diff] [blame] | 36 | /** |
| 37 | * @description Each row in the Trust Token table has a delete button. This is the text shown |
| 38 | * when hovering over this button. The placeholder is a normal URL, indicating the site which |
| 39 | * provided the Trust Tokens that will be deleted when the button is clicked. |
| 40 | * @example {https://google.com} PH1 |
| 41 | */ |
| 42 | deleteTrustTokens: 'Delete all stored Trust Tokens issued by {PH1}.', |
Vidal Guillermo Diazleal Ortega | 0a997bb | 2021-02-09 04:33:01 | [diff] [blame] | 43 | }; |
Tim van der Lippe | c59708f | 2021-03-31 15:07:19 | [diff] [blame] | 44 | const str_ = i18n.i18n.registerUIStrings('panels/application/TrustTokensView.ts', UIStrings); |
Vidal Guillermo Diazleal Ortega | 0a997bb | 2021-02-09 04:33:01 | [diff] [blame] | 45 | const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_); |
Simon Zünd | 0a6a1a7 | 2021-02-10 06:42:06 | [diff] [blame] | 46 | |
| 47 | /** Fetch the Trust Token data regularly from the backend while the panel is open */ |
| 48 | const REFRESH_INTERVAL_MS = 1000; |
| 49 | |
Simon Zünd | 30152ed | 2021-01-21 09:18:28 | [diff] [blame] | 50 | export class TrustTokensTreeElement extends ApplicationPanelTreeElement { |
| 51 | private view?: TrustTokensViewWidgetWrapper; |
| 52 | |
| 53 | constructor(storagePanel: ResourcesPanel) { |
Vidal Guillermo Diazleal Ortega | 0a997bb | 2021-02-09 04:33:01 | [diff] [blame] | 54 | super(storagePanel, i18nString(UIStrings.trustTokens), false); |
Simon Zünd | 30152ed | 2021-01-21 09:18:28 | [diff] [blame] | 55 | const icon = UI.Icon.Icon.create('mediumicon-database', 'resource-tree-item'); |
| 56 | this.setLeadingIcons([icon]); |
| 57 | } |
| 58 | |
| 59 | get itemURL(): string { |
| 60 | return 'trustTokens://'; |
| 61 | } |
| 62 | |
| 63 | onselect(selectedByUser?: boolean): boolean { |
| 64 | super.onselect(selectedByUser); |
| 65 | if (!this.view) { |
| 66 | this.view = new TrustTokensViewWidgetWrapper(); |
| 67 | } |
| 68 | this.showView(this.view); |
| 69 | return false; |
| 70 | } |
| 71 | } |
| 72 | |
Simon Zünd | 0a6a1a7 | 2021-02-10 06:42:06 | [diff] [blame] | 73 | class TrustTokensViewWidgetWrapper extends UI.ThrottledWidget.ThrottledWidget { |
Simon Zünd | 30152ed | 2021-01-21 09:18:28 | [diff] [blame] | 74 | private readonly trustTokensView = new TrustTokensView(); |
| 75 | |
| 76 | constructor() { |
Simon Zünd | 0a6a1a7 | 2021-02-10 06:42:06 | [diff] [blame] | 77 | super(/* isWebComponent */ false, REFRESH_INTERVAL_MS); |
Simon Zünd | 30152ed | 2021-01-21 09:18:28 | [diff] [blame] | 78 | this.contentElement.appendChild(this.trustTokensView); |
Simon Zünd | 0a6a1a7 | 2021-02-10 06:42:06 | [diff] [blame] | 79 | this.update(); |
Simon Zünd | 30152ed | 2021-01-21 09:18:28 | [diff] [blame] | 80 | } |
| 81 | |
Simon Zünd | 0a6a1a7 | 2021-02-10 06:42:06 | [diff] [blame] | 82 | protected async doUpdate(): Promise<void> { |
Simon Zünd | 30152ed | 2021-01-21 09:18:28 | [diff] [blame] | 83 | const mainTarget = SDK.SDKModel.TargetManager.instance().mainTarget(); |
| 84 | if (!mainTarget) { |
| 85 | return; |
| 86 | } |
Simon Zünd | 30152ed | 2021-01-21 09:18:28 | [diff] [blame] | 87 | const {tokens} = await mainTarget.storageAgent().invoke_getTrustTokens(); |
Simon Zünd | cf83ad8 | 2021-03-12 09:58:00 | [diff] [blame] | 88 | this.trustTokensView.data = { |
| 89 | tokens, |
| 90 | deleteClickHandler: (issuer: string): void => { |
| 91 | mainTarget.storageAgent().invoke_clearTrustTokens({issuerOrigin: issuer}); |
| 92 | }, |
| 93 | }; |
Simon Zünd | 0a6a1a7 | 2021-02-10 06:42:06 | [diff] [blame] | 94 | |
| 95 | this.update(); |
Simon Zünd | 30152ed | 2021-01-21 09:18:28 | [diff] [blame] | 96 | } |
| 97 | } |
| 98 | |
| 99 | export interface TrustTokensViewData { |
| 100 | tokens: Protocol.Storage.TrustTokens[]; |
Simon Zünd | cf83ad8 | 2021-03-12 09:58:00 | [diff] [blame] | 101 | deleteClickHandler: (issuerOrigin: string) => void; |
Simon Zünd | 30152ed | 2021-01-21 09:18:28 | [diff] [blame] | 102 | } |
| 103 | |
| 104 | export class TrustTokensView extends HTMLElement { |
| 105 | private readonly shadow = this.attachShadow({mode: 'open'}); |
| 106 | private tokens: Protocol.Storage.TrustTokens[] = []; |
Simon Zünd | cf83ad8 | 2021-03-12 09:58:00 | [diff] [blame] | 107 | private deleteClickHandler: (issuerOrigin: string) => void = () => {}; |
Simon Zünd | 30152ed | 2021-01-21 09:18:28 | [diff] [blame] | 108 | |
| 109 | connectedCallback(): void { |
| 110 | this.render(); |
| 111 | } |
| 112 | |
| 113 | set data(data: TrustTokensViewData) { |
| 114 | this.tokens = data.tokens; |
Simon Zünd | cf83ad8 | 2021-03-12 09:58:00 | [diff] [blame] | 115 | this.deleteClickHandler = data.deleteClickHandler; |
Simon Zünd | 30152ed | 2021-01-21 09:18:28 | [diff] [blame] | 116 | this.render(); |
| 117 | } |
| 118 | |
| 119 | private render(): void { |
Simon Zünd | d4ba153 | 2021-02-10 08:35:49 | [diff] [blame] | 120 | LitHtml.render( |
| 121 | LitHtml.html` |
| 122 | <style> |
| 123 | :host { |
| 124 | padding: 20px; |
| 125 | } |
| 126 | |
| 127 | .heading { |
| 128 | font-size: 15px; |
| 129 | } |
| 130 | |
| 131 | devtools-data-grid-controller { |
| 132 | border: 1px solid var(--color-details-hairline); |
| 133 | margin-top: 20px; |
| 134 | } |
| 135 | |
| 136 | .info-icon { |
| 137 | vertical-align: text-bottom; |
| 138 | height: 14px; |
| 139 | } |
| 140 | |
| 141 | .no-tt-message { |
| 142 | margin-top: 20px; |
| 143 | } |
| 144 | </style> |
| 145 | <div> |
| 146 | <span class="heading">Trust Tokens</span> |
| 147 | <devtools-icon class="info-icon" title=${i18nString(UIStrings.allStoredTrustTokensAvailableIn)} |
| 148 | .data=${ |
| 149 | {iconName: 'ic_info_black_18dp', color: 'var(--color-link)', width: '14px'} as |
| 150 | Components.Icon.IconWithName}> |
| 151 | </devtools-icon> |
| 152 | ${this.renderGridOrNoDataMessage()} |
| 153 | </div> |
| 154 | `, |
| 155 | this.shadow); |
| 156 | } |
| 157 | |
| 158 | private renderGridOrNoDataMessage(): LitHtml.TemplateResult { |
| 159 | if (this.tokens.length === 0) { |
| 160 | return LitHtml.html`<div class="no-tt-message">${i18nString(UIStrings.noTrustTokensStored)}</div>`; |
| 161 | } |
| 162 | |
Simon Zünd | 427fbd7 | 2021-02-03 07:15:31 | [diff] [blame] | 163 | const gridData: Components.DataGridController.DataGridControllerData = { |
Simon Zünd | 30152ed | 2021-01-21 09:18:28 | [diff] [blame] | 164 | columns: [ |
Vidal Guillermo Diazleal Ortega | 0a997bb | 2021-02-09 04:33:01 | [diff] [blame] | 165 | { |
| 166 | id: 'issuer', |
| 167 | title: i18nString(UIStrings.issuer), |
Simon Zünd | cf83ad8 | 2021-03-12 09:58:00 | [diff] [blame] | 168 | widthWeighting: 10, |
Vidal Guillermo Diazleal Ortega | 0a997bb | 2021-02-09 04:33:01 | [diff] [blame] | 169 | hideable: false, |
| 170 | visible: true, |
| 171 | sortable: true, |
| 172 | }, |
| 173 | { |
| 174 | id: 'count', |
| 175 | title: i18nString(UIStrings.storedTokenCount), |
Simon Zünd | cf83ad8 | 2021-03-12 09:58:00 | [diff] [blame] | 176 | widthWeighting: 5, |
Vidal Guillermo Diazleal Ortega | 0a997bb | 2021-02-09 04:33:01 | [diff] [blame] | 177 | hideable: false, |
| 178 | visible: true, |
| 179 | sortable: true, |
| 180 | }, |
Simon Zünd | cf83ad8 | 2021-03-12 09:58:00 | [diff] [blame] | 181 | { |
| 182 | id: 'delete-button', |
| 183 | title: '', |
| 184 | widthWeighting: 1, |
| 185 | hideable: false, |
| 186 | visible: true, |
| 187 | sortable: false, |
| 188 | }, |
Simon Zünd | 30152ed | 2021-01-21 09:18:28 | [diff] [blame] | 189 | ], |
| 190 | rows: this.buildRowsFromTokens(), |
Simon Zünd | 427fbd7 | 2021-02-03 07:15:31 | [diff] [blame] | 191 | initialSort: { |
| 192 | columnId: 'issuer', |
| 193 | direction: Components.DataGridUtils.SortDirection.ASC, |
| 194 | }, |
Simon Zünd | 30152ed | 2021-01-21 09:18:28 | [diff] [blame] | 195 | }; |
| 196 | |
Simon Zünd | d4ba153 | 2021-02-10 08:35:49 | [diff] [blame] | 197 | return LitHtml.html` |
| 198 | <devtools-data-grid-controller .data=${ |
| 199 | gridData as Components.DataGridController.DataGridControllerData}></devtools-data-grid-controller> |
| 200 | `; |
Simon Zünd | 30152ed | 2021-01-21 09:18:28 | [diff] [blame] | 201 | } |
| 202 | |
| 203 | private buildRowsFromTokens(): Components.DataGridUtils.Row[] { |
| 204 | const tokens = this.tokens.filter(token => token.count > 0); |
| 205 | return tokens.map(token => ({ |
| 206 | cells: [ |
Simon Zünd | cf83ad8 | 2021-03-12 09:58:00 | [diff] [blame] | 207 | { |
| 208 | columnId: 'delete-button', |
| 209 | value: removeTrailingSlash(token.issuerOrigin), |
| 210 | renderer: this.deleteButtonRenderer.bind(this), |
| 211 | }, |
Simon Zünd | 400dcba | 2021-02-03 09:19:50 | [diff] [blame] | 212 | {columnId: 'issuer', value: removeTrailingSlash(token.issuerOrigin)}, |
Simon Zünd | 30152ed | 2021-01-21 09:18:28 | [diff] [blame] | 213 | {columnId: 'count', value: token.count}, |
| 214 | ], |
| 215 | })); |
| 216 | } |
Simon Zünd | cf83ad8 | 2021-03-12 09:58:00 | [diff] [blame] | 217 | |
| 218 | private deleteButtonRenderer(issuer: Components.DataGridUtils.CellValue): LitHtml.TemplateResult { |
| 219 | // clang-format off |
| 220 | return LitHtml.html` |
| 221 | <style> |
| 222 | .delete-button { |
| 223 | width: 16px; |
| 224 | height: 16px; |
| 225 | background: transparent; |
| 226 | overflow: hidden; |
| 227 | border: none; |
| 228 | padding: 0; |
| 229 | outline: none; |
| 230 | cursor: pointer; |
| 231 | } |
| 232 | |
| 233 | .delete-button:hover devtools-icon { |
| 234 | --icon-color: var(--color-text-primary); |
| 235 | } |
| 236 | |
| 237 | .delete-button:focus devtools-icon { |
| 238 | --icon-color: var(--color-text-secondary); |
| 239 | } |
| 240 | |
| 241 | .button-container { |
| 242 | display: block; |
| 243 | text-align: center; |
| 244 | } |
| 245 | </style> |
| 246 | <!-- Wrap the button in a container, otherwise we can't center it inside the column. --> |
| 247 | <span class="button-container"> |
| 248 | <button class="delete-button" |
| 249 | title=${i18nString(UIStrings.deleteTrustTokens, {PH1: issuer as string})} |
| 250 | @click=${(): void => this.deleteClickHandler(issuer as string)}> |
| 251 | <devtools-icon .data=${ |
| 252 | {iconName: 'trash_bin_icon', color: 'var(--color-text-secondary)', width: '9px', height: '14px'} as |
| 253 | Components.Icon.IconWithName}> |
| 254 | </devtools-icon> |
| 255 | </button> |
| 256 | </span>`; |
| 257 | // clang-format on |
| 258 | } |
Simon Zünd | 30152ed | 2021-01-21 09:18:28 | [diff] [blame] | 259 | } |
| 260 | |
Simon Zünd | 400dcba | 2021-02-03 09:19:50 | [diff] [blame] | 261 | function removeTrailingSlash(s: string): string { |
| 262 | return s.replace(/\/$/, ''); |
| 263 | } |
| 264 | |
Simon Zünd | 30152ed | 2021-01-21 09:18:28 | [diff] [blame] | 265 | customElements.define('devtools-trust-tokens-storage-view', TrustTokensView); |
| 266 | |
| 267 | declare global { |
| 268 | // eslint-disable-next-line @typescript-eslint/no-unused-vars |
| 269 | interface HTMLElementTagNameMap { |
| 270 | 'devtools-trust-tokens-storage-view': TrustTokensView; |
| 271 | } |
| 272 | } |