chrome-devtools-frontend 1.0.1534251 → 1.0.1535712

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 (44) hide show
  1. package/docs/contributing/infrastructure.md +32 -0
  2. package/docs/contributing/issues.md +15 -37
  3. package/eslint.config.mjs +1 -0
  4. package/front_end/core/host/InspectorFrontendHost.ts +2 -0
  5. package/front_end/core/host/InspectorFrontendHostAPI.ts +1 -0
  6. package/front_end/core/host/UserMetrics.ts +29 -1
  7. package/front_end/core/protocol_client/CDPConnection.ts +53 -5
  8. package/front_end/core/protocol_client/InspectorBackend.ts +0 -89
  9. package/front_end/core/protocol_client/protocol_client.ts +2 -0
  10. package/front_end/core/root/Runtime.ts +1 -0
  11. package/front_end/core/sdk/RehydratingConnection.ts +1 -1
  12. package/front_end/devtools_compatibility.js +230 -32
  13. package/front_end/generated/SupportedCSSProperties.js +38 -0
  14. package/front_end/models/ai_assistance/BuiltInAi.ts +141 -39
  15. package/front_end/panels/ai_assistance/PatchWidget.ts +39 -40
  16. package/front_end/panels/ai_assistance/components/ExploreWidget.ts +0 -2
  17. package/front_end/panels/autofill/AutofillView.ts +2 -3
  18. package/front_end/panels/changes/CombinedDiffView.ts +13 -14
  19. package/front_end/panels/common/BadgeNotification.ts +1 -3
  20. package/front_end/panels/console/ConsoleInsightTeaser.ts +8 -1
  21. package/front_end/panels/console/ConsoleView.ts +1 -0
  22. package/front_end/panels/console/consoleView.css +0 -1
  23. package/front_end/panels/elements/ElementsTreeOutline.ts +1 -1
  24. package/front_end/panels/network/components/DirectSocketConnectionView.ts +4 -6
  25. package/front_end/panels/network/components/ResponseHeaderSection.ts +1 -2
  26. package/front_end/panels/security/CookieControlsView.ts +72 -66
  27. package/front_end/panels/security/CookieReportView.ts +15 -14
  28. package/front_end/panels/security/IPProtectionView.ts +1 -2
  29. package/front_end/panels/security/SecurityPanel.ts +19 -19
  30. package/front_end/panels/timeline/TimelineSelectorStatsView.ts +36 -36
  31. package/front_end/panels/timeline/components/SidebarAnnotationsTab.ts +1 -2
  32. package/front_end/third_party/chromium/README.chromium +1 -1
  33. package/front_end/ui/components/dialogs/Dialog.ts +7 -17
  34. package/front_end/ui/components/text_editor/TextEditor.ts +2 -3
  35. package/front_end/ui/components/text_editor/config.ts +1 -3
  36. package/front_end/ui/legacy/UIUtils.ts +5 -0
  37. package/front_end/ui/legacy/components/inline_editor/CSSAngle.ts +1 -1
  38. package/front_end/ui/legacy/components/perf_ui/BrickBreaker.ts +2 -2
  39. package/front_end/ui/legacy/components/perf_ui/Font.ts +1 -14
  40. package/front_end/ui/visual_logging/KnownContextValues.ts +2 -0
  41. package/inspector_overlay/testing/InspectorOverlayHelpers.ts +2 -10
  42. package/package.json +1 -1
  43. package/front_end/services/window_bounds/WindowBoundsService.ts +0 -27
  44. package/front_end/services/window_bounds/window_bounds.ts +0 -9
@@ -2,10 +2,13 @@
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
4
 
5
+ import * as Host from '../../core/host/host.js';
5
6
  import * as Root from '../../core/root/root.js';
6
7
 
7
8
  let builtInAiInstance: BuiltInAi|undefined;
8
- let availability = '';
9
+ let availability: LanguageModelAvailability|undefined;
10
+ let hasGpu: boolean|undefined;
11
+ let isFirstRun = true;
9
12
 
10
13
  export interface LanguageModel {
11
14
  promptStreaming: (arg0: string, opts?: {
@@ -15,20 +18,62 @@ export interface LanguageModel {
15
18
  destroy: () => void;
16
19
  }
17
20
 
21
+ export const enum LanguageModelAvailability {
22
+ UNAVAILABLE = 'unavailable',
23
+ DOWNLOADABLE = 'downloadable',
24
+ DOWNLOADING = 'downloading',
25
+ AVAILABLE = 'available',
26
+ DISABLED = 'disabled',
27
+ }
28
+
18
29
  export class BuiltInAi {
19
30
  #consoleInsightsSession: LanguageModel;
20
31
 
21
- static async isAvailable(): Promise<boolean> {
32
+ static async getLanguageModelAvailability(): Promise<LanguageModelAvailability> {
22
33
  if (!Root.Runtime.hostConfig.devToolsAiPromptApi?.enabled) {
23
- return false;
34
+ return LanguageModelAvailability.DISABLED;
35
+ }
36
+ try {
37
+ // @ts-expect-error
38
+ availability = await window.LanguageModel.availability({expectedOutputs: [{type: 'text', languages: ['en']}]}) as
39
+ LanguageModelAvailability;
40
+ return availability;
41
+ } catch {
42
+ return LanguageModelAvailability.UNAVAILABLE;
24
43
  }
25
- // @ts-expect-error
26
- availability = await window.LanguageModel.availability({expectedOutputs: [{type: 'text', languages: ['en']}]});
27
- return availability === 'available';
28
44
  }
29
45
 
30
46
  static cachedIsAvailable(): boolean {
31
- return availability === 'available';
47
+ return availability === LanguageModelAvailability.AVAILABLE;
48
+ }
49
+
50
+ static isGpuAvailable(): boolean {
51
+ const hasGpuHelper = (): boolean => {
52
+ const canvas = document.createElement('canvas');
53
+ try {
54
+ const webgl = canvas.getContext('webgl');
55
+ if (!webgl) {
56
+ return false;
57
+ }
58
+ const debugInfo = webgl.getExtension('WEBGL_debug_renderer_info');
59
+ if (!debugInfo) {
60
+ return false;
61
+ }
62
+ const renderer = webgl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL);
63
+ if (renderer.includes('SwiftShader')) {
64
+ return false;
65
+ }
66
+ } catch {
67
+ return false;
68
+ }
69
+ return true;
70
+ };
71
+
72
+ if (hasGpu !== undefined) {
73
+ return hasGpu;
74
+ }
75
+ hasGpu = hasGpuHelper();
76
+ return hasGpu;
32
77
  }
33
78
 
34
79
  private constructor(consoleInsightsSession: LanguageModel) {
@@ -37,40 +82,97 @@ export class BuiltInAi {
37
82
 
38
83
  static async instance(): Promise<BuiltInAi|undefined> {
39
84
  if (builtInAiInstance === undefined) {
40
- if (!(await BuiltInAi.isAvailable())) {
41
- return undefined;
85
+ if (isFirstRun) {
86
+ const languageModelAvailability = await BuiltInAi.getLanguageModelAvailability();
87
+ const hasGpu = BuiltInAi.isGpuAvailable();
88
+ if (hasGpu) {
89
+ switch (languageModelAvailability) {
90
+ case LanguageModelAvailability.UNAVAILABLE:
91
+ Host.userMetrics.builtInAiAvailability(Host.UserMetrics.BuiltInAiAvailability.UNAVAILABLE_HAS_GPU);
92
+ break;
93
+ case LanguageModelAvailability.DOWNLOADABLE:
94
+ Host.userMetrics.builtInAiAvailability(Host.UserMetrics.BuiltInAiAvailability.DOWNLOADABLE_HAS_GPU);
95
+ break;
96
+ case LanguageModelAvailability.DOWNLOADING:
97
+ Host.userMetrics.builtInAiAvailability(Host.UserMetrics.BuiltInAiAvailability.DOWNLOADING_HAS_GPU);
98
+ break;
99
+ case LanguageModelAvailability.AVAILABLE:
100
+ Host.userMetrics.builtInAiAvailability(Host.UserMetrics.BuiltInAiAvailability.AVAILABLE_HAS_GPU);
101
+ break;
102
+ case LanguageModelAvailability.DISABLED:
103
+ Host.userMetrics.builtInAiAvailability(Host.UserMetrics.BuiltInAiAvailability.DISABLED_HAS_GPU);
104
+ break;
105
+ }
106
+ } else {
107
+ switch (languageModelAvailability) {
108
+ case LanguageModelAvailability.UNAVAILABLE:
109
+ Host.userMetrics.builtInAiAvailability(Host.UserMetrics.BuiltInAiAvailability.UNAVAILABLE_NO_GPU);
110
+ break;
111
+ case LanguageModelAvailability.DOWNLOADABLE:
112
+ Host.userMetrics.builtInAiAvailability(Host.UserMetrics.BuiltInAiAvailability.DOWNLOADABLE_NO_GPU);
113
+ break;
114
+ case LanguageModelAvailability.DOWNLOADING:
115
+ Host.userMetrics.builtInAiAvailability(Host.UserMetrics.BuiltInAiAvailability.DOWNLOADING_NO_GPU);
116
+ break;
117
+ case LanguageModelAvailability.AVAILABLE:
118
+ Host.userMetrics.builtInAiAvailability(Host.UserMetrics.BuiltInAiAvailability.AVAILABLE_NO_GPU);
119
+ break;
120
+ case LanguageModelAvailability.DISABLED:
121
+ Host.userMetrics.builtInAiAvailability(Host.UserMetrics.BuiltInAiAvailability.DISABLED_NO_GPU);
122
+ break;
123
+ }
124
+ }
125
+ isFirstRun = false;
126
+ if (!Root.Runtime.hostConfig.devToolsAiPromptApi?.allowWithoutGpu && !hasGpu) {
127
+ return undefined;
128
+ }
129
+ if (languageModelAvailability !== LanguageModelAvailability.AVAILABLE) {
130
+ return undefined;
131
+ }
132
+ } else {
133
+ if (!Root.Runtime.hostConfig.devToolsAiPromptApi?.allowWithoutGpu && !BuiltInAi.isGpuAvailable()) {
134
+ return undefined;
135
+ }
136
+ if ((await BuiltInAi.getLanguageModelAvailability()) !== LanguageModelAvailability.AVAILABLE) {
137
+ return undefined;
138
+ }
42
139
  }
43
- // @ts-expect-error
44
- const consoleInsightsSession = await window.LanguageModel.create({
45
- initialPrompts: [{
46
- role: 'system',
47
- content: `
48
- You are an expert web developer. Your goal is to help a human web developer who
49
- is using Chrome DevTools to debug a web site or web app. The Chrome DevTools
50
- console is showing a message which is either an error or a warning. Please help
51
- the user understand the problematic console message.
52
140
 
53
- Your instructions are as follows:
54
- - Explain the reason why the error or warning is showing up.
55
- - The explanation has a maximum length of 200 characters. Anything beyond this
56
- length will be cut off. Make sure that your explanation is at most 200 characters long.
57
- - Your explanation should not end in the middle of a sentence.
58
- - Your explanation should consist of a single paragraph only. Do not include any
59
- headings or code blocks. Only write a single paragraph of text.
60
- - Your response should be concise and to the point. Avoid lengthy explanations
61
- or unnecessary details.
62
- `
63
- }],
64
- expectedInputs: [{
65
- type: 'text',
66
- languages: ['en'],
67
- }],
68
- expectedOutputs: [{
69
- type: 'text',
70
- languages: ['en'],
71
- }],
72
- }) as LanguageModel;
73
- builtInAiInstance = new BuiltInAi(consoleInsightsSession);
141
+ try {
142
+ // @ts-expect-error
143
+ const consoleInsightsSession = await window.LanguageModel.create({
144
+ initialPrompts: [{
145
+ role: 'system',
146
+ content: `
147
+ You are an expert web developer. Your goal is to help a human web developer who
148
+ is using Chrome DevTools to debug a web site or web app. The Chrome DevTools
149
+ console is showing a message which is either an error or a warning. Please help
150
+ the user understand the problematic console message.
151
+
152
+ Your instructions are as follows:
153
+ - Explain the reason why the error or warning is showing up.
154
+ - The explanation has a maximum length of 200 characters. Anything beyond this
155
+ length will be cut off. Make sure that your explanation is at most 200 characters long.
156
+ - Your explanation should not end in the middle of a sentence.
157
+ - Your explanation should consist of a single paragraph only. Do not include any
158
+ headings or code blocks. Only write a single paragraph of text.
159
+ - Your response should be concise and to the point. Avoid lengthy explanations
160
+ or unnecessary details.
161
+ `
162
+ }],
163
+ expectedInputs: [{
164
+ type: 'text',
165
+ languages: ['en'],
166
+ }],
167
+ expectedOutputs: [{
168
+ type: 'text',
169
+ languages: ['en'],
170
+ }],
171
+ }) as LanguageModel;
172
+ builtInAiInstance = new BuiltInAi(consoleInsightsSession);
173
+ } catch {
174
+ return undefined;
175
+ }
74
176
  }
75
177
  return builtInAiInstance;
76
178
  }
@@ -1,7 +1,6 @@
1
1
  // Copyright 2025 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-lit-render-outside-of-view */
5
4
 
6
5
  import '../../ui/legacy/legacy.js';
7
6
  import '../../ui/components/markdown_view/markdown_view.js';
@@ -187,42 +186,8 @@ export interface ViewOutput {
187
186
  }
188
187
 
189
188
  type View = (input: ViewInput, output: ViewOutput, target: HTMLElement) => void;
190
-
191
- export class PatchWidget extends UI.Widget.Widget {
192
- changeSummary = '';
193
- changeManager: AiAssistanceModel.ChangeManager.ChangeManager|undefined;
194
- // Whether the user completed first run experience dialog or not.
195
- #aiPatchingFreCompletedSetting =
196
- Common.Settings.Settings.instance().createSetting('ai-assistance-patching-fre-completed', false);
197
- #projectIdSetting =
198
- Common.Settings.Settings.instance().createSetting('ai-assistance-patching-selected-project-id', '');
199
- #view: View;
200
- #viewOutput: ViewOutput = {};
201
- #aidaClient: Host.AidaClient.AidaClient;
202
- #applyPatchAbortController?: AbortController;
203
- #project?: Workspace.Workspace.Project;
204
- #patchSources?: string;
205
- #savedToDisk?: boolean;
206
- #noLogging: boolean; // Whether the enterprise setting is `ALLOW_WITHOUT_LOGGING` or not.
207
- #patchSuggestionState = PatchSuggestionState.INITIAL;
208
- #workspaceDiff = WorkspaceDiff.WorkspaceDiff.workspaceDiff();
209
- #workspace = Workspace.Workspace.WorkspaceImpl.instance();
210
- #automaticFileSystem =
211
- Persistence.AutomaticFileSystemManager.AutomaticFileSystemManager.instance().automaticFileSystem;
212
- #applyToDisconnectedAutomaticWorkspace = false;
213
- // `rpcId` from the `applyPatch` request
214
- #rpcId: Host.AidaClient.RpcGlobalId|null = null;
215
-
216
- constructor(element?: HTMLElement, view?: View, opts?: {
217
- aidaClient: Host.AidaClient.AidaClient,
218
- }) {
219
- super(element);
220
- this.#aidaClient = opts?.aidaClient ?? new Host.AidaClient.AidaClient();
221
- this.#noLogging = Root.Runtime.hostConfig.aidaAvailability?.enterprisePolicyValue ===
222
- Root.Runtime.GenAiEnterprisePolicyValue.ALLOW_WITHOUT_LOGGING;
223
-
224
- // clang-format off
225
- this.#view = view ?? ((input, output, target) => {
189
+ const DEFAULT_VIEW: View =
190
+ (input, output, target) => {
226
191
  if (!input.changeSummary && input.patchSuggestionState === PatchSuggestionState.INITIAL) {
227
192
  return;
228
193
  }
@@ -428,9 +393,43 @@ export class PatchWidget extends UI.Widget.Widget {
428
393
  </details>
429
394
  `;
430
395
 
431
- render(template, target, {host: target});
432
- });
433
- // clang-format on
396
+ render(template, target);
397
+ };
398
+
399
+ export class PatchWidget extends UI.Widget.Widget {
400
+ changeSummary = '';
401
+ changeManager: AiAssistanceModel.ChangeManager.ChangeManager|undefined;
402
+ // Whether the user completed first run experience dialog or not.
403
+ #aiPatchingFreCompletedSetting =
404
+ Common.Settings.Settings.instance().createSetting('ai-assistance-patching-fre-completed', false);
405
+ #projectIdSetting =
406
+ Common.Settings.Settings.instance().createSetting('ai-assistance-patching-selected-project-id', '');
407
+ #view: View;
408
+ #viewOutput: ViewOutput = {};
409
+ #aidaClient: Host.AidaClient.AidaClient;
410
+ #applyPatchAbortController?: AbortController;
411
+ #project?: Workspace.Workspace.Project;
412
+ #patchSources?: string;
413
+ #savedToDisk?: boolean;
414
+ #noLogging: boolean; // Whether the enterprise setting is `ALLOW_WITHOUT_LOGGING` or not.
415
+ #patchSuggestionState = PatchSuggestionState.INITIAL;
416
+ #workspaceDiff = WorkspaceDiff.WorkspaceDiff.workspaceDiff();
417
+ #workspace = Workspace.Workspace.WorkspaceImpl.instance();
418
+ #automaticFileSystem =
419
+ Persistence.AutomaticFileSystemManager.AutomaticFileSystemManager.instance().automaticFileSystem;
420
+ #applyToDisconnectedAutomaticWorkspace = false;
421
+ // `rpcId` from the `applyPatch` request
422
+ #rpcId: Host.AidaClient.RpcGlobalId|null = null;
423
+
424
+ constructor(element?: HTMLElement, view = DEFAULT_VIEW, opts?: {
425
+ aidaClient: Host.AidaClient.AidaClient,
426
+ }) {
427
+ super(element);
428
+ this.#aidaClient = opts?.aidaClient ?? new Host.AidaClient.AidaClient();
429
+ this.#noLogging = Root.Runtime.hostConfig.aidaAvailability?.enterprisePolicyValue ===
430
+ Root.Runtime.GenAiEnterprisePolicyValue.ALLOW_WITHOUT_LOGGING;
431
+ this.#view = view;
432
+
434
433
  this.requestUpdate();
435
434
  }
436
435
 
@@ -2,7 +2,6 @@
2
2
  // Copyright 2025 The Chromium Authors
3
3
  // Use of this source code is governed by a BSD-style license that can be
4
4
  // found in the LICENSE file.
5
- /* eslint-disable @devtools/no-lit-render-outside-of-view */
6
5
 
7
6
  import * as i18n from '../../../core/i18n/i18n.js';
8
7
  import * as Root from '../../../core/root/root.js';
@@ -105,7 +104,6 @@ export const DEFAULT_VIEW = (
105
104
  </div>
106
105
  `,
107
106
  target,
108
- { host: target },
109
107
  );
110
108
  // clang-format on
111
109
  };
@@ -1,7 +1,6 @@
1
1
  // Copyright 2023 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-lit-render-outside-of-view */
5
4
 
6
5
  import '../../ui/components/adorners/adorners.js';
7
6
  import '../../ui/legacy/components/data_grid/data_grid.js';
@@ -250,7 +249,7 @@ const DEFAULT_VIEW: View = (input: ViewInput, _output: ViewOutput, target: HTMLE
250
249
  </div>
251
250
  </div>
252
251
  </main>
253
- `, target, {host: this});
252
+ `, target);
254
253
  // clang-format on
255
254
  return;
256
255
  }
@@ -287,7 +286,7 @@ const DEFAULT_VIEW: View = (input: ViewInput, _output: ViewOutput, target: HTMLE
287
286
  ${renderFilledFields()}
288
287
  </div>
289
288
  </main>
290
- `, target, {host: this});
289
+ `, target);
291
290
  // clang-format on
292
291
  };
293
292
 
@@ -1,7 +1,6 @@
1
1
  // Copyright 2025 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-lit-render-outside-of-view */
5
4
 
6
5
  import * as Common from '../../core/common/common.js';
7
6
  import * as i18n from '../../core/i18n/i18n.js';
@@ -101,6 +100,18 @@ function renderSingleDiffView(singleDiffViewInput: SingleDiffViewInput): Lit.Tem
101
100
  // clang-format on
102
101
  }
103
102
 
103
+ const DEFAULT_VIEW: View = (input, output, target) => {
104
+ // clang-format off
105
+ Lit.render(
106
+ html`
107
+ <div class="combined-diff-view">
108
+ ${input.singleDiffViewInputs.map(singleDiffViewInput => renderSingleDiffView(singleDiffViewInput))}
109
+ </div>
110
+ `,
111
+ target);
112
+ // clang-format on
113
+ };
114
+
104
115
  export class CombinedDiffView extends UI.Widget.Widget {
105
116
  /**
106
117
  * Ignores urls that start with any in the list
@@ -113,19 +124,7 @@ export class CombinedDiffView extends UI.Widget.Widget {
113
124
  #copiedFiles: Record<string, boolean> = {};
114
125
  #view: View;
115
126
  #viewOutput: ViewOutput = {};
116
- constructor(element?: HTMLElement, view: View = (input, output, target) => {
117
- output.scrollToSelectedDiff = () => {
118
- target.querySelector('details.selected')?.scrollIntoView();
119
- };
120
-
121
- Lit.render(
122
- html`
123
- <div class="combined-diff-view">
124
- ${input.singleDiffViewInputs.map(singleDiffViewInput => renderSingleDiffView(singleDiffViewInput))}
125
- </div>
126
- `,
127
- target, {host: target});
128
- }) {
127
+ constructor(element?: HTMLElement, view: View = DEFAULT_VIEW) {
129
128
  super(element);
130
129
  this.registerRequiredCSS(combinedDiffViewStyles);
131
130
  this.#view = view;
@@ -6,7 +6,6 @@ import * as Common from '../../core/common/common.js';
6
6
  import * as Host from '../../core/host/host.js';
7
7
  import * as i18n from '../../core/i18n/i18n.js';
8
8
  import * as Badges from '../../models/badges/badges.js';
9
- import * as WindowBoundsService from '../../services/window_bounds/window_bounds.js';
10
9
  import * as Buttons from '../../ui/components/buttons/buttons.js';
11
10
  import * as UI from '../../ui/legacy/legacy.js';
12
11
  import * as Lit from '../../ui/lit/lit.js';
@@ -166,8 +165,7 @@ export class BadgeNotification extends UI.Widget.Widget {
166
165
 
167
166
  #positionNotification(): void {
168
167
  const boundingRect = this.contentElement.getBoundingClientRect();
169
- const container =
170
- WindowBoundsService.WindowBoundsService.WindowBoundsServiceImpl.instance().getDevToolsBoundingElement();
168
+ const container = UI.UIUtils.getDevToolsBoundingElement();
171
169
  this.contentElement.positionAt(
172
170
  LEFT_OFFSET, container.clientHeight - boundingRect.height - BOTTOM_OFFSET, container);
173
171
  }
@@ -124,6 +124,7 @@ export const DEFAULT_VIEW = (input: ViewInput, _output: undefined, target: HTMLE
124
124
  variant="rich"
125
125
  vertical-distance-increase=-6
126
126
  prefer-span-left
127
+ jslogContext="console-insight-teaser"
127
128
  >
128
129
  <div class="teaser-tooltip-container">
129
130
  ${input.isError ? html`
@@ -177,7 +178,7 @@ export const DEFAULT_VIEW = (input: ViewInput, _output: undefined, target: HTMLE
177
178
  aria-details=${'teaser-info-tooltip-' + input.uuid}
178
179
  .accessibleLabel=${lockedString(UIStringsNotTranslate.learnDataUsage)}
179
180
  ></devtools-button>
180
- <devtools-tooltip id=${'teaser-info-tooltip-' + input.uuid} variant="rich">
181
+ <devtools-tooltip id=${'teaser-info-tooltip-' + input.uuid} variant="rich" jslogContext="teaser-info-tooltip">
181
182
  <div class="info-tooltip-text">${lockedString(UIStringsNotTranslate.infoTooltipText)}</div>
182
183
  <div class="learn-more">
183
184
  <x-link
@@ -322,6 +323,7 @@ export class ConsoleInsightTeaser extends UI.Widget.Widget {
322
323
  }
323
324
  if (this.#isGenerating) {
324
325
  this.#mainText = '';
326
+ Host.userMetrics.actionTaken(Host.UserMetrics.Action.InsightTeaserGenerationAborted);
325
327
  }
326
328
  this.#isGenerating = false;
327
329
  if (this.#timeoutId) {
@@ -345,7 +347,9 @@ export class ConsoleInsightTeaser extends UI.Widget.Widget {
345
347
  async #generateTeaserText(): Promise<void> {
346
348
  this.#headerText = this.#consoleViewMessage.toMessageTextString().substring(0, 70);
347
349
  this.#isGenerating = true;
350
+ Host.userMetrics.actionTaken(Host.UserMetrics.Action.InsightTeaserGenerationStarted);
348
351
  this.#timeoutId = setTimeout(this.#setSlow.bind(this), SLOW_GENERATION_CUTOFF_MILLISECONDS);
352
+ const startTime = performance.now();
349
353
  let teaserText = '';
350
354
  try {
351
355
  for await (const chunk of this.#getOnDeviceInsight()) {
@@ -358,6 +362,7 @@ export class ConsoleInsightTeaser extends UI.Widget.Widget {
358
362
  if (err.name !== 'AbortError') {
359
363
  console.error(err.name, err.message);
360
364
  this.#isError = true;
365
+ Host.userMetrics.actionTaken(Host.UserMetrics.Action.InsightTeaserGenerationErrored);
361
366
  }
362
367
  this.#isGenerating = false;
363
368
  clearTimeout(this.#timeoutId);
@@ -366,8 +371,10 @@ export class ConsoleInsightTeaser extends UI.Widget.Widget {
366
371
  }
367
372
 
368
373
  clearTimeout(this.#timeoutId);
374
+ Host.userMetrics.consoleInsightTeaserGenerated(performance.now() - startTime);
369
375
  this.#isGenerating = false;
370
376
  this.#mainText = teaserText;
377
+ Host.userMetrics.actionTaken(Host.UserMetrics.Action.InsightTeaserGenerationCompleted);
371
378
  this.requestUpdate();
372
379
  }
373
380
 
@@ -505,6 +505,7 @@ export class ConsoleView extends UI.Widget.VBox implements
505
505
 
506
506
  this.pinPane = new ConsolePinPane(liveExpressionButton, () => this.prompt.focus());
507
507
  this.pinPane.element.classList.add('console-view-pinpane');
508
+ this.pinPane.element.classList.remove('flex-auto');
508
509
  this.pinPane.show(this.contentsElement);
509
510
 
510
511
  this.viewport = new ConsoleViewport(this);
@@ -527,7 +527,6 @@
527
527
  }
528
528
 
529
529
  .console-view-pinpane {
530
- flex: none;
531
530
  max-height: 50%;
532
531
  }
533
532
 
@@ -422,7 +422,7 @@ export class DOMTreeWidget extends UI.Widget.Widget {
422
422
  if (domModel.parentModel()) {
423
423
  continue;
424
424
  }
425
- if (!this.rootDOMNode) {
425
+ if (!this.rootDOMNode || this.rootDOMNode.domModel() !== domModel) {
426
426
  if (domModel.existingDocument()) {
427
427
  this.rootDOMNode = domModel.existingDocument();
428
428
  this.onDocumentUpdated(domModel);
@@ -2,8 +2,6 @@
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
4
 
5
- /* eslint-disable @devtools/no-lit-render-outside-of-view */
6
-
7
5
  import * as Common from '../../../core/common/common.js';
8
6
  import * as Host from '../../../core/host/host.js';
9
7
  import * as i18n from '../../../core/i18n/i18n.js';
@@ -112,9 +110,9 @@ export interface ViewInput {
112
110
  onCopyRow: () => void;
113
111
  }
114
112
 
115
- export type View = (input: ViewInput, target: HTMLElement) => void;
113
+ export type View = (input: ViewInput, output: undefined, target: HTMLElement) => void;
116
114
 
117
- export const DEFAULT_VIEW: View = (input, target) => {
115
+ export const DEFAULT_VIEW: View = (input, _output, target) => {
118
116
  function isCategoryOpen(name: string): boolean {
119
117
  return input.openCategories.includes(name);
120
118
  }
@@ -203,7 +201,7 @@ export const DEFAULT_VIEW: View = (input, target) => {
203
201
  ${renderCategory(CATEGORY_NAME_GENERAL, i18nString(UIStrings.general), generalContent)}
204
202
  ${renderCategory(CATEGORY_NAME_OPTIONS, i18nString(UIStrings.options), optionsContent)}
205
203
  ${socketInfo.openInfo ? renderCategory(CATEGORY_NAME_OPEN_INFO, i18nString(UIStrings.openInfo), openInfoContent) : Lit.nothing}
206
- `, target, {host: input});
204
+ `, target);
207
205
  // clang-format on
208
206
  };
209
207
 
@@ -278,7 +276,7 @@ export class DirectSocketConnectionView extends UI.Widget.Widget {
278
276
  Host.userMetrics.actionTaken(Host.UserMetrics.Action.NetworkPanelCopyValue);
279
277
  }
280
278
  };
281
- this.#view(viewInput, this.contentElement);
279
+ this.#view(viewInput, undefined, this.contentElement);
282
280
  }
283
281
 
284
282
  #setIsOpen(categoryName: string, open: boolean): void {
@@ -29,7 +29,6 @@ import {
29
29
  type HeaderEditedEvent,
30
30
  type HeaderEditorDescriptor,
31
31
  type HeaderRemovedEvent,
32
- type HeaderSectionRow,
33
32
  type HeaderSectionRowData,
34
33
  isValidHeaderName,
35
34
  } from './HeaderSectionRow.js';
@@ -515,7 +514,7 @@ export class ResponseHeaderSection extends ResponseHeaderSectionBase {
515
514
  this.#updateOverrides(this.#headerEditors[index].name, this.#headerEditors[index].value || '', index);
516
515
  this.#render();
517
516
 
518
- const rows = this.shadow.querySelectorAll<HeaderSectionRow>('devtools-header-section-row');
517
+ const rows = this.shadow.querySelectorAll('devtools-header-section-row');
519
518
  const [lastRow] = Array.from(rows).slice(-1);
520
519
  lastRow?.focus();
521
520
  Host.userMetrics.actionTaken(Host.UserMetrics.Action.HeaderOverrideHeaderAdded);