chrome-devtools-frontend 1.0.1595090 → 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 (49) 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/generated/InspectorBackendCommands.ts +3 -5
  10. package/front_end/generated/protocol-mapping.d.ts +0 -8
  11. package/front_end/generated/protocol-proxy-api.d.ts +0 -6
  12. package/front_end/generated/protocol.ts +1 -19
  13. package/front_end/models/ai_assistance/ConversationHandler.ts +1 -1
  14. package/front_end/models/ai_code_completion/AiCodeCompletion.ts +1 -1
  15. package/front_end/models/javascript_metadata/NativeFunctions.js +5 -1
  16. package/front_end/panels/ai_assistance/AiAssistancePanel.ts +2 -0
  17. package/front_end/panels/ai_assistance/components/ChatMessage.ts +48 -31
  18. package/front_end/panels/ai_assistance/components/WalkthroughView.ts +38 -11
  19. package/front_end/panels/ai_assistance/components/chatMessage.css +4 -2
  20. package/front_end/panels/ai_assistance/components/walkthroughView.css +15 -0
  21. package/front_end/panels/application/IndexedDBModel.ts +2 -4
  22. package/front_end/panels/application/ServiceWorkersView.ts +3 -11
  23. package/front_end/panels/developer_resources/DeveloperResourcesListView.ts +1 -1
  24. package/front_end/panels/elements/ComputedStyleWidget.ts +25 -16
  25. package/front_end/panels/elements/ElementsPanel.ts +0 -1
  26. package/front_end/panels/elements/StylePropertyTreeElement.ts +11 -5
  27. package/front_end/panels/elements/nodeStackTraceWidget.css +1 -1
  28. package/front_end/panels/emulation/DeviceModeToolbar.ts +171 -89
  29. package/front_end/panels/greendev/GreenDevPanel.css +2 -2
  30. package/front_end/panels/issues/AffectedPermissionElementsView.ts +9 -6
  31. package/front_end/panels/profiler/HeapSnapshotGridNodes.ts +11 -3
  32. package/front_end/panels/settings/components/SyncSection.ts +4 -13
  33. package/front_end/panels/timeline/CompatibilityTracksAppender.ts +2 -15
  34. package/front_end/panels/timeline/RecordingMetadata.ts +1 -1
  35. package/front_end/panels/timeline/TimelineDetailsView.ts +2 -2
  36. package/front_end/panels/timeline/TimelineUIUtils.ts +7 -4
  37. package/front_end/panels/timeline/components/Utils.ts +2 -2
  38. package/front_end/panels/webauthn/webauthnPane.css +1 -1
  39. package/front_end/third_party/chromium/README.chromium +1 -1
  40. package/front_end/ui/components/tree_outline/TreeOutline.ts +1 -1
  41. package/front_end/ui/legacy/ProgressIndicator.ts +1 -1
  42. package/front_end/ui/legacy/Toolbar.ts +1 -1
  43. package/front_end/ui/legacy/components/object_ui/ObjectPropertiesSection.ts +14 -7
  44. package/front_end/ui/legacy/legacy.ts +0 -2
  45. package/front_end/ui/visual_logging/KnownContextValues.ts +4 -0
  46. package/inspector_overlay/loadCSS.rollup.js +2 -2
  47. package/inspector_overlay/tool_source_order.css +1 -0
  48. package/package.json +1 -1
  49. package/front_end/ui/legacy/Fragment.ts +0 -234
@@ -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}
@@ -117,13 +130,16 @@ export const DEFAULT_VIEW = (
117
130
  const stepsOutput = steps.length > 0 ? html`
118
131
  <div class="steps-container">
119
132
  ${steps.map((step, index) => html`
120
- <div class="step-wrapper">
121
- ${renderStep({
122
- step,
123
- isLoading: input.isLoading,
124
- markdownRenderer: input.markdownRenderer,
125
- isLast: index === steps.length - 1
126
- })}
133
+ <div class="walkthrough-step">
134
+ <span class="step-number">${index+1}</span>
135
+ <div class="step-wrapper">
136
+ ${renderStep({
137
+ step,
138
+ isLoading: input.isLoading,
139
+ markdownRenderer: input.markdownRenderer,
140
+ isLast: index === steps.length - 1
141
+ })}
142
+ </div>
127
143
  </div>
128
144
  `)}
129
145
  </div>
@@ -152,6 +168,7 @@ export class WalkthroughView extends UI.Widget.Widget {
152
168
  #isLoading = false;
153
169
  #markdownRenderer: MarkdownLitRenderer|null = null;
154
170
  #onToggle: (isOpen: boolean) => void = () => {};
171
+ #onOpen: (message: ModelChatMessage) => void = () => {};
155
172
  #isInlined = false;
156
173
  #isExpanded = false;
157
174
 
@@ -182,6 +199,15 @@ export class WalkthroughView extends UI.Widget.Widget {
182
199
  return this.#message;
183
200
  }
184
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
+
185
211
  set message(message: ModelChatMessage|null) {
186
212
  this.#message = message;
187
213
  this.requestUpdate();
@@ -211,6 +237,7 @@ export class WalkthroughView extends UI.Widget.Widget {
211
237
  isLoading: this.#isLoading,
212
238
  markdownRenderer: this.#markdownRenderer,
213
239
  onToggle: this.#onToggle,
240
+ onOpen: this.#onOpen,
214
241
  isInlined: this.#isInlined,
215
242
  isExpanded: this.#isExpanded,
216
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
 
@@ -38,6 +38,21 @@
38
38
  gap: var(--sys-size-6);
39
39
  }
40
40
 
41
+ .walkthrough-step {
42
+ display: flex;
43
+ gap: var(--sys-size-6);
44
+ align-items: flex-start;
45
+ justify-content: flex-start;
46
+
47
+ .step-number {
48
+ font: var(--sys-typescale-body4-regular);
49
+ color: var(--sys-color-on-surface-subtle);
50
+ padding-top:var(--sys-size-4);
51
+ flex-grow: 0;
52
+ flex-shrink: 0;
53
+ }
54
+ }
55
+
41
56
  .step-wrapper {
42
57
  display: flex;
43
58
  flex-direction: column;
@@ -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
  }
@@ -10,6 +10,7 @@ import * as SDK from '../../core/sdk/sdk.js';
10
10
  import type * as Protocol from '../../generated/protocol.js';
11
11
  import * as NetworkForward from '../../panels/network/forward/forward.js';
12
12
  import * as Buttons from '../../ui/components/buttons/buttons.js';
13
+ import {Link} from '../../ui/kit/kit.js';
13
14
  import * as Components from '../../ui/legacy/components/utils/utils.js';
14
15
  import * as UI from '../../ui/legacy/legacy.js';
15
16
  import * as VisualLogging from '../../ui/visual_logging/visual_logging.js';
@@ -227,17 +228,8 @@ export class ServiceWorkersView extends UI.Widget.VBox implements
227
228
  othersView.show(othersDiv);
228
229
  const othersSection = othersView.appendSection(i18nString(UIStrings.serviceWorkersFromOtherOrigins));
229
230
  const othersSectionRow = othersSection.appendRow();
230
- const seeOthers =
231
- UI.Fragment
232
- .html`<a class="devtools-link" role="link" tabindex="0" href="chrome://serviceworker-internals" target="_blank" style="display: inline; cursor: pointer;">${
233
- i18nString(UIStrings.seeAllRegistrations)}</a>`;
234
- seeOthers.setAttribute('jslog', `${VisualLogging.link('view-all').track({click: true})}`);
235
- self.onInvokeElement(seeOthers, event => {
236
- const rootTarget = SDK.TargetManager.TargetManager.instance().rootTarget();
237
- rootTarget &&
238
- void rootTarget.targetAgent().invoke_createTarget({url: 'chrome://serviceworker-internals?devtools'});
239
- event.consume(true);
240
- });
231
+ const seeOthers = Link.create(
232
+ 'chrome://serviceworker-internals', i18nString(UIStrings.seeAllRegistrations), undefined, 'view-all');
241
233
  othersSectionRow.appendChild(seeOthers);
242
234
 
243
235
  this.toolbar.appendToolbarItem(
@@ -104,7 +104,7 @@ const DEFAULT_VIEW: View = (input, _output, target) => {
104
104
  if (!filter?.regex) {
105
105
  return '';
106
106
  }
107
- const matches = filter.regex.exec(textContent ?? '');
107
+ const matches = filter.regex.exec(textContent);
108
108
  if (!matches?.length) {
109
109
  return '';
110
110
  }
@@ -294,7 +294,14 @@ export const DEFAULT_VIEW: View = (input, _output, target) => {
294
294
  };
295
295
 
296
296
  export class ComputedStyleWidget extends UI.Widget.VBox {
297
- #computedStyleModel?: ComputedStyleModule.ComputedStyleModel.ComputedStyleModel;
297
+ /**
298
+ * We store these because they are used when calculating the dimensions for the image preview.
299
+ * When we need to get those dimensions, we try to resolve them against the
300
+ * node fresh (in case the dimensions have changed), but if that doesn't work,
301
+ * we fallback to the precomputed ones, which helps to deal with situations
302
+ * where the node might have been removed from the DOM.
303
+ */
304
+ #storedNodeFeatures: Components.ImagePreview.PrecomputedFeatures|null = null;
298
305
  #nodeStyle: ComputedStyleModule.ComputedStyleModel.ComputedStyle|null = null;
299
306
  #matchedStyles: SDK.CSSMatchedStyles.CSSMatchedStyles|null = null;
300
307
  #propertyTraces: Map<string, SDK.CSSProperty.CSSProperty[]>|null = null;
@@ -363,7 +370,8 @@ export class ComputedStyleWidget extends UI.Widget.VBox {
363
370
  return null;
364
371
  },
365
372
  async () => {
366
- return await Components.ImagePreview.loadPrecomputedFeatures(this.#computedStyleModel?.node);
373
+ const liveFeatures = await Components.ImagePreview.loadPrecomputedFeatures(this.#nodeStyle?.node);
374
+ return liveFeatures ?? this.#storedNodeFeatures ?? undefined;
367
375
  });
368
376
 
369
377
  this.#updateView({hasMatches: true});
@@ -430,7 +438,13 @@ export class ComputedStyleWidget extends UI.Widget.VBox {
430
438
 
431
439
  set nodeStyle(nodeStyle: ComputedStyleModule.ComputedStyleModel.ComputedStyle|null) {
432
440
  this.#nodeStyle = nodeStyle;
433
- this.requestUpdate();
441
+ if (nodeStyle) {
442
+ // Make sure we get the node features before we request an update so we
443
+ // don't run the update before we have the features fetched.
444
+ void this.#storeNodeFeatures(nodeStyle.node).then(() => this.requestUpdate());
445
+ } else {
446
+ this.requestUpdate();
447
+ }
434
448
  }
435
449
 
436
450
  get matchedStyles(): SDK.CSSMatchedStyles.CSSMatchedStyles|null {
@@ -447,13 +461,13 @@ export class ComputedStyleWidget extends UI.Widget.VBox {
447
461
  this.requestUpdate();
448
462
  }
449
463
 
450
- get computedStyleModel(): ComputedStyleModule.ComputedStyleModel.ComputedStyleModel|undefined {
451
- return this.#computedStyleModel;
452
- }
453
-
454
- set computedStyleModel(computedStyleModel: ComputedStyleModule.ComputedStyleModel.ComputedStyleModel) {
455
- this.#computedStyleModel = computedStyleModel;
456
- this.requestUpdate();
464
+ async #storeNodeFeatures(node: SDK.DOMModel.DOMNode|null): Promise<void> {
465
+ if (node) {
466
+ const features = await Components.ImagePreview.loadPrecomputedFeatures(node);
467
+ this.#storedNodeFeatures = features ?? null;
468
+ } else {
469
+ this.#storedNodeFeatures = null;
470
+ }
457
471
  }
458
472
 
459
473
  #shouldGroupStyles(): boolean {
@@ -483,10 +497,6 @@ export class ComputedStyleWidget extends UI.Widget.VBox {
483
497
  matchedStyles: SDK.CSSMatchedStyles.CSSMatchedStyles): Promise<void> {
484
498
  this.imagePreviewPopover.hide();
485
499
  this.linkifier.reset();
486
- const cssModel = this.#computedStyleModel?.cssModel();
487
- if (!cssModel) {
488
- return;
489
- }
490
500
 
491
501
  const uniqueProperties = [...nodeStyle.computedStyle.keys()];
492
502
  uniqueProperties.sort(propertySorter);
@@ -526,8 +536,7 @@ export class ComputedStyleWidget extends UI.Widget.VBox {
526
536
  matchedStyles: SDK.CSSMatchedStyles.CSSMatchedStyles|null): Promise<void> {
527
537
  this.imagePreviewPopover.hide();
528
538
  this.linkifier.reset();
529
- const cssModel = this.#computedStyleModel?.cssModel();
530
- if (!nodeStyle || !matchedStyles || !cssModel) {
539
+ if (!nodeStyle || !matchedStyles) {
531
540
  this.#updateView({hasMatches: false});
532
541
  return;
533
542
  }
@@ -316,7 +316,6 @@ export class ElementsPanel extends UI.Panel.Panel implements UI.SearchableView.S
316
316
 
317
317
  this.stylesWidget = new StylesSidebarPane(this.#computedStyleModel);
318
318
  this.#computedStyleWidget = new ComputedStyleWidget();
319
- this.#computedStyleWidget.computedStyleModel = this.#computedStyleModel;
320
319
  this.#computedStyleModel.addEventListener(
321
320
  ComputedStyle.ComputedStyleModel.Events.COMPUTED_STYLE_CHANGED, this.#updateComputedStyles, this);
322
321
  this.#computedStyleModel.addEventListener(
@@ -1575,11 +1575,17 @@ export class GridTemplateRenderer extends rendererBase(SDK.CSSPropertyParserMatc
1575
1575
 
1576
1576
  const indent = Common.Settings.Settings.instance().moduleSetting('text-editor-indent').get();
1577
1577
  const container = document.createDocumentFragment();
1578
- for (const line of match.lines) {
1579
- const value = Renderer.render(line, context);
1580
- const lineBreak = UI.Fragment.html`<br /><span class='styles-clipboard-only'>${indent.repeat(2)}</span>`;
1581
- container.append(lineBreak, ...value.nodes);
1582
- }
1578
+
1579
+ const template = html`
1580
+ ${match.lines.map(line => {
1581
+ const lines = Renderer.render(line, context).nodes;
1582
+ return html`
1583
+ <span class='styles-clipboard-only'>${indent.repeat(2)}</span>
1584
+ ${lines}`;
1585
+ })}
1586
+ `;
1587
+
1588
+ render(template, container);
1583
1589
  return [container];
1584
1590
  }
1585
1591
  }
@@ -6,6 +6,6 @@
6
6
  @scope to (devtools-widget > *) {
7
7
  .stack-trace {
8
8
  font-size: 11px !important; /* stylelint-disable-line declaration-no-important */
9
- font-family: Menlo, monospace;
9
+ font-family: Menlo, var(--monospace-font-family);
10
10
  }
11
11
  }