chrome-devtools-frontend 1.0.1697998 → 1.0.1699061

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 (145) hide show
  1. package/.agents/skills/devtools-verification/SKILL.md +19 -0
  2. package/.agents/skills/{repro-flaky-tests → fix-tests}/SKILL.md +2 -2
  3. package/.agents/skills/version-control/SKILL.md +21 -9
  4. package/front_end/core/platform/HostRuntime.ts +12 -0
  5. package/front_end/core/platform/api/HostRuntime.ts +13 -0
  6. package/front_end/core/platform/browser/HostRuntime.ts +34 -0
  7. package/front_end/core/platform/node/HostRuntime.ts +40 -0
  8. package/front_end/core/sdk/AnimationModel.ts +12 -98
  9. package/front_end/core/sdk/CSSModel.ts +4 -2
  10. package/front_end/core/sdk/CSSPropertyParser.ts +4 -4
  11. package/front_end/core/sdk/CSSPropertyParserMatchers.ts +4 -44
  12. package/front_end/core/sdk/Connections.ts +6 -1
  13. package/front_end/core/sdk/ConsoleModel.ts +1 -12
  14. package/front_end/core/sdk/DOMModel.ts +6 -56
  15. package/front_end/core/sdk/DebuggerModel.ts +9 -8
  16. package/front_end/core/sdk/NetworkManager.ts +2 -2
  17. package/front_end/core/sdk/NetworkRequest.ts +1 -1
  18. package/front_end/core/sdk/OverlayModel.ts +11 -12
  19. package/front_end/core/sdk/PageFunctions.ts +187 -0
  20. package/front_end/core/sdk/Resource.ts +1 -1
  21. package/front_end/core/sdk/RuntimeModel.ts +1 -20
  22. package/front_end/core/sdk/Script.ts +21 -0
  23. package/front_end/core/sdk/SourceMap.ts +15 -2
  24. package/front_end/core/sdk/SourceMapCache.ts +13 -10
  25. package/front_end/core/sdk/SourceMapManager.ts +11 -9
  26. package/front_end/core/sdk/SourceMapScopeChainEntry.ts +109 -15
  27. package/front_end/core/sdk/SourceMapScopesInfo.ts +8 -2
  28. package/front_end/entrypoints/heap_snapshot_worker/ContextAnalyzer.ts +635 -0
  29. package/front_end/entrypoints/heap_snapshot_worker/HeapSnapshot.ts +26 -12
  30. package/front_end/entrypoints/heap_snapshot_worker/HeapSnapshotLoader.ts +16 -2
  31. package/front_end/entrypoints/heap_snapshot_worker/HeapSnapshotWorkerDispatcher.ts +4 -4
  32. package/front_end/entrypoints/heap_snapshot_worker/heap_snapshot_worker.ts +2 -0
  33. package/front_end/entrypoints/shell/shell.ts +1 -1
  34. package/front_end/models/ai_assistance/DOMHelpers.ts +95 -0
  35. package/front_end/models/ai_assistance/EvaluateAction.ts +5 -42
  36. package/front_end/models/ai_assistance/ExtensionScope.ts +2 -31
  37. package/front_end/models/ai_assistance/agents/AiAgent.ts +2 -1
  38. package/front_end/models/ai_assistance/agents/ContextSelectionAgent.ts +3 -4
  39. package/front_end/models/ai_assistance/ai_assistance.ts +2 -0
  40. package/front_end/models/ai_assistance/contexts/DOMNodeContext.ts +10 -7
  41. package/front_end/models/ai_assistance/contexts/FileContext.ts +2 -10
  42. package/front_end/models/ai_assistance/tools/GetSourceContent.ts +3 -2
  43. package/front_end/models/ai_assistance/tools/ListSources.ts +21 -14
  44. package/front_end/models/bindings/CompilerScriptMapping.ts +3 -10
  45. package/front_end/models/bindings/DebuggerLanguagePlugins.ts +48 -17
  46. package/front_end/models/bindings/ResourceScriptMapping.ts +2 -2
  47. package/front_end/models/bindings/StylesSourceMapping.ts +1 -1
  48. package/front_end/models/comment_manager/CD4ABridge.ts +55 -12
  49. package/front_end/models/comment_manager/CommentManager.ts +46 -91
  50. package/front_end/models/comment_manager/CommentThread.ts +145 -0
  51. package/front_end/models/comment_manager/comment_manager.ts +2 -0
  52. package/front_end/models/emulation/DeviceModeModel.ts +54 -1
  53. package/front_end/models/extensions/ExtensionAPI.ts +8 -6
  54. package/front_end/models/extensions/ExtensionEndpoint.ts +11 -8
  55. package/front_end/models/extensions/HostUrlPattern.ts +4 -3
  56. package/front_end/models/extensions/LanguageExtensionEndpoint.ts +3 -2
  57. package/front_end/models/extensions/RecorderExtensionEndpoint.ts +2 -1
  58. package/front_end/models/formatter/FormatterWorkerPool.ts +4 -6
  59. package/front_end/models/heap_snapshot/HeapSnapshotModel.ts +55 -0
  60. package/front_end/models/heap_snapshot/HeapSnapshotProxy.ts +18 -14
  61. package/front_end/models/issues_manager/CookieIssue.ts +0 -13
  62. package/front_end/models/issues_manager/IssuesManager.ts +5 -0
  63. package/front_end/models/issues_manager/WebInstallIssue.ts +63 -0
  64. package/front_end/models/issues_manager/descriptions/webInstallManifestMissingId.md +3 -0
  65. package/front_end/models/issues_manager/descriptions/webInstallManifestMissingNameOrShortName.md +3 -0
  66. package/front_end/models/issues_manager/descriptions/webInstallManifestParsingOrNetworkError.md +3 -0
  67. package/front_end/models/issues_manager/descriptions/webInstallNoManifest.md +3 -0
  68. package/front_end/models/issues_manager/descriptions/webInstallStartUrlInvalid.md +3 -0
  69. package/front_end/models/issues_manager/issues_manager.ts +2 -0
  70. package/front_end/models/source_map_scopes/NamesResolver.ts +11 -3
  71. package/front_end/models/source_map_scopes/ScopeChainModel.ts +10 -6
  72. package/front_end/models/workspace/UISourceCode.ts +20 -0
  73. package/front_end/panels/accessibility/AccessibilityAnnouncementRecordingView.ts +281 -3
  74. package/front_end/panels/accessibility/accessibilityAnnouncementRecordingView.css +85 -0
  75. package/front_end/panels/ai_assistance/AiAssistancePanel.ts +5 -8
  76. package/front_end/panels/ai_assistance/components/AIv2MarkdownRenderer.ts +4 -10
  77. package/front_end/panels/animation/AnimationTimeline.ts +1 -1
  78. package/front_end/panels/application/CrashReportContextView.ts +1 -1
  79. package/front_end/panels/application/IndexedDBViews.ts +1 -1
  80. package/front_end/panels/application/KeyValueStorageItemsView.ts +1 -0
  81. package/front_end/panels/application/components/AdsView.ts +63 -1
  82. package/front_end/panels/application/components/adsView.css +11 -1
  83. package/front_end/{ui/comments → panels/common}/CommentThreadWidget.ts +5 -20
  84. package/front_end/{ui/comments → panels/common}/CommentsOverlayWidget.ts +39 -62
  85. package/front_end/{ui/comments → panels/common}/CommentsStatusBarPill.ts +5 -5
  86. package/front_end/panels/common/ExtensionServer.ts +2 -1
  87. package/front_end/{ui/comments → panels/common}/commentThreadWidget.css +0 -12
  88. package/front_end/{ui/comments → panels/common}/commentsOverlay.css +2 -1
  89. package/front_end/{ui/comments/comments-meta.ts → panels/common/common-meta.ts} +11 -11
  90. package/front_end/panels/common/common.ts +3 -0
  91. package/front_end/panels/elements/AdoptedStyleSheetTreeElement.ts +3 -3
  92. package/front_end/panels/elements/CSSRuleValidator.ts +1 -1
  93. package/front_end/panels/elements/ElementsPanel.ts +9 -5
  94. package/front_end/panels/elements/ElementsTreeElement.ts +11 -3
  95. package/front_end/panels/elements/ElementsTreeOutline.ts +69 -74
  96. package/front_end/panels/elements/PropertiesWidget.ts +11 -10
  97. package/front_end/panels/elements/PropertyRenderer.ts +29 -25
  98. package/front_end/panels/elements/StylePropertyTreeElement.ts +6 -6
  99. package/front_end/panels/elements/elementsTreeOutline.css +11 -5
  100. package/front_end/panels/emulation/DeviceModeToolbar.ts +2 -3
  101. package/front_end/panels/event_listeners/EventListenersView.ts +33 -44
  102. package/front_end/panels/lighthouse/LighthouseController.ts +1 -1
  103. package/front_end/panels/linear_memory_inspector/LinearMemoryInspectorController.ts +6 -7
  104. package/front_end/panels/linear_memory_inspector/components/linearMemoryInspector.css +5 -1
  105. package/front_end/panels/linear_memory_inspector/linear_memory_inspector-meta.ts +1 -1
  106. package/front_end/panels/mobile_throttling/NetworkPanelIndicator.ts +1 -1
  107. package/front_end/panels/mobile_throttling/ThrottlingManager.ts +1 -1
  108. package/front_end/panels/mobile_throttling/ThrottlingSettingsTab.ts +1 -1
  109. package/front_end/panels/recorder/StepView.ts +19 -4
  110. package/front_end/panels/recorder/TimelineSection.ts +1 -1
  111. package/front_end/panels/recorder/stepView.css +1 -0
  112. package/front_end/panels/security/SecurityPanel.ts +5 -5
  113. package/front_end/panels/security/SecurityPanelSidebar.ts +2 -11
  114. package/front_end/panels/settings/BackendLinkingSettingsTab.ts +2 -1
  115. package/front_end/panels/settings/EditFileSystemView.ts +1 -1
  116. package/front_end/panels/sources/CallStackSidebarPane.ts +1 -1
  117. package/front_end/panels/sources/DebuggerPlugin.ts +2 -2
  118. package/front_end/panels/sources/WatchExpressionsSidebarPane.ts +11 -16
  119. package/front_end/panels/sources/sources-meta.ts +1 -1
  120. package/front_end/panels/timeline/components/DetailsView.ts +3 -3
  121. package/front_end/panels/timeline/components/OriginMap.ts +1 -1
  122. package/front_end/panels/timeline/components/insights/BaseInsightComponent.ts +2 -3
  123. package/front_end/panels/timeline/overlays/OverlaysImpl.ts +0 -3
  124. package/front_end/third_party/chromium/README.chromium +1 -1
  125. package/front_end/ui/comments/CommentOverlayManager.ts +28 -110
  126. package/front_end/ui/comments/comments.ts +0 -6
  127. package/front_end/ui/components/buttons/Button.ts +19 -1
  128. package/front_end/ui/legacy/ContextMenu.ts +16 -1
  129. package/front_end/ui/legacy/PlusButton.ts +5 -6
  130. package/front_end/ui/legacy/Treeoutline.ts +6 -0
  131. package/front_end/ui/legacy/Widget.ts +33 -0
  132. package/front_end/ui/legacy/components/cookie_table/CookiesTable.ts +3 -0
  133. package/front_end/ui/legacy/components/data_grid/DataGridElement.ts +14 -4
  134. package/front_end/ui/legacy/components/object_ui/ObjectPropertiesSection.ts +36 -32
  135. package/front_end/ui/visual_logging/KnownContextValues.ts +4 -0
  136. package/package.json +1 -1
  137. package/.agents/skills/devtools-ci/SKILL.md +0 -65
  138. package/.agents/skills/evaluate-ai-css-completion/SKILL.md +0 -143
  139. package/.agents/skills/evaluate-ai-css-completion/scripts/evaluate.js +0 -104
  140. package/.agents/skills/fixing-skipped-tests/SKILL.md +0 -32
  141. package/.agents/skills/gerrit-cli/SKILL.md +0 -74
  142. package/.agents/skills/gerrit-cli/references/commands.md +0 -169
  143. package/.agents/skills/gerrit-cli/references/workflows.md +0 -63
  144. package/.agents/skills/gerrit-cli/scripts/import_upstream.py +0 -85
  145. package/front_end/models/issues_manager/descriptions/cookieExcludeBlockedWithinRelatedWebsiteSet.md +0 -4
@@ -0,0 +1,63 @@
1
+ // Copyright 2026 The Chromium Authors
2
+ // Use of this source code is governed by a BSD-style license that can be
3
+ // found in the LICENSE file.
4
+
5
+ import type * as SDK from '../../core/sdk/sdk.js';
6
+ import * as Protocol from '../../generated/protocol.js';
7
+
8
+ import {Issue, IssueCategory, IssueKind} from './Issue.js';
9
+ import type {MarkdownIssueDescription} from './MarkdownIssueDescription.js';
10
+
11
+ export class WebInstallIssue extends Issue<Protocol.Audits.WebInstallIssueDetails> {
12
+ constructor(issueDetails: Protocol.Audits.WebInstallIssueDetails, issuesModel: SDK.IssuesModel.IssuesModel|null) {
13
+ super(`${Protocol.Audits.InspectorIssueCode.WebInstallIssue}::${issueDetails.reason}`, issueDetails, issuesModel);
14
+ }
15
+
16
+ override requests(): Protocol.Audits.AffectedRequest[] {
17
+ const {manifestUrl, reason} = this.details();
18
+ if (reason === Protocol.Audits.WebInstallIssueReason.NoManifest || !manifestUrl) {
19
+ return [];
20
+ }
21
+ return [{url: manifestUrl}];
22
+ }
23
+
24
+ getCategory(): IssueCategory {
25
+ return IssueCategory.OTHER;
26
+ }
27
+
28
+ getDescription(): MarkdownIssueDescription|null {
29
+ switch (this.details().reason) {
30
+ case Protocol.Audits.WebInstallIssueReason.ManifestParsingOrNetworkError:
31
+ return {file: 'webInstallManifestParsingOrNetworkError.md', links: []};
32
+ case Protocol.Audits.WebInstallIssueReason.StartUrlInvalid:
33
+ return {file: 'webInstallStartUrlInvalid.md', links: []};
34
+ case Protocol.Audits.WebInstallIssueReason.ManifestMissingNameOrShortName:
35
+ return {file: 'webInstallManifestMissingNameOrShortName.md', links: []};
36
+ case Protocol.Audits.WebInstallIssueReason.ManifestMissingId:
37
+ return {file: 'webInstallManifestMissingId.md', links: []};
38
+ case Protocol.Audits.WebInstallIssueReason.NoManifest:
39
+ return {file: 'webInstallNoManifest.md', links: []};
40
+ default:
41
+ console.warn('Unknown WebInstallIssueReason:', this.details().reason);
42
+ return null;
43
+ }
44
+ }
45
+
46
+ getKind(): IssueKind {
47
+ return IssueKind.PAGE_ERROR;
48
+ }
49
+
50
+ primaryKey(): string {
51
+ return JSON.stringify(this.details());
52
+ }
53
+
54
+ static fromInspectorIssue(issuesModel: SDK.IssuesModel.IssuesModel|null,
55
+ inspectorIssue: Protocol.Audits.InspectorIssue): WebInstallIssue[] {
56
+ const details = inspectorIssue.details.webInstallIssueDetails;
57
+ if (!details) {
58
+ console.warn('Web install issue without details received.');
59
+ return [];
60
+ }
61
+ return [new WebInstallIssue(details, issuesModel)];
62
+ }
63
+ }
@@ -0,0 +1,3 @@
1
+ # Manifest does not contain an `id` field
2
+
3
+ Add an `id` to the manifest. Alternatively, for installs using an explicit manifest URL, provide the computed manifest ID as `manifestId` alongside the manifest URL.
@@ -0,0 +1,3 @@
1
+ # Manifest does not contain a 'name' or 'short_name' field
2
+
3
+ Add a `name` or `short_name` field to the manifest.
@@ -0,0 +1,3 @@
1
+ # Manifest could not be fetched, is empty, or could not be parsed
2
+
3
+ Check that the web app manifest can be fetched and that it contains valid, non-empty JSON.
@@ -0,0 +1,3 @@
1
+ # No manifest detected
2
+
3
+ Provide a web app manifest for this page.
@@ -0,0 +1,3 @@
1
+ # Manifest 'start_url' is not valid
2
+
3
+ Provide a valid `start_url` in the manifest.
@@ -36,6 +36,7 @@ import * as SourceFrameIssuesManager from './SourceFrameIssuesManager.js';
36
36
  import * as SRIMessageSignatureIssue from './SRIMessageSignatureIssue.js';
37
37
  import * as StylesheetLoadingIssue from './StylesheetLoadingIssue.js';
38
38
  import * as UnencodedDigestIssue from './UnencodedDigestIssue.js';
39
+ import * as WebInstallIssue from './WebInstallIssue.js';
39
40
 
40
41
  export {
41
42
  ClientHintIssue,
@@ -72,4 +73,5 @@ export {
72
73
  SRIMessageSignatureIssue,
73
74
  StylesheetLoadingIssue,
74
75
  UnencodedDigestIssue,
76
+ WebInstallIssue,
75
77
  };
@@ -2,6 +2,7 @@
2
2
  // Use of this source code is governed by a BSD-style license that can be
3
3
  // found in the LICENSE file.
4
4
 
5
+ import * as Root from '../../core/root/root.js';
5
6
  import * as SDK from '../../core/sdk/sdk.js';
6
7
  import * as TextUtils from '../../core/text_utils/text_utils.js';
7
8
  import * as Protocol from '../../generated/protocol.js';
@@ -380,9 +381,16 @@ export const resolveScopeChain =
380
381
  return scopeChain;
381
382
  }
382
383
 
383
- // TODO(crbug.com/465968290): Re-enable creating the scope chain from the source map once:
384
- // 1) We have a flag indicating whether the source map contained variable/binding information.
385
- // 2) We have a chrome feature flag.
384
+ if (Root.Runtime.hostConfig.devToolsSourceMapScopesInSourcesPanel?.enabled) {
385
+ let sourceMap = callFrame.script.sourceMap();
386
+ if (!sourceMap && callFrame.debuggerModel?.sourceMapManager) {
387
+ sourceMap = await callFrame.debuggerModel.sourceMapManager().sourceMapForClientPromise(callFrame.script);
388
+ }
389
+ const mappedScopeChain = sourceMap?.resolveScopeChain(callFrame);
390
+ if (mappedScopeChain) {
391
+ return mappedScopeChain;
392
+ }
393
+ }
386
394
 
387
395
  if (callFrame.script.isWasm()) {
388
396
  return callFrame.scopeChain().filter(scope => !scope.empty());
@@ -35,8 +35,10 @@ export class ScopeChainModel extends Common.ObjectWrapper.ObjectWrapper<EventTyp
35
35
  this.#debuggerWorkspaceBinding = debuggerWorkspaceBinding;
36
36
  this.#callFrame.debuggerModel.addEventListener(
37
37
  SDK.DebuggerModel.Events.DebugInfoAttached, this.#debugInfoAttached, this);
38
- this.#callFrame.debuggerModel.sourceMapManager().addEventListener(
39
- SDK.SourceMapManager.Events.SourceMapAttached, this.#sourceMapAttached, this);
38
+ this.#callFrame.debuggerModel.sourceMapManager().addEventListener(SDK.SourceMapManager.Events.SourceMapAttached,
39
+ this.#sourceMapChanged, this);
40
+ this.#callFrame.debuggerModel.sourceMapManager().addEventListener(SDK.SourceMapManager.Events.SourceMapDetached,
41
+ this.#sourceMapChanged, this);
40
42
 
41
43
  void this.#throttler.schedule(this.#boundUpdate);
42
44
  }
@@ -44,8 +46,10 @@ export class ScopeChainModel extends Common.ObjectWrapper.ObjectWrapper<EventTyp
44
46
  dispose(): void {
45
47
  this.#callFrame.debuggerModel.removeEventListener(
46
48
  SDK.DebuggerModel.Events.DebugInfoAttached, this.#debugInfoAttached, this);
47
- this.#callFrame.debuggerModel.sourceMapManager().removeEventListener(
48
- SDK.SourceMapManager.Events.SourceMapAttached, this.#sourceMapAttached, this);
49
+ this.#callFrame.debuggerModel.sourceMapManager().removeEventListener(SDK.SourceMapManager.Events.SourceMapAttached,
50
+ this.#sourceMapChanged, this);
51
+ this.#callFrame.debuggerModel.sourceMapManager().removeEventListener(SDK.SourceMapManager.Events.SourceMapDetached,
52
+ this.#sourceMapChanged, this);
49
53
  this.listeners?.clear();
50
54
  }
51
55
 
@@ -60,8 +64,8 @@ export class ScopeChainModel extends Common.ObjectWrapper.ObjectWrapper<EventTyp
60
64
  }
61
65
  }
62
66
 
63
- #sourceMapAttached(event: Common.EventTarget
64
- .EventTargetEvent<{client: SDK.Script.Script, sourceMap: SDK.SourceMap.SourceMap}>): void {
67
+ #sourceMapChanged(event: Common.EventTarget
68
+ .EventTargetEvent<{client: SDK.Script.Script, sourceMap: SDK.SourceMap.SourceMap}>): void {
65
69
  if (event.data.client === this.#callFrame.script) {
66
70
  void this.#throttler.schedule(this.#boundUpdate);
67
71
  }
@@ -7,6 +7,7 @@
7
7
  import * as Common from '../../core/common/common.js';
8
8
  import * as i18n from '../../core/i18n/i18n.js';
9
9
  import * as Platform from '../../core/platform/platform.js';
10
+ import * as SDK from '../../core/sdk/sdk.js';
10
11
  import * as TextUtils from '../../core/text_utils/text_utils.js';
11
12
 
12
13
  import {IgnoreListManager} from './IgnoreListManager.js';
@@ -29,6 +30,7 @@ export class UISourceCode extends Common.ObjectWrapper.ObjectWrapper<EventTypes>
29
30
  TextUtils.ContentProvider.ContentProvider {
30
31
  readonly #origin: Platform.DevToolsPath.UrlString;
31
32
  readonly #parentURL: Platform.DevToolsPath.UrlString;
33
+ #securityOrigin?: SDK.SecurityOrigin.SecurityOrigin;
32
34
  #project: Project;
33
35
  #url: Platform.DevToolsPath.UrlString;
34
36
  #name: string;
@@ -110,6 +112,23 @@ export class UISourceCode extends Common.ObjectWrapper.ObjectWrapper<EventTypes>
110
112
  return this.#origin;
111
113
  }
112
114
 
115
+ /**
116
+ * Returns the security origin for this source code.
117
+ * Prefers the project security origin if available. If the project does not
118
+ * define a security origin, derives it from the source code's URL and caches
119
+ * the result until the source code is renamed.
120
+ */
121
+ securityOrigin(): SDK.SecurityOrigin.SecurityOrigin {
122
+ const projectOrigin = this.#project.securityOrigin?.();
123
+ if (projectOrigin) {
124
+ return projectOrigin;
125
+ }
126
+ if (!this.#securityOrigin) {
127
+ this.#securityOrigin = SDK.SecurityOrigin.SecurityOrigin.create(this.#url);
128
+ }
129
+ return this.#securityOrigin;
130
+ }
131
+
113
132
  fullDisplayName(): string {
114
133
  return this.#project.fullDisplayName(this);
115
134
  }
@@ -150,6 +169,7 @@ export class UISourceCode extends Common.ObjectWrapper.ObjectWrapper<EventTypes>
150
169
  #updateName(
151
170
  name: Platform.DevToolsPath.RawPathString, url: Platform.DevToolsPath.UrlString,
152
171
  contentType?: Common.ResourceType.ResourceType): void {
172
+ this.#securityOrigin = undefined;
153
173
  const oldURL = this.#url;
154
174
  this.#name = name;
155
175
  if (url) {
@@ -6,25 +6,192 @@ import type * as Common from '../../core/common/common.js';
6
6
  import * as i18n from '../../core/i18n/i18n.js';
7
7
  import * as SDK from '../../core/sdk/sdk.js';
8
8
  import type * as Protocol from '../../generated/protocol.js';
9
+ import * as Buttons from '../../ui/components/buttons/buttons.js';
10
+ import * as UI from '../../ui/legacy/legacy.js';
11
+ import * as Lit from '../../ui/lit/lit.js';
12
+ import * as VisualLogging from '../../ui/visual_logging/visual_logging.js';
9
13
 
14
+ import {AccessibilityAnnouncementRecordingListView} from './AccessibilityAnnouncementRecordingListView.js';
15
+ import accessibilityAnnouncementRecordingViewStyles from './accessibilityAnnouncementRecordingView.css.js';
10
16
  import {AccessibilitySubPane} from './AccessibilitySubPane.js';
11
17
 
18
+ const {html, render} = Lit;
19
+ const {widget} = UI.Widget;
20
+
12
21
  const UIStrings = {
13
22
  /**
14
23
  * @description Title for the ARIA-Live and JS announcements recording tool
15
24
  */
16
25
  ariaLiveRecording: 'Announcements recording',
26
+ /**
27
+ * @description Tooltip for the start recording button in the announcements tool.
28
+ */
29
+ startRecording: 'Start recording',
30
+ /**
31
+ * @description Tooltip for the stop recording button in the announcements tool.
32
+ */
33
+ stopRecording: 'Stop recording',
34
+ /**
35
+ * @description Tooltip for the clear announcements button in the announcements tool.
36
+ */
37
+ clearAnnouncements: 'Clear announcements',
38
+ /**
39
+ * @description Label/title for the dropdown filter to select which announcement types to record.
40
+ */
41
+ filterByType: 'Filter by type',
42
+ /**
43
+ * @description Option label to record and display both ARIA-live and JavaScript announcements.
44
+ */
45
+ recordBoth: 'Record both',
46
+ /**
47
+ * @description Option label to record and display only ARIA-live announcements.
48
+ */
49
+ ariaLiveOnly: 'ARIA-live only',
50
+ /**
51
+ * @description Option label to record and display only JavaScript-triggered announcements.
52
+ */
53
+ announcementsOnly: 'Announcements only',
54
+ /**
55
+ * @description Placeholder text for the filter input in the announcements tool.
56
+ */
57
+ filter: 'Filter',
58
+ /**
59
+ * @description Screen reader announcement when no events match the filter in the announcements tool.
60
+ */
61
+ noEventsMatch: 'No events match',
62
+ /**
63
+ * @description Screen reader announcement when exactly one event matches the filter in the announcements tool.
64
+ */
65
+ oneEventMatches: '1 event matches',
66
+ /**
67
+ * @description Screen reader announcement when multiple events match the filter in the announcements tool.
68
+ * @example {15} PH1
69
+ */
70
+ nEventsMatch: '{PH1} events match',
71
+ /**
72
+ * @description Warning banner title shown when recording could not be enabled in some frames.
73
+ */
74
+ recordingBlockedWarning: 'Recording was blocked for some frames:',
75
+ /**
76
+ * @description Warning item describing a specific frame and the reason recording was blocked.
77
+ * @example {iframe#main} PH1
78
+ * @example {Script evaluation failed} PH2
79
+ */
80
+ frameBlockedReason: '{PH1}: {PH2}',
81
+ /**
82
+ * @description Fallback reason shown when an unknown error occurs while blocking recording.
83
+ */
84
+ unknownError: 'Unknown error',
17
85
  } as const;
18
86
  const str_ = i18n.i18n.registerUIStrings('panels/accessibility/AccessibilityAnnouncementRecordingView.ts', UIStrings);
19
87
  const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
20
88
 
21
89
  export const BINDING_NAME = '__announcementsRecorderBinding';
22
90
 
23
- export const enum AnnouncementApi {
91
+ export enum AnnouncementApi {
24
92
  ARIA_LIVE = 'aria-live',
25
93
  JS_TRIGGERED = 'js-triggered',
26
94
  }
27
95
 
96
+ export const enum RecordTypeFilter {
97
+ BOTH = 'both',
98
+ ARIA_LIVE = 'aria-live',
99
+ JS_TRIGGERED = 'js-triggered',
100
+ }
101
+
102
+ export interface BlockedTargetInfo {
103
+ targetName: string;
104
+ reason: string;
105
+ }
106
+
107
+ export interface ViewInput {
108
+ isRecording: boolean;
109
+ onToggleRecording: () => void;
110
+ onClear: () => void;
111
+ recordTypeFilter: RecordTypeFilter;
112
+ onRecordTypeFilterChange: (type: RecordTypeFilter) => void;
113
+ textFilter: string;
114
+ onTextFilterChange: (text: string) => void;
115
+ blockedTargets: BlockedTargetInfo[];
116
+ announcements: readonly A11yAnnouncement[];
117
+ }
118
+
119
+ export type View = (input: ViewInput, output: undefined, target: HTMLElement) => void;
120
+
121
+ export const DEFAULT_VIEW: View = (input, _output, target) => {
122
+ // clang-format off
123
+ render(html`
124
+ <style>${accessibilityAnnouncementRecordingViewStyles}</style>
125
+ <div class="accessibility-announcement-recording-view">
126
+ <div class="announcements-toolbar-container">
127
+ <devtools-toolbar class="announcements-toolbar" jslog=${VisualLogging.toolbar()}>
128
+ <devtools-button
129
+ title=${input.isRecording ? i18nString(UIStrings.stopRecording) : i18nString(UIStrings.startRecording)}
130
+ aria-label=${input.isRecording ? i18nString(UIStrings.stopRecording) : i18nString(UIStrings.startRecording)}
131
+ .iconName=${'record-start'}
132
+ .toggledIconName=${'record-stop'}
133
+ .toggleType=${Buttons.Button.ToggleType.PRIMARY}
134
+ .toggled=${input.isRecording}
135
+ @click=${input.onToggleRecording}
136
+ .variant=${Buttons.Button.Variant.TOOLBAR}
137
+ .jslogContext=${'accessibility.toggle-recording'}>
138
+ </devtools-button>
139
+ <devtools-button
140
+ title=${i18nString(UIStrings.clearAnnouncements)}
141
+ aria-label=${i18nString(UIStrings.clearAnnouncements)}
142
+ .iconName=${'clear'}
143
+ @click=${input.onClear}
144
+ .variant=${Buttons.Button.Variant.TOOLBAR}
145
+ .jslogContext=${'accessibility.clear-announcements'}>
146
+ </devtools-button>
147
+ <div class="toolbar-divider" role="separator"></div>
148
+ <select
149
+ title=${i18nString(UIStrings.filterByType)}
150
+ aria-label=${i18nString(UIStrings.filterByType)}
151
+ @change=${(event: Event) => input.onRecordTypeFilterChange((event.target as HTMLSelectElement).value as RecordTypeFilter)}
152
+ .value=${input.recordTypeFilter}
153
+ jslog=${VisualLogging.dropDown('accessibility-announcements.filter-by-type').track({change: true})}>
154
+ <option value=${RecordTypeFilter.BOTH} .selected=${input.recordTypeFilter === RecordTypeFilter.BOTH}>
155
+ ${i18nString(UIStrings.recordBoth)}
156
+ </option>
157
+ <option value=${RecordTypeFilter.ARIA_LIVE} .selected=${input.recordTypeFilter === RecordTypeFilter.ARIA_LIVE}>
158
+ ${i18nString(UIStrings.ariaLiveOnly)}
159
+ </option>
160
+ <option value=${RecordTypeFilter.JS_TRIGGERED} .selected=${input.recordTypeFilter === RecordTypeFilter.JS_TRIGGERED}>
161
+ ${i18nString(UIStrings.announcementsOnly)}
162
+ </option>
163
+ </select>
164
+ <div class="toolbar-divider" role="separator"></div>
165
+ <devtools-toolbar-input
166
+ type="filter"
167
+ placeholder=${i18nString(UIStrings.filter)}
168
+ .value=${input.textFilter}
169
+ @change=${(event: CustomEvent<string>) => input.onTextFilterChange(event.detail)}
170
+ style="flex-grow: 1">
171
+ </devtools-toolbar-input>
172
+ </devtools-toolbar>
173
+ </div>
174
+ ${input.blockedTargets.length > 0 ? html`
175
+ <div class="announcements-blocked-banner" role="alert">
176
+ <div class="blocked-banner-header">
177
+ <devtools-icon name="warning-filled"></devtools-icon>
178
+ <span>${i18nString(UIStrings.recordingBlockedWarning)}</span>
179
+ </div>
180
+ <ul class="blocked-targets-list">
181
+ ${input.blockedTargets.map(targetInfo => html`
182
+ <li>${i18nString(UIStrings.frameBlockedReason, {PH1: targetInfo.targetName, PH2: targetInfo.reason || i18nString(UIStrings.unknownError)})}</li>
183
+ `)}
184
+ </ul>
185
+ </div>
186
+ ` : Lit.nothing}
187
+ <div class="announcements-main-pane">
188
+ ${widget(AccessibilityAnnouncementRecordingListView, {items: input.announcements})}
189
+ </div>
190
+ </div>`,
191
+ target);
192
+ // clang-format on
193
+ };
194
+
28
195
  declare global {
29
196
  interface Window {
30
197
  // eslint-disable-next-line @typescript-eslint/naming-convention
@@ -564,17 +731,23 @@ export function validateAndSanitizeAnnouncement(payload: unknown): A11yAnnouncem
564
731
 
565
732
  export class AccessibilityAnnouncementRecordingView extends AccessibilitySubPane implements SDK.TargetManager.Observer {
566
733
  #announcements: A11yAnnouncement[] = [];
734
+ #filteredAnnouncements: readonly A11yAnnouncement[]|null = null;
567
735
  #isRecording = false;
568
736
  #blockedTargets = new Map<SDK.Target.Target, string>();
569
737
  #scriptIdentifiers = new Map<SDK.Target.Target, Protocol.Page.ScriptIdentifier>();
570
738
  #targets = new Set<SDK.Target.Target>();
571
739
  #enabledTargets = new Set<SDK.Target.Target>();
740
+ #recordTypeFilter: RecordTypeFilter = RecordTypeFilter.BOTH;
741
+ #textFilter = '';
742
+ #regexFilter: RegExp|null = null;
743
+ readonly #view: View;
572
744
 
573
- constructor() {
745
+ constructor(view: View = DEFAULT_VIEW) {
574
746
  super({
575
747
  title: i18nString(UIStrings.ariaLiveRecording),
576
748
  viewId: 'aria-live-recording',
577
749
  });
750
+ this.#view = view;
578
751
  SDK.TargetManager.TargetManager.instance().observeTargets(this, {scoped: true});
579
752
  }
580
753
 
@@ -595,8 +768,11 @@ export class AccessibilityAnnouncementRecordingView extends AccessibilitySubPane
595
768
 
596
769
  async targetRemoved(target: SDK.Target.Target): Promise<void> {
597
770
  this.#targets.delete(target);
598
- this.#blockedTargets.delete(target);
771
+ const wasBlocked = this.#blockedTargets.delete(target);
599
772
  await this.#disableTarget(target);
773
+ if (wasBlocked) {
774
+ this.requestUpdate();
775
+ }
600
776
  }
601
777
 
602
778
  async #enableTarget(target: SDK.Target.Target): Promise<void> {
@@ -708,6 +884,11 @@ export class AccessibilityAnnouncementRecordingView extends AccessibilitySubPane
708
884
  return;
709
885
  }
710
886
  this.#announcements.push(announcement);
887
+ if (this.#filteredAnnouncements !== null) {
888
+ if (this.#matchesFilter(announcement)) {
889
+ this.#filteredAnnouncements = [...this.#filteredAnnouncements, announcement];
890
+ }
891
+ }
711
892
  this.requestUpdate();
712
893
  }
713
894
 
@@ -736,9 +917,106 @@ export class AccessibilityAnnouncementRecordingView extends AccessibilitySubPane
736
917
 
737
918
  clearAnnouncements(): void {
738
919
  this.#announcements = [];
920
+ this.#filteredAnnouncements = [];
921
+ this.requestUpdate();
922
+ }
923
+
924
+ #matchesFilter(announcement: A11yAnnouncement): boolean {
925
+ if (this.#recordTypeFilter === RecordTypeFilter.ARIA_LIVE && announcement.api !== AnnouncementApi.ARIA_LIVE) {
926
+ return false;
927
+ }
928
+ if (this.#recordTypeFilter === RecordTypeFilter.JS_TRIGGERED && announcement.api !== AnnouncementApi.JS_TRIGGERED) {
929
+ return false;
930
+ }
931
+ if (this.#regexFilter && !this.#regexFilter.test(announcement.message)) {
932
+ return false;
933
+ }
934
+ return true;
935
+ }
936
+
937
+ get filteredAnnouncements(): readonly A11yAnnouncement[] {
938
+ if (this.#filteredAnnouncements !== null) {
939
+ return this.#filteredAnnouncements;
940
+ }
941
+ this.#filteredAnnouncements = this.#announcements.filter(announcement => this.#matchesFilter(announcement));
942
+ return this.#filteredAnnouncements;
943
+ }
944
+
945
+ #announceFilterMatches(): void {
946
+ const count = this.filteredAnnouncements.length;
947
+ let message: string;
948
+ if (count === 0) {
949
+ message = i18nString(UIStrings.noEventsMatch);
950
+ } else if (count === 1) {
951
+ message = i18nString(UIStrings.oneEventMatches);
952
+ } else {
953
+ message = i18nString(UIStrings.nEventsMatch, {PH1: count});
954
+ }
955
+ UI.ARIAUtils.LiveAnnouncer.alert(message);
956
+ }
957
+
958
+ setRecordTypeFilter(type: RecordTypeFilter): void {
959
+ if (this.#recordTypeFilter === type) {
960
+ return;
961
+ }
962
+ this.#recordTypeFilter = type;
963
+ this.#filteredAnnouncements = null;
964
+ this.#announceFilterMatches();
739
965
  this.requestUpdate();
740
966
  }
741
967
 
968
+ setTextFilter(text: string): void {
969
+ if (this.#textFilter === text) {
970
+ return;
971
+ }
972
+ this.#textFilter = text;
973
+ if (!text) {
974
+ this.#regexFilter = null;
975
+ } else {
976
+ try {
977
+ this.#regexFilter = new RegExp(text, 'i');
978
+ } catch {
979
+ this.#regexFilter = new RegExp('(?!)', 'i');
980
+ }
981
+ }
982
+ this.#filteredAnnouncements = null;
983
+ this.#announceFilterMatches();
984
+ this.requestUpdate();
985
+ }
986
+
987
+ override performUpdate(): void {
988
+ const blockedTargets: BlockedTargetInfo[] = [];
989
+ for (const [target, reason] of this.#blockedTargets) {
990
+ const targetName = target.name() || target.inspectedURL() || target.id();
991
+ blockedTargets.push({targetName, reason});
992
+ }
993
+
994
+ const input: ViewInput = {
995
+ isRecording: this.#isRecording,
996
+ onToggleRecording: () => {
997
+ if (this.#isRecording) {
998
+ void this.stopRecording();
999
+ } else {
1000
+ void this.startRecording();
1001
+ }
1002
+ },
1003
+ onClear: () => {
1004
+ this.clearAnnouncements();
1005
+ },
1006
+ recordTypeFilter: this.#recordTypeFilter,
1007
+ onRecordTypeFilterChange: (type: RecordTypeFilter) => {
1008
+ this.setRecordTypeFilter(type);
1009
+ },
1010
+ textFilter: this.#textFilter,
1011
+ onTextFilterChange: (text: string) => {
1012
+ this.setTextFilter(text);
1013
+ },
1014
+ blockedTargets,
1015
+ announcements: this.filteredAnnouncements,
1016
+ };
1017
+ this.#view(input, undefined, this.contentElement);
1018
+ }
1019
+
742
1020
  announcementsForTest(): A11yAnnouncement[] {
743
1021
  return [...this.#announcements];
744
1022
  }
@@ -0,0 +1,85 @@
1
+ /*
2
+ * Copyright 2026 The Chromium Authors
3
+ * Use of this source code is governed by a BSD-style license that can be
4
+ * found in the LICENSE file.
5
+ */
6
+
7
+ @scope to (devtools-widget > *) {
8
+ :scope {
9
+ font-family: inherit;
10
+ display: flex;
11
+ flex-direction: column;
12
+ flex: auto;
13
+ height: 100%;
14
+ overflow: hidden;
15
+ }
16
+
17
+ .accessibility-announcement-recording-view {
18
+ display: flex;
19
+ flex-direction: column;
20
+ flex: auto;
21
+ height: 100%;
22
+ overflow: hidden;
23
+ }
24
+
25
+ .announcements-toolbar-container {
26
+ display: flex;
27
+ flex: none;
28
+ background-color: var(--sys-color-cdt-base-container);
29
+ border-bottom: var(--sys-size-1) solid var(--sys-color-divider);
30
+ }
31
+
32
+ .announcements-toolbar {
33
+ flex: auto;
34
+ }
35
+
36
+ .announcements-toolbar select {
37
+ background-color: transparent;
38
+ border: none;
39
+ color: var(--sys-color-on-surface);
40
+ font-size: var(--sys-typescale-body4-size);
41
+ padding: var(--sys-size-2) var(--sys-size-4);
42
+ height: 100%;
43
+ cursor: pointer;
44
+ }
45
+
46
+ .toolbar-divider {
47
+ background-color: var(--sys-color-divider);
48
+ width: var(--sys-size-1);
49
+ margin: 5px var(--sys-size-3);
50
+ height: var(--sys-size-8);
51
+ }
52
+
53
+ .announcements-blocked-banner {
54
+ display: flex;
55
+ flex-direction: column;
56
+ flex: none;
57
+ gap: var(--sys-size-3);
58
+ padding: var(--sys-size-5) var(--sys-size-8);
59
+ background-color: var(--sys-color-surface-yellow);
60
+ color: var(--sys-color-on-surface-yellow);
61
+ border-bottom: var(--sys-size-1) solid var(--sys-color-divider);
62
+ font-size: var(--sys-typescale-body4-size);
63
+ line-height: var(--sys-typescale-body4-line-height);
64
+ }
65
+
66
+ .blocked-banner-header {
67
+ display: flex;
68
+ align-items: center;
69
+ gap: var(--sys-size-4);
70
+ font-weight: var(--ref-typeface-weight-medium);
71
+ }
72
+
73
+ .blocked-targets-list {
74
+ margin: 0;
75
+ padding-left: var(--sys-size-8);
76
+ }
77
+
78
+ .announcements-main-pane {
79
+ display: flex;
80
+ flex-direction: column;
81
+ flex: auto;
82
+ min-height: 0;
83
+ overflow: hidden;
84
+ }
85
+ }
@@ -282,12 +282,10 @@ async function getEmptyStateSuggestions(conversation?: AiAssistanceModel.AiConve
282
282
  }
283
283
  }
284
284
 
285
- function createV2MarkdownRenderer(conversation?: AiAssistanceModel.AiConversation.AiConversation):
286
- AIv2MarkdownRenderer {
287
- const options: AIv2MarkdownRendererOptions = {};
288
- if (conversation) {
289
- options.getEstablishedOrigin = () => conversation.origin;
290
- }
285
+ function createV2MarkdownRenderer(conversation: AiAssistanceModel.AiConversation.AiConversation): AIv2MarkdownRenderer {
286
+ const options: AIv2MarkdownRendererOptions = {
287
+ getOriginLock: () => conversation.getOriginLock(),
288
+ };
291
289
  const primaryTarget = SDK.TargetManager.TargetManager.instance().primaryPageTarget();
292
290
  const domModel = primaryTarget?.model(SDK.DOMModel.DOMModel);
293
291
  const resourceTreeModel = primaryTarget?.model(SDK.ResourceTreeModel.ResourceTreeModel);
@@ -393,9 +391,8 @@ function toolbarView(input: ToolbarViewInput): Lit.LitTemplate {
393
391
  @click=${input.onNewChatClick}></devtools-button>
394
392
  <div class="toolbar-divider"></div>
395
393
  <devtools-menu-button
396
- title=${i18nString(UIStrings.history)}
397
- aria-label=${i18nString(UIStrings.history)}
398
394
  .iconName=${'history'}
395
+ .accessibleLabel=${i18nString(UIStrings.history)}
399
396
  .jslogContext=${'freestyler.history'}
400
397
  .populateMenuCall=${input.populateHistoryMenu}
401
398
  ></devtools-menu-button>`