chrome-devtools-frontend 1.0.1595925 → 1.0.1596260

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 (42) hide show
  1. package/.stylelintrc.json +3 -1
  2. package/docs/ui_engineering.md +0 -36
  3. package/front_end/core/host/UserMetrics.ts +0 -1
  4. package/front_end/core/root/ExperimentNames.ts +0 -1
  5. package/front_end/core/sdk/DOMModel.ts +206 -0
  6. package/front_end/core/sdk/SourceMapScopesInfo.ts +1 -1
  7. package/front_end/entrypoints/greendev_floaty/floaty.css +2 -2
  8. package/front_end/entrypoints/main/MainImpl.ts +0 -5
  9. package/front_end/models/ai_assistance/ConversationHandler.ts +1 -1
  10. package/front_end/models/ai_code_completion/AiCodeCompletion.ts +1 -1
  11. package/front_end/panels/ai_assistance/AiAssistancePanel.ts +2 -0
  12. package/front_end/panels/ai_assistance/components/ChatMessage.ts +48 -31
  13. package/front_end/panels/ai_assistance/components/WalkthroughView.ts +28 -4
  14. package/front_end/panels/ai_assistance/components/chatMessage.css +4 -2
  15. package/front_end/panels/application/IndexedDBModel.ts +2 -4
  16. package/front_end/panels/application/ServiceWorkersView.ts +3 -11
  17. package/front_end/panels/developer_resources/DeveloperResourcesListView.ts +1 -1
  18. package/front_end/panels/elements/ComputedStyleWidget.ts +25 -16
  19. package/front_end/panels/elements/ElementsPanel.ts +0 -1
  20. package/front_end/panels/elements/StylePropertyTreeElement.ts +11 -5
  21. package/front_end/panels/elements/nodeStackTraceWidget.css +1 -1
  22. package/front_end/panels/emulation/DeviceModeToolbar.ts +171 -89
  23. package/front_end/panels/greendev/GreenDevPanel.css +2 -2
  24. package/front_end/panels/issues/AffectedPermissionElementsView.ts +9 -6
  25. package/front_end/panels/profiler/HeapSnapshotGridNodes.ts +11 -3
  26. package/front_end/panels/settings/components/SyncSection.ts +4 -13
  27. package/front_end/panels/timeline/CompatibilityTracksAppender.ts +2 -15
  28. package/front_end/panels/timeline/RecordingMetadata.ts +1 -1
  29. package/front_end/panels/timeline/TimelineDetailsView.ts +2 -2
  30. package/front_end/panels/timeline/TimelineUIUtils.ts +7 -4
  31. package/front_end/panels/timeline/components/Utils.ts +2 -2
  32. package/front_end/panels/webauthn/webauthnPane.css +1 -1
  33. package/front_end/ui/components/tree_outline/TreeOutline.ts +1 -1
  34. package/front_end/ui/legacy/ProgressIndicator.ts +1 -1
  35. package/front_end/ui/legacy/Toolbar.ts +1 -1
  36. package/front_end/ui/legacy/components/object_ui/ObjectPropertiesSection.ts +14 -7
  37. package/front_end/ui/legacy/legacy.ts +0 -2
  38. package/front_end/ui/visual_logging/KnownContextValues.ts +4 -0
  39. package/inspector_overlay/loadCSS.rollup.js +2 -2
  40. package/inspector_overlay/tool_source_order.css +1 -0
  41. package/package.json +1 -1
  42. package/front_end/ui/legacy/Fragment.ts +0 -234
package/.stylelintrc.json CHANGED
@@ -2,11 +2,13 @@
2
2
  "extends": "stylelint-config-standard",
3
3
  "plugins": [
4
4
  "./scripts/stylelint_rules/lib/use_theme_colors.mjs",
5
- "./scripts/stylelint_rules/lib/check_highlight_scope.mjs"
5
+ "./scripts/stylelint_rules/lib/check_highlight_scope.mjs",
6
+ "./scripts/stylelint_rules/lib/use_monospace_font.mjs"
6
7
  ],
7
8
  "rules": {
8
9
  "plugin/use_theme_colors": true,
9
10
  "plugin/check_highlight_scope": true,
11
+ "plugin/use_monospace_font": true,
10
12
  "alpha-value-notation": "percentage",
11
13
  "color-function-notation": "modern",
12
14
  "hue-degree-notation": "angle",
@@ -1183,42 +1183,6 @@ export const DEFAULT_VIEW = (input, _output, target) => {
1183
1183
  };
1184
1184
  ```
1185
1185
 
1186
- ## Migrating `UI.Fragment`
1187
-
1188
- Replace `UI.Fragment.Fragment.build` with a standard lit-html template. To get a reference to an element, use the `ref` directive.
1189
-
1190
- **Before:**
1191
- ```typescript
1192
-
1193
- class SomeWidget extends UI.Widget.Widget {
1194
- constructor() {
1195
- super();
1196
- const contrastFragment = UI.Fragment.Fragment.build`
1197
- <div class="contrast-container-in-grid" $="contrast-container-element">
1198
- <span class="contrast-preview">Aa</span>
1199
- <span>${contrastRatioString}</span>
1200
- </div>`;
1201
- this.contentElement.appendChild(contrastFragment.element());
1202
- }
1203
- }
1204
- ```
1205
-
1206
- **After:**
1207
- ```typescript
1208
-
1209
-
1210
- export const DEFAULT_VIEW = (input, output, target) => {
1211
- render(html`
1212
- <div>
1213
- <div class="contrast-container-in-grid" ${ref(e => { output.contrastContainerElement = e; })}>
1214
- <span class="contrast-preview">Aa</span>
1215
- <span>${contrastRatioString}</span>
1216
- </div>
1217
- </div>`,
1218
- target, {host: input});
1219
- };
1220
- ```
1221
-
1222
1186
  ## Migrating `UI.ARIAUtils` helpers
1223
1187
 
1224
1188
  Replace calls to `UI.ARIAUtils` helper functions with the corresponding ARIA attributes directly in the lit-html template.
@@ -822,7 +822,6 @@ export enum DevtoolsExperiments {
822
822
  'authored-deployed-grouping' = 63,
823
823
  'just-my-code' = 65,
824
824
  'use-source-map-scopes' = 76,
825
- 'timeline-show-postmessage-events' = 86,
826
825
  'timeline-debug-mode' = 93,
827
826
  'durable-messages' = 110,
828
827
  'jpeg-xl' = 111,
@@ -20,7 +20,6 @@ export enum ExperimentName {
20
20
  AUTHORED_DEPLOYED_GROUPING = 'authored-deployed-grouping',
21
21
  JUST_MY_CODE = 'just-my-code',
22
22
  USE_SOURCE_MAP_SCOPES = 'use-source-map-scopes',
23
- TIMELINE_SHOW_POST_MESSAGE_EVENTS = 'timeline-show-postmessage-events',
24
23
  TIMELINE_DEBUG_MODE = 'timeline-debug-mode',
25
24
  DURABLE_MESSAGES = 'durable-messages',
26
25
  JPEG_XL = 'jpeg-xl',
@@ -1223,6 +1223,122 @@ export class DOMNode extends Common.ObjectWrapper.ObjectWrapper<DOMNodeEventType
1223
1223
  return this.domModel().nodeForId(response.nodeId);
1224
1224
  }
1225
1225
 
1226
+ async takeSnapshot(ownerDocumentSnapshot?: DOMDocument): Promise<DOMNode> {
1227
+ const snapshot = (this instanceof DOMDocument) ? new DOMDocumentSnapshot(this.domModel(), {
1228
+ nodeId: this.id,
1229
+ backendNodeId: this.backendNodeId(),
1230
+ nodeType: this.nodeType(),
1231
+ nodeName: this.nodeName(),
1232
+ localName: this.localName(),
1233
+ nodeValue: this.nodeValueInternal,
1234
+ } as Protocol.DOM.Node) :
1235
+ new DOMNodeSnapshot(this.domModel());
1236
+ snapshot.id = this.id;
1237
+ snapshot.#backendNodeId = this.#backendNodeId;
1238
+ snapshot.#frameOwnerFrameId = this.#frameOwnerFrameId;
1239
+ snapshot.#nodeType = this.#nodeType;
1240
+ snapshot.#nodeName = this.#nodeName;
1241
+ snapshot.#localName = this.#localName;
1242
+ snapshot.nodeValueInternal = this.nodeValueInternal;
1243
+ snapshot.#pseudoType = this.#pseudoType;
1244
+ snapshot.#pseudoIdentifier = this.#pseudoIdentifier;
1245
+ snapshot.#shadowRootType = this.#shadowRootType;
1246
+ snapshot.#xmlVersion = this.#xmlVersion;
1247
+ snapshot.#isSVGNode = this.#isSVGNode;
1248
+ snapshot.#isScrollable = this.#isScrollable;
1249
+ snapshot.#affectedByStartingStyles = this.#affectedByStartingStyles;
1250
+ snapshot.ownerDocument =
1251
+ ownerDocumentSnapshot || ((snapshot instanceof DOMDocument) ? snapshot : this.ownerDocument);
1252
+ snapshot.#isInShadowTree = this.#isInShadowTree;
1253
+ snapshot.childNodeCountInternal = this.childNodeCountInternal;
1254
+
1255
+ if (snapshot instanceof DOMDocument && this instanceof DOMDocument) {
1256
+ snapshot.documentURL = this.documentURL;
1257
+ snapshot.baseURL = this.baseURL;
1258
+ }
1259
+
1260
+ if (!this.childrenInternal && this.childNodeCountInternal > 0) {
1261
+ await this.getSubtree(1, false);
1262
+ }
1263
+
1264
+ for (const [name, attr] of this.#attributes) {
1265
+ snapshot.#attributes.set(name, {name: attr.name, value: attr.value, _node: snapshot});
1266
+ }
1267
+
1268
+ if (this.childrenInternal) {
1269
+ snapshot.childrenInternal = [];
1270
+ for (const child of this.childrenInternal) {
1271
+ const childSnapshot = await child.takeSnapshot(snapshot.ownerDocument || undefined);
1272
+ childSnapshot.parentNode = snapshot;
1273
+ childSnapshot.ownerDocument = (snapshot instanceof DOMDocument) ? snapshot : snapshot.ownerDocument;
1274
+ snapshot.childrenInternal.push(childSnapshot);
1275
+ if (childSnapshot.ownerDocument instanceof DOMDocument) {
1276
+ if (childSnapshot.nodeName() === 'HTML' && !childSnapshot.ownerDocument.documentElement) {
1277
+ childSnapshot.ownerDocument.documentElement = childSnapshot;
1278
+ }
1279
+ if (childSnapshot.nodeName() === 'BODY' && !childSnapshot.ownerDocument.body) {
1280
+ childSnapshot.ownerDocument.body = childSnapshot;
1281
+ }
1282
+ }
1283
+ }
1284
+ }
1285
+
1286
+ for (const root of this.shadowRootsInternal) {
1287
+ const rootSnapshot = await root.takeSnapshot(snapshot.ownerDocument || undefined);
1288
+ rootSnapshot.parentNode = snapshot;
1289
+ rootSnapshot.ownerDocument = snapshot.ownerDocument;
1290
+ snapshot.shadowRootsInternal.push(rootSnapshot);
1291
+ }
1292
+
1293
+ if (this.templateContentInternal) {
1294
+ const templateSnapshot = await this.templateContentInternal.takeSnapshot(snapshot.ownerDocument || undefined);
1295
+ templateSnapshot.parentNode = snapshot;
1296
+ templateSnapshot.ownerDocument = snapshot.ownerDocument;
1297
+ snapshot.templateContentInternal = templateSnapshot;
1298
+ }
1299
+
1300
+ if (this.contentDocumentInternal) {
1301
+ const contentDocSnapshot = await this.contentDocumentInternal.takeSnapshot();
1302
+ contentDocSnapshot.parentNode = snapshot;
1303
+ snapshot.contentDocumentInternal = contentDocSnapshot as DOMDocumentSnapshot;
1304
+ }
1305
+
1306
+ if (this.#importedDocument) {
1307
+ const importedDocSnapshot = await this.#importedDocument.takeSnapshot(snapshot.ownerDocument || undefined);
1308
+ importedDocSnapshot.parentNode = snapshot;
1309
+ importedDocSnapshot.ownerDocument = snapshot.ownerDocument;
1310
+ snapshot.#importedDocument = importedDocSnapshot;
1311
+ }
1312
+
1313
+ for (const [pseudoType, nodes] of this.#pseudoElements) {
1314
+ const snapshots = [];
1315
+ for (const node of nodes) {
1316
+ const pseudoSnapshot = await node.takeSnapshot(snapshot.ownerDocument || undefined);
1317
+ pseudoSnapshot.parentNode = snapshot;
1318
+ pseudoSnapshot.ownerDocument = snapshot.ownerDocument;
1319
+ snapshots.push(pseudoSnapshot);
1320
+ }
1321
+ snapshot.#pseudoElements.set(pseudoType, snapshots);
1322
+ }
1323
+
1324
+ // We intentionally preserve references to live nodes for assignedSlot and distributedNodes.
1325
+ // This allows slot adorners in the Elements panel to remain functional within the snapshot,
1326
+ // enabling users to resolve and jump to the actual nodes in the live DOM tree.
1327
+ if (this.#distributedNodes) {
1328
+ snapshot.#distributedNodes = [...this.#distributedNodes];
1329
+ }
1330
+
1331
+ snapshot.assignedSlot = this.assignedSlot;
1332
+
1333
+ snapshot.#retainedNodes = this.#retainedNodes;
1334
+
1335
+ if (this.#adoptedStyleSheets.length) {
1336
+ snapshot.setAdoptedStyleSheets(this.#adoptedStyleSheets.map(sheet => sheet.id));
1337
+ }
1338
+
1339
+ return snapshot;
1340
+ }
1341
+
1226
1342
  classNames(): string[] {
1227
1343
  const classes = this.getAttribute('class');
1228
1344
  return classes ? classes.split(/\s+/) : [];
@@ -2176,6 +2292,96 @@ export class DOMModelUndoStack {
2176
2292
  }
2177
2293
 
2178
2294
  SDKModel.register(DOMModel, {capabilities: Capability.DOM, autostart: true});
2295
+ export class DOMNodeSnapshot extends DOMNode {
2296
+ override init(
2297
+ _doc: DOMDocument|null, _isInShadowTree: boolean, _payload: Protocol.DOM.Node,
2298
+ _retainedNodes?: Set<Protocol.DOM.BackendNodeId>|undefined): void {
2299
+ }
2300
+
2301
+ override setNodeName(_name: string, _callback?: ((arg0: string|null, arg1: DOMNode|null) => void)|undefined): void {
2302
+ }
2303
+
2304
+ override setNodeValue(_value: string, _callback?: ((arg0: string|null) => void)|undefined): void {
2305
+ }
2306
+
2307
+ override setAttribute(_name: string, _text: string, _callback?: ((arg0: string|null) => void)|undefined): void {
2308
+ }
2309
+
2310
+ override setAttributeValue(_name: string, _value: string, _callback?: ((arg0: string|null) => void)|undefined): void {
2311
+ }
2312
+
2313
+ override removeAttribute(_name: string): Promise<void> {
2314
+ return Promise.resolve();
2315
+ }
2316
+
2317
+ override setOuterHTML(_html: string, _callback?: ((arg0: string|null) => void)|undefined): void {
2318
+ }
2319
+
2320
+ override removeNode(_callback?: ((arg0: string|null, arg1?: Protocol.DOM.NodeId|undefined) => void)|undefined):
2321
+ Promise<void> {
2322
+ return Promise.resolve();
2323
+ }
2324
+
2325
+ override copyTo(
2326
+ _targetNode: DOMNode, _anchorNode: DOMNode|null,
2327
+ _callback?: ((arg0: string|null, arg1: DOMNode|null) => void)|undefined): void {
2328
+ }
2329
+
2330
+ override moveTo(
2331
+ _targetNode: DOMNode, _anchorNode: DOMNode|null,
2332
+ _callback?: ((arg0: string|null, arg1: DOMNode|null) => void)|undefined): void {
2333
+ }
2334
+
2335
+ override setAsInspectedNode(): Promise<void> {
2336
+ return Promise.resolve();
2337
+ }
2338
+ }
2339
+
2340
+ export class DOMDocumentSnapshot extends DOMDocument {
2341
+ override init(
2342
+ _doc: DOMDocument|null, _isInShadowTree: boolean, _payload: Protocol.DOM.Node,
2343
+ _retainedNodes?: Set<Protocol.DOM.BackendNodeId>|undefined): void {
2344
+ }
2345
+
2346
+ override setNodeName(_name: string, _callback?: ((arg0: string|null, arg1: DOMNode|null) => void)|undefined): void {
2347
+ }
2348
+
2349
+ override setNodeValue(_value: string, _callback?: ((arg0: string|null) => void)|undefined): void {
2350
+ }
2351
+
2352
+ override setAttribute(_name: string, _text: string, _callback?: ((arg0: string|null) => void)|undefined): void {
2353
+ }
2354
+
2355
+ override setAttributeValue(_name: string, _value: string, _callback?: ((arg0: string|null) => void)|undefined): void {
2356
+ }
2357
+
2358
+ override removeAttribute(_name: string): Promise<void> {
2359
+ return Promise.resolve();
2360
+ }
2361
+
2362
+ override setOuterHTML(_html: string, _callback?: ((arg0: string|null) => void)|undefined): void {
2363
+ }
2364
+
2365
+ override removeNode(_callback?: ((arg0: string|null, arg1?: Protocol.DOM.NodeId|undefined) => void)|undefined):
2366
+ Promise<void> {
2367
+ return Promise.resolve();
2368
+ }
2369
+
2370
+ override copyTo(
2371
+ _targetNode: DOMNode, _anchorNode: DOMNode|null,
2372
+ _callback?: ((arg0: string|null, arg1: DOMNode|null) => void)|undefined): void {
2373
+ }
2374
+
2375
+ override moveTo(
2376
+ _targetNode: DOMNode, _anchorNode: DOMNode|null,
2377
+ _callback?: ((arg0: string|null, arg1: DOMNode|null) => void)|undefined): void {
2378
+ }
2379
+
2380
+ override setAsInspectedNode(): Promise<void> {
2381
+ return Promise.resolve();
2382
+ }
2383
+ }
2384
+
2179
2385
  export interface Attribute {
2180
2386
  name: string;
2181
2387
  value: string;
@@ -418,7 +418,7 @@ export class SourceMapScopesInfo {
418
418
  * Returns the authored function name of the function containing the provided generated position.
419
419
  */
420
420
  findOriginalFunctionName(position: ScopesCodec.Position): string|null {
421
- const originalInnerMostScope = this.findOriginalFunctionScope(position)?.scope ?? undefined;
421
+ const originalInnerMostScope = this.findOriginalFunctionScope(position)?.scope;
422
422
  return this.#findFunctionNameInOriginalScopeChain(originalInnerMostScope);
423
423
  }
424
424
 
@@ -134,7 +134,7 @@ html, body {
134
134
  margin-top: 8px;
135
135
  padding-top: 8px;
136
136
  border-top: 1px solid #eee;
137
- font-family: monospace;
137
+ font-family: var(--monospace-font-family);
138
138
  }
139
139
 
140
140
  .thought, .action {
@@ -186,7 +186,7 @@ html, body {
186
186
 
187
187
  .green-dev-floaty-dialog-node-description {
188
188
  font-size: 11px;
189
- font-family: monospace;
189
+ font-family: var(--monospace-font-family);
190
190
  color: #0b57d0;
191
191
  background-color: #d3e3fd;
192
192
  padding: 2px 8px;
@@ -413,11 +413,6 @@ export class MainImpl {
413
413
  Root.Runtime.experiments.register(
414
414
  Root.ExperimentNames.ExperimentName.JUST_MY_CODE, 'Hide ignore-listed code in Sources tree view');
415
415
 
416
- Root.Runtime.experiments.register(
417
- Root.ExperimentNames.ExperimentName.TIMELINE_SHOW_POST_MESSAGE_EVENTS,
418
- 'Performance panel: show postMessage dispatch and handling flows',
419
- );
420
-
421
416
  Root.Runtime.experiments.registerHostExperiment({
422
417
  name: Root.ExperimentNames.ExperimentName.DURABLE_MESSAGES,
423
418
  title: 'Durable Messages',
@@ -129,7 +129,7 @@ export class ConversationHandler extends Common.ObjectWrapper.ObjectWrapper<Even
129
129
  }): ConversationHandler {
130
130
  if (opts?.forceNew || conversationHandlerInstance === undefined) {
131
131
  const aidaClient = opts?.aidaClient ?? new Host.AidaClient.AidaClient();
132
- conversationHandlerInstance = new ConversationHandler(aidaClient, opts?.aidaAvailability ?? undefined);
132
+ conversationHandlerInstance = new ConversationHandler(aidaClient, opts?.aidaAvailability);
133
133
  }
134
134
  return conversationHandlerInstance;
135
135
  }
@@ -169,7 +169,7 @@ export class AiCodeCompletion {
169
169
  // As a temporary fix for b/441221870 we are prepending a newline for each prefix.
170
170
  prefix = '\n' + prefix;
171
171
 
172
- let additionalContextFiles = additionalFiles ?? undefined;
172
+ let additionalContextFiles = additionalFiles;
173
173
  if (!additionalContextFiles) {
174
174
  additionalContextFiles = this.#panel === ContextFlavor.CONSOLE ? [{
175
175
  path: 'devtools-console-context.js',
@@ -761,6 +761,8 @@ export class AiAssistancePanel extends UI.Panel.Panel {
761
761
  if (isNarrow === this.#walkthrough.isInlined) {
762
762
  return;
763
763
  }
764
+ // If the UI changed, we reset the visibility of the AI Walkthrough.
765
+ this.#clearWalkthrough();
764
766
  this.#walkthrough.isInlined = isNarrow;
765
767
  this.requestUpdate();
766
768
  }
@@ -11,6 +11,7 @@ import * as i18n from '../../../core/i18n/i18n.js';
11
11
  import type * as Platform from '../../../core/platform/platform.js';
12
12
  import * as Root from '../../../core/root/root.js';
13
13
  import * as SDK from '../../../core/sdk/sdk.js';
14
+ import type * as Protocol from '../../../generated/protocol.js';
14
15
  import type {AiWidget, ComputedStyleAiWidget} from '../../../models/ai_assistance/agents/AiAgent.js';
15
16
  import * as AiAssistanceModel from '../../../models/ai_assistance/ai_assistance.js';
16
17
  import * as ComputedStyle from '../../../models/computed_style/computed_style.js';
@@ -278,6 +279,7 @@ export interface MessageInput {
278
279
  isExpanded: boolean,
279
280
  onToggle: (isOpen: boolean) => void,
280
281
  isInlined: boolean,
282
+ activeMessage: ModelChatMessage|null,
281
283
  };
282
284
  }
283
285
 
@@ -483,14 +485,18 @@ function renderStepDetails({
483
485
 
484
486
  function renderWalkthroughSidebarButton(
485
487
  input: ChatMessageViewInput,
486
- lastStep: Step,
488
+ steps: Step[],
487
489
  ): Lit.LitTemplate {
488
490
  const {message, walkthrough} = input;
489
- if (walkthrough.isInlined) {
491
+ const lastStep = steps.at(-1);
492
+ if (walkthrough.isInlined || !lastStep) {
490
493
  return Lit.nothing;
491
494
  }
495
+
496
+ const hasOneStepWithWidget = steps.some(step => step.widgets?.length);
492
497
  const title = walkthroughTitle({
493
498
  isLoading: input.isLoading,
499
+ hasWidgets: hasOneStepWithWidget,
494
500
  lastStep,
495
501
  });
496
502
 
@@ -505,7 +511,7 @@ function renderWalkthroughSidebarButton(
505
511
  .jslogContext=${walkthrough.isExpanded ? 'ai-hide-walkthrough-sidebar' : 'ai-show-walkthrough-sidebar'}
506
512
  data-show-walkthrough
507
513
  @click=${() => {
508
- if(walkthrough.isExpanded) {
514
+ if(walkthrough.activeMessage === input.message && walkthrough.isExpanded) {
509
515
  walkthrough.onToggle(false);
510
516
  } else {
511
517
  // Can't just toggle the visibility here; we need to ensure we
@@ -535,7 +541,7 @@ function renderWalkthroughUI(input: ChatMessageViewInput, steps: Step[]): Lit.Li
535
541
  // If the walkthrough is in the sidebar, we render a button into the
536
542
  // ChatView to open it.
537
543
  const openWalkThroughSidebarButton =
538
- !input.walkthrough.isInlined ? renderWalkthroughSidebarButton(input, lastStep) : Lit.nothing;
544
+ !input.walkthrough.isInlined ? renderWalkthroughSidebarButton(input, steps) : Lit.nothing;
539
545
 
540
546
  // If there are side-effect steps, and the walkthrough is not open, we render
541
547
  // those inline so that the user can see them and approve them.
@@ -554,6 +560,9 @@ function renderWalkthroughUI(input: ChatMessageViewInput, steps: Step[]): Lit.Li
554
560
 
555
561
  // If the walkthrough is inlined (narrow width screens), render it here.
556
562
  // Note that we force it open if there is a side-effect.
563
+
564
+ const isExpanded = (input.walkthrough.isExpanded && input.walkthrough.activeMessage === input.message) ||
565
+ steps.some(s => s.requestApproval);
557
566
  // clang-format off
558
567
  const walkthroughInline = input.walkthrough.isInlined ? html`
559
568
  <div class="walkthrough-container">
@@ -562,9 +571,9 @@ function renderWalkthroughUI(input: ChatMessageViewInput, steps: Step[]): Lit.Li
562
571
  isLoading: input.isLoading && input.isLastMessage,
563
572
  markdownRenderer: input.markdownRenderer,
564
573
  isInlined: true,
565
- isExpanded: input.isLastMessage &&
566
- (input.walkthrough.isExpanded || steps.some(step => Boolean(step.requestApproval))),
574
+ isExpanded,
567
575
  onToggle: input.walkthrough.onToggle,
576
+ onOpen: input.walkthrough.onOpen,
568
577
  })}></devtools-widget>
569
578
  </div>
570
579
  ` : Lit.nothing;
@@ -649,37 +658,42 @@ interface WidgetMakerResponse {
649
658
  revealable: unknown;
650
659
  }
651
660
 
661
+ const computedStyleNodeCache = new Map<Protocol.DOM.BackendNodeId, SDK.DOMModel.DOMNode>();
662
+
652
663
  async function makeComputedStyleWidget(widgetData: ComputedStyleAiWidget): Promise<WidgetMakerResponse|null> {
653
- const target = SDK.TargetManager.TargetManager.instance().primaryPageTarget();
654
- if (!target) {
655
- return null;
656
- }
657
- const node = new SDK.DOMModel.DeferredDOMNode(
658
- target,
659
- widgetData.data.backendNodeId,
660
- );
661
- const resolved = await node.resolvePromise();
662
- if (!resolved) {
663
- return null;
664
+ let domNodeForId = computedStyleNodeCache.get(widgetData.data.backendNodeId);
665
+ if (!domNodeForId) {
666
+ const target = SDK.TargetManager.TargetManager.instance().primaryPageTarget();
667
+ if (!target) {
668
+ return null;
669
+ }
670
+ const node = new SDK.DOMModel.DeferredDOMNode(
671
+ target,
672
+ widgetData.data.backendNodeId,
673
+ );
674
+ const resolved = await node.resolvePromise();
675
+ if (!resolved) {
676
+ return null;
677
+ }
678
+ domNodeForId = resolved;
679
+ computedStyleNodeCache.set(widgetData.data.backendNodeId, resolved);
664
680
  }
665
- const model = new ComputedStyle.ComputedStyleModel.ComputedStyleModel(resolved);
666
- const styles = new ComputedStyle.ComputedStyleModel.ComputedStyle(resolved, widgetData.data.computedStyles);
681
+ const styles = new ComputedStyle.ComputedStyleModel.ComputedStyle(domNodeForId, widgetData.data.computedStyles);
667
682
 
668
683
  const widgetConfig = UI.Widget.widgetConfig(Elements.ComputedStyleWidget.ComputedStyleWidget, {
669
684
  nodeStyle: styles,
670
685
  matchedStyles: widgetData.data.matchedCascade,
671
686
  // This disables showing the nested traces and detailed information in the widget.
672
687
  propertyTraces: null,
673
- computedStyleModel: model,
674
688
  allowUserControl: false,
675
689
  filterText: new RegExp(widgetData.data.properties.join('|'), 'i')
676
690
  });
677
691
 
678
692
  // clang-format off
679
- const widget = html`<devtools-widget class="computed-styles-widget" .widgetConfig=${widgetConfig}></devtools-widget>`;
693
+ const widget = html`<devtools-widget class="computed-styles-widget" .widgetConfig=${widgetConfig}></devtools-widget>`;
680
694
  // clang-format on
681
695
 
682
- return {renderedWidget: widget, revealable: new Elements.ElementsPanel.NodeComputedStyles(resolved)};
696
+ return {renderedWidget: widget, revealable: new Elements.ElementsPanel.NodeComputedStyles(domNodeForId)};
683
697
  }
684
698
 
685
699
  function renderWidgetResponse(response: WidgetMakerResponse|null): Lit.LitTemplate {
@@ -696,15 +710,17 @@ function renderWidgetResponse(response: WidgetMakerResponse|null): Lit.LitTempla
696
710
 
697
711
  // clang-format off
698
712
  return html`
699
- <div class="widget-content-container">
700
- ${response.renderedWidget}
701
- </div>
702
- <div class="widget-reveal-container">
703
- <devtools-button class="widget-reveal"
704
- .iconName=${'tab-move'}
705
- .variant=${Buttons.Button.Variant.TEXT}
706
- @click=${onReveal}
707
- >${lockedString(UIStringsNotTranslate.reveal)}</devtools-button>
713
+ <div class="widget-and-revealer-container">
714
+ <div class="widget-content-container">
715
+ ${response.renderedWidget}
716
+ </div>
717
+ <div class="widget-reveal-container">
718
+ <devtools-button class="widget-reveal"
719
+ .iconName=${'tab-move'}
720
+ .variant=${Buttons.Button.Variant.TEXT}
721
+ @click=${onReveal}
722
+ >${lockedString(UIStringsNotTranslate.reveal)}</devtools-button>
723
+ </div>
708
724
  </div>
709
725
  `;
710
726
  // clang-format on
@@ -994,6 +1010,7 @@ export class ChatMessage extends UI.Widget.Widget {
994
1010
  onToggle: () => {},
995
1011
  isInlined: false,
996
1012
  isExpanded: false,
1013
+ activeMessage: null,
997
1014
  };
998
1015
 
999
1016
  #suggestionsResizeObserver = new ResizeObserver(() => this.#handleSuggestionsScrollOrResize());
@@ -27,9 +27,13 @@ const UIStrings = {
27
27
  */
28
28
  title: 'Investigation steps',
29
29
  /**
30
- * @description Title for the button that shows the thinking process (walkthrough).
30
+ * @description Title for the button that shows the walkthrough when there are no widgets in the walkthrough.
31
31
  */
32
32
  showThinking: 'Show thinking',
33
+ /**
34
+ * @description Title for the button that shows the walkthrough when there are widgets in the walkthrough.
35
+ */
36
+ showAgentWalkthrough: 'Show agent walkthrough',
33
37
  } as const;
34
38
  const str_ = i18n.i18n.registerUIStrings('panels/ai_assistance/components/WalkthroughView.ts', UIStrings);
35
39
  const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
@@ -41,14 +45,21 @@ export interface ViewInput {
41
45
  isInlined: boolean;
42
46
  isExpanded: boolean;
43
47
  onToggle: (isOpen: boolean) => void;
48
+ onOpen: (message: ModelChatMessage) => void;
44
49
  }
45
50
 
46
51
  export function walkthroughTitle(input: {
47
52
  isLoading: boolean,
53
+ hasWidgets: boolean,
48
54
  lastStep: Step,
49
55
  }): string {
50
- const title = input.isLoading ? titleForStep(input.lastStep) : lockedString(UIStrings.showThinking);
51
- return title;
56
+ if (input.isLoading) {
57
+ return titleForStep(input.lastStep);
58
+ }
59
+ if (input.hasWidgets) {
60
+ return lockedString(UIStrings.showAgentWalkthrough);
61
+ }
62
+ return lockedString(UIStrings.showThinking);
52
63
  }
53
64
 
54
65
  function renderInlineWalkthrough(input: ViewInput, stepsOutput: Lit.LitTemplate, steps: Step[]): Lit.LitTemplate {
@@ -61,12 +72,14 @@ function renderInlineWalkthrough(input: ViewInput, stepsOutput: Lit.LitTemplate,
61
72
  input.onToggle((event.target as HTMLDetailsElement).open);
62
73
  }
63
74
 
75
+ const hasWidgets = steps.some(s => s.widgets?.length);
76
+
64
77
  // clang-format off
65
78
  return html`
66
79
  <details class="walkthrough-inline" ?open=${input.isExpanded} @toggle=${onToggle}>
67
80
  <summary>
68
81
  ${input.isLoading ? html`<devtools-spinner></devtools-spinner>` : Lit.nothing}
69
- ${walkthroughTitle({isLoading: input.isLoading, lastStep,})}
82
+ ${walkthroughTitle({isLoading: input.isLoading, lastStep, hasWidgets})}
70
83
  <devtools-icon name="chevron-down"></devtools-icon>
71
84
  </summary>
72
85
  ${stepsOutput}
@@ -155,6 +168,7 @@ export class WalkthroughView extends UI.Widget.Widget {
155
168
  #isLoading = false;
156
169
  #markdownRenderer: MarkdownLitRenderer|null = null;
157
170
  #onToggle: (isOpen: boolean) => void = () => {};
171
+ #onOpen: (message: ModelChatMessage) => void = () => {};
158
172
  #isInlined = false;
159
173
  #isExpanded = false;
160
174
 
@@ -185,6 +199,15 @@ export class WalkthroughView extends UI.Widget.Widget {
185
199
  return this.#message;
186
200
  }
187
201
 
202
+ get onOpen(): (message: ModelChatMessage) => void {
203
+ return this.#onOpen;
204
+ }
205
+
206
+ set onOpen(onOpen: (message: ModelChatMessage) => void) {
207
+ this.#onOpen = onOpen;
208
+ this.requestUpdate();
209
+ }
210
+
188
211
  set message(message: ModelChatMessage|null) {
189
212
  this.#message = message;
190
213
  this.requestUpdate();
@@ -214,6 +237,7 @@ export class WalkthroughView extends UI.Widget.Widget {
214
237
  isLoading: this.#isLoading,
215
238
  markdownRenderer: this.#markdownRenderer,
216
239
  onToggle: this.#onToggle,
240
+ onOpen: this.#onOpen,
217
241
  isInlined: this.#isInlined,
218
242
  isExpanded: this.#isExpanded,
219
243
  message: this.#message,
@@ -364,10 +364,13 @@
364
364
 
365
365
  .step-widgets-wrapper {
366
366
  width: fit-content;
367
+ display: flex;
368
+ flex-direction: column;
369
+ align-items: flex-start;
370
+ gap: var(--sys-size-5);
367
371
  }
368
372
 
369
373
  .widget-reveal-container {
370
- width: 100%;
371
374
  background: var(--sys-color-surface5);
372
375
  border-bottom-right-radius: var(--sys-shape-corner-medium);
373
376
  border-bottom-left-radius: var(--sys-shape-corner-medium);
@@ -378,7 +381,6 @@
378
381
  padding: var(--sys-size-4);
379
382
  border-top-left-radius: var(--sys-shape-corner-medium);
380
383
  border-top-right-radius: var(--sys-shape-corner-medium);
381
- width: 100%;
382
384
  overflow-x: auto;
383
385
  background-color: var(--sys-color-surface3);
384
386
 
@@ -143,8 +143,7 @@ export class IndexedDBModel extends SDK.SDKModel.SDKModel<EventTypes> implements
143
143
  }
144
144
 
145
145
  for (const [storageBucketName] of this.databaseNamesByStorageKeyAndBucket.get(storageKey) || []) {
146
- const storageBucket =
147
- this.storageBucketModel?.getBucketByName(storageKey, storageBucketName ?? undefined)?.bucket;
146
+ const storageBucket = this.storageBucketModel?.getBucketByName(storageKey, storageBucketName)?.bucket;
148
147
  if (storageBucket) {
149
148
  this.removeStorageBucket(storageBucket);
150
149
  }
@@ -169,8 +168,7 @@ export class IndexedDBModel extends SDK.SDKModel.SDKModel<EventTypes> implements
169
168
  for (const [storageKey] of this.databaseNamesByStorageKeyAndBucket) {
170
169
  const storageBucketNames = this.databaseNamesByStorageKeyAndBucket.get(storageKey)?.keys() || [];
171
170
  for (const storageBucketName of storageBucketNames) {
172
- const storageBucket =
173
- this.storageBucketModel?.getBucketByName(storageKey, storageBucketName ?? undefined)?.bucket;
171
+ const storageBucket = this.storageBucketModel?.getBucketByName(storageKey, storageBucketName)?.bucket;
174
172
  if (storageBucket) {
175
173
  await this.loadDatabaseNamesByStorageBucket(storageBucket);
176
174
  }