chrome-devtools-frontend 1.0.1697595 → 1.0.1698733

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 (124) hide show
  1. package/.agents/skills/version-control/SKILL.md +21 -9
  2. package/.stylelintignore +2 -0
  3. package/front_end/core/common/VersionController.ts +5 -1
  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 +21 -99
  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 +3 -51
  15. package/front_end/core/sdk/DebuggerModel.ts +9 -8
  16. package/front_end/core/sdk/NetworkRequest.ts +1 -1
  17. package/front_end/core/sdk/PageFunctions.ts +187 -0
  18. package/front_end/core/sdk/Resource.ts +1 -1
  19. package/front_end/core/sdk/RuntimeModel.ts +1 -20
  20. package/front_end/core/sdk/Script.ts +21 -0
  21. package/front_end/core/sdk/SourceMap.ts +15 -2
  22. package/front_end/core/sdk/SourceMapCache.ts +13 -10
  23. package/front_end/core/sdk/SourceMapManager.ts +11 -9
  24. package/front_end/core/sdk/SourceMapScopeChainEntry.ts +59 -17
  25. package/front_end/core/sdk/SourceMapScopesInfo.ts +8 -2
  26. package/front_end/entrypoints/heap_snapshot_worker/ContextAnalyzer.ts +635 -0
  27. package/front_end/entrypoints/heap_snapshot_worker/HeapSnapshot.ts +26 -12
  28. package/front_end/entrypoints/heap_snapshot_worker/HeapSnapshotLoader.ts +16 -2
  29. package/front_end/entrypoints/heap_snapshot_worker/HeapSnapshotWorkerDispatcher.ts +4 -4
  30. package/front_end/entrypoints/heap_snapshot_worker/heap_snapshot_worker.ts +2 -0
  31. package/front_end/entrypoints/shell/shell.ts +1 -1
  32. package/front_end/generated/InspectorBackendCommands.ts +1 -1
  33. package/front_end/generated/protocol.ts +8 -2
  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 +27 -2
  39. package/front_end/models/ai_assistance/ai_assistance.ts +6 -4
  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/skills/performance.md +4 -3
  43. package/front_end/models/ai_assistance/tools/GetSourceContent.ts +3 -2
  44. package/front_end/models/ai_assistance/tools/{GetFunctionCode.ts → GetTraceFunctionCode.ts} +26 -13
  45. package/front_end/models/ai_assistance/tools/{GetResourceContent.ts → GetTraceResourceContent.ts} +31 -11
  46. package/front_end/models/ai_assistance/tools/ListSources.ts +21 -14
  47. package/front_end/models/ai_assistance/tools/Tool.ts +2 -2
  48. package/front_end/models/ai_assistance/tools/ToolRegistry.ts +6 -6
  49. package/front_end/models/bindings/CompilerScriptMapping.ts +3 -10
  50. package/front_end/models/bindings/DebuggerLanguagePlugins.ts +48 -17
  51. package/front_end/models/bindings/ResourceScriptMapping.ts +2 -2
  52. package/front_end/models/bindings/StylesSourceMapping.ts +1 -1
  53. package/front_end/models/comment_manager/CD4ABridge.ts +55 -12
  54. package/front_end/models/comment_manager/CommentManager.ts +46 -91
  55. package/front_end/models/comment_manager/CommentThread.ts +145 -0
  56. package/front_end/models/comment_manager/comment_manager.ts +2 -0
  57. package/front_end/models/elements/DOMChanges.ts +238 -0
  58. package/front_end/models/elements/elements.ts +2 -0
  59. package/front_end/models/emulation/DeviceModeModel.ts +53 -0
  60. package/front_end/models/extensions/ExtensionAPI.ts +8 -6
  61. package/front_end/models/extensions/ExtensionEndpoint.ts +11 -8
  62. package/front_end/models/extensions/HostUrlPattern.ts +4 -3
  63. package/front_end/models/extensions/LanguageExtensionEndpoint.ts +3 -2
  64. package/front_end/models/extensions/RecorderExtensionEndpoint.ts +2 -1
  65. package/front_end/models/formatter/FormatterWorkerPool.ts +4 -6
  66. package/front_end/models/heap_snapshot/HeapSnapshotModel.ts +55 -0
  67. package/front_end/models/heap_snapshot/HeapSnapshotProxy.ts +18 -14
  68. package/front_end/models/issues_manager/IssuesManager.ts +5 -0
  69. package/front_end/models/issues_manager/WebInstallIssue.ts +63 -0
  70. package/front_end/models/issues_manager/descriptions/webInstallManifestMissingId.md +3 -0
  71. package/front_end/models/issues_manager/descriptions/webInstallManifestMissingNameOrShortName.md +3 -0
  72. package/front_end/models/issues_manager/descriptions/webInstallManifestParsingOrNetworkError.md +3 -0
  73. package/front_end/models/issues_manager/descriptions/webInstallNoManifest.md +3 -0
  74. package/front_end/models/issues_manager/descriptions/webInstallStartUrlInvalid.md +3 -0
  75. package/front_end/models/issues_manager/issues_manager.ts +2 -0
  76. package/front_end/models/workspace/UISourceCode.ts +20 -0
  77. package/front_end/panels/ai_assistance/AiAssistancePanel.ts +7 -7
  78. package/front_end/panels/ai_assistance/components/AIv2MarkdownRenderer.ts +4 -10
  79. package/front_end/panels/ai_assistance/components/MarkdownRendererWithCodeBlock.ts +25 -5
  80. package/front_end/panels/browser_debugger/ObjectEventListenersSidebarPane.ts +2 -3
  81. package/front_end/panels/common/CommentThreadWidget.ts +221 -0
  82. package/front_end/{ui/comments → panels/common}/CommentsOverlayWidget.ts +117 -28
  83. package/front_end/{ui/comments → panels/common}/CommentsStatusBarPill.ts +5 -5
  84. package/front_end/panels/common/ExtensionServer.ts +2 -1
  85. package/front_end/panels/common/commentThreadWidget.css +173 -0
  86. package/front_end/{ui/comments → panels/common}/commentsOverlay.css +8 -1
  87. package/front_end/{ui/comments/comments-meta.ts → panels/common/common-meta.ts} +11 -11
  88. package/front_end/panels/common/common.ts +3 -0
  89. package/front_end/panels/elements/AdoptedStyleSheetTreeElement.ts +3 -3
  90. package/front_end/panels/elements/ElementsPanel.ts +5 -1
  91. package/front_end/panels/elements/ElementsTreeElement.ts +102 -21
  92. package/front_end/panels/elements/ElementsTreeOutline.ts +326 -91
  93. package/front_end/panels/elements/EventListenersWidget.ts +0 -1
  94. package/front_end/panels/elements/PropertiesWidget.ts +11 -10
  95. package/front_end/panels/elements/PropertyRenderer.ts +29 -25
  96. package/front_end/panels/elements/StylePropertyTreeElement.ts +6 -6
  97. package/front_end/panels/elements/elementsTreeOutline.css +7 -1
  98. package/front_end/panels/emulation/DeviceModeToolbar.ts +2 -3
  99. package/front_end/panels/event_listeners/EventListenersView.ts +235 -302
  100. package/front_end/panels/linear_memory_inspector/LinearMemoryInspectorController.ts +6 -7
  101. package/front_end/panels/linear_memory_inspector/components/linearMemoryInspector.css +5 -1
  102. package/front_end/panels/linear_memory_inspector/linear_memory_inspector-meta.ts +1 -1
  103. package/front_end/panels/mobile_throttling/ThrottlingSettingsTab.ts +10 -2
  104. package/front_end/panels/network/NetworkConfigView.ts +141 -116
  105. package/front_end/panels/network/NetworkPanel.ts +7 -4
  106. package/front_end/panels/network/RequestHeadersView.ts +1 -1
  107. package/front_end/panels/network/forward/BackendLinking.ts +5 -2
  108. package/front_end/panels/recorder/stepView.css +1 -0
  109. package/front_end/panels/security/SecurityPanel.ts +5 -5
  110. package/front_end/panels/security/SecurityPanelSidebar.ts +2 -11
  111. package/front_end/panels/settings/BackendLinkingSettingsTab.ts +7 -2
  112. package/front_end/panels/sources/DebuggerPlugin.ts +1 -1
  113. package/front_end/panels/sources/WatchExpressionsSidebarPane.ts +11 -16
  114. package/front_end/panels/sources/sources-meta.ts +1 -1
  115. package/front_end/panels/timeline/TimelineTreeView.ts +11 -1
  116. package/front_end/panels/timeline/overlays/OverlaysImpl.ts +0 -3
  117. package/front_end/third_party/chromium/README.chromium +1 -1
  118. package/front_end/ui/comments/CommentOverlayManager.ts +89 -42
  119. package/front_end/ui/comments/comments.ts +0 -4
  120. package/front_end/ui/legacy/Treeoutline.ts +4 -0
  121. package/front_end/ui/legacy/components/object_ui/ObjectPropertiesSection.ts +15 -8
  122. package/front_end/ui/legacy/components/perf_ui/FlameChart.ts +4 -0
  123. package/front_end/ui/visual_logging/KnownContextValues.ts +1 -0
  124. package/package.json +5 -5
@@ -4,75 +4,30 @@
4
4
 
5
5
  import * as Common from '../../core/common/common.js';
6
6
 
7
- export interface EditorAnchorSignature {
8
- /** 1-based line number for CodeMirror text editor anchors */
9
- lineNumber: number;
10
- /** File path associated with the editor */
11
- filePath?: string;
12
- }
13
-
14
- export interface DOMNodeAnchorSignature {
15
- /** Backend NodeId for DOM nodes (`data-backend-node-id`) */
16
- backendNodeId: number;
17
- /** Target ID associated with the DOM node (`data-target-id`) */
18
- targetId: string;
19
- }
20
-
21
- export interface TimelineAnchorSignature {
22
- /** Identifier of the trace to scope comments to a specific recording */
23
- traceId: string;
24
- /** Serializable key from Trace.EventsSerializer (e.g. 'r-123', 'p-1-2-3-4', 's-5') */
25
- traceEventKey: string;
26
- /** Primary event title or category name */
27
- entryName: string;
28
- /** Event start timestamp in microseconds */
29
- startTimeMicro: number;
30
- /** Chart location */
31
- chartLocation: 'main'|'network';
32
- /** Event duration in microseconds (omitted for instant markers) */
33
- durationMicro?: number;
34
- }
35
-
36
- export interface CommentAnchorSignature {
37
- /** Visual logging tree path, e.g. "Panel: elements > Pane: styles > TreeOutline > TreeItem: color" */
38
- vePath: string;
39
- /** Normalized text content of the target node */
40
- textSignature: string;
41
- /** Text content of the parent container VE node for sibling disambiguation */
42
- parentTextSignature?: string;
43
- /** 0-indexed position among siblings sharing the same visual logging path */
44
- siblingIndex?: number;
45
- /** Optional backend RequestId for Network panel elements (`data-network-request-id`) */
46
- networkRequestId?: string;
47
- /** Optional DOM node identifiers (`data-backend-node-id`, `data-target-id`) */
48
- node?: DOMNodeAnchorSignature;
49
- /** Optional editor anchor coordinates for CodeMirror text editors */
50
- editor?: EditorAnchorSignature;
51
- /** Optional timeline flamechart anchor coordinates for Performance panel trace entries */
52
- timeline?: TimelineAnchorSignature;
53
- }
54
-
55
- export interface Comment {
56
- author: 'DEVELOPER'|'AGENT';
57
- text: string;
58
- timestamp: number;
59
- }
60
-
61
- export interface ChangeRecord {
62
- id: string;
63
- description: string;
64
- timestamp: number;
65
- }
66
-
67
- export interface CommentThread {
68
- id: string;
69
- anchor: CommentAnchorSignature;
70
- comments: Comment[];
71
- status: 'ACTIVE'|'RESOLVED';
72
- transmitted?: boolean;
73
- changes?: ChangeRecord[];
74
- index: number;
75
- }
7
+ import {
8
+ type ChangeRecord,
9
+ type Comment,
10
+ type CommentAnchorSignature,
11
+ CommentThread,
12
+ type CommentThreadOptions,
13
+ type CommentThreadStatus,
14
+ type DOMNodeAnchorSignature,
15
+ type EditorAnchorSignature,
16
+ Events as CommentThreadEvents,
17
+ type TimelineAnchorSignature,
18
+ } from './CommentThread.js';
19
+
20
+ export {
21
+ type ChangeRecord,
22
+ type Comment,
23
+ type CommentAnchorSignature,
24
+ CommentThread,
25
+ type CommentThreadOptions,
26
+ type CommentThreadStatus,
27
+ type DOMNodeAnchorSignature,
28
+ type EditorAnchorSignature,
29
+ type TimelineAnchorSignature,
30
+ };
76
31
 
77
32
  export const enum Events {
78
33
  COMMENT_THREADS_CHANGED = 'CommentThreadsChanged',
@@ -90,7 +45,15 @@ export interface EventTypes {
90
45
  export class CommentManager extends Common.ObjectWrapper.ObjectWrapper<EventTypes> {
91
46
  readonly #commentThreads = new Map<string, CommentThread>();
92
47
  #commentMode = false;
93
- #nextId = 1;
48
+
49
+ constructor() {
50
+ super();
51
+ CommentThread.resetIndex();
52
+ }
53
+
54
+ #onThreadChanged(): void {
55
+ this.dispatchEventToListeners(Events.COMMENT_THREADS_CHANGED, this.getCommentThreads());
56
+ }
94
57
 
95
58
  setCommentMode(active: boolean): void {
96
59
  if (this.#commentMode === active) {
@@ -110,25 +73,20 @@ export class CommentManager extends Common.ObjectWrapper.ObjectWrapper<EventType
110
73
  author: 'DEVELOPER'|'AGENT' = 'DEVELOPER',
111
74
  changes?: ChangeRecord[],
112
75
  ): CommentThread {
113
- const index = this.#nextId++;
114
- const id = `comment-${index}`;
115
76
  const comments: Comment[] = text ? [{
116
77
  author,
117
78
  text,
118
79
  timestamp: Date.now(),
119
80
  }] :
120
81
  [];
121
- const thread: CommentThread = {
122
- id,
82
+ const thread = new CommentThread({
123
83
  anchor,
124
84
  comments,
125
- status: 'ACTIVE',
126
- transmitted: false,
127
85
  changes,
128
- index,
129
- };
86
+ });
87
+ thread.addEventListener(CommentThreadEvents.CHANGED, this.#onThreadChanged, this);
130
88
 
131
- this.#commentThreads.set(id, thread);
89
+ this.#commentThreads.set(thread.id, thread);
132
90
  this.dispatchEventToListeners(Events.COMMENT_THREADS_CHANGED, this.getCommentThreads());
133
91
  return thread;
134
92
  }
@@ -144,7 +102,7 @@ export class CommentManager extends Common.ObjectWrapper.ObjectWrapper<EventType
144
102
  takeComments(): CommentThread[] {
145
103
  const threads: CommentThread[] = [];
146
104
  for (const thread of this.#commentThreads.values()) {
147
- if (!thread.transmitted) {
105
+ if (thread.status === 'ACTIVE' && !thread.transmitted) {
148
106
  thread.transmitted = true;
149
107
  threads.push(thread);
150
108
  }
@@ -157,30 +115,27 @@ export class CommentManager extends Common.ObjectWrapper.ObjectWrapper<EventType
157
115
  if (!thread) {
158
116
  return false;
159
117
  }
160
- if (replyText && replyText.trim().length > 0) {
161
- const comment: Comment = {
162
- author: 'AGENT',
163
- text: replyText.trim(),
164
- timestamp: Date.now(),
165
- };
166
- thread.comments.push(comment);
167
- }
168
- thread.status = 'RESOLVED';
169
- this.dispatchEventToListeners(Events.COMMENT_THREADS_CHANGED, this.getCommentThreads());
118
+ thread.resolve(replyText);
170
119
  return true;
171
120
  }
172
121
 
173
122
  removeCommentThread(id: string): void {
174
- if (!this.#commentThreads.has(id)) {
123
+ const thread = this.#commentThreads.get(id);
124
+ if (!thread) {
175
125
  return;
176
126
  }
127
+ thread.removeEventListener(CommentThreadEvents.CHANGED, this.#onThreadChanged, this);
177
128
  this.#commentThreads.delete(id);
178
129
  this.dispatchEventToListeners(Events.COMMENT_THREADS_CHANGED, this.getCommentThreads());
179
130
  }
180
131
 
181
132
  clear(): void {
182
133
  this.setCommentMode(false);
134
+ for (const thread of this.#commentThreads.values()) {
135
+ thread.removeEventListener(CommentThreadEvents.CHANGED, this.#onThreadChanged, this);
136
+ }
183
137
  this.#commentThreads.clear();
138
+ CommentThread.resetIndex();
184
139
  this.dispatchEventToListeners(Events.COMMENT_THREADS_CHANGED, []);
185
140
  }
186
141
  }
@@ -0,0 +1,145 @@
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 * as Common from '../../core/common/common.js';
6
+
7
+ export interface EditorAnchorSignature {
8
+ /** 1-based line number for CodeMirror text editor anchors */
9
+ lineNumber: number;
10
+ /** File path associated with the editor */
11
+ filePath?: string;
12
+ }
13
+
14
+ export interface DOMNodeAnchorSignature {
15
+ /** Backend NodeId for DOM nodes (`data-backend-node-id`) */
16
+ backendNodeId: number;
17
+ /** Target ID associated with the DOM node (`data-target-id`) */
18
+ targetId: string;
19
+ }
20
+
21
+ export interface TimelineAnchorSignature {
22
+ /** Identifier of the trace to scope comments to a specific recording */
23
+ traceId: string;
24
+ /** Serializable key from Trace.EventsSerializer (e.g. 'r-123', 'p-1-2-3-4', 's-5') */
25
+ traceEventKey: string;
26
+ /** Primary event title or category name */
27
+ entryName: string;
28
+ /** Event start timestamp in microseconds */
29
+ startTimeMicro: number;
30
+ /** Chart location */
31
+ chartLocation: 'main'|'network';
32
+ /** Event duration in microseconds (omitted for instant markers) */
33
+ durationMicro?: number;
34
+ }
35
+
36
+ export interface CommentAnchorSignature {
37
+ /** Visual logging tree path, e.g. "Panel: elements > Pane: styles > TreeOutline > TreeItem: color" */
38
+ vePath: string;
39
+ /** Normalized text content of the target node */
40
+ textSignature: string;
41
+ /** Text content of the parent container VE node for sibling disambiguation */
42
+ parentTextSignature?: string;
43
+ /** 0-indexed position among siblings sharing the same visual logging path */
44
+ siblingIndex?: number;
45
+ /** Optional backend RequestId for Network panel elements (`data-network-request-id`) */
46
+ networkRequestId?: string;
47
+ /** Optional DOM node identifiers (`data-backend-node-id`, `data-target-id`) */
48
+ node?: DOMNodeAnchorSignature;
49
+ /** Optional editor anchor coordinates for CodeMirror text editors */
50
+ editor?: EditorAnchorSignature;
51
+ /** Optional timeline flamechart anchor coordinates for Performance panel trace entries */
52
+ timeline?: TimelineAnchorSignature;
53
+ }
54
+
55
+ export interface Comment {
56
+ author: 'DEVELOPER'|'AGENT';
57
+ text: string;
58
+ timestamp: number;
59
+ }
60
+
61
+ export interface ChangeRecord {
62
+ id: string;
63
+ description: string;
64
+ timestamp: number;
65
+ }
66
+
67
+ export type CommentThreadStatus = 'DRAFT'|'ACTIVE'|'RESOLVED';
68
+
69
+ export const enum Events {
70
+ CHANGED = 'Changed',
71
+ }
72
+
73
+ export interface EventTypes {
74
+ [Events.CHANGED]: void;
75
+ }
76
+
77
+ export interface CommentThreadOptions {
78
+ anchor: CommentAnchorSignature;
79
+ comments?: Comment[];
80
+ changes?: ChangeRecord[];
81
+ }
82
+
83
+ export class CommentThread extends Common.ObjectWrapper.ObjectWrapper<EventTypes> {
84
+ static #nextIndex = 1;
85
+
86
+ static resetIndex(): void {
87
+ CommentThread.#nextIndex = 1;
88
+ }
89
+
90
+ readonly id: string = crypto.randomUUID();
91
+ readonly anchor: CommentAnchorSignature;
92
+ #savedIndex?: number;
93
+ comments: Comment[];
94
+ status: CommentThreadStatus = 'DRAFT';
95
+ transmitted: boolean = false;
96
+ changes?: ChangeRecord[];
97
+
98
+ constructor(options: CommentThreadOptions) {
99
+ super();
100
+ this.anchor = options.anchor;
101
+ this.comments = options.comments ?? [];
102
+ this.changes = options.changes;
103
+ }
104
+
105
+ get index(): number {
106
+ return this.#savedIndex ?? CommentThread.#nextIndex;
107
+ }
108
+
109
+ save(text?: string, author: 'DEVELOPER'|'AGENT' = 'DEVELOPER'): void {
110
+ let changed = false;
111
+ if (text && text.trim().length > 0) {
112
+ this.comments.push({
113
+ author,
114
+ text: text.trim(),
115
+ timestamp: Date.now(),
116
+ });
117
+ changed = true;
118
+ }
119
+ if (this.status === 'DRAFT') {
120
+ if (this.#savedIndex === undefined) {
121
+ this.#savedIndex = CommentThread.#nextIndex++;
122
+ }
123
+ this.status = 'ACTIVE';
124
+ changed = true;
125
+ }
126
+ if (changed) {
127
+ this.dispatchEventToListeners(Events.CHANGED);
128
+ }
129
+ }
130
+
131
+ resolve(replyText?: string): void {
132
+ if (replyText && replyText.trim().length > 0) {
133
+ this.comments.push({
134
+ author: 'AGENT',
135
+ text: replyText.trim(),
136
+ timestamp: Date.now(),
137
+ });
138
+ }
139
+ if (this.#savedIndex === undefined) {
140
+ this.#savedIndex = CommentThread.#nextIndex++;
141
+ }
142
+ this.status = 'RESOLVED';
143
+ this.dispatchEventToListeners(Events.CHANGED);
144
+ }
145
+ }
@@ -4,8 +4,10 @@
4
4
 
5
5
  import * as CD4ABridge from './CD4ABridge.js';
6
6
  import * as CommentManager from './CommentManager.js';
7
+ import * as CommentThread from './CommentThread.js';
7
8
 
8
9
  export {
9
10
  CD4ABridge,
10
11
  CommentManager,
12
+ CommentThread,
11
13
  };
@@ -0,0 +1,238 @@
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 * as Platform from '../../core/platform/platform.js';
6
+ import type * as SDK from '../../core/sdk/sdk.js';
7
+ import type * as ChangeTracker from '../change_tracker/change_tracker.js';
8
+ import type * as CommentManager from '../comment_manager/comment_manager.js';
9
+
10
+ type Tracker = ChangeTracker.ChangeTracker.ChangeTracker|undefined;
11
+
12
+ /**
13
+ * Maximum number of characters of page controlled data (tag names, attribute names and values,
14
+ * text node values and HTML) that a change description embeds. The inspected page fully controls
15
+ * these strings and `Changed HTML from ... to ...` in particular can otherwise hold on to the
16
+ * entire subtree of an element.
17
+ */
18
+ export const MAX_VALUE_LENGTH = 100;
19
+
20
+ /** Shortens page controlled data so that both its start and its end stay recognizable. */
21
+ function trimValue(value: string): string {
22
+ return Platform.StringUtilities.trimMiddle(value, MAX_VALUE_LENGTH);
23
+ }
24
+
25
+ function buildAnchor(node: SDK.DOMModel.DOMNode,
26
+ selector = 'element'): CommentManager.CommentManager.CommentAnchorSignature {
27
+ return {
28
+ vePath: 'Panel: elements > Tree: elements > TreeItem',
29
+ textSignature: selector,
30
+ node: {
31
+ backendNodeId: node.backendNodeId(),
32
+ targetId: node.domModel().target().id(),
33
+ },
34
+ };
35
+ }
36
+
37
+ /**
38
+ * Parses a raw attribute string (e.g. 'class="btn"', 'data-id=123', or 'disabled')
39
+ * into its attribute name and unquoted value for single-attribute inline edits.
40
+ */
41
+ function parseAttributeText(text: string): {name: string, value: string} {
42
+ const trimmed = text.trim();
43
+ const eqIdx = trimmed.indexOf('=');
44
+ if (eqIdx === -1) {
45
+ return {name: trimmed, value: ''};
46
+ }
47
+ const name = trimmed.slice(0, eqIdx).trim();
48
+ let value = trimmed.slice(eqIdx + 1).trim();
49
+ if (value.length >= 2 &&
50
+ ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith('\'') && value.endsWith('\'')))) {
51
+ value = value.slice(1, -1);
52
+ }
53
+ return {name, value};
54
+ }
55
+
56
+ function describeAddAttribute(attributeName: string, value?: string): string {
57
+ if (value !== undefined && value !== '') {
58
+ return `Added attribute ${trimValue(attributeName)}="${trimValue(value)}"`;
59
+ }
60
+ return `Added attribute "${trimValue(attributeName)}"`;
61
+ }
62
+
63
+ function describeRemoveAttribute(attributeName: string): string {
64
+ return `Removed attribute "${trimValue(attributeName)}"`;
65
+ }
66
+
67
+ /**
68
+ * Describes a modification of an existing attribute, which covers renaming the attribute, changing
69
+ * its value, or both at once. `oldValue` is `undefined` if the previous value is unknown.
70
+ */
71
+ function describeAttributeChange(
72
+ oldAttributeName: string,
73
+ oldValue: string|undefined,
74
+ newAttributeName: string,
75
+ newValue: string,
76
+ ): string {
77
+ if (oldAttributeName !== newAttributeName) {
78
+ if (oldValue !== undefined && oldValue !== newValue) {
79
+ return `Renamed attribute "${trimValue(oldAttributeName)}"="${trimValue(oldValue)}" to "${
80
+ trimValue(newAttributeName)}"="${trimValue(newValue)}"`;
81
+ }
82
+ return `Renamed attribute "${trimValue(oldAttributeName)}" to "${trimValue(newAttributeName)}"`;
83
+ }
84
+ if (oldValue !== undefined) {
85
+ return `Changed attribute "${trimValue(newAttributeName)}" from "${trimValue(oldValue)}" to "${
86
+ trimValue(newValue)}"`;
87
+ }
88
+ return `Changed attribute "${trimValue(newAttributeName)}" to "${trimValue(newValue)}"`;
89
+ }
90
+
91
+ export interface AttributeEdit {
92
+ /** Name of the edited attribute, or an empty string when a new attribute is added. */
93
+ attributeName: string;
94
+ /** Raw attribute text before the edit, if there was any. */
95
+ oldText: string|null;
96
+ /** Raw attribute text the user committed. Empty when the attribute is removed. */
97
+ newText: string;
98
+ }
99
+
100
+ function describeAttributeEdit({attributeName, oldText, newText}: AttributeEdit): string {
101
+ const parsedNew = parseAttributeText(newText);
102
+ const parsedOld = oldText !== null ? parseAttributeText(oldText) : undefined;
103
+
104
+ if (!newText.trim()) {
105
+ return describeRemoveAttribute(attributeName.trim());
106
+ }
107
+ if (!attributeName.trim()) {
108
+ return describeAddAttribute(parsedNew.name, parsedNew.value);
109
+ }
110
+ return describeAttributeChange(
111
+ attributeName.trim(),
112
+ parsedOld?.value,
113
+ parsedNew.name || attributeName.trim(),
114
+ parsedNew.value,
115
+ );
116
+ }
117
+
118
+ /**
119
+ * These strings are for the consumption by AI agents, they don't need to be translated.
120
+ */
121
+ function describeTagNameEdit(oldTagName: string, newTagName: string): string {
122
+ return `Renamed tag from <${trimValue(oldTagName)}> to <${trimValue(newTagName)}>`;
123
+ }
124
+
125
+ function describeTextNodeEdit(oldText: string, newText: string): string {
126
+ return `Changed text from "${trimValue(oldText)}" to "${trimValue(newText)}"`;
127
+ }
128
+
129
+ function describeNodeRemoval(tagName: string): string {
130
+ return `Removed node <${trimValue(tagName)}>`;
131
+ }
132
+
133
+ function describeHTMLEdit(oldValue?: string, newValue?: string): string {
134
+ if (oldValue !== undefined && newValue !== undefined) {
135
+ return `Changed HTML from "${trimValue(oldValue)}" to "${trimValue(newValue)}"`;
136
+ }
137
+ if (newValue !== undefined) {
138
+ return `Changed HTML to "${trimValue(newValue)}"`;
139
+ }
140
+ return 'Edited HTML';
141
+ }
142
+
143
+ function describeVisibilityToggle(tagName: string, hidden: boolean): string {
144
+ return hidden ? `Hid element <${trimValue(tagName)}>` : `Unhid element <${trimValue(tagName)}>`;
145
+ }
146
+
147
+ function describeNodeDuplication(tagName: string): string {
148
+ return `Duplicated node <${trimValue(tagName)}>`;
149
+ }
150
+
151
+ function describeNodeMove(tagName: string, directionUp: boolean): string {
152
+ return `Moved node <${trimValue(tagName)}> ${directionUp ? 'up' : 'down'}`;
153
+ }
154
+
155
+ function describeNodeDrop(tagName: string): string {
156
+ return `Moved node <${trimValue(tagName)}> via drag and drop`;
157
+ }
158
+
159
+ function describeNodePaste(tagName: string, isCut: boolean): string {
160
+ return isCut ? `Pasted (moved) node <${trimValue(tagName)}>` : `Pasted node <${trimValue(tagName)}>`;
161
+ }
162
+
163
+ export function trackAttributeEdit(tracker: Tracker, node: SDK.DOMModel.DOMNode, selector: string|undefined,
164
+ edit: AttributeEdit): void {
165
+ if (!tracker?.isTracking) {
166
+ return;
167
+ }
168
+ tracker.trackChange(describeAttributeEdit(edit), buildAnchor(node, selector));
169
+ }
170
+
171
+ export function trackTagNameEdit(tracker: Tracker, node: SDK.DOMModel.DOMNode, selector: string|undefined,
172
+ oldTagName: string, newTagName: string): void {
173
+ if (!tracker?.isTracking) {
174
+ return;
175
+ }
176
+ tracker.trackChange(describeTagNameEdit(oldTagName, newTagName), buildAnchor(node, selector));
177
+ }
178
+
179
+ export function trackTextNodeEdit(tracker: Tracker, node: SDK.DOMModel.DOMNode, selector: string|undefined,
180
+ oldText: string, newText: string): void {
181
+ if (!tracker?.isTracking) {
182
+ return;
183
+ }
184
+ tracker.trackChange(describeTextNodeEdit(oldText, newText), buildAnchor(node, selector));
185
+ }
186
+
187
+ export function trackNodeRemoval(tracker: Tracker, node: SDK.DOMModel.DOMNode, selector: string|undefined): void {
188
+ if (!tracker?.isTracking) {
189
+ return;
190
+ }
191
+ tracker.trackChange(describeNodeRemoval(node.nodeName().toLowerCase()), buildAnchor(node, selector));
192
+ }
193
+
194
+ export function trackHTMLEdit(tracker: Tracker, node: SDK.DOMModel.DOMNode, selector: string|undefined,
195
+ oldValue: string, newValue: string): void {
196
+ if (!tracker?.isTracking) {
197
+ return;
198
+ }
199
+ tracker.trackChange(describeHTMLEdit(oldValue, newValue), buildAnchor(node, selector));
200
+ }
201
+
202
+ export function trackVisibilityToggle(tracker: Tracker, node: SDK.DOMModel.DOMNode, selector: string|undefined,
203
+ hidden: boolean): void {
204
+ if (!tracker?.isTracking) {
205
+ return;
206
+ }
207
+ tracker.trackChange(describeVisibilityToggle(node.nodeName().toLowerCase(), hidden), buildAnchor(node, selector));
208
+ }
209
+
210
+ export function trackNodeDuplication(tracker: Tracker, node: SDK.DOMModel.DOMNode, selector: string|undefined): void {
211
+ if (!tracker?.isTracking) {
212
+ return;
213
+ }
214
+ tracker.trackChange(describeNodeDuplication(node.nodeName().toLowerCase()), buildAnchor(node, selector));
215
+ }
216
+
217
+ export function trackNodeMove(tracker: Tracker, node: SDK.DOMModel.DOMNode, selector: string|undefined,
218
+ directionUp: boolean): void {
219
+ if (!tracker?.isTracking) {
220
+ return;
221
+ }
222
+ tracker.trackChange(describeNodeMove(node.nodeName().toLowerCase(), directionUp), buildAnchor(node, selector));
223
+ }
224
+
225
+ export function trackNodeDrop(tracker: Tracker, node: SDK.DOMModel.DOMNode, selector: string|undefined): void {
226
+ if (!tracker?.isTracking) {
227
+ return;
228
+ }
229
+ tracker.trackChange(describeNodeDrop(node.nodeName().toLowerCase()), buildAnchor(node, selector));
230
+ }
231
+
232
+ export function trackNodePaste(tracker: Tracker, node: SDK.DOMModel.DOMNode, selector: string|undefined,
233
+ isCut: boolean): void {
234
+ if (!tracker?.isTracking) {
235
+ return;
236
+ }
237
+ tracker.trackChange(describeNodePaste(node.nodeName().toLowerCase(), isCut), buildAnchor(node, selector));
238
+ }
@@ -2,8 +2,10 @@
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 DOMChanges from './DOMChanges.js';
5
6
  import * as ElementUpdateRecord from './ElementUpdateRecord.js';
6
7
 
7
8
  export {
9
+ DOMChanges,
8
10
  ElementUpdateRecord,
9
11
  };
@@ -107,6 +107,8 @@ export class DeviceModeModel extends Common.ObjectWrapper.ObjectWrapper<EventTyp
107
107
  #appliedUserAgentType: UA;
108
108
  readonly #scaleSetting: Common.Settings.Setting<number>;
109
109
  #scale: number;
110
+ readonly #autoAdjustScaleSetting: Common.Settings.Setting<boolean>;
111
+ readonly #deviceScaleMapSetting: Common.Settings.Setting<Record<string, {scale: number, autoAdjust: boolean}>>;
110
112
  #widthSetting: Common.Settings.Setting<number>;
111
113
  #heightSetting: Common.Settings.Setting<number>;
112
114
  #uaSetting: Common.Settings.Setting<UA>;
@@ -153,6 +155,9 @@ export class DeviceModeModel extends Common.ObjectWrapper.ObjectWrapper<EventTyp
153
155
  this.#scaleSetting.addChangeListener(this.scaleSettingChanged, this);
154
156
  this.#scale = 1;
155
157
 
158
+ this.#autoAdjustScaleSetting = this.#settings.createSetting('emulation.auto-adjust-scale', true);
159
+ this.#deviceScaleMapSetting = this.#settings.createSetting('emulation.device-scale-map', {});
160
+
156
161
  this.#widthSetting = this.#settings.createSetting('emulation.device-width', 400);
157
162
  if (this.#widthSetting.get() < MinDeviceSize) {
158
163
  this.#widthSetting.set(MinDeviceSize);
@@ -327,12 +332,44 @@ export class DeviceModeModel extends Common.ObjectWrapper.ObjectWrapper<EventTyp
327
332
 
328
333
  emulate(type: Type, device: EmulatedDevice|null, mode: Mode|null, scale?: number): void {
329
334
  const resetPageScaleFactor = this.#type !== type || this.#device !== device || this.#mode !== mode;
335
+ const deviceChanged = this.#type !== type || this.#device !== device;
336
+
337
+ if (deviceChanged) {
338
+ if (this.#type === Type.Device && this.#device) {
339
+ const map = this.#deviceScaleMapSetting.get();
340
+ map[this.#device.title] = {
341
+ scale: this.#scaleSetting.get(),
342
+ autoAdjust: this.#autoAdjustScaleSetting.get(),
343
+ };
344
+ this.#deviceScaleMapSetting.set(map);
345
+ } else if (this.#type === Type.Responsive) {
346
+ const map = this.#deviceScaleMapSetting.get();
347
+ map['Responsive'] = {
348
+ scale: this.#scaleSetting.get(),
349
+ autoAdjust: this.#autoAdjustScaleSetting.get(),
350
+ };
351
+ this.#deviceScaleMapSetting.set(map);
352
+ }
353
+ }
354
+
330
355
  this.#type = type;
331
356
 
332
357
  if (type === Type.Device && device && mode) {
333
358
  console.assert(Boolean(device) && Boolean(mode), 'Must pass device and mode for device emulation');
334
359
  this.#mode = mode;
335
360
  this.#device = device;
361
+
362
+ if (deviceChanged) {
363
+ const map = this.#deviceScaleMapSetting.get();
364
+ const savedDevice = map[device.title];
365
+ if (savedDevice) {
366
+ this.#autoAdjustScaleSetting.set(savedDevice.autoAdjust);
367
+ scale = savedDevice.autoAdjust ? undefined : savedDevice.scale;
368
+ } else {
369
+ this.#autoAdjustScaleSetting.set(scale === undefined);
370
+ }
371
+ }
372
+
336
373
  if (scale !== undefined) {
337
374
  this.#autoFitScaleOnInitialize = false;
338
375
  this.#scaleSetting.set(scale);
@@ -346,6 +383,22 @@ export class DeviceModeModel extends Common.ObjectWrapper.ObjectWrapper<EventTyp
346
383
  this.#device = null;
347
384
  this.#mode = null;
348
385
  this.#autoFitScaleOnInitialize = false;
386
+
387
+ if (deviceChanged && type === Type.Responsive) {
388
+ const map = this.#deviceScaleMapSetting.get();
389
+ const savedDevice = map['Responsive'];
390
+ if (savedDevice) {
391
+ this.#autoAdjustScaleSetting.set(savedDevice.autoAdjust);
392
+ if (!savedDevice.autoAdjust) {
393
+ this.#scaleSetting.set(savedDevice.scale);
394
+ } else {
395
+ this.#scaleSetting.set(this.calculateFitScale(this.#widthSetting.get(), this.#heightSetting.get()));
396
+ }
397
+ } else {
398
+ this.#autoAdjustScaleSetting.set(true);
399
+ this.#scaleSetting.set(1);
400
+ }
401
+ }
349
402
  }
350
403
 
351
404
  if (type !== Type.None) {