blob: 4c455b21e53afadac1afdbed4f6184078d84d671 [file] [log] [blame]
Charlie Hu2086be52021-07-02 16:12:121// Copyright 2021 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
Charlie Hud2002ce2021-07-20 14:58:315import * as i18n from '../../../core/i18n/i18n.js';
Charlie Hu2086be52021-07-02 16:12:126import * as Protocol from '../../../generated/protocol.js';
Sigurd Schneiderd45f6ad2021-07-08 09:19:247import * as Adorners from '../../../ui/components/adorners/adorners.js';
Charlie Hu2086be52021-07-02 16:12:128import * as ComponentHelpers from '../../../ui/components/helpers/helpers.js';
9import * as TreeOutline from '../../../ui/components/tree_outline/tree_outline.js';
10import * as LitHtml from '../../../ui/lit-html/lit-html.js';
Charlie Hud2002ce2021-07-20 14:58:3111
Kriti Saprad143c6e2021-07-16 11:03:5112import badgeStyles from './badge.css.js';
13import originTrialTokenRowsStyles from './originTrialTokenRows.css.js';
Charlie Hu2086be52021-07-02 16:12:1214
15const UIStrings = {
16 /**
Jack Franklinfd72c072022-12-21 11:45:0117 *@description Label for the 'origin' field in a parsed Origin Trial Token.
18 */
Charlie Hu2086be52021-07-02 16:12:1219 origin: 'Origin',
20 /**
Charlie Hu20b933e2021-10-07 20:32:0721 *@description Label for `trialName` field in a parsed Origin Trial Token.
22 * This field is only shown when token has unknown trial name as the token
23 * will be put into 'UNKNOWN' group.
24 */
25 trialName: 'Trial Name',
26 /**
Charlie Hu2086be52021-07-02 16:12:1227 *@description Label for `expiryTime` field in a parsed Origin Trial Token.
28 */
29 expiryTime: 'Expiry Time',
30 /**
31 *@description Label for `usageRestriction` field in a parsed Origin Trial Token.
32 */
33 usageRestriction: 'Usage Restriction',
34 /**
35 *@description Label for `isThirdParty` field in a parsed Origin Trial Token.
36 */
37 isThirdParty: 'Third Party',
38 /**
Wolfgang Beyer657d45c2021-07-22 10:10:4639 *@description Label for a field containing info about an Origin Trial Token's `matchSubDomains` field.
40 *An Origin Trial Token contains an origin URL. The `matchSubDomains` field describes whether the token
41 *only applies to the origin URL or to all subdomains of the origin URL as well.
42 *The field contains either 'true' or 'false'.
Charlie Hu2086be52021-07-02 16:12:1243 */
Wolfgang Beyer657d45c2021-07-22 10:10:4644 matchSubDomains: 'Subdomain Matching',
Charlie Hu2086be52021-07-02 16:12:1245 /**
Wolfgang Beyer657d45c2021-07-22 10:10:4646 *@description Label for the raw(= encoded / not human-readable) Origin Trial Token.
Charlie Hu2086be52021-07-02 16:12:1247 */
48 rawTokenText: 'Raw Token',
49 /**
50 *@description Label for `status` field in an Origin Trial Token.
51 */
Charlie Hu5ebe35a2021-07-14 18:37:3752 status: 'Token Status',
Charlie Hu2086be52021-07-02 16:12:1253 /**
54 *@description Label for tokenWithStatus node.
55 */
56 token: 'Token',
57 /**
Wolfgang Beyer657d45c2021-07-22 10:10:4658 *@description Label for a badge showing the number of Origin Trial Tokens. This number is always greater than 1.
59 *@example {2} PH1
Charlie Hu2086be52021-07-02 16:12:1260 */
Wolfgang Beyer657d45c2021-07-22 10:10:4661 tokens: '{PH1} tokens',
Charlie Hu2086be52021-07-02 16:12:1262};
Wolfgang Beyer657d45c2021-07-22 10:10:4663const str_ = i18n.i18n.registerUIStrings('panels/application/components/OriginTrialTreeView.ts', UIStrings);
64const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
Charlie Hu2086be52021-07-02 16:12:1265
66export interface BadgeData {
67 badgeContent: string;
68 style: 'error'|'success'|'secondary';
69}
70
71export class Badge extends HTMLElement {
72 static readonly litTagName = LitHtml.literal`devtools-resources-origin-trial-tree-view-badge`;
Tim van der Lippec3f835a2021-12-15 13:28:3273 readonly #shadow = this.attachShadow({mode: 'open'});
74 #adorner = new Adorners.Adorner.Adorner();
Charlie Hu2086be52021-07-02 16:12:1275
76 set data(data: BadgeData) {
Tim van der Lippec3f835a2021-12-15 13:28:3277 this.#render(data);
Charlie Hu2086be52021-07-02 16:12:1278 }
79
Kriti Saprad143c6e2021-07-16 11:03:5180 connectedCallback(): void {
Tim van der Lippec3f835a2021-12-15 13:28:3281 this.#shadow.adoptedStyleSheets = [badgeStyles];
Kriti Saprad143c6e2021-07-16 11:03:5182 }
83
Tim van der Lippec3f835a2021-12-15 13:28:3284 #render(data: BadgeData): void {
Charlie Hu2086be52021-07-02 16:12:1285 const adornerContent = document.createElement('span');
86 adornerContent.textContent = data.badgeContent;
Tim van der Lippec3f835a2021-12-15 13:28:3287 this.#adorner.data = {
Charlie Hu2086be52021-07-02 16:12:1288 name: 'badge',
89 content: adornerContent,
Charlie Hu2086be52021-07-02 16:12:1290 };
Tim van der Lippec3f835a2021-12-15 13:28:3291 this.#adorner.classList.add(`badge-${data.style}`);
Charlie Hu2086be52021-07-02 16:12:1292
Charlie Hu2086be52021-07-02 16:12:1293 LitHtml.render(
Charlie Hu2086be52021-07-02 16:12:1294 LitHtml.html`
Tim van der Lippec3f835a2021-12-15 13:28:3295 ${this.#adorner}
Charlie Hu2086be52021-07-02 16:12:1296 `,
Tim van der Lippec3f835a2021-12-15 13:28:3297 this.#shadow, {host: this});
Charlie Hu2086be52021-07-02 16:12:1298 }
99}
100
101ComponentHelpers.CustomElements.defineComponent('devtools-resources-origin-trial-tree-view-badge', Badge);
102
103type TreeNode<DataType> = TreeOutline.TreeOutlineUtils.TreeNode<DataType>;
104
105// The Origin Trial Tree has 4 levels of content:
106// - Origin Trial (has multiple Origin Trial tokens)
107// - Origin Trial Token (has only 1 raw token text)
108// - Fields in Origin Trial Token
109// - Raw Origin Trial Token text (folded because the content is long)
110export type OriginTrialTreeNodeData = Protocol.Page.OriginTrial|Protocol.Page.OriginTrialTokenWithStatus|string;
111
112function constructOriginTrialTree(originTrial: Protocol.Page.OriginTrial): TreeNode<OriginTrialTreeNodeData> {
113 return {
114 treeNodeData: originTrial,
Johan Bay08670a72021-08-24 11:51:17115 id: 'OriginTrialTreeNode#' + originTrial.trialName,
Charlie Hu2086be52021-07-02 16:12:12116 children: async(): Promise<TreeNode<OriginTrialTreeNodeData>[]> => originTrial.tokensWithStatus.length > 1 ?
117 originTrial.tokensWithStatus.map(constructTokenNode) :
118 constructTokenDetailsNodes(originTrial.tokensWithStatus[0]),
119 renderer: (node: TreeNode<OriginTrialTreeNodeData>): LitHtml.TemplateResult => {
120 const trial = node.treeNodeData as Protocol.Page.OriginTrial;
121 const tokenCountBadge = LitHtml.html`
122 <${Badge.litTagName} .data=${{
Wolfgang Beyer657d45c2021-07-22 10:10:46123 badgeContent: i18nString(UIStrings.tokens, {PH1: trial.tokensWithStatus.length}),
Charlie Hu2086be52021-07-02 16:12:12124 style: 'secondary',
125 } as BadgeData}></${Badge.litTagName}>
126 `;
127
128 return LitHtml.html`
129 ${trial.trialName}
130 <${Badge.litTagName} .data=${{
131 badgeContent: trial.status,
132 style: trial.status === Protocol.Page.OriginTrialStatus.Enabled ? 'success' : 'error',
133 } as BadgeData}></${Badge.litTagName}>
134 ${trial.tokensWithStatus.length > 1 ? tokenCountBadge : LitHtml.nothing}
135 `;
136 },
137 };
138}
139
140function constructTokenNode(token: Protocol.Page.OriginTrialTokenWithStatus): TreeNode<OriginTrialTreeNodeData> {
141 return {
142 treeNodeData: token.status,
Johan Bay08670a72021-08-24 11:51:17143 id: 'TokenNode#' + token.rawTokenText,
Charlie Hu2086be52021-07-02 16:12:12144 children: async(): Promise<TreeNode<OriginTrialTreeNodeData>[]> => constructTokenDetailsNodes(token),
145 renderer: (node: TreeNode<OriginTrialTreeNodeData>, state: {isExpanded: boolean}): LitHtml.TemplateResult => {
146 const tokenStatus = node.treeNodeData as string;
147 const statusBadge = LitHtml.html`
148 <${Badge.litTagName} .data=${{
149 badgeContent: tokenStatus,
150 style: tokenStatus === Protocol.Page.OriginTrialTokenStatus.Success ? 'success' : 'error',
151 } as BadgeData}></${Badge.litTagName}>
152 `;
153 // Only display token status for convenience when the node is not expanded.
Wolfgang Beyer657d45c2021-07-22 10:10:46154 return LitHtml.html`${i18nString(UIStrings.token)} ${state.isExpanded ? LitHtml.nothing : statusBadge}`;
Charlie Hu2086be52021-07-02 16:12:12155 },
156 };
157}
158
159interface TokenField {
160 name: string;
161 value: LitHtml.TemplateResult;
162}
163
164function renderTokenDetails(node: TreeNode<OriginTrialTreeNodeData>): LitHtml.TemplateResult {
Charlie Hu2086be52021-07-02 16:12:12165 return LitHtml.html`
Kriti Saprad143c6e2021-07-16 11:03:51166 <${OriginTrialTokenRows.litTagName} .data=${{node: node} as OriginTrialTokenRowsData}>
167 </${OriginTrialTokenRows.litTagName}>
Charlie Hu2086be52021-07-02 16:12:12168 `;
169}
170
171function constructTokenDetailsNodes(token: Protocol.Page.OriginTrialTokenWithStatus):
172 TreeNode<OriginTrialTreeNodeData>[] {
173 return [
174 {
175 treeNodeData: token,
Johan Bay08670a72021-08-24 11:51:17176 id: 'TokenDetailsNode#' + token.rawTokenText,
Charlie Hu2086be52021-07-02 16:12:12177 renderer: renderTokenDetails,
178 },
179 constructRawTokenTextNode(token.rawTokenText),
180 ];
181}
182
183function constructRawTokenTextNode(tokenText: string): TreeNode<OriginTrialTreeNodeData> {
184 return {
Wolfgang Beyer657d45c2021-07-22 10:10:46185 treeNodeData: i18nString(UIStrings.rawTokenText),
Johan Bay08670a72021-08-24 11:51:17186 id: 'TokenRawTextContainerNode#' + tokenText,
Charlie Hu2086be52021-07-02 16:12:12187 children: async(): Promise<TreeNode<OriginTrialTreeNodeData>[]> => [{
188 treeNodeData: tokenText,
Johan Bay08670a72021-08-24 11:51:17189 id: 'TokenRawTextNode#' + tokenText,
Charlie Hu2086be52021-07-02 16:12:12190 renderer: (data: TreeNode<OriginTrialTreeNodeData>): LitHtml.TemplateResult => {
191 const tokenText = data.treeNodeData as string;
192 return LitHtml.html`
193 <div style="overflow-wrap: break-word;">
194 ${tokenText}
195 </div>
196 `;
197 },
198 }],
199 };
200}
201
202function defaultRenderer(node: TreeNode<OriginTrialTreeNodeData>): LitHtml.TemplateResult {
203 return LitHtml.html`${String(node.treeNodeData)}`;
204}
205
Kriti Saprad143c6e2021-07-16 11:03:51206export interface OriginTrialTokenRowsData {
207 node: TreeNode<OriginTrialTreeNodeData>;
208}
209
210export class OriginTrialTokenRows extends HTMLElement {
Jack Franklin734befd2021-09-03 13:11:35211 static readonly litTagName = LitHtml.literal`devtools-resources-origin-trial-token-rows`;
Tim van der Lippec3f835a2021-12-15 13:28:32212 readonly #shadow = this.attachShadow({mode: 'open'});
213 #tokenWithStatus: Protocol.Page.OriginTrialTokenWithStatus|null = null;
214 #parsedTokenDetails: TokenField[] = [];
215 #dateFormatter: Intl.DateTimeFormat = new Intl.DateTimeFormat(
Charlie Hud2002ce2021-07-20 14:58:31216 i18n.DevToolsLocale.DevToolsLocale.instance().locale,
217 {dateStyle: 'long', timeStyle: 'long'},
218 );
Kriti Saprad143c6e2021-07-16 11:03:51219
220 set data(data: OriginTrialTokenRowsData) {
Tim van der Lippec3f835a2021-12-15 13:28:32221 this.#tokenWithStatus = data.node.treeNodeData as Protocol.Page.OriginTrialTokenWithStatus;
222 this.#setTokenFields();
Kriti Saprad143c6e2021-07-16 11:03:51223 }
224
225 connectedCallback(): void {
Tim van der Lippec3f835a2021-12-15 13:28:32226 this.#shadow.adoptedStyleSheets = [originTrialTokenRowsStyles];
227 this.#render();
Kriti Saprad143c6e2021-07-16 11:03:51228 }
229
Tim van der Lippec3f835a2021-12-15 13:28:32230 #renderTokenField = (fieldValue: string, hasError?: boolean): LitHtml.TemplateResult => LitHtml.html`
Jack Franklina31102b2021-12-03 15:01:22231 <div class=${LitHtml.Directives.ifDefined(hasError ? 'error-text' : undefined)}>
Kriti Saprad143c6e2021-07-16 11:03:51232 ${fieldValue}
233 </div>`;
234
Tim van der Lippec3f835a2021-12-15 13:28:32235 #setTokenFields(): void {
236 if (!this.#tokenWithStatus?.parsedToken) {
Kriti Saprad143c6e2021-07-16 11:03:51237 return;
238 }
Tim van der Lippec3f835a2021-12-15 13:28:32239 this.#parsedTokenDetails = [
Kriti Saprad143c6e2021-07-16 11:03:51240 {
Wolfgang Beyer657d45c2021-07-22 10:10:46241 name: i18nString(UIStrings.origin),
Tim van der Lippec3f835a2021-12-15 13:28:32242 value: this.#renderTokenField(
243 this.#tokenWithStatus.parsedToken.origin,
244 this.#tokenWithStatus.status === Protocol.Page.OriginTrialTokenStatus.WrongOrigin),
Kriti Saprad143c6e2021-07-16 11:03:51245 },
246 {
Wolfgang Beyer657d45c2021-07-22 10:10:46247 name: i18nString(UIStrings.expiryTime),
Tim van der Lippec3f835a2021-12-15 13:28:32248 value: this.#renderTokenField(
249 this.#dateFormatter.format(this.#tokenWithStatus.parsedToken.expiryTime * 1000),
250 this.#tokenWithStatus.status === Protocol.Page.OriginTrialTokenStatus.Expired),
Kriti Saprad143c6e2021-07-16 11:03:51251 },
252 {
Wolfgang Beyer657d45c2021-07-22 10:10:46253 name: i18nString(UIStrings.usageRestriction),
Tim van der Lippec3f835a2021-12-15 13:28:32254 value: this.#renderTokenField(this.#tokenWithStatus.parsedToken.usageRestriction),
Kriti Saprad143c6e2021-07-16 11:03:51255 },
256 {
Wolfgang Beyer657d45c2021-07-22 10:10:46257 name: i18nString(UIStrings.isThirdParty),
Tim van der Lippec3f835a2021-12-15 13:28:32258 value: this.#renderTokenField(this.#tokenWithStatus.parsedToken.isThirdParty.toString()),
Kriti Saprad143c6e2021-07-16 11:03:51259 },
260 {
Wolfgang Beyer657d45c2021-07-22 10:10:46261 name: i18nString(UIStrings.matchSubDomains),
Tim van der Lippec3f835a2021-12-15 13:28:32262 value: this.#renderTokenField(this.#tokenWithStatus.parsedToken.matchSubDomains.toString()),
Kriti Saprad143c6e2021-07-16 11:03:51263 },
264 ];
Charlie Hu20b933e2021-10-07 20:32:07265
Tim van der Lippec3f835a2021-12-15 13:28:32266 if (this.#tokenWithStatus.status === Protocol.Page.OriginTrialTokenStatus.UnknownTrial) {
267 this.#parsedTokenDetails = [
Charlie Hu20b933e2021-10-07 20:32:07268 {
269 name: i18nString(UIStrings.trialName),
Tim van der Lippec3f835a2021-12-15 13:28:32270 value: this.#renderTokenField(this.#tokenWithStatus.parsedToken.trialName),
Charlie Hu20b933e2021-10-07 20:32:07271 },
Tim van der Lippec3f835a2021-12-15 13:28:32272 ...this.#parsedTokenDetails,
Charlie Hu20b933e2021-10-07 20:32:07273 ];
274 }
Kriti Saprad143c6e2021-07-16 11:03:51275 }
276
Tim van der Lippec3f835a2021-12-15 13:28:32277 #render(): void {
278 if (!this.#tokenWithStatus) {
Kriti Saprad143c6e2021-07-16 11:03:51279 return;
280 }
281
282 const tokenDetails: TokenField[] = [
283 {
Wolfgang Beyer657d45c2021-07-22 10:10:46284 name: i18nString(UIStrings.status),
Kriti Saprad143c6e2021-07-16 11:03:51285 value: LitHtml.html`
286 <${Badge.litTagName} .data=${{
Tim van der Lippec3f835a2021-12-15 13:28:32287 badgeContent: this.#tokenWithStatus.status,
288 style: this.#tokenWithStatus.status === Protocol.Page.OriginTrialTokenStatus.Success ? 'success' : 'error',
Kriti Saprad143c6e2021-07-16 11:03:51289 } as BadgeData}></${Badge.litTagName}>`,
290 },
Tim van der Lippec3f835a2021-12-15 13:28:32291 ...this.#parsedTokenDetails,
Kriti Saprad143c6e2021-07-16 11:03:51292 ];
293
294 const tokenDetailRows = tokenDetails.map((field: TokenField): LitHtml.TemplateResult => {
295 return LitHtml.html`
296 <div class="key">${field.name}</div>
297 <div class="value">${field.value}</div>
298 `;
299 });
300
301 LitHtml.render(
302 LitHtml.html`
303 <div class="content">
304 ${tokenDetailRows}
305 </div>
306 `,
Tim van der Lippec3f835a2021-12-15 13:28:32307 this.#shadow, {host: this});
Kriti Saprad143c6e2021-07-16 11:03:51308 }
309}
310
311ComponentHelpers.CustomElements.defineComponent('devtools-resources-origin-trial-token-rows', OriginTrialTokenRows);
312
Charlie Hu2086be52021-07-02 16:12:12313export interface OriginTrialTreeViewData {
314 trials: Protocol.Page.OriginTrial[];
315}
316
317export class OriginTrialTreeView extends HTMLElement {
Jack Franklin734befd2021-09-03 13:11:35318 static readonly litTagName = LitHtml.literal`devtools-resources-origin-trial-tree-view`;
Tim van der Lippec3f835a2021-12-15 13:28:32319 readonly #shadow = this.attachShadow({mode: 'open'});
Charlie Hu2086be52021-07-02 16:12:12320
321 set data(data: OriginTrialTreeViewData) {
Tim van der Lippec3f835a2021-12-15 13:28:32322 this.#render(data.trials);
Charlie Hu2086be52021-07-02 16:12:12323 }
324
Tim van der Lippec3f835a2021-12-15 13:28:32325 #render(trials: Protocol.Page.OriginTrial[]): void {
Charlie Hu2086be52021-07-02 16:12:12326 if (!trials.length) {
327 return;
328 }
329
330 LitHtml.render(
Charlie Hu2086be52021-07-02 16:12:12331 LitHtml.html`
Jack Franklina31102b2021-12-03 15:01:22332 <${TreeOutline.TreeOutline.TreeOutline.litTagName} .data=${{
Charlie Hu2086be52021-07-02 16:12:12333 tree: trials.map(constructOriginTrialTree),
334 defaultRenderer,
Jack Franklina31102b2021-12-03 15:01:22335 } as TreeOutline.TreeOutline.TreeOutlineData<OriginTrialTreeNodeData>}>
Charlie Hu2086be52021-07-02 16:12:12336 </${TreeOutline.TreeOutline.TreeOutline.litTagName}>
337 `,
Tim van der Lippec3f835a2021-12-15 13:28:32338 this.#shadow, {host: this});
Charlie Hu2086be52021-07-02 16:12:12339 }
340}
341
342ComponentHelpers.CustomElements.defineComponent('devtools-resources-origin-trial-tree-view', OriginTrialTreeView);
Jack Franklin734befd2021-09-03 13:11:35343
344declare global {
345 interface HTMLElementTagNameMap {
346 'devtools-resources-origin-trial-tree-view': OriginTrialTreeView;
347 'devtools-resources-origin-trial-token-rows': OriginTrialTokenRows;
348 'devtools-resources-origin-trial-tree-view-badge': Badge;
349 }
350}