chrome-devtools-frontend 1.0.1605390 → 1.0.1606789

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 (60) hide show
  1. package/front_end/core/host/UserMetrics.ts +0 -1
  2. package/front_end/core/platform/api/HostRuntime.ts +9 -6
  3. package/front_end/core/platform/browser/HostRuntime.ts +2 -2
  4. package/front_end/core/platform/node/HostRuntime.ts +7 -7
  5. package/front_end/core/protocol_client/InspectorBackend.ts +4 -0
  6. package/front_end/core/root/ExperimentNames.ts +0 -1
  7. package/front_end/core/sdk/CrashReportContextModel.ts +28 -0
  8. package/front_end/core/sdk/sdk.ts +2 -2
  9. package/front_end/entrypoints/heap_snapshot_worker/HeapSnapshotWorkerDispatcher.ts +3 -3
  10. package/front_end/entrypoints/heap_snapshot_worker/heap_snapshot_worker-entrypoint.ts +5 -4
  11. package/front_end/entrypoints/main/MainImpl.ts +0 -101
  12. package/front_end/models/ai_assistance/ChangeManager.ts +6 -6
  13. package/front_end/models/ai_assistance/agents/AccessibilityAgent.ts +112 -5
  14. package/front_end/models/ai_assistance/agents/AiAgent.ts +0 -24
  15. package/front_end/models/ai_assistance/agents/ContextSelectionAgent.snapshot.txt +2 -2
  16. package/front_end/models/ai_assistance/agents/ContextSelectionAgent.ts +6 -3
  17. package/front_end/models/ai_assistance/agents/ExecuteJavascript.ts +13 -1
  18. package/front_end/models/ai_assistance/agents/FileAgent.ts +15 -1
  19. package/front_end/models/ai_assistance/agents/NetworkAgent.ts +15 -1
  20. package/front_end/models/ai_assistance/agents/StylingAgent.ts +24 -15
  21. package/front_end/models/ai_assistance/ai_assistance.ts +0 -2
  22. package/front_end/models/javascript_metadata/NativeFunctions.js +1 -5
  23. package/front_end/models/web_mcp/WebMCPModel.ts +187 -0
  24. package/front_end/models/web_mcp/web_mcp.ts +9 -0
  25. package/front_end/panels/ai_assistance/AiAssistancePanel.ts +8 -1
  26. package/front_end/panels/ai_assistance/ai_assistance.ts +1 -0
  27. package/front_end/panels/ai_assistance/components/AccessibilityAgentMarkdownRenderer.ts +88 -0
  28. package/front_end/panels/ai_assistance/components/ChatMessage.ts +19 -3
  29. package/front_end/panels/ai_assistance/components/ChatView.ts +11 -4
  30. package/front_end/panels/ai_assistance/components/ExportForAgentsDialog.ts +12 -3
  31. package/front_end/panels/ai_assistance/components/chatMessage.css +10 -0
  32. package/front_end/panels/ai_assistance/components/chatView.css +0 -2
  33. package/front_end/panels/ai_assistance/components/exportForAgentsDialog.css +13 -0
  34. package/front_end/panels/application/WebMCPView.ts +126 -30
  35. package/front_end/panels/application/webMCPView.css +28 -2
  36. package/front_end/panels/elements/ElementsTreeElement.ts +1 -1
  37. package/front_end/panels/elements/StylePropertiesSection.ts +0 -4
  38. package/front_end/panels/elements/elements.ts +3 -0
  39. package/front_end/panels/elements/stylePropertiesTreeOutline.css +7 -0
  40. package/front_end/panels/lighthouse/LighthousePanel.ts +7 -1
  41. package/front_end/panels/lighthouse/LighthouseProtocolService.ts +25 -2
  42. package/front_end/panels/profiler/HeapSnapshotProxy.ts +2 -6
  43. package/front_end/panels/profiler/HeapSnapshotView.ts +7 -16
  44. package/front_end/panels/profiler/ProfileHeader.ts +1 -4
  45. package/front_end/panels/protocol_monitor/ProtocolMonitor.ts +27 -22
  46. package/front_end/panels/timeline/TimelinePanel.ts +0 -153
  47. package/front_end/panels/timeline/TimelineTreeView.ts +11 -1
  48. package/front_end/panels/timeline/TimelineUIUtils.ts +2 -1
  49. package/front_end/third_party/chromium/README.chromium +1 -1
  50. package/front_end/ui/components/markdown_view/CodeBlock.ts +47 -1
  51. package/front_end/ui/components/markdown_view/codeBlock.css +8 -0
  52. package/front_end/ui/legacy/TabbedPane.ts +123 -3
  53. package/front_end/ui/legacy/Widget.ts +67 -28
  54. package/front_end/ui/legacy/components/source_frame/JSONView.ts +1 -1
  55. package/front_end/ui/legacy/components/utils/jsUtils.css +2 -0
  56. package/front_end/ui/legacy/infobar.css +1 -0
  57. package/front_end/ui/visual_logging/KnownContextValues.ts +0 -1
  58. package/package.json +1 -1
  59. package/front_end/core/sdk/WebMCPModel.ts +0 -159
  60. package/front_end/models/ai_assistance/ConversationHandler.ts +0 -325
@@ -2,6 +2,8 @@
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 '../../../ui/components/spinners/spinners.js';
6
+
5
7
  import * as Host from '../../../core/host/host.js';
6
8
  import * as i18n from '../../../core/i18n/i18n.js';
7
9
  import * as Buttons from '../../../ui/components/buttons/buttons.js';
@@ -80,6 +82,7 @@ export const DEFAULT_VIEW: View = (input, _output, target): void => {
80
82
  i18nString(UIStrings.generatingSummary) :
81
83
  (isPrompt ? input.state.promptText : input.state.conversationText);
82
84
  // clang-format off
85
+
83
86
  render(html`
84
87
  <style>${styles}</style>
85
88
  <div class="export-for-agents-dialog">
@@ -111,9 +114,15 @@ export const DEFAULT_VIEW: View = (input, _output, target): void => {
111
114
  </label>
112
115
  </div>
113
116
  <main>
114
- <textarea readonly .value=${exportText}></textarea>
117
+ ${input.state.isPromptLoading ? html`
118
+ <span class="prompt-loading">
119
+ <devtools-spinner></devtools-spinner>
120
+ ${i18nString(UIStrings.generatingSummary)}
121
+ </span>
122
+ ` : Lit.nothing}
123
+ <textarea readonly .value=${input.state.isPromptLoading ? '' : exportText}></textarea>
115
124
  </main>
116
- ${isPrompt ? html`<div class="disclaimer">${i18nString(UIStrings.disclaimer)}</div>` : Lit.nothing}
125
+ <div class="disclaimer">${i18nString(UIStrings.disclaimer)}</div>
117
126
  <footer>
118
127
  <div class="right-buttons">
119
128
  <devtools-button
@@ -156,7 +165,7 @@ export class ExportForAgentsDialog extends UI.Widget.VBox {
156
165
  this.#onConversationSaveAs = options.onConversationSaveAs;
157
166
  this.#view = view;
158
167
 
159
- if (options.promptText instanceof Promise) {
168
+ if (typeof options.promptText !== 'string') {
160
169
  void options.promptText.then(promptText => {
161
170
  this.#state.promptText = promptText;
162
171
  this.#state.isPromptLoading = false;
@@ -12,6 +12,7 @@
12
12
  justify-content: space-between;
13
13
  align-items: center;
14
14
  margin-block: calc(-1 * var(--sys-size-3));
15
+ margin-top: var(--sys-size-5);
15
16
 
16
17
  &.not-v2 {
17
18
  /* Can be removed when AIv2 ships */
@@ -126,6 +127,7 @@
126
127
  .user-query-wrapper {
127
128
  display: flex;
128
129
  justify-content: flex-end;
130
+ padding: 0 var(--sys-size-5);
129
131
  align-items: center;
130
132
  }
131
133
 
@@ -165,6 +167,10 @@
165
167
  border-bottom: none;
166
168
  }
167
169
 
170
+ .ai-css-change {
171
+ margin: var(--sys-size-6) 0;
172
+ }
173
+
168
174
  &.ai-v2 .answer-body-wrapper {
169
175
  @container(min-width: 700px) {
170
176
  /* Purposefully not using design system variables, this is a
@@ -437,6 +443,10 @@
437
443
 
438
444
  .widget-name {
439
445
  font: var(--sys-typescale-body4-regular);
446
+ max-width: 80%;
447
+ overflow: hidden;
448
+ text-overflow: ellipsis;
449
+ white-space: nowrap; /* stop the titles going onto multiple lines */
440
450
  }
441
451
 
442
452
  .widget-reveal-container {
@@ -62,8 +62,6 @@
62
62
  }
63
63
  }
64
64
 
65
-
66
-
67
65
  .link {
68
66
  color: var(--text-link);
69
67
  text-decoration: underline;
@@ -63,6 +63,19 @@
63
63
  border: none;
64
64
  }
65
65
 
66
+ main {
67
+ position: relative;
68
+ }
69
+
70
+ .prompt-loading {
71
+ position: absolute;
72
+ padding: var(--sys-size-5);
73
+ display: flex;
74
+ align-items: center;
75
+ justify-content: flex-start;
76
+ gap: var(--sys-size-5);
77
+ }
78
+
66
79
  .export-for-agents-dialog .disclaimer {
67
80
  margin-top: var(--sys-size-5);
68
81
  font: var(--sys-typescale-body4-regular);
@@ -11,8 +11,10 @@ import * as i18n from '../../core/i18n/i18n.js';
11
11
  import * as Platform from '../../core/platform/platform.js';
12
12
  import * as SDK from '../../core/sdk/sdk.js';
13
13
  import * as Protocol from '../../generated/protocol.js';
14
+ import * as WebMCP from '../../models/web_mcp/web_mcp.js';
14
15
  import * as Adorners from '../../ui/components/adorners/adorners.js';
15
16
  import * as Buttons from '../../ui/components/buttons/buttons.js';
17
+ import type * as IconButton from '../../ui/components/icon_button/icon_button.js';
16
18
  import * as UI from '../../ui/legacy/legacy.js';
17
19
  import {
18
20
  Directives,
@@ -109,6 +111,26 @@ const UIStrings = {
109
111
  * @description Text for the status of a tool call that has failed
110
112
  */
111
113
  pending: 'In Progress',
114
+ /**
115
+ * @description Text for the total number of tool calls
116
+ * @example {2} PH1
117
+ */
118
+ totalCalls: '{PH1} Total calls',
119
+ /**
120
+ * @description Text for the number of failed tool calls
121
+ * @example {1} PH1
122
+ */
123
+ failed: '{PH1} Failed',
124
+ /**
125
+ * @description Text for the number of canceled tool calls
126
+ * @example {1} PH1
127
+ */
128
+ canceledCount: '{PH1} Canceled',
129
+ /**
130
+ * @description Text for the number of in progress tool calls
131
+ * @example {1} PH1
132
+ */
133
+ inProgressCount: '{PH1} In Progress',
112
134
  } as const;
113
135
  const str_ = i18n.i18n.registerUIStrings('panels/application/WebMCPView.ts', UIStrings);
114
136
  const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
@@ -136,15 +158,16 @@ export interface FilterMenuButtons {
136
158
  statusTypes: FilterMenuButton;
137
159
  }
138
160
  export interface ViewInput {
139
- tools: Protocol.WebMCP.Tool[];
161
+ tools: WebMCP.WebMCPModel.Tool[];
140
162
  filters: FilterState;
141
163
  filterButtons: FilterMenuButtons;
142
164
  onClearLogClick: () => void;
143
165
  onFilterChange: (filters: FilterState) => void;
144
- toolCalls: SDK.WebMCPModel.Call[];
166
+ toolCalls: WebMCP.WebMCPModel.Call[];
145
167
  }
146
168
 
147
- export function filterToolCalls(toolCalls: SDK.WebMCPModel.Call[], filterState: FilterState): SDK.WebMCPModel.Call[] {
169
+ export function filterToolCalls(
170
+ toolCalls: WebMCP.WebMCPModel.Call[], filterState: FilterState): WebMCP.WebMCPModel.Call[] {
148
171
  let filtered = [...toolCalls];
149
172
 
150
173
  const statusTypes = filterState.statusTypes;
@@ -168,11 +191,10 @@ export function filterToolCalls(toolCalls: SDK.WebMCPModel.Call[], filterState:
168
191
  if (toolTypes) {
169
192
  filtered = filtered.filter(call => {
170
193
  const {imperative, declarative} = toolTypes;
171
- const isDeclarative = call.tool?.backendNodeId !== undefined;
172
- if (imperative && !isDeclarative) {
194
+ if (imperative && !call.tool.isDeclarative) {
173
195
  return true;
174
196
  }
175
- if (declarative && isDeclarative) {
197
+ if (declarative && call.tool.isDeclarative) {
176
198
  return true;
177
199
  }
178
200
  return false;
@@ -190,13 +212,71 @@ export function filterToolCalls(toolCalls: SDK.WebMCPModel.Call[], filterState:
190
212
 
191
213
  return filtered;
192
214
  }
193
-
194
215
  export type View = (input: ViewInput, output: object, target: HTMLElement) => void;
216
+ function calculateToolStats(calls: WebMCP.WebMCPModel.Call[]):
217
+ {total: number, success: number, failed: number, canceled: number, inProgress: number} {
218
+ let total = 0, success = 0, failed = 0, canceled = 0, inProgress = 0;
219
+ for (const call of calls) {
220
+ total++;
221
+ if (call.result?.status === Protocol.WebMCP.InvocationStatus.Error) {
222
+ failed++;
223
+ } else if (call.result?.status === Protocol.WebMCP.InvocationStatus.Canceled) {
224
+ canceled++;
225
+ } else if (call.result?.status === Protocol.WebMCP.InvocationStatus.Success) {
226
+ success++;
227
+ } else if (call.result === undefined) {
228
+ inProgress++;
229
+ }
230
+ }
231
+ return {total, success, failed, canceled, inProgress};
232
+ }
233
+
234
+ function getIconGroupsFromStats(toolStats: ReturnType<typeof calculateToolStats>):
235
+ IconButton.IconButton.IconWithTextData[] {
236
+ const groups = [];
237
+ if (toolStats.success > 0) {
238
+ groups.push({
239
+ iconName: 'check-circle',
240
+ iconColor: 'var(--sys-color-green)',
241
+ iconWidth: '16px',
242
+ iconHeight: '16px',
243
+ text: String(toolStats.success),
244
+ });
245
+ }
246
+ if (toolStats.failed > 0) {
247
+ groups.push({
248
+ iconName: 'cross-circle-filled',
249
+ iconColor: 'var(--sys-color-error)',
250
+ iconWidth: '16px',
251
+ iconHeight: '16px',
252
+ text: String(toolStats.failed),
253
+ });
254
+ }
255
+ if (toolStats.canceled > 0) {
256
+ groups.push({
257
+ iconName: 'record-stop',
258
+ iconColor: 'var(--sys-color-on-surface-light)',
259
+ iconWidth: '16px',
260
+ iconHeight: '16px',
261
+ text: String(toolStats.canceled),
262
+ });
263
+ }
264
+ if (toolStats.inProgress > 0) {
265
+ groups.push({
266
+ iconName: 'dots-circle',
267
+ iconWidth: '16px',
268
+ iconHeight: '16px',
269
+ text: String(toolStats.inProgress),
270
+ });
271
+ }
272
+ return groups;
273
+ }
195
274
  export const DEFAULT_VIEW: View = (input, output, target) => {
196
275
  const tools = input.tools;
276
+ const stats = calculateToolStats(input.toolCalls);
197
277
  const isFilterActive =
198
278
  Boolean(input.filters.text) || Boolean(input.filters.toolTypes) || Boolean(input.filters.statusTypes);
199
- const iconName = (call: SDK.WebMCPModel.Call): string => {
279
+ const iconName = (call: WebMCP.WebMCPModel.Call): string => {
200
280
  switch (call.result?.status) {
201
281
  case Protocol.WebMCP.InvocationStatus.Error:
202
282
  return 'cross-circle-filled';
@@ -208,7 +288,7 @@ export const DEFAULT_VIEW: View = (input, output, target) => {
208
288
  return '';
209
289
  }
210
290
  };
211
- const statusString = (call: SDK.WebMCPModel.Call): string => {
291
+ const statusString = (call: WebMCP.WebMCPModel.Call): string => {
212
292
  switch (call.result?.status) {
213
293
  case Protocol.WebMCP.InvocationStatus.Error:
214
294
  return i18nString(UIStrings.error);
@@ -282,6 +362,18 @@ export const DEFAULT_VIEW: View = (input, output, target) => {
282
362
  `)}
283
363
  </table>
284
364
  </devtools-data-grid>
365
+ <div class="webmcp-toolbar-container" role="toolbar">
366
+ <devtools-toolbar class="webmcp-toolbar" role="presentation" wrappable>
367
+ <span class="toolbar-text">${i18nString(UIStrings.totalCalls, {PH1: stats.total})}</span>
368
+ <div class="toolbar-divider"></div>
369
+ <span class="toolbar-text status-error-text">${i18nString(UIStrings.failed, {PH1: stats.failed})}</span>
370
+ <div class="toolbar-divider"></div>
371
+ <span class="toolbar-text status-cancelled-text">${
372
+ i18nString(UIStrings.canceledCount, {PH1: stats.canceled})}</span>
373
+ <div class="toolbar-divider"></div>
374
+ <span class="toolbar-text">${i18nString(UIStrings.inProgressCount, {PH1: stats.inProgress})}</span>
375
+ </devtools-toolbar>
376
+ </div>
285
377
  ` : html`
286
378
  ${UI.Widget.widget(UI.EmptyWidget.EmptyWidget, {header: i18nString(UIStrings.noCallsPlaceholderTitle),
287
379
  text: i18nString(UIStrings.noCallsPlaceholder)})}
@@ -294,14 +386,20 @@ export const DEFAULT_VIEW: View = (input, output, target) => {
294
386
  text: i18nString(UIStrings.noToolsPlaceholder)})}
295
387
  ` : html`
296
388
  <devtools-list>
297
- ${tools.map(tool => html`
389
+ ${tools.map(tool => {
390
+ const toolStats = calculateToolStats(input.toolCalls.filter(c => c.tool === tool));
391
+ const groups = getIconGroupsFromStats(toolStats);
392
+ return html`
298
393
  <div class="tool-item">
299
394
  <div class="tool-name-container">
300
395
  <div class="tool-name source-code">${tool.name}</div>
396
+ ${groups.length > 0 ? html`<icon-button .data=${
397
+ {groups, compact: false} as IconButton.IconButton.IconButtonData}></icon-button>` : ''}
301
398
  </div>
302
399
  <div class="tool-description">${tool.description}</div>
303
400
  </div>
304
- `)}
401
+ `;
402
+ })}
305
403
  </devtools-list>
306
404
  `}
307
405
  </div>
@@ -361,9 +459,9 @@ export class WebMCPView extends UI.Widget.VBox {
361
459
  this.#showToolTypesContextMenu.bind(this),
362
460
  this.#showStatusTypesContextMenu.bind(this),
363
461
  );
364
- SDK.TargetManager.TargetManager.instance().observeModels(SDK.WebMCPModel.WebMCPModel, {
365
- modelAdded: (model: SDK.WebMCPModel.WebMCPModel) => this.#webMCPModelAdded(model),
366
- modelRemoved: (model: SDK.WebMCPModel.WebMCPModel) => this.#webMCPModelRemoved(model),
462
+ SDK.TargetManager.TargetManager.instance().observeModels(WebMCP.WebMCPModel.WebMCPModel, {
463
+ modelAdded: (model: WebMCP.WebMCPModel.WebMCPModel) => this.#webMCPModelAdded(model),
464
+ modelRemoved: (model: WebMCP.WebMCPModel.WebMCPModel) => this.#webMCPModelRemoved(model),
367
465
  });
368
466
  this.requestUpdate();
369
467
  }
@@ -408,22 +506,22 @@ export class WebMCPView extends UI.Widget.VBox {
408
506
  i18nString(UIStrings.pending), () => toggle('pending'),
409
507
  {checked: this.#filterState.statusTypes?.['pending'] ?? false, jslogContext: 'webmcp.pending'});
410
508
  }
411
- #webMCPModelAdded(model: SDK.WebMCPModel.WebMCPModel): void {
412
- model.addEventListener(SDK.WebMCPModel.Events.TOOLS_ADDED, this.requestUpdate, this);
413
- model.addEventListener(SDK.WebMCPModel.Events.TOOLS_REMOVED, this.requestUpdate, this);
414
- model.addEventListener(SDK.WebMCPModel.Events.TOOL_INVOKED, this.requestUpdate, this);
415
- model.addEventListener(SDK.WebMCPModel.Events.TOOL_RESPONDED, this.requestUpdate, this);
509
+ #webMCPModelAdded(model: WebMCP.WebMCPModel.WebMCPModel): void {
510
+ model.addEventListener(WebMCP.WebMCPModel.Events.TOOLS_ADDED, this.requestUpdate, this);
511
+ model.addEventListener(WebMCP.WebMCPModel.Events.TOOLS_REMOVED, this.requestUpdate, this);
512
+ model.addEventListener(WebMCP.WebMCPModel.Events.TOOL_INVOKED, this.requestUpdate, this);
513
+ model.addEventListener(WebMCP.WebMCPModel.Events.TOOL_RESPONDED, this.requestUpdate, this);
416
514
  }
417
515
 
418
- #webMCPModelRemoved(model: SDK.WebMCPModel.WebMCPModel): void {
419
- model.removeEventListener(SDK.WebMCPModel.Events.TOOLS_ADDED, this.requestUpdate, this);
420
- model.removeEventListener(SDK.WebMCPModel.Events.TOOLS_REMOVED, this.requestUpdate, this);
421
- model.removeEventListener(SDK.WebMCPModel.Events.TOOL_INVOKED, this.requestUpdate, this);
422
- model.removeEventListener(SDK.WebMCPModel.Events.TOOL_RESPONDED, this.requestUpdate, this);
516
+ #webMCPModelRemoved(model: WebMCP.WebMCPModel.WebMCPModel): void {
517
+ model.removeEventListener(WebMCP.WebMCPModel.Events.TOOLS_ADDED, this.requestUpdate, this);
518
+ model.removeEventListener(WebMCP.WebMCPModel.Events.TOOLS_REMOVED, this.requestUpdate, this);
519
+ model.removeEventListener(WebMCP.WebMCPModel.Events.TOOL_INVOKED, this.requestUpdate, this);
520
+ model.removeEventListener(WebMCP.WebMCPModel.Events.TOOL_RESPONDED, this.requestUpdate, this);
423
521
  }
424
522
 
425
523
  #handleClearLogClick = (): void => {
426
- const models = SDK.TargetManager.TargetManager.instance().models(SDK.WebMCPModel.WebMCPModel);
524
+ const models = SDK.TargetManager.TargetManager.instance().models(WebMCP.WebMCPModel.WebMCPModel);
427
525
  for (const model of models) {
428
526
  model.clearCalls();
429
527
  }
@@ -444,17 +542,15 @@ export class WebMCPView extends UI.Widget.VBox {
444
542
  this.requestUpdate();
445
543
  };
446
544
 
447
- #getTools(): Protocol.WebMCP.Tool[] {
448
- const models = SDK.TargetManager.TargetManager.instance().models(SDK.WebMCPModel.WebMCPModel);
545
+ #getTools(): WebMCP.WebMCPModel.Tool[] {
546
+ const models = SDK.TargetManager.TargetManager.instance().models(WebMCP.WebMCPModel.WebMCPModel);
449
547
  const tools = models.flatMap(model => model.tools.toArray());
450
548
  return tools.sort((a, b) => a.name.localeCompare(b.name));
451
549
  }
452
-
453
550
  override performUpdate(): void {
454
- const models = SDK.TargetManager.TargetManager.instance().models(SDK.WebMCPModel.WebMCPModel);
551
+ const models = SDK.TargetManager.TargetManager.instance().models(WebMCP.WebMCPModel.WebMCPModel);
455
552
  const toolCalls = models.flatMap(model => model.toolCalls);
456
553
  const filteredCalls = filterToolCalls(toolCalls, this.#filterState);
457
-
458
554
  const input: ViewInput = {
459
555
  tools: this.#getTools(),
460
556
  toolCalls: filteredCalls,
@@ -49,7 +49,9 @@
49
49
 
50
50
  devtools-list {
51
51
  flex: 1 1 auto;
52
- margin: var(--sys-size-4);
52
+ margin: 0;
53
+ padding: var(--sys-size-4);
54
+ box-sizing: border-box;
53
55
  }
54
56
 
55
57
  .tool-item {
@@ -58,17 +60,33 @@
58
60
  padding: 8px 0;
59
61
  gap: 4px;
60
62
  width: 100%;
63
+ min-width: 0;
61
64
  border-bottom: 1px solid var(--sys-color-divider);
62
65
  }
63
66
 
64
67
  .tool-name-container {
65
68
  display: flex;
66
69
  justify-content: space-between;
67
- align-items: center;
70
+ align-items: flex-start;
71
+ gap: 8px;
68
72
  }
69
73
 
70
74
  .tool-name.source-code {
71
75
  color: var(--sys-color-token-string);
76
+ white-space: nowrap;
77
+ overflow: hidden;
78
+ text-overflow: ellipsis;
79
+ min-width: 0;
80
+ flex: 1;
81
+ }
82
+
83
+ /* stylelint-disable-next-line selector-type-no-unknown */
84
+ .tool-name-container icon-button {
85
+ flex-shrink: 0;
86
+ height: 0;
87
+ overflow: visible;
88
+ display: flex;
89
+ align-items: center;
72
90
  }
73
91
 
74
92
  .tool-description {
@@ -87,4 +105,12 @@
87
105
  tr.status-cancelled {
88
106
  color: var(--sys-color-on-surface-light);
89
107
  }
108
+
109
+ .toolbar-text.status-error-text {
110
+ color: var(--sys-color-error);
111
+ }
112
+
113
+ .toolbar-text.status-cancelled-text {
114
+ color: var(--sys-color-on-surface-light);
115
+ }
90
116
  }
@@ -878,7 +878,7 @@ function maybeRenderAdAdorner(input: ViewInput): Lit.TemplateResult|typeof nothi
878
878
  // clang-format off
879
879
  return html`
880
880
  <devtools-adorner
881
- aria-describedby=${input.adTooltipId}
881
+ aria-details=${input.adTooltipId}
882
882
  aria-label=${i18nString(UIStrings.thisElementWasIdentifiedAsAnAd)}
883
883
  .name=${ElementsComponents.AdornerManager.RegisteredAdorners.AD}
884
884
  jslog=${VisualLogging.adorner(ElementsComponents.AdornerManager.RegisteredAdorners.AD)}
@@ -1075,10 +1075,6 @@ export class StylePropertiesSection {
1075
1075
 
1076
1076
  protected createContainerQueryElement(containerQuery: SDK.CSSContainerQuery.CSSContainerQuery):
1077
1077
  ElementsComponents.CSSQuery.CSSQuery|undefined {
1078
- if (!containerQuery.text) {
1079
- return;
1080
- }
1081
-
1082
1078
  let onQueryTextClick;
1083
1079
  if (containerQuery.styleSheetId) {
1084
1080
  onQueryTextClick = this.handleQueryRuleClick.bind(this, containerQuery);
@@ -10,6 +10,7 @@ import './ElementsTreeElement.js';
10
10
  import './AdoptedStyleSheetTreeElement.js';
11
11
  import './TopLayerContainer.js';
12
12
  import './ElementsTreeOutline.js';
13
+ import './ImagePreviewPopover.js';
13
14
  import './EventListenersWidget.js';
14
15
  import './MarkerDecorator.js';
15
16
  import './MetricsSidebarPane.js';
@@ -45,6 +46,7 @@ import * as ElementsTreeElement from './ElementsTreeElement.js';
45
46
  import * as ElementsTreeOutline from './ElementsTreeOutline.js';
46
47
  import * as ElementsTreeOutlineRenderer from './ElementsTreeOutlineRenderer.js';
47
48
  import * as EventListenersWidget from './EventListenersWidget.js';
49
+ import * as ImagePreviewPopover from './ImagePreviewPopover.js';
48
50
  import * as InspectElementModeController from './InspectElementModeController.js';
49
51
  import * as LayersWidget from './LayersWidget.js';
50
52
  import * as LayoutPane from './LayoutPane.js';
@@ -82,6 +84,7 @@ export {
82
84
  ElementsTreeOutline,
83
85
  ElementsTreeOutlineRenderer,
84
86
  EventListenersWidget,
87
+ ImagePreviewPopover,
85
88
  InspectElementModeController,
86
89
  LayersWidget,
87
90
  LayoutPane,
@@ -21,6 +21,7 @@
21
21
  padding-left: 22px;
22
22
  white-space: normal;
23
23
  text-overflow: ellipsis;
24
+ overflow-wrap: break-word;
24
25
  cursor: auto;
25
26
  display: block;
26
27
 
@@ -74,6 +75,12 @@
74
75
  overflow-wrap: break-word !important; /* stylelint-disable-line declaration-no-important */
75
76
  white-space: normal !important; /* stylelint-disable-line declaration-no-important */
76
77
  padding-left: 0;
78
+
79
+ /* Constrain the TextPrompt proxy span (display: inline-block) so that
80
+ long values wrap instead of overflowing the section. */
81
+ > span:has(> .text-prompt-root) {
82
+ max-width: 100%;
83
+ }
77
84
  }
78
85
 
79
86
  .info {
@@ -19,7 +19,7 @@ import {
19
19
  type PageWarningsChangedEvent
20
20
  } from './LighthouseController.js';
21
21
  import lighthousePanelStyles from './lighthousePanel.css.js';
22
- import {ProtocolService} from './LighthouseProtocolService.js';
22
+ import {CancelledError, ProtocolService} from './LighthouseProtocolService.js';
23
23
  import {LighthouseReportRenderer} from './LighthouseReportRenderer.js';
24
24
  import {Item, ReportSelector} from './LighthouseReportSelector.js';
25
25
  import {StartView} from './LighthouseStartView.js';
@@ -176,6 +176,12 @@ export class LighthousePanel extends UI.Panel.Panel {
176
176
  }
177
177
 
178
178
  private handleError(err: unknown): void {
179
+ if (err instanceof CancelledError) {
180
+ // If the error was an explicit choice to cancel, we don't want to render
181
+ // the bug report view.
182
+ return;
183
+ }
184
+
179
185
  if (err instanceof Error) {
180
186
  this.statusView.renderBugReport(err);
181
187
  }
@@ -58,6 +58,12 @@ export interface LighthouseRun {
58
58
  isAIControlled?: boolean;
59
59
  }
60
60
 
61
+ export class CancelledError extends Error {
62
+ constructor() {
63
+ super('Lighthouse run cancelled');
64
+ }
65
+ }
66
+
61
67
  /**
62
68
  * ProtocolService manages a connection between the frontend (Lighthouse panel) and the Lighthouse worker.
63
69
  */
@@ -71,6 +77,14 @@ export class ProtocolService implements ProtocolClient.CDPConnection.CDPConnecti
71
77
  private configForTesting?: object;
72
78
  private connection?: ProtocolClient.CDPConnection.CDPConnection;
73
79
 
80
+ /**
81
+ * Tracks pending requests to the Lighthouse worker.
82
+ * Key: The message ID sent to the worker.
83
+ * Value: The rejection function for the corresponding promise.
84
+ * This is used to gracefully cancel hanging promises if the ProtocolService is detached before the worker replies.
85
+ */
86
+ readonly #pendingRequests = new Map<number, (reason: Error) => void>();
87
+
74
88
  async attach(): Promise<void> {
75
89
  await SDK.TargetManager.TargetManager.instance().suspendAllTargets();
76
90
  const mainTarget = SDK.TargetManager.TargetManager.instance().primaryPageTarget();
@@ -174,6 +188,10 @@ export class ProtocolService implements ProtocolClient.CDPConnection.CDPConnecti
174
188
  }
175
189
 
176
190
  async detach(): Promise<void> {
191
+ for (const reject of this.#pendingRequests.values()) {
192
+ reject(new CancelledError());
193
+ }
194
+ this.#pendingRequests.clear();
177
195
  const oldLighthouseWorker = this.lighthouseWorkerPromise;
178
196
  const oldRootTarget = this.rootTarget;
179
197
 
@@ -245,7 +263,7 @@ export class ProtocolService implements ProtocolClient.CDPConnection.CDPConnecti
245
263
  return this.lighthouseWorkerPromise;
246
264
  }
247
265
 
248
- private async ensureWorkerExists(): Promise<Worker> {
266
+ async ensureWorkerExists(): Promise<Worker> {
249
267
  let worker: Worker;
250
268
  if (!this.lighthouseWorkerPromise) {
251
269
  worker = await this.initWorker();
@@ -293,16 +311,21 @@ export class ProtocolService implements ProtocolClient.CDPConnection.CDPConnecti
293
311
  Promise<LighthouseModel.ReporterTypes.RunnerResult> {
294
312
  const worker = await this.ensureWorkerExists();
295
313
  const messageId = lastId++;
296
- const messageResult = new Promise<LighthouseModel.ReporterTypes.RunnerResult>(resolve => {
314
+ const messageResult = new Promise<LighthouseModel.ReporterTypes.RunnerResult>((resolve, reject) => {
297
315
  const workerListener = (event: MessageEvent): void => {
298
316
  const lighthouseMessage = event.data;
299
317
 
300
318
  if (lighthouseMessage.id === messageId) {
301
319
  worker.removeEventListener('message', workerListener);
320
+ this.#pendingRequests.delete(messageId);
302
321
  resolve(lighthouseMessage.result);
303
322
  }
304
323
  };
305
324
  worker.addEventListener('message', workerListener);
325
+ this.#pendingRequests.set(messageId, (err: Error) => {
326
+ worker.removeEventListener('message', workerListener);
327
+ reject(err);
328
+ });
306
329
  });
307
330
  worker.postMessage({id: messageId, action, args: {...args, id: messageId}});
308
331
 
@@ -23,9 +23,7 @@ export class HeapSnapshotWorkerProxy extends Common.ObjectWrapper.ObjectWrapper<
23
23
  readonly eventHandler: (arg0: string, arg1: string) => void;
24
24
  nextObjectId = 1;
25
25
  nextCallId = 1;
26
- // TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration)
27
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
28
- callbacks = new Map<number, (arg0: any) => void>();
26
+ callbacks = new Map<number, (...args: any[]) => void>();
29
27
  readonly previousCallbacks = new Set<number>();
30
28
  readonly worker: PlatformApi.HostRuntime.Worker;
31
29
  interval?: number;
@@ -53,9 +51,7 @@ export class HeapSnapshotWorkerProxy extends Common.ObjectWrapper.ObjectWrapper<
53
51
 
54
52
  dispose(): void {
55
53
  this.worker.terminate();
56
- if (this.interval) {
57
- clearInterval(this.interval);
58
- }
54
+ clearInterval(this.interval);
59
55
  }
60
56
 
61
57
  disposeObject(objectId: number): void {