@atlaskit/react-ufo 3.14.6 → 3.14.8

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 (55) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/cjs/create-experimental-interaction-metrics-payload/index.js +14 -12
  3. package/dist/cjs/create-payload/utils/get-vc-metrics.js +17 -13
  4. package/dist/cjs/interaction-metrics/index.js +35 -15
  5. package/dist/cjs/interaction-metrics-init/index.js +5 -3
  6. package/dist/cjs/vc/index.js +46 -6
  7. package/dist/cjs/vc/vc-observer/index.js +10 -2
  8. package/dist/cjs/vc/vc-observer/observers/index.js +12 -7
  9. package/dist/cjs/vc/vc-observer/observers/ssr-placeholders/index.js +76 -40
  10. package/dist/cjs/vc/vc-observer-new/index.js +84 -0
  11. package/dist/cjs/vc/vc-observer-new/viewport-observer/index.js +214 -71
  12. package/dist/cjs/vc/vc-observer-new/viewport-observer/mutation-observer/index.js +97 -59
  13. package/dist/es2019/create-experimental-interaction-metrics-payload/index.js +4 -2
  14. package/dist/es2019/create-payload/utils/get-vc-metrics.js +10 -4
  15. package/dist/es2019/interaction-metrics/index.js +36 -16
  16. package/dist/es2019/interaction-metrics-init/index.js +5 -3
  17. package/dist/es2019/vc/index.js +42 -5
  18. package/dist/es2019/vc/vc-observer/index.js +8 -2
  19. package/dist/es2019/vc/vc-observer/observers/index.js +11 -5
  20. package/dist/es2019/vc/vc-observer/observers/ssr-placeholders/index.js +57 -26
  21. package/dist/es2019/vc/vc-observer-new/index.js +67 -1
  22. package/dist/es2019/vc/vc-observer-new/viewport-observer/index.js +87 -22
  23. package/dist/es2019/vc/vc-observer-new/viewport-observer/mutation-observer/index.js +50 -34
  24. package/dist/esm/create-experimental-interaction-metrics-payload/index.js +14 -12
  25. package/dist/esm/create-payload/utils/get-vc-metrics.js +17 -13
  26. package/dist/esm/interaction-metrics/index.js +36 -16
  27. package/dist/esm/interaction-metrics-init/index.js +5 -3
  28. package/dist/esm/vc/index.js +45 -6
  29. package/dist/esm/vc/vc-observer/index.js +10 -2
  30. package/dist/esm/vc/vc-observer/observers/index.js +12 -7
  31. package/dist/esm/vc/vc-observer/observers/ssr-placeholders/index.js +76 -40
  32. package/dist/esm/vc/vc-observer-new/index.js +84 -0
  33. package/dist/esm/vc/vc-observer-new/viewport-observer/index.js +214 -71
  34. package/dist/esm/vc/vc-observer-new/viewport-observer/mutation-observer/index.js +97 -59
  35. package/dist/types/common/common/types.d.ts +4 -1
  36. package/dist/types/vc/index.d.ts +3 -0
  37. package/dist/types/vc/types.d.ts +2 -0
  38. package/dist/types/vc/vc-observer/index.d.ts +1 -0
  39. package/dist/types/vc/vc-observer/observers/index.d.ts +2 -0
  40. package/dist/types/vc/vc-observer/observers/ssr-placeholders/index.d.ts +6 -0
  41. package/dist/types/vc/vc-observer-new/index.d.ts +30 -0
  42. package/dist/types/vc/vc-observer-new/types.d.ts +1 -1
  43. package/dist/types/vc/vc-observer-new/viewport-observer/index.d.ts +5 -1
  44. package/dist/types/vc/vc-observer-new/viewport-observer/mutation-observer/index.d.ts +2 -0
  45. package/dist/types-ts4.5/common/common/types.d.ts +4 -1
  46. package/dist/types-ts4.5/vc/index.d.ts +3 -0
  47. package/dist/types-ts4.5/vc/types.d.ts +2 -0
  48. package/dist/types-ts4.5/vc/vc-observer/index.d.ts +1 -0
  49. package/dist/types-ts4.5/vc/vc-observer/observers/index.d.ts +2 -0
  50. package/dist/types-ts4.5/vc/vc-observer/observers/ssr-placeholders/index.d.ts +6 -0
  51. package/dist/types-ts4.5/vc/vc-observer-new/index.d.ts +30 -0
  52. package/dist/types-ts4.5/vc/vc-observer-new/types.d.ts +1 -1
  53. package/dist/types-ts4.5/vc/vc-observer-new/viewport-observer/index.d.ts +5 -1
  54. package/dist/types-ts4.5/vc/vc-observer-new/viewport-observer/mutation-observer/index.d.ts +2 -0
  55. package/package.json +11 -6
@@ -1,5 +1,6 @@
1
1
  import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
2
  import { fg } from '@atlaskit/platform-feature-flags';
3
+ import { SSRPlaceholderHandlers } from '../vc-observer/observers/ssr-placeholders';
3
4
  import EntriesTimeline from './entries-timeline';
4
5
  import getElementName from './get-element-name';
5
6
  import VCCalculator_FY25_03 from './metric-calculator/fy25_03';
@@ -7,6 +8,11 @@ import getViewportHeight from './metric-calculator/utils/get-viewport-height';
7
8
  import getViewportWidth from './metric-calculator/utils/get-viewport-width';
8
9
  import ViewportObserver from './viewport-observer';
9
10
  import WindowEventObserver from './window-event-observer';
11
+ const SSRState = {
12
+ normal: 1,
13
+ waitingForFirstRender: 2,
14
+ ignoring: 3
15
+ };
10
16
  const DEFAULT_SELECTOR_CONFIG = {
11
17
  id: false,
12
18
  testId: true,
@@ -19,9 +25,31 @@ export default class VCObserverNew {
19
25
  var _config$isPostInterac, _config$selectorConfi;
20
26
  _defineProperty(this, "viewportObserver", null);
21
27
  _defineProperty(this, "windowEventObserver", null);
28
+ // SSR related properties
29
+ _defineProperty(this, "ssrPlaceholderHandler", null);
30
+ _defineProperty(this, "ssr", {
31
+ state: SSRState.normal,
32
+ reactRootElement: null,
33
+ renderStart: -1,
34
+ renderStop: -1
35
+ });
22
36
  this.entriesTimeline = new EntriesTimeline();
23
37
  this.isPostInteraction = (_config$isPostInterac = config.isPostInteraction) !== null && _config$isPostInterac !== void 0 ? _config$isPostInterac : false;
24
38
  this.selectorConfig = (_config$selectorConfi = config.selectorConfig) !== null && _config$selectorConfi !== void 0 ? _config$selectorConfi : DEFAULT_SELECTOR_CONFIG;
39
+
40
+ // Use shared SSR placeholder handler if provided, otherwise create new one if feature flag is enabled
41
+ if (config.ssrPlaceholderHandler) {
42
+ this.ssrPlaceholderHandler = config.ssrPlaceholderHandler;
43
+ } else {
44
+ var _config$SSRConfig$ena, _config$SSRConfig, _config$SSRConfig$dis, _config$SSRConfig2;
45
+ this.ssrPlaceholderHandler = new SSRPlaceholderHandlers({
46
+ enablePageLayoutPlaceholder: (_config$SSRConfig$ena = (_config$SSRConfig = config.SSRConfig) === null || _config$SSRConfig === void 0 ? void 0 : _config$SSRConfig.enablePageLayoutPlaceholder) !== null && _config$SSRConfig$ena !== void 0 ? _config$SSRConfig$ena : false,
47
+ disableSizeAndPositionCheck: (_config$SSRConfig$dis = (_config$SSRConfig2 = config.SSRConfig) === null || _config$SSRConfig2 === void 0 ? void 0 : _config$SSRConfig2.disableSizeAndPositionCheck) !== null && _config$SSRConfig$dis !== void 0 ? _config$SSRConfig$dis : {
48
+ v: false,
49
+ h: false
50
+ }
51
+ });
52
+ }
25
53
  this.viewportObserver = new ViewportObserver({
26
54
  onChange: onChangeArg => {
27
55
  const {
@@ -51,7 +79,10 @@ export default class VCObserverNew {
51
79
  newValue: mutationData === null || mutationData === void 0 ? void 0 : mutationData.newValue
52
80
  }
53
81
  });
54
- }
82
+ },
83
+ // Pass SSR context to ViewportObserver
84
+ getSSRState: () => this.getSSRState(),
85
+ getSSRPlaceholderHandler: () => this.getSSRPlaceholderHandler()
55
86
  });
56
87
  this.windowEventObserver = new WindowEventObserver({
57
88
  onEvent: ({
@@ -72,6 +103,14 @@ export default class VCObserverNew {
72
103
  startTime
73
104
  }) {
74
105
  var _this$viewportObserve, _window, _this$windowEventObse;
106
+ // Reset SSR state on start (matches old VCObserver behavior)
107
+ this.ssr = {
108
+ state: SSRState.normal,
109
+ reactRootElement: null,
110
+ // Reset to null (matches old VCObserver)
111
+ renderStart: -1,
112
+ renderStop: -1
113
+ };
75
114
  (_this$viewportObserve = this.viewportObserver) === null || _this$viewportObserve === void 0 ? void 0 : _this$viewportObserve.start();
76
115
  if ((_window = window) !== null && _window !== void 0 && _window.__SSR_ABORT_LISTENERS__ && fg('platform_ufo_vc_observer_new_ssr_abort_listener')) {
77
116
  const abortListeners = window.__SSR_ABORT_LISTENERS__;
@@ -97,6 +136,33 @@ export default class VCObserverNew {
97
136
  var _this$viewportObserve2, _this$windowEventObse2;
98
137
  (_this$viewportObserve2 = this.viewportObserver) === null || _this$viewportObserve2 === void 0 ? void 0 : _this$viewportObserve2.stop();
99
138
  (_this$windowEventObse2 = this.windowEventObserver) === null || _this$windowEventObse2 === void 0 ? void 0 : _this$windowEventObse2.stop();
139
+
140
+ // Clear SSR state on stop (matches old VCObserver behavior)
141
+ this.ssr.reactRootElement = null;
142
+ }
143
+
144
+ // SSR related methods
145
+ setReactRootElement(element) {
146
+ this.ssr.reactRootElement = element;
147
+ }
148
+ setReactRootRenderStart(startTime = performance.now()) {
149
+ this.ssr.renderStart = startTime;
150
+ this.ssr.state = SSRState.waitingForFirstRender;
151
+ }
152
+ setReactRootRenderStop(stopTime = performance.now()) {
153
+ this.ssr.renderStop = stopTime;
154
+ }
155
+ collectSSRPlaceholders() {
156
+ // This is handled by the shared SSRPlaceholderHandlers in VCObserverWrapper
157
+ // Individual observers don't need to implement this
158
+ }
159
+
160
+ // Internal methods for ViewportObserver to access SSR state
161
+ getSSRState() {
162
+ return this.ssr;
163
+ }
164
+ getSSRPlaceholderHandler() {
165
+ return this.ssrPlaceholderHandler;
100
166
  }
101
167
  addSSR(ssr) {
102
168
  this.entriesTimeline.push({
@@ -14,7 +14,7 @@ function isElementVisible(element) {
14
14
  }
15
15
  try {
16
16
  const visible = element.checkVisibility({
17
- // @ts-expect-error
17
+ // @ts-ignore - visibilityProperty may not exist in all TS environments
18
18
  visibilityProperty: true,
19
19
  contentVisibilityAuto: true,
20
20
  opacityProperty: true
@@ -59,8 +59,12 @@ const createElementMutationsWatcher = removedNodeRects => ({
59
59
  return 'mutation:element';
60
60
  };
61
61
  export default class ViewportObserver {
62
+ // SSR context functions
63
+
62
64
  constructor({
63
- onChange
65
+ onChange,
66
+ getSSRState,
67
+ getSSRPlaceholderHandler
64
68
  }) {
65
69
  _defineProperty(this, "handleIntersectionEntry", ({
66
70
  target,
@@ -85,9 +89,11 @@ export default class ViewportObserver {
85
89
  mutationData
86
90
  });
87
91
  });
88
- _defineProperty(this, "handleChildListMutation", ({
92
+ _defineProperty(this, "handleChildListMutation", async ({
93
+ target,
89
94
  addedNodes,
90
- removedNodes
95
+ removedNodes,
96
+ timestamp
91
97
  }) => {
92
98
  const removedNodeRects = removedNodes.map(ref => {
93
99
  const n = ref.deref();
@@ -96,11 +102,66 @@ export default class ViewportObserver {
96
102
  }
97
103
  return this.mapVisibleNodeRects.get(n);
98
104
  });
99
- addedNodes.forEach(addedNodeRef => {
100
- var _this$intersectionObs4;
105
+ const targetNode = target.deref();
106
+ for (const addedNodeRef of addedNodes) {
107
+ var _this$intersectionObs8;
101
108
  const addedNode = addedNodeRef.deref();
102
109
  if (!addedNode) {
103
- return;
110
+ continue;
111
+ }
112
+
113
+ // SSR hydration logic
114
+ if (this.getSSRState && fg('platform_ufo_vc_v3_ssr_placeholder')) {
115
+ const ssrState = this.getSSRState();
116
+ const SSRStateEnum = {
117
+ normal: 1,
118
+ waitingForFirstRender: 2,
119
+ ignoring: 3
120
+ };
121
+ if (ssrState.state === SSRStateEnum.waitingForFirstRender && timestamp > ssrState.renderStart && targetNode === ssrState.reactRootElement) {
122
+ var _this$intersectionObs;
123
+ ssrState.state = SSRStateEnum.ignoring;
124
+ if (ssrState.renderStop === -1) {
125
+ // arbitrary 500ms DOM update window
126
+ ssrState.renderStop = timestamp + 500;
127
+ }
128
+ (_this$intersectionObs = this.intersectionObserver) === null || _this$intersectionObs === void 0 ? void 0 : _this$intersectionObs.watchAndTag(addedNode, 'ssr-hydration');
129
+ continue;
130
+ }
131
+ if (ssrState.state === SSRStateEnum.ignoring && timestamp > ssrState.renderStart && targetNode === ssrState.reactRootElement) {
132
+ if (timestamp <= ssrState.renderStop) {
133
+ var _this$intersectionObs2;
134
+ (_this$intersectionObs2 = this.intersectionObserver) === null || _this$intersectionObs2 === void 0 ? void 0 : _this$intersectionObs2.watchAndTag(addedNode, 'ssr-hydration');
135
+ continue;
136
+ } else {
137
+ ssrState.state = SSRStateEnum.normal;
138
+ }
139
+ }
140
+ }
141
+
142
+ // SSR placeholder logic - check and handle with await
143
+ if (this.getSSRPlaceholderHandler && fg('platform_ufo_vc_v3_ssr_placeholder')) {
144
+ const ssrPlaceholderHandler = this.getSSRPlaceholderHandler();
145
+ if (ssrPlaceholderHandler) {
146
+ if (ssrPlaceholderHandler.isPlaceholder(addedNode) || ssrPlaceholderHandler.isPlaceholderIgnored(addedNode)) {
147
+ const result = await ssrPlaceholderHandler.checkIfExistedAndSizeMatching(addedNode);
148
+ if (result !== false) {
149
+ var _this$intersectionObs3;
150
+ (_this$intersectionObs3 = this.intersectionObserver) === null || _this$intersectionObs3 === void 0 ? void 0 : _this$intersectionObs3.watchAndTag(addedNode, 'mutation:ssr-placeholder');
151
+ continue;
152
+ }
153
+ // If result is false, continue to normal mutation logic below
154
+ }
155
+ if (ssrPlaceholderHandler.isPlaceholderReplacement(addedNode) || ssrPlaceholderHandler.isPlaceholderIgnored(addedNode)) {
156
+ const result = await ssrPlaceholderHandler.validateReactComponentMatchToPlaceholder(addedNode);
157
+ if (result !== false) {
158
+ var _this$intersectionObs4;
159
+ (_this$intersectionObs4 = this.intersectionObserver) === null || _this$intersectionObs4 === void 0 ? void 0 : _this$intersectionObs4.watchAndTag(addedNode, 'mutation:ssr-placeholder');
160
+ continue;
161
+ }
162
+ // If result is false, continue to normal mutation logic below
163
+ }
164
+ }
104
165
  }
105
166
  const sameDeletedNode = removedNodes.find(ref => {
106
167
  const n = ref.deref();
@@ -115,27 +176,27 @@ export default class ViewportObserver {
115
176
  // When fg('platform_vc_ignore_no_ls_mutation_marker') is not enabled,
116
177
  // no layout shift mutation is excluded as per existing fy25.03 logic
117
178
  if (sameDeletedNode && (!isNoLsMarkerEnabled || isInIgnoreLsMarker)) {
118
- var _this$intersectionObs;
119
- (_this$intersectionObs = this.intersectionObserver) === null || _this$intersectionObs === void 0 ? void 0 : _this$intersectionObs.watchAndTag(addedNode, 'mutation:remount');
120
- return;
179
+ var _this$intersectionObs5;
180
+ (_this$intersectionObs5 = this.intersectionObserver) === null || _this$intersectionObs5 === void 0 ? void 0 : _this$intersectionObs5.watchAndTag(addedNode, 'mutation:remount');
181
+ continue;
121
182
  }
122
183
  if (isContainedWithinMediaWrapper(addedNode)) {
123
- var _this$intersectionObs2;
124
- (_this$intersectionObs2 = this.intersectionObserver) === null || _this$intersectionObs2 === void 0 ? void 0 : _this$intersectionObs2.watchAndTag(addedNode, 'mutation:media');
125
- return;
184
+ var _this$intersectionObs6;
185
+ (_this$intersectionObs6 = this.intersectionObserver) === null || _this$intersectionObs6 === void 0 ? void 0 : _this$intersectionObs6.watchAndTag(addedNode, 'mutation:media');
186
+ continue;
126
187
  }
127
188
  const {
128
189
  isWithinThirdPartySegment,
129
190
  ignoredReason
130
191
  } = checkThirdPartySegmentWithIgnoreReason(addedNode);
131
192
  if (isWithinThirdPartySegment) {
132
- var _this$intersectionObs3;
193
+ var _this$intersectionObs7;
133
194
  const assignedReason = createMutationTypeWithIgnoredReason(ignoredReason || 'third-party-element');
134
- (_this$intersectionObs3 = this.intersectionObserver) === null || _this$intersectionObs3 === void 0 ? void 0 : _this$intersectionObs3.watchAndTag(addedNode, assignedReason);
135
- return;
195
+ (_this$intersectionObs7 = this.intersectionObserver) === null || _this$intersectionObs7 === void 0 ? void 0 : _this$intersectionObs7.watchAndTag(addedNode, assignedReason);
196
+ continue;
136
197
  }
137
- (_this$intersectionObs4 = this.intersectionObserver) === null || _this$intersectionObs4 === void 0 ? void 0 : _this$intersectionObs4.watchAndTag(addedNode, createElementMutationsWatcher(removedNodeRects));
138
- });
198
+ (_this$intersectionObs8 = this.intersectionObserver) === null || _this$intersectionObs8 === void 0 ? void 0 : _this$intersectionObs8.watchAndTag(addedNode, createElementMutationsWatcher(removedNodeRects));
199
+ }
139
200
  });
140
201
  _defineProperty(this, "handleAttributeMutation", ({
141
202
  target,
@@ -143,8 +204,8 @@ export default class ViewportObserver {
143
204
  oldValue,
144
205
  newValue
145
206
  }) => {
146
- var _this$intersectionObs5;
147
- (_this$intersectionObs5 = this.intersectionObserver) === null || _this$intersectionObs5 === void 0 ? void 0 : _this$intersectionObs5.watchAndTag(target, ({
207
+ var _this$intersectionObs9;
208
+ (_this$intersectionObs9 = this.intersectionObserver) === null || _this$intersectionObs9 === void 0 ? void 0 : _this$intersectionObs9.watchAndTag(target, ({
148
209
  target,
149
210
  rect
150
211
  }) => {
@@ -243,6 +304,10 @@ export default class ViewportObserver {
243
304
  this.intersectionObserver = null;
244
305
  this.mutationObserver = null;
245
306
  this.performanceObserver = null;
307
+
308
+ // Initialize SSR context functions
309
+ this.getSSRState = getSSRState;
310
+ this.getSSRPlaceholderHandler = getSSRPlaceholderHandler;
246
311
  }
247
312
  initializeObservers() {
248
313
  if (this.isStarted) {
@@ -280,12 +345,12 @@ export default class ViewportObserver {
280
345
  this.isStarted = true;
281
346
  }
282
347
  stop() {
283
- var _this$mutationObserve2, _this$intersectionObs6, _this$performanceObse2;
348
+ var _this$mutationObserve2, _this$intersectionObs0, _this$performanceObse2;
284
349
  if (!this.isStarted) {
285
350
  return;
286
351
  }
287
352
  (_this$mutationObserve2 = this.mutationObserver) === null || _this$mutationObserve2 === void 0 ? void 0 : _this$mutationObserve2.disconnect();
288
- (_this$intersectionObs6 = this.intersectionObserver) === null || _this$intersectionObs6 === void 0 ? void 0 : _this$intersectionObs6.disconnect();
353
+ (_this$intersectionObs0 = this.intersectionObserver) === null || _this$intersectionObs0 === void 0 ? void 0 : _this$intersectionObs0.disconnect();
289
354
  (_this$performanceObse2 = this.performanceObserver) === null || _this$performanceObse2 === void 0 ? void 0 : _this$performanceObse2.disconnect();
290
355
  this.isStarted = false;
291
356
  }
@@ -1,4 +1,5 @@
1
- import { fg } from '@atlaskit/platform-feature-flags';
1
+ // Batched mutation data for performance optimization
2
+
2
3
  function createMutationObserver({
3
4
  onAttributeMutation,
4
5
  onChildListMutation,
@@ -8,10 +9,10 @@ function createMutationObserver({
8
9
  return null;
9
10
  }
10
11
  const mutationObserverCallback = mutations => {
11
- const addedNodes = [];
12
- const removedNodes = [];
13
- const attributeMutations = [];
14
12
  const targets = [];
13
+ // Use nested Maps for O(1) batching performance
14
+ // Short-lived Maps are safe since they're discarded after each callback
15
+ const batchedMutations = new Map();
15
16
  for (const mut of mutations) {
16
17
  if (!(mut.target instanceof HTMLElement)) {
17
18
  continue;
@@ -29,51 +30,66 @@ function createMutationObserver({
29
30
  const oldValue = (_mut$oldValue = mut.oldValue) !== null && _mut$oldValue !== void 0 ? _mut$oldValue : undefined;
30
31
  const newValue = mut.attributeName ? mut.target.getAttribute(mut.attributeName) : undefined;
31
32
  if (oldValue !== newValue) {
32
- if (fg('platform_vc_ignore_no_ls_mutation_marker')) {
33
- var _mut$attributeName;
34
- attributeMutations.push({
35
- target: new WeakRef(mut.target),
36
- attributeName: (_mut$attributeName = mut.attributeName) !== null && _mut$attributeName !== void 0 ? _mut$attributeName : 'unknown',
37
- oldValue,
38
- newValue
39
- });
40
- } else {
41
- var _mut$attributeName2;
42
- onAttributeMutation({
43
- target: mut.target,
44
- attributeName: (_mut$attributeName2 = mut.attributeName) !== null && _mut$attributeName2 !== void 0 ? _mut$attributeName2 : 'unknown',
45
- oldValue,
46
- newValue
47
- });
48
- }
33
+ var _mut$attributeName;
34
+ onAttributeMutation({
35
+ target: mut.target,
36
+ attributeName: (_mut$attributeName = mut.attributeName) !== null && _mut$attributeName !== void 0 ? _mut$attributeName : 'unknown',
37
+ oldValue,
38
+ newValue
39
+ });
49
40
  }
50
41
  continue;
51
42
  } else if (mut.type === 'childList') {
52
43
  var _mut$addedNodes, _mut$removedNodes;
44
+ // In chromium browser MutationRecord has timestamp field, which we should use.
45
+ const timestamp = Math.round(mut.timestamp || performance.now());
46
+
47
+ // Get or create timestamp bucket
48
+ let timestampBucket = batchedMutations.get(timestamp);
49
+ if (!timestampBucket) {
50
+ timestampBucket = new Map();
51
+ batchedMutations.set(timestamp, timestampBucket);
52
+ }
53
+
54
+ // Get or create target batch within timestamp bucket
55
+ let batch = timestampBucket.get(mut.target);
56
+ if (!batch) {
57
+ batch = {
58
+ target: new WeakRef(mut.target),
59
+ addedNodes: [],
60
+ removedNodes: [],
61
+ timestamp
62
+ };
63
+ timestampBucket.set(mut.target, batch);
64
+ }
65
+
66
+ // Accumulate added nodes
53
67
  ((_mut$addedNodes = mut.addedNodes) !== null && _mut$addedNodes !== void 0 ? _mut$addedNodes : []).forEach(node => {
54
68
  if (node instanceof HTMLElement) {
55
- addedNodes.push(new WeakRef(node));
69
+ batch.addedNodes.push(new WeakRef(node));
56
70
  }
57
71
  });
72
+
73
+ // Accumulate removed nodes
58
74
  ((_mut$removedNodes = mut.removedNodes) !== null && _mut$removedNodes !== void 0 ? _mut$removedNodes : []).forEach(node => {
59
75
  if (node instanceof HTMLElement) {
60
- removedNodes.push(new WeakRef(node));
76
+ batch.removedNodes.push(new WeakRef(node));
61
77
  }
62
78
  });
63
79
  }
64
80
  targets.push(mut.target);
65
81
  }
66
- onChildListMutation({
67
- addedNodes,
68
- removedNodes
69
- });
70
- for (const mut of attributeMutations) {
71
- onAttributeMutation({
72
- target: mut.target.deref(),
73
- attributeName: mut.attributeName,
74
- oldValue: mut.oldValue,
75
- newValue: mut.newValue
76
- });
82
+
83
+ // Process all batched childList mutations
84
+ for (const timestampBucket of batchedMutations.values()) {
85
+ for (const batch of timestampBucket.values()) {
86
+ onChildListMutation({
87
+ target: batch.target,
88
+ addedNodes: batch.addedNodes,
89
+ removedNodes: batch.removedNodes,
90
+ timestamp: batch.timestamp
91
+ });
92
+ }
77
93
  }
78
94
  onMutationFinished === null || onMutationFinished === void 0 ? void 0 : onMutationFinished({
79
95
  targets
@@ -84,17 +84,19 @@ export function getExperimentalVCMetrics(_x) {
84
84
  }
85
85
  function _getExperimentalVCMetrics() {
86
86
  _getExperimentalVCMetrics = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee(interaction) {
87
- var _interaction$apdex, prefix, result, VC, pageVisibilityUpToTTAI;
87
+ var vcObserver, _interaction$apdex, prefix, result, VC, pageVisibilityUpToTTAI;
88
88
  return _regeneratorRuntime.wrap(function _callee$(_context) {
89
89
  while (1) switch (_context.prev = _context.next) {
90
90
  case 0:
91
- if (!experimentalVC.vcObserver) {
92
- _context.next = 12;
91
+ // Use per-interaction VC observer if available, otherwise fall back to global experimentalVC
92
+ vcObserver = interaction.experimentalVCObserver || experimentalVC.vcObserver;
93
+ if (!vcObserver) {
94
+ _context.next = 13;
93
95
  break;
94
96
  }
95
97
  prefix = 'ufo-experimental';
96
- _context.next = 4;
97
- return experimentalVC.vcObserver.getVCResult({
98
+ _context.next = 5;
99
+ return vcObserver.getVCResult({
98
100
  start: interaction.start,
99
101
  stop: interaction.end,
100
102
  tti: (_interaction$apdex = interaction.apdex) === null || _interaction$apdex === void 0 || (_interaction$apdex = _interaction$apdex[0]) === null || _interaction$apdex === void 0 ? void 0 : _interaction$apdex.stopTime,
@@ -104,28 +106,28 @@ function _getExperimentalVCMetrics() {
104
106
  experienceKey: interaction.ufoName,
105
107
  interactionId: interaction.id
106
108
  });
107
- case 4:
109
+ case 5:
108
110
  result = _context.sent;
109
111
  VC = result === null || result === void 0 ? void 0 : result['metrics:vc'];
110
112
  if (!(!VC || !(result !== null && result !== void 0 && result["".concat(prefix, ":vc:clean")]))) {
111
- _context.next = 8;
113
+ _context.next = 9;
112
114
  break;
113
115
  }
114
116
  return _context.abrupt("return", result);
115
- case 8:
117
+ case 9:
116
118
  pageVisibilityUpToTTAI = getPageVisibilityState(interaction.start, interaction.end);
117
119
  if (!(interaction.abortReason || pageVisibilityUpToTTAI !== 'visible')) {
118
- _context.next = 11;
120
+ _context.next = 12;
119
121
  break;
120
122
  }
121
123
  return _context.abrupt("return", result);
122
- case 11:
124
+ case 12:
123
125
  return _context.abrupt("return", _objectSpread(_objectSpread({}, result), {}, {
124
126
  'metric:experimental:vc90': VC['90']
125
127
  }));
126
- case 12:
127
- return _context.abrupt("return", null);
128
128
  case 13:
129
+ return _context.abrupt("return", null);
130
+ case 14:
129
131
  case "end":
130
132
  return _context.stop();
131
133
  }
@@ -16,7 +16,7 @@ function getVCMetrics(_x) {
16
16
  function _getVCMetrics() {
17
17
  _getVCMetrics = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee(interaction) {
18
18
  var _config$vc, _config$vc$ssrWhiteli, _interaction$apdex, _config$vc2, _config$vc3, _config$experimentalI, _result$ufoVcRev;
19
- var config, interactionStatus, pageVisibilityUpToTTAI, shouldReportVCMetrics, isSSREnabled, ssr, tti, prefix, result, mostRecentVCRevision, mostRecentVCRevisionPayload;
19
+ var config, interactionStatus, pageVisibilityUpToTTAI, shouldReportVCMetrics, observer, isSSREnabled, ssr, tti, prefix, result, mostRecentVCRevision, mostRecentVCRevisionPayload;
20
20
  return _regeneratorRuntime.wrap(function _callee$(_context) {
21
21
  while (1) switch (_context.prev = _context.next) {
22
22
  case 0:
@@ -27,7 +27,7 @@ function _getVCMetrics() {
27
27
  }
28
28
  return _context.abrupt("return", {});
29
29
  case 3:
30
- if (!(fg('platform_ufo_enable_interactions_vc') || fg('platform_ufo_enable_interactivity_jsm'))) {
30
+ if (!fg('platform_ufo_enable_vc_press_interactions')) {
31
31
  _context.next = 8;
32
32
  break;
33
33
  }
@@ -48,14 +48,15 @@ function _getVCMetrics() {
48
48
  case 10:
49
49
  interactionStatus = getInteractionStatus(interaction);
50
50
  pageVisibilityUpToTTAI = getPageVisibilityUpToTTAI(interaction);
51
- shouldReportVCMetrics = interactionStatus.originalInteractionStatus === 'SUCCEEDED' && pageVisibilityUpToTTAI === 'visible';
51
+ shouldReportVCMetrics = interactionStatus.originalInteractionStatus === 'SUCCEEDED' && pageVisibilityUpToTTAI === 'visible'; // Use per-interaction VC observer if available, otherwise fall back to global
52
+ observer = interaction.vcObserver || getVCObserver();
52
53
  if (!(!shouldReportVCMetrics && fg('platform_ufo_no_vc_on_aborted'))) {
53
- _context.next = 16;
54
+ _context.next = 17;
54
55
  break;
55
56
  }
56
- getVCObserver().stop(interaction.ufoName);
57
+ observer.stop(interaction.ufoName);
57
58
  return _context.abrupt("return", {});
58
- case 16:
59
+ case 17:
59
60
  isSSREnabled = interaction.type === 'page_load' && ((config === null || config === void 0 ? void 0 : config.ssr) || (config === null || config === void 0 || (_config$vc$ssrWhiteli = config.vc.ssrWhitelist) === null || _config$vc$ssrWhiteli === void 0 ? void 0 : _config$vc$ssrWhiteli.includes(interaction.ufoName)));
60
61
  ssr = interaction.type === 'page_load' && isSSREnabled ? {
61
62
  ssr: getSSRDoneTimeValue(config)
@@ -63,8 +64,8 @@ function _getVCMetrics() {
63
64
  postInteractionLog.setVCObserverSSRConfig(ssr);
64
65
  tti = (_interaction$apdex = interaction.apdex) === null || _interaction$apdex === void 0 || (_interaction$apdex = _interaction$apdex[0]) === null || _interaction$apdex === void 0 ? void 0 : _interaction$apdex.stopTime;
65
66
  prefix = 'ufo';
66
- _context.next = 23;
67
- return getVCObserver().getVCResult(_objectSpread({
67
+ _context.next = 24;
68
+ return observer.getVCResult(_objectSpread({
68
69
  start: interaction.start,
69
70
  stop: interaction.end,
70
71
  tti: tti,
@@ -76,10 +77,13 @@ function _getVCMetrics() {
76
77
  interactionId: interaction.id,
77
78
  includeSSRRatio: (_config$vc3 = config.vc) === null || _config$vc3 === void 0 ? void 0 : _config$vc3.includeSSRRatio
78
79
  }, ssr));
79
- case 23:
80
+ case 24:
80
81
  result = _context.sent;
82
+ if (fg('platform_ufo_enable_vc_observer_per_interaction')) {
83
+ observer.stop(interaction.ufoName);
84
+ }
81
85
  if ((_config$experimentalI = config.experimentalInteractionMetrics) !== null && _config$experimentalI !== void 0 && _config$experimentalI.enabled) {
82
- getVCObserver().stop(interaction.ufoName);
86
+ observer.stop(interaction.ufoName);
83
87
  }
84
88
  postInteractionLog.setLastInteractionFinishVCResult(result);
85
89
  mostRecentVCRevision = getMostRecentVCRevision(interaction.ufoName);
@@ -88,15 +92,15 @@ function _getVCMetrics() {
88
92
  return revision === mostRecentVCRevision;
89
93
  });
90
94
  if (!(!shouldReportVCMetrics || !(mostRecentVCRevisionPayload !== null && mostRecentVCRevisionPayload !== void 0 && mostRecentVCRevisionPayload.clean))) {
91
- _context.next = 30;
95
+ _context.next = 32;
92
96
  break;
93
97
  }
94
98
  return _context.abrupt("return", result);
95
- case 30:
99
+ case 32:
96
100
  return _context.abrupt("return", _objectSpread(_objectSpread({}, result), {}, {
97
101
  'metric:vc90': mostRecentVCRevisionPayload['metric:vc90']
98
102
  }));
99
- case 31:
103
+ case 33:
100
104
  case "end":
101
105
  return _context.stop();
102
106
  }
@@ -16,7 +16,7 @@ import { experimentalVC, getExperimentalVCMetrics, onExperimentalInteractionComp
16
16
  import { clearActiveTrace } from '../experience-trace-id-context';
17
17
  import { allFeatureFlagsAccessed, currentFeatureFlagsAccessed } from '../feature-flags-accessed';
18
18
  import { getInteractionId } from '../interaction-id-context';
19
- import { getVCObserver } from '../vc';
19
+ import { getVCObserver, newVCObserver } from '../vc';
20
20
  import { interactions } from './common/constants';
21
21
  import PostInteractionLog from './post-interaction-log';
22
22
  var PreviousInteractionLog = {
@@ -523,7 +523,9 @@ function finishInteraction(id, data) {
523
523
  clearActiveTrace();
524
524
  callCleanUpCallbacks(data);
525
525
  if ((_getConfig4 = getConfig()) !== null && _getConfig4 !== void 0 && (_getConfig4 = _getConfig4.vc) !== null && _getConfig4 !== void 0 && _getConfig4.stopVCAtInteractionFinish) {
526
- data.vc = getVCObserver().getVCRawData();
526
+ // Use per-interaction VC observer if available, otherwise fall back to global
527
+ var observer = data.vcObserver || getVCObserver();
528
+ data.vc = observer.getVCRawData();
527
529
  }
528
530
  if (!((_getConfig5 = getConfig()) !== null && _getConfig5 !== void 0 && (_getConfig5 = _getConfig5.experimentalInteractionMetrics) !== null && _getConfig5 !== void 0 && _getConfig5.enabled)) {
529
531
  remove(id);
@@ -735,6 +737,7 @@ export function addNewInteraction(interactionId, ufoName, type, startTime, rate,
735
737
  if ((_getConfig11 = getConfig()) !== null && _getConfig11 !== void 0 && (_getConfig11 = _getConfig11.postInteractionLog) !== null && _getConfig11 !== void 0 && _getConfig11.enabled) {
736
738
  postInteractionLog.reset();
737
739
  }
740
+ var vcObserver;
738
741
  var previousTime = startTime;
739
742
  var timeoutTime = fg('platform_ufo_enable_timeout_config') ? getInteractionTimeout(ufoName) : CLEANUP_TIMEOUT;
740
743
  var timerID = setTimeout(function () {
@@ -757,6 +760,21 @@ export function addNewInteraction(interactionId, ufoName, type, startTime, rate,
757
760
  this.timerID = newTimerID;
758
761
  }
759
762
  var addFeatureFlagsToInteraction = coinflip(getCapabilityRate('feature_flag_access'));
763
+ var config = getConfig();
764
+ if (config && config.vc) {
765
+ var vcOptions = {
766
+ heatmapSize: config.vc.heatmapSize,
767
+ oldDomUpdates: config.vc.oldDomUpdates,
768
+ devToolsEnabled: config.vc.devToolsEnabled,
769
+ selectorConfig: config.vc.selectorConfig,
770
+ ssrEnablePageLayoutPlaceholder: config.vc.ssrEnablePageLayoutPlaceholder,
771
+ disableSizeAndPositionCheck: config.vc.disableSizeAndPositionCheck
772
+ };
773
+ vcObserver = fg('platform_ufo_enable_vc_observer_per_interaction') ? newVCObserver(vcOptions) : undefined;
774
+ }
775
+
776
+ // Create per-interaction VC observer when feature flag is enabled
777
+
760
778
  var metrics = {
761
779
  id: interactionId,
762
780
  start: startTime,
@@ -797,7 +815,8 @@ export function addNewInteraction(interactionId, ufoName, type, startTime, rate,
797
815
  redirects: [],
798
816
  timerID: timerID,
799
817
  changeTimeout: changeTimeout,
800
- trace: trace
818
+ trace: trace,
819
+ vcObserver: vcObserver
801
820
  };
802
821
  if (addFeatureFlagsToInteraction) {
803
822
  currentFeatureFlagsAccessed.clear();
@@ -816,13 +835,21 @@ export function addNewInteraction(interactionId, ufoName, type, startTime, rate,
816
835
  metrics.cleanupCallbacks.push(function () {
817
836
  clearTimeout(metrics.timerID);
818
837
  });
838
+ // Add cleanup for per-interaction VC observer
839
+ if (vcObserver) {
840
+ metrics.cleanupCallbacks.push(function () {
841
+ vcObserver.stop(ufoName);
842
+ });
843
+ }
819
844
  var awaitBM3TTIList = getAwaitBM3TTIList();
820
845
  if (awaitBM3TTIList.includes(ufoName)) {
821
846
  addHoldByID(interactionId, [], ufoName, ufoName, true);
822
847
  }
823
- if (type === 'transition') {
848
+ if (type === 'transition' || type === 'page_load') {
824
849
  var _getConfig12;
825
- getVCObserver().start({
850
+ // Use per-interaction VC observer if available, otherwise fall back to global
851
+ var observer = vcObserver || getVCObserver();
852
+ observer.start({
826
853
  startTime: startTime,
827
854
  experienceKey: ufoName
828
855
  });
@@ -835,20 +862,13 @@ export function addNewInteraction(interactionId, ufoName, type, startTime, rate,
835
862
  });
836
863
  }
837
864
  }
838
- if (type === 'press' && (fg('platform_ufo_enable_interactions_vc') || fg('platform_ufo_enable_interactivity_jsm'))) {
839
- var _getConfig13;
840
- getVCObserver().start({
865
+ if (type === 'press' && fg('platform_ufo_enable_vc_press_interactions')) {
866
+ // Use per-interaction VC observer if available, otherwise fall back to global
867
+ var _observer = vcObserver || getVCObserver();
868
+ _observer.start({
841
869
  startTime: startTime,
842
870
  experienceKey: ufoName
843
871
  });
844
- postInteractionLog.startVCObserver({
845
- startTime: startTime
846
- });
847
- if ((_getConfig13 = getConfig()) !== null && _getConfig13 !== void 0 && (_getConfig13 = _getConfig13.experimentalInteractionMetrics) !== null && _getConfig13 !== void 0 && _getConfig13.enabled) {
848
- experimentalVC.start({
849
- startTime: startTime
850
- });
851
- }
852
872
  }
853
873
  }
854
874
  export function addBrowserMetricEvent(event) {