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.
- package/.agents/skills/version-control/SKILL.md +21 -9
- package/.stylelintignore +2 -0
- package/front_end/core/common/VersionController.ts +5 -1
- package/front_end/core/platform/HostRuntime.ts +12 -0
- package/front_end/core/platform/api/HostRuntime.ts +13 -0
- package/front_end/core/platform/browser/HostRuntime.ts +34 -0
- package/front_end/core/platform/node/HostRuntime.ts +40 -0
- package/front_end/core/sdk/AnimationModel.ts +21 -99
- package/front_end/core/sdk/CSSModel.ts +4 -2
- package/front_end/core/sdk/CSSPropertyParser.ts +4 -4
- package/front_end/core/sdk/CSSPropertyParserMatchers.ts +4 -44
- package/front_end/core/sdk/Connections.ts +6 -1
- package/front_end/core/sdk/ConsoleModel.ts +1 -12
- package/front_end/core/sdk/DOMModel.ts +3 -51
- package/front_end/core/sdk/DebuggerModel.ts +9 -8
- package/front_end/core/sdk/NetworkRequest.ts +1 -1
- package/front_end/core/sdk/PageFunctions.ts +187 -0
- package/front_end/core/sdk/Resource.ts +1 -1
- package/front_end/core/sdk/RuntimeModel.ts +1 -20
- package/front_end/core/sdk/Script.ts +21 -0
- package/front_end/core/sdk/SourceMap.ts +15 -2
- package/front_end/core/sdk/SourceMapCache.ts +13 -10
- package/front_end/core/sdk/SourceMapManager.ts +11 -9
- package/front_end/core/sdk/SourceMapScopeChainEntry.ts +59 -17
- package/front_end/core/sdk/SourceMapScopesInfo.ts +8 -2
- package/front_end/entrypoints/heap_snapshot_worker/ContextAnalyzer.ts +635 -0
- package/front_end/entrypoints/heap_snapshot_worker/HeapSnapshot.ts +26 -12
- package/front_end/entrypoints/heap_snapshot_worker/HeapSnapshotLoader.ts +16 -2
- package/front_end/entrypoints/heap_snapshot_worker/HeapSnapshotWorkerDispatcher.ts +4 -4
- package/front_end/entrypoints/heap_snapshot_worker/heap_snapshot_worker.ts +2 -0
- package/front_end/entrypoints/shell/shell.ts +1 -1
- package/front_end/generated/InspectorBackendCommands.ts +1 -1
- package/front_end/generated/protocol.ts +8 -2
- package/front_end/models/ai_assistance/DOMHelpers.ts +95 -0
- package/front_end/models/ai_assistance/EvaluateAction.ts +5 -42
- package/front_end/models/ai_assistance/ExtensionScope.ts +2 -31
- package/front_end/models/ai_assistance/agents/AiAgent.ts +2 -1
- package/front_end/models/ai_assistance/agents/ContextSelectionAgent.ts +27 -2
- package/front_end/models/ai_assistance/ai_assistance.ts +6 -4
- package/front_end/models/ai_assistance/contexts/DOMNodeContext.ts +10 -7
- package/front_end/models/ai_assistance/contexts/FileContext.ts +2 -10
- package/front_end/models/ai_assistance/skills/performance.md +4 -3
- package/front_end/models/ai_assistance/tools/GetSourceContent.ts +3 -2
- package/front_end/models/ai_assistance/tools/{GetFunctionCode.ts → GetTraceFunctionCode.ts} +26 -13
- package/front_end/models/ai_assistance/tools/{GetResourceContent.ts → GetTraceResourceContent.ts} +31 -11
- package/front_end/models/ai_assistance/tools/ListSources.ts +21 -14
- package/front_end/models/ai_assistance/tools/Tool.ts +2 -2
- package/front_end/models/ai_assistance/tools/ToolRegistry.ts +6 -6
- package/front_end/models/bindings/CompilerScriptMapping.ts +3 -10
- package/front_end/models/bindings/DebuggerLanguagePlugins.ts +48 -17
- package/front_end/models/bindings/ResourceScriptMapping.ts +2 -2
- package/front_end/models/bindings/StylesSourceMapping.ts +1 -1
- package/front_end/models/comment_manager/CD4ABridge.ts +55 -12
- package/front_end/models/comment_manager/CommentManager.ts +46 -91
- package/front_end/models/comment_manager/CommentThread.ts +145 -0
- package/front_end/models/comment_manager/comment_manager.ts +2 -0
- package/front_end/models/elements/DOMChanges.ts +238 -0
- package/front_end/models/elements/elements.ts +2 -0
- package/front_end/models/emulation/DeviceModeModel.ts +53 -0
- package/front_end/models/extensions/ExtensionAPI.ts +8 -6
- package/front_end/models/extensions/ExtensionEndpoint.ts +11 -8
- package/front_end/models/extensions/HostUrlPattern.ts +4 -3
- package/front_end/models/extensions/LanguageExtensionEndpoint.ts +3 -2
- package/front_end/models/extensions/RecorderExtensionEndpoint.ts +2 -1
- package/front_end/models/formatter/FormatterWorkerPool.ts +4 -6
- package/front_end/models/heap_snapshot/HeapSnapshotModel.ts +55 -0
- package/front_end/models/heap_snapshot/HeapSnapshotProxy.ts +18 -14
- package/front_end/models/issues_manager/IssuesManager.ts +5 -0
- package/front_end/models/issues_manager/WebInstallIssue.ts +63 -0
- package/front_end/models/issues_manager/descriptions/webInstallManifestMissingId.md +3 -0
- package/front_end/models/issues_manager/descriptions/webInstallManifestMissingNameOrShortName.md +3 -0
- package/front_end/models/issues_manager/descriptions/webInstallManifestParsingOrNetworkError.md +3 -0
- package/front_end/models/issues_manager/descriptions/webInstallNoManifest.md +3 -0
- package/front_end/models/issues_manager/descriptions/webInstallStartUrlInvalid.md +3 -0
- package/front_end/models/issues_manager/issues_manager.ts +2 -0
- package/front_end/models/workspace/UISourceCode.ts +20 -0
- package/front_end/panels/ai_assistance/AiAssistancePanel.ts +7 -7
- package/front_end/panels/ai_assistance/components/AIv2MarkdownRenderer.ts +4 -10
- package/front_end/panels/ai_assistance/components/MarkdownRendererWithCodeBlock.ts +25 -5
- package/front_end/panels/browser_debugger/ObjectEventListenersSidebarPane.ts +2 -3
- package/front_end/panels/common/CommentThreadWidget.ts +221 -0
- package/front_end/{ui/comments → panels/common}/CommentsOverlayWidget.ts +117 -28
- package/front_end/{ui/comments → panels/common}/CommentsStatusBarPill.ts +5 -5
- package/front_end/panels/common/ExtensionServer.ts +2 -1
- package/front_end/panels/common/commentThreadWidget.css +173 -0
- package/front_end/{ui/comments → panels/common}/commentsOverlay.css +8 -1
- package/front_end/{ui/comments/comments-meta.ts → panels/common/common-meta.ts} +11 -11
- package/front_end/panels/common/common.ts +3 -0
- package/front_end/panels/elements/AdoptedStyleSheetTreeElement.ts +3 -3
- package/front_end/panels/elements/ElementsPanel.ts +5 -1
- package/front_end/panels/elements/ElementsTreeElement.ts +102 -21
- package/front_end/panels/elements/ElementsTreeOutline.ts +326 -91
- package/front_end/panels/elements/EventListenersWidget.ts +0 -1
- package/front_end/panels/elements/PropertiesWidget.ts +11 -10
- package/front_end/panels/elements/PropertyRenderer.ts +29 -25
- package/front_end/panels/elements/StylePropertyTreeElement.ts +6 -6
- package/front_end/panels/elements/elementsTreeOutline.css +7 -1
- package/front_end/panels/emulation/DeviceModeToolbar.ts +2 -3
- package/front_end/panels/event_listeners/EventListenersView.ts +235 -302
- package/front_end/panels/linear_memory_inspector/LinearMemoryInspectorController.ts +6 -7
- package/front_end/panels/linear_memory_inspector/components/linearMemoryInspector.css +5 -1
- package/front_end/panels/linear_memory_inspector/linear_memory_inspector-meta.ts +1 -1
- package/front_end/panels/mobile_throttling/ThrottlingSettingsTab.ts +10 -2
- package/front_end/panels/network/NetworkConfigView.ts +141 -116
- package/front_end/panels/network/NetworkPanel.ts +7 -4
- package/front_end/panels/network/RequestHeadersView.ts +1 -1
- package/front_end/panels/network/forward/BackendLinking.ts +5 -2
- package/front_end/panels/recorder/stepView.css +1 -0
- package/front_end/panels/security/SecurityPanel.ts +5 -5
- package/front_end/panels/security/SecurityPanelSidebar.ts +2 -11
- package/front_end/panels/settings/BackendLinkingSettingsTab.ts +7 -2
- package/front_end/panels/sources/DebuggerPlugin.ts +1 -1
- package/front_end/panels/sources/WatchExpressionsSidebarPane.ts +11 -16
- package/front_end/panels/sources/sources-meta.ts +1 -1
- package/front_end/panels/timeline/TimelineTreeView.ts +11 -1
- package/front_end/panels/timeline/overlays/OverlaysImpl.ts +0 -3
- package/front_end/third_party/chromium/README.chromium +1 -1
- package/front_end/ui/comments/CommentOverlayManager.ts +89 -42
- package/front_end/ui/comments/comments.ts +0 -4
- package/front_end/ui/legacy/Treeoutline.ts +4 -0
- package/front_end/ui/legacy/components/object_ui/ObjectPropertiesSection.ts +15 -8
- package/front_end/ui/legacy/components/perf_ui/FlameChart.ts +4 -0
- package/front_end/ui/visual_logging/KnownContextValues.ts +1 -0
- package/package.json +5 -5
|
@@ -4,75 +4,30 @@
|
|
|
4
4
|
|
|
5
5
|
import * as Common from '../../core/common/common.js';
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
-
|
|
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
|
|
122
|
-
id,
|
|
82
|
+
const thread = new CommentThread({
|
|
123
83
|
anchor,
|
|
124
84
|
comments,
|
|
125
|
-
status: 'ACTIVE',
|
|
126
|
-
transmitted: false,
|
|
127
85
|
changes,
|
|
128
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
}
|
|
@@ -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) {
|