chrome-devtools-frontend 1.0.1703574 → 1.0.1705227
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/AUTHORS +2 -0
- package/docs/architecture_of_devtools.md +1 -1
- package/docs/cookbook/localization.md +2 -2
- package/front_end/core/common/ParsedURL.ts +54 -0
- package/front_end/core/common/README.md +4 -3
- package/front_end/core/platform/StringUtilities.ts +65 -0
- package/front_end/core/sdk/CPUThrottlingManager.ts +3 -3
- package/front_end/core/sdk/SourceMap.ts +3 -3
- package/front_end/core/sdk/SourceMapScopeChainEntry.ts +4 -3
- package/front_end/core/sdk/SourceMapScopesInfo.ts +17 -5
- package/front_end/entrypoints/formatter_actions/formatter_actions.ts +18 -0
- package/front_end/entrypoints/formatter_worker/Substitute.ts +9 -8
- package/front_end/entrypoints/formatter_worker/formatter_worker-entrypoint.ts +2 -2
- package/front_end/entrypoints/main/MainImpl.ts +3 -3
- package/front_end/foundation/Universe.ts +9 -0
- package/front_end/generated/InspectorBackendCommands.ts +3 -2
- package/front_end/generated/protocol.ts +16 -0
- package/front_end/models/bindings/CompilerScriptMapping.ts +1 -1
- package/front_end/models/formatter/FormatterWorkerPool.ts +8 -3
- package/front_end/models/har/HARFormat.ts +7 -2
- package/front_end/models/source_map_scopes/NamesResolver.ts +75 -38
- package/front_end/models/source_map_scopes/ScopeChainModel.ts +13 -45
- package/front_end/models/source_map_scopes/ScopeChainResolver.ts +101 -0
- package/front_end/models/source_map_scopes/source_map_scopes.ts +2 -0
- package/front_end/panels/accessibility/AccessibilityAnnouncementRecordingListView.ts +33 -1
- package/front_end/panels/accessibility/AccessibilityAnnouncementRecordingView.ts +4 -12
- package/front_end/panels/application/ApplicationPanelSidebar.ts +58 -60
- package/front_end/panels/application/BackForwardCacheTreeElement.ts +1 -1
- package/front_end/panels/application/FrameDetailsView.ts +64 -82
- package/front_end/panels/application/OpenedWindowDetailsView.ts +20 -21
- package/front_end/panels/application/PreloadingTreeElement.ts +3 -3
- package/front_end/panels/common/CommentsOverlayWidget.ts +145 -72
- package/front_end/panels/common/commentsOverlay.css +34 -23
- package/front_end/panels/console/ConsolePrompt.ts +2 -1
- package/front_end/panels/elements/DOMTreeWidget.ts +29 -10
- package/front_end/panels/network/RequestPayloadView.ts +7 -2
- package/front_end/panels/network/ShowMoreDetailsWidget.ts +12 -9
- package/front_end/panels/sources/DebuggerPlugin.ts +52 -20
- package/front_end/panels/sources/ScopeChainSidebarPane.ts +2 -2
- package/front_end/panels/sources/SourcesPanel.ts +1 -1
- package/front_end/panels/sources/SourcesView.ts +3 -12
- package/front_end/panels/sources/TabbedEditorContainer.ts +0 -3
- package/front_end/panels/sources/components/HeadersView.ts +193 -123
- package/front_end/third_party/chromium/README.chromium +1 -1
- package/front_end/third_party/puppeteer/README.chromium +2 -2
- package/front_end/third_party/puppeteer/package/lib/es5-iife/puppeteer-core-browser.d.ts +5 -0
- package/front_end/third_party/puppeteer/package/lib/es5-iife/puppeteer-core-browser.js +24 -32
- package/front_end/third_party/puppeteer/package/lib/puppeteer/api/ElementHandle.d.ts +1 -1
- package/front_end/third_party/puppeteer/package/lib/puppeteer/api/ElementHandle.d.ts.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/puppeteer/api/ElementHandle.js +15 -1
- package/front_end/third_party/puppeteer/package/lib/puppeteer/api/ElementHandle.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/Accessibility.js +8 -1
- package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/Accessibility.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/puppeteer/common/Debug.d.ts +0 -8
- package/front_end/third_party/puppeteer/package/lib/puppeteer/common/Debug.d.ts.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/puppeteer/common/Debug.js +0 -24
- package/front_end/third_party/puppeteer/package/lib/puppeteer/common/Debug.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/puppeteer/node/BrowserLauncher.d.ts +12 -0
- package/front_end/third_party/puppeteer/package/lib/puppeteer/node/BrowserLauncher.d.ts.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/puppeteer/node/BrowserLauncher.js +79 -17
- package/front_end/third_party/puppeteer/package/lib/puppeteer/node/BrowserLauncher.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/puppeteer/node-env-setup.js +1 -1
- package/front_end/third_party/puppeteer/package/lib/puppeteer/node-env-setup.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/puppeteer/revisions.d.ts +3 -3
- package/front_end/third_party/puppeteer/package/lib/puppeteer/revisions.js +3 -3
- package/front_end/third_party/puppeteer/package/lib/puppeteer/util/version.d.ts +1 -1
- package/front_end/third_party/puppeteer/package/lib/puppeteer/util/version.js +1 -1
- package/front_end/third_party/puppeteer/package/lib/types.d.ts +5 -0
- package/front_end/third_party/puppeteer/package/package.json +4 -4
- package/front_end/third_party/puppeteer/package/src/api/ElementHandle.ts +15 -2
- package/front_end/third_party/puppeteer/package/src/cdp/Accessibility.ts +10 -1
- package/front_end/third_party/puppeteer/package/src/common/Debug.ts +0 -27
- package/front_end/third_party/puppeteer/package/src/node/BrowserLauncher.ts +96 -17
- package/front_end/third_party/puppeteer/package/src/node-env-setup.ts +1 -1
- package/front_end/third_party/puppeteer/package/src/revisions.ts +3 -3
- package/front_end/third_party/puppeteer/package/src/util/version.ts +1 -1
- package/front_end/ui/comments/CommentAnchorResolver.ts +60 -22
- package/front_end/ui/comments/CommentOverlayManager.ts +78 -25
- package/front_end/ui/legacy/README.md +12 -12
- package/front_end/ui/legacy/components/data_grid/DataGridExporter.ts +7 -13
- package/front_end/ui/legacy/components/inline_editor/PositionAreaEditor.ts +51 -57
- package/front_end/ui/legacy/components/source_frame/ImageView.ts +101 -42
- package/front_end/ui/legacy/components/utils/Linkifier.ts +1 -2
- package/front_end/ui/visual_logging/KnownContextValues.ts +7 -0
- package/package.json +1 -1
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import type {Protocol} from 'devtools-protocol';
|
|
8
8
|
|
|
9
9
|
import type {Frame} from '../api/Frame.js';
|
|
10
|
-
import type
|
|
10
|
+
import {DEBUG_PREFIXES, type Logger} from '../common/Debug.js';
|
|
11
11
|
import {getQueryHandlerAndSelector} from '../common/GetQueryHandler.js';
|
|
12
12
|
import {LazyArg} from '../common/LazyArg.js';
|
|
13
13
|
import type {
|
|
@@ -830,20 +830,25 @@ export abstract class ElementHandle<
|
|
|
830
830
|
this: ElementHandle<Element>,
|
|
831
831
|
target: Point | ElementHandle<Element>,
|
|
832
832
|
): Promise<Protocol.Input.DragData | void> {
|
|
833
|
-
await this.scrollIntoViewIfNeeded();
|
|
834
833
|
const page = this.frame.page();
|
|
835
834
|
if (page.isDragInterceptionEnabled()) {
|
|
835
|
+
await this.scrollIntoViewIfNeeded();
|
|
836
836
|
const source = await this.clickablePoint();
|
|
837
837
|
if (target instanceof ElementHandle) {
|
|
838
838
|
target = await target.clickablePoint();
|
|
839
839
|
}
|
|
840
840
|
return await page.mouse.drag(source, target);
|
|
841
841
|
}
|
|
842
|
+
// The button is down either because an earlier `drag()` pressed it, or
|
|
843
|
+
// because this call is about to.
|
|
844
|
+
let isMouseDown = page._isDragging;
|
|
842
845
|
try {
|
|
846
|
+
await this.scrollIntoViewIfNeeded();
|
|
843
847
|
if (!page._isDragging) {
|
|
844
848
|
page._isDragging = true;
|
|
845
849
|
await this.hover();
|
|
846
850
|
await page.mouse.down();
|
|
851
|
+
isMouseDown = true;
|
|
847
852
|
}
|
|
848
853
|
if (target instanceof ElementHandle) {
|
|
849
854
|
await target.hover();
|
|
@@ -852,6 +857,14 @@ export abstract class ElementHandle<
|
|
|
852
857
|
}
|
|
853
858
|
} catch (error) {
|
|
854
859
|
page._isDragging = false;
|
|
860
|
+
if (isMouseDown) {
|
|
861
|
+
// `drop()` is the only thing that releases the button and it will never
|
|
862
|
+
// run now, so without this the button stays pressed for the rest of the
|
|
863
|
+
// session. It must not mask the error that got us here.
|
|
864
|
+
await page.mouse.up().catch(error => {
|
|
865
|
+
this.logger(DEBUG_PREFIXES.error)?.(error);
|
|
866
|
+
});
|
|
867
|
+
}
|
|
855
868
|
throw error;
|
|
856
869
|
}
|
|
857
870
|
}
|
|
@@ -627,7 +627,16 @@ class AXNode {
|
|
|
627
627
|
// Since Text nodes are not elements, we want to
|
|
628
628
|
// return a handle to the parent element for them.
|
|
629
629
|
return (await handle.evaluateHandle(node => {
|
|
630
|
-
|
|
630
|
+
if (node.nodeType !== Node.TEXT_NODE) {
|
|
631
|
+
return node;
|
|
632
|
+
}
|
|
633
|
+
// A text node placed directly in a shadow root has no parent
|
|
634
|
+
// element, so fall back to the shadow host.
|
|
635
|
+
return (
|
|
636
|
+
node.parentElement ??
|
|
637
|
+
(node.parentNode as ShadowRoot | null)?.host ??
|
|
638
|
+
null
|
|
639
|
+
);
|
|
631
640
|
})) as ElementHandle<Element>;
|
|
632
641
|
},
|
|
633
642
|
backendNodeId: this.payload.backendDOMNodeId,
|
|
@@ -109,9 +109,6 @@ export const debug: Logger = (prefix): LoggerFunction | undefined => {
|
|
|
109
109
|
}
|
|
110
110
|
|
|
111
111
|
return (...logArgs: unknown[]) => {
|
|
112
|
-
if (captureLogs) {
|
|
113
|
-
capturedLogs.push(prefix + logArgs);
|
|
114
|
-
}
|
|
115
112
|
(nodeDebug as LoggerFunction)(...logArgs);
|
|
116
113
|
};
|
|
117
114
|
}
|
|
@@ -142,27 +139,3 @@ export const debug: Logger = (prefix): LoggerFunction | undefined => {
|
|
|
142
139
|
console.log(`${prefix}:`, ...logArgs);
|
|
143
140
|
};
|
|
144
141
|
};
|
|
145
|
-
|
|
146
|
-
/**
|
|
147
|
-
* @internal
|
|
148
|
-
*/
|
|
149
|
-
let capturedLogs: string[] = [];
|
|
150
|
-
/**
|
|
151
|
-
* @internal
|
|
152
|
-
*/
|
|
153
|
-
let captureLogs = false;
|
|
154
|
-
|
|
155
|
-
/**
|
|
156
|
-
* @internal
|
|
157
|
-
*/
|
|
158
|
-
export function setLogCapture(value: boolean): void {
|
|
159
|
-
capturedLogs = [];
|
|
160
|
-
captureLogs = value;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/**
|
|
164
|
-
* @internal
|
|
165
|
-
*/
|
|
166
|
-
export function getCapturedLogs(): string[] {
|
|
167
|
-
return capturedLogs;
|
|
168
|
-
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Copyright 2017 Google Inc.
|
|
4
4
|
* SPDX-License-Identifier: Apache-2.0
|
|
5
5
|
*/
|
|
6
|
-
import {accessSync, constants, existsSync} from 'node:fs';
|
|
6
|
+
import {accessSync, constants, existsSync, rmSync} from 'node:fs';
|
|
7
7
|
import {tmpdir} from 'node:os';
|
|
8
8
|
import {join} from 'node:path';
|
|
9
9
|
|
|
@@ -185,9 +185,13 @@ export abstract class BrowserLauncher {
|
|
|
185
185
|
const usePipe = launchArgs.args.includes('--remote-debugging-pipe');
|
|
186
186
|
|
|
187
187
|
const onProcessExit = async () => {
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
188
|
+
try {
|
|
189
|
+
await this.cleanUserDataDir(launchArgs.userDataDir, {
|
|
190
|
+
isTemp: launchArgs.isTempUserDataDir,
|
|
191
|
+
});
|
|
192
|
+
} finally {
|
|
193
|
+
removeTempUserDataDirOnExit?.();
|
|
194
|
+
}
|
|
191
195
|
};
|
|
192
196
|
|
|
193
197
|
if (
|
|
@@ -200,19 +204,36 @@ export abstract class BrowserLauncher {
|
|
|
200
204
|
);
|
|
201
205
|
}
|
|
202
206
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
207
|
+
let removeTempUserDataDirOnExit: (() => void) | undefined;
|
|
208
|
+
|
|
209
|
+
let browserProcess: ReturnType<typeof launch>;
|
|
210
|
+
try {
|
|
211
|
+
browserProcess = launch({
|
|
212
|
+
executablePath: launchArgs.executablePath,
|
|
213
|
+
args: launchArgs.args,
|
|
214
|
+
handleSIGHUP,
|
|
215
|
+
handleSIGTERM,
|
|
216
|
+
handleSIGINT,
|
|
217
|
+
dumpio,
|
|
218
|
+
env,
|
|
219
|
+
pipe: usePipe,
|
|
220
|
+
onExit: onProcessExit,
|
|
221
|
+
signal: options.signal,
|
|
222
|
+
logger: options.logger,
|
|
223
|
+
});
|
|
224
|
+
// Register after @puppeteer/browsers has installed its process-exit
|
|
225
|
+
// dispatcher. That dispatcher kills the browser before this synchronous
|
|
226
|
+
// fallback removes the profile directory.
|
|
227
|
+
removeTempUserDataDirOnExit = launchArgs.isTempUserDataDir
|
|
228
|
+
? registerProcessExitCleanup(launchArgs.userDataDir, this.#logger)
|
|
229
|
+
: undefined;
|
|
230
|
+
} catch (error) {
|
|
231
|
+
removeTempUserDataDirOnExit?.();
|
|
232
|
+
await this.cleanUserDataDir(launchArgs.userDataDir, {
|
|
233
|
+
isTemp: launchArgs.isTempUserDataDir,
|
|
234
|
+
});
|
|
235
|
+
throw error;
|
|
236
|
+
}
|
|
216
237
|
|
|
217
238
|
let browser: Browser;
|
|
218
239
|
let cdpConnection: Connection;
|
|
@@ -652,3 +673,61 @@ export abstract class BrowserLauncher {
|
|
|
652
673
|
return executablePath;
|
|
653
674
|
}
|
|
654
675
|
}
|
|
676
|
+
|
|
677
|
+
interface ProcessExitEmitter {
|
|
678
|
+
once(event: 'exit', listener: () => void): void;
|
|
679
|
+
off(event: 'exit', listener: () => void): void;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
interface ProcessExitCleanupEntry {
|
|
683
|
+
userDataDir: string;
|
|
684
|
+
logger: Logger;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
const processExitCleanupEntries = new WeakMap<
|
|
688
|
+
ProcessExitEmitter,
|
|
689
|
+
{entries: Set<ProcessExitCleanupEntry>; onExit: () => void}
|
|
690
|
+
>();
|
|
691
|
+
|
|
692
|
+
/**
|
|
693
|
+
* Registers a synchronous fallback for removing a temporary profile when the
|
|
694
|
+
* host process exits before the browser process can run its async cleanup.
|
|
695
|
+
*
|
|
696
|
+
* @internal
|
|
697
|
+
*/
|
|
698
|
+
export function registerProcessExitCleanup(
|
|
699
|
+
userDataDir: string,
|
|
700
|
+
logger: Logger,
|
|
701
|
+
processEmitter: ProcessExitEmitter = process,
|
|
702
|
+
): () => void {
|
|
703
|
+
let cleanup = processExitCleanupEntries.get(processEmitter);
|
|
704
|
+
if (!cleanup) {
|
|
705
|
+
const entries = new Set<ProcessExitCleanupEntry>();
|
|
706
|
+
const onExit = (): void => {
|
|
707
|
+
for (const entry of entries) {
|
|
708
|
+
try {
|
|
709
|
+
rmSync(entry.userDataDir, {
|
|
710
|
+
recursive: true,
|
|
711
|
+
force: true,
|
|
712
|
+
maxRetries: 3,
|
|
713
|
+
retryDelay: 100,
|
|
714
|
+
});
|
|
715
|
+
} catch (error) {
|
|
716
|
+
entry.logger(DEBUG_PREFIXES.error)?.(error);
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
};
|
|
720
|
+
cleanup = {entries, onExit};
|
|
721
|
+
processExitCleanupEntries.set(processEmitter, cleanup);
|
|
722
|
+
processEmitter.once('exit', onExit);
|
|
723
|
+
}
|
|
724
|
+
const entry = {userDataDir, logger};
|
|
725
|
+
cleanup.entries.add(entry);
|
|
726
|
+
return () => {
|
|
727
|
+
if (!cleanup?.entries.delete(entry) || cleanup.entries.size > 0) {
|
|
728
|
+
return;
|
|
729
|
+
}
|
|
730
|
+
processEmitter.off('exit', cleanup.onExit);
|
|
731
|
+
processExitCleanupEntries.delete(processEmitter);
|
|
732
|
+
};
|
|
733
|
+
}
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* @internal
|
|
9
9
|
*/
|
|
10
10
|
export const PUPPETEER_REVISIONS = Object.freeze({
|
|
11
|
-
chrome: '
|
|
12
|
-
'chrome-headless-shell': '
|
|
13
|
-
firefox: '
|
|
11
|
+
chrome: '154.0.8037.57',
|
|
12
|
+
'chrome-headless-shell': '154.0.8037.57',
|
|
13
|
+
firefox: 'stable_156.0.1',
|
|
14
14
|
});
|
|
@@ -662,15 +662,57 @@ function isClippingOverflow(overflow: string): boolean {
|
|
|
662
662
|
return overflow === 'hidden' || overflow === 'auto' || overflow === 'scroll' || overflow === 'clip';
|
|
663
663
|
}
|
|
664
664
|
|
|
665
|
+
interface ClippingAncestor {
|
|
666
|
+
element: Element;
|
|
667
|
+
clipsX: boolean;
|
|
668
|
+
clipsY: boolean;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
let clippingAncestorsCache = new WeakMap<Element, ClippingAncestor[]>();
|
|
672
|
+
|
|
673
|
+
/**
|
|
674
|
+
* Clears the cached clipping ancestor chains for elements.
|
|
675
|
+
* Called when DOM mutations or comment rematches occur.
|
|
676
|
+
*/
|
|
677
|
+
export function clearClippingAncestorsCache(): void {
|
|
678
|
+
clippingAncestorsCache = new WeakMap<Element, ClippingAncestor[]>();
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
function getClippingAncestors(element: Element, doc: Document, win: Window): ClippingAncestor[] {
|
|
682
|
+
const cached = clippingAncestorsCache.get(element);
|
|
683
|
+
if (cached && cached.every(item => item.element.isConnected)) {
|
|
684
|
+
return cached;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
const ancestors: ClippingAncestor[] = [];
|
|
688
|
+
let current = element.parentElementOrShadowHost();
|
|
689
|
+
while (current && current !== doc.documentElement && current !== doc.body) {
|
|
690
|
+
const style = win.getComputedStyle(current);
|
|
691
|
+
const clipsX = isClippingOverflow(style.overflowX);
|
|
692
|
+
const clipsY = isClippingOverflow(style.overflowY);
|
|
693
|
+
if (clipsX || clipsY) {
|
|
694
|
+
ancestors.push({element: current, clipsX, clipsY});
|
|
695
|
+
}
|
|
696
|
+
current = current.parentElementOrShadowHost();
|
|
697
|
+
}
|
|
698
|
+
clippingAncestorsCache.set(element, ancestors);
|
|
699
|
+
return ancestors;
|
|
700
|
+
}
|
|
701
|
+
|
|
665
702
|
/**
|
|
666
703
|
* Computes the visible viewport-relative bounding box of an element after clipping against
|
|
667
704
|
* all ancestor scroll/overflow containers and viewport boundaries across shadow DOM roots.
|
|
668
705
|
*
|
|
669
706
|
* @param element The source DOM element.
|
|
670
707
|
* @param targetRect Optional explicit bounding box (e.g. for sub-lines or custom targets).
|
|
708
|
+
* @param rectCache Optional per-frame cache of element bounding client rects to avoid redundant queries.
|
|
671
709
|
* @returns The clipped viewport-relative rectangle or null if the element is completely clipped out of view or invisible.
|
|
672
710
|
*/
|
|
673
|
-
export function computeVisibleRect(
|
|
711
|
+
export function computeVisibleRect(
|
|
712
|
+
element: Element,
|
|
713
|
+
targetRect?: DOMRect,
|
|
714
|
+
rectCache?: Map<Element, DOMRect>,
|
|
715
|
+
): VisibleRect|null {
|
|
674
716
|
if (!element.isConnected) {
|
|
675
717
|
return null;
|
|
676
718
|
}
|
|
@@ -702,29 +744,25 @@ export function computeVisibleRect(element: Element, targetRect?: DOMRect): Visi
|
|
|
702
744
|
return null;
|
|
703
745
|
}
|
|
704
746
|
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
visibleBottom = Math.min(visibleBottom, parentRect.bottom);
|
|
720
|
-
}
|
|
721
|
-
|
|
722
|
-
if (visibleLeft >= visibleRight || visibleTop >= visibleBottom) {
|
|
723
|
-
return null;
|
|
724
|
-
}
|
|
747
|
+
const clippingAncestors = getClippingAncestors(element, doc, win);
|
|
748
|
+
for (const {element: ancestor, clipsX, clipsY} of clippingAncestors) {
|
|
749
|
+
let parentRect = rectCache?.get(ancestor);
|
|
750
|
+
if (!parentRect) {
|
|
751
|
+
parentRect = ancestor.getBoundingClientRect();
|
|
752
|
+
rectCache?.set(ancestor, parentRect);
|
|
753
|
+
}
|
|
754
|
+
if (clipsX) {
|
|
755
|
+
visibleLeft = Math.max(visibleLeft, parentRect.left);
|
|
756
|
+
visibleRight = Math.min(visibleRight, parentRect.right);
|
|
757
|
+
}
|
|
758
|
+
if (clipsY) {
|
|
759
|
+
visibleTop = Math.max(visibleTop, parentRect.top);
|
|
760
|
+
visibleBottom = Math.min(visibleBottom, parentRect.bottom);
|
|
725
761
|
}
|
|
726
762
|
|
|
727
|
-
|
|
763
|
+
if (visibleLeft >= visibleRight || visibleTop >= visibleBottom) {
|
|
764
|
+
return null;
|
|
765
|
+
}
|
|
728
766
|
}
|
|
729
767
|
|
|
730
768
|
const width = visibleRight - visibleLeft;
|
|
@@ -6,6 +6,7 @@ import * as Common from '../../core/common/common.js';
|
|
|
6
6
|
import * as CommentManager from '../../models/comment_manager/comment_manager.js';
|
|
7
7
|
|
|
8
8
|
import {
|
|
9
|
+
clearClippingAncestorsCache,
|
|
9
10
|
closestAcrossShadow,
|
|
10
11
|
COMMENT_THREAD_UI_SELECTOR,
|
|
11
12
|
type CommentAnchorSignature,
|
|
@@ -69,6 +70,7 @@ export interface EventTypes {
|
|
|
69
70
|
[Events.POSITIONS_UPDATED]: {
|
|
70
71
|
pins: PinPositionData[],
|
|
71
72
|
highlights: HighlightRectData[],
|
|
73
|
+
isRealtimeSync?: boolean,
|
|
72
74
|
};
|
|
73
75
|
[Events.HOVER_HIGHLIGHT_CHANGED]: HoverHighlightData|null;
|
|
74
76
|
}
|
|
@@ -113,11 +115,13 @@ export class CommentOverlayManager extends Common.ObjectWrapper.ObjectWrapper<Ev
|
|
|
113
115
|
#scrollListener?: (event: Event) => void;
|
|
114
116
|
#scrollTarget?: EventTarget;
|
|
115
117
|
#scrollRafId?: number;
|
|
118
|
+
#observedScrollRoots = new Set<ShadowRoot>();
|
|
116
119
|
|
|
117
120
|
#devToolsResizeObserver?: ResizeObserver;
|
|
118
|
-
#
|
|
121
|
+
#resizeObservedElements = new WeakSet<Element>();
|
|
119
122
|
|
|
120
123
|
#mutationObserver?: MutationObserver;
|
|
124
|
+
#mutationRafId?: number;
|
|
121
125
|
#rematchTimeoutId?: ReturnType<typeof setTimeout>;
|
|
122
126
|
#cursorElement: HTMLElement|null = null;
|
|
123
127
|
#isCreatingComment = false;
|
|
@@ -162,7 +166,7 @@ export class CommentOverlayManager extends Common.ObjectWrapper.ObjectWrapper<Ev
|
|
|
162
166
|
#getIntersectionObserver(): IntersectionObserver {
|
|
163
167
|
if (!this.#intersectionObserver) {
|
|
164
168
|
this.#intersectionObserver = new IntersectionObserver(() => {
|
|
165
|
-
this.#updatePositions();
|
|
169
|
+
this.#updatePositions(true);
|
|
166
170
|
});
|
|
167
171
|
}
|
|
168
172
|
return this.#intersectionObserver;
|
|
@@ -287,6 +291,7 @@ export class CommentOverlayManager extends Common.ObjectWrapper.ObjectWrapper<Ev
|
|
|
287
291
|
const observer = this.#getIntersectionObserver();
|
|
288
292
|
observer.observe(anchorElement);
|
|
289
293
|
this.#observedThreads.add(anchorElement);
|
|
294
|
+
this.#trackElementAncestors(anchorElement);
|
|
290
295
|
}
|
|
291
296
|
|
|
292
297
|
this.#updatePositions();
|
|
@@ -294,6 +299,25 @@ export class CommentOverlayManager extends Common.ObjectWrapper.ObjectWrapper<Ev
|
|
|
294
299
|
return thread;
|
|
295
300
|
}
|
|
296
301
|
|
|
302
|
+
#trackElementAncestors(element: Element): void {
|
|
303
|
+
if (!this.#devToolsResizeObserver || !this.#scrollListener) {
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
let current: Element|null = element;
|
|
307
|
+
while (current) {
|
|
308
|
+
if (this.#resizeObservedElements.has(current)) {
|
|
309
|
+
break;
|
|
310
|
+
}
|
|
311
|
+
this.#devToolsResizeObserver.observe(current);
|
|
312
|
+
this.#resizeObservedElements.add(current);
|
|
313
|
+
if (current.parentNode instanceof ShadowRoot && !this.#observedScrollRoots.has(current.parentNode)) {
|
|
314
|
+
current.parentNode.addEventListener('scroll', this.#scrollListener, {capture: true, passive: true});
|
|
315
|
+
this.#observedScrollRoots.add(current.parentNode);
|
|
316
|
+
}
|
|
317
|
+
current = current.parentElementOrShadowHost();
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
297
321
|
#resolveAnchor(element: Element, options?: {clientX: number, clientY: number}):
|
|
298
322
|
{anchor: CommentAnchorSignature, anchorElement: Element}|null {
|
|
299
323
|
let anchorElement: Element|null = null;
|
|
@@ -355,6 +379,7 @@ export class CommentOverlayManager extends Common.ObjectWrapper.ObjectWrapper<Ev
|
|
|
355
379
|
* during batch rematching, and cleans up unobserved IntersectionObserver nodes in O(N) time.
|
|
356
380
|
*/
|
|
357
381
|
#rematchAllComments(root: Document|Element = document): void {
|
|
382
|
+
clearClippingAncestorsCache();
|
|
358
383
|
const jslogElements = deepQuerySelectorAll(root, '[jslog]');
|
|
359
384
|
const oldElements = new Set<Element>();
|
|
360
385
|
const newElements = new Set<Element>();
|
|
@@ -392,13 +417,14 @@ export class CommentOverlayManager extends Common.ObjectWrapper.ObjectWrapper<Ev
|
|
|
392
417
|
this.#updatePositions();
|
|
393
418
|
}
|
|
394
419
|
|
|
395
|
-
#updatePositions(): void {
|
|
420
|
+
#updatePositions(isRealtimeSync = false): void {
|
|
396
421
|
const scrollX = window.scrollX;
|
|
397
422
|
const scrollY = window.scrollY;
|
|
398
423
|
|
|
399
424
|
const newPins: PinPositionData[] = [];
|
|
400
425
|
const newHighlights: HighlightRectData[] = [];
|
|
401
426
|
const elementPinCounts = new Map<Element, number>();
|
|
427
|
+
const rectCache = new Map<Element, DOMRect>();
|
|
402
428
|
|
|
403
429
|
for (const thread of this.#commentManager.getCommentThreads()) {
|
|
404
430
|
// Non-DOM anchors have their positions managed by their respective panels,
|
|
@@ -423,8 +449,9 @@ export class CommentOverlayManager extends Common.ObjectWrapper.ObjectWrapper<Ev
|
|
|
423
449
|
observer.observe(el);
|
|
424
450
|
this.#observedThreads.add(el);
|
|
425
451
|
}
|
|
452
|
+
this.#trackElementAncestors(el);
|
|
426
453
|
|
|
427
|
-
const visibleRect = computeVisibleRect(el);
|
|
454
|
+
const visibleRect = computeVisibleRect(el, undefined, rectCache);
|
|
428
455
|
if (!visibleRect) {
|
|
429
456
|
continue;
|
|
430
457
|
}
|
|
@@ -458,6 +485,7 @@ export class CommentOverlayManager extends Common.ObjectWrapper.ObjectWrapper<Ev
|
|
|
458
485
|
this.dispatchEventToListeners(Events.POSITIONS_UPDATED, {
|
|
459
486
|
pins: newPins,
|
|
460
487
|
highlights: newHighlights,
|
|
488
|
+
isRealtimeSync,
|
|
461
489
|
});
|
|
462
490
|
}
|
|
463
491
|
|
|
@@ -492,6 +520,7 @@ export class CommentOverlayManager extends Common.ObjectWrapper.ObjectWrapper<Ev
|
|
|
492
520
|
this.#installScrollListener(scrollTarget);
|
|
493
521
|
this.#installResizeObserver(resizeTarget);
|
|
494
522
|
this.#installMutationObserver(root);
|
|
523
|
+
this.#updatePositions();
|
|
495
524
|
}
|
|
496
525
|
|
|
497
526
|
/**
|
|
@@ -499,6 +528,7 @@ export class CommentOverlayManager extends Common.ObjectWrapper.ObjectWrapper<Ev
|
|
|
499
528
|
*/
|
|
500
529
|
stop(): void {
|
|
501
530
|
document.body.style.cursor = '';
|
|
531
|
+
clearClippingAncestorsCache();
|
|
502
532
|
this.clearDraftThreads();
|
|
503
533
|
this.#removeClickListener();
|
|
504
534
|
this.#removeScrollListener();
|
|
@@ -693,22 +723,36 @@ export class CommentOverlayManager extends Common.ObjectWrapper.ObjectWrapper<Ev
|
|
|
693
723
|
#installScrollListener(target: EventTarget = window): void {
|
|
694
724
|
this.#removeScrollListener();
|
|
695
725
|
this.#scrollTarget = target;
|
|
726
|
+
let needsTrailingUpdate = false;
|
|
696
727
|
this.#scrollListener = (): void => {
|
|
697
728
|
this.#clearHover();
|
|
698
|
-
if (this.#scrollRafId
|
|
699
|
-
|
|
729
|
+
if (this.#scrollRafId === undefined) {
|
|
730
|
+
// Leading-edge synchronous update so pins and highlights move immediately in the current frame.
|
|
731
|
+
this.#updatePositions(true);
|
|
732
|
+
needsTrailingUpdate = false;
|
|
733
|
+
this.#scrollRafId = requestAnimationFrame(() => {
|
|
734
|
+
this.#scrollRafId = undefined;
|
|
735
|
+
if (needsTrailingUpdate) {
|
|
736
|
+
needsTrailingUpdate = false;
|
|
737
|
+
this.#updatePositions(true);
|
|
738
|
+
}
|
|
739
|
+
});
|
|
740
|
+
} else {
|
|
741
|
+
needsTrailingUpdate = true;
|
|
700
742
|
}
|
|
701
|
-
this.#scrollRafId = requestAnimationFrame(() => {
|
|
702
|
-
this.#scrollRafId = undefined;
|
|
703
|
-
this.#updatePositions();
|
|
704
|
-
});
|
|
705
743
|
};
|
|
706
744
|
target.addEventListener('scroll', this.#scrollListener, {capture: true, passive: true});
|
|
707
745
|
}
|
|
708
746
|
|
|
709
747
|
#removeScrollListener(): void {
|
|
710
|
-
if (this.#
|
|
711
|
-
this.#scrollTarget
|
|
748
|
+
if (this.#scrollListener) {
|
|
749
|
+
if (this.#scrollTarget) {
|
|
750
|
+
this.#scrollTarget.removeEventListener('scroll', this.#scrollListener, {capture: true});
|
|
751
|
+
}
|
|
752
|
+
for (const root of this.#observedScrollRoots) {
|
|
753
|
+
root.removeEventListener('scroll', this.#scrollListener, {capture: true});
|
|
754
|
+
}
|
|
755
|
+
this.#observedScrollRoots.clear();
|
|
712
756
|
this.#scrollListener = undefined;
|
|
713
757
|
this.#scrollTarget = undefined;
|
|
714
758
|
}
|
|
@@ -729,15 +773,10 @@ export class CommentOverlayManager extends Common.ObjectWrapper.ObjectWrapper<Ev
|
|
|
729
773
|
this.#removeResizeObserver();
|
|
730
774
|
this.#devToolsResizeObserver = new ResizeObserver(() => {
|
|
731
775
|
this.#clearHover();
|
|
732
|
-
|
|
733
|
-
cancelAnimationFrame(this.#resizeRafId);
|
|
734
|
-
}
|
|
735
|
-
this.#resizeRafId = requestAnimationFrame(() => {
|
|
736
|
-
this.#resizeRafId = undefined;
|
|
737
|
-
this.#updatePositions();
|
|
738
|
-
});
|
|
776
|
+
this.#updatePositions(true);
|
|
739
777
|
});
|
|
740
778
|
this.#devToolsResizeObserver.observe(element);
|
|
779
|
+
this.#resizeObservedElements.add(element);
|
|
741
780
|
}
|
|
742
781
|
|
|
743
782
|
#removeResizeObserver(): void {
|
|
@@ -745,16 +784,19 @@ export class CommentOverlayManager extends Common.ObjectWrapper.ObjectWrapper<Ev
|
|
|
745
784
|
this.#devToolsResizeObserver.disconnect();
|
|
746
785
|
this.#devToolsResizeObserver = undefined;
|
|
747
786
|
}
|
|
748
|
-
|
|
749
|
-
cancelAnimationFrame(this.#resizeRafId);
|
|
750
|
-
this.#resizeRafId = undefined;
|
|
751
|
-
}
|
|
787
|
+
this.#resizeObservedElements = new WeakSet();
|
|
752
788
|
}
|
|
753
789
|
|
|
754
790
|
/**
|
|
755
791
|
* Debounces rematching of comments across dynamic DOM updates.
|
|
756
792
|
*/
|
|
757
793
|
#scheduleRematch(root: Document|Element = document): void {
|
|
794
|
+
if (this.#mutationRafId === undefined) {
|
|
795
|
+
this.#mutationRafId = requestAnimationFrame(() => {
|
|
796
|
+
this.#mutationRafId = undefined;
|
|
797
|
+
this.#updatePositions(true);
|
|
798
|
+
});
|
|
799
|
+
}
|
|
758
800
|
if (this.#rematchTimeoutId) {
|
|
759
801
|
clearTimeout(this.#rematchTimeoutId);
|
|
760
802
|
}
|
|
@@ -774,8 +816,15 @@ export class CommentOverlayManager extends Common.ObjectWrapper.ObjectWrapper<Ev
|
|
|
774
816
|
*/
|
|
775
817
|
#installMutationObserver(root: Document|Element = document): void {
|
|
776
818
|
this.#removeMutationObserver();
|
|
777
|
-
this.#mutationObserver = new MutationObserver(
|
|
778
|
-
|
|
819
|
+
this.#mutationObserver = new MutationObserver(records => {
|
|
820
|
+
const hasExternalMutation = records.some(record => {
|
|
821
|
+
const target = record.target;
|
|
822
|
+
const element = target instanceof Element ? target : target.parentElement;
|
|
823
|
+
return !(element && element.closest('.comments-overlay-container'));
|
|
824
|
+
});
|
|
825
|
+
if (hasExternalMutation) {
|
|
826
|
+
this.#scheduleRematch(root);
|
|
827
|
+
}
|
|
779
828
|
});
|
|
780
829
|
const targetNode = root instanceof Document ? (root.body || root.documentElement) : root;
|
|
781
830
|
this.#mutationObserver.observe(targetNode, {
|
|
@@ -797,6 +846,10 @@ export class CommentOverlayManager extends Common.ObjectWrapper.ObjectWrapper<Ev
|
|
|
797
846
|
this.#mutationObserver.disconnect();
|
|
798
847
|
this.#mutationObserver = undefined;
|
|
799
848
|
}
|
|
849
|
+
if (this.#mutationRafId !== undefined) {
|
|
850
|
+
cancelAnimationFrame(this.#mutationRafId);
|
|
851
|
+
this.#mutationRafId = undefined;
|
|
852
|
+
}
|
|
800
853
|
if (this.#rematchTimeoutId) {
|
|
801
854
|
clearTimeout(this.#rematchTimeoutId);
|
|
802
855
|
this.#rematchTimeoutId = undefined;
|