chrome-devtools-frontend 1.0.1537860 → 1.0.1538523

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/.env.template +3 -2
  2. package/eslint.config.mjs +151 -149
  3. package/front_end/core/host/AidaClient.ts +1 -0
  4. package/front_end/core/host/UserMetrics.ts +3 -1
  5. package/front_end/core/root/Runtime.ts +8 -0
  6. package/front_end/models/ai_code_generation/AiCodeGeneration.ts +151 -0
  7. package/front_end/models/ai_code_generation/ai_code_generation.ts +6 -0
  8. package/front_end/models/ai_code_generation/debug.ts +30 -0
  9. package/front_end/panels/application/PreloadingTreeElement.ts +10 -2
  10. package/front_end/panels/application/components/OriginTrialTreeView.ts +97 -129
  11. package/front_end/panels/application/components/originTrialTreeView.css +37 -7
  12. package/front_end/panels/application/preloading/components/PreloadingString.ts +13 -11
  13. package/front_end/panels/emulation/components/DeviceSizeInputElement.ts +1 -0
  14. package/front_end/panels/network/NetworkItemView.ts +1 -1
  15. package/front_end/panels/network/NetworkWaterfallColumn.ts +5 -6
  16. package/front_end/panels/network/RequestTimingView.ts +404 -348
  17. package/front_end/panels/network/networkTimingTable.css +22 -2
  18. package/front_end/panels/timeline/components/NetworkRequestTooltip.ts +42 -3
  19. package/front_end/panels/timeline/components/networkRequestTooltip.css +19 -0
  20. package/front_end/ui/components/adorners/Adorner.ts +1 -0
  21. package/front_end/ui/components/icon_button/IconButton.ts +1 -0
  22. package/front_end/ui/components/settings/SettingCheckbox.ts +1 -0
  23. package/front_end/ui/legacy/Treeoutline.ts +15 -0
  24. package/front_end/ui/legacy/UIUtils.ts +3 -0
  25. package/front_end/ui/legacy/Widget.ts +6 -4
  26. package/front_end/ui/legacy/XLink.ts +1 -0
  27. package/front_end/ui/legacy/components/inline_editor/Swatches.ts +1 -0
  28. package/front_end/ui/legacy/components/perf_ui/BrickBreaker.ts +1 -0
  29. package/front_end/ui/legacy/popover.css +12 -11
  30. package/package.json +1 -1
  31. package/front_end/panels/application/components/badge.css +0 -33
@@ -342,6 +342,7 @@ export interface AidaRegisterClientEvent {
342
342
  disable_user_content_logging: boolean;
343
343
  do_conversation_client_event?: DoConversationClientEvent;
344
344
  complete_code_client_event?: {user_acceptance: UserAcceptance}|{user_impression: UserImpression};
345
+ generate_code_client_event?: {user_acceptance: UserAcceptance}|{user_impression: UserImpression};
345
346
  }
346
347
  /* eslint-enable @typescript-eslint/naming-convention */
347
348
 
@@ -527,7 +527,9 @@ export enum Action {
527
527
  InsightTeaserGenerationCompleted = 192,
528
528
  InsightTeaserGenerationAborted = 193,
529
529
  InsightTeaserGenerationErrored = 194,
530
- MAX_VALUE = 195,
530
+ AiCodeGenerationSuggestionDisplayed = 195,
531
+ AiCodeGenerationSuggestionAccepted = 196,
532
+ MAX_VALUE = 197,
531
533
  /* eslint-enable @typescript-eslint/naming-convention */
532
534
  }
533
535
 
@@ -422,6 +422,13 @@ export interface HostConfigAiCodeCompletion {
422
422
  userTier: string;
423
423
  }
424
424
 
425
+ export interface HostConfigAiCodeGeneration {
426
+ modelId: string;
427
+ temperature: number;
428
+ enabled: boolean;
429
+ userTier: string;
430
+ }
431
+
425
432
  export interface HostConfigDeepLinksViaExtensibilityApi {
426
433
  enabled: boolean;
427
434
  }
@@ -556,6 +563,7 @@ export type HostConfig = Platform.TypeScriptUtilities.RecursivePartial<{
556
563
  devToolsAiAssistanceFileAgent: HostConfigAiAssistanceFileAgent,
557
564
  devToolsAiAssistancePerformanceAgent: HostConfigAiAssistancePerformanceAgent,
558
565
  devToolsAiCodeCompletion: HostConfigAiCodeCompletion,
566
+ devToolsAiCodeGeneration: HostConfigAiCodeGeneration,
559
567
  devToolsVeLogging: HostConfigVeLogging,
560
568
  devToolsWellKnown: HostConfigWellKnown,
561
569
  devToolsPrivacyUI: HostConfigPrivacyUI,
@@ -0,0 +1,151 @@
1
+ // Copyright 2025 The Chromium Authors
2
+ // Use of this source code is governed by a BSD-style license that can be
3
+ // found in the LICENSE file.
4
+
5
+ import * as Host from '../../core/host/host.js';
6
+ import * as Root from '../../core/root/root.js';
7
+
8
+ import {debugLog} from './debug.js';
9
+
10
+ export const basePreamble =
11
+ `You are a highly skilled senior software engineer with deep expertise across multiple web technologies and programming languages, including JavaScript, TypeScript, HTML, and CSS.
12
+ Your role is to act as an expert pair programmer within the Chrome DevTools environment.
13
+
14
+ **Core Directives (Adhere to these strictly):**
15
+
16
+ 1. **Language and Quality:**
17
+ * Generate code that is modern, efficient, and idiomatic for the inferred language (e.g., modern JavaScript/ES6+, semantic HTML5, efficient CSS).
18
+ * Where appropriate, include basic error handling (e.g., for API calls).
19
+ `;
20
+
21
+ export const additionalContextForConsole = `
22
+ You are operating within the execution environment of the Chrome DevTools Console.
23
+ The console has direct access to the inspected page's \`window\` and \`document\`.
24
+
25
+ * **Utilize Console Utilities:** You have access to the Console Utilities API. You **should** use these helper functions and variables when they are the most direct way to accomplish the user's goal.
26
+ `;
27
+
28
+ interface Options {
29
+ aidaClient: Host.AidaClient.AidaClient;
30
+ serverSideLoggingEnabled?: boolean;
31
+ confirmSideEffectForTest?: typeof Promise.withResolvers;
32
+ }
33
+
34
+ interface RequestOptions {
35
+ temperature?: number;
36
+ modelId?: string;
37
+ }
38
+
39
+ /**
40
+ * The AiCodeGeneration class is responsible for fetching generated code suggestions
41
+ * from the AIDA backend.
42
+ */
43
+ export class AiCodeGeneration {
44
+ readonly #sessionId: string = crypto.randomUUID();
45
+ readonly #aidaClient: Host.AidaClient.AidaClient;
46
+ readonly #serverSideLoggingEnabled: boolean;
47
+
48
+ constructor(opts: Options) {
49
+ this.#aidaClient = opts.aidaClient;
50
+ this.#serverSideLoggingEnabled = opts.serverSideLoggingEnabled ?? false;
51
+ }
52
+
53
+ #buildRequest(
54
+ prompt: string,
55
+ preamble: string,
56
+ inferenceLanguage: Host.AidaClient.AidaInferenceLanguage = Host.AidaClient.AidaInferenceLanguage.JAVASCRIPT,
57
+ ): Host.AidaClient.GenerateCodeRequest {
58
+ const userTier = Host.AidaClient.convertToUserTierEnum(this.#userTier);
59
+ function validTemperature(temperature: number|undefined): number|undefined {
60
+ return typeof temperature === 'number' && temperature >= 0 ? temperature : undefined;
61
+ }
62
+ return {
63
+ client: Host.AidaClient.CLIENT_NAME,
64
+ preamble,
65
+ current_message: {
66
+ parts: [{
67
+ text: prompt,
68
+ }],
69
+ role: Host.AidaClient.Role.USER,
70
+ },
71
+ use_case: Host.AidaClient.UseCase.CODE_GENERATION,
72
+ options: {
73
+ inference_language: inferenceLanguage,
74
+ temperature: validTemperature(this.#options.temperature),
75
+ model_id: this.#options.modelId || undefined,
76
+ },
77
+ metadata: {
78
+ disable_user_content_logging: !(this.#serverSideLoggingEnabled ?? false),
79
+ string_session_id: this.#sessionId,
80
+ user_tier: userTier,
81
+ client_version: Root.Runtime.getChromeVersion(),
82
+ },
83
+ };
84
+ }
85
+
86
+ get #userTier(): string|undefined {
87
+ return Root.Runtime.hostConfig.devToolsAiCodeGeneration?.userTier;
88
+ }
89
+
90
+ get #options(): RequestOptions {
91
+ const temperature = Root.Runtime.hostConfig.devToolsAiCodeGeneration?.temperature;
92
+ const modelId = Root.Runtime.hostConfig.devToolsAiCodeGeneration?.modelId;
93
+
94
+ return {
95
+ temperature,
96
+ modelId,
97
+ };
98
+ }
99
+
100
+ registerUserImpression(rpcGlobalId: Host.AidaClient.RpcGlobalId, latency: number, sampleId?: number): void {
101
+ const seconds = Math.floor(latency / 1_000);
102
+ const remainingMs = latency % 1_000;
103
+ const nanos = Math.floor(remainingMs * 1_000_000);
104
+
105
+ void this.#aidaClient.registerClientEvent({
106
+ corresponding_aida_rpc_global_id: rpcGlobalId,
107
+ disable_user_content_logging: true,
108
+ generate_code_client_event: {
109
+ user_impression: {
110
+ sample: {
111
+ sample_id: sampleId,
112
+ },
113
+ latency: {
114
+ duration: {
115
+ seconds,
116
+ nanos,
117
+ },
118
+ }
119
+ },
120
+ },
121
+ });
122
+ debugLog('Registered user impression with latency {seconds:', seconds, ', nanos:', nanos, '}');
123
+ Host.userMetrics.actionTaken(Host.UserMetrics.Action.AiCodeGenerationSuggestionDisplayed);
124
+ }
125
+
126
+ registerUserAcceptance(rpcGlobalId: Host.AidaClient.RpcGlobalId, sampleId?: number): void {
127
+ void this.#aidaClient.registerClientEvent({
128
+ corresponding_aida_rpc_global_id: rpcGlobalId,
129
+ disable_user_content_logging: true,
130
+ generate_code_client_event: {
131
+ user_acceptance: {
132
+ sample: {
133
+ sample_id: sampleId,
134
+ }
135
+ },
136
+ },
137
+ });
138
+ debugLog('Registered user acceptance');
139
+ Host.userMetrics.actionTaken(Host.UserMetrics.Action.AiCodeGenerationSuggestionAccepted);
140
+ }
141
+
142
+ async generateCode(prompt: string, preamble: string, inferenceLanguage?: Host.AidaClient.AidaInferenceLanguage):
143
+ Promise<Host.AidaClient.GenerateCodeResponse|null> {
144
+ const request = this.#buildRequest(prompt, preamble, inferenceLanguage);
145
+ const response = await this.#aidaClient.generateCode(request);
146
+
147
+ debugLog({request, response});
148
+
149
+ return response;
150
+ }
151
+ }
@@ -0,0 +1,6 @@
1
+ // Copyright 2025 The Chromium Authors
2
+ // Use of this source code is governed by a BSD-style license that can be
3
+ // found in the LICENSE file.
4
+
5
+ export * from './debug.js';
6
+ export * as AiCodeGeneration from './AiCodeGeneration.js';
@@ -0,0 +1,30 @@
1
+ // Copyright 2025 The Chromium Authors
2
+ // Use of this source code is governed by a BSD-style license that can be
3
+ // found in the LICENSE file.
4
+
5
+ /**
6
+ * @file Local debugging utilities.
7
+ */
8
+
9
+ export function isDebugMode(): boolean {
10
+ return Boolean(localStorage.getItem('debugAiCodeGenerationEnabled'));
11
+ }
12
+
13
+ export function debugLog(...log: unknown[]): void {
14
+ if (!isDebugMode()) {
15
+ return;
16
+ }
17
+
18
+ // eslint-disable-next-line no-console
19
+ console.log(...log);
20
+ }
21
+
22
+ function setDebugAiCodeGenerationEnabled(enabled: boolean): void {
23
+ if (enabled) {
24
+ localStorage.setItem('debugAiCodeGenerationEnabled', 'true');
25
+ } else {
26
+ localStorage.removeItem('debugAiCodeGenerationEnabled');
27
+ }
28
+ }
29
+ // @ts-expect-error
30
+ globalThis.setDebugAiCodeGenerationEnabled = setDebugAiCodeGenerationEnabled;
@@ -8,7 +8,7 @@ import type * as SDK from '../../core/sdk/sdk.js';
8
8
  import * as IconButton from '../../ui/components/icon_button/icon_button.js';
9
9
 
10
10
  import {ApplicationPanelTreeElement, ExpandableApplicationPanelTreeElement} from './ApplicationPanelTreeElement.js';
11
- import type * as PreloadingHelper from './preloading/helper/helper.js';
11
+ import * as PreloadingHelper from './preloading/helper/helper.js';
12
12
  import {PreloadingAttemptView, PreloadingRuleSetView, PreloadingSummaryView} from './preloading/PreloadingView.js';
13
13
  import type {ResourcesPanel} from './ResourcesPanel.js';
14
14
 
@@ -125,7 +125,15 @@ export class PreloadingSummaryTreeElement extends ExpandableApplicationPanelTree
125
125
  this.#attempt.initialize(model);
126
126
 
127
127
  // Show the view if the model was initialized after selection.
128
- if (this.#selected && !this.#view) {
128
+ // However, if the user last viewed this page and clicked into Rules or
129
+ // Speculations, we ensure that we instead show those pages.
130
+ if (this.#attempt.selected) {
131
+ const filter = new PreloadingHelper.PreloadingForward.AttemptViewWithFilter(null);
132
+ this.expandAndRevealAttempts(filter);
133
+ } else if (this.#ruleSet.selected) {
134
+ const filter = new PreloadingHelper.PreloadingForward.RuleSetView(null);
135
+ this.expandAndRevealRuleSet(filter);
136
+ } else if (this.#selected && !this.#view) {
129
137
  this.onselect(false);
130
138
  }
131
139
  }
@@ -1,24 +1,22 @@
1
1
  // Copyright 2021 The Chromium Authors
2
2
  // Use of this source code is governed by a BSD-style license that can be
3
3
  // found in the LICENSE file.
4
- /* eslint-disable @devtools/no-imperative-dom-api */
5
- /* eslint-disable @devtools/no-lit-render-outside-of-view */
6
4
 
7
5
  import '../../../ui/components/icon_button/icon_button.js';
8
6
  import '../../../ui/legacy/legacy.js';
7
+ import '../../../ui/components/adorners/adorners.js';
9
8
 
10
9
  import * as i18n from '../../../core/i18n/i18n.js';
11
10
  import * as Protocol from '../../../generated/protocol.js';
12
- import * as Adorners from '../../../ui/components/adorners/adorners.js';
13
11
  import type * as TreeOutline from '../../../ui/components/tree_outline/tree_outline.js';
14
12
  import * as UI from '../../../ui/legacy/legacy.js';
15
- import * as Lit from '../../../ui/lit/lit.js';
13
+ import {Directives, html, type LitTemplate, nothing, render, type TemplateResult} from '../../../ui/lit/lit.js';
16
14
 
17
- import badgeStyles from './badge.css.js';
18
15
  import originTrialTokenRowsStyles from './originTrialTokenRows.css.js';
19
16
  import originTrialTreeViewStyles from './originTrialTreeView.css.js';
20
17
 
21
- const {html, Directives: {ifDefined}} = Lit;
18
+ const {classMap} = Directives;
19
+ const {widgetConfig} = UI.Widget;
22
20
 
23
21
  const UIStrings = {
24
22
  /**
@@ -75,27 +73,6 @@ const UIStrings = {
75
73
  const str_ = i18n.i18n.registerUIStrings('panels/application/components/OriginTrialTreeView.ts', UIStrings);
76
74
  const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
77
75
 
78
- export interface BadgeData {
79
- badgeContent: string;
80
- style: 'error'|'success'|'secondary';
81
- additionalClass?: string;
82
- }
83
-
84
- function createBadge(data: BadgeData): Adorners.Adorner.Adorner {
85
- const adorner = new Adorners.Adorner.Adorner();
86
- const adornerContent = document.createElement('span');
87
- adornerContent.textContent = data.badgeContent;
88
- adorner.data = {
89
- name: 'badge',
90
- content: adornerContent,
91
- };
92
- adorner.classList.add(`badge-${data.style}`);
93
- if (data.additionalClass) {
94
- adorner.classList.add(data.additionalClass);
95
- }
96
- return adorner;
97
- }
98
-
99
76
  type TreeNode<DataType> = TreeOutline.TreeOutlineUtils.TreeNode<DataType>;
100
77
 
101
78
  /**
@@ -107,72 +84,69 @@ type TreeNode<DataType> = TreeOutline.TreeOutlineUtils.TreeNode<DataType>;
107
84
  **/
108
85
  export type OriginTrialTreeNodeData = Protocol.Page.OriginTrial|Protocol.Page.OriginTrialTokenWithStatus|string;
109
86
 
110
- function constructOriginTrialTree(originTrial: Protocol.Page.OriginTrial): Lit.LitTemplate {
111
- const tokenCountBadge = createBadge({
112
- badgeContent: i18nString(UIStrings.tokens, {PH1: originTrial.tokensWithStatus.length}),
113
- style: 'secondary',
114
- });
115
-
87
+ function renderOriginTrialTree(originTrial: Protocol.Page.OriginTrial): LitTemplate {
88
+ const success = originTrial.status === Protocol.Page.OriginTrialStatus.Enabled;
116
89
  // clang-format off
117
90
  return html`
118
91
  <li role="treeitem">
119
92
  ${originTrial.trialName}
120
- <style>${badgeStyles}</style>
121
- ${createBadge({
122
- badgeContent: originTrial.status,
123
- style: originTrial.status === Protocol.Page.OriginTrialStatus.Enabled ? 'success' : 'error',
124
- })}
125
- ${originTrial.tokensWithStatus.length > 1 ? tokenCountBadge : Lit.nothing}
93
+ <devtools-adorner class="badge-${success ? 'success' : 'error'}">
94
+ ${originTrial.status}
95
+ </devtools-adorner>
96
+ ${originTrial.tokensWithStatus.length > 1 ? html`
97
+ <devtools-adorner class="badge-secondary">
98
+ ${i18nString(UIStrings.tokens, {PH1: originTrial.tokensWithStatus.length})}
99
+ </devtools-adorner>`
100
+ : nothing}
126
101
  <ul role="group" hidden>
127
102
  ${originTrial.tokensWithStatus.length > 1 ?
128
- originTrial.tokensWithStatus.map(constructTokenNode) :
129
- constructTokenDetailsNodes(originTrial.tokensWithStatus[0])}
103
+ originTrial.tokensWithStatus.map(renderTokenNode) :
104
+ renderTokenDetailsNodes(originTrial.tokensWithStatus[0])}
130
105
  </ul>
131
106
  </li>`;
132
107
  // clang-format on
133
108
  }
134
109
 
135
- function constructTokenNode(token: Protocol.Page.OriginTrialTokenWithStatus): Lit.LitTemplate {
136
- const statusBadge = createBadge({
137
- badgeContent: token.status,
138
- style: token.status === Protocol.Page.OriginTrialTokenStatus.Success ? 'success' : 'error',
139
- additionalClass: 'token-status-badge',
140
- });
110
+ function renderTokenNode(token: Protocol.Page.OriginTrialTokenWithStatus): LitTemplate {
111
+ const success = token.status === Protocol.Page.OriginTrialTokenStatus.Success;
141
112
  // Only display token status for convenience when the node is not expanded.
142
113
  // clang-format off
143
114
  return html`
144
115
  <li role="treeitem">
145
- ${i18nString(UIStrings.token)} ${statusBadge}
116
+ ${i18nString(UIStrings.token)}
117
+ <devtools-adorner class="token-status-badge badge-${success ? 'success' : 'error'}">
118
+ ${token.status}
119
+ </devtools-adorner>
146
120
  <ul role="group" hidden>
147
- ${constructTokenDetailsNodes(token)}
121
+ ${renderTokenDetailsNodes(token)}
148
122
  </ul>
149
123
  </li>`;
150
124
  }
151
125
 
152
126
  interface TokenField {
153
127
  name: string;
154
- value: Lit.TemplateResult;
128
+ value: {text: string, hasError?: boolean};
155
129
  }
156
130
 
157
- function renderTokenDetails(token: Protocol.Page.OriginTrialTokenWithStatus): Lit.TemplateResult {
131
+ function renderTokenDetails(token: Protocol.Page.OriginTrialTokenWithStatus): TemplateResult {
158
132
  return html`
159
133
  <li role="treeitem">
160
- <devtools-resources-origin-trial-token-rows .data=${token}>
161
- </devtools-resources-origin-trial-token-rows>
134
+ <devtools-widget .widgetConfig=${widgetConfig(OriginTrialTokenRows, {data: token})}>
135
+ </devtools-widget>
162
136
  </li>`;
163
137
  }
164
138
 
165
- function constructTokenDetailsNodes(token: Protocol.Page.OriginTrialTokenWithStatus):
166
- Lit.LitTemplate {
139
+ function renderTokenDetailsNodes(token: Protocol.Page.OriginTrialTokenWithStatus):
140
+ TemplateResult {
167
141
  // clang-format off
168
142
  return html`
169
143
  ${renderTokenDetails(token)}
170
- ${constructRawTokenTextNode(token.rawTokenText)}
144
+ ${renderRawTokenTextNode(token.rawTokenText)}
171
145
  `;
172
146
  // clang-format on
173
147
  }
174
148
 
175
- function constructRawTokenTextNode(tokenText: string): Lit.LitTemplate {
149
+ function renderRawTokenTextNode(tokenText: string): LitTemplate {
176
150
  // clang-format off
177
151
  return html`
178
152
  <li role="treeitem">
@@ -192,8 +166,42 @@ export interface OriginTrialTokenRowsData {
192
166
  node: TreeNode<OriginTrialTreeNodeData>;
193
167
  }
194
168
 
195
- export class OriginTrialTokenRows extends HTMLElement {
196
- readonly #shadow = this.attachShadow({mode: 'open'});
169
+ interface RowsViewInput {
170
+ tokenWithStatus: Protocol.Page.OriginTrialTokenWithStatus;
171
+ parsedTokenDetails: TokenField[];
172
+ }
173
+
174
+ type RowsView = (input: RowsViewInput, output: undefined, target: HTMLElement) => void;
175
+
176
+ const ROWS_DEFAULT_VIEW: RowsView = (input, _output, target) => {
177
+ const success = input.tokenWithStatus.status === Protocol.Page.OriginTrialTokenStatus.Success;
178
+ // clang-format off
179
+ render(html`
180
+ <style>
181
+ ${originTrialTokenRowsStyles}
182
+ ${originTrialTreeViewStyles}
183
+ </style>
184
+ <div class="content">
185
+ <div class="key">${i18nString(UIStrings.status)}</div>
186
+ <div class="value">
187
+ <devtools-adorner class="badge-${success ? 'success' : 'error'}">
188
+ ${input.tokenWithStatus.status}
189
+ </devtools-adorner>
190
+ </div>
191
+ ${input.parsedTokenDetails.map((field: TokenField) => html`
192
+ <div class="key">${field.name}</div>
193
+ <div class="value">
194
+ <div class=${classMap({'error-text': Boolean(field.value.hasError)})}>
195
+ ${field.value.text}
196
+ </div>
197
+ </div>
198
+ `)}
199
+ </div>`, target);
200
+ // clang-format on
201
+ };
202
+
203
+ export class OriginTrialTokenRows extends UI.Widget.Widget {
204
+ #view: RowsView;
197
205
  #tokenWithStatus: Protocol.Page.OriginTrialTokenWithStatus|null = null;
198
206
  #parsedTokenDetails: TokenField[] = [];
199
207
  #dateFormatter: Intl.DateTimeFormat = new Intl.DateTimeFormat(
@@ -201,28 +209,20 @@ export class OriginTrialTokenRows extends HTMLElement {
201
209
  {dateStyle: 'long', timeStyle: 'long'},
202
210
  );
203
211
 
212
+ constructor(element?: HTMLElement, view: RowsView = ROWS_DEFAULT_VIEW) {
213
+ super(element, {useShadowDom: true});
214
+ this.#view = view;
215
+ }
216
+
204
217
  set data(data: Protocol.Page.OriginTrialTokenWithStatus) {
205
218
  this.#tokenWithStatus = data;
206
219
  this.#setTokenFields();
207
220
  }
208
221
 
209
222
  connectedCallback(): void {
210
- this.#render();
211
- }
212
-
213
- override cloneNode(): HTMLElement {
214
- const clone = UI.UIUtils.cloneCustomElement(this);
215
- if (this.#tokenWithStatus) {
216
- clone.data = this.#tokenWithStatus;
217
- }
218
- return clone;
223
+ this.requestUpdate();
219
224
  }
220
225
 
221
- #renderTokenField = (fieldValue: string, hasError?: boolean): Lit.TemplateResult => html`
222
- <div class=${ifDefined(hasError ? 'error-text' : undefined)}>
223
- ${fieldValue}
224
- </div>`;
225
-
226
226
  #setTokenFields(): void {
227
227
  if (!this.#tokenWithStatus?.parsedToken) {
228
228
  return;
@@ -230,27 +230,29 @@ export class OriginTrialTokenRows extends HTMLElement {
230
230
  this.#parsedTokenDetails = [
231
231
  {
232
232
  name: i18nString(UIStrings.origin),
233
- value: this.#renderTokenField(
234
- this.#tokenWithStatus.parsedToken.origin,
235
- this.#tokenWithStatus.status === Protocol.Page.OriginTrialTokenStatus.WrongOrigin),
233
+ value: {
234
+ text: this.#tokenWithStatus.parsedToken.origin,
235
+ hasError: this.#tokenWithStatus.status === Protocol.Page.OriginTrialTokenStatus.WrongOrigin,
236
+ },
236
237
  },
237
238
  {
238
239
  name: i18nString(UIStrings.expiryTime),
239
- value: this.#renderTokenField(
240
- this.#dateFormatter.format(this.#tokenWithStatus.parsedToken.expiryTime * 1000),
241
- this.#tokenWithStatus.status === Protocol.Page.OriginTrialTokenStatus.Expired),
240
+ value: {
241
+ text: this.#dateFormatter.format(this.#tokenWithStatus.parsedToken.expiryTime * 1000),
242
+ hasError: this.#tokenWithStatus.status === Protocol.Page.OriginTrialTokenStatus.Expired
243
+ },
242
244
  },
243
245
  {
244
246
  name: i18nString(UIStrings.usageRestriction),
245
- value: this.#renderTokenField(this.#tokenWithStatus.parsedToken.usageRestriction),
247
+ value: {text: this.#tokenWithStatus.parsedToken.usageRestriction},
246
248
  },
247
249
  {
248
250
  name: i18nString(UIStrings.isThirdParty),
249
- value: this.#renderTokenField(this.#tokenWithStatus.parsedToken.isThirdParty.toString()),
251
+ value: {text: this.#tokenWithStatus.parsedToken.isThirdParty.toString()},
250
252
  },
251
253
  {
252
254
  name: i18nString(UIStrings.matchSubDomains),
253
- value: this.#renderTokenField(this.#tokenWithStatus.parsedToken.matchSubDomains.toString()),
255
+ value: {text: this.#tokenWithStatus.parsedToken.matchSubDomains.toString()},
254
256
  },
255
257
  ];
256
258
 
@@ -258,53 +260,27 @@ export class OriginTrialTokenRows extends HTMLElement {
258
260
  this.#parsedTokenDetails = [
259
261
  {
260
262
  name: i18nString(UIStrings.trialName),
261
- value: this.#renderTokenField(this.#tokenWithStatus.parsedToken.trialName),
263
+ value: {text: this.#tokenWithStatus.parsedToken.trialName},
262
264
  },
263
265
  ...this.#parsedTokenDetails,
264
266
  ];
265
267
  }
268
+ this.requestUpdate();
266
269
  }
267
270
 
268
- #render(): void {
271
+ override performUpdate(): void {
269
272
  if (!this.#tokenWithStatus) {
270
273
  return;
271
274
  }
272
275
 
273
- const tokenDetails: TokenField[] = [
274
- {
275
- name: i18nString(UIStrings.status),
276
- value: html`
277
- <style>${badgeStyles}</style>
278
- ${createBadge({
279
- badgeContent: this.#tokenWithStatus.status,
280
- style: this.#tokenWithStatus.status === Protocol.Page.OriginTrialTokenStatus.Success ? 'success' : 'error',
281
- })}`,
282
- },
283
- ...this.#parsedTokenDetails,
284
- ];
285
-
286
- const tokenDetailRows = tokenDetails.map((field: TokenField) => {
287
- return html`
288
- <div class="key">${field.name}</div>
289
- <div class="value">${field.value}</div>
290
- `;
291
- });
292
-
293
- Lit.render(
294
- html`
295
- <style>
296
- ${originTrialTokenRowsStyles}
297
- </style>
298
- <div class="content">
299
- ${tokenDetailRows}
300
- </div>
301
- `,
302
- this.#shadow, {host: this});
276
+ const viewInput: RowsViewInput = {
277
+ tokenWithStatus: this.#tokenWithStatus,
278
+ parsedTokenDetails: this.#parsedTokenDetails,
279
+ };
280
+ this.#view(viewInput, undefined, this.contentElement);
303
281
  }
304
282
  }
305
283
 
306
- customElements.define('devtools-resources-origin-trial-token-rows', OriginTrialTokenRows);
307
-
308
284
  export interface OriginTrialTreeViewData {
309
285
  trials: Protocol.Page.OriginTrial[];
310
286
  }
@@ -314,8 +290,7 @@ type View = (input: OriginTrialTreeViewData, output: undefined, target: HTMLElem
314
290
  const DEFAULT_VIEW: View = (input, _output, target) => {
315
291
  if (!input.trials.length) {
316
292
  // clang-format off
317
- Lit.render(html`
318
- <style>${originTrialTreeViewStyles}</style>
293
+ render(html`
319
294
  <span class="status-badge">
320
295
  <devtools-icon class="medium" name="clear"></devtools-icon>
321
296
  <span>${i18nString(UIStrings.noTrialTokens)}</span>
@@ -325,13 +300,12 @@ const DEFAULT_VIEW: View = (input, _output, target) => {
325
300
  }
326
301
 
327
302
  // clang-format off
328
- Lit.render(html`
329
- <style>
330
- ${originTrialTreeViewStyles}
331
- </style>
303
+ render(html`
304
+ <style>${originTrialTreeViewStyles}</style>
332
305
  <devtools-tree .template=${html`
306
+ <style>${originTrialTreeViewStyles}</style>
333
307
  <ul role="tree">
334
- ${input.trials.map(constructOriginTrialTree)}
308
+ ${input.trials.map(renderOriginTrialTree)}
335
309
  </ul>
336
310
  `}>
337
311
  </devtools-tree>
@@ -357,9 +331,3 @@ export class OriginTrialTreeView extends UI.Widget.Widget {
357
331
  this.#view(this.#data, undefined, this.contentElement);
358
332
  }
359
333
  }
360
-
361
- declare global {
362
- interface HTMLElementTagNameMap {
363
- 'devtools-resources-origin-trial-token-rows': OriginTrialTokenRows;
364
- }
365
- }
@@ -4,12 +4,42 @@
4
4
  * found in the LICENSE file.
5
5
  */
6
6
 
7
- .status-badge {
8
- border-radius: 4px;
9
- padding: 4px;
10
- background: var(--sys-color-neutral-container);
7
+ :host {
8
+ .status-badge {
9
+ border-radius: 4px;
10
+ padding: 4px;
11
+ background: var(--sys-color-neutral-container);
11
12
 
12
- & > devtools-icon {
13
- vertical-align: sub;
13
+ & > devtools-icon {
14
+ vertical-align: sub;
15
+ }
14
16
  }
15
- }
17
+
18
+ .badge-error {
19
+ --override-adorner-text-color: var(--sys-color-error-bright);
20
+ --override-adorner-border-color: var(--sys-color-error-bright);
21
+ }
22
+
23
+ .badge-success {
24
+ --override-adorner-text-color: var(--sys-color-tertiary);
25
+ --override-adorner-border-color: var(--sys-color-tertiary);
26
+ }
27
+
28
+ .badge-secondary {
29
+ --override-adorner-text-color: var(--sys-color-token-subtle);
30
+ --override-adorner-border-color: var(--sys-color-token-subtle);
31
+ }
32
+
33
+ /* Use mono-space source code font to assist reading of adorner content */
34
+ devtools-adorner {
35
+ font-family: var(--source-code-font-family);
36
+ }
37
+
38
+ .token-status-badge {
39
+ display: none;
40
+ }
41
+
42
+ [aria-expanded='false'] .token-status-badge {
43
+ display: inline-flex;
44
+ }
45
+ }