chrome-devtools-frontend 1.0.1675430 → 1.0.1676824
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/WATCHLISTS +0 -4
- package/front_end/core/common/Settings.ts +0 -66
- package/front_end/core/root/Runtime.ts +0 -5
- package/front_end/core/sdk/CSSMetadata.ts +5 -9
- package/front_end/core/sdk/CSSPropertyParserMatchers.ts +8 -28
- package/front_end/core/sdk/DebuggerModel.ts +50 -30
- package/front_end/core/sdk/ResourceTreeModel.ts +20 -1
- package/front_end/core/sdk/Script.ts +9 -53
- package/front_end/entrypoints/heap_snapshot_worker/HeapSnapshot.ts +213 -23
- package/front_end/entrypoints/main/MainImpl.ts +31 -0
- package/front_end/generated/InspectorBackendCommands.ts +3 -1
- package/front_end/generated/SupportedCSSProperties.ts +21 -4
- package/front_end/generated/protocol-mapping.d.ts +14 -0
- package/front_end/generated/protocol-proxy-api.d.ts +10 -0
- package/front_end/generated/protocol.ts +35 -0
- package/front_end/models/ai_assistance/AiAgent2.ts +11 -0
- package/front_end/models/ai_assistance/AiConversation.ts +22 -5
- package/front_end/models/ai_assistance/README.md +52 -26
- package/front_end/models/ai_assistance/agents/AiAgent.ts +54 -8
- package/front_end/models/ai_assistance/agents/ContextSelectionAgent.ts +1 -1
- package/front_end/models/ai_assistance/agents/PerformanceAgent.ts +7 -194
- package/front_end/models/ai_assistance/agents/StorageAgent.ts +8 -87
- package/front_end/models/ai_assistance/agents/StylingAgent.snapshot.txt +2 -1
- package/front_end/models/ai_assistance/ai_assistance.ts +16 -0
- package/front_end/models/ai_assistance/contexts/PerformanceTraceContext.ts +124 -0
- package/front_end/models/ai_assistance/contexts/StorageContext.ts +111 -0
- package/front_end/models/ai_assistance/data_formatters/PerformanceTraceFormatter.ts +69 -1
- package/front_end/models/ai_assistance/skills/README.md +63 -1
- package/front_end/models/ai_assistance/skills/Skill.ts +1 -1
- package/front_end/models/ai_assistance/skills/SkillRegistry.ts +4 -0
- package/front_end/models/ai_assistance/skills/performance.md +8 -0
- package/front_end/models/ai_assistance/skills/sources.md +16 -0
- package/front_end/models/ai_assistance/skills/storage.md +31 -0
- package/front_end/models/ai_assistance/tools/GetSourceContent.ts +101 -0
- package/front_end/models/ai_assistance/tools/GetTraceEventByKey.ts +83 -0
- package/front_end/models/ai_assistance/tools/GetTraceMainThreadSummary.ts +106 -0
- package/front_end/models/ai_assistance/tools/GetTraceNetworkSummary.ts +104 -0
- package/front_end/models/ai_assistance/tools/ListPageOrigins.ts +81 -0
- package/front_end/models/ai_assistance/tools/ListSources.ts +116 -0
- package/front_end/models/ai_assistance/tools/SelectTraceEventByKey.ts +90 -0
- package/front_end/models/ai_assistance/tools/Tool.ts +22 -1
- package/front_end/models/ai_assistance/tools/ToolRegistry.ts +14 -0
- package/front_end/models/bindings/CSSWorkspaceBinding.ts +4 -0
- package/front_end/models/bindings/CompilerScriptMapping.ts +4 -0
- package/front_end/models/bindings/DebuggerWorkspaceBinding.ts +8 -0
- package/front_end/models/bindings/ResourceScriptMapping.ts +3 -201
- package/front_end/models/bindings/SASSSourceMapping.ts +6 -0
- package/front_end/models/breakpoints/BreakpointManager.ts +2 -49
- package/front_end/models/heap_snapshot/HeapSnapshotModel.ts +27 -0
- package/front_end/models/heap_snapshot/HeapSnapshotProxy.ts +10 -2
- package/front_end/models/issues_manager/descriptions/connectionAllowlistEmbeddingRequirementNotSatisfied.md +26 -0
- package/front_end/models/issues_manager/descriptions/connectionAllowlistIFrameAttributeLoosensEmbeddingRequirement.md +19 -0
- package/front_end/models/issues_manager/descriptions/connectionAllowlistInvalidAllowConnectionAllowlistFrom.md +21 -0
- package/front_end/models/javascript_metadata/NativeFunctions.js +1 -1
- package/front_end/models/persistence/IsolatedFileSystem.ts +7 -8
- package/front_end/models/persistence/IsolatedFileSystemManager.ts +2 -2
- package/front_end/models/persistence/PlatformFileSystem.ts +1 -1
- package/front_end/models/persistence/persistence-meta.ts +9 -9
- package/front_end/models/trace/types/TraceEvents.ts +36 -0
- package/front_end/panels/accessibility/ARIAAttributesView.ts +3 -3
- package/front_end/panels/accessibility/AccessibilityNodeView.ts +36 -36
- package/front_end/panels/accessibility/AccessibilityStrings.ts +168 -168
- package/front_end/panels/accessibility/SourceOrderView.ts +7 -7
- package/front_end/panels/accessibility/accessibility-meta.ts +2 -2
- package/front_end/panels/ai_assistance/AiAssistancePanel.ts +5 -5
- package/front_end/panels/ai_assistance/components/AIv2MarkdownRenderer.ts +2 -3
- package/front_end/panels/ai_assistance/components/ChatInput.ts +2 -2
- package/front_end/panels/ai_assistance/components/ChatMessage.ts +27 -2
- package/front_end/panels/application/IndexedDBViews.ts +65 -63
- package/front_end/panels/application/WebMCPView.ts +16 -7
- package/front_end/panels/common/AiCodeCompletionSummaryToolbar.ts +5 -5
- package/front_end/panels/common/BadgeNotification.ts +8 -8
- package/front_end/panels/common/ExtensionServer.ts +21 -1
- package/front_end/panels/common/GdpSignUpDialog.ts +16 -16
- package/front_end/panels/common/GeminiRebrandPromoDialog.ts +5 -6
- package/front_end/panels/common/PersistenceUtils.ts +2 -2
- package/front_end/panels/common/common.ts +0 -97
- package/front_end/panels/console/ConsoleInsightTeaser.ts +2 -2
- package/front_end/panels/console/ConsolePinPane.ts +2 -2
- package/front_end/panels/console/ConsoleView.ts +20 -19
- package/front_end/panels/elements/LayoutPane.ts +2 -8
- package/front_end/panels/event_listeners/EventListenersView.ts +8 -4
- package/front_end/panels/issues/AffectedTrackingSitesView.ts +25 -14
- package/front_end/panels/media/PlayerMessagesView.ts +149 -68
- package/front_end/panels/network/NetworkPanel.ts +1 -1
- package/front_end/panels/network/RequestPayloadView.ts +1 -0
- package/front_end/panels/profiler/HeapSnapshotView.ts +0 -2
- package/front_end/panels/profiler/heapProfiler.css +0 -9
- package/front_end/panels/recorder/RecorderPanel.ts +2 -3
- package/front_end/panels/screencast/ScreencastView.ts +148 -138
- package/front_end/panels/sources/AiCodeCompletionPlugin.ts +2 -2
- package/front_end/panels/sources/DebuggerPlugin.ts +9 -58
- package/front_end/panels/sources/ScopeChainSidebarPane.ts +1 -1
- package/front_end/panels/sources/UISourceCodeFrame.ts +3 -8
- package/front_end/panels/sources/WatchExpressionsSidebarPane.ts +4 -2
- package/front_end/panels/timeline/TimelineController.ts +36 -64
- package/front_end/panels/timeline/TimelinePanel.ts +11 -8
- package/front_end/panels/timeline/TimelineUIUtils.ts +5 -2
- package/front_end/panels/timeline/overlays/components/EntryLabelOverlay.ts +2 -2
- package/front_end/panels/timeline/timelinePanel.css +0 -10
- package/front_end/panels/utils/utils.ts +7 -7
- package/front_end/services/tracing/PerformanceTracing.ts +5 -17
- package/front_end/third_party/chromium/README.chromium +1 -1
- package/front_end/third_party/codemirror.next/chunk/codemirror.js +1 -1
- package/front_end/third_party/codemirror.next/chunk/codemirror.js.map +1 -1
- package/front_end/third_party/codemirror.next/package.json +2 -2
- package/front_end/third_party/codemirror.next/rebuild.sh +4 -1
- package/front_end/ui/comments/CommentAnchorResolver.ts +436 -10
- package/front_end/ui/comments/CommentManager.ts +640 -0
- package/front_end/ui/comments/CommentsOverlayWidget.ts +121 -0
- package/front_end/ui/comments/comments.ts +4 -0
- package/front_end/ui/comments/commentsOverlay.css +49 -0
- package/front_end/{panels/common → ui/components/dialogs}/FreDialog.ts +6 -6
- package/front_end/ui/components/dialogs/TypeToAllowDialog.ts +96 -0
- package/front_end/ui/components/dialogs/dialogs.ts +4 -0
- package/front_end/ui/components/settings/SettingCheckbox.ts +3 -3
- package/front_end/{panels/common → ui/components/text_editor}/AiCodeCompletionDisclaimer.ts +13 -13
- package/front_end/ui/components/text_editor/AiCodeCompletionProvider.ts +7 -6
- package/front_end/{panels/common → ui/components/text_editor}/AiCodeCompletionTeaser.ts +43 -46
- package/front_end/ui/components/text_editor/AiCodeGenerationProvider.ts +22 -26
- package/front_end/{panels/common → ui/components/text_editor}/AiCodeGenerationTeaser.ts +10 -10
- package/front_end/{panels/common → ui/components/text_editor}/AiCodeGenerationUpgradeDialog.ts +5 -5
- package/front_end/ui/components/text_editor/text_editor.ts +4 -0
- package/front_end/ui/legacy/Toolbar.ts +1 -1
- package/front_end/ui/legacy/components/object_ui/CustomPreviewComponent.ts +11 -3
- package/front_end/ui/legacy/components/object_ui/ObjectPropertiesSection.ts +270 -162
- package/front_end/ui/legacy/components/object_ui/objectPropertiesSection.css +5 -0
- package/front_end/ui/legacy/components/quick_open/CommandMenu.ts +11 -16
- package/front_end/ui/legacy/components/settings_ui/SettingsUI.ts +5 -12
- package/front_end/ui/legacy/components/source_frame/JSONView.ts +8 -1
- package/front_end/ui/legacy/components/source_frame/SourceFrame.ts +2 -2
- package/front_end/ui/settings/SettingUIRegistration.ts +55 -6
- package/front_end/ui/visual_logging/KnownContextValues.ts +1 -0
- package/mcp/mcp.ts +6 -1
- package/package.json +1 -1
- package/front_end/Images/toolbarResizerVertical.png +0 -0
- package/front_end/emulated_devices/MotoG4-landscape.svg +0 -155
- package/front_end/emulated_devices/MotoG4-portrait.svg +0 -155
- package/front_end/emulated_devices/Nexus5X-landscape.svg +0 -19
- package/front_end/emulated_devices/Nexus5X-portrait.svg +0 -19
- package/front_end/emulated_devices/Nexus6P-landscape.svg +0 -50
- package/front_end/emulated_devices/Nexus6P-portrait.svg +0 -54
- package/front_end/emulated_devices/google-nest-hub-horizontal.png +0 -0
- package/front_end/emulated_devices/google-nest-hub-max-horizontal.png +0 -0
- package/front_end/emulated_devices/google-nexus-5-horizontal-default-1x.png +0 -0
- package/front_end/emulated_devices/google-nexus-5-horizontal-default-2x.png +0 -0
- package/front_end/emulated_devices/google-nexus-5-horizontal-keyboard-1x.png +0 -0
- package/front_end/emulated_devices/google-nexus-5-horizontal-keyboard-2x.png +0 -0
- package/front_end/emulated_devices/google-nexus-5-horizontal-navigation-1x.png +0 -0
- package/front_end/emulated_devices/google-nexus-5-horizontal-navigation-2x.png +0 -0
- package/front_end/emulated_devices/google-nexus-5-vertical-default-1x.png +0 -0
- package/front_end/emulated_devices/google-nexus-5-vertical-default-2x.png +0 -0
- package/front_end/emulated_devices/google-nexus-5-vertical-keyboard-1x.png +0 -0
- package/front_end/emulated_devices/google-nexus-5-vertical-keyboard-2x.png +0 -0
- package/front_end/emulated_devices/google-nexus-5-vertical-navigation-1x.png +0 -0
- package/front_end/emulated_devices/google-nexus-5-vertical-navigation-2x.png +0 -0
- package/front_end/emulated_devices/google-nexus-5x-horizontal-default-1x.png +0 -0
- package/front_end/emulated_devices/google-nexus-5x-horizontal-default-2x.png +0 -0
- package/front_end/emulated_devices/google-nexus-5x-horizontal-keyboard-1x.png +0 -0
- package/front_end/emulated_devices/google-nexus-5x-horizontal-keyboard-2x.png +0 -0
- package/front_end/emulated_devices/google-nexus-5x-horizontal-navigation-1x.png +0 -0
- package/front_end/emulated_devices/google-nexus-5x-horizontal-navigation-2x.png +0 -0
- package/front_end/emulated_devices/google-nexus-5x-vertical-default-1x.png +0 -0
- package/front_end/emulated_devices/google-nexus-5x-vertical-default-2x.png +0 -0
- package/front_end/emulated_devices/google-nexus-5x-vertical-keyboard-1x.png +0 -0
- package/front_end/emulated_devices/google-nexus-5x-vertical-keyboard-2x.png +0 -0
- package/front_end/emulated_devices/google-nexus-5x-vertical-navigation-1x.png +0 -0
- package/front_end/emulated_devices/google-nexus-5x-vertical-navigation-2x.png +0 -0
- package/front_end/emulated_devices/iPad-landscape.svg +0 -73
- package/front_end/emulated_devices/iPad-portrait.svg +0 -76
- package/front_end/emulated_devices/iPhone5-landscape.svg +0 -99
- package/front_end/emulated_devices/iPhone5-portrait.svg +0 -83
- package/front_end/emulated_devices/iPhone6-landscape.svg +0 -111
- package/front_end/emulated_devices/iPhone6-portrait.svg +0 -116
- package/front_end/emulated_devices/iPhone6Plus-landscape.svg +0 -118
- package/front_end/emulated_devices/iPhone6Plus-portrait.svg +0 -116
- package/front_end/emulated_devices/optimized/MotoG4-landscape.avif +0 -0
- package/front_end/emulated_devices/optimized/MotoG4-portrait.avif +0 -0
- package/front_end/emulated_devices/optimized/Nexus5X-landscape.avif +0 -0
- package/front_end/emulated_devices/optimized/Nexus5X-portrait.avif +0 -0
- package/front_end/emulated_devices/optimized/Nexus6P-landscape.avif +0 -0
- package/front_end/emulated_devices/optimized/Nexus6P-portrait.avif +0 -0
- package/front_end/emulated_devices/optimized/README.md +0 -9
- package/front_end/emulated_devices/optimized/google-nest-hub-horizontal.avif +0 -0
- package/front_end/emulated_devices/optimized/google-nest-hub-max-horizontal.avif +0 -0
- package/front_end/emulated_devices/optimized/google-nexus-5-horizontal-default-1x.avif +0 -0
- package/front_end/emulated_devices/optimized/google-nexus-5-horizontal-default-2x.avif +0 -0
- package/front_end/emulated_devices/optimized/google-nexus-5-horizontal-keyboard-1x.avif +0 -0
- package/front_end/emulated_devices/optimized/google-nexus-5-horizontal-keyboard-2x.avif +0 -0
- package/front_end/emulated_devices/optimized/google-nexus-5-horizontal-navigation-1x.avif +0 -0
- package/front_end/emulated_devices/optimized/google-nexus-5-horizontal-navigation-2x.avif +0 -0
- package/front_end/emulated_devices/optimized/google-nexus-5-vertical-default-1x.avif +0 -0
- package/front_end/emulated_devices/optimized/google-nexus-5-vertical-default-2x.avif +0 -0
- package/front_end/emulated_devices/optimized/google-nexus-5-vertical-keyboard-1x.avif +0 -0
- package/front_end/emulated_devices/optimized/google-nexus-5-vertical-keyboard-2x.avif +0 -0
- package/front_end/emulated_devices/optimized/google-nexus-5-vertical-navigation-1x.avif +0 -0
- package/front_end/emulated_devices/optimized/google-nexus-5-vertical-navigation-2x.avif +0 -0
- package/front_end/emulated_devices/optimized/google-nexus-5x-horizontal-default-1x.avif +0 -0
- package/front_end/emulated_devices/optimized/google-nexus-5x-horizontal-default-2x.avif +0 -0
- package/front_end/emulated_devices/optimized/google-nexus-5x-horizontal-keyboard-1x.avif +0 -0
- package/front_end/emulated_devices/optimized/google-nexus-5x-horizontal-keyboard-2x.avif +0 -0
- package/front_end/emulated_devices/optimized/google-nexus-5x-horizontal-navigation-1x.avif +0 -0
- package/front_end/emulated_devices/optimized/google-nexus-5x-horizontal-navigation-2x.avif +0 -0
- package/front_end/emulated_devices/optimized/google-nexus-5x-vertical-default-1x.avif +0 -0
- package/front_end/emulated_devices/optimized/google-nexus-5x-vertical-default-2x.avif +0 -0
- package/front_end/emulated_devices/optimized/google-nexus-5x-vertical-keyboard-1x.avif +0 -0
- package/front_end/emulated_devices/optimized/google-nexus-5x-vertical-keyboard-2x.avif +0 -0
- package/front_end/emulated_devices/optimized/google-nexus-5x-vertical-navigation-1x.avif +0 -0
- package/front_end/emulated_devices/optimized/google-nexus-5x-vertical-navigation-2x.avif +0 -0
- package/front_end/emulated_devices/optimized/iPad-landscape.avif +0 -0
- package/front_end/emulated_devices/optimized/iPad-portrait.avif +0 -0
- package/front_end/emulated_devices/optimized/iPhone5-landscape.avif +0 -0
- package/front_end/emulated_devices/optimized/iPhone5-portrait.avif +0 -0
- package/front_end/emulated_devices/optimized/iPhone6-landscape.avif +0 -0
- package/front_end/emulated_devices/optimized/iPhone6-portrait.avif +0 -0
- package/front_end/emulated_devices/optimized/iPhone6Plus-landscape.avif +0 -0
- package/front_end/emulated_devices/optimized/iPhone6Plus-portrait.avif +0 -0
- package/front_end/emulated_devices/optimized/optimized_images.gni +0 -53
- package/front_end/models/ai_assistance/agents/README.md +0 -136
- /package/front_end/{panels/common → ui/components/dialogs}/freDialog.css +0 -0
- /package/front_end/{panels/common/common.css → ui/components/dialogs/typeToAllowDialog.css} +0 -0
- /package/front_end/{panels/common → ui/components/text_editor}/aiCodeCompletionDisclaimer.css +0 -0
- /package/front_end/{panels/common → ui/components/text_editor}/aiCodeCompletionTeaser.css +0 -0
- /package/front_end/{panels/common → ui/components/text_editor}/aiCodeGenerationTeaser.css +0 -0
- /package/front_end/{panels/common → ui/components/text_editor}/aiCodeGenerationUpgradeDialog.css +0 -0
|
@@ -0,0 +1,640 @@
|
|
|
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
|
+
import {
|
|
8
|
+
type CommentThread,
|
|
9
|
+
deepQuerySelectorAll,
|
|
10
|
+
isElementVisible,
|
|
11
|
+
rematchCommentAnchor,
|
|
12
|
+
resolveCommentAnchor,
|
|
13
|
+
resolveCommentAnchorElement,
|
|
14
|
+
} from './CommentAnchorResolver.js';
|
|
15
|
+
|
|
16
|
+
export interface StartOptions {
|
|
17
|
+
root?: Document|Element;
|
|
18
|
+
scrollTarget?: EventTarget;
|
|
19
|
+
resizeTarget?: Element;
|
|
20
|
+
defaultText?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface PinPositionData {
|
|
24
|
+
id: string;
|
|
25
|
+
top: number;
|
|
26
|
+
left: number;
|
|
27
|
+
visible: boolean;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface HighlightRectData {
|
|
31
|
+
id: string;
|
|
32
|
+
top: number;
|
|
33
|
+
left: number;
|
|
34
|
+
width: number;
|
|
35
|
+
height: number;
|
|
36
|
+
visible: boolean;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface HoverHighlightData {
|
|
40
|
+
top: number;
|
|
41
|
+
left: number;
|
|
42
|
+
width: number;
|
|
43
|
+
height: number;
|
|
44
|
+
visible: boolean;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface EventTypes {
|
|
48
|
+
[Events.COMMENT_THREADS_CHANGED]: CommentThread[];
|
|
49
|
+
[Events.POSITIONS_UPDATED]: {
|
|
50
|
+
pins: PinPositionData[],
|
|
51
|
+
highlights: HighlightRectData[],
|
|
52
|
+
};
|
|
53
|
+
[Events.COMMENT_MODE_CHANGED]: boolean;
|
|
54
|
+
[Events.HOVER_HIGHLIGHT_CHANGED]: HoverHighlightData|null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export const enum Events {
|
|
58
|
+
COMMENT_THREADS_CHANGED = 'CommentThreadsChanged',
|
|
59
|
+
POSITIONS_UPDATED = 'PositionsUpdated',
|
|
60
|
+
COMMENT_MODE_CHANGED = 'CommentModeChanged',
|
|
61
|
+
HOVER_HIGHLIGHT_CHANGED = 'HoverHighlightChanged',
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Orchestrates comment threads, coordinates interactive commenting UI mode,
|
|
66
|
+
* and tracks live DOM element positions via observers and event listeners.
|
|
67
|
+
*/
|
|
68
|
+
export class CommentManager extends Common.ObjectWrapper.ObjectWrapper<EventTypes> {
|
|
69
|
+
readonly #commentThreads = new Map<string, CommentThread>();
|
|
70
|
+
readonly #liveNodeCache = new WeakMap<CommentThread, Element>();
|
|
71
|
+
#observedThreads = new WeakSet<Element>();
|
|
72
|
+
#intersectionObserver?: IntersectionObserver;
|
|
73
|
+
#commentMode = false;
|
|
74
|
+
#nextId = 1;
|
|
75
|
+
|
|
76
|
+
#hoverData: HoverHighlightData|null = null;
|
|
77
|
+
#pinPositions: PinPositionData[] = [];
|
|
78
|
+
#highlightRects: HighlightRectData[] = [];
|
|
79
|
+
|
|
80
|
+
#clickListener?: (event: Event) => void;
|
|
81
|
+
#hoverListener?: (event: Event) => void;
|
|
82
|
+
#suppressListener?: (event: Event) => void;
|
|
83
|
+
#clickContainer?: Element|Document;
|
|
84
|
+
|
|
85
|
+
readonly #hoverEventTypes = [
|
|
86
|
+
'mouseover',
|
|
87
|
+
'mouseout',
|
|
88
|
+
'mouseenter',
|
|
89
|
+
'mouseleave',
|
|
90
|
+
'pointerover',
|
|
91
|
+
'pointerout',
|
|
92
|
+
'mousemove',
|
|
93
|
+
];
|
|
94
|
+
readonly #suppressEventTypes = [
|
|
95
|
+
'mousedown',
|
|
96
|
+
'pointerdown',
|
|
97
|
+
'mouseup',
|
|
98
|
+
'pointerup',
|
|
99
|
+
'dblclick',
|
|
100
|
+
];
|
|
101
|
+
|
|
102
|
+
#scrollListener?: (event: Event) => void;
|
|
103
|
+
#scrollTarget?: EventTarget;
|
|
104
|
+
#scrollRafId?: number;
|
|
105
|
+
|
|
106
|
+
#devToolsResizeObserver?: ResizeObserver;
|
|
107
|
+
#resizeRafId?: number;
|
|
108
|
+
|
|
109
|
+
#mutationObserver?: MutationObserver;
|
|
110
|
+
#rematchTimeoutId?: ReturnType<typeof setTimeout>;
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Lazily creates an IntersectionObserver that monitors the visibility and viewport intersection
|
|
114
|
+
* of elements with active comment anchors.
|
|
115
|
+
*
|
|
116
|
+
* As anchored DOM nodes scroll in or out of visible viewport regions or virtualized lists,
|
|
117
|
+
* the observer triggers position recalculations to ensure comment pins and highlights are
|
|
118
|
+
* positioned accurately or hidden when out of view.
|
|
119
|
+
*/
|
|
120
|
+
#getIntersectionObserver(): IntersectionObserver {
|
|
121
|
+
if (!this.#intersectionObserver) {
|
|
122
|
+
this.#intersectionObserver = new IntersectionObserver(() => {
|
|
123
|
+
this.#updatePositions();
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
return this.#intersectionObserver;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
setCommentMode(active: boolean): void {
|
|
130
|
+
if (this.#commentMode === active) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
this.#commentMode = active;
|
|
134
|
+
if (!active) {
|
|
135
|
+
this.#setHoverHighlight(null);
|
|
136
|
+
}
|
|
137
|
+
document.body.style.cursor = active ? 'crosshair' : '';
|
|
138
|
+
this.dispatchEventToListeners(Events.COMMENT_MODE_CHANGED, active);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
isCommentMode(): boolean {
|
|
142
|
+
return this.#commentMode;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
#setHoverHighlight(data: HoverHighlightData|null): void {
|
|
146
|
+
if (data === null && this.#hoverData === null) {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
if (data && this.#hoverData && data.top === this.#hoverData.top && data.left === this.#hoverData.left &&
|
|
150
|
+
data.width === this.#hoverData.width && data.height === this.#hoverData.height &&
|
|
151
|
+
data.visible === this.#hoverData.visible) {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
this.#hoverData = data;
|
|
155
|
+
this.dispatchEventToListeners(Events.HOVER_HIGHLIGHT_CHANGED, data);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
getHoverHighlight(): HoverHighlightData|null {
|
|
159
|
+
return this.#hoverData;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
getPinPositions(): PinPositionData[] {
|
|
163
|
+
return this.#pinPositions;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
getHighlightRects(): HighlightRectData[] {
|
|
167
|
+
return this.#highlightRects;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
handleElementClick(element: Element, commentText = 'New comment'): CommentThread|null {
|
|
171
|
+
if (!this.#commentMode) {
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
return this.createComment(element, commentText, 'DEVELOPER');
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
createComment(
|
|
178
|
+
element: Element,
|
|
179
|
+
text: string,
|
|
180
|
+
author: 'DEVELOPER'|'AGENT' = 'DEVELOPER',
|
|
181
|
+
changes?: Array<Record<string, unknown>>,
|
|
182
|
+
): CommentThread|null {
|
|
183
|
+
const anchorEl = resolveCommentAnchorElement(element);
|
|
184
|
+
const anchor = resolveCommentAnchor(element);
|
|
185
|
+
if (!anchor || !anchorEl) {
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const id = `comment-${this.#nextId++}`;
|
|
190
|
+
const thread: CommentThread = {
|
|
191
|
+
id,
|
|
192
|
+
anchor,
|
|
193
|
+
comments: [{
|
|
194
|
+
author,
|
|
195
|
+
text,
|
|
196
|
+
timestamp: Date.now(),
|
|
197
|
+
}],
|
|
198
|
+
status: 'ACTIVE',
|
|
199
|
+
changes,
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
this.#commentThreads.set(id, thread);
|
|
203
|
+
this.#liveNodeCache.set(thread, anchorEl);
|
|
204
|
+
|
|
205
|
+
const observer = this.#getIntersectionObserver();
|
|
206
|
+
observer.observe(anchorEl);
|
|
207
|
+
this.#observedThreads.add(anchorEl);
|
|
208
|
+
|
|
209
|
+
this.#updatePositions();
|
|
210
|
+
this.dispatchEventToListeners(Events.COMMENT_THREADS_CHANGED, this.getCommentThreads());
|
|
211
|
+
|
|
212
|
+
return thread;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
getCommentThread(id: string): CommentThread|undefined {
|
|
216
|
+
return this.#commentThreads.get(id);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
getCommentThreads(): CommentThread[] {
|
|
220
|
+
return Array.from(this.#commentThreads.values());
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
removeCommentThread(id: string): void {
|
|
224
|
+
const thread = this.#commentThreads.get(id);
|
|
225
|
+
if (!thread) {
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
const el = this.#liveNodeCache.get(thread);
|
|
229
|
+
this.#commentThreads.delete(id);
|
|
230
|
+
this.#liveNodeCache.delete(thread);
|
|
231
|
+
|
|
232
|
+
if (el && this.#intersectionObserver) {
|
|
233
|
+
let isElementStillObserved = false;
|
|
234
|
+
for (const remainingThread of this.#commentThreads.values()) {
|
|
235
|
+
if (this.#liveNodeCache.get(remainingThread) === el) {
|
|
236
|
+
isElementStillObserved = true;
|
|
237
|
+
break;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
if (!isElementStillObserved) {
|
|
241
|
+
this.#intersectionObserver.unobserve(el);
|
|
242
|
+
this.#observedThreads.delete(el);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
this.#updatePositions();
|
|
247
|
+
this.dispatchEventToListeners(Events.COMMENT_THREADS_CHANGED, this.getCommentThreads());
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Rematches stored comments to live DOM nodes across dynamic container updates.
|
|
252
|
+
*
|
|
253
|
+
* Pre-queries [jslog] elements once across the container to avoid redundant deep DOM traversals
|
|
254
|
+
* during batch rematching, and cleans up unobserved IntersectionObserver nodes in O(N) time.
|
|
255
|
+
*/
|
|
256
|
+
#rematchAllComments(root: Document|Element = document): void {
|
|
257
|
+
const jslogElements = deepQuerySelectorAll(root, '[jslog]');
|
|
258
|
+
const oldElements = new Set<Element>();
|
|
259
|
+
const newElements = new Set<Element>();
|
|
260
|
+
|
|
261
|
+
for (const thread of this.#commentThreads.values()) {
|
|
262
|
+
const oldEl = this.#liveNodeCache.get(thread);
|
|
263
|
+
if (oldEl) {
|
|
264
|
+
oldElements.add(oldEl);
|
|
265
|
+
}
|
|
266
|
+
// Always try to rematch to support virtualized lists where DOM nodes are recycled.
|
|
267
|
+
const el = rematchCommentAnchor(thread, root, jslogElements);
|
|
268
|
+
|
|
269
|
+
if (el) {
|
|
270
|
+
this.#liveNodeCache.set(thread, el);
|
|
271
|
+
newElements.add(el);
|
|
272
|
+
} else {
|
|
273
|
+
this.#liveNodeCache.delete(thread);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if (this.#intersectionObserver) {
|
|
278
|
+
// Unobserve old elements no longer referenced by any comment thread in O(N) time.
|
|
279
|
+
for (const oldEl of oldElements) {
|
|
280
|
+
if (!newElements.has(oldEl)) {
|
|
281
|
+
this.#intersectionObserver.unobserve(oldEl);
|
|
282
|
+
this.#observedThreads.delete(oldEl);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
this.#updatePositions();
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
#updatePositions(): void {
|
|
291
|
+
const scrollX = window.scrollX;
|
|
292
|
+
const scrollY = window.scrollY;
|
|
293
|
+
|
|
294
|
+
const newPins: PinPositionData[] = [];
|
|
295
|
+
const newHighlights: HighlightRectData[] = [];
|
|
296
|
+
const elementPinCounts = new Map<Element, number>();
|
|
297
|
+
|
|
298
|
+
for (const thread of this.#commentThreads.values()) {
|
|
299
|
+
const el = this.#liveNodeCache.get(thread) || null;
|
|
300
|
+
if (!el || !el.isConnected) {
|
|
301
|
+
continue;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const observer = this.#getIntersectionObserver();
|
|
305
|
+
if (!this.#observedThreads.has(el)) {
|
|
306
|
+
observer.observe(el);
|
|
307
|
+
this.#observedThreads.add(el);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if (!isElementVisible(el)) {
|
|
311
|
+
continue;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const rect = el.getBoundingClientRect();
|
|
315
|
+
const offsetIndex = elementPinCounts.get(el) || 0;
|
|
316
|
+
elementPinCounts.set(el, offsetIndex + 1);
|
|
317
|
+
// Offset by 26px vertically (24px pin icon height + 2px spacing) so multiple comment pins on the same element stack vertically without overlapping.
|
|
318
|
+
const offsetY = offsetIndex * 26;
|
|
319
|
+
|
|
320
|
+
// Offset by -12px (half of the 24px pin diameter) so the pin icon is centered on the top-right corner of the target element.
|
|
321
|
+
newPins.push({
|
|
322
|
+
id: thread.id,
|
|
323
|
+
top: scrollY + rect.top - 12 + offsetY,
|
|
324
|
+
left: scrollX + rect.right - 12,
|
|
325
|
+
visible: true,
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
newHighlights.push({
|
|
329
|
+
id: thread.id,
|
|
330
|
+
top: scrollY + rect.top,
|
|
331
|
+
left: scrollX + rect.left,
|
|
332
|
+
width: rect.width,
|
|
333
|
+
height: rect.height,
|
|
334
|
+
visible: true,
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
this.#pinPositions = newPins;
|
|
339
|
+
this.#highlightRects = newHighlights;
|
|
340
|
+
this.dispatchEventToListeners(Events.POSITIONS_UPDATED, {
|
|
341
|
+
pins: newPins,
|
|
342
|
+
highlights: newHighlights,
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Initializes event listeners and lifecycle observers across the target DOM container.
|
|
348
|
+
*
|
|
349
|
+
* Sets up:
|
|
350
|
+
* - Capturing click, hover, and interaction suppression handlers to coordinate comment placement.
|
|
351
|
+
* - A capturing scroll listener on the window to track scrolling across nested subpanes.
|
|
352
|
+
* - A ResizeObserver to recalculate overlay coordinates when DevTools panels or drawers are resized.
|
|
353
|
+
* - A MutationObserver to automatically rematch existing comment anchors when the DOM re-renders.
|
|
354
|
+
*/
|
|
355
|
+
start(rootOrOptions?: Document|Element|StartOptions, defaultText = 'New comment'): void {
|
|
356
|
+
let root: Document|Element|undefined;
|
|
357
|
+
let scrollTarget: EventTarget|undefined;
|
|
358
|
+
let resizeTarget: Element|undefined;
|
|
359
|
+
let text = defaultText;
|
|
360
|
+
|
|
361
|
+
if (rootOrOptions && !(rootOrOptions instanceof Document) && !(rootOrOptions instanceof Element)) {
|
|
362
|
+
root = rootOrOptions.root;
|
|
363
|
+
scrollTarget = rootOrOptions.scrollTarget;
|
|
364
|
+
resizeTarget = rootOrOptions.resizeTarget;
|
|
365
|
+
text = rootOrOptions.defaultText ?? defaultText;
|
|
366
|
+
} else if (rootOrOptions) {
|
|
367
|
+
root = rootOrOptions;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
root = root || document;
|
|
371
|
+
scrollTarget = scrollTarget || (root instanceof Document ? (root.defaultView || window) : window);
|
|
372
|
+
resizeTarget = resizeTarget || (root instanceof Document ? (root.body || root.documentElement) : root);
|
|
373
|
+
|
|
374
|
+
this.stop();
|
|
375
|
+
this.#installClickListener(root, text);
|
|
376
|
+
this.#installScrollListener(scrollTarget);
|
|
377
|
+
this.#installResizeObserver(resizeTarget);
|
|
378
|
+
this.#installMutationObserver(root);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Stops and detaches all active listeners and observers without clearing comment threads.
|
|
383
|
+
*/
|
|
384
|
+
stop(): void {
|
|
385
|
+
this.#removeClickListener();
|
|
386
|
+
this.#removeScrollListener();
|
|
387
|
+
this.#removeResizeObserver();
|
|
388
|
+
this.#removeMutationObserver();
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* Sets up capturing click, hover, and pointer interaction listeners on the container.
|
|
393
|
+
*
|
|
394
|
+
* When Comment Mode is active:
|
|
395
|
+
* - Clicks on anchorable elements create new comment threads and consume the click event,
|
|
396
|
+
* preventing normal DevTools UI triggers such as node selection or navigation.
|
|
397
|
+
* - Pointer and mouse press events are suppressed to prevent accidental text selections or drag interactions.
|
|
398
|
+
* - Hover events compute and display a real-time preview highlight over the candidate anchor element.
|
|
399
|
+
*/
|
|
400
|
+
#installClickListener(container: Element|Document = document, defaultText = 'New comment'): void {
|
|
401
|
+
this.#removeClickListener();
|
|
402
|
+
this.#clickContainer = container;
|
|
403
|
+
|
|
404
|
+
this.#clickListener = (event: Event): void => {
|
|
405
|
+
if (!this.#commentMode) {
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
const composedTarget = event.composedPath()[0];
|
|
409
|
+
const target = (composedTarget instanceof Element) ? composedTarget : event.target;
|
|
410
|
+
if (!(target instanceof Element)) {
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
const thread = this.handleElementClick(target, defaultText);
|
|
414
|
+
if (thread) {
|
|
415
|
+
event.consume(true);
|
|
416
|
+
}
|
|
417
|
+
};
|
|
418
|
+
|
|
419
|
+
this.#suppressListener = (event: Event): void => {
|
|
420
|
+
if (!this.#commentMode) {
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
const composedTarget = event.composedPath()[0];
|
|
424
|
+
const target = (composedTarget instanceof Element) ? composedTarget : event.target;
|
|
425
|
+
if (!(target instanceof Element)) {
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
const anchorEl = resolveCommentAnchorElement(target);
|
|
429
|
+
if (anchorEl) {
|
|
430
|
+
event.consume(true);
|
|
431
|
+
}
|
|
432
|
+
};
|
|
433
|
+
|
|
434
|
+
this.#hoverListener = (event: Event): void => {
|
|
435
|
+
if (!this.#commentMode) {
|
|
436
|
+
this.#setHoverHighlight(null);
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
const composedTarget = event.composedPath()[0];
|
|
440
|
+
const target = (composedTarget instanceof Element) ? composedTarget : event.target;
|
|
441
|
+
if (!(target instanceof Element)) {
|
|
442
|
+
this.#setHoverHighlight(null);
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
const isLeaveEvent = event.type === 'mouseout' || event.type === 'mouseleave' || event.type === 'pointerout';
|
|
446
|
+
const anchorEl = resolveCommentAnchorElement(target);
|
|
447
|
+
if (isLeaveEvent) {
|
|
448
|
+
const relatedTarget = (event as MouseEvent | PointerEvent).relatedTarget;
|
|
449
|
+
if (anchorEl && relatedTarget instanceof Node && anchorEl.contains(relatedTarget)) {
|
|
450
|
+
event.consume(true);
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
this.#setHoverHighlight(null);
|
|
455
|
+
if (anchorEl) {
|
|
456
|
+
event.consume(true);
|
|
457
|
+
}
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
if (anchorEl) {
|
|
461
|
+
const cmLine = target.closest('.cm-line');
|
|
462
|
+
const highlightTarget = (cmLine && anchorEl.classList.contains('cm-editor')) ? cmLine : anchorEl;
|
|
463
|
+
const rect = highlightTarget.getBoundingClientRect();
|
|
464
|
+
const scrollX = window.scrollX;
|
|
465
|
+
const scrollY = window.scrollY;
|
|
466
|
+
this.#setHoverHighlight({
|
|
467
|
+
top: scrollY + rect.top,
|
|
468
|
+
left: scrollX + rect.left,
|
|
469
|
+
width: rect.width,
|
|
470
|
+
height: rect.height,
|
|
471
|
+
visible: true,
|
|
472
|
+
});
|
|
473
|
+
event.consume(true);
|
|
474
|
+
} else {
|
|
475
|
+
this.#setHoverHighlight(null);
|
|
476
|
+
}
|
|
477
|
+
};
|
|
478
|
+
|
|
479
|
+
container.addEventListener('click', this.#clickListener, {capture: true});
|
|
480
|
+
for (const type of this.#suppressEventTypes) {
|
|
481
|
+
container.addEventListener(type, this.#suppressListener, {capture: true});
|
|
482
|
+
}
|
|
483
|
+
for (const type of this.#hoverEventTypes) {
|
|
484
|
+
container.addEventListener(type, this.#hoverListener, {capture: true});
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
#removeClickListener(): void {
|
|
489
|
+
if (this.#clickContainer) {
|
|
490
|
+
if (this.#clickListener) {
|
|
491
|
+
this.#clickContainer.removeEventListener('click', this.#clickListener, {capture: true});
|
|
492
|
+
}
|
|
493
|
+
if (this.#suppressListener) {
|
|
494
|
+
for (const type of this.#suppressEventTypes) {
|
|
495
|
+
this.#clickContainer.removeEventListener(type, this.#suppressListener, {capture: true});
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
if (this.#hoverListener) {
|
|
499
|
+
for (const type of this.#hoverEventTypes) {
|
|
500
|
+
this.#clickContainer.removeEventListener(type, this.#hoverListener, {capture: true});
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
this.#clickListener = undefined;
|
|
504
|
+
this.#suppressListener = undefined;
|
|
505
|
+
this.#hoverListener = undefined;
|
|
506
|
+
this.#clickContainer = undefined;
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
/**
|
|
511
|
+
* Registers a capturing scroll listener on the window or target container.
|
|
512
|
+
*
|
|
513
|
+
* Because DevTools contains multiple independently scrolling subpanes (such as the Elements tree,
|
|
514
|
+
* Sources editor, and Network list), a capturing listener on the top-level window catches scroll
|
|
515
|
+
* events anywhere in the tree and schedules a throttled position update using requestAnimationFrame.
|
|
516
|
+
*/
|
|
517
|
+
#installScrollListener(target: EventTarget = window): void {
|
|
518
|
+
this.#removeScrollListener();
|
|
519
|
+
this.#scrollTarget = target;
|
|
520
|
+
this.#scrollListener = (): void => {
|
|
521
|
+
this.#setHoverHighlight(null);
|
|
522
|
+
if (this.#scrollRafId !== undefined) {
|
|
523
|
+
cancelAnimationFrame(this.#scrollRafId);
|
|
524
|
+
}
|
|
525
|
+
this.#scrollRafId = requestAnimationFrame(() => {
|
|
526
|
+
this.#scrollRafId = undefined;
|
|
527
|
+
this.#updatePositions();
|
|
528
|
+
});
|
|
529
|
+
};
|
|
530
|
+
target.addEventListener('scroll', this.#scrollListener, {capture: true, passive: true});
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
#removeScrollListener(): void {
|
|
534
|
+
if (this.#scrollTarget && this.#scrollListener) {
|
|
535
|
+
this.#scrollTarget.removeEventListener('scroll', this.#scrollListener, {capture: true});
|
|
536
|
+
this.#scrollListener = undefined;
|
|
537
|
+
this.#scrollTarget = undefined;
|
|
538
|
+
}
|
|
539
|
+
if (this.#scrollRafId !== undefined) {
|
|
540
|
+
cancelAnimationFrame(this.#scrollRafId);
|
|
541
|
+
this.#scrollRafId = undefined;
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
/**
|
|
546
|
+
* Observes dimensions of the root element to react to layout changes.
|
|
547
|
+
*
|
|
548
|
+
* Resizing DevTools windows, adjusting drawer splitters, or toggling sidebars alters the bounding
|
|
549
|
+
* boxes of anchored elements. The observer ensures overlay pins and highlight boxes are updated
|
|
550
|
+
* whenever container dimensions change.
|
|
551
|
+
*/
|
|
552
|
+
#installResizeObserver(element: Element = document.body): void {
|
|
553
|
+
this.#removeResizeObserver();
|
|
554
|
+
this.#devToolsResizeObserver = new ResizeObserver(() => {
|
|
555
|
+
this.#setHoverHighlight(null);
|
|
556
|
+
if (this.#resizeRafId !== undefined) {
|
|
557
|
+
cancelAnimationFrame(this.#resizeRafId);
|
|
558
|
+
}
|
|
559
|
+
this.#resizeRafId = requestAnimationFrame(() => {
|
|
560
|
+
this.#resizeRafId = undefined;
|
|
561
|
+
this.#updatePositions();
|
|
562
|
+
});
|
|
563
|
+
});
|
|
564
|
+
this.#devToolsResizeObserver.observe(element);
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
#removeResizeObserver(): void {
|
|
568
|
+
if (this.#devToolsResizeObserver) {
|
|
569
|
+
this.#devToolsResizeObserver.disconnect();
|
|
570
|
+
this.#devToolsResizeObserver = undefined;
|
|
571
|
+
}
|
|
572
|
+
if (this.#resizeRafId !== undefined) {
|
|
573
|
+
cancelAnimationFrame(this.#resizeRafId);
|
|
574
|
+
this.#resizeRafId = undefined;
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
/**
|
|
579
|
+
* Debounces rematching of comments across dynamic DOM updates.
|
|
580
|
+
*/
|
|
581
|
+
#scheduleRematch(root: Document|Element = document): void {
|
|
582
|
+
if (this.#rematchTimeoutId) {
|
|
583
|
+
clearTimeout(this.#rematchTimeoutId);
|
|
584
|
+
}
|
|
585
|
+
this.#rematchTimeoutId = setTimeout(() => {
|
|
586
|
+
this.#rematchTimeoutId = undefined;
|
|
587
|
+
this.#rematchAllComments(root);
|
|
588
|
+
}, 250);
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
/**
|
|
592
|
+
* Monitors DOM tree additions, removals, and attribute mutations across the container.
|
|
593
|
+
*
|
|
594
|
+
* DevTools inspector panes dynamically re-render items when expanding trees, filtering results,
|
|
595
|
+
* or updating state. By observing child list mutations and key domain attributes (`jslog`,
|
|
596
|
+
* `data-network-request-id`, `data-backend-node-id`, `aria-expanded`), the manager schedules a
|
|
597
|
+
* debounced rematch so comments remain bound to their corresponding live DOM elements.
|
|
598
|
+
*/
|
|
599
|
+
#installMutationObserver(root: Document|Element = document): void {
|
|
600
|
+
this.#removeMutationObserver();
|
|
601
|
+
this.#mutationObserver = new MutationObserver(() => {
|
|
602
|
+
this.#scheduleRematch(root);
|
|
603
|
+
});
|
|
604
|
+
const targetNode = root instanceof Document ? (root.body || root.documentElement) : root;
|
|
605
|
+
this.#mutationObserver.observe(targetNode, {
|
|
606
|
+
childList: true,
|
|
607
|
+
subtree: true,
|
|
608
|
+
attributes: true,
|
|
609
|
+
attributeFilter: ['jslog', 'data-network-request-id', 'data-backend-node-id', 'aria-expanded'],
|
|
610
|
+
});
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
#removeMutationObserver(): void {
|
|
614
|
+
if (this.#mutationObserver) {
|
|
615
|
+
this.#mutationObserver.disconnect();
|
|
616
|
+
this.#mutationObserver = undefined;
|
|
617
|
+
}
|
|
618
|
+
if (this.#rematchTimeoutId) {
|
|
619
|
+
clearTimeout(this.#rematchTimeoutId);
|
|
620
|
+
this.#rematchTimeoutId = undefined;
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
/**
|
|
625
|
+
* Fully resets the manager by disabling comment mode, disconnecting all observers and listeners,
|
|
626
|
+
* and purging all active comment threads and overlay data.
|
|
627
|
+
*/
|
|
628
|
+
clear(): void {
|
|
629
|
+
this.setCommentMode(false);
|
|
630
|
+
this.stop();
|
|
631
|
+
this.#intersectionObserver?.disconnect();
|
|
632
|
+
this.#intersectionObserver = undefined;
|
|
633
|
+
this.#observedThreads = new WeakSet();
|
|
634
|
+
this.#commentThreads.clear();
|
|
635
|
+
this.#pinPositions = [];
|
|
636
|
+
this.#highlightRects = [];
|
|
637
|
+
this.#updatePositions();
|
|
638
|
+
this.dispatchEventToListeners(Events.COMMENT_THREADS_CHANGED, []);
|
|
639
|
+
}
|
|
640
|
+
}
|