chrome-devtools-frontend 1.0.1701636 → 1.0.1702515

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 (78) hide show
  1. package/eslint.config.mjs +3 -1
  2. package/front_end/core/host/UserMetricsEnums.ts +2 -1
  3. package/front_end/core/root/ExperimentNames.ts +1 -0
  4. package/front_end/core/sdk/CSSPropertyParserMatchers.ts +2 -7
  5. package/front_end/devtools_compatibility.js +2 -1
  6. package/front_end/entrypoints/main/MainImpl.ts +8 -0
  7. package/front_end/generated/InspectorBackendCommands.ts +3 -3
  8. package/front_end/generated/protocol.ts +0 -3
  9. package/front_end/models/ai_assistance/AiAgent2.ts +13 -1
  10. package/front_end/models/ai_assistance/AiConversation.ts +1 -1
  11. package/front_end/models/ai_assistance/agents/ContextSelectionAgent.ts +13 -19
  12. package/front_end/models/change_tracker/ChangeTracker.ts +5 -64
  13. package/front_end/models/comment_manager/CommentManager.ts +3 -5
  14. package/front_end/models/comment_manager/CommentThread.ts +30 -13
  15. package/front_end/models/issues_manager/CookieDeprecationMetadataIssue.ts +6 -7
  16. package/front_end/models/issues_manager/DeprecationIssue.ts +1 -1
  17. package/front_end/models/issues_manager/MarkdownIssueDescription.ts +14 -5
  18. package/front_end/models/issues_manager/SRIMessageSignatureIssue.ts +2 -4
  19. package/front_end/models/issues_manager/descriptions/cookieWarnMetadataGrantRead.md +0 -1
  20. package/front_end/models/issues_manager/descriptions/cookieWarnMetadataGrantReadOptOut.md +5 -0
  21. package/front_end/models/issues_manager/descriptions/cookieWarnMetadataGrantSet.md +0 -1
  22. package/front_end/models/issues_manager/descriptions/cookieWarnMetadataGrantSetOptOut.md +5 -0
  23. package/front_end/models/issues_manager/descriptions/deprecation.md +1 -1
  24. package/front_end/models/issues_manager/descriptions/sriValidationFailedIntegrityMismatch.md +2 -0
  25. package/front_end/models/persistence/NetworkPersistenceManager.ts +14 -2
  26. package/front_end/panels/common/CommentThreadWidget.ts +29 -10
  27. package/front_end/panels/common/CommentsOverlayWidget.ts +50 -14
  28. package/front_end/panels/common/commentThreadWidget.css +11 -0
  29. package/front_end/panels/elements/StylePropertiesSection.ts +10 -0
  30. package/front_end/panels/elements/StylePropertyTreeElement.ts +1 -1
  31. package/front_end/panels/elements/StylesSidebarPane.ts +101 -49
  32. package/front_end/panels/elements/components/CSSQuery.ts +1 -1
  33. package/front_end/panels/elements/components/cssQuery.css +4 -0
  34. package/front_end/panels/elements/elements.ts +0 -1
  35. package/front_end/panels/elements/stylesSidebarPane.css +6 -0
  36. package/front_end/panels/sources/ResourceOriginPlugin.ts +8 -3
  37. package/front_end/panels/sources/ScopeChainSidebarPane.ts +3 -4
  38. package/front_end/panels/sources/TabbedEditorContainer.ts +339 -126
  39. package/front_end/panels/sources/UISourceCodeFrame.ts +9 -0
  40. package/front_end/third_party/chromium/README.chromium +1 -1
  41. package/front_end/third_party/chromium/client-variations/ClientVariations.js +25 -96
  42. package/front_end/third_party/codemirror.next/chunk/angular.js +1 -1
  43. package/front_end/third_party/codemirror.next/chunk/codemirror.js +1 -1
  44. package/front_end/third_party/codemirror.next/chunk/codemirror.js.map +1 -1
  45. package/front_end/third_party/codemirror.next/chunk/cpp.js +1 -1
  46. package/front_end/third_party/codemirror.next/chunk/cpp.js.map +1 -1
  47. package/front_end/third_party/codemirror.next/chunk/java.js +1 -1
  48. package/front_end/third_party/codemirror.next/chunk/java.js.map +1 -1
  49. package/front_end/third_party/codemirror.next/chunk/legacy.js +1 -1
  50. package/front_end/third_party/codemirror.next/chunk/less.js +1 -1
  51. package/front_end/third_party/codemirror.next/chunk/less.js.map +1 -1
  52. package/front_end/third_party/codemirror.next/chunk/markdown.js +1 -1
  53. package/front_end/third_party/codemirror.next/chunk/markdown.js.map +1 -1
  54. package/front_end/third_party/codemirror.next/chunk/php.js +1 -1
  55. package/front_end/third_party/codemirror.next/chunk/php.js.map +1 -1
  56. package/front_end/third_party/codemirror.next/chunk/python.js +1 -1
  57. package/front_end/third_party/codemirror.next/chunk/python.js.map +1 -1
  58. package/front_end/third_party/codemirror.next/chunk/sass.js +1 -1
  59. package/front_end/third_party/codemirror.next/chunk/sass.js.map +1 -1
  60. package/front_end/third_party/codemirror.next/chunk/svelte.js +1 -1
  61. package/front_end/third_party/codemirror.next/chunk/svelte.js.map +1 -1
  62. package/front_end/third_party/codemirror.next/chunk/vue.js +1 -1
  63. package/front_end/third_party/codemirror.next/chunk/wast.js +1 -1
  64. package/front_end/third_party/codemirror.next/chunk/wast.js.map +1 -1
  65. package/front_end/third_party/codemirror.next/chunk/xml.js +1 -1
  66. package/front_end/third_party/codemirror.next/chunk/xml.js.map +1 -1
  67. package/front_end/third_party/codemirror.next/codemirror.next.js +1 -1
  68. package/front_end/third_party/codemirror.next/package.json +4 -1
  69. package/front_end/third_party/codemirror.next/rebuild.sh +1 -1
  70. package/front_end/ui/comments/CommentOverlayManager.ts +11 -10
  71. package/front_end/ui/components/markdown_view/MarkdownPlaceholderLitRenderer.ts +117 -0
  72. package/front_end/ui/components/markdown_view/markdown_view.ts +2 -0
  73. package/front_end/ui/legacy/Treeoutline.ts +28 -19
  74. package/front_end/ui/legacy/UIUtils.ts +1 -1
  75. package/front_end/ui/legacy/components/object_ui/ObjectPropertiesSection.ts +228 -343
  76. package/front_end/ui/legacy/components/object_ui/objectPropertiesSection.css +2 -27
  77. package/mcp/mcp.ts +1 -0
  78. package/package.json +1 -1
@@ -1,3 +1,3 @@
1
- # {PLACEHOLDER_title}
1
+ # Deprecated feature used
2
2
 
3
3
  {PLACEHOLDER_message}
@@ -7,4 +7,6 @@ assertions made by the request’s initiator. Verification failed.
7
7
 
8
8
  The following are the keys specified by the request’s initiator:
9
9
 
10
+ ```
10
11
  {PLACEHOLDER_integrityAssertions}
12
+ ```
@@ -270,6 +270,8 @@ export class NetworkPersistenceManager extends Common.ObjectWrapper.ObjectWrappe
270
270
  encodeURI(pathPart).replace(/[\/\*]/g, match => '%' + match[0].charCodeAt(0).toString(16).toUpperCase());
271
271
  if (encodedName === '..') {
272
272
  encodedName = '%2E%2E';
273
+ } else if (encodedName === '.') {
274
+ encodedName = '%2E';
273
275
  }
274
276
  if (Host.Platform.isWin()) {
275
277
  // Windows does not allow ':' and '?' in filenames
@@ -335,7 +337,10 @@ export class NetworkPersistenceManager extends Common.ObjectWrapper.ObjectWrappe
335
337
  if (!encodedFilePath) {
336
338
  return null;
337
339
  }
338
- const encodedPath = Common.ParsedURL.ParsedURL.substring(encodedFilePath, 0, encodedFilePath.lastIndexOf('/'));
340
+ const encodedPath = Common.ParsedURL.ParsedURL.substr(encodedFilePath, 0, encodedFilePath.lastIndexOf('/'));
341
+ if (!encodedPath) {
342
+ return null;
343
+ }
339
344
  uiSourceCode = await this.#project.createFile(encodedPath, HEADERS_FILENAME, '');
340
345
  Host.userMetrics.actionTaken(Host.UserMetrics.Action.HeaderOverrideFileCreated);
341
346
  }
@@ -525,6 +530,10 @@ export class NetworkPersistenceManager extends Common.ObjectWrapper.ObjectWrappe
525
530
  const encodedFileName = Common.ParsedURL.ParsedURL.substring(encodedPath, lastIndexOfSlash + 1);
526
531
  const rawFileName = Common.ParsedURL.ParsedURL.encodedPathToRawPathString(encodedFileName);
527
532
  encodedPath = Common.ParsedURL.ParsedURL.substr(encodedPath, 0, lastIndexOfSlash);
533
+ if (!encodedPath || rawFileName === HEADERS_FILENAME) {
534
+ this.#savingForOverrides.delete(uiSourceCode);
535
+ return;
536
+ }
528
537
  if (this.#project) {
529
538
  await this.#project.createFile(encodedPath, rawFileName, content ?? '', isEncoded);
530
539
  }
@@ -564,7 +573,7 @@ export class NetworkPersistenceManager extends Common.ObjectWrapper.ObjectWrappe
564
573
  const relativePathParts = FileSystemWorkspaceBinding.relativePath(uiSourceCode);
565
574
  // Decode twice to handle paths generated on Windows OS.
566
575
  const host = this.decodeLocalPathToUrlPath(this.decodeLocalPathToUrlPath(relativePathParts[0] || '')).toLowerCase();
567
- return ['chrome:', 'data:', 'blob:', 'javascript:', 'about:', 'mailto:', 'vbscript:'].includes(host) ||
576
+ return ['chrome:', 'data:', 'blob:', 'javascript:', 'about:', 'mailto:', 'vbscript:', '.', '..'].includes(host) ||
568
577
  forbiddenUrls.includes(host);
569
578
  }
570
579
 
@@ -578,6 +587,9 @@ export class NetworkPersistenceManager extends Common.ObjectWrapper.ObjectWrappe
578
587
  if (!url) {
579
588
  return false;
580
589
  }
590
+ if ((url.scheme === 'http' || url.scheme === 'https') && (!url.host || url.host === '.' || url.host === '..')) {
591
+ return true;
592
+ }
581
593
  return !['http', 'https', 'file'].includes(url.scheme) || forbiddenUrls.includes(url.host);
582
594
  }
583
595
 
@@ -7,6 +7,7 @@ import '../../ui/components/tooltips/tooltips.js';
7
7
  import * as i18n from '../../core/i18n/i18n.js';
8
8
  import type * as SDK from '../../core/sdk/sdk.js';
9
9
  import type * as CommentManager from '../../models/comment_manager/comment_manager.js';
10
+ import * as Buttons from '../../ui/components/buttons/buttons.js';
10
11
  import * as Input from '../../ui/components/input/input.js';
11
12
  import * as MarkdownView from '../../ui/components/markdown_view/markdown_view.js';
12
13
  import * as UI from '../../ui/legacy/legacy.js';
@@ -41,6 +42,10 @@ const UIStrings = {
41
42
  * @description aria-label for the comment text area.
42
43
  */
43
44
  commentInputAriaLabel: 'Comment input',
45
+ /**
46
+ * @description Tooltip and aria-label for the close button in the comment thread header.
47
+ */
48
+ close: 'Close',
44
49
  } as const;
45
50
 
46
51
  const UIStringsNotTranslate = {
@@ -70,6 +75,7 @@ export interface ViewInput {
70
75
  textAreaRef: Lit.Directives.Ref<HTMLTextAreaElement>;
71
76
  onAddComment: (text: string) => void;
72
77
  onCommentTextChange: (event: Event) => void;
78
+ onClose?: () => void;
73
79
  }
74
80
 
75
81
  export type ViewOutput = undefined;
@@ -87,16 +93,27 @@ export const DEFAULT_VIEW = (input: ViewInput, _output: ViewOutput, target: HTML
87
93
  widget(DOMNodeLink, {node: input.title.node}) :
88
94
  html`<span class="selected-item-text">${input.title.text}</span>`}
89
95
  </span>
90
- ${hasComment ? html`
91
- <div class="sent-status">
92
- <devtools-icon
93
- class="check-icon"
94
- name="checkmark"
95
- aria-label=${i18nString(UIStrings.sentCheckmark)}>
96
- </devtools-icon>
97
- <span>${i18nString(UIStrings.sent)}</span>
98
- </div>
99
- ` : Lit.nothing}
96
+ <div class="header-actions">
97
+ ${hasComment ? html`
98
+ <div class="sent-status">
99
+ <devtools-icon
100
+ class="check-icon"
101
+ name="checkmark"
102
+ aria-label=${i18nString(UIStrings.sentCheckmark)}>
103
+ </devtools-icon>
104
+ <span>${i18nString(UIStrings.sent)}</span>
105
+ </div>
106
+ ` : Lit.nothing}
107
+ <devtools-button
108
+ class="close-button"
109
+ aria-label=${i18nString(UIStrings.close)}
110
+ .iconName=${'cross'}
111
+ .variant=${Buttons.Button.Variant.ICON}
112
+ .size=${Buttons.Button.Size.SMALL}
113
+ .title=${i18nString(UIStrings.close)}
114
+ @click=${input.onClose}
115
+ ></devtools-button>
116
+ </div>
100
117
  </div>
101
118
 
102
119
  ${hasComment ? html`
@@ -171,6 +188,7 @@ export class CommentThreadWidget extends UI.Widget.Widget {
171
188
  #textAreaRef = createRef<HTMLTextAreaElement>();
172
189
  #view: View;
173
190
  onAddComment?: (text: string) => void;
191
+ onClose?: () => void;
174
192
 
175
193
  constructor(element?: HTMLElement, view: View = DEFAULT_VIEW) {
176
194
  super(element);
@@ -213,6 +231,7 @@ export class CommentThreadWidget extends UI.Widget.Widget {
213
231
  textAreaRef: this.#textAreaRef,
214
232
  onAddComment: this.#handleAddComment,
215
233
  onCommentTextChange: this.#handleCommentTextChange,
234
+ onClose: this.onClose,
216
235
  };
217
236
  this.#view(viewInput, undefined, this.contentElement);
218
237
  }
@@ -31,6 +31,7 @@ export interface ViewInput {
31
31
  activePin: Comments.CommentOverlayManager.PinPositionData|null;
32
32
  title: Title;
33
33
  onAddComment: (text: string) => void;
34
+ onCloseCommentThread: () => void;
34
35
  }
35
36
 
36
37
  export type View = (
@@ -103,6 +104,7 @@ const DEFAULT_VIEW: View = (input: ViewInput, _output: undefined, target: HTMLEl
103
104
  title: input.title,
104
105
  comments: [...item.thread.comments],
105
106
  onAddComment: input.onAddComment,
107
+ onClose: input.onCloseCommentThread,
106
108
  })}
107
109
  </div>
108
110
  `,
@@ -124,6 +126,18 @@ export class CommentsOverlayWidget extends UI.Widget.Widget {
124
126
  #cachedTitle: Title = {text: ''};
125
127
  #cachedTitleAnchor: CommentManager.CommentManager.CommentAnchorSignature|null = null;
126
128
 
129
+ #setActiveThreadId(threadId: string|null): void {
130
+ if (this.#activeThreadId !== threadId) {
131
+ this.#clearCloseTimeout();
132
+ this.#activeThreadId = threadId;
133
+ if (threadId && this.isShowing()) {
134
+ document.documentElement.addEventListener('keydown', this.#onKeyDown);
135
+ } else {
136
+ document.documentElement.removeEventListener('keydown', this.#onKeyDown);
137
+ }
138
+ }
139
+ }
140
+
127
141
  constructor(
128
142
  element: HTMLElement|undefined,
129
143
  [commentManager]: UI.Widget.WidgetDependencies<typeof CommentsOverlayWidget>,
@@ -170,12 +184,16 @@ export class CommentsOverlayWidget extends UI.Widget.Widget {
170
184
  this.#onAgentAttachedChanged,
171
185
  this,
172
186
  );
187
+ if (this.#activeThreadId) {
188
+ document.documentElement.addEventListener('keydown', this.#onKeyDown);
189
+ }
173
190
 
174
191
  this.requestUpdate();
175
192
  }
176
193
 
177
194
  override willHide(): void {
178
195
  this.#clearCloseTimeout();
196
+ document.documentElement.removeEventListener('keydown', this.#onKeyDown);
179
197
  this.#commentOverlayManager.stop();
180
198
  this.#commentOverlayManager.removeEventListener(
181
199
  Comments.CommentOverlayManager.Events.POSITIONS_UPDATED,
@@ -219,7 +237,7 @@ export class CommentsOverlayWidget extends UI.Widget.Widget {
219
237
  CommentManager.CommentManager.EventTypes[CommentManager.CommentManager.Events.AGENT_ATTACHED_CHANGED]>,
220
238
  ): void {
221
239
  if (!event.data) {
222
- this.#activeThreadId = null;
240
+ this.#setActiveThreadId(null);
223
241
  }
224
242
  this.requestUpdate();
225
243
  }
@@ -230,7 +248,7 @@ export class CommentsOverlayWidget extends UI.Widget.Widget {
230
248
  ): void {
231
249
  const isModeActive = event.data;
232
250
  if (!isModeActive) {
233
- this.#activeThreadId = null;
251
+ this.#setActiveThreadId(null);
234
252
  }
235
253
  const action = UI.ActionRegistry.ActionRegistry.instance().getAction(
236
254
  'comments.toggle-comment-mode',
@@ -242,9 +260,9 @@ export class CommentsOverlayWidget extends UI.Widget.Widget {
242
260
  #onStateChanged(): void {
243
261
  const draftThread = this.#commentManager.getCommentThreads().find(t => t.status === 'DRAFT');
244
262
  if (draftThread) {
245
- this.#activeThreadId = draftThread.id;
263
+ this.#setActiveThreadId(draftThread.id);
246
264
  } else if (this.#activeThreadId && !this.#commentManager.getCommentThread(this.#activeThreadId)) {
247
- this.#activeThreadId = null;
265
+ this.#setActiveThreadId(null);
248
266
  }
249
267
  this.requestUpdate();
250
268
  }
@@ -272,7 +290,6 @@ export class CommentsOverlayWidget extends UI.Widget.Widget {
272
290
  return {node};
273
291
  }
274
292
  }
275
- return {text: ''};
276
293
  }
277
294
 
278
295
  if (anchor.networkRequestId) {
@@ -287,19 +304,35 @@ export class CommentsOverlayWidget extends UI.Widget.Widget {
287
304
  }
288
305
 
289
306
  #handlePinClick = (threadId: string): void => {
290
- const thread = this.#commentManager.getCommentThread(threadId);
291
307
  if (this.#activeThreadId === threadId) {
292
- if (thread?.status === 'DRAFT') {
293
- this.#commentOverlayManager.clearDraftThreads();
294
- }
295
- this.#activeThreadId = null;
296
- } else {
308
+ this.#handleCloseCommentThread();
309
+ return;
310
+ }
311
+
312
+ this.#commentOverlayManager.clearDraftThreads();
313
+ this.#setActiveThreadId(threadId);
314
+ this.requestUpdate();
315
+ };
316
+
317
+ #handleCloseCommentThread = (): void => {
318
+ if (!this.#activeThreadId) {
319
+ return;
320
+ }
321
+ const thread = this.#commentManager.getCommentThread(this.#activeThreadId);
322
+ if (thread?.status === 'DRAFT') {
297
323
  this.#commentOverlayManager.clearDraftThreads();
298
- this.#activeThreadId = threadId;
299
324
  }
325
+ this.#setActiveThreadId(null);
300
326
  this.requestUpdate();
301
327
  };
302
328
 
329
+ #onKeyDown = (event: KeyboardEvent): void => {
330
+ if (this.#activeThreadId && event.key === 'Escape' && !event.isComposing) {
331
+ event.consume(true);
332
+ this.#handleCloseCommentThread();
333
+ }
334
+ };
335
+
303
336
  override async performUpdate(signal?: AbortSignal): Promise<void> {
304
337
  if (!this.#commentManager.isAgentAttached()) {
305
338
  this.#view(
@@ -313,6 +346,7 @@ export class CommentsOverlayWidget extends UI.Widget.Widget {
313
346
  activePin: null,
314
347
  title: {text: ''},
315
348
  onAddComment: () => {},
349
+ onCloseCommentThread: this.#handleCloseCommentThread,
316
350
  },
317
351
  undefined,
318
352
  this.contentElement,
@@ -342,16 +376,18 @@ export class CommentsOverlayWidget extends UI.Widget.Widget {
342
376
  if (!activeThread) {
343
377
  return;
344
378
  }
345
- activeThread.save(text);
379
+ activeThread.sendToAgent(text);
346
380
  const threadId = activeThread.id;
381
+ this.#clearCloseTimeout();
347
382
  this.#closeTimeoutId = window.setTimeout(() => {
348
383
  this.#closeTimeoutId = null;
349
384
  if (this.#activeThreadId === threadId) {
350
- this.#activeThreadId = null;
385
+ this.#setActiveThreadId(null);
351
386
  this.requestUpdate();
352
387
  }
353
388
  }, AUTO_CLOSE_DELAY_MS);
354
389
  },
390
+ onCloseCommentThread: this.#handleCloseCommentThread,
355
391
  };
356
392
  this.#view(viewInput, undefined, this.contentElement);
357
393
  }
@@ -30,6 +30,17 @@
30
30
  gap: var(--sys-size-4);
31
31
  }
32
32
 
33
+ .header-actions {
34
+ display: flex;
35
+ align-items: center;
36
+ gap: var(--sys-size-3);
37
+ flex-shrink: 0;
38
+ }
39
+
40
+ .close-button {
41
+ flex-shrink: 0;
42
+ }
43
+
33
44
  .sent-status {
34
45
  display: flex;
35
46
  align-items: center;
@@ -304,6 +304,7 @@ export class StylePropertiesSection {
304
304
  closeBrace.createChild('span').textContent = '}';
305
305
  } else {
306
306
  this.titleElement.classList.add('hidden');
307
+ this.updateAncestorRuleList();
307
308
  }
308
309
 
309
310
  if (rule) {
@@ -425,6 +426,7 @@ export class StylePropertiesSection {
425
426
  this.computedStyleExtraFields = computedStyleExtraFields;
426
427
  this.#lastInheritedNode = matchedStyles.isInherited(style) ? matchedStyles.nodeForStyle(style) : null;
427
428
  this.update(true);
429
+ this.updateCollapsedState();
428
430
  }
429
431
 
430
432
  inheritedNode(): SDK.DOMModel.DOMNode|null {
@@ -1250,6 +1252,14 @@ export class StylePropertiesSection {
1250
1252
  this.#ancestorClosingBracesElement.removeChildren();
1251
1253
  this.maybeCreateAncestorRules(this.styleInternal);
1252
1254
  this.#styleRuleElement.style.paddingLeft = `${this.nestingLevel}ch`;
1255
+ if (this.headerText().length === 0 && (this.styleInternal.parentRule instanceof SDK.CSSRule.CSSStyleRule)) {
1256
+ const lastAncestor = this.#ancestorRuleListElement.lastElementChild;
1257
+ if (lastAncestor && this.#collapseIcon && this.#statusElement) {
1258
+ this.#collapseIcon.slot = 'indent';
1259
+ lastAncestor.append(this.#collapseIcon, this.#statusElement);
1260
+ }
1261
+ this.#ancestorClosingBracesElement.firstElementChild?.classList.add('sidebar-pane-closing-brace');
1262
+ }
1253
1263
  }
1254
1264
 
1255
1265
  isPropertyInherited(propertyName: string): boolean {
@@ -2044,7 +2044,7 @@ export class AnchorFunctionRenderer extends AnchorFunctionRendererBase {
2044
2044
 
2045
2045
  override render(match: SDK.CSSPropertyParserMatchers.AnchorFunctionMatch, context: RenderingContext): Node[] {
2046
2046
  const content = document.createElement('span');
2047
- if (match.node.name !== 'CallExpression') {
2047
+ if (match.node.name === 'VariableName') {
2048
2048
  // Link an anchor double-dashed ident to its matching anchor element.
2049
2049
  void AnchorFunctionRenderer.decorateAnchorForAnchorLink(this.#stylesContainer, content, {identifier: match.text});
2050
2050
  } else {
@@ -1126,27 +1126,39 @@ export class StylesSidebarPane extends StylesSidebarPaneBase implements StylesCo
1126
1126
  }
1127
1127
  }
1128
1128
 
1129
- private getStyleId(style: SDK.CSSStyleDeclaration.CSSStyleDeclaration): string {
1129
+ private getStyleId(section: StylePropertiesSection): string {
1130
+ const style = section.styleInternal;
1131
+ const node = section.matchedStyles.isInherited(style) ? section.matchedStyles.nodeForStyle(style) :
1132
+ section.matchedStyles.node();
1133
+ const nodeId = node?.id ?? '';
1130
1134
  if (style.range) {
1131
- return `${style.styleSheetId || ''}:${style.range.toString()}`;
1135
+ return `${nodeId}:${style.styleSheetId || ''}:${style.range.toString()}`;
1132
1136
  }
1133
1137
  if (style.type === SDK.CSSStyleDeclaration.Type.Inline || style.type === SDK.CSSStyleDeclaration.Type.Attributes) {
1134
- return style.type;
1138
+ return `${nodeId}:${style.type}`;
1135
1139
  }
1136
1140
  if (style.type === SDK.CSSStyleDeclaration.Type.Animation) {
1137
- return `${style.type}:${style.animationName() || ''}:${style.cssText}`;
1141
+ return `${nodeId}:${style.type}:${style.animationName() || ''}:${style.cssText}`;
1138
1142
  }
1139
1143
  const parentRule = style.parentRule;
1140
1144
  if (parentRule instanceof SDK.CSSRule.CSSStyleRule) {
1141
- return `${style.type}:${parentRule.selectorText()}`;
1145
+ const ruleTypes = parentRule.ruleTypes.join(',');
1146
+ const nesting = parentRule.nestingSelectors?.join(',') ?? '';
1147
+ const media = parentRule.media.map(m => m.text).join(',');
1148
+ const containers = parentRule.containerQueries.map(c => c.text).join(',');
1149
+ const supports = parentRule.supports.map(s => s.text).join(',');
1150
+ const scopes = parentRule.scopes.map(s => s.text).join(',');
1151
+ const layers = parentRule.layers.map(l => l.text).join(',');
1152
+ return `${nodeId}:${style.type}:${parentRule.selectorText()}:${ruleTypes}:${nesting}:${media}:${containers}:${
1153
+ supports}:${scopes}:${layers}`;
1142
1154
  }
1143
1155
  if (parentRule instanceof SDK.CSSRule.CSSKeyframeRule) {
1144
- return `${style.type}:${parentRule.parentRuleName()}:${parentRule.key().text}`;
1156
+ return `${nodeId}:${style.type}:${parentRule.parentRuleName()}:${parentRule.key().text}`;
1145
1157
  }
1146
1158
  if (parentRule instanceof SDK.CSSRule.CSSPropertyRule) {
1147
- return `${style.type}:${parentRule.propertyName().text}`;
1159
+ return `${nodeId}:${style.type}:${parentRule.propertyName().text}`;
1148
1160
  }
1149
- return `${style.type}:${style.cssText}`;
1161
+ return `${nodeId}:${style.type}:${style.cssText}`;
1150
1162
  }
1151
1163
 
1152
1164
  rebuildSectionsForMatchedStyleRulesForTest(matchedStyles: SDK.CSSMatchedStyles.CSSMatchedStyles,
@@ -1170,7 +1182,7 @@ export class StylesSidebarPane extends StylesSidebarPaneBase implements StylesCo
1170
1182
 
1171
1183
  this.idleCallbackManager = new IdleCallbackManager();
1172
1184
 
1173
- const blocks = [new SectionBlock(null)];
1185
+ const blocks = [new SectionBlock(null, undefined, undefined, 'main')];
1174
1186
  let sectionIdx = 0;
1175
1187
  let lastParentNode: SDK.DOMModel.DOMNode|null = null;
1176
1188
 
@@ -1183,7 +1195,7 @@ export class StylesSidebarPane extends StylesSidebarPaneBase implements StylesCo
1183
1195
  if (parentRule instanceof SDK.CSSRule.CSSStyleRule) {
1184
1196
  const layers = parentRule.layers;
1185
1197
  if ((layers.length || lastLayers) && lastLayers !== layers) {
1186
- const block = SectionBlock.createLayerBlock(parentRule);
1198
+ const block = SectionBlock.createLayerBlock(parentRule, lastLayerParent?.id);
1187
1199
  blocks.push(block);
1188
1200
  lastLayerParent?.childBlocks.push(block);
1189
1201
  sawLayers = true;
@@ -1409,37 +1421,36 @@ export class StylesSidebarPane extends StylesSidebarPaneBase implements StylesCo
1409
1421
  return blocks;
1410
1422
  }
1411
1423
 
1412
- return this.mergeInactiveStyles(blocks);
1424
+ return this.mergeInactiveStyles(blocks, matchedStyles, computedStyles, parentsComputedStyles,
1425
+ computedStyleExtraFields);
1413
1426
  }
1414
1427
 
1415
- private computeBlockIds(blocks: SectionBlock[]): Map<SectionBlock, string> {
1416
- let nullBlockCounter = 0;
1417
- const blockIds = new Map<SectionBlock, string>();
1418
- for (const block of blocks) {
1419
- blockIds.set(block, block.titleElement()?.textContent || `MAIN_BLOCK_NULL_${nullBlockCounter++}`);
1420
- }
1421
- return blockIds;
1422
- }
1423
-
1424
- private mergeInactiveStyles(blocks: SectionBlock[]): SectionBlock[] {
1425
- const blockIds = this.computeBlockIds(blocks);
1426
- for (const [id, block] of this.#allKnownBlocks) {
1427
- if (!blockIds.has(block)) {
1428
- blockIds.set(block, id);
1429
- }
1430
- }
1431
- const getBlockId = (block: SectionBlock): string => blockIds.get(block) || 'UNKNOWN_BLOCK';
1428
+ private mergeInactiveStyles(
1429
+ blocks: SectionBlock[],
1430
+ matchedStyles: SDK.CSSMatchedStyles.CSSMatchedStyles,
1431
+ computedStyles: Map<string, string>|null,
1432
+ parentsComputedStyles: Map<string, string>|null,
1433
+ computedStyleExtraFields: Protocol.CSS.ComputedStyleExtraFields|null,
1434
+ ): SectionBlock[] {
1435
+ const getBlockId = (block: SectionBlock): string => block.id;
1432
1436
 
1433
1437
  for (const block of blocks) {
1434
1438
  const bid = getBlockId(block);
1435
1439
  const knownBlock = this.#allKnownBlocks.get(bid);
1436
1440
  if (knownBlock) {
1437
- block.sections = mergeOrderedItems(
1441
+ knownBlock.sections = mergeOrderedItems(
1438
1442
  knownBlock.sections,
1439
1443
  block.sections,
1440
- section => this.getStyleId(section.styleInternal),
1441
- section => section.setInactive(true),
1444
+ section => this.getStyleId(section),
1445
+ (section, active, newSection) => {
1446
+ if (active && newSection) {
1447
+ section.rebuildWithPayload(matchedStyles, newSection.style(), computedStyles, parentsComputedStyles,
1448
+ computedStyleExtraFields);
1449
+ }
1450
+ section.setInactive(!active);
1451
+ },
1442
1452
  );
1453
+ block.sections = knownBlock.sections;
1443
1454
  }
1444
1455
  }
1445
1456
 
@@ -1448,7 +1459,11 @@ export class StylesSidebarPane extends StylesSidebarPaneBase implements StylesCo
1448
1459
  oldBlocks,
1449
1460
  blocks,
1450
1461
  getBlockId,
1451
- block => block.sections.forEach(section => section.setInactive(true)),
1462
+ (block, active) => {
1463
+ if (!active) {
1464
+ block.sections.forEach(section => section.setInactive(true));
1465
+ }
1466
+ },
1452
1467
  );
1453
1468
 
1454
1469
  this.#allKnownBlocks.clear();
@@ -1456,6 +1471,19 @@ export class StylesSidebarPane extends StylesSidebarPaneBase implements StylesCo
1456
1471
  this.#allKnownBlocks.set(getBlockId(block), block);
1457
1472
  }
1458
1473
 
1474
+ // Restore child relationships using the new tree structure,
1475
+ // but pointing to the final instances.
1476
+ for (const newBlock of blocks) {
1477
+ const finalBlock = this.#allKnownBlocks.get(getBlockId(newBlock));
1478
+ if (finalBlock) {
1479
+ const newChildBlocks =
1480
+ newBlock.childBlocks.map(child => this.#allKnownBlocks.get(getBlockId(child))).filter(b => b !== undefined);
1481
+ finalBlock.childBlocks = finalBlock === newBlock ?
1482
+ newChildBlocks :
1483
+ mergeOrderedItems(finalBlock.childBlocks, newChildBlocks, getBlockId, () => {});
1484
+ }
1485
+ }
1486
+
1459
1487
  return finalBlocks;
1460
1488
  }
1461
1489
 
@@ -1871,7 +1899,7 @@ export class SectionBlock {
1871
1899
  childBlocks: SectionBlock[] = [];
1872
1900
  #expanded = false;
1873
1901
  #icon: Icon|undefined;
1874
- constructor(titleElement: Element|null, expandable?: boolean, expandedByDefault?: boolean) {
1902
+ constructor(titleElement: Element|null, expandable?: boolean, expandedByDefault?: boolean, readonly id = 'main') {
1875
1903
  this.#titleElement = titleElement;
1876
1904
  this.sections = [];
1877
1905
  this.#expanded = expandedByDefault ?? false;
@@ -1905,7 +1933,7 @@ export class SectionBlock {
1905
1933
  const pseudoArgumentString = pseudoArgument ? `(${pseudoArgument})` : '';
1906
1934
  const pseudoTypeString = `${pseudoType}${pseudoArgumentString}`;
1907
1935
  separatorElement.textContent = i18nString(UIStrings.pseudoSElement, {PH1: pseudoTypeString});
1908
- return new SectionBlock(separatorElement);
1936
+ return new SectionBlock(separatorElement, false, false, `pseudo:${pseudoType}:${pseudoArgument ?? ''}`);
1909
1937
  }
1910
1938
 
1911
1939
  static async createInheritedPseudoTypeBlock(pseudoType: Protocol.DOM.PseudoType, pseudoArgument: string|null,
@@ -1918,12 +1946,13 @@ export class SectionBlock {
1918
1946
  UI.UIUtils.createTextChild(separatorElement, i18nString(UIStrings.inheritedFromSPseudoOf, {PH1: pseudoTypeString}));
1919
1947
  const link = PanelsCommon.DOMLinkifier.Linkifier.instance().linkify(node, {preventKeyboardFocus: true});
1920
1948
  render(link, separatorElement);
1921
- return new SectionBlock(separatorElement);
1949
+ return new SectionBlock(separatorElement, false, false,
1950
+ `inherited-pseudo:${pseudoType}:${pseudoArgument ?? ''}:${node.id}`);
1922
1951
  }
1923
1952
 
1924
1953
  static createRegisteredPropertiesBlock(expandedByDefault: boolean): SectionBlock {
1925
1954
  const separatorElement = document.createElement('div');
1926
- const block = new SectionBlock(separatorElement, true, expandedByDefault);
1955
+ const block = new SectionBlock(separatorElement, true, expandedByDefault, 'registered-properties');
1927
1956
  separatorElement.className = 'sidebar-separator';
1928
1957
  separatorElement.appendChild(document.createTextNode(REGISTERED_PROPERTY_SECTION_NAME));
1929
1958
  return block;
@@ -1931,7 +1960,7 @@ export class SectionBlock {
1931
1960
 
1932
1961
  static createFunctionBlock(expandedByDefault: boolean): SectionBlock {
1933
1962
  const separatorElement = document.createElement('div');
1934
- const block = new SectionBlock(separatorElement, true, expandedByDefault);
1963
+ const block = new SectionBlock(separatorElement, true, expandedByDefault, 'functions');
1935
1964
  separatorElement.className = 'sidebar-separator';
1936
1965
  separatorElement.appendChild(document.createTextNode(FUNCTION_SECTION_NAME));
1937
1966
  return block;
@@ -1942,12 +1971,12 @@ export class SectionBlock {
1942
1971
  separatorElement.className = 'sidebar-separator';
1943
1972
  separatorElement.setAttribute('jslog', `${VisualLogging.sectionHeader('keyframes')}`);
1944
1973
  separatorElement.textContent = `@keyframes ${keyframesName}`;
1945
- return new SectionBlock(separatorElement);
1974
+ return new SectionBlock(separatorElement, false, false, `keyframes:${keyframesName}`);
1946
1975
  }
1947
1976
 
1948
1977
  static createAtRuleBlock(expandedByDefault: boolean): SectionBlock {
1949
1978
  const separatorElement = document.createElement('div');
1950
- const block = new SectionBlock(separatorElement, true, expandedByDefault);
1979
+ const block = new SectionBlock(separatorElement, true, expandedByDefault, 'at-rules');
1951
1980
  separatorElement.className = 'sidebar-separator';
1952
1981
  separatorElement.appendChild(document.createTextNode(i18nString(UIStrings.atRuleSection)));
1953
1982
  return block;
@@ -1958,7 +1987,7 @@ export class SectionBlock {
1958
1987
  separatorElement.className = 'sidebar-separator';
1959
1988
  separatorElement.setAttribute('jslog', `${VisualLogging.sectionHeader('position-try')}`);
1960
1989
  separatorElement.textContent = `@position-try ${positionTryName}`;
1961
- return new SectionBlock(separatorElement);
1990
+ return new SectionBlock(separatorElement, false, false, `position-try:${positionTryName}`);
1962
1991
  }
1963
1992
 
1964
1993
  static async createInheritedNodeBlock(node: SDK.DOMModel.DOMNode): Promise<SectionBlock> {
@@ -1970,10 +1999,10 @@ export class SectionBlock {
1970
1999
  preventKeyboardFocus: true,
1971
2000
  });
1972
2001
  render(link, separatorElement);
1973
- return new SectionBlock(separatorElement);
2002
+ return new SectionBlock(separatorElement, false, false, `inherited-node:${node.id}`);
1974
2003
  }
1975
2004
 
1976
- static createLayerBlock(rule: SDK.CSSRule.CSSStyleRule): SectionBlock {
2005
+ static createLayerBlock(rule: SDK.CSSRule.CSSStyleRule, parentBlockId = 'main'): SectionBlock {
1977
2006
  const separatorElement = document.createElement('div');
1978
2007
  separatorElement.className = 'sidebar-separator layer-separator';
1979
2008
  separatorElement.setAttribute('jslog', `${VisualLogging.sectionHeader('layer')}`);
@@ -1983,7 +2012,7 @@ export class SectionBlock {
1983
2012
  const name = rule.origin === Protocol.CSS.StyleSheetOrigin.UserAgent ? '\xa0user\xa0agent\xa0stylesheet' :
1984
2013
  '\xa0implicit\xa0outer\xa0layer';
1985
2014
  UI.UIUtils.createTextChild(separatorElement.createChild('div'), name);
1986
- return new SectionBlock(separatorElement);
2015
+ return new SectionBlock(separatorElement, false, false, `layer:${parentBlockId}:${name}`);
1987
2016
  }
1988
2017
  const layerLink = separatorElement.createChild('button');
1989
2018
  layerLink.className = 'link';
@@ -1991,7 +2020,7 @@ export class SectionBlock {
1991
2020
  const name = layers.map(layer => SDK.CSSModel.CSSModel.readableLayerName(layer.text)).join('.');
1992
2021
  layerLink.textContent = name;
1993
2022
  layerLink.onclick = () => LayersWidget.LayersWidget.instance().revealLayer(name);
1994
- return new SectionBlock(separatorElement);
2023
+ return new SectionBlock(separatorElement, false, false, `layer:${parentBlockId}:${name}`);
1995
2024
  }
1996
2025
 
1997
2026
  updateFilter(): number {
@@ -2701,30 +2730,53 @@ export function mergeOrderedItems<T>(
2701
2730
  oldItems: T[],
2702
2731
  newItems: T[],
2703
2732
  getId: (item: T) => string,
2704
- markInactive: (item: T) => void,
2733
+ toggleActive: (item: T, active: boolean, newItem?: T) => void,
2705
2734
  ): T[] {
2706
2735
  const newIds = new Set(newItems.map(getId));
2736
+ const oldItemById = new Map(oldItems.map(item => [getId(item), item]));
2737
+ const handledIds = new Set<string>();
2707
2738
  const merged: T[] = [];
2708
2739
  let newIdx = 0;
2709
2740
 
2710
2741
  for (const oldItem of oldItems) {
2711
2742
  const oldId = getId(oldItem);
2743
+ if (handledIds.has(oldId)) {
2744
+ continue;
2745
+ }
2712
2746
  if (newIds.has(oldId)) {
2713
2747
  while (newIdx < newItems.length) {
2714
2748
  const newItem = newItems[newIdx++];
2715
- merged.push(newItem);
2716
- if (getId(newItem) === oldId) {
2749
+ const newId = getId(newItem);
2750
+ const existingItem = oldItemById.get(newId);
2751
+ if (existingItem) {
2752
+ toggleActive(existingItem, true, newItem);
2753
+ merged.push(existingItem);
2754
+ handledIds.add(newId);
2755
+ } else {
2756
+ merged.push(newItem);
2757
+ }
2758
+ if (newId === oldId) {
2717
2759
  break;
2718
2760
  }
2719
2761
  }
2720
2762
  } else {
2721
- markInactive(oldItem);
2763
+ toggleActive(oldItem, false);
2722
2764
  merged.push(oldItem);
2765
+ handledIds.add(oldId);
2723
2766
  }
2724
2767
  }
2725
2768
 
2726
2769
  while (newIdx < newItems.length) {
2727
- merged.push(newItems[newIdx++]);
2770
+ const newItem = newItems[newIdx++];
2771
+ const newId = getId(newItem);
2772
+ const existingItem = oldItemById.get(newId);
2773
+ if (existingItem && !handledIds.has(newId)) {
2774
+ toggleActive(existingItem, true, newItem);
2775
+ merged.push(existingItem);
2776
+ handledIds.add(newId);
2777
+ } else if (!handledIds.has(newId)) {
2778
+ merged.push(newItem);
2779
+ }
2728
2780
  }
2729
2781
 
2730
2782
  return merged;