@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
@@ -4,10 +4,11 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.default = void 0;
7
- var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
8
7
  function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t.return || t.return(); } finally { if (u) throw o; } } }; }
9
8
  function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
10
9
  function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
10
+ // Batched mutation data for performance optimization
11
+
11
12
  function createMutationObserver(_ref) {
12
13
  var onAttributeMutation = _ref.onAttributeMutation,
13
14
  onChildListMutation = _ref.onChildListMutation,
@@ -16,82 +17,119 @@ function createMutationObserver(_ref) {
16
17
  return null;
17
18
  }
18
19
  var mutationObserverCallback = function mutationObserverCallback(mutations) {
19
- var addedNodes = [];
20
- var removedNodes = [];
21
- var attributeMutations = [];
22
20
  var targets = [];
21
+ // Use nested Maps for O(1) batching performance
22
+ // Short-lived Maps are safe since they're discarded after each callback
23
+ var batchedMutations = new Map();
23
24
  var _iterator = _createForOfIteratorHelper(mutations),
24
25
  _step;
25
26
  try {
26
- for (_iterator.s(); !(_step = _iterator.n()).done;) {
27
- var _mut = _step.value;
28
- if (!(_mut.target instanceof HTMLElement)) {
29
- continue;
30
- }
31
- if (_mut.type === 'attributes') {
32
- var _mut$oldValue;
33
- /*
34
- "MutationObserver was explicitly designed to work that way, but I can't now recall the reasoning.
35
- I think it might have been something along the lines that for consistency every setAttribute call should create a record.
36
- Conceptually there is after all a mutation: there is an old value replaced with a new one,
37
- and whether or not they are the same doesn't really matter.
38
- And Custom elements should work the same way as MutationObserver."
39
- https://github.com/whatwg/dom/issues/520#issuecomment-336574796
40
- */
41
- var oldValue = (_mut$oldValue = _mut.oldValue) !== null && _mut$oldValue !== void 0 ? _mut$oldValue : undefined;
42
- var newValue = _mut.attributeName ? _mut.target.getAttribute(_mut.attributeName) : undefined;
43
- if (oldValue !== newValue) {
44
- if ((0, _platformFeatureFlags.fg)('platform_vc_ignore_no_ls_mutation_marker')) {
27
+ var _loop = function _loop() {
28
+ var mut = _step.value;
29
+ if (!(mut.target instanceof HTMLElement)) {
30
+ return 0; // continue
31
+ }
32
+ if (mut.type === 'attributes') {
33
+ var _mut$oldValue;
34
+ /*
35
+ "MutationObserver was explicitly designed to work that way, but I can't now recall the reasoning.
36
+ I think it might have been something along the lines that for consistency every setAttribute call should create a record.
37
+ Conceptually there is after all a mutation: there is an old value replaced with a new one,
38
+ and whether or not they are the same doesn't really matter.
39
+ And Custom elements should work the same way as MutationObserver."
40
+ https://github.com/whatwg/dom/issues/520#issuecomment-336574796
41
+ */
42
+ var oldValue = (_mut$oldValue = mut.oldValue) !== null && _mut$oldValue !== void 0 ? _mut$oldValue : undefined;
43
+ var newValue = mut.attributeName ? mut.target.getAttribute(mut.attributeName) : undefined;
44
+ if (oldValue !== newValue) {
45
45
  var _mut$attributeName;
46
- attributeMutations.push({
47
- target: new WeakRef(_mut.target),
48
- attributeName: (_mut$attributeName = _mut.attributeName) !== null && _mut$attributeName !== void 0 ? _mut$attributeName : 'unknown',
49
- oldValue: oldValue,
50
- newValue: newValue
51
- });
52
- } else {
53
- var _mut$attributeName2;
54
46
  onAttributeMutation({
55
- target: _mut.target,
56
- attributeName: (_mut$attributeName2 = _mut.attributeName) !== null && _mut$attributeName2 !== void 0 ? _mut$attributeName2 : 'unknown',
47
+ target: mut.target,
48
+ attributeName: (_mut$attributeName = mut.attributeName) !== null && _mut$attributeName !== void 0 ? _mut$attributeName : 'unknown',
57
49
  oldValue: oldValue,
58
50
  newValue: newValue
59
51
  });
60
52
  }
61
- }
62
- continue;
63
- } else if (_mut.type === 'childList') {
64
- var _mut$addedNodes, _mut$removedNodes;
65
- ((_mut$addedNodes = _mut.addedNodes) !== null && _mut$addedNodes !== void 0 ? _mut$addedNodes : []).forEach(function (node) {
66
- if (node instanceof HTMLElement) {
67
- addedNodes.push(new WeakRef(node));
53
+ return 0; // continue
54
+ } else if (mut.type === 'childList') {
55
+ var _mut$addedNodes, _mut$removedNodes;
56
+ // In chromium browser MutationRecord has timestamp field, which we should use.
57
+ var timestamp = Math.round(mut.timestamp || performance.now());
58
+
59
+ // Get or create timestamp bucket
60
+ var timestampBucket = batchedMutations.get(timestamp);
61
+ if (!timestampBucket) {
62
+ timestampBucket = new Map();
63
+ batchedMutations.set(timestamp, timestampBucket);
68
64
  }
69
- });
70
- ((_mut$removedNodes = _mut.removedNodes) !== null && _mut$removedNodes !== void 0 ? _mut$removedNodes : []).forEach(function (node) {
71
- if (node instanceof HTMLElement) {
72
- removedNodes.push(new WeakRef(node));
65
+
66
+ // Get or create target batch within timestamp bucket
67
+ var batch = timestampBucket.get(mut.target);
68
+ if (!batch) {
69
+ batch = {
70
+ target: new WeakRef(mut.target),
71
+ addedNodes: [],
72
+ removedNodes: [],
73
+ timestamp: timestamp
74
+ };
75
+ timestampBucket.set(mut.target, batch);
73
76
  }
74
- });
75
- }
76
- targets.push(_mut.target);
77
+
78
+ // Accumulate added nodes
79
+ ((_mut$addedNodes = mut.addedNodes) !== null && _mut$addedNodes !== void 0 ? _mut$addedNodes : []).forEach(function (node) {
80
+ if (node instanceof HTMLElement) {
81
+ batch.addedNodes.push(new WeakRef(node));
82
+ }
83
+ });
84
+
85
+ // Accumulate removed nodes
86
+ ((_mut$removedNodes = mut.removedNodes) !== null && _mut$removedNodes !== void 0 ? _mut$removedNodes : []).forEach(function (node) {
87
+ if (node instanceof HTMLElement) {
88
+ batch.removedNodes.push(new WeakRef(node));
89
+ }
90
+ });
91
+ }
92
+ targets.push(mut.target);
93
+ },
94
+ _ret;
95
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
96
+ _ret = _loop();
97
+ if (_ret === 0) continue;
77
98
  }
99
+
100
+ // Process all batched childList mutations
78
101
  } catch (err) {
79
102
  _iterator.e(err);
80
103
  } finally {
81
104
  _iterator.f();
82
105
  }
83
- onChildListMutation({
84
- addedNodes: addedNodes,
85
- removedNodes: removedNodes
86
- });
87
- for (var _i = 0, _attributeMutations = attributeMutations; _i < _attributeMutations.length; _i++) {
88
- var mut = _attributeMutations[_i];
89
- onAttributeMutation({
90
- target: mut.target.deref(),
91
- attributeName: mut.attributeName,
92
- oldValue: mut.oldValue,
93
- newValue: mut.newValue
94
- });
106
+ var _iterator2 = _createForOfIteratorHelper(batchedMutations.values()),
107
+ _step2;
108
+ try {
109
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
110
+ var timestampBucket = _step2.value;
111
+ var _iterator3 = _createForOfIteratorHelper(timestampBucket.values()),
112
+ _step3;
113
+ try {
114
+ for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
115
+ var batch = _step3.value;
116
+ onChildListMutation({
117
+ target: batch.target,
118
+ addedNodes: batch.addedNodes,
119
+ removedNodes: batch.removedNodes,
120
+ timestamp: batch.timestamp
121
+ });
122
+ }
123
+ } catch (err) {
124
+ _iterator3.e(err);
125
+ } finally {
126
+ _iterator3.f();
127
+ }
128
+ }
129
+ } catch (err) {
130
+ _iterator2.e(err);
131
+ } finally {
132
+ _iterator2.f();
95
133
  }
96
134
  onMutationFinished === null || onMutationFinished === void 0 || onMutationFinished({
97
135
  targets: targets
@@ -58,10 +58,12 @@ export class ExperimentalVCMetrics {
58
58
  }
59
59
  export const experimentalVC = new ExperimentalVCMetrics();
60
60
  export async function getExperimentalVCMetrics(interaction) {
61
- if (experimentalVC.vcObserver) {
61
+ // Use per-interaction VC observer if available, otherwise fall back to global experimentalVC
62
+ const vcObserver = interaction.experimentalVCObserver || experimentalVC.vcObserver;
63
+ if (vcObserver) {
62
64
  var _interaction$apdex, _interaction$apdex$;
63
65
  const prefix = 'ufo-experimental';
64
- const result = await experimentalVC.vcObserver.getVCResult({
66
+ const result = await vcObserver.getVCResult({
65
67
  start: interaction.start,
66
68
  stop: interaction.end,
67
69
  tti: (_interaction$apdex = interaction.apdex) === null || _interaction$apdex === void 0 ? void 0 : (_interaction$apdex$ = _interaction$apdex[0]) === null || _interaction$apdex$ === void 0 ? void 0 : _interaction$apdex$.stopTime,
@@ -11,7 +11,7 @@ async function getVCMetrics(interaction) {
11
11
  if (!(config !== null && config !== void 0 && (_config$vc = config.vc) !== null && _config$vc !== void 0 && _config$vc.enabled)) {
12
12
  return {};
13
13
  }
14
- if (fg('platform_ufo_enable_interactions_vc') || fg('platform_ufo_enable_interactivity_jsm')) {
14
+ if (fg('platform_ufo_enable_vc_press_interactions')) {
15
15
  if (interaction.type !== 'page_load' && interaction.type !== 'transition' && interaction.type !== 'press') {
16
16
  return {};
17
17
  }
@@ -23,8 +23,11 @@ async function getVCMetrics(interaction) {
23
23
  const interactionStatus = getInteractionStatus(interaction);
24
24
  const pageVisibilityUpToTTAI = getPageVisibilityUpToTTAI(interaction);
25
25
  const shouldReportVCMetrics = interactionStatus.originalInteractionStatus === 'SUCCEEDED' && pageVisibilityUpToTTAI === 'visible';
26
+
27
+ // Use per-interaction VC observer if available, otherwise fall back to global
28
+ const observer = interaction.vcObserver || getVCObserver();
26
29
  if (!shouldReportVCMetrics && fg('platform_ufo_no_vc_on_aborted')) {
27
- getVCObserver().stop(interaction.ufoName);
30
+ observer.stop(interaction.ufoName);
28
31
  return {};
29
32
  }
30
33
  const isSSREnabled = interaction.type === 'page_load' && ((config === null || config === void 0 ? void 0 : config.ssr) || (config === null || config === void 0 ? void 0 : (_config$vc$ssrWhiteli = config.vc.ssrWhitelist) === null || _config$vc$ssrWhiteli === void 0 ? void 0 : _config$vc$ssrWhiteli.includes(interaction.ufoName)));
@@ -34,7 +37,7 @@ async function getVCMetrics(interaction) {
34
37
  postInteractionLog.setVCObserverSSRConfig(ssr);
35
38
  const tti = (_interaction$apdex = interaction.apdex) === null || _interaction$apdex === void 0 ? void 0 : (_interaction$apdex$ = _interaction$apdex[0]) === null || _interaction$apdex$ === void 0 ? void 0 : _interaction$apdex$.stopTime;
36
39
  const prefix = 'ufo';
37
- const result = await getVCObserver().getVCResult({
40
+ const result = await observer.getVCResult({
38
41
  start: interaction.start,
39
42
  stop: interaction.end,
40
43
  tti,
@@ -47,8 +50,11 @@ async function getVCMetrics(interaction) {
47
50
  includeSSRRatio: (_config$vc3 = config.vc) === null || _config$vc3 === void 0 ? void 0 : _config$vc3.includeSSRRatio,
48
51
  ...ssr
49
52
  });
53
+ if (fg('platform_ufo_enable_vc_observer_per_interaction')) {
54
+ observer.stop(interaction.ufoName);
55
+ }
50
56
  if ((_config$experimentalI = config.experimentalInteractionMetrics) !== null && _config$experimentalI !== void 0 && _config$experimentalI.enabled) {
51
- getVCObserver().stop(interaction.ufoName);
57
+ observer.stop(interaction.ufoName);
52
58
  }
53
59
  postInteractionLog.setLastInteractionFinishVCResult(result);
54
60
  const mostRecentVCRevision = getMostRecentVCRevision(interaction.ufoName);
@@ -6,7 +6,7 @@ import { experimentalVC, getExperimentalVCMetrics, onExperimentalInteractionComp
6
6
  import { clearActiveTrace } from '../experience-trace-id-context';
7
7
  import { allFeatureFlagsAccessed, currentFeatureFlagsAccessed } from '../feature-flags-accessed';
8
8
  import { getInteractionId } from '../interaction-id-context';
9
- import { getVCObserver } from '../vc';
9
+ import { getVCObserver, newVCObserver } from '../vc';
10
10
  import { interactions } from './common/constants';
11
11
  import PostInteractionLog from './post-interaction-log';
12
12
  const PreviousInteractionLog = {
@@ -487,7 +487,9 @@ function finishInteraction(id, data, endTime = performance.now()) {
487
487
  clearActiveTrace();
488
488
  callCleanUpCallbacks(data);
489
489
  if ((_getConfig4 = getConfig()) !== null && _getConfig4 !== void 0 && (_getConfig4$vc = _getConfig4.vc) !== null && _getConfig4$vc !== void 0 && _getConfig4$vc.stopVCAtInteractionFinish) {
490
- data.vc = getVCObserver().getVCRawData();
490
+ // Use per-interaction VC observer if available, otherwise fall back to global
491
+ const observer = data.vcObserver || getVCObserver();
492
+ data.vc = observer.getVCRawData();
491
493
  }
492
494
  if (!((_getConfig5 = getConfig()) !== null && _getConfig5 !== void 0 && (_getConfig5$experimen = _getConfig5.experimentalInteractionMetrics) !== null && _getConfig5$experimen !== void 0 && _getConfig5$experimen.enabled)) {
493
495
  remove(id);
@@ -656,6 +658,7 @@ export function addNewInteraction(interactionId, ufoName, type, startTime, rate,
656
658
  if ((_getConfig11 = getConfig()) !== null && _getConfig11 !== void 0 && (_getConfig11$postInte = _getConfig11.postInteractionLog) !== null && _getConfig11$postInte !== void 0 && _getConfig11$postInte.enabled) {
657
659
  postInteractionLog.reset();
658
660
  }
661
+ let vcObserver;
659
662
  let previousTime = startTime;
660
663
  let timeoutTime = fg('platform_ufo_enable_timeout_config') ? getInteractionTimeout(ufoName) : CLEANUP_TIMEOUT;
661
664
  const timerID = setTimeout(() => {
@@ -678,6 +681,21 @@ export function addNewInteraction(interactionId, ufoName, type, startTime, rate,
678
681
  this.timerID = newTimerID;
679
682
  }
680
683
  const addFeatureFlagsToInteraction = coinflip(getCapabilityRate('feature_flag_access'));
684
+ const config = getConfig();
685
+ if (config && config.vc) {
686
+ const vcOptions = {
687
+ heatmapSize: config.vc.heatmapSize,
688
+ oldDomUpdates: config.vc.oldDomUpdates,
689
+ devToolsEnabled: config.vc.devToolsEnabled,
690
+ selectorConfig: config.vc.selectorConfig,
691
+ ssrEnablePageLayoutPlaceholder: config.vc.ssrEnablePageLayoutPlaceholder,
692
+ disableSizeAndPositionCheck: config.vc.disableSizeAndPositionCheck
693
+ };
694
+ vcObserver = fg('platform_ufo_enable_vc_observer_per_interaction') ? newVCObserver(vcOptions) : undefined;
695
+ }
696
+
697
+ // Create per-interaction VC observer when feature flag is enabled
698
+
681
699
  const metrics = {
682
700
  id: interactionId,
683
701
  start: startTime,
@@ -718,7 +736,8 @@ export function addNewInteraction(interactionId, ufoName, type, startTime, rate,
718
736
  redirects: [],
719
737
  timerID,
720
738
  changeTimeout,
721
- trace
739
+ trace,
740
+ vcObserver
722
741
  };
723
742
  if (addFeatureFlagsToInteraction) {
724
743
  currentFeatureFlagsAccessed.clear();
@@ -737,13 +756,21 @@ export function addNewInteraction(interactionId, ufoName, type, startTime, rate,
737
756
  metrics.cleanupCallbacks.push(() => {
738
757
  clearTimeout(metrics.timerID);
739
758
  });
759
+ // Add cleanup for per-interaction VC observer
760
+ if (vcObserver) {
761
+ metrics.cleanupCallbacks.push(() => {
762
+ vcObserver.stop(ufoName);
763
+ });
764
+ }
740
765
  const awaitBM3TTIList = getAwaitBM3TTIList();
741
766
  if (awaitBM3TTIList.includes(ufoName)) {
742
767
  addHoldByID(interactionId, [], ufoName, ufoName, true);
743
768
  }
744
- if (type === 'transition') {
769
+ if (type === 'transition' || type === 'page_load') {
745
770
  var _getConfig12, _getConfig12$experime;
746
- getVCObserver().start({
771
+ // Use per-interaction VC observer if available, otherwise fall back to global
772
+ const observer = vcObserver || getVCObserver();
773
+ observer.start({
747
774
  startTime,
748
775
  experienceKey: ufoName
749
776
  });
@@ -756,20 +783,13 @@ export function addNewInteraction(interactionId, ufoName, type, startTime, rate,
756
783
  });
757
784
  }
758
785
  }
759
- if (type === 'press' && (fg('platform_ufo_enable_interactions_vc') || fg('platform_ufo_enable_interactivity_jsm'))) {
760
- var _getConfig13, _getConfig13$experime;
761
- getVCObserver().start({
786
+ if (type === 'press' && fg('platform_ufo_enable_vc_press_interactions')) {
787
+ // Use per-interaction VC observer if available, otherwise fall back to global
788
+ const observer = vcObserver || getVCObserver();
789
+ observer.start({
762
790
  startTime,
763
791
  experienceKey: ufoName
764
792
  });
765
- postInteractionLog.startVCObserver({
766
- startTime
767
- });
768
- if ((_getConfig13 = getConfig()) !== null && _getConfig13 !== void 0 && (_getConfig13$experime = _getConfig13.experimentalInteractionMetrics) !== null && _getConfig13$experime !== void 0 && _getConfig13$experime.enabled) {
769
- experimentalVC.start({
770
- startTime
771
- });
772
- }
773
793
  }
774
794
  }
775
795
  export function addBrowserMetricEvent(event) {
@@ -91,9 +91,11 @@ export function init(analyticsWebClientAsync, config) {
91
91
  ssrEnablePageLayoutPlaceholder: config.vc.ssrEnablePageLayoutPlaceholder,
92
92
  disableSizeAndPositionCheck: config.vc.disableSizeAndPositionCheck
93
93
  };
94
- getVCObserver(vcOptions).start({
95
- startTime: 0
96
- });
94
+ if (!fg('platform_ufo_enable_vc_observer_per_interaction')) {
95
+ getVCObserver(vcOptions).start({
96
+ startTime: 0
97
+ });
98
+ }
97
99
  postInteractionLog.initializeVCObserver(vcOptions);
98
100
  postInteractionLog.startVCObserver({
99
101
  startTime: 0
@@ -4,17 +4,41 @@ import { VCObserverNOOP } from './no-op-vc-observer';
4
4
  import { VCObserver } from './vc-observer';
5
5
  import VCObserverNew from './vc-observer-new';
6
6
  import { RLLPlaceholderHandlers } from './vc-observer/observers/rll-placeholders';
7
+ import { SSRPlaceholderHandlers } from './vc-observer/observers/ssr-placeholders';
7
8
  export class VCObserverWrapper {
8
9
  constructor(opts = {}) {
10
+ var _opts$ssrEnablePageLa, _opts$disableSizeAndP;
9
11
  this.newVCObserver = null;
10
12
  this.oldVCObserver = null;
13
+
14
+ // Initialize SSR placeholder handler once
15
+ this.ssrPlaceholderHandler = new SSRPlaceholderHandlers({
16
+ enablePageLayoutPlaceholder: (_opts$ssrEnablePageLa = opts.ssrEnablePageLayoutPlaceholder) !== null && _opts$ssrEnablePageLa !== void 0 ? _opts$ssrEnablePageLa : false,
17
+ disableSizeAndPositionCheck: (_opts$disableSizeAndP = opts.disableSizeAndPositionCheck) !== null && _opts$disableSizeAndP !== void 0 ? _opts$disableSizeAndP : {
18
+ v: false,
19
+ h: false
20
+ }
21
+ });
11
22
  if (isVCRevisionEnabled('fy25.03')) {
23
+ var _opts$ssrEnablePageLa2, _opts$disableSizeAndP2;
12
24
  this.newVCObserver = new VCObserverNew({
13
- selectorConfig: opts.selectorConfig
25
+ selectorConfig: opts.selectorConfig,
26
+ isPostInteraction: opts.isPostInteraction,
27
+ SSRConfig: {
28
+ enablePageLayoutPlaceholder: (_opts$ssrEnablePageLa2 = opts.ssrEnablePageLayoutPlaceholder) !== null && _opts$ssrEnablePageLa2 !== void 0 ? _opts$ssrEnablePageLa2 : false,
29
+ disableSizeAndPositionCheck: (_opts$disableSizeAndP2 = opts.disableSizeAndPositionCheck) !== null && _opts$disableSizeAndP2 !== void 0 ? _opts$disableSizeAndP2 : {
30
+ v: false,
31
+ h: false
32
+ }
33
+ },
34
+ ssrPlaceholderHandler: this.ssrPlaceholderHandler
14
35
  });
15
36
  }
16
37
  if (isVCRevisionEnabled('fy25.01') || isVCRevisionEnabled('fy25.02')) {
17
- this.oldVCObserver = new VCObserver(opts);
38
+ this.oldVCObserver = new VCObserver({
39
+ ...opts,
40
+ ssrPlaceholderHandler: this.ssrPlaceholderHandler
41
+ });
18
42
  }
19
43
  }
20
44
 
@@ -68,6 +92,8 @@ export class VCObserverWrapper {
68
92
  (_this$newVCObserver2 = this.newVCObserver) === null || _this$newVCObserver2 === void 0 ? void 0 : _this$newVCObserver2.stop();
69
93
  }
70
94
  RLLPlaceholderHandlers.getInstance().reset();
95
+ // Clear shared SSR placeholder handler
96
+ this.ssrPlaceholderHandler.clear();
71
97
  }
72
98
  getVCRawData() {
73
99
  var _this$oldVCObserver$g, _this$oldVCObserver3;
@@ -94,16 +120,22 @@ export class VCObserverWrapper {
94
120
  };
95
121
  }
96
122
  setSSRElement(element) {
97
- var _this$oldVCObserver5;
123
+ var _this$oldVCObserver5, _this$newVCObserver4;
98
124
  (_this$oldVCObserver5 = this.oldVCObserver) === null || _this$oldVCObserver5 === void 0 ? void 0 : _this$oldVCObserver5.setSSRElement(element);
125
+ (_this$newVCObserver4 = this.newVCObserver) === null || _this$newVCObserver4 === void 0 ? void 0 : _this$newVCObserver4.setReactRootElement(element);
99
126
  }
100
127
  setReactRootRenderStart(startTime) {
101
- var _this$oldVCObserver6;
128
+ var _this$oldVCObserver6, _this$newVCObserver5;
102
129
  (_this$oldVCObserver6 = this.oldVCObserver) === null || _this$oldVCObserver6 === void 0 ? void 0 : _this$oldVCObserver6.setReactRootRenderStart(startTime || performance.now());
130
+ (_this$newVCObserver5 = this.newVCObserver) === null || _this$newVCObserver5 === void 0 ? void 0 : _this$newVCObserver5.setReactRootRenderStart(startTime || performance.now());
103
131
  }
104
132
  setReactRootRenderStop(stopTime) {
105
- var _this$oldVCObserver7;
133
+ var _this$oldVCObserver7, _this$newVCObserver6;
106
134
  (_this$oldVCObserver7 = this.oldVCObserver) === null || _this$oldVCObserver7 === void 0 ? void 0 : _this$oldVCObserver7.setReactRootRenderStop(stopTime || performance.now());
135
+ (_this$newVCObserver6 = this.newVCObserver) === null || _this$newVCObserver6 === void 0 ? void 0 : _this$newVCObserver6.setReactRootRenderStop(stopTime || performance.now());
136
+ }
137
+ collectSSRPlaceholders() {
138
+ this.ssrPlaceholderHandler.collectExistingPlaceholders();
107
139
  }
108
140
  }
109
141
 
@@ -133,4 +165,9 @@ export function getVCObserver(opts = {}) {
133
165
  globalThis.__vcObserver = shouldMockVCObserver ? new VCObserverNOOP() : new VCObserverWrapper(opts);
134
166
  }
135
167
  return globalThis.__vcObserver;
168
+ }
169
+ export function newVCObserver(opts = {}) {
170
+ const shouldMockVCObserver = !isEnvironmentSupported();
171
+ const observer = shouldMockVCObserver ? new VCObserverNOOP() : new VCObserverWrapper(opts);
172
+ return observer;
136
173
  }
@@ -495,7 +495,8 @@ export class VCObserver {
495
495
  this.oldDomUpdatesEnabled = options.oldDomUpdates || false;
496
496
  const {
497
497
  ssrEnablePageLayoutPlaceholder,
498
- disableSizeAndPositionCheck
498
+ disableSizeAndPositionCheck,
499
+ ssrPlaceholderHandler
499
500
  } = options;
500
501
  this.observers = new Observers({
501
502
  selectorConfig: options.selectorConfig || {
@@ -508,7 +509,8 @@ export class VCObserver {
508
509
  SSRConfig: {
509
510
  enablePageLayoutPlaceholder: ssrEnablePageLayoutPlaceholder || false,
510
511
  disableSizeAndPositionCheck: disableSizeAndPositionCheck
511
- }
512
+ },
513
+ ssrPlaceholderHandler: ssrPlaceholderHandler
512
514
  });
513
515
  this.heatmap = !isVCRevisionEnabled('fy25.01') ? [] : this.getCleanHeatmap();
514
516
  this.heatmapNext = this.getCleanHeatmap();
@@ -654,6 +656,10 @@ export class VCObserver {
654
656
  setReactRootRenderStop(stopTime = performance.now()) {
655
657
  this.observers.setReactRootRenderStop(stopTime);
656
658
  }
659
+ collectSSRPlaceholders() {
660
+ // This is handled by the shared SSRPlaceholderHandlers in VCObserverWrapper
661
+ // Individual observers don't need to implement this
662
+ }
657
663
  setAbortReason(abort, timestamp, info = '') {
658
664
  if (this.abortReason.reason === null || this.abortReason.blocking === false) {
659
665
  this.abortReason.reason = abort;
@@ -21,7 +21,6 @@ function isElementVisible(target) {
21
21
  }
22
22
  export class Observers {
23
23
  constructor(opts) {
24
- var _opts$SSRConfig, _opts$SSRConfig2;
25
24
  _defineProperty(this, "observedMutations", new WeakMap());
26
25
  _defineProperty(this, "elementsInView", new Set());
27
26
  _defineProperty(this, "callbacks", new Set());
@@ -61,10 +60,17 @@ export class Observers {
61
60
  };
62
61
  this.intersectionObserver = this.getIntersectionObserver();
63
62
  this.mutationObserver = this.getMutationObserver();
64
- this.ssrPlaceholderHandler = new SSRPlaceholderHandlers({
65
- enablePageLayoutPlaceholder: (_opts$SSRConfig = opts.SSRConfig) === null || _opts$SSRConfig === void 0 ? void 0 : _opts$SSRConfig.enablePageLayoutPlaceholder,
66
- disableSizeAndPositionCheck: (_opts$SSRConfig2 = opts.SSRConfig) === null || _opts$SSRConfig2 === void 0 ? void 0 : _opts$SSRConfig2.disableSizeAndPositionCheck
67
- });
63
+
64
+ // Use shared SSR placeholder handler if provided, otherwise create new one
65
+ if (opts.ssrPlaceholderHandler) {
66
+ this.ssrPlaceholderHandler = opts.ssrPlaceholderHandler;
67
+ } else {
68
+ var _opts$SSRConfig, _opts$SSRConfig2;
69
+ this.ssrPlaceholderHandler = new SSRPlaceholderHandlers({
70
+ enablePageLayoutPlaceholder: (_opts$SSRConfig = opts.SSRConfig) === null || _opts$SSRConfig === void 0 ? void 0 : _opts$SSRConfig.enablePageLayoutPlaceholder,
71
+ disableSizeAndPositionCheck: (_opts$SSRConfig2 = opts.SSRConfig) === null || _opts$SSRConfig2 === void 0 ? void 0 : _opts$SSRConfig2.disableSizeAndPositionCheck
72
+ });
73
+ }
68
74
  }
69
75
  isBrowserSupported() {
70
76
  return typeof window.IntersectionObserver === 'function' && typeof window.MutationObserver === 'function';
@@ -96,32 +96,8 @@ export class SSRPlaceholderHandlers {
96
96
  this.disableSizeAndPositionCheck = disableSizeAndPositionCheck;
97
97
  if (window.document) {
98
98
  try {
99
- const selector = this.enablePageLayoutPlaceholder ? '[data-ssr-placeholder],[data-testid="page-layout.root"]' : '[data-ssr-placeholder]';
100
- const existingElements = document.querySelectorAll(selector);
101
- existingElements.forEach(el => {
102
- const placeholderId = el instanceof HTMLElement && this.getPlaceholderId(el);
103
- if (placeholderId) {
104
- var _window$__SSR_PLACEHO, _this$intersectionObs2;
105
- let width = -1;
106
- let height = -1;
107
- let x = -1;
108
- let y = -1;
109
- const boundingClientRect = (_window$__SSR_PLACEHO = window.__SSR_PLACEHOLDERS_DIMENSIONS__) === null || _window$__SSR_PLACEHO === void 0 ? void 0 : _window$__SSR_PLACEHO[placeholderId];
110
- if (boundingClientRect) {
111
- width = boundingClientRect.width;
112
- height = boundingClientRect.height;
113
- x = boundingClientRect.x;
114
- y = boundingClientRect.y;
115
- }
116
- this.staticPlaceholders.set(placeholderId, {
117
- width,
118
- height,
119
- x,
120
- y
121
- });
122
- (_this$intersectionObs2 = this.intersectionObserver) === null || _this$intersectionObs2 === void 0 ? void 0 : _this$intersectionObs2.observe(el);
123
- }
124
- });
99
+ // Collect initial placeholders using SSR dimensions
100
+ this.collectPlaceholdersInternal();
125
101
  } catch (e) {} finally {
126
102
  delete window.__SSR_PLACEHOLDERS_DIMENSIONS__;
127
103
  }
@@ -133,6 +109,61 @@ export class SSRPlaceholderHandlers {
133
109
  this.getSizeCallbacks = new Map();
134
110
  this.reactValidateCallbacks = new Map();
135
111
  }
112
+ collectPlaceholdersInternal() {
113
+ const selector = this.enablePageLayoutPlaceholder ? '[data-ssr-placeholder],[data-testid="page-layout.root"]' : '[data-ssr-placeholder]';
114
+ const existingElements = document.querySelectorAll(selector);
115
+ existingElements.forEach(el => {
116
+ const placeholderId = el instanceof HTMLElement && this.getPlaceholderId(el);
117
+ if (placeholderId && !this.staticPlaceholders.has(placeholderId)) {
118
+ var _window$__SSR_PLACEHO, _this$intersectionObs2;
119
+ let width = -1;
120
+ let height = -1;
121
+ let x = -1;
122
+ let y = -1;
123
+
124
+ // Use SSR dimensions from window global if available
125
+ const boundingClientRect = (_window$__SSR_PLACEHO = window.__SSR_PLACEHOLDERS_DIMENSIONS__) === null || _window$__SSR_PLACEHO === void 0 ? void 0 : _window$__SSR_PLACEHO[placeholderId];
126
+ if (boundingClientRect) {
127
+ width = boundingClientRect.width;
128
+ height = boundingClientRect.height;
129
+ x = boundingClientRect.x;
130
+ y = boundingClientRect.y;
131
+ } else {
132
+ // Fallback to current bounding rect if SSR dimensions not available
133
+ const rect = el.getBoundingClientRect();
134
+ width = rect.width;
135
+ height = rect.height;
136
+ x = rect.x;
137
+ y = rect.y;
138
+ }
139
+ this.staticPlaceholders.set(placeholderId, {
140
+ width,
141
+ height,
142
+ x,
143
+ y
144
+ });
145
+ (_this$intersectionObs2 = this.intersectionObserver) === null || _this$intersectionObs2 === void 0 ? void 0 : _this$intersectionObs2.observe(el);
146
+ }
147
+ });
148
+ }
149
+
150
+ /**
151
+ * Added this method to be utilised for testing purposes.
152
+ * In production it collection placeholder should only happens on constructor
153
+ */
154
+ collectExistingPlaceholders() {
155
+ if (!window.document) {
156
+ return;
157
+ }
158
+ try {
159
+ // Collect placeholders using SSR dimensions or fallback to live dimensions
160
+ this.collectPlaceholdersInternal();
161
+ } catch (e) {
162
+ // Silently fail if there are any issues
163
+ } finally {
164
+ delete window.__SSR_PLACEHOLDERS_DIMENSIONS__;
165
+ }
166
+ }
136
167
  isPlaceholder(element) {
137
168
  return Boolean(this.getPlaceholderId(element));
138
169
  }