// Copyright 2020 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import '../../../ui/kit/kit.js'; import '../../../ui/legacy/components/data_grid/data_grid.js'; import * as i18n from '../../../core/i18n/i18n.js'; import * as SDK from '../../../core/sdk/sdk.js'; import type * as Protocol from '../../../generated/protocol.js'; import * as Buttons from '../../../ui/components/buttons/buttons.js'; import * as UI from '../../../ui/legacy/legacy.js'; import * as Lit from '../../../ui/lit/lit.js'; import * as VisualLogging from '../../../ui/visual_logging/visual_logging.js'; import trustTokensViewStyles from './trustTokensView.css.js'; const PRIVATE_STATE_TOKENS_EXPLANATION_URL = 'https://developers.google.com/privacy-sandbox/protections/private-state-tokens'; const {html} = Lit; const UIStrings = { /** * @description Text for the issuer of an item */ issuer: 'Issuer', /** * @description Column header for Trust Token table */ storedTokenCount: 'Stored token count', /** * @description Hover text for an info icon in the Private State Token panel */ allStoredTrustTokensAvailableIn: 'All stored private state tokens available in this browser instance.', /** * @description Text shown instead of a table when the table would be empty. https://developers.google.com/privacy-sandbox/protections/private-state-tokens */ noTrustTokens: 'No private state tokens detected', /** * @description Text shown if there are no private state tokens. https://developers.google.com/privacy-sandbox/protections/private-state-tokens */ trustTokensDescription: 'On this page you can view all available private state tokens in the current browsing context.', /** * @description Each row in the Private State 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 Private State Tokens that will be deleted when the button is clicked. * @example {https://google.com} PH1 */ deleteTrustTokens: 'Delete all stored private state tokens issued by {PH1}.', /** * @description Heading label for a view. Previously known as 'Trust Tokens'. */ trustTokens: 'Private state tokens', /** * @description Text used in a link to learn more about the topic. */ learnMore: 'Learn more', } as const; const str_ = i18n.i18n.registerUIStrings('panels/application/components/TrustTokensView.ts', UIStrings); export const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_); export interface TrustTokensViewInput { tokens: Protocol.Storage.TrustTokens[]; deleteClickHandler: (issuerOrigin: string) => void; } /** Fetch the Trust Token data regularly from the backend while the panel is open */ const REFRESH_INTERVAL_MS = 1000; function renderGridOrNoDataMessage(input: TrustTokensViewInput): Lit.TemplateResult { if (input.tokens.length === 0) { // clang-format off return html`
${i18nString(UIStrings.noTrustTokens)}
${i18nString(UIStrings.trustTokensDescription)} ${i18nString(UIStrings.learnMore)}
`; // clang-format on } // clang-format off return html`
${i18nString(UIStrings.trustTokens)} ${input.tokens.filter(token => token.count > 0) .map(token => html` `)}
${i18nString(UIStrings.issuer)} ${i18nString(UIStrings.storedTokenCount)}
${removeTrailingSlash(token.issuerOrigin)} ${token.count} input.deleteClickHandler(removeTrailingSlash(token.issuerOrigin))}>
`; // clang-format on } type View = (input: TrustTokensViewInput, output: undefined, target: HTMLElement) => void; const DEFAULT_VIEW: View = (input, output, target) => { // clang-format off Lit.render(html` ${renderGridOrNoDataMessage(input)} `, target); // clang-format on }; export class TrustTokensView extends UI.Widget.VBox { #updateInterval = 0; #tokens: Protocol.Storage.TrustTokens[] = []; #view: View; constructor(element?: HTMLElement, view = DEFAULT_VIEW) { super(element, {useShadowDom: true}); this.#view = view; } override wasShown(): void { super.wasShown(); this.requestUpdate(); this.#updateInterval = setInterval(this.requestUpdate.bind(this), REFRESH_INTERVAL_MS); } override willHide(): void { super.willHide(); clearInterval(this.#updateInterval); this.#updateInterval = 0; } override async performUpdate(): Promise { const mainTarget = SDK.TargetManager.TargetManager.instance().primaryPageTarget(); if (!mainTarget) { return; } const {tokens} = await mainTarget.storageAgent().invoke_getTrustTokens(); tokens.sort((a, b) => a.issuerOrigin.localeCompare(b.issuerOrigin)); this.#tokens = tokens; this.#view( {tokens: this.#tokens, deleteClickHandler: this.#deleteClickHandler.bind(this)}, undefined, this.contentElement); } #deleteClickHandler(issuerOrigin: string): void { const mainTarget = SDK.TargetManager.TargetManager.instance().primaryPageTarget(); void mainTarget?.storageAgent().invoke_clearTrustTokens({issuerOrigin}); } } function removeTrailingSlash(s: string): string { return s.replace(/\/$/, ''); }