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.
Files changed (85) hide show
  1. package/AUTHORS +2 -0
  2. package/docs/architecture_of_devtools.md +1 -1
  3. package/docs/cookbook/localization.md +2 -2
  4. package/front_end/core/common/ParsedURL.ts +54 -0
  5. package/front_end/core/common/README.md +4 -3
  6. package/front_end/core/platform/StringUtilities.ts +65 -0
  7. package/front_end/core/sdk/CPUThrottlingManager.ts +3 -3
  8. package/front_end/core/sdk/SourceMap.ts +3 -3
  9. package/front_end/core/sdk/SourceMapScopeChainEntry.ts +4 -3
  10. package/front_end/core/sdk/SourceMapScopesInfo.ts +17 -5
  11. package/front_end/entrypoints/formatter_actions/formatter_actions.ts +18 -0
  12. package/front_end/entrypoints/formatter_worker/Substitute.ts +9 -8
  13. package/front_end/entrypoints/formatter_worker/formatter_worker-entrypoint.ts +2 -2
  14. package/front_end/entrypoints/main/MainImpl.ts +3 -3
  15. package/front_end/foundation/Universe.ts +9 -0
  16. package/front_end/generated/InspectorBackendCommands.ts +3 -2
  17. package/front_end/generated/protocol.ts +16 -0
  18. package/front_end/models/bindings/CompilerScriptMapping.ts +1 -1
  19. package/front_end/models/formatter/FormatterWorkerPool.ts +8 -3
  20. package/front_end/models/har/HARFormat.ts +7 -2
  21. package/front_end/models/source_map_scopes/NamesResolver.ts +75 -38
  22. package/front_end/models/source_map_scopes/ScopeChainModel.ts +13 -45
  23. package/front_end/models/source_map_scopes/ScopeChainResolver.ts +101 -0
  24. package/front_end/models/source_map_scopes/source_map_scopes.ts +2 -0
  25. package/front_end/panels/accessibility/AccessibilityAnnouncementRecordingListView.ts +33 -1
  26. package/front_end/panels/accessibility/AccessibilityAnnouncementRecordingView.ts +4 -12
  27. package/front_end/panels/application/ApplicationPanelSidebar.ts +58 -60
  28. package/front_end/panels/application/BackForwardCacheTreeElement.ts +1 -1
  29. package/front_end/panels/application/FrameDetailsView.ts +64 -82
  30. package/front_end/panels/application/OpenedWindowDetailsView.ts +20 -21
  31. package/front_end/panels/application/PreloadingTreeElement.ts +3 -3
  32. package/front_end/panels/common/CommentsOverlayWidget.ts +145 -72
  33. package/front_end/panels/common/commentsOverlay.css +34 -23
  34. package/front_end/panels/console/ConsolePrompt.ts +2 -1
  35. package/front_end/panels/elements/DOMTreeWidget.ts +29 -10
  36. package/front_end/panels/network/RequestPayloadView.ts +7 -2
  37. package/front_end/panels/network/ShowMoreDetailsWidget.ts +12 -9
  38. package/front_end/panels/sources/DebuggerPlugin.ts +52 -20
  39. package/front_end/panels/sources/ScopeChainSidebarPane.ts +2 -2
  40. package/front_end/panels/sources/SourcesPanel.ts +1 -1
  41. package/front_end/panels/sources/SourcesView.ts +3 -12
  42. package/front_end/panels/sources/TabbedEditorContainer.ts +0 -3
  43. package/front_end/panels/sources/components/HeadersView.ts +193 -123
  44. package/front_end/third_party/chromium/README.chromium +1 -1
  45. package/front_end/third_party/puppeteer/README.chromium +2 -2
  46. package/front_end/third_party/puppeteer/package/lib/es5-iife/puppeteer-core-browser.d.ts +5 -0
  47. package/front_end/third_party/puppeteer/package/lib/es5-iife/puppeteer-core-browser.js +24 -32
  48. package/front_end/third_party/puppeteer/package/lib/puppeteer/api/ElementHandle.d.ts +1 -1
  49. package/front_end/third_party/puppeteer/package/lib/puppeteer/api/ElementHandle.d.ts.map +1 -1
  50. package/front_end/third_party/puppeteer/package/lib/puppeteer/api/ElementHandle.js +15 -1
  51. package/front_end/third_party/puppeteer/package/lib/puppeteer/api/ElementHandle.js.map +1 -1
  52. package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/Accessibility.js +8 -1
  53. package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/Accessibility.js.map +1 -1
  54. package/front_end/third_party/puppeteer/package/lib/puppeteer/common/Debug.d.ts +0 -8
  55. package/front_end/third_party/puppeteer/package/lib/puppeteer/common/Debug.d.ts.map +1 -1
  56. package/front_end/third_party/puppeteer/package/lib/puppeteer/common/Debug.js +0 -24
  57. package/front_end/third_party/puppeteer/package/lib/puppeteer/common/Debug.js.map +1 -1
  58. package/front_end/third_party/puppeteer/package/lib/puppeteer/node/BrowserLauncher.d.ts +12 -0
  59. package/front_end/third_party/puppeteer/package/lib/puppeteer/node/BrowserLauncher.d.ts.map +1 -1
  60. package/front_end/third_party/puppeteer/package/lib/puppeteer/node/BrowserLauncher.js +79 -17
  61. package/front_end/third_party/puppeteer/package/lib/puppeteer/node/BrowserLauncher.js.map +1 -1
  62. package/front_end/third_party/puppeteer/package/lib/puppeteer/node-env-setup.js +1 -1
  63. package/front_end/third_party/puppeteer/package/lib/puppeteer/node-env-setup.js.map +1 -1
  64. package/front_end/third_party/puppeteer/package/lib/puppeteer/revisions.d.ts +3 -3
  65. package/front_end/third_party/puppeteer/package/lib/puppeteer/revisions.js +3 -3
  66. package/front_end/third_party/puppeteer/package/lib/puppeteer/util/version.d.ts +1 -1
  67. package/front_end/third_party/puppeteer/package/lib/puppeteer/util/version.js +1 -1
  68. package/front_end/third_party/puppeteer/package/lib/types.d.ts +5 -0
  69. package/front_end/third_party/puppeteer/package/package.json +4 -4
  70. package/front_end/third_party/puppeteer/package/src/api/ElementHandle.ts +15 -2
  71. package/front_end/third_party/puppeteer/package/src/cdp/Accessibility.ts +10 -1
  72. package/front_end/third_party/puppeteer/package/src/common/Debug.ts +0 -27
  73. package/front_end/third_party/puppeteer/package/src/node/BrowserLauncher.ts +96 -17
  74. package/front_end/third_party/puppeteer/package/src/node-env-setup.ts +1 -1
  75. package/front_end/third_party/puppeteer/package/src/revisions.ts +3 -3
  76. package/front_end/third_party/puppeteer/package/src/util/version.ts +1 -1
  77. package/front_end/ui/comments/CommentAnchorResolver.ts +60 -22
  78. package/front_end/ui/comments/CommentOverlayManager.ts +78 -25
  79. package/front_end/ui/legacy/README.md +12 -12
  80. package/front_end/ui/legacy/components/data_grid/DataGridExporter.ts +7 -13
  81. package/front_end/ui/legacy/components/inline_editor/PositionAreaEditor.ts +51 -57
  82. package/front_end/ui/legacy/components/source_frame/ImageView.ts +101 -42
  83. package/front_end/ui/legacy/components/utils/Linkifier.ts +1 -2
  84. package/front_end/ui/visual_logging/KnownContextValues.ts +7 -0
  85. 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 {Logger} from '../common/Debug.js';
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
- return node.nodeType === Node.TEXT_NODE ? node.parentElement : node;
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
- await this.cleanUserDataDir(launchArgs.userDataDir, {
189
- isTemp: launchArgs.isTempUserDataDir,
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
- const browserProcess = launch({
204
- executablePath: launchArgs.executablePath,
205
- args: launchArgs.args,
206
- handleSIGHUP,
207
- handleSIGTERM,
208
- handleSIGINT,
209
- dumpio,
210
- env,
211
- pipe: usePipe,
212
- onExit: onProcessExit,
213
- signal: options.signal,
214
- logger: options.logger,
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
+ }
@@ -52,7 +52,7 @@ async function readFile(
52
52
 
53
53
  environment.value = {
54
54
  path,
55
- debuglog,
55
+ debuglog: environment.value.debuglog ?? debuglog,
56
56
  ScreenRecorder: environment.value.ScreenRecorder,
57
57
  followSymlinks: true,
58
58
  readFile,
@@ -8,7 +8,7 @@
8
8
  * @internal
9
9
  */
10
10
  export const PUPPETEER_REVISIONS = Object.freeze({
11
- chrome: '153.0.8010.36',
12
- 'chrome-headless-shell': '153.0.8010.36',
13
- firefox: 'stable_155.0.1',
11
+ chrome: '154.0.8037.57',
12
+ 'chrome-headless-shell': '154.0.8037.57',
13
+ firefox: 'stable_156.0.1',
14
14
  });
@@ -6,5 +6,5 @@
6
6
 
7
7
  // If moved update release-please config
8
8
  // x-release-please-start-version
9
- export const packageVersion = '25.11.0';
9
+ export const packageVersion = '25.12.0';
10
10
  // x-release-please-end
@@ -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(element: Element, targetRect?: DOMRect): VisibleRect|null {
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
- let current = element.parentElementOrShadowHost();
706
- while (current && current !== doc.documentElement && current !== doc.body) {
707
- const style = win.getComputedStyle(current);
708
- const clipsX = isClippingOverflow(style.overflowX);
709
- const clipsY = isClippingOverflow(style.overflowY);
710
-
711
- if (clipsX || clipsY) {
712
- const parentRect = current.getBoundingClientRect();
713
- if (clipsX) {
714
- visibleLeft = Math.max(visibleLeft, parentRect.left);
715
- visibleRight = Math.min(visibleRight, parentRect.right);
716
- }
717
- if (clipsY) {
718
- visibleTop = Math.max(visibleTop, parentRect.top);
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
- current = current.parentElementOrShadowHost();
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
- #resizeRafId?: number;
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 !== undefined) {
699
- cancelAnimationFrame(this.#scrollRafId);
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.#scrollTarget && this.#scrollListener) {
711
- this.#scrollTarget.removeEventListener('scroll', this.#scrollListener, {capture: true});
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
- if (this.#resizeRafId !== undefined) {
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
- if (this.#resizeRafId !== undefined) {
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
- this.#scheduleRematch(root);
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;