chrome-devtools-frontend 1.0.1700561 → 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 (119) 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/root/Runtime.ts +5 -0
  5. package/front_end/core/sdk/CSSPropertyParserMatchers.ts +10 -10
  6. package/front_end/core/sdk/NetworkManager.ts +7 -0
  7. package/front_end/core/sdk/SecurityOrigin.ts +1 -0
  8. package/front_end/core/sdk/SourceMap.ts +12 -1
  9. package/front_end/core/sdk/SourceMapCache.ts +12 -0
  10. package/front_end/core/sdk/SourceMapManager.ts +17 -8
  11. package/front_end/core/sdk/SourceMapScopeChainEntry.ts +2 -19
  12. package/front_end/core/sdk/SourceMapScopesInfo.ts +21 -0
  13. package/front_end/devtools_compatibility.js +2 -1
  14. package/front_end/entrypoints/formatter_worker/ScopeParser.ts +7 -3
  15. package/front_end/entrypoints/formatter_worker/Substitute.ts +90 -26
  16. package/front_end/entrypoints/formatter_worker/formatter_worker-entrypoint.ts +1 -1
  17. package/front_end/entrypoints/main/ExecutionContextSelector.ts +17 -4
  18. package/front_end/entrypoints/main/MainImpl.ts +8 -0
  19. package/front_end/generated/InspectorBackendCommands.ts +4 -6
  20. package/front_end/generated/protocol-mapping.d.ts +0 -8
  21. package/front_end/generated/protocol-proxy-api.d.ts +0 -6
  22. package/front_end/generated/protocol.ts +4 -25
  23. package/front_end/models/ai_assistance/AiAgent2.ts +13 -1
  24. package/front_end/models/ai_assistance/AiConversation.ts +1 -1
  25. package/front_end/models/ai_assistance/agents/ContextSelectionAgent.ts +13 -19
  26. package/front_end/models/ai_assistance/debug.ts +10 -2
  27. package/front_end/models/change_tracker/ChangeTracker.ts +5 -64
  28. package/front_end/models/comment_manager/CD4ABridge.ts +2 -2
  29. package/front_end/models/comment_manager/CommentManager.ts +6 -5
  30. package/front_end/models/comment_manager/CommentThread.ts +30 -13
  31. package/front_end/models/formatter/FormatterWorkerPool.ts +2 -2
  32. package/front_end/models/issues_manager/CookieDeprecationMetadataIssue.ts +6 -7
  33. package/front_end/models/issues_manager/DeprecationIssue.ts +1 -1
  34. package/front_end/models/issues_manager/MarkdownIssueDescription.ts +14 -5
  35. package/front_end/models/issues_manager/SRIMessageSignatureIssue.ts +2 -4
  36. package/front_end/models/issues_manager/descriptions/cookieWarnMetadataGrantRead.md +0 -1
  37. package/front_end/models/issues_manager/descriptions/cookieWarnMetadataGrantReadOptOut.md +5 -0
  38. package/front_end/models/issues_manager/descriptions/cookieWarnMetadataGrantSet.md +0 -1
  39. package/front_end/models/issues_manager/descriptions/cookieWarnMetadataGrantSetOptOut.md +5 -0
  40. package/front_end/models/issues_manager/descriptions/deprecation.md +1 -1
  41. package/front_end/models/issues_manager/descriptions/sriValidationFailedIntegrityMismatch.md +2 -0
  42. package/front_end/models/javascript_metadata/NativeFunctions.ts +7 -2
  43. package/front_end/models/persistence/NetworkPersistenceManager.ts +14 -2
  44. package/front_end/models/source_map_scopes/NamesResolver.ts +42 -39
  45. package/front_end/panels/accessibility/AccessibilityAnnouncementRecordingView.ts +70 -1
  46. package/front_end/panels/ai_assistance/components/ChatMessage.ts +1 -1
  47. package/front_end/panels/common/CommentThreadWidget.ts +29 -10
  48. package/front_end/panels/common/CommentsOverlayWidget.ts +77 -14
  49. package/front_end/panels/common/ExtensionServer.ts +18 -8
  50. package/front_end/panels/common/commentThreadWidget.css +11 -0
  51. package/front_end/panels/console/ConsolePrompt.ts +1 -1
  52. package/front_end/panels/console/ConsoleViewMessage.ts +1 -1
  53. package/front_end/panels/elements/ClassesPaneWidget.ts +2 -1
  54. package/front_end/panels/elements/DOMTreeContextMenu.ts +1 -1
  55. package/front_end/panels/elements/{ElementsTreeOutline.ts → DOMTreeWidget.ts} +34 -24
  56. package/front_end/panels/elements/ElementsPanel.ts +1 -1
  57. package/front_end/panels/elements/ElementsTreeElement.ts +1 -1
  58. package/front_end/panels/elements/ShortcutTreeElement.ts +1 -1
  59. package/front_end/panels/elements/StylePropertiesSection.ts +10 -0
  60. package/front_end/panels/elements/StylesSidebarPane.ts +105 -55
  61. package/front_end/panels/elements/TopLayerContainer.ts +1 -1
  62. package/front_end/panels/elements/components/CSSQuery.ts +1 -1
  63. package/front_end/panels/elements/components/cssQuery.css +4 -0
  64. package/front_end/panels/elements/elements.ts +3 -3
  65. package/front_end/panels/elements/stylesSidebarPane.css +6 -0
  66. package/front_end/panels/profiler/HeapDetachedElementsDataGrid.ts +1 -1
  67. package/front_end/panels/profiler/HeapProfileView.ts +8 -6
  68. package/front_end/panels/profiler/HeapSnapshotDataGrids.ts +8 -1
  69. package/front_end/panels/profiler/HeapSnapshotView.ts +6 -4
  70. package/front_end/panels/security/SecurityPanel.ts +87 -99
  71. package/front_end/panels/sensors/SensorsView.ts +87 -0
  72. package/front_end/panels/sensors/sensors-meta.ts +69 -1
  73. package/front_end/panels/settings/keybindsSettingsTab.css +1 -1
  74. package/front_end/panels/sources/ResourceOriginPlugin.ts +8 -3
  75. package/front_end/panels/sources/ScopeChainSidebarPane.ts +3 -4
  76. package/front_end/panels/sources/TabbedEditorContainer.ts +339 -126
  77. package/front_end/panels/sources/UISourceCodeFrame.ts +9 -0
  78. package/front_end/third_party/chromium/README.chromium +1 -1
  79. package/front_end/third_party/chromium/client-variations/ClientVariations.js +25 -96
  80. package/front_end/third_party/codemirror.next/chunk/angular.js +1 -1
  81. package/front_end/third_party/codemirror.next/chunk/codemirror.js +1 -1
  82. package/front_end/third_party/codemirror.next/chunk/codemirror.js.map +1 -1
  83. package/front_end/third_party/codemirror.next/chunk/cpp.js +1 -1
  84. package/front_end/third_party/codemirror.next/chunk/cpp.js.map +1 -1
  85. package/front_end/third_party/codemirror.next/chunk/java.js +1 -1
  86. package/front_end/third_party/codemirror.next/chunk/java.js.map +1 -1
  87. package/front_end/third_party/codemirror.next/chunk/legacy.js +1 -1
  88. package/front_end/third_party/codemirror.next/chunk/less.js +1 -1
  89. package/front_end/third_party/codemirror.next/chunk/less.js.map +1 -1
  90. package/front_end/third_party/codemirror.next/chunk/markdown.js +1 -1
  91. package/front_end/third_party/codemirror.next/chunk/markdown.js.map +1 -1
  92. package/front_end/third_party/codemirror.next/chunk/php.js +1 -1
  93. package/front_end/third_party/codemirror.next/chunk/php.js.map +1 -1
  94. package/front_end/third_party/codemirror.next/chunk/python.js +1 -1
  95. package/front_end/third_party/codemirror.next/chunk/python.js.map +1 -1
  96. package/front_end/third_party/codemirror.next/chunk/sass.js +1 -1
  97. package/front_end/third_party/codemirror.next/chunk/sass.js.map +1 -1
  98. package/front_end/third_party/codemirror.next/chunk/svelte.js +1 -1
  99. package/front_end/third_party/codemirror.next/chunk/svelte.js.map +1 -1
  100. package/front_end/third_party/codemirror.next/chunk/vue.js +1 -1
  101. package/front_end/third_party/codemirror.next/chunk/wast.js +1 -1
  102. package/front_end/third_party/codemirror.next/chunk/wast.js.map +1 -1
  103. package/front_end/third_party/codemirror.next/chunk/xml.js +1 -1
  104. package/front_end/third_party/codemirror.next/chunk/xml.js.map +1 -1
  105. package/front_end/third_party/codemirror.next/codemirror.next.js +1 -1
  106. package/front_end/third_party/codemirror.next/package.json +4 -1
  107. package/front_end/third_party/codemirror.next/rebuild.sh +1 -1
  108. package/front_end/ui/comments/CommentOverlayManager.ts +11 -10
  109. package/front_end/ui/components/markdown_view/MarkdownPlaceholderLitRenderer.ts +117 -0
  110. package/front_end/ui/components/markdown_view/markdown_view.ts +2 -0
  111. package/front_end/ui/legacy/Treeoutline.ts +30 -20
  112. package/front_end/ui/legacy/UIUtils.ts +1 -1
  113. package/front_end/ui/legacy/components/data_grid/DataGridElement.ts +1 -1
  114. package/front_end/ui/legacy/components/object_ui/ObjectPropertiesSection.ts +228 -343
  115. package/front_end/ui/legacy/components/object_ui/objectPropertiesSection.css +2 -27
  116. package/front_end/ui/legacy/treeoutline.css +2 -2
  117. package/front_end/ui/visual_logging/KnownContextValues.ts +7 -0
  118. package/mcp/mcp.ts +1 -0
  119. package/package.json +1 -1
@@ -13,12 +13,14 @@ import * as Marked from '../../third_party/marked/marked.js';
13
13
  */
14
14
  export interface MarkdownIssueDescription {
15
15
  file: string;
16
+ title?: string;
16
17
  substitutions?: Map<string, string>;
17
18
  links: Array<{link: string, linkTitle: string}>;
18
19
  }
19
20
 
20
21
  export interface LazyMarkdownIssueDescription {
21
22
  file: string;
23
+ title?: () => string;
22
24
  substitutions?: Map<string, () => string>;
23
25
  links: Array<{link: string, linkTitle: () => string}>;
24
26
  }
@@ -38,8 +40,9 @@ export function resolveLazyDescription(lazyDescription: LazyMarkdownIssueDescrip
38
40
  substitutionMap.set(key, value());
39
41
  });
40
42
 
41
- const description = {
43
+ const description: MarkdownIssueDescription = {
42
44
  file: lazyDescription.file,
45
+ title: lazyDescription.title?.(),
43
46
  links: lazyDescription.links.map(linksMap),
44
47
  substitutions: substitutionMap,
45
48
  };
@@ -54,6 +57,7 @@ export interface IssueDescription {
54
57
  title: string;
55
58
  markdown: Marked.Marked.Token[];
56
59
  links: Array<{link: string, linkTitle: string}>;
60
+ substitutions?: Map<string, string>;
57
61
  }
58
62
 
59
63
  export async function getFileContent(url: URL): Promise<string> {
@@ -82,15 +86,16 @@ export async function createIssueDescriptionFromMarkdown(description: MarkdownIs
82
86
  export function createIssueDescriptionFromRawMarkdown(
83
87
  markdown: string, description: MarkdownIssueDescription): IssueDescription {
84
88
  const markdownAst = Marked.Marked.lexer(markdown);
85
- const title = findTitleFromMarkdownAst(markdownAst);
86
- if (!title) {
89
+ const markdownTitle = findTitleFromMarkdownAst(markdownAst);
90
+ if (!markdownTitle) {
87
91
  throw new Error('Markdown issue descriptions must start with a heading');
88
92
  }
89
93
 
90
94
  return {
91
- title,
95
+ title: description.title ?? markdownTitle,
92
96
  markdown: markdownAst.slice(1),
93
97
  links: description.links,
98
+ substitutions: description.substitutions,
94
99
  };
95
100
  }
96
101
 
@@ -148,5 +153,9 @@ export async function getIssueTitleFromMarkdownDescription(description: Markdown
148
153
  Promise<string|null> {
149
154
  const rawMarkdown = await getMarkdownFileContent(description.file);
150
155
  const markdownAst = Marked.Marked.lexer(rawMarkdown);
151
- return findTitleFromMarkdownAst(markdownAst);
156
+ const markdownTitle = findTitleFromMarkdownAst(markdownAst);
157
+ if (!markdownTitle) {
158
+ return null;
159
+ }
160
+ return description.title ?? markdownTitle;
152
161
  }
@@ -77,10 +77,8 @@ export class SRIMessageSignatureIssue extends Issue<Protocol.Audits.SRIMessageSi
77
77
  description.substitutions?.set('PLACEHOLDER_signatureBase', () => details.signatureBase);
78
78
  }
79
79
  if (details.error === Protocol.Audits.SRIMessageSignatureError.ValidationFailedIntegrityMismatch) {
80
- description.substitutions?.set('PLACEHOLDER_integrityAssertions', () => {
81
- const prefix = '\n* ';
82
- return prefix + this.details().integrityAssertions.join(prefix);
83
- });
80
+ description.substitutions?.set('PLACEHOLDER_integrityAssertions',
81
+ () => this.details().integrityAssertions.join('\n'));
84
82
  }
85
83
  return resolveLazyDescription(description);
86
84
  }
@@ -1,4 +1,3 @@
1
1
  # Third-party websites are allowed to read cookies on this page
2
2
 
3
3
  One or more websites are allowed to bypass user settings to read third-party cookies on this page. Web developers should take steps to remove these reads without disrupting user experience. To forcefully block these third-party cookies, update [user settings](manageCookiesHelpPage).
4
- {PLACEHOLDER_topleveloptout}
@@ -0,0 +1,5 @@
1
+ # Third-party websites are allowed to read cookies on this page
2
+
3
+ One or more websites are allowed to bypass user settings to read third-party cookies on this page. Web developers should take steps to remove these reads without disrupting user experience. To forcefully block these third-party cookies, update [user settings](manageCookiesHelpPage).
4
+
5
+ (Top level site opt-out: {PLACEHOLDER_optOutPercentage}% - [learn more](gracePeriodStagedControlExplainer))
@@ -1,4 +1,3 @@
1
1
  # Third-party websites are allowed to set cookies on this page
2
2
 
3
3
  One or more websites are allowed to bypass user settings to set third-party cookies on this page. Web developers should take steps to remove these sets without disrupting user experience. To forcefully block these third-party cookies, update [user settings](manageCookiesHelpPage).
4
- {PLACEHOLDER_topleveloptout}
@@ -0,0 +1,5 @@
1
+ # Third-party websites are allowed to set cookies on this page
2
+
3
+ One or more websites are allowed to bypass user settings to set third-party cookies on this page. Web developers should take steps to remove these sets without disrupting user experience. To forcefully block these third-party cookies, update [user settings](manageCookiesHelpPage).
4
+
5
+ (Top level site opt-out: {PLACEHOLDER_optOutPercentage}% - [learn more](gracePeriodStagedControlExplainer))
@@ -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
+ ```
@@ -1367,6 +1367,11 @@ export const NativeFunctions: readonly NativeFunctionValue[] = [
1367
1367
  signatures: [["message","?options"],["message","transfer"],["message","targetOrigin","?transfer"]],
1368
1368
  receivers: ["Window"]
1369
1369
  },
1370
+ {
1371
+ name: "postMessage",
1372
+ signatures: [["message","targetOrigin"],["message","?options"]],
1373
+ receivers: ["HTMLPersistentWidgetElement","PersistentWidgetOpener"]
1374
+ },
1370
1375
  {
1371
1376
  name: "deleteRule",
1372
1377
  signatures: [["index"]],
@@ -2050,7 +2055,7 @@ export const NativeFunctions: readonly NativeFunctionValue[] = [
2050
2055
  },
2051
2056
  {
2052
2057
  name: "write",
2053
- signatures: [["...text"],["text"],["text1","...text"]],
2058
+ signatures: [["...text"]],
2054
2059
  receivers: ["Document"]
2055
2060
  },
2056
2061
  {
@@ -2489,7 +2494,7 @@ export const NativeFunctions: readonly NativeFunctionValue[] = [
2489
2494
  },
2490
2495
  {
2491
2496
  name: "writeln",
2492
- signatures: [["...text"],["text"],["text1","...text"]]
2497
+ signatures: [["...text"]]
2493
2498
  },
2494
2499
  {
2495
2500
  name: "elementFromPoint",
@@ -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
 
@@ -15,7 +15,7 @@ interface CachedScopeMap {
15
15
  }
16
16
 
17
17
  const scopeToCachedIdentifiersMap = new WeakMap<Formatter.FormatterWorkerPool.ScopeTreeNode, CachedScopeMap>();
18
- const cachedMapByCallFrame = new WeakMap<SDK.DebuggerModel.CallFrame, Map<string, string|null>>();
18
+ const cachedMapByCallFrame = new WeakMap<SDK.DebuggerModel.CallFrame, Array<Map<string, string|null>>>();
19
19
 
20
20
  export async function getTextFor(contentProvider: TextUtils.ContentProvider.ContentProvider):
21
21
  Promise<TextUtils.Text.Text|null> {
@@ -401,56 +401,61 @@ export const resolveScopeChain =
401
401
  return scopes.map(scope => new ScopeWithSourceMappedVariables(scope, thisObject, debuggerWorkspaceBinding));
402
402
  };
403
403
 
404
+ function reverseScopeMapping(variableMapping: Map<string, string>): Map<string, string|null> {
405
+ const result = new Map<string, string|null>();
406
+ for (const [compiledName, originalName] of variableMapping) {
407
+ if (originalName && !result.has(originalName)) {
408
+ result.set(originalName, compiledName);
409
+ }
410
+ }
411
+ return result;
412
+ }
413
+
404
414
  /**
405
- * @returns A mapping from original name -> compiled name. If the orignal name is unavailable (e.g. because the compiled name was
406
- * shadowed) we set it to `null`.
415
+ * @returns An array of mappings (from inner-most to outer-most scope) of original name -> compiled name or binding expression.
407
416
  */
408
417
  export const allVariablesInCallFrame = async(
409
418
  callFrame: SDK.DebuggerModel.CallFrame,
410
419
  debuggerWorkspaceBinding: Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding):
411
- Promise<Map<string, string|null>> => {
420
+ Promise<Array<Map<string, string|null>>> => {
412
421
  if (!callFrame.debuggerModel.target()
413
422
  .targetManager()
414
423
  .settings.resolve(SDK.SDKSettings.jsSourceMapsEnabledSettingDescriptor)
415
424
  .get()) {
416
- return new Map<string, string|null>();
425
+ return [];
417
426
  }
418
- const cachedMap = cachedMapByCallFrame.get(callFrame);
419
- if (cachedMap) {
420
- return cachedMap;
427
+ const cached = cachedMapByCallFrame.get(callFrame);
428
+ if (cached) {
429
+ return cached;
430
+ }
431
+
432
+ if (Root.Runtime.hostConfig.devToolsSourceMapScopesInSourcesPanel?.enabled) {
433
+ const sourceMap = callFrame.script.sourceMap() ??
434
+ await callFrame.debuggerModel.sourceMapManager().sourceMapForClientPromise(callFrame.script);
435
+ const mappedVariables =
436
+ sourceMap?.resolveMappedVariablesAtPosition(callFrame.location(), callFrame.returnValue() !== null);
437
+ if (mappedVariables) {
438
+ cachedMapByCallFrame.set(callFrame, mappedVariables);
439
+ return mappedVariables;
440
+ }
421
441
  }
422
442
 
423
443
  const scopeChain = callFrame.scopeChain().filter(scope => !scope.empty());
424
444
  const nameMappings =
425
445
  await Promise.all(scopeChain.map(scope => resolveDebuggerScope(scope, debuggerWorkspaceBinding)));
426
- const reverseMapping = new Map<string, string|null>();
427
- const compiledNames = new Set<string>();
428
- for (const {variableMapping} of nameMappings) {
429
- for (const [compiledName, originalName] of variableMapping) {
430
- if (!originalName) {
431
- continue;
432
- }
433
- if (!reverseMapping.has(originalName)) {
434
- // An inner scope might have shadowed {compiledName}. Mark it as "unavailable" in that case.
435
- const compiledNameOrNull = compiledNames.has(compiledName) ? null : compiledName;
436
- reverseMapping.set(originalName, compiledNameOrNull);
437
- }
438
- compiledNames.add(compiledName);
439
- }
440
- }
446
+ const reverseMapping = nameMappings.map(({variableMapping}) => reverseScopeMapping(variableMapping));
441
447
  cachedMapByCallFrame.set(callFrame, reverseMapping);
442
448
  return reverseMapping;
443
449
  };
444
450
 
445
451
  /**
446
- * @returns A mapping from original name -> compiled name. If the orignal name is unavailable (e.g. because the compiled name was
447
- * shadowed) we set it to `null`.
452
+ * @returns An array of mappings (from inner-most to outer-most scope) of original name -> compiled name or binding expression.
448
453
  */
449
454
  export const allVariablesAtPosition =
450
455
  async(location: SDK.DebuggerModel.Location,
451
456
  debuggerWorkspaceBinding: Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding):
452
- Promise<Map<string, string|null>> => {
453
- const reverseMapping = new Map<string, string|null>();
457
+ Promise<Array<Map<string, string|null>>> => {
458
+ const reverseMapping: Array<Map<string, string|null>> = [];
454
459
  const script = location.script();
455
460
  if (!script) {
456
461
  return reverseMapping;
@@ -462,6 +467,15 @@ export const allVariablesAtPosition =
462
467
  return reverseMapping;
463
468
  }
464
469
 
470
+ if (Root.Runtime.hostConfig.devToolsSourceMapScopesInSourcesPanel?.enabled) {
471
+ const sourceMap =
472
+ script.sourceMap() ?? await script.debuggerModel.sourceMapManager().sourceMapForClientPromise(script);
473
+ const mappedVariables = sourceMap?.resolveMappedVariablesAtPosition(location);
474
+ if (mappedVariables) {
475
+ return mappedVariables;
476
+ }
477
+ }
478
+
465
479
  const scopeTreeAndText = await computeScopeTree(script);
466
480
  if (!scopeTreeAndText) {
467
481
  return reverseMapping;
@@ -470,21 +484,10 @@ export const allVariablesAtPosition =
470
484
  const {scopeTree, text} = scopeTreeAndText;
471
485
  const locationOffset = text.offsetFromPosition(location.lineNumber, location.columnNumber);
472
486
  const scopeChain = findScopeChain(scopeTree, {start: locationOffset, end: locationOffset});
473
- const compiledNames = new Set<string>();
474
487
 
475
488
  while (scopeChain.length > 0) {
476
489
  const {variableMapping} = await resolveScope(script, scopeChain, debuggerWorkspaceBinding);
477
- for (const [compiledName, originalName] of variableMapping) {
478
- if (!originalName) {
479
- continue;
480
- }
481
- if (!reverseMapping.has(originalName)) {
482
- // An inner scope might have shadowed {compiledName}. Mark it as "unavailable" in that case.
483
- const compiledNameOrNull = compiledNames.has(compiledName) ? null : compiledName;
484
- reverseMapping.set(originalName, compiledNameOrNull);
485
- }
486
- compiledNames.add(compiledName);
487
- }
490
+ reverseMapping.push(reverseScopeMapping(variableMapping));
488
491
  scopeChain.pop();
489
492
  }
490
493
  return reverseMapping;
@@ -4,6 +4,7 @@
4
4
 
5
5
  import type * as Common from '../../core/common/common.js';
6
6
  import * as i18n from '../../core/i18n/i18n.js';
7
+ import * as Platform from '../../core/platform/platform.js';
7
8
  import * as SDK from '../../core/sdk/sdk.js';
8
9
  import type * as Protocol from '../../generated/protocol.js';
9
10
  import * as Buttons from '../../ui/components/buttons/buttons.js';
@@ -35,6 +36,10 @@ const UIStrings = {
35
36
  * @description Tooltip for the clear announcements button in the announcements tool.
36
37
  */
37
38
  clearAnnouncements: 'Clear announcements',
39
+ /**
40
+ * @description Tooltip for the export to CSV button in the announcements tool.
41
+ */
42
+ exportCsv: 'Export to CSV',
38
43
  /**
39
44
  * @description Label/title for the dropdown filter to select which announcement types to record.
40
45
  */
@@ -108,6 +113,8 @@ export interface ViewInput {
108
113
  isRecording: boolean;
109
114
  onToggleRecording: () => void;
110
115
  onClear: () => void;
116
+ onExportCsv: () => void;
117
+ canExport: boolean;
111
118
  recordTypeFilter: RecordTypeFilter;
112
119
  onRecordTypeFilterChange: (type: RecordTypeFilter) => void;
113
120
  textFilter: string;
@@ -144,6 +151,14 @@ export const DEFAULT_VIEW: View = (input, _output, target) => {
144
151
  .variant=${Buttons.Button.Variant.TOOLBAR}
145
152
  .jslogContext=${'accessibility.clear-announcements'}>
146
153
  </devtools-button>
154
+ <devtools-button
155
+ title=${i18nString(UIStrings.exportCsv)}
156
+ .iconName=${'download'}
157
+ .disabled=${!input.canExport}
158
+ @click=${input.onExportCsv}
159
+ .variant=${Buttons.Button.Variant.TOOLBAR}
160
+ .jslogContext=${'accessibility.export-csv'}>
161
+ </devtools-button>
147
162
  <div class="toolbar-divider" role="separator"></div>
148
163
  <select
149
164
  title=${i18nString(UIStrings.filterByType)}
@@ -729,6 +744,32 @@ export function validateAndSanitizeAnnouncement(payload: unknown): A11yAnnouncem
729
744
  };
730
745
  }
731
746
 
747
+ export function escapeCsvValue(val: string): string {
748
+ const escaped = val.replace(/"/g, '""');
749
+ if (escaped.includes(',') || escaped.includes('\n') || escaped.includes('\r') || escaped.includes('"')) {
750
+ return `"${escaped}"`;
751
+ }
752
+ return escaped;
753
+ }
754
+
755
+ export function buildCsvContent(announcements: readonly A11yAnnouncement[]): string {
756
+ const csvRows: string[] = [];
757
+ csvRows.push(['Time', 'API', 'Politeness', 'Message'].join(','));
758
+
759
+ for (const item of announcements) {
760
+ const timeString = new Date(item.time).toISOString();
761
+ const row = [
762
+ escapeCsvValue(timeString),
763
+ escapeCsvValue(item.api),
764
+ escapeCsvValue(item.politeness),
765
+ escapeCsvValue(item.message),
766
+ ];
767
+ csvRows.push(row.join(','));
768
+ }
769
+
770
+ return csvRows.join('\r\n');
771
+ }
772
+
732
773
  export class AccessibilityAnnouncementRecordingView extends AccessibilitySubPane implements SDK.TargetManager.Observer {
733
774
  #announcements: A11yAnnouncement[] = [];
734
775
  #filteredAnnouncements: readonly A11yAnnouncement[]|null = null;
@@ -984,6 +1025,29 @@ export class AccessibilityAnnouncementRecordingView extends AccessibilitySubPane
984
1025
  this.requestUpdate();
985
1026
  }
986
1027
 
1028
+ #exportCsv(): void {
1029
+ const csvContent = this.#buildCsvContent();
1030
+ const blob = new Blob(['\ufeff', csvContent], {type: 'text/csv;charset=utf-8'});
1031
+ const url = URL.createObjectURL(blob);
1032
+ /* eslint-disable-next-line @devtools/no-imperative-dom-api */
1033
+ const link = document.createElement('a');
1034
+ link.setAttribute('href', url);
1035
+ link.setAttribute('download', `aria-live-announcements-${Platform.DateUtilities.toISO8601Compact(new Date())}.csv`);
1036
+ link.style.visibility = 'hidden';
1037
+ document.body.appendChild(link);
1038
+ link.click();
1039
+ document.body.removeChild(link);
1040
+ URL.revokeObjectURL(url);
1041
+ }
1042
+
1043
+ #buildCsvContent(): string {
1044
+ return buildCsvContent(this.filteredAnnouncements);
1045
+ }
1046
+
1047
+ exportCsvForTest(): string {
1048
+ return this.#buildCsvContent();
1049
+ }
1050
+
987
1051
  override performUpdate(): void {
988
1052
  const blockedTargets: BlockedTargetInfo[] = [];
989
1053
  for (const [target, reason] of this.#blockedTargets) {
@@ -991,6 +1055,7 @@ export class AccessibilityAnnouncementRecordingView extends AccessibilitySubPane
991
1055
  blockedTargets.push({targetName, reason});
992
1056
  }
993
1057
 
1058
+ const filteredAnnouncements = this.filteredAnnouncements;
994
1059
  const input: ViewInput = {
995
1060
  isRecording: this.#isRecording,
996
1061
  onToggleRecording: () => {
@@ -1003,6 +1068,10 @@ export class AccessibilityAnnouncementRecordingView extends AccessibilitySubPane
1003
1068
  onClear: () => {
1004
1069
  this.clearAnnouncements();
1005
1070
  },
1071
+ onExportCsv: () => {
1072
+ this.#exportCsv();
1073
+ },
1074
+ canExport: filteredAnnouncements.length > 0,
1006
1075
  recordTypeFilter: this.#recordTypeFilter,
1007
1076
  onRecordTypeFilterChange: (type: RecordTypeFilter) => {
1008
1077
  this.setRecordTypeFilter(type);
@@ -1012,7 +1081,7 @@ export class AccessibilityAnnouncementRecordingView extends AccessibilitySubPane
1012
1081
  this.setTextFilter(text);
1013
1082
  },
1014
1083
  blockedTargets,
1015
- announcements: this.filteredAnnouncements,
1084
+ announcements: filteredAnnouncements,
1016
1085
  };
1017
1086
  this.#view(input, undefined, this.contentElement);
1018
1087
  }
@@ -1636,7 +1636,7 @@ async function makeDomTreeWidget(widgetData: AiAssistanceModel.AiAgent.DomTreeAi
1636
1636
  // clang-format off
1637
1637
  const renderedWidget = html`
1638
1638
  ${networkRequest ? renderNetworkRequestPreview(networkRequest) : Lit.nothing}
1639
- <devtools-widget class="dom-tree-widget" ${widget(Elements.ElementsTreeOutline.DOMTreeWidget, {
1639
+ <devtools-widget class="dom-tree-widget" ${widget(Elements.DOMTreeWidget.DOMTreeWidget, {
1640
1640
  maxTreeDepth: 2,
1641
1641
  enableContextMenu: false,
1642
1642
  showComments: false,
@@ -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
  }