chrome-devtools-frontend 1.0.1703574 → 1.0.1704172

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 (69) hide show
  1. package/AUTHORS +1 -0
  2. package/front_end/core/common/ParsedURL.ts +54 -0
  3. package/front_end/core/platform/StringUtilities.ts +65 -0
  4. package/front_end/core/sdk/CPUThrottlingManager.ts +3 -3
  5. package/front_end/core/sdk/SourceMap.ts +3 -3
  6. package/front_end/core/sdk/SourceMapScopeChainEntry.ts +4 -3
  7. package/front_end/core/sdk/SourceMapScopesInfo.ts +17 -5
  8. package/front_end/entrypoints/main/MainImpl.ts +3 -3
  9. package/front_end/models/bindings/CompilerScriptMapping.ts +1 -1
  10. package/front_end/models/har/HARFormat.ts +7 -2
  11. package/front_end/models/source_map_scopes/NamesResolver.ts +13 -5
  12. package/front_end/panels/accessibility/AccessibilityAnnouncementRecordingListView.ts +33 -1
  13. package/front_end/panels/accessibility/AccessibilityAnnouncementRecordingView.ts +4 -12
  14. package/front_end/panels/application/ApplicationPanelSidebar.ts +58 -60
  15. package/front_end/panels/application/BackForwardCacheTreeElement.ts +1 -1
  16. package/front_end/panels/application/FrameDetailsView.ts +64 -82
  17. package/front_end/panels/application/OpenedWindowDetailsView.ts +20 -21
  18. package/front_end/panels/application/PreloadingTreeElement.ts +3 -3
  19. package/front_end/panels/common/CommentsOverlayWidget.ts +145 -72
  20. package/front_end/panels/common/commentsOverlay.css +34 -23
  21. package/front_end/panels/elements/DOMTreeWidget.ts +29 -10
  22. package/front_end/panels/network/RequestPayloadView.ts +7 -2
  23. package/front_end/panels/network/ShowMoreDetailsWidget.ts +12 -9
  24. package/front_end/panels/sources/DebuggerPlugin.ts +49 -16
  25. package/front_end/panels/sources/SourcesPanel.ts +1 -1
  26. package/front_end/panels/sources/SourcesView.ts +3 -12
  27. package/front_end/panels/sources/TabbedEditorContainer.ts +0 -3
  28. package/front_end/panels/sources/components/HeadersView.ts +193 -123
  29. package/front_end/third_party/chromium/README.chromium +1 -1
  30. package/front_end/third_party/puppeteer/README.chromium +2 -2
  31. package/front_end/third_party/puppeteer/package/lib/es5-iife/puppeteer-core-browser.d.ts +5 -0
  32. package/front_end/third_party/puppeteer/package/lib/es5-iife/puppeteer-core-browser.js +24 -32
  33. package/front_end/third_party/puppeteer/package/lib/puppeteer/api/ElementHandle.d.ts +1 -1
  34. package/front_end/third_party/puppeteer/package/lib/puppeteer/api/ElementHandle.d.ts.map +1 -1
  35. package/front_end/third_party/puppeteer/package/lib/puppeteer/api/ElementHandle.js +15 -1
  36. package/front_end/third_party/puppeteer/package/lib/puppeteer/api/ElementHandle.js.map +1 -1
  37. package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/Accessibility.js +8 -1
  38. package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/Accessibility.js.map +1 -1
  39. package/front_end/third_party/puppeteer/package/lib/puppeteer/common/Debug.d.ts +0 -8
  40. package/front_end/third_party/puppeteer/package/lib/puppeteer/common/Debug.d.ts.map +1 -1
  41. package/front_end/third_party/puppeteer/package/lib/puppeteer/common/Debug.js +0 -24
  42. package/front_end/third_party/puppeteer/package/lib/puppeteer/common/Debug.js.map +1 -1
  43. package/front_end/third_party/puppeteer/package/lib/puppeteer/node/BrowserLauncher.d.ts +12 -0
  44. package/front_end/third_party/puppeteer/package/lib/puppeteer/node/BrowserLauncher.d.ts.map +1 -1
  45. package/front_end/third_party/puppeteer/package/lib/puppeteer/node/BrowserLauncher.js +79 -17
  46. package/front_end/third_party/puppeteer/package/lib/puppeteer/node/BrowserLauncher.js.map +1 -1
  47. package/front_end/third_party/puppeteer/package/lib/puppeteer/node-env-setup.js +1 -1
  48. package/front_end/third_party/puppeteer/package/lib/puppeteer/node-env-setup.js.map +1 -1
  49. package/front_end/third_party/puppeteer/package/lib/puppeteer/revisions.d.ts +3 -3
  50. package/front_end/third_party/puppeteer/package/lib/puppeteer/revisions.js +3 -3
  51. package/front_end/third_party/puppeteer/package/lib/puppeteer/util/version.d.ts +1 -1
  52. package/front_end/third_party/puppeteer/package/lib/puppeteer/util/version.js +1 -1
  53. package/front_end/third_party/puppeteer/package/lib/types.d.ts +5 -0
  54. package/front_end/third_party/puppeteer/package/package.json +4 -4
  55. package/front_end/third_party/puppeteer/package/src/api/ElementHandle.ts +15 -2
  56. package/front_end/third_party/puppeteer/package/src/cdp/Accessibility.ts +10 -1
  57. package/front_end/third_party/puppeteer/package/src/common/Debug.ts +0 -27
  58. package/front_end/third_party/puppeteer/package/src/node/BrowserLauncher.ts +96 -17
  59. package/front_end/third_party/puppeteer/package/src/node-env-setup.ts +1 -1
  60. package/front_end/third_party/puppeteer/package/src/revisions.ts +3 -3
  61. package/front_end/third_party/puppeteer/package/src/util/version.ts +1 -1
  62. package/front_end/ui/comments/CommentAnchorResolver.ts +60 -22
  63. package/front_end/ui/comments/CommentOverlayManager.ts +78 -25
  64. package/front_end/ui/legacy/components/data_grid/DataGridExporter.ts +7 -13
  65. package/front_end/ui/legacy/components/inline_editor/PositionAreaEditor.ts +51 -57
  66. package/front_end/ui/legacy/components/source_frame/ImageView.ts +101 -42
  67. package/front_end/ui/legacy/components/utils/Linkifier.ts +1 -2
  68. package/front_end/ui/visual_logging/KnownContextValues.ts +7 -0
  69. package/package.json +1 -1
@@ -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;
@@ -2,6 +2,8 @@
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 Platform from '../../../../core/platform/platform.js';
6
+
5
7
  import type {DataGridImpl} from './DataGrid.js';
6
8
 
7
9
  /**
@@ -72,18 +74,10 @@ function escapeMarkdown(val: string): string {
72
74
  .replace(/\r?\n/g, '<br>');
73
75
  }
74
76
 
77
+ /**
78
+ * Escapes a string for CSV export (handling both RFC 4180 quoting and
79
+ * prefixing spreadsheet formula characters like `=`, `+`, `-`, `@` with `'`).
80
+ */
75
81
  function escapeCSV(val: string): string {
76
- let needQuotes = false;
77
- let escaped = val;
78
- if (escaped.includes('"')) {
79
- escaped = escaped.replace(/"/g, '""');
80
- needQuotes = true;
81
- }
82
- if (escaped.includes(',') || escaped.includes('\n') || escaped.includes('\r')) {
83
- needQuotes = true;
84
- }
85
- if (needQuotes) {
86
- return `"${escaped}"`;
87
- }
88
- return escaped;
82
+ return Platform.StringUtilities.escapeCsvCell(val);
89
83
  }
@@ -4,6 +4,7 @@
4
4
  import * as Common from '../../../../core/common/common.js';
5
5
  import * as i18n from '../../../../core/i18n/i18n.js';
6
6
  import * as Lit from '../../../../ui/lit/lit.js';
7
+ import * as VisualLogging from '../../../../ui/visual_logging/visual_logging.js';
7
8
  import * as UI from '../../legacy.js';
8
9
 
9
10
  import positionAreaEditorStyles from './positionAreaEditor.css.js';
@@ -19,21 +20,9 @@ const UIStrings = {
19
20
  */
20
21
  positionAreaGrid: 'position-area grid',
21
22
  /**
22
- * @description Title for the block axis section in the position-area editor.
23
+ * @description Title for the mode section and accessible label for the mode radio button group in the position-area editor.
23
24
  */
24
- block: 'Block',
25
- /**
26
- * @description Title for the inline axis section in the position-area editor.
27
- */
28
- inline: 'Inline',
29
- /**
30
- * @description Accessible label for the block axis mode radio button group.
31
- */
32
- blockAxisMode: 'Block axis mode',
33
- /**
34
- * @description Accessible label for the inline axis mode radio button group.
35
- */
36
- inlineAxisMode: 'Inline axis mode',
25
+ mode: 'Mode',
37
26
  /**
38
27
  * @description Label for physical mode radio button in the position-area editor.
39
28
  */
@@ -299,14 +288,15 @@ export interface ViewInput {
299
288
  onSelectStart: (x: number, y: number) => void;
300
289
  onSelect: (x: number, y: number) => void;
301
290
  onSelectEnd: (x?: number, y?: number) => void;
302
- onModeChange: (axis: Axis, mode: Mode) => void;
303
- onSelfChange: (axis: Axis, self: boolean) => void;
291
+ onModeChange: (mode: Mode) => void;
292
+ onSelfChange: (self: boolean) => void;
304
293
  }
305
294
  export type View = (input: ViewInput, output: undefined, target: HTMLElement) => void;
306
295
  export const DEFAULT_VIEW: View = (input, output, target) => {
307
296
  const container = {
308
297
  attributes: {
309
298
  tabindex: '0',
299
+ jslog: `${VisualLogging.dialog('position-area-editor').parent('mapped').track({keydown: 'Enter|Escape'})}`,
310
300
  },
311
301
  };
312
302
  if (!input.area) {
@@ -486,35 +476,42 @@ export const DEFAULT_VIEW: View = (input, output, target) => {
486
476
 
487
477
  const blockAxis = input.area.primaryAxis === Axis.BLOCK ? input.area.first : input.area.second;
488
478
  const inlineAxis = input.area.primaryAxis === Axis.INLINE ? input.area.first : input.area.second;
479
+ const currentMode = blockAxis.mode === inlineAxis.mode ? blockAxis.mode : undefined;
480
+ const selfDisabled = (isGeneric(blockAxis) || blockAxis.mode === Mode.PHYSICAL) &&
481
+ (isGeneric(inlineAxis) || inlineAxis.mode === Mode.PHYSICAL);
482
+ const selfIndeterminate = !isGeneric(blockAxis) && !isGeneric(inlineAxis) && blockAxis.self !== inlineAxis.self;
483
+ const selfChecked = !selfIndeterminate && (blockAxis.self || inlineAxis.self);
489
484
 
490
- function renderModeRadioGroup(axis: Axis, currentMode: Mode): Lit.TemplateResult {
485
+ function renderModeRadioGroup(currentMode: Mode|undefined): Lit.TemplateResult {
491
486
  const modes = [
492
487
  {mode: Mode.PHYSICAL, label: i18nString(UIStrings.physical)},
493
488
  {mode: Mode.COORDINATE, label: i18nString(UIStrings.coordinate)},
494
489
  {mode: Mode.LOGICAL, label: i18nString(UIStrings.logical)},
495
490
  {mode: Mode.AUTO, label: i18nString(UIStrings.auto)},
496
491
  ];
497
- const axisModeLabel =
498
- axis === Axis.BLOCK ? i18nString(UIStrings.blockAxisMode) : i18nString(UIStrings.inlineAxisMode);
499
492
 
493
+ // clang-format off
500
494
  return html`
501
- <fieldset class="chip-radio-group" aria-label=${axisModeLabel}>
495
+ <fieldset class="chip-radio-group" aria-label=${i18nString(UIStrings.mode)}>
502
496
  ${modes.map(({mode, label}) => {
503
- const id = `${axis}-mode-${mode}`;
504
- return html`
505
- <input
506
- type="radio"
507
- id=${id}
508
- name="${axis}-mode"
509
- value=${mode}
510
- .checked=${currentMode === mode}
511
- @change=${() => input.onModeChange(axis, mode)}
512
- >
513
- <label for=${id}>${label}</label>
514
- `;
515
- })}
516
- </fieldset>
517
- `;
497
+ const id = `mode-${mode}`;
498
+ return html`
499
+ <input
500
+ type="radio"
501
+ id=${id}
502
+ name="mode"
503
+ value=${mode}
504
+ .checked=${currentMode === mode}
505
+ @change=${() => input.onModeChange(mode)}
506
+ >
507
+ <label
508
+ for=${id}
509
+ jslog=${VisualLogging.toggle(mode).track({click: true})}
510
+ >${label}</label>
511
+ `;
512
+ })}
513
+ </fieldset>`;
514
+ // clang-format on
518
515
  }
519
516
 
520
517
  // clang-format off
@@ -531,6 +528,7 @@ export const DEFAULT_VIEW: View = (input, output, target) => {
531
528
  aria-label=${i18nString(UIStrings.positionAreaGrid)}
532
529
  aria-description=${i18nString(UIStrings.positionAreaGridDescription)}
533
530
  aria-multiselectable="true"
531
+ jslog=${VisualLogging.canvas('position-area-grid').track({click: true, drag: true, keydown: 'ArrowUp|ArrowDown|ArrowLeft|ArrowRight|Enter|Space'})}
534
532
  data-x-start=${x.start} data-x-end=${x.end} data-y-start=${y.start} data-y-end=${y.end}
535
533
  @pointerdown=${onPointerDown}
536
534
  @pointermove=${onPointerMove}
@@ -558,27 +556,17 @@ export const DEFAULT_VIEW: View = (input, output, target) => {
558
556
  <div class=position-area-controls>
559
557
  <div class=axis-section>
560
558
  <div class=axis-header>
561
- <span class=axis-title>${i18nString(UIStrings.block)}</span>
562
- <devtools-checkbox
563
- .checked=${blockAxis.self}
564
- ?disabled=${isGeneric(blockAxis)}
565
- @change=${(e: Event) => input.onSelfChange(Axis.BLOCK, (e.target as UI.UIUtils.CheckboxLabel).checked)}>
566
- <span class="self-checkbox-label source-code">self</span>
567
- </devtools-checkbox>
568
- </div>
569
- ${renderModeRadioGroup(Axis.BLOCK, blockAxis.mode)}
570
- </div>
571
- <div class=axis-section>
572
- <div class=axis-header>
573
- <span class=axis-title>${i18nString(UIStrings.inline)}</span>
559
+ <span class=axis-title>${i18nString(UIStrings.mode)}</span>
574
560
  <devtools-checkbox
575
- .checked=${inlineAxis.self}
576
- ?disabled=${isGeneric(inlineAxis)}
577
- @change=${(e: Event) => input.onSelfChange(Axis.INLINE, (e.target as UI.UIUtils.CheckboxLabel).checked)}>
561
+ .checked=${selfChecked}
562
+ .indeterminate=${selfIndeterminate}
563
+ ?disabled=${selfDisabled}
564
+ jslog=${VisualLogging.toggle('self').track({change: true})}
565
+ @change=${(e: Event) => input.onSelfChange((e.target as UI.UIUtils.CheckboxLabel).checked)}>
578
566
  <span class="self-checkbox-label source-code">self</span>
579
567
  </devtools-checkbox>
580
568
  </div>
581
- ${renderModeRadioGroup(Axis.INLINE, inlineAxis.mode)}
569
+ ${renderModeRadioGroup(currentMode)}
582
570
  </div>
583
571
  </div>
584
572
  `,
@@ -725,10 +713,10 @@ export class PositionAreaEditor extends PositionAreaEditorBase {
725
713
  other.mode = mode === Mode.COORDINATE ? Mode.COORDINATE : (other.self ? Mode.COORDINATE : Mode.PHYSICAL);
726
714
  }
727
715
  } else {
716
+ const self = current.self || other.self;
717
+ current.self = !isGeneric(current) && self;
728
718
  other.mode = mode;
729
- if (!isGeneric(current)) {
730
- other.self = current.self;
731
- }
719
+ other.self = !isGeneric(other) && self;
732
720
  }
733
721
  } else {
734
722
  other.mode = mode;
@@ -779,8 +767,14 @@ export class PositionAreaEditor extends PositionAreaEditorBase {
779
767
  onSelectStart: this.#startSelection.bind(this),
780
768
  onSelect: this.#select.bind(this),
781
769
  onSelectEnd: this.#finishSelection.bind(this),
782
- onModeChange: this.#setAxisMode.bind(this),
783
- onSelfChange: this.#setAxisSelf.bind(this),
770
+ onModeChange: (mode: Mode) => {
771
+ this.#setAxisMode(Axis.BLOCK, mode);
772
+ this.#setAxisMode(Axis.INLINE, mode);
773
+ },
774
+ onSelfChange: (self: boolean) => {
775
+ this.#setAxisSelf(Axis.BLOCK, self);
776
+ this.#setAxisSelf(Axis.INLINE, self);
777
+ },
784
778
  },
785
779
  undefined, this.contentElement);
786
780
  }