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
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"@codemirror/search": "6.5.4",
|
|
27
27
|
"@codemirror/view": "6.23.0",
|
|
28
28
|
"@lezer/common": "1.2.3",
|
|
29
|
-
"@lezer/css": "1.3.
|
|
29
|
+
"@lezer/css": "1.3.5",
|
|
30
30
|
"@lezer/highlight": "1.2.1",
|
|
31
31
|
"@lezer/html": "1.3.10",
|
|
32
32
|
"@lezer/javascript": "1.5.4",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"style-mod": "4.1.0"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
|
-
"rollup": "4.
|
|
39
|
+
"rollup": "4.62.4",
|
|
40
40
|
"rollup-plugin-dts": "6.1.1",
|
|
41
41
|
"typescript": "5.9.2"
|
|
42
42
|
}
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
set -e
|
|
2
|
+
npm install --package-lock-only
|
|
3
|
+
npm audit
|
|
1
4
|
npm install
|
|
2
5
|
../../../node_modules/.bin/tsc -d -t esnext -m esnext --moduleResolution bundler --ignoreConfig bundle.ts
|
|
3
6
|
../../../node_modules/rollup/dist/bin/rollup -c
|
|
4
|
-
rm -rf node_modules bundle.js bundle.d.ts
|
|
7
|
+
rm -rf node_modules bundle.js bundle.d.ts package-lock.json chunk/*.d.ts
|
|
5
8
|
# Because there's a bug in clang causing it to reformat import lists even where formatting is disabled, run it right away
|
|
6
9
|
git cl format --js
|
|
@@ -2,8 +2,16 @@
|
|
|
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 CodeMirror from '../../third_party/codemirror.next/codemirror.next.js';
|
|
5
6
|
import * as VisualLogging from '../../ui/visual_logging/visual_logging.js';
|
|
6
7
|
|
|
8
|
+
export interface EditorAnchorSignature {
|
|
9
|
+
/** 1-based line number for CodeMirror text editor anchors */
|
|
10
|
+
lineNumber: number;
|
|
11
|
+
/** File path associated with the editor */
|
|
12
|
+
filePath?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
7
15
|
export interface CommentAnchorSignature {
|
|
8
16
|
/** Visual logging tree path, e.g. "Panel: elements > Pane: styles > TreeOutline > TreeItem: color" */
|
|
9
17
|
vePath: string;
|
|
@@ -11,16 +19,14 @@ export interface CommentAnchorSignature {
|
|
|
11
19
|
textSignature: string;
|
|
12
20
|
/** Text content of the parent container VE node for sibling disambiguation */
|
|
13
21
|
parentTextSignature?: string;
|
|
14
|
-
/** 0-indexed position among siblings sharing the same visual logging path
|
|
22
|
+
/** 0-indexed position among siblings sharing the same visual logging path */
|
|
15
23
|
siblingIndex?: number;
|
|
16
24
|
/** Optional backend RequestId for Network panel elements (`data-network-request-id`) */
|
|
17
25
|
networkRequestId?: string;
|
|
18
26
|
/** Optional backend NodeId for Elements panel DOM nodes (`data-backend-node-id`) */
|
|
19
27
|
backendNodeId?: number;
|
|
20
|
-
/** Optional
|
|
21
|
-
|
|
22
|
-
/** Optional file path of the document displayed in the CodeMirror editor */
|
|
23
|
-
editorFilePath?: string;
|
|
28
|
+
/** Optional editor anchor coordinates for CodeMirror text editors */
|
|
29
|
+
editor?: EditorAnchorSignature;
|
|
24
30
|
}
|
|
25
31
|
|
|
26
32
|
export interface CommentThread {
|
|
@@ -44,6 +50,14 @@ const IGNORED_MINOR_CONTROLS = new Set<number>([
|
|
|
44
50
|
VisualLogging.VisualElements.Toolbar,
|
|
45
51
|
]);
|
|
46
52
|
|
|
53
|
+
/**
|
|
54
|
+
* Finds the closest ancestor (or the element itself) matching a CSS selector,
|
|
55
|
+
* traversing across Shadow DOM boundaries (shadow root boundaries to shadow hosts).
|
|
56
|
+
*
|
|
57
|
+
* @param element The starting element for traversal.
|
|
58
|
+
* @param selector The CSS selector to match against.
|
|
59
|
+
* @returns The first matching Element or null if none is found.
|
|
60
|
+
*/
|
|
47
61
|
export function closestAcrossShadow(element: Element, selector: string): Element|null {
|
|
48
62
|
let current: Element|null = element;
|
|
49
63
|
while (current) {
|
|
@@ -55,10 +69,35 @@ export function closestAcrossShadow(element: Element, selector: string): Element
|
|
|
55
69
|
return null;
|
|
56
70
|
}
|
|
57
71
|
|
|
72
|
+
/**
|
|
73
|
+
* Checks whether an element is a CodeMirror editor container (`.cm-editor`).
|
|
74
|
+
*
|
|
75
|
+
* @param element The element to check.
|
|
76
|
+
* @returns True if the element has the `.cm-editor` class; otherwise false.
|
|
77
|
+
*/
|
|
78
|
+
function isCodeMirrorEditor(element: Element): boolean {
|
|
79
|
+
return element.classList.contains('cm-editor');
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Checks whether an element contains non-empty text content (after trimming whitespace),
|
|
84
|
+
* including text from any nested shadow roots.
|
|
85
|
+
*
|
|
86
|
+
* @param element The element to check.
|
|
87
|
+
* @returns True if the element contains non-empty text; otherwise false.
|
|
88
|
+
*/
|
|
58
89
|
export function isNonEmptyItem(element: Element): boolean {
|
|
59
90
|
return element.deepTextContent().trim().length > 0;
|
|
60
91
|
}
|
|
61
92
|
|
|
93
|
+
/**
|
|
94
|
+
* Determines whether an element represents a tab header or tab title
|
|
95
|
+
* (e.g. PanelTabHeader, role="tab", or .tab-header class) across shadow DOM boundaries,
|
|
96
|
+
* which should be excluded from commenting.
|
|
97
|
+
*
|
|
98
|
+
* @param element The element to check.
|
|
99
|
+
* @returns True if the element or any of its ancestors is a tab title; otherwise false.
|
|
100
|
+
*/
|
|
62
101
|
export function isTabTitle(element: Element): boolean {
|
|
63
102
|
let current: Element|null = element;
|
|
64
103
|
while (current) {
|
|
@@ -84,19 +123,100 @@ export function isTabTitle(element: Element): boolean {
|
|
|
84
123
|
return false;
|
|
85
124
|
}
|
|
86
125
|
|
|
126
|
+
interface CodeMirrorLineInfo {
|
|
127
|
+
lineNumber: number;
|
|
128
|
+
textSignature: string;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Resolves line information (1-based line number and trimmed line text) for an element inside a CodeMirror editor.
|
|
133
|
+
*
|
|
134
|
+
* Uses the underlying `CodeMirror.EditorView` and `EditorState.doc` data model directly via
|
|
135
|
+
* `EditorView.findFromDOM()`. This avoids relying on virtualized DOM nodes (`.cm-line` elements)
|
|
136
|
+
* which only exist for the currently visible viewport.
|
|
137
|
+
*
|
|
138
|
+
* @param element The source DOM element inside or on the editor.
|
|
139
|
+
* @returns An object containing the 1-based line number and line text, or null if unresolvable or empty.
|
|
140
|
+
*/
|
|
141
|
+
function resolveCodeMirrorLineInfo(element: Element): CodeMirrorLineInfo|null {
|
|
142
|
+
const cmEditor = element.closest('.cm-editor') as HTMLElement | null;
|
|
143
|
+
if (!cmEditor) {
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
const view = CodeMirror.EditorView.findFromDOM(cmEditor);
|
|
147
|
+
if (!view) {
|
|
148
|
+
throw new Error('Could not find CodeMirror EditorView from .cm-editor element');
|
|
149
|
+
}
|
|
150
|
+
const doc = view.state.doc;
|
|
151
|
+
|
|
152
|
+
// 1. Gutter element clicked (line numbers)
|
|
153
|
+
const gutterEl = element.closest('.cm-gutterElement');
|
|
154
|
+
if (gutterEl) {
|
|
155
|
+
const rawText = gutterEl.textContent?.trim() || '';
|
|
156
|
+
if (rawText.length > 0 && /^\d+$/.test(rawText)) {
|
|
157
|
+
const lineNum = parseInt(rawText, 10);
|
|
158
|
+
if (lineNum > 0 && lineNum <= doc.lines) {
|
|
159
|
+
const line = doc.line(lineNum);
|
|
160
|
+
const textSignature = line.text.trim();
|
|
161
|
+
return textSignature ? {lineNumber: line.number, textSignature} : null;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// 2. Content / line / token clicked: use line element directly or escalate to closest .cm-line
|
|
168
|
+
const cmLine = element.classList.contains('cm-line') ? element : element.closest('.cm-line');
|
|
169
|
+
if (cmLine) {
|
|
170
|
+
try {
|
|
171
|
+
const pos = view.posAtDOM(cmLine);
|
|
172
|
+
const line = doc.lineAt(pos);
|
|
173
|
+
const textSignature = line.text.trim();
|
|
174
|
+
return textSignature ? {lineNumber: line.number, textSignature} : null;
|
|
175
|
+
} catch {
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Resolves an arbitrary clicked or targeted DOM element to its appropriate semantic comment anchor element.
|
|
185
|
+
*
|
|
186
|
+
* Traversal hierarchy:
|
|
187
|
+
* 1. Checks if the element is part of a tab title (returns null if so).
|
|
188
|
+
* 2. Escalates CodeMirror line/gutter elements to .cm-editor (only if the clicked line is non-empty).
|
|
189
|
+
* 3. Checks for domain IDs (`data-network-request-id` or `data-backend-node-id`) across shadow boundaries,
|
|
190
|
+
* returning the owning domain element.
|
|
191
|
+
* 4. Escalates minor controls / sub-elements up to semantic containers (e.g., TableRow, TreeItem).
|
|
192
|
+
* 5. Falls back to the nearest visual logging element if no semantic container is found.
|
|
193
|
+
*
|
|
194
|
+
* @param element The source DOM element to resolve.
|
|
195
|
+
* @returns The resolved semantic anchor Element, or null if unresolvable/empty/excluded.
|
|
196
|
+
*/
|
|
87
197
|
export function resolveCommentAnchorElement(element: Element): Element|null {
|
|
88
198
|
if (isTabTitle(element)) {
|
|
89
199
|
return null;
|
|
90
200
|
}
|
|
201
|
+
// CodeMirror internal lines, gutters, and content live inside .cm-editor.
|
|
202
|
+
// We only allow commenting on non-empty lines within the editor; the whole editor
|
|
203
|
+
// container is never a valid comment target.
|
|
204
|
+
const cmEditor = element.closest('.cm-editor');
|
|
205
|
+
if (cmEditor) {
|
|
206
|
+
const lineInfo = resolveCodeMirrorLineInfo(element);
|
|
207
|
+
if (!lineInfo) {
|
|
208
|
+
return null;
|
|
209
|
+
}
|
|
210
|
+
return cmEditor;
|
|
211
|
+
}
|
|
212
|
+
const domainElement = closestAcrossShadow(element, '[data-network-request-id], [data-backend-node-id]');
|
|
213
|
+
if (domainElement) {
|
|
214
|
+
return isNonEmptyItem(domainElement) ? domainElement : null;
|
|
215
|
+
}
|
|
91
216
|
let target: Element|null = element;
|
|
92
217
|
let fallbackCandidate: Element|null = null;
|
|
93
218
|
|
|
94
219
|
while (target) {
|
|
95
|
-
const hasDomainId = target.hasAttribute('data-network-request-id') || target.hasAttribute('data-backend-node-id');
|
|
96
|
-
|
|
97
|
-
if (hasDomainId) {
|
|
98
|
-
return isNonEmptyItem(target) ? target : null;
|
|
99
|
-
}
|
|
100
220
|
if (VisualLogging.needsLogging(target)) {
|
|
101
221
|
try {
|
|
102
222
|
const config = VisualLogging.getLoggingConfig(target);
|
|
@@ -120,6 +240,312 @@ export function resolveCommentAnchorElement(element: Element): Element|null {
|
|
|
120
240
|
return null;
|
|
121
241
|
}
|
|
122
242
|
|
|
243
|
+
/**
|
|
244
|
+
* Extracts the trailing Visual Element type name from a full visual logging path.
|
|
245
|
+
* Used as a fast pre-filter optimization before calculating full ancestor VE paths.
|
|
246
|
+
*
|
|
247
|
+
* @param vePath The full visual logging path string (e.g. "Panel: elements > TreeItem: rule").
|
|
248
|
+
* @returns The trailing VE type name (e.g. "TreeItem").
|
|
249
|
+
*/
|
|
250
|
+
export function extractVeName(vePath: string): string {
|
|
251
|
+
return vePath.split(' > ').pop()?.split(':')[0]?.trim() || '';
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Checks if an element matches the given visual logging path.
|
|
256
|
+
*
|
|
257
|
+
* @param element The DOM element to test.
|
|
258
|
+
* @param vePath The expected visual logging path.
|
|
259
|
+
* @param targetVeName Optional trailing VE name used as a fast pre-filter optimization to reject
|
|
260
|
+
* non-matching elements without performing an expensive full DOM ancestor traversal in `VisualLogging.getVePath`.
|
|
261
|
+
* @returns True if the element's VE path matches vePath; otherwise false.
|
|
262
|
+
*/
|
|
263
|
+
export function matchesVePath(element: Element, vePath: string, targetVeName: string = extractVeName(vePath)): boolean {
|
|
264
|
+
if (!VisualLogging.needsLogging(element)) {
|
|
265
|
+
return false;
|
|
266
|
+
}
|
|
267
|
+
// Optimization: fast pre-filter on local jslog attribute to avoid full getVePath() ancestor tree traversal
|
|
268
|
+
const jslog = element.getAttribute('jslog');
|
|
269
|
+
if (targetVeName && jslog) {
|
|
270
|
+
const match = jslog.trim().match(/^([a-zA-Z0-9_-]+)/);
|
|
271
|
+
if (!match || match[1] !== targetVeName) {
|
|
272
|
+
return false;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
// Authoritative check
|
|
276
|
+
return VisualLogging.getVePath(element) === vePath;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Computes the 0-indexed position of an element among all elements sharing the same visual logging path
|
|
281
|
+
* in document order across light and shadow DOM trees.
|
|
282
|
+
*
|
|
283
|
+
* @param element The target element.
|
|
284
|
+
* @param vePath The visual logging path to match.
|
|
285
|
+
* @param root The root Document or Element to search within (defaults to element's ownerDocument or document).
|
|
286
|
+
* @returns The 0-based index among VE siblings.
|
|
287
|
+
*/
|
|
288
|
+
export function getSiblingIndex(element: Element, vePath: string,
|
|
289
|
+
root: Document|Element = element.ownerDocument || document): number {
|
|
290
|
+
const targetVeName = extractVeName(vePath);
|
|
291
|
+
const allJslog = deepQuerySelectorAll(root, '[jslog]');
|
|
292
|
+
let index = 0;
|
|
293
|
+
for (const el of allJslog) {
|
|
294
|
+
if (el === element) {
|
|
295
|
+
return index;
|
|
296
|
+
}
|
|
297
|
+
if (matchesVePath(el, vePath, targetVeName)) {
|
|
298
|
+
index++;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
return index;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Checks whether a CodeMirror editor matches a specific line number and text signature.
|
|
306
|
+
*
|
|
307
|
+
* Uses `EditorView.findFromDOM()` to inspect the document model in memory rather than
|
|
308
|
+
* querying virtualized DOM line nodes.
|
|
309
|
+
*
|
|
310
|
+
* @param editor The `.cm-editor` element.
|
|
311
|
+
* @param editorLineNumber The expected 1-based line number.
|
|
312
|
+
* @param textSignature The expected text content of the line.
|
|
313
|
+
* @returns True if the editor contains the line matching both line number and text signature; otherwise false.
|
|
314
|
+
*/
|
|
315
|
+
function checkCodeMirrorLineMatch(editor: Element, editorLineNumber: number, textSignature: string): boolean {
|
|
316
|
+
const view = CodeMirror.EditorView.findFromDOM(editor as HTMLElement);
|
|
317
|
+
if (!view) {
|
|
318
|
+
return false;
|
|
319
|
+
}
|
|
320
|
+
const doc = view.state.doc;
|
|
321
|
+
if (editorLineNumber <= 0 || editorLineNumber > doc.lines) {
|
|
322
|
+
return false;
|
|
323
|
+
}
|
|
324
|
+
const line = doc.line(editorLineNumber);
|
|
325
|
+
return line.text.trim() === textSignature;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Resolves a DOM element to a robust, serializable `CommentAnchorSignature`.
|
|
330
|
+
*
|
|
331
|
+
* The signature captures visual logging paths, text content, sibling index disambiguation,
|
|
332
|
+
* domain IDs (`networkRequestId`, `backendNodeId`), and CodeMirror editor coordinates to allow
|
|
333
|
+
* resilient rematching across DOM re-renders, filtering, and DevTools sessions.
|
|
334
|
+
*
|
|
335
|
+
* @param element The source DOM element to resolve into an anchor signature.
|
|
336
|
+
* @param root Optional root Document or Element to search within for sibling index calculation.
|
|
337
|
+
* @returns The resolved CommentAnchorSignature, or null if unresolvable.
|
|
338
|
+
*/
|
|
339
|
+
export function resolveCommentAnchor(
|
|
340
|
+
element: Element, root: Document|Element = element.ownerDocument || document): CommentAnchorSignature|null {
|
|
341
|
+
const target = resolveCommentAnchorElement(element);
|
|
342
|
+
if (!target) {
|
|
343
|
+
return null;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// 1. Construct vePath
|
|
347
|
+
const vePath = VisualLogging.getVePath(target);
|
|
348
|
+
if (!vePath) {
|
|
349
|
+
return null;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// 2. Extract text signature and parent text signature
|
|
353
|
+
const isEditorTarget = isCodeMirrorEditor(target);
|
|
354
|
+
let textSignature: string;
|
|
355
|
+
let parentTextSignature: string|undefined;
|
|
356
|
+
let editor: EditorAnchorSignature|undefined;
|
|
357
|
+
|
|
358
|
+
if (isEditorTarget) {
|
|
359
|
+
const lineInfo = resolveCodeMirrorLineInfo(element);
|
|
360
|
+
if (!lineInfo) {
|
|
361
|
+
return null;
|
|
362
|
+
}
|
|
363
|
+
textSignature = lineInfo.textSignature;
|
|
364
|
+
const filePath = target.getAttribute('data-file-path') ?? undefined;
|
|
365
|
+
editor = {lineNumber: lineInfo.lineNumber, filePath};
|
|
366
|
+
} else {
|
|
367
|
+
textSignature = target.deepTextContent();
|
|
368
|
+
const parentEl = target.parentElementOrShadowHost();
|
|
369
|
+
parentTextSignature = parentEl ? parentEl.deepTextContent() : undefined;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// 3. Calculate sibling index among elements with same vePath in document order
|
|
373
|
+
const siblingIndex = getSiblingIndex(target, vePath, root);
|
|
374
|
+
|
|
375
|
+
// 4. Extract optional domain IDs directly from the resolved target element
|
|
376
|
+
const networkRequestId = target.getAttribute('data-network-request-id') ?? undefined;
|
|
377
|
+
|
|
378
|
+
const backendNodeIdStr = target.getAttribute('data-backend-node-id');
|
|
379
|
+
const backendNodeId = backendNodeIdStr ? Number(backendNodeIdStr) : undefined;
|
|
380
|
+
|
|
381
|
+
return {
|
|
382
|
+
vePath,
|
|
383
|
+
textSignature,
|
|
384
|
+
parentTextSignature,
|
|
385
|
+
siblingIndex,
|
|
386
|
+
networkRequestId,
|
|
387
|
+
backendNodeId,
|
|
388
|
+
editor,
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* Searches a document or element tree (recursively traversing all Shadow DOM roots)
|
|
394
|
+
* and returns all matching descendant elements up to the specified limit in document order.
|
|
395
|
+
*
|
|
396
|
+
* Note: The root container itself is not matched against selector; only descendants are returned.
|
|
397
|
+
*
|
|
398
|
+
* @param root The root Document or Element to search from.
|
|
399
|
+
* @param selector The CSS selector to match against.
|
|
400
|
+
* @param limit Maximum number of matching elements to return (defaults to Infinity).
|
|
401
|
+
* @returns Array of matching Elements in document order.
|
|
402
|
+
*/
|
|
403
|
+
export function deepQuerySelectorAll(root: Document|Element, selector: string, limit = Infinity): Element[] {
|
|
404
|
+
const results: Element[] = [];
|
|
405
|
+
if (limit <= 0 || Number.isNaN(limit)) {
|
|
406
|
+
return results;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
function collectFromContainer(container: Document|Element|ShadowRoot): boolean {
|
|
410
|
+
if (container instanceof Element && container.shadowRoot) {
|
|
411
|
+
if (collectFromContainer(container.shadowRoot)) {
|
|
412
|
+
return true;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
let child = container.firstElementChild;
|
|
416
|
+
while (child) {
|
|
417
|
+
if (child.matches(selector)) {
|
|
418
|
+
results.push(child);
|
|
419
|
+
if (results.length >= limit) {
|
|
420
|
+
return true;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
if (collectFromContainer(child)) {
|
|
424
|
+
return true;
|
|
425
|
+
}
|
|
426
|
+
child = child.nextElementSibling;
|
|
427
|
+
}
|
|
428
|
+
return false;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
collectFromContainer(root);
|
|
432
|
+
return results;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* Finds the first matching descendant element across light and shadow DOM trees.
|
|
437
|
+
*
|
|
438
|
+
* @param root The root Document or Element to search from.
|
|
439
|
+
* @param selector The CSS selector to match against.
|
|
440
|
+
* @returns The first matching Element or null if none is found.
|
|
441
|
+
*/
|
|
442
|
+
export function deepQuerySelector(root: Document|Element, selector: string): Element|null {
|
|
443
|
+
return deepQuerySelectorAll(root, selector, 1)[0] ?? null;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
/**
|
|
447
|
+
* Rematches a stored comment thread to its live corresponding DOM element.
|
|
448
|
+
*
|
|
449
|
+
* Matching pipeline:
|
|
450
|
+
* 1. Primary fast-path: Query by domain IDs (`networkRequestId` or `backendNodeId`) across shadow roots.
|
|
451
|
+
* 2. CodeMirror editor line match: Match editor and line number/text, scoped by `filePath` if present.
|
|
452
|
+
* 3. Visual logging path fallback: Find all candidate elements matching `vePath`.
|
|
453
|
+
* 4. Text content refinement: Filter candidates by `textSignature` and `parentTextSignature`.
|
|
454
|
+
* 5. Sibling index disambiguation: Match exact sibling position when multiple candidates exist.
|
|
455
|
+
*
|
|
456
|
+
* @param comment The comment thread containing the anchor signature to rematch.
|
|
457
|
+
* @param root The root Document or Element to search within (defaults to document).
|
|
458
|
+
* @param cachedJslogElements Optional pre-collected list of `[jslog]` elements for performance.
|
|
459
|
+
* @returns The rematched live Element, or null if no match is found.
|
|
460
|
+
*/
|
|
461
|
+
export function rematchCommentAnchor(comment: CommentThread, root: Document|Element = document,
|
|
462
|
+
cachedJslogElements?: Element[]): Element|null {
|
|
463
|
+
const {anchor} = comment;
|
|
464
|
+
|
|
465
|
+
// Step 1: Primary domain ID fast-path (shadow-piercing data attribute query)
|
|
466
|
+
if (anchor.networkRequestId) {
|
|
467
|
+
return deepQuerySelector(root, `[data-network-request-id="${CSS.escape(anchor.networkRequestId)}"]`);
|
|
468
|
+
}
|
|
469
|
+
if (anchor.backendNodeId !== undefined) {
|
|
470
|
+
return deepQuerySelector(root, `[data-backend-node-id="${CSS.escape(String(anchor.backendNodeId))}"]`);
|
|
471
|
+
}
|
|
472
|
+
if (anchor.editor) {
|
|
473
|
+
const {lineNumber, filePath} = anchor.editor;
|
|
474
|
+
const cmEditors =
|
|
475
|
+
cachedJslogElements ? cachedJslogElements.filter(isCodeMirrorEditor) : deepQuerySelectorAll(root, '.cm-editor');
|
|
476
|
+
const matchingEditors = cmEditors.filter(cmEditor => {
|
|
477
|
+
if (filePath !== undefined && cmEditor.getAttribute('data-file-path') !== filePath) {
|
|
478
|
+
return false;
|
|
479
|
+
}
|
|
480
|
+
return VisualLogging.getVePath(cmEditor) === anchor.vePath;
|
|
481
|
+
});
|
|
482
|
+
for (const cmEditor of matchingEditors) {
|
|
483
|
+
if (checkCodeMirrorLineMatch(cmEditor, lineNumber, anchor.textSignature)) {
|
|
484
|
+
return cmEditor;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
return matchingEditors[0] ?? null;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// Step 2: VE path and deepTextContent fallback (for non-ID controls and console logs)
|
|
491
|
+
const targetVeName = extractVeName(anchor.vePath);
|
|
492
|
+
// Optimization: use cachedJslogElements if provided to avoid re-scanning the entire DOM
|
|
493
|
+
const allJslog = cachedJslogElements || deepQuerySelectorAll(root, '[jslog]');
|
|
494
|
+
const candidates = allJslog.filter(el => matchesVePath(el, anchor.vePath, targetVeName));
|
|
495
|
+
|
|
496
|
+
if (candidates.length === 0) {
|
|
497
|
+
return null;
|
|
498
|
+
}
|
|
499
|
+
if (candidates.length === 1) {
|
|
500
|
+
return candidates[0];
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
let candidateList = candidates;
|
|
504
|
+
|
|
505
|
+
// Filter by textSignature
|
|
506
|
+
if (anchor.textSignature !== undefined) {
|
|
507
|
+
const textMatches = candidateList.filter(el => el.deepTextContent() === anchor.textSignature);
|
|
508
|
+
if (textMatches.length > 0) {
|
|
509
|
+
candidateList = textMatches;
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
if (candidateList.length === 1) {
|
|
513
|
+
return candidateList[0];
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// Filter by parentTextSignature
|
|
517
|
+
if (anchor.parentTextSignature !== undefined) {
|
|
518
|
+
const parentMatches = candidateList.filter(el => {
|
|
519
|
+
const parentEl = el.parentElementOrShadowHost();
|
|
520
|
+
return parentEl?.deepTextContent() === anchor.parentTextSignature;
|
|
521
|
+
});
|
|
522
|
+
if (parentMatches.length > 0) {
|
|
523
|
+
candidateList = parentMatches;
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
if (candidateList.length === 1) {
|
|
527
|
+
return candidateList[0];
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
// Disambiguate with siblingIndex
|
|
531
|
+
if (anchor.siblingIndex !== undefined) {
|
|
532
|
+
const siblingMatches = candidateList.filter(el => candidates.indexOf(el) === anchor.siblingIndex);
|
|
533
|
+
if (siblingMatches.length > 0) {
|
|
534
|
+
candidateList = siblingMatches;
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// Step 3: Single-Element Canonicalization (return the first matching node in document order)
|
|
539
|
+
return candidateList[0] || null;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
/**
|
|
543
|
+
* Checks whether an element is connected to the DOM, visible according to `checkVisibility()`,
|
|
544
|
+
* and has non-zero bounding box dimensions.
|
|
545
|
+
*
|
|
546
|
+
* @param element The element to check visibility for.
|
|
547
|
+
* @returns True if the element is connected and rendered with non-zero size; otherwise false.
|
|
548
|
+
*/
|
|
123
549
|
export function isElementVisible(element: Element): boolean {
|
|
124
550
|
if (!element.isConnected) {
|
|
125
551
|
return false;
|