chrome-devtools-frontend 1.0.1597624 → 1.0.1598808

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 (79) hide show
  1. package/docs/ui_engineering.md +6 -6
  2. package/front_end/core/host/AidaClient.ts +2 -0
  3. package/front_end/models/ai_assistance/AiConversation.ts +16 -14
  4. package/front_end/models/ai_assistance/agents/AiAgent.ts +14 -4
  5. package/front_end/models/ai_assistance/agents/ConversationSummaryAgent.ts +109 -0
  6. package/front_end/models/ai_assistance/agents/PerformanceAgent.ts +30 -1
  7. package/front_end/models/ai_assistance/ai_assistance.ts +2 -0
  8. package/front_end/panels/ai_assistance/AiAssistancePanel.ts +6 -9
  9. package/front_end/panels/ai_assistance/PatchWidget.ts +1 -1
  10. package/front_end/panels/ai_assistance/components/ChatMessage.ts +53 -29
  11. package/front_end/panels/ai_assistance/components/ChatView.ts +1 -1
  12. package/front_end/panels/application/AppManifestView.ts +2 -2
  13. package/front_end/panels/application/CookieItemsView.ts +9 -15
  14. package/front_end/panels/application/DeviceBoundSessionsView.ts +3 -5
  15. package/front_end/panels/application/FrameDetailsView.ts +2 -2
  16. package/front_end/panels/application/ReportingApiView.ts +2 -2
  17. package/front_end/panels/application/preloading/PreloadingView.ts +18 -3
  18. package/front_end/panels/changes/ChangesView.ts +7 -11
  19. package/front_end/panels/coverage/CoverageView.ts +4 -4
  20. package/front_end/panels/css_overview/CSSOverviewCompletedView.ts +3 -3
  21. package/front_end/panels/elements/ElementsTreeOutline.ts +34 -8
  22. package/front_end/panels/elements/StylePropertiesSection.ts +129 -1
  23. package/front_end/panels/elements/StylePropertyHighlighter.ts +3 -0
  24. package/front_end/panels/elements/stylesSidebarPane.css +34 -0
  25. package/front_end/panels/issues/AffectedSelectivePermissionsInterventionView.ts +3 -5
  26. package/front_end/panels/layer_viewer/LayerDetailsView.ts +2 -1
  27. package/front_end/panels/network/RequestConditionsDrawer.ts +4 -4
  28. package/front_end/panels/network/RequestCookiesView.ts +2 -2
  29. package/front_end/panels/network/RequestHeadersView.ts +4 -4
  30. package/front_end/panels/network/RequestResponseView.ts +2 -2
  31. package/front_end/panels/performance_monitor/PerformanceMonitor.ts +2 -2
  32. package/front_end/panels/protocol_monitor/ProtocolMonitor.ts +3 -3
  33. package/front_end/panels/recorder/components/StepView.ts +2 -1
  34. package/front_end/panels/search/SearchView.ts +2 -2
  35. package/front_end/panels/timeline/TimelineDetailsView.ts +8 -6
  36. package/front_end/panels/timeline/components/SidebarSingleInsightSet.ts +2 -7
  37. package/front_end/panels/timeline/components/insights/CharacterSet.ts +2 -6
  38. package/front_end/panels/timeline/components/insights/EventRef.ts +2 -2
  39. package/front_end/panels/webauthn/WebauthnPane.ts +2 -2
  40. package/front_end/third_party/chromium/README.chromium +1 -1
  41. package/front_end/third_party/puppeteer/README.chromium +2 -2
  42. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/Realm.js +1 -1
  43. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/Realm.js.map +1 -1
  44. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/util.d.ts +4 -0
  45. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/util.d.ts.map +1 -1
  46. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/util.js +13 -0
  47. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/util.js.map +1 -1
  48. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/injected/injected.d.ts +1 -1
  49. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/revisions.d.ts +3 -3
  50. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/revisions.js +3 -3
  51. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/revisions.js.map +1 -1
  52. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/Mutex.d.ts +2 -2
  53. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/version.d.ts +1 -1
  54. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/version.js +1 -1
  55. package/front_end/third_party/puppeteer/package/lib/cjs/third_party/mitt/mitt.js +2 -2
  56. package/front_end/third_party/puppeteer/package/lib/cjs/third_party/parsel-js/parsel-js.js +1 -1
  57. package/front_end/third_party/puppeteer/package/lib/cjs/third_party/rxjs/rxjs.js +446 -446
  58. package/front_end/third_party/puppeteer/package/lib/es5-iife/puppeteer-core-browser.js +166 -167
  59. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/Realm.js +2 -2
  60. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/Realm.js.map +1 -1
  61. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/util.d.ts +4 -0
  62. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/util.d.ts.map +1 -1
  63. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/util.js +12 -0
  64. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/util.js.map +1 -1
  65. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/revisions.d.ts +3 -3
  66. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/revisions.js +3 -3
  67. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/revisions.js.map +1 -1
  68. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/util/version.d.ts +1 -1
  69. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/util/version.js +1 -1
  70. package/front_end/third_party/puppeteer/package/lib/esm/third_party/mitt/mitt.js +1 -1
  71. package/front_end/third_party/puppeteer/package/lib/esm/third_party/parsel-js/parsel-js.js +1 -1
  72. package/front_end/third_party/puppeteer/package/lib/esm/third_party/rxjs/rxjs.js +97 -97
  73. package/front_end/third_party/puppeteer/package/package.json +1 -1
  74. package/front_end/third_party/puppeteer/package/src/bidi/Realm.ts +2 -2
  75. package/front_end/third_party/puppeteer/package/src/bidi/util.ts +17 -0
  76. package/front_end/third_party/puppeteer/package/src/revisions.ts +3 -3
  77. package/front_end/third_party/puppeteer/package/src/util/version.ts +1 -1
  78. package/front_end/ui/legacy/Widget.ts +42 -6
  79. package/package.json +1 -1
@@ -28,6 +28,7 @@ import preloadingViewStyles from './preloadingView.css.js';
28
28
  import preloadingViewDropDownStyles from './preloadingViewDropDown.css.js';
29
29
 
30
30
  const {createRef, ref} = Directives;
31
+ const {widget} = UI.Widget;
31
32
 
32
33
  const UIStrings = {
33
34
  /**
@@ -266,7 +267,7 @@ export class PreloadingRuleSetView extends UI.Widget.VBox {
266
267
  <div slot="main" ${ref(this.ruleSetGridContainerRef)}>
267
268
  </div>
268
269
  <div slot="sidebar" jslog=${VisualLogging.section('rule-set-details')}>
269
- <devtools-widget .widgetConfig=${UI.Widget.widgetConfig(PreloadingComponents.RuleSetDetailsView.RuleSetDetailsView, {
270
+ <devtools-widget ${widget(PreloadingComponents.RuleSetDetailsView.RuleSetDetailsView, {
270
271
  ruleSet: this.getRuleSet(),
271
272
  shouldPrettyPrint: this.shouldPrettyPrint,
272
273
  })} ${ref(this.ruleSetDetailsRef)}></devtools-widget>
@@ -448,6 +449,7 @@ export class PreloadingAttemptView extends UI.Widget.VBox {
448
449
  new PreloadingComponents.PreloadingDetailsReportView.PreloadingDetailsReportView();
449
450
  private readonly ruleSetSelector: PreloadingRuleSetSelector;
450
451
  private readonly textFilterUI: UI.Toolbar.ToolbarFilter;
452
+ private hsplit?: UI.SplitWidget.SplitWidget;
451
453
  private clearButton: UI.Toolbar.ToolbarButton;
452
454
 
453
455
  constructor(model: SDK.PreloadingModel.PreloadingModel) {
@@ -541,7 +543,12 @@ export class PreloadingAttemptView extends UI.Widget.VBox {
541
543
  >${i18nString(UIStrings.learnMore)}</devtools-link>
542
544
  </div>
543
545
  </div>
544
- <devtools-split-view sidebar-position="second">
546
+ <devtools-split-view sidebar-position="second" ${
547
+ UI.Widget.widgetRef(
548
+ UI.SplitWidget.SplitWidget,
549
+ w => {
550
+ this.hsplit = w;
551
+ })}>
545
552
  <div slot="main" class="overflow-auto" style="height: 100%">
546
553
  ${preloadingGridContainer}
547
554
  </div>
@@ -632,7 +639,15 @@ export class PreloadingAttemptView extends UI.Widget.VBox {
632
639
  this.preloadingGrid.rows = filteredRows;
633
640
  this.preloadingGrid.pageURL = pageURL();
634
641
  // Only show empty state when there are truly no speculations (not when filter has no matches)
635
- this.contentElement.classList.toggle('empty', rows.length === 0);
642
+ const wasEmpty = this.contentElement.classList.contains('empty');
643
+ const isEmpty = rows.length === 0;
644
+ this.contentElement.classList.toggle('empty', isEmpty);
645
+
646
+ // When this view starts empty, the split view gets laid out while hidden and collapses
647
+ // its sidebar to 0. Re-layout once it becomes visible.
648
+ if (wasEmpty && !isEmpty) {
649
+ this.hsplit?.doLayout();
650
+ }
636
651
 
637
652
  this.updatePreloadingDetails();
638
653
  }
@@ -58,11 +58,11 @@ export const DEFAULT_VIEW: View = (input, _output, target) => {
58
58
  <div class=vbox slot="main">
59
59
  <devtools-widget
60
60
  ?hidden=${input.workspaceDiff.modifiedUISourceCodes().length > 0}
61
- .widgetConfig=${UI.Widget.widgetConfig(UI.EmptyWidget.EmptyWidget, {
62
- header: i18nString(UIStrings.noChanges),
63
- text: i18nString(UIStrings.changesViewDescription),
64
- link: CHANGES_VIEW_URL,
65
- })}>
61
+ ${widget(UI.EmptyWidget.EmptyWidget, {
62
+ header: i18nString(UIStrings.noChanges),
63
+ text: i18nString(UIStrings.changesViewDescription),
64
+ link: CHANGES_VIEW_URL,
65
+ })}>
66
66
  </devtools-widget>
67
67
  <div class=diff-container role=tabpanel ?hidden=${input.workspaceDiff.modifiedUISourceCodes().length === 0}>
68
68
  ${widget(CombinedDiffView.CombinedDiffView, {
@@ -72,18 +72,14 @@ export const DEFAULT_VIEW: View = (input, _output, target) => {
72
72
  </div>
73
73
  ${hasCopyToPrompt ? html`
74
74
  <devtools-widget class="copy-to-prompt"
75
- .widgetConfig=${UI.Widget.widgetConfig(PanelsCommon.CopyChangesToPrompt, {
75
+ ${widget(PanelsCommon.CopyChangesToPrompt, {
76
76
  workspaceDiff: input.workspaceDiff,
77
77
  patchAgentCSSChange: null,
78
78
  })}
79
79
  ></devtools-widget>
80
80
  ` : Lit.nothing}
81
81
  </div>
82
- <devtools-widget
83
- slot="sidebar"
84
- .widgetConfig=${UI.Widget.widgetConfig(ChangesSidebar, {
85
- workspaceDiff: input.workspaceDiff
86
- })}
82
+ <devtools-widget slot="sidebar" ${widget(ChangesSidebar, {workspaceDiff: input.workspaceDiff})}
87
83
  ${UI.Widget.widgetRef(ChangesSidebar, onSidebar)}>
88
84
  </devtools-widget>
89
85
  </devtools-split-view>`,
@@ -127,7 +127,7 @@ const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
127
127
  const i18nTemplate = unboundI18nTemplate.bind(undefined, str_);
128
128
  const {ref} = Directives;
129
129
  const {bindToAction, bindToSetting} = UI.UIUtils;
130
- const {widgetConfig} = UI.Widget;
130
+ const {widget} = UI.Widget;
131
131
 
132
132
  let coverageViewInstance: CoverageView|undefined;
133
133
 
@@ -222,7 +222,7 @@ export const DEFAULT_VIEW: View = (input, output, target) => {
222
222
  i18nString(UIStrings.bfcacheNoCapture) : i18nString(UIStrings.activationNoCapture),
223
223
  input.needsReload)
224
224
  : input.coverageInfo ? html`
225
- <devtools-widget autofocus class="results" .widgetConfig=${widgetConfig(CoverageListView, {
225
+ <devtools-widget autofocus class="results" ${widget(CoverageListView, {
226
226
  coverageInfo: input.coverageInfo,
227
227
  highlightRegExp: input.textFilter,
228
228
  selectedUrl: input.selectedUrl,
@@ -242,7 +242,7 @@ function renderLandingPage(supportsRecordOnReload: boolean): TemplateResult {
242
242
  if (supportsRecordOnReload) {
243
243
  // clang-format off
244
244
  return html`
245
- <devtools-widget .widgetConfig=${widgetConfig(UI.EmptyWidget.EmptyWidget,{
245
+ <devtools-widget ${widget(UI.EmptyWidget.EmptyWidget,{
246
246
  header: i18nString(UIStrings.noCoverageData),
247
247
  link: 'https://developer.chrome.com/docs/devtools/coverage' as Platform.DevToolsPath.UrlString,
248
248
  text: i18nString(UIStrings.clickTheReloadButtonSToReloadAnd, {PH1: i18nString(UIStrings.reloadPage)}),
@@ -256,7 +256,7 @@ function renderLandingPage(supportsRecordOnReload: boolean): TemplateResult {
256
256
  }
257
257
  // clang-format off
258
258
  return html`
259
- <devtools-widget .widgetConfig=${widgetConfig(UI.EmptyWidget.EmptyWidget,{
259
+ <devtools-widget ${widget(UI.EmptyWidget.EmptyWidget,{
260
260
  header: i18nString(UIStrings.noCoverageData),
261
261
  link: 'https://developer.chrome.com/docs/devtools/coverage' as Platform.DevToolsPath.UrlString,
262
262
  text: i18nString(UIStrings.clickTheRecordButtonSToStart, {PH1: i18nString(UIStrings.startRecording)}),
@@ -25,7 +25,7 @@ import {CSSOverviewSidebarPanel} from './CSSOverviewSidebarPanel.js';
25
25
  import type {UnusedDeclaration} from './CSSOverviewUnusedDeclarations.js';
26
26
 
27
27
  const {styleMap, ref} = Directives;
28
- const {widgetConfig} = UI.Widget;
28
+ const {widget} = UI.Widget;
29
29
 
30
30
  const UIStrings = {
31
31
  /**
@@ -281,7 +281,7 @@ export const DEFAULT_VIEW: View = (input, output, target) => {
281
281
  render(html`
282
282
  <style>${cssOverviewCompletedViewStyles}</style>
283
283
  <devtools-split-view direction="column" sidebar-position="first" sidebar-initial-size="200">
284
- <devtools-widget slot="sidebar" .widgetConfig=${widgetConfig(CSSOverviewSidebarPanel, {
284
+ <devtools-widget slot="sidebar" ${widget(CSSOverviewSidebarPanel, {
285
285
  minimumSize: new Geometry.Size(100, 25),
286
286
  items: [
287
287
  {name: i18nString(UIStrings.overviewSummary), id: 'summary'},
@@ -325,7 +325,7 @@ export const DEFAULT_VIEW: View = (input, output, target) => {
325
325
  ${renderMediaQueries(input.mediaQueries)}
326
326
  </div>
327
327
  </div>
328
- <devtools-widget slot="sidebar" .widgetConfig=${widgetConfig(e => {
328
+ <devtools-widget slot="sidebar" ${widget(e => {
329
329
  const tabbedPane = new UI.TabbedPane.TabbedPane(e);
330
330
  output.closeAllTabs = () => { tabbedPane.closeTabs(tabbedPane.tabIds()); };
331
331
  output.addTab = (id: string, tabTitle: string, view: UI.Widget.Widget, jslogContext: string) => {
@@ -137,6 +137,13 @@ export const DEFAULT_VIEW = (input: ViewInput, output: ViewOutput, target: HTMLE
137
137
  target.appendChild(output.elementsTreeOutline.element);
138
138
  }
139
139
 
140
+ output.elementsTreeOutline.maxTreeDepth = input.maxTreeDepth;
141
+ output.elementsTreeOutline.enableContextMenu = input.enableContextMenu ?? true;
142
+ output.elementsTreeOutline.showComments = input.showComments ?? true;
143
+ output.elementsTreeOutline.showAIButton = input.showAIButton ?? true;
144
+ output.elementsTreeOutline.disableEdits = input.disableEdits ?? false;
145
+ output.elementsTreeOutline.expandRoot = input.expandRoot ?? false;
146
+
140
147
  if (input.visibleWidth !== undefined) {
141
148
  output.elementsTreeOutline.setVisibleWidth(input.visibleWidth);
142
149
  }
@@ -223,6 +230,7 @@ export class DOMTreeWidget extends UI.Widget.Widget {
223
230
  #showComments = true;
224
231
  #showAIButton = true;
225
232
  #disableEdits = false;
233
+ #expandRoot = false;
226
234
  #visible = false;
227
235
  #visibleWidth?: number;
228
236
  #wrap = false;
@@ -291,6 +299,15 @@ export class DOMTreeWidget extends UI.Widget.Widget {
291
299
  this.performUpdate();
292
300
  }
293
301
 
302
+ get expandRoot(): boolean {
303
+ return this.#expandRoot;
304
+ }
305
+
306
+ set expandRoot(expandRoot: boolean) {
307
+ this.#expandRoot = expandRoot;
308
+ this.performUpdate();
309
+ }
310
+
294
311
  #currentHighlightedNode: SDK.DOMModel.DOMNode|null = null;
295
312
 
296
313
  #view: View;
@@ -347,11 +364,19 @@ export class DOMTreeWidget extends UI.Widget.Widget {
347
364
  this.#viewOutput?.elementsTreeOutline?.highlightNodeAttribute(node, attribute);
348
365
  }
349
366
 
350
- setWordWrap(wrap: boolean): void {
367
+ get wrap(): boolean {
368
+ return this.#wrap;
369
+ }
370
+
371
+ set wrap(wrap: boolean) {
351
372
  this.#wrap = wrap;
352
373
  this.performUpdate();
353
374
  }
354
375
 
376
+ setWordWrap(wrap: boolean): void {
377
+ this.wrap = wrap;
378
+ }
379
+
355
380
  selectedDOMNode(): SDK.DOMModel.DOMNode|null {
356
381
  return this.#viewOutput.elementsTreeOutline?.selectedDOMNode() ?? null;
357
382
  }
@@ -387,6 +412,7 @@ export class DOMTreeWidget extends UI.Widget.Widget {
387
412
  showComments: this.#showComments,
388
413
  showAIButton: this.#showAIButton,
389
414
  disableEdits: this.#disableEdits,
415
+ expandRoot: this.#expandRoot,
390
416
  visibleWidth: this.#visibleWidth,
391
417
  visible: this.#visible,
392
418
  wrap: this.#wrap,
@@ -565,12 +591,12 @@ export class ElementsTreeOutline extends
565
591
  #issuesManager?: IssuesManager.IssuesManager.IssuesManager;
566
592
  #popupHelper?: UI.PopoverHelper.PopoverHelper;
567
593
  #nodeElementToIssues = new Map<Element, IssuesManager.Issue.Issue[]>();
568
- readonly maxTreeDepth?: number;
569
- readonly enableContextMenu: boolean;
570
- readonly showComments: boolean;
571
- readonly showAIButton: boolean;
572
- readonly disableEdits: boolean;
573
- readonly expandRoot: boolean;
594
+ maxTreeDepth?: number;
595
+ enableContextMenu: boolean;
596
+ showComments: boolean;
597
+ showAIButton: boolean;
598
+ disableEdits: boolean;
599
+ expandRoot: boolean;
574
600
 
575
601
  constructor(
576
602
  omitRootDOMNode?: boolean, selectEnabled?: boolean, hideGutter?: boolean, maxTreeDepth?: number,
@@ -1388,7 +1414,7 @@ export class ElementsTreeOutline extends
1388
1414
  }
1389
1415
 
1390
1416
  async showContextMenu(treeElement: ElementsTreeElement, event: Event): Promise<void> {
1391
- if (UI.UIUtils.isEditing()) {
1417
+ if (UI.UIUtils.isEditing() || !this.enableContextMenu) {
1392
1418
  return;
1393
1419
  }
1394
1420
 
@@ -47,6 +47,7 @@ import * as ComputedStyle from '../../models/computed_style/computed_style.js';
47
47
  import * as TextUtils from '../../models/text_utils/text_utils.js';
48
48
  import * as Buttons from '../../ui/components/buttons/buttons.js';
49
49
  import * as Tooltips from '../../ui/components/tooltips/tooltips.js';
50
+ import {createIcon, type Icon} from '../../ui/kit/kit.js';
50
51
  import type * as Components from '../../ui/legacy/components/utils/utils.js';
51
52
  import * as UI from '../../ui/legacy/legacy.js';
52
53
  import * as VisualLogging from '../../ui/visual_logging/visual_logging.js';
@@ -115,6 +116,14 @@ const UIStrings = {
115
116
  * @example {(0,0,1)} PH1
116
117
  */
117
118
  specificity: 'Specificity: {PH1}',
119
+ /**
120
+ * @description Accessibility label for the button that expands a collapsed CSS rule in the Styles pane.
121
+ */
122
+ expandCollapsedRule: 'Expand collapsed rule',
123
+ /**
124
+ * @description Accessibility label for the button that collapses an expanded CSS rule in the Styles pane.
125
+ */
126
+ collapseExpandedRule: 'Collapse expanded rule',
118
127
  } as const;
119
128
 
120
129
  const str_ = i18n.i18n.registerUIStrings('panels/elements/StylePropertiesSection.ts', UIStrings);
@@ -151,6 +160,8 @@ export class StylePropertiesSection {
151
160
  protected readonly selectorRefElement: HTMLElement;
152
161
  private hoverableSelectorsMode: boolean;
153
162
  #isHidden: boolean;
163
+ #isCollapsed: boolean;
164
+ #collapseIcon: Icon|null = null;
154
165
  protected customPopulateCallback: () => void;
155
166
 
156
167
  nestingLevel = 0;
@@ -228,6 +239,25 @@ export class StylePropertiesSection {
228
239
  const selectorContainer = document.createElement('div');
229
240
  selectorContainer.createChild('span', 'styles-clipboard-only').textContent = indent.repeat(this.nestingLevel);
230
241
  selectorContainer.classList.add('selector-container');
242
+
243
+ // Collapse/expand toggle icon for rules that can be manually toggled.
244
+ this.#collapseIcon = createIcon('triangle-right', 'section-collapse-icon');
245
+ UI.ARIAUtils.markAsButton(this.#collapseIcon);
246
+ UI.ARIAUtils.setControls(this.#collapseIcon, this.propertiesTreeOutline.element);
247
+ this.#collapseIcon.tabIndex = 0;
248
+ this.#collapseIcon.addEventListener('click', (event: Event) => {
249
+ event.consume(true);
250
+ this.#toggleCollapsed();
251
+ });
252
+ this.#collapseIcon.addEventListener('keydown', (event: KeyboardEvent) => {
253
+ if (!Platform.KeyboardUtilities.isEnterOrSpaceKey(event)) {
254
+ return;
255
+ }
256
+ event.consume(true);
257
+ this.#toggleCollapsed();
258
+ });
259
+ selectorContainer.appendChild(this.#collapseIcon);
260
+
231
261
  this.selectorElement = document.createElement('span');
232
262
  UI.ARIAUtils.setLabel(this.selectorElement, i18nString(UIStrings.cssSelector));
233
263
  this.selectorElement.classList.add('selector');
@@ -324,8 +354,10 @@ export class StylePropertiesSection {
324
354
  }
325
355
  this.hoverableSelectorsMode = false;
326
356
  this.#isHidden = false;
357
+ this.#isCollapsed = false;
327
358
  this.markSelectorMatches();
328
359
  this.onpopulate();
360
+ this.#updateCollapsedState();
329
361
 
330
362
  this.stylesContainer.computedStyleModel().addEventListener(
331
363
  ComputedStyle.ComputedStyleModel.Events.CSS_MODEL_CHANGED, this.#onCSSModelChanged, this);
@@ -620,9 +652,18 @@ export class StylePropertiesSection {
620
652
  keyboardEvent.metaKey) {
621
653
  return;
622
654
  }
655
+
656
+ // Only handle section-level edit shortcuts when the event originated from
657
+ // the section itself. Child editors dispatch key events that bubble up to
658
+ // the section, and we must not treat those as section shortcuts.
659
+ const isSectionLevelShortcut = keyboardEvent.target === this.element;
660
+
623
661
  switch (keyboardEvent.key) {
624
662
  case 'Enter':
625
663
  case ' ':
664
+ if (!isSectionLevelShortcut) {
665
+ return;
666
+ }
626
667
  this.startEditingAtFirstPosition();
627
668
  keyboardEvent.consume(true);
628
669
  break;
@@ -634,7 +675,7 @@ export class StylePropertiesSection {
634
675
  break;
635
676
  default:
636
677
  // Filter out non-printable key strokes.
637
- if (keyboardEvent.key.length === 1) {
678
+ if (isSectionLevelShortcut && keyboardEvent.key.length === 1) {
638
679
  this.addNewBlankProperty(0).startEditingName();
639
680
  }
640
681
  break;
@@ -1333,6 +1374,82 @@ export class StylePropertiesSection {
1333
1374
  return this.#isHidden;
1334
1375
  }
1335
1376
 
1377
+ /**
1378
+ * Checks whether the section should be collapsed because it does not
1379
+ * contribute any active styles. A section is collapsed when:
1380
+ * - It has no leading properties (empty rule), OR
1381
+ * - All its leading properties have PropertyState.OVERLOADED
1382
+ *
1383
+ * Sections containing only user-disabled properties are NOT collapsed,
1384
+ * since the user intentionally toggled them off and they should remain visible.
1385
+ */
1386
+ #shouldCollapse(): boolean {
1387
+ const style = this.styleInternal;
1388
+ const properties = style.leadingProperties();
1389
+
1390
+ // Never collapse the element's own inline style section.
1391
+ if (style.type === SDK.CSSStyleDeclaration.Type.Inline) {
1392
+ return false;
1393
+ }
1394
+
1395
+ // Empty rule (e.g. from CSS nesting): collapse.
1396
+ if (properties.length === 0) {
1397
+ return true;
1398
+ }
1399
+
1400
+ // If any property is user-disabled (toggled off via checkbox), do not
1401
+ // collapse. The user deliberately disabled them.
1402
+ const hasDisabledProperty = properties.some(p => p.disabled);
1403
+ if (hasDisabledProperty) {
1404
+ return false;
1405
+ }
1406
+
1407
+ // If every property is OVERLOADED, collapse.
1408
+ const allOverloaded =
1409
+ properties.every(p => this.matchedStyles.propertyState(p) === SDK.CSSMatchedStyles.PropertyState.OVERLOADED);
1410
+ return allOverloaded;
1411
+ }
1412
+
1413
+ #updateCollapsedState(): void {
1414
+ const shouldCollapse = this.#shouldCollapse();
1415
+ // Mark as collapsible so the toggle icon is always shown for
1416
+ // sections that can be collapsed, even after manual expansion.
1417
+ this.element.classList.toggle('collapsible', shouldCollapse);
1418
+ this.#setCollapsed(shouldCollapse);
1419
+ }
1420
+
1421
+ #setCollapsed(collapsed: boolean): void {
1422
+ this.#isCollapsed = collapsed;
1423
+ this.element.classList.toggle('collapsed', collapsed);
1424
+ this.propertiesTreeOutline.element.classList.toggle('hidden', collapsed);
1425
+ if (this.#collapseIcon) {
1426
+ this.#collapseIcon.name = collapsed ? 'triangle-right' : 'triangle-down';
1427
+ UI.ARIAUtils.setExpanded(this.#collapseIcon, !collapsed);
1428
+ UI.ARIAUtils.setLabel(
1429
+ this.#collapseIcon,
1430
+ collapsed ? i18nString(UIStrings.expandCollapsedRule) : i18nString(UIStrings.collapseExpandedRule));
1431
+ }
1432
+ }
1433
+
1434
+ #toggleCollapsed(): void {
1435
+ this.#setCollapsed(!this.#isCollapsed);
1436
+ }
1437
+
1438
+ /**
1439
+ * Expand a collapsed section, e.g. when navigating to a property
1440
+ * via a var() link.
1441
+ */
1442
+ expand(): void {
1443
+ if (!this.#isCollapsed) {
1444
+ return;
1445
+ }
1446
+ this.#setCollapsed(false);
1447
+ }
1448
+
1449
+ isCollapsed(): boolean {
1450
+ return this.#isCollapsed;
1451
+ }
1452
+
1336
1453
  markSelectorMatches(): void {
1337
1454
  const rule = this.styleInternal.parentRule;
1338
1455
  if (!rule || !(rule instanceof SDK.CSSRule.CSSStyleRule)) {
@@ -1395,6 +1512,10 @@ export class StylePropertiesSection {
1395
1512
 
1396
1513
  addNewBlankProperty(index: number|undefined = this.propertiesTreeOutline.rootElement().childCount()):
1397
1514
  StylePropertyTreeElement {
1515
+ if (this.#isCollapsed) {
1516
+ this.expand();
1517
+ }
1518
+
1398
1519
  const property = this.styleInternal.newBlankProperty(index);
1399
1520
  const item = new StylePropertyTreeElement({
1400
1521
  stylesContainer: this.stylesContainer,
@@ -1416,6 +1537,13 @@ export class StylePropertiesSection {
1416
1537
  }
1417
1538
 
1418
1539
  private handleEmptySpaceClick(event: Event): void {
1540
+ // If collapsed, clicking anywhere on the section should expand it.
1541
+ if (this.#isCollapsed) {
1542
+ this.#toggleCollapsed();
1543
+ event.consume(true);
1544
+ return;
1545
+ }
1546
+
1419
1547
  // `this.willCauseCancelEditing` is a hacky way to understand whether we should
1420
1548
  // create a new property or not on empty space click.
1421
1549
  // For empty space clicks, the order of events are:
@@ -24,6 +24,7 @@ export class StylePropertyHighlighter {
24
24
  if (!section) {
25
25
  return;
26
26
  }
27
+ section.expand();
27
28
  section.showAllItems();
28
29
  const populatePromises: Array<Promise<void>> = [];
29
30
  for (let treeElement = section.propertiesTreeOutline.firstChild(); treeElement;
@@ -57,6 +58,7 @@ export class StylePropertyHighlighter {
57
58
  return;
58
59
  }
59
60
  block.expand(true);
61
+ section.expand();
60
62
  section.showAllItems();
61
63
  PanelUtils.highlightElement(section.element);
62
64
  }
@@ -78,6 +80,7 @@ export class StylePropertyHighlighter {
78
80
  continue;
79
81
  }
80
82
  block?.expand(true);
83
+ section.expand();
81
84
  section.showAllItems();
82
85
  const treeElement = this.findTreeElementFromSection(
83
86
  treeElement => treeElement.property.name === propertyName && !treeElement.overloaded(), section);
@@ -94,6 +94,40 @@
94
94
  --override-styles-section-text-hover-text-decoration: underline;
95
95
  --override-styles-section-text-hover-cursor: default;
96
96
  }
97
+
98
+ & .section-collapse-icon {
99
+ width: 12px;
100
+ height: 12px;
101
+ margin-right: 2px;
102
+ margin-left: -2px;
103
+ vertical-align: middle;
104
+ cursor: pointer;
105
+ display: none;
106
+ }
107
+
108
+ &.collapsible .section-collapse-icon {
109
+ display: inline-block;
110
+ }
111
+
112
+ &.collapsed {
113
+ opacity: 60%;
114
+
115
+ & .style-properties {
116
+ display: none;
117
+ }
118
+
119
+ & .styles-section-subtitle {
120
+ display: none;
121
+ }
122
+
123
+ & .sidebar-pane-closing-brace {
124
+ display: none;
125
+ }
126
+
127
+ & .sidebar-pane-open-brace::after {
128
+ content: "}";
129
+ }
130
+ }
97
131
  }
98
132
 
99
133
  .sidebar-pane-closing-brace {
@@ -42,6 +42,7 @@ const UIStrings = {
42
42
  } as const;
43
43
  const str_ = i18n.i18n.registerUIStrings('panels/issues/AffectedSelectivePermissionsInterventionView.ts', UIStrings);
44
44
  const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
45
+ const {widget} = UI.Widget;
45
46
 
46
47
  export class AffectedSelectivePermissionsInterventionView extends AffectedResourcesView {
47
48
  readonly #linkifier = new Components.Linkifier.Linkifier();
@@ -103,13 +104,10 @@ export class AffectedSelectivePermissionsInterventionView extends AffectedResour
103
104
  const debuggerWorkspaceBinding = Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding.instance();
104
105
  const stackTraceTranslated =
105
106
  await debuggerWorkspaceBinding.createStackTraceFromProtocolRuntime(stackTrace, issuesModel.target());
106
- return html`
107
- <devtools-widget .widgetConfig=${UI.Widget.widgetConfig(Components.JSPresentationUtils.StackTracePreviewContent, {
107
+ return html`${widget(Components.JSPresentationUtils.StackTracePreviewContent, {
108
108
  stackTrace: stackTraceTranslated,
109
109
  options: {expandable: true},
110
- })}>
111
- </devtools-widget>
112
- `;
110
+ })}`;
113
111
  }
114
112
 
115
113
  update(): void {
@@ -20,6 +20,7 @@ import {
20
20
  } from './LayerViewHost.js';
21
21
 
22
22
  const {html, nothing} = Lit;
23
+ const {widget} = UI.Widget;
23
24
 
24
25
  const UIStrings = {
25
26
  /**
@@ -157,7 +158,7 @@ export const DEFAULT_VIEW = (input: ViewInput, _output: ViewOutput, target: HTML
157
158
  if (!layer) {
158
159
  // clang-format off
159
160
  Lit.render(html`<div class="layer-details-container">
160
- <devtools-widget class="learn-more" .widgetConfig=${UI.Widget.widgetConfig(UI.EmptyWidget.EmptyWidget, {
161
+ <devtools-widget class="learn-more" ${widget(UI.EmptyWidget.EmptyWidget, {
161
162
  header: i18nString(UIStrings.noLayerSelected),
162
163
  text: i18nString(UIStrings.selectALayerToSeeItsDetails)})}>
163
164
  </devtools-widget>
@@ -24,7 +24,7 @@ import * as PanelUtils from '../utils/utils.js';
24
24
  import requestConditionsDrawerStyles from './requestConditionsDrawer.css.js';
25
25
 
26
26
  const {ref, live} = Directives;
27
- const {widgetConfig} = UI.Widget;
27
+ const {widget} = UI.Widget;
28
28
 
29
29
  const UIStrings = {
30
30
  /**
@@ -175,7 +175,7 @@ export const DEFAULT_VIEW: View = (input, output, target) => {
175
175
  ${i18nString(UIStrings.addRule)}
176
176
  </devtools-button>
177
177
  </div>
178
- <devtools-widget .widgetConfig=${UI.Widget.widgetConfig(UI.Widget.VBox)}>
178
+ <devtools-widget ${widget(UI.Widget.VBox)}>
179
179
  ${input.list.element}
180
180
  </devtools-widget>
181
181
  `,
@@ -271,7 +271,7 @@ function renderItem(
271
271
  <devtools-widget
272
272
  class=conditions-selector
273
273
  title=${i18nString(UIStrings.requestConditionsLabel)}
274
- .widgetConfig=${UI.Widget.widgetConfig(
274
+ ${widget(
275
275
  MobileThrottling.NetworkThrottlingSelector.NetworkThrottlingSelectorWidget, {
276
276
  variant:
277
277
  MobileThrottling.NetworkThrottlingSelector.NetworkThrottlingSelect.Variant.INDIVIDUAL_REQUEST_CONDITIONS,
@@ -282,7 +282,7 @@ function renderItem(
282
282
  })}></devtools-widget>
283
283
  <devtools-widget
284
284
  ?disabled=${!editable || !originalOrUpgradedURLPattern}
285
- .widgetConfig=${widgetConfig(AffectedCountWidget, {condition, lookUpRequestCount})}></devtools-widget>`;
285
+ ${widget(AffectedCountWidget, {condition, lookUpRequestCount})}></devtools-widget>`;
286
286
  // clang-format on
287
287
  }
288
288
 
@@ -113,7 +113,7 @@ export const DEFAULT_VIEW: ViewFunction = (input, _output, target) => {
113
113
  </div>
114
114
 
115
115
  ${input.requestCookies.cookies.length > 0 ? html`
116
- <devtools-widget .widgetConfig=${UI.Widget.widgetConfig(CookieTable.CookiesTable.CookiesTable, {
116
+ <devtools-widget ${widget(CookieTable.CookiesTable.CookiesTable, {
117
117
  cookiesData: input.requestCookies,
118
118
  inline: true
119
119
  })} class="cookie-table cookies-panel-item"></devtools-widget>
@@ -131,7 +131,7 @@ export const DEFAULT_VIEW: ViewFunction = (input, _output, target) => {
131
131
  </div>
132
132
 
133
133
  ${input.responseCookies.cookies.length ? html`
134
- <devtools-widget .widgetConfig=${UI.Widget.widgetConfig(CookieTable.CookiesTable.CookiesTable, {
134
+ <devtools-widget ${widget(CookieTable.CookiesTable.CookiesTable, {
135
135
  cookiesData: input.responseCookies,
136
136
  inline: true })} class="cookie-table cookies-panel-item"></devtools-widget>
137
137
  ` : Lit.nothing}
@@ -21,6 +21,7 @@ import * as NetworkComponents from './components/components.js';
21
21
  import {ShowMoreDetailsWidget} from './ShowMoreDetailsWidget.js';
22
22
 
23
23
  const {render, html} = Lit;
24
+ const {widget} = UI.Widget;
24
25
 
25
26
  const UIStrings = {
26
27
  /**
@@ -210,8 +211,7 @@ export const DEFAULT_VIEW: View = (input, output, target) => {
210
211
  contents: (input.showRequestHeadersText && requestHeadersText) ?
211
212
  renderRawHeaders(requestHeadersText) :
212
213
  html`
213
- <devtools-widget .widgetConfig=${
214
- UI.Widget.widgetConfig(NetworkComponents.RequestHeaderSection.RequestHeaderSection, {
214
+ <devtools-widget ${widget(NetworkComponents.RequestHeaderSection.RequestHeaderSection, {
215
215
  request: input.request,
216
216
  toReveal: input.toReveal,
217
217
  })} jslog=${VisualLogging.section('request-headers')}></devtools-widget>`
@@ -392,8 +392,8 @@ function renderHeaderOverridesLink(input: ViewInput): Lit.LitTemplate {
392
392
  }
393
393
 
394
394
  function renderRawHeaders(text: string): Lit.TemplateResult {
395
- return html`<div class="row raw-headers-row"><devtools-widget class=raw-headers .widgetConfig=${
396
- UI.Widget.widgetConfig(ShowMoreDetailsWidget, {text})}></devtools-widget></div>`;
395
+ return html`<div class="row raw-headers-row"><devtools-widget class=raw-headers
396
+ ${widget(ShowMoreDetailsWidget, {text})}></devtools-widget></div>`;
397
397
  }
398
398
 
399
399
  function renderGeneralRow(
@@ -43,7 +43,7 @@ export interface ViewOutput {
43
43
  revealPosition: (position: SourceFrame.SourceFrame.RevealPosition) => Promise<void>;
44
44
  }
45
45
 
46
- const {widgetConfig, widgetRef, widget} = UI.Widget;
46
+ const {widgetRef, widget} = UI.Widget;
47
47
  type View = (input: ViewInput, output: ViewOutput, target: HTMLElement) => void;
48
48
 
49
49
  export const DEFAULT_VIEW: View = (input: ViewInput, output: ViewOutput, target: HTMLElement): void => {
@@ -62,7 +62,7 @@ export const DEFAULT_VIEW: View = (input: ViewInput, output: ViewOutput, target:
62
62
  // clang-format on
63
63
  } else if (input.renderAsText) {
64
64
  // clang-format off
65
- widgetTemplate = html`<devtools-widget .widgetConfig=${widgetConfig(element => new SourceFrame.ResourceSourceFrame.SearchableContainer(
65
+ widgetTemplate = html`<devtools-widget ${widget(element => new SourceFrame.ResourceSourceFrame.SearchableContainer(
66
66
  input.request, input.mimeType, element))}
67
67
  ${widgetRef(SourceFrame.ResourceSourceFrame.SearchableContainer, widget => {output.revealPosition = widget.revealPosition.bind(widget);})}></devtools-widget>`;
68
68
  // clang-format on