@atlaskit/react-ufo 4.15.3 → 4.15.5

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 (34) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/cjs/segment/segment.js +10 -1
  3. package/dist/cjs/segment/ssr-render-profiler.js +79 -0
  4. package/dist/cjs/vc/vc-observer/observers/ssr-placeholders/index.js +7 -0
  5. package/dist/cjs/vc/vc-observer-new/metric-calculator/vcnext/index.js +4 -1
  6. package/dist/cjs/vc/vc-observer-new/viewport-observer/index.js +290 -191
  7. package/dist/cjs/vc/vc-observer-new/viewport-observer/intersection-observer/index.js +45 -2
  8. package/dist/cjs/vc/vc-observer-new/viewport-observer/utils/get-mutated-elements.js +15 -4
  9. package/dist/cjs/vc/vc-observer-new/viewport-observer/utils/is-zero-dimension-rectangle.js +9 -0
  10. package/dist/es2019/segment/segment.js +10 -1
  11. package/dist/es2019/segment/ssr-render-profiler.js +68 -0
  12. package/dist/es2019/vc/vc-observer/observers/ssr-placeholders/index.js +5 -0
  13. package/dist/es2019/vc/vc-observer-new/metric-calculator/vcnext/index.js +4 -1
  14. package/dist/es2019/vc/vc-observer-new/viewport-observer/index.js +158 -81
  15. package/dist/es2019/vc/vc-observer-new/viewport-observer/intersection-observer/index.js +33 -2
  16. package/dist/es2019/vc/vc-observer-new/viewport-observer/utils/get-mutated-elements.js +15 -4
  17. package/dist/es2019/vc/vc-observer-new/viewport-observer/utils/is-zero-dimension-rectangle.js +3 -0
  18. package/dist/esm/segment/segment.js +10 -1
  19. package/dist/esm/segment/ssr-render-profiler.js +72 -0
  20. package/dist/esm/vc/vc-observer/observers/ssr-placeholders/index.js +7 -0
  21. package/dist/esm/vc/vc-observer-new/metric-calculator/vcnext/index.js +4 -1
  22. package/dist/esm/vc/vc-observer-new/viewport-observer/index.js +288 -189
  23. package/dist/esm/vc/vc-observer-new/viewport-observer/intersection-observer/index.js +45 -2
  24. package/dist/esm/vc/vc-observer-new/viewport-observer/utils/get-mutated-elements.js +15 -4
  25. package/dist/esm/vc/vc-observer-new/viewport-observer/utils/is-zero-dimension-rectangle.js +3 -0
  26. package/dist/types/segment/ssr-render-profiler.d.ts +9 -0
  27. package/dist/types/vc/vc-observer/observers/ssr-placeholders/index.d.ts +1 -0
  28. package/dist/types/vc/vc-observer-new/types.d.ts +1 -1
  29. package/dist/types/vc/vc-observer-new/viewport-observer/utils/is-zero-dimension-rectangle.d.ts +1 -0
  30. package/dist/types-ts4.5/segment/ssr-render-profiler.d.ts +9 -0
  31. package/dist/types-ts4.5/vc/vc-observer/observers/ssr-placeholders/index.d.ts +1 -0
  32. package/dist/types-ts4.5/vc/vc-observer-new/types.d.ts +1 -1
  33. package/dist/types-ts4.5/vc/vc-observer-new/viewport-observer/utils/is-zero-dimension-rectangle.d.ts +1 -0
  34. package/package.json +10 -1
@@ -10,9 +10,21 @@ var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
10
10
  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; } } }; }
11
11
  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; } }
12
12
  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; }
13
+ function isElementStyledWithDisplayContents(element) {
14
+ var _window2;
15
+ // To minimise calling `getComputedStyle`, we are making an assumption that if an element is from the Entrypoints framework, then it will have `display: contents` styling
16
+ // as per https://bitbucket.org/atlassian/atlassian-frontend-monorepo/src/e4ccf437262ef4c0fd3c651ffb7ad4770b15aed4/jira/src/packages/platform/entry-points/entry-point-placeholder/src/index.tsx#lines-136
17
+ if ((0, _platformFeatureFlags.fg)('platform_ufo_detect_entrypoint_parent')) {
18
+ var _window;
19
+ if (element.hasAttribute('data-ep-placeholder-id')) {
20
+ return true;
21
+ }
22
+ return ((_window = window) === null || _window === void 0 || (_window = _window.getComputedStyle(element)) === null || _window === void 0 ? void 0 : _window.display) === 'contents';
23
+ }
24
+ return ((_window2 = window) === null || _window2 === void 0 || (_window2 = _window2.getComputedStyle(element)) === null || _window2 === void 0 ? void 0 : _window2.display) === 'contents';
25
+ }
13
26
  var MAX_NESTED_LEVELS_OF_DISPLAY_CONTENT_ELEMENTS_HANDLED = 3;
14
27
  function getMutatedElements(element) {
15
- var _window;
16
28
  var depthLevel = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
17
29
  if ((0, _platformFeatureFlags.fg)('platform_ufo_disable_vcnext_observations')) {
18
30
  return [{
@@ -20,16 +32,15 @@ function getMutatedElements(element) {
20
32
  isDisplayContentsElementChildren: false
21
33
  }];
22
34
  }
23
- if (((_window = window) === null || _window === void 0 || (_window = _window.getComputedStyle(element)) === null || _window === void 0 ? void 0 : _window.display) === 'contents') {
35
+ if (isElementStyledWithDisplayContents(element)) {
24
36
  var mutatedElements = [];
25
37
  var nestedDisplayContentsElementChildren = [];
26
38
  var _iterator = _createForOfIteratorHelper(element.children),
27
39
  _step;
28
40
  try {
29
41
  for (_iterator.s(); !(_step = _iterator.n()).done;) {
30
- var _window2;
31
42
  var child = _step.value;
32
- if (((_window2 = window) === null || _window2 === void 0 || (_window2 = _window2.getComputedStyle(child)) === null || _window2 === void 0 ? void 0 : _window2.display) === 'contents') {
43
+ if (isElementStyledWithDisplayContents(child)) {
33
44
  nestedDisplayContentsElementChildren.push(child);
34
45
  }
35
46
  mutatedElements.push({
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.isZeroDimensionRectangle = isZeroDimensionRectangle;
7
+ function isZeroDimensionRectangle(rect) {
8
+ return rect.bottom === 0 && rect.top === 0 && rect.left === 0 && rect.right === 0 && rect.x === 0 && rect.y === 0 && rect.width === 0 && rect.height === 0;
9
+ }
@@ -2,6 +2,7 @@ import React, { Profiler, useCallback, useContext, useEffect, useMemo, useRef }
2
2
  import { unstable_NormalPriority as NormalPriority, unstable_scheduleCallback as scheduleCallback } from 'scheduler';
3
3
  // eslint-disable-next-line @atlaskit/platform/prefer-crypto-random-uuid -- Use crypto.randomUUID instead
4
4
  import { v4 as createUUID } from 'uuid';
5
+ import { fg } from '@atlaskit/platform-feature-flags';
5
6
  import coinflip from '../coinflip';
6
7
  import { getConfig, getDoNotAbortActivePressInteraction, getInteractionRate, getMinorInteractions } from '../config';
7
8
  import { getActiveTrace, setInteractionActiveTrace } from '../experience-trace-id-context';
@@ -11,6 +12,7 @@ import { abortByNewInteraction, addApdex, addCustomData, addCustomTiming, addHol
11
12
  import UFORouteName from '../route-name-context';
12
13
  import generateId from '../short-id';
13
14
  import scheduleOnPaint from './schedule-on-paint';
15
+ import SsrRenderProfiler from './ssr-render-profiler';
14
16
  let tryCompleteHandle;
15
17
  let hasMarkedFirstSegmentLoad = false;
16
18
 
@@ -255,11 +257,18 @@ export default function UFOSegment({
255
257
  };
256
258
  }, [interactionId, parentContext, interactionContext, labelStack]);
257
259
  const reactProfilerId = useMemo(() => labelStack.map(l => l.name).join('/'), [labelStack]);
258
- return /*#__PURE__*/React.createElement(UFOInteractionContext.Provider, {
260
+ const ufoSegment = /*#__PURE__*/React.createElement(UFOInteractionContext.Provider, {
259
261
  value: interactionContext
260
262
  }, /*#__PURE__*/React.createElement(Profiler, {
261
263
  id: reactProfilerId,
262
264
  onRender: onRender
263
265
  }, children));
266
+ if (fg('platform_ufo_ssr_render_profiler')) {
267
+ return /*#__PURE__*/React.createElement(SsrRenderProfiler, {
268
+ labelStack: labelStack,
269
+ onRender: interactionContext.onRender
270
+ }, ufoSegment);
271
+ }
272
+ return ufoSegment;
264
273
  }
265
274
  UFOSegment.displayName = 'UFOSegment';
@@ -0,0 +1,68 @@
1
+ var _process, _process$env;
2
+ import React, { useMemo } from 'react';
3
+ import { getActiveInteraction } from '../interaction-metrics';
4
+
5
+ // These are stored outside react context to be resilient to concurrent mode
6
+ // restarting the start marker from a suspense and losing the initial render
7
+ const startTimes = new Map();
8
+ // Keep track of the last interaction id and reset the start timers if it ever changes.
9
+ // This is to allow multi-step ssr to track the render timers from different "interaction"s seperately
10
+ let lastActiveInteraction;
11
+ function checkActiveInteractionAndResetStartMarksIfSet() {
12
+ var _getActiveInteraction;
13
+ const activeInteractionId = (_getActiveInteraction = getActiveInteraction()) === null || _getActiveInteraction === void 0 ? void 0 : _getActiveInteraction.id;
14
+ if (!!lastActiveInteraction && lastActiveInteraction !== activeInteractionId) {
15
+ startTimes.clear();
16
+ }
17
+ lastActiveInteraction = activeInteractionId;
18
+ }
19
+ const onStartRender = id => {
20
+ var _startTimes$get;
21
+ if (!startTimes.has(id)) {
22
+ startTimes.set(id, []);
23
+ }
24
+ (_startTimes$get = startTimes.get(id)) === null || _startTimes$get === void 0 ? void 0 : _startTimes$get.push(performance.now());
25
+ };
26
+ const isInSSR = Boolean(globalThis === null || globalThis === void 0 ? void 0 : globalThis.__SERVER__) || typeof process !== 'undefined' && Boolean(((_process = process) === null || _process === void 0 ? void 0 : (_process$env = _process.env) === null || _process$env === void 0 ? void 0 : _process$env.REACT_SSR) || false);
27
+ const ProfilerMarker = ({
28
+ onRender
29
+ }) => {
30
+ if (isInSSR) {
31
+ onRender === null || onRender === void 0 ? void 0 : onRender();
32
+ }
33
+ return null;
34
+ };
35
+ export const SsrRenderProfilerInner = ({
36
+ children,
37
+ labelStack,
38
+ onRender
39
+ }) => {
40
+ const reactProfilerId = useMemo(() => labelStack.map(l => l.name).join('/'), [labelStack]);
41
+ checkActiveInteractionAndResetStartMarksIfSet();
42
+ return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(ProfilerMarker, {
43
+ onRender: () => onStartRender(reactProfilerId)
44
+ }), children, /*#__PURE__*/React.createElement(ProfilerMarker, {
45
+ onRender: () => {
46
+ const startTimesForId = startTimes.get(reactProfilerId);
47
+ if (startTimesForId !== null && startTimesForId !== void 0 && startTimesForId.length) {
48
+ const endTime = performance.now();
49
+ const firstStartTime = startTimesForId[0];
50
+ const lastStartTime = startTimesForId[startTimesForId.length - 1];
51
+ const baseDuration = endTime - lastStartTime;
52
+ const actualDuration = endTime - firstStartTime;
53
+ onRender('mount',
54
+ // this is incorrect, but on the server there is no mount phase
55
+ actualDuration, baseDuration, firstStartTime, endTime);
56
+ }
57
+ }
58
+ }));
59
+ };
60
+ const SsrRenderProfiler = props => {
61
+ if (isInSSR) {
62
+ return /*#__PURE__*/React.createElement(SsrRenderProfilerInner, props);
63
+ }
64
+
65
+ // ensure structure similar to SSR implementation
66
+ return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(ProfilerMarker, null), props.children);
67
+ };
68
+ export default SsrRenderProfiler;
@@ -233,6 +233,11 @@ export class SSRPlaceholderHandlers {
233
233
  }
234
234
  });
235
235
  }
236
+ validateReactComponentMatchToPlaceholderV4(el) {
237
+ el = this.findNearestPlaceholderContainerIfIgnored(el);
238
+ const id = this.getPlaceholderReplacementId(el);
239
+ return this.staticPlaceholders.has(id);
240
+ }
236
241
  hasSameSizePosition(rect, boundingClientRect) {
237
242
  if (!rect) {
238
243
  return false;
@@ -8,7 +8,10 @@ const getConsideredEntryTypes = () => {
8
8
  if (fg('platform_ufo_remove_ssr_placeholder_in_ttvc_v4')) {
9
9
  consideredEntryTypes.push('mutation:ssr-placeholder');
10
10
  }
11
- return ['mutation:display-contents-children-element'];
11
+ if (fg('platform_ufo_detect_zero_dimension_rectangles')) {
12
+ consideredEntryTypes.push('mutation:display-contents-children-attribute');
13
+ }
14
+ return consideredEntryTypes;
12
15
  };
13
16
  const getExcludedEntryTypes = () => {
14
17
  const excludedEntryTypes = ['layout-shift:same-rect'];
@@ -31,6 +31,67 @@ const createElementMutationsWatcher = removedNodeRects => ({
31
31
  }
32
32
  return 'mutation:element';
33
33
  };
34
+ const createElementMutationsWatcherV4 = (removedNodeRects, isWithinThirdPartySegment, hasSameDeletedNode, timestamp, isTargetReactRoot, getSSRState, getSSRPlaceholderHandler) => ({
35
+ target,
36
+ rect
37
+ }) => {
38
+ if (getSSRState) {
39
+ const ssrState = getSSRState();
40
+ const SSRStateEnum = {
41
+ normal: 1,
42
+ waitingForFirstRender: 2,
43
+ ignoring: 3
44
+ };
45
+ if (ssrState.state === SSRStateEnum.waitingForFirstRender && timestamp > ssrState.renderStart && isTargetReactRoot) {
46
+ ssrState.state = SSRStateEnum.ignoring;
47
+ if (ssrState.renderStop === -1) {
48
+ // arbitrary 500ms DOM update window
49
+ ssrState.renderStop = timestamp + 500;
50
+ }
51
+ return 'ssr-hydration';
52
+ }
53
+ if (ssrState.state === SSRStateEnum.ignoring && timestamp > ssrState.renderStart && isTargetReactRoot) {
54
+ if (timestamp <= ssrState.renderStop) {
55
+ return 'ssr-hydration';
56
+ } else {
57
+ ssrState.state = SSRStateEnum.normal;
58
+ }
59
+ }
60
+ }
61
+ if (getSSRPlaceholderHandler) {
62
+ const ssrPlaceholderHandler = getSSRPlaceholderHandler();
63
+ if (ssrPlaceholderHandler) {
64
+ if ((ssrPlaceholderHandler.isPlaceholder(target) || ssrPlaceholderHandler.isPlaceholderIgnored(target)) && ssrPlaceholderHandler.checkIfExistedAndSizeMatchingV3(target)) {
65
+ return 'mutation:ssr-placeholder';
66
+ }
67
+ if ((ssrPlaceholderHandler.isPlaceholderReplacement(target) || ssrPlaceholderHandler.isPlaceholderIgnored(target)) && ssrPlaceholderHandler.validateReactComponentMatchToPlaceholderV4(target)) {
68
+ return 'mutation:ssr-placeholder';
69
+ }
70
+ }
71
+ }
72
+ if (hasSameDeletedNode && isInVCIgnoreIfNoLayoutShiftMarker(target)) {
73
+ return 'mutation:remount';
74
+ }
75
+ if (isContainedWithinMediaWrapper(target)) {
76
+ return 'mutation:media';
77
+ }
78
+ if (isWithinThirdPartySegment) {
79
+ return 'mutation:third-party-element';
80
+ }
81
+ const isInIgnoreLsMarker = isInVCIgnoreIfNoLayoutShiftMarker(target);
82
+ if (!isInIgnoreLsMarker) {
83
+ return 'mutation:element';
84
+ }
85
+ const isRLLPlaceholder = RLLPlaceholderHandlers.getInstance().isRLLPlaceholderHydration(rect);
86
+ if (isRLLPlaceholder && isInIgnoreLsMarker) {
87
+ return 'mutation:rll-placeholder';
88
+ }
89
+ const wasDeleted = removedNodeRects.some(nr => isSameRectDimensions(nr, rect));
90
+ if (wasDeleted && isInIgnoreLsMarker) {
91
+ return 'mutation:element-replacement';
92
+ }
93
+ return 'mutation:element';
94
+ };
34
95
  export default class ViewportObserver {
35
96
  // SSR context functions
36
97
 
@@ -81,96 +142,112 @@ export default class ViewportObserver {
81
142
  if (!addedNode) {
82
143
  continue;
83
144
  }
84
- for (const {
85
- isDisplayContentsElementChildren,
86
- element
87
- } of getMutatedElements(addedNode)) {
88
- // SSR hydration logic
89
- if (this.getSSRState) {
90
- const ssrState = this.getSSRState();
91
- const SSRStateEnum = {
92
- normal: 1,
93
- waitingForFirstRender: 2,
94
- ignoring: 3
95
- };
96
- if (ssrState.state === SSRStateEnum.waitingForFirstRender && timestamp > ssrState.renderStart && targetNode === ssrState.reactRootElement) {
97
- var _this$intersectionObs;
98
- ssrState.state = SSRStateEnum.ignoring;
99
- if (ssrState.renderStop === -1) {
100
- // arbitrary 500ms DOM update window
101
- ssrState.renderStop = timestamp + 500;
102
- }
103
- (_this$intersectionObs = this.intersectionObserver) === null || _this$intersectionObs === void 0 ? void 0 : _this$intersectionObs.watchAndTag(element, 'ssr-hydration');
104
- continue;
145
+ if (fg('platform_ufo_detect_zero_dimension_rectangles')) {
146
+ var _this$getSSRState, _this$getSSRState$cal, _this$intersectionObs;
147
+ const hasSameDeletedNode = removedNodes.find(ref => {
148
+ const n = ref.deref();
149
+ if (!n || !addedNode) {
150
+ return false;
105
151
  }
106
- if (ssrState.state === SSRStateEnum.ignoring && timestamp > ssrState.renderStart && targetNode === ssrState.reactRootElement) {
107
- if (timestamp <= ssrState.renderStop) {
152
+ return n.isEqualNode(addedNode);
153
+ });
154
+ const {
155
+ isWithin: isWithinThirdPartySegment
156
+ } = checkWithinComponent(addedNode, 'UFOThirdPartySegment', this.mapIs3pResult);
157
+ const isTargetReactRoot = targetNode === ((_this$getSSRState = this.getSSRState) === null || _this$getSSRState === void 0 ? void 0 : (_this$getSSRState$cal = _this$getSSRState.call(this)) === null || _this$getSSRState$cal === void 0 ? void 0 : _this$getSSRState$cal.reactRootElement);
158
+ (_this$intersectionObs = this.intersectionObserver) === null || _this$intersectionObs === void 0 ? void 0 : _this$intersectionObs.watchAndTag(addedNode, createElementMutationsWatcherV4(removedNodeRects, isWithinThirdPartySegment, !!hasSameDeletedNode, timestamp, isTargetReactRoot, this.getSSRState, this.getSSRPlaceholderHandler));
159
+ } else {
160
+ for (const {
161
+ isDisplayContentsElementChildren,
162
+ element
163
+ } of getMutatedElements(addedNode)) {
164
+ // SSR hydration logic
165
+ if (this.getSSRState) {
166
+ const ssrState = this.getSSRState();
167
+ const SSRStateEnum = {
168
+ normal: 1,
169
+ waitingForFirstRender: 2,
170
+ ignoring: 3
171
+ };
172
+ if (ssrState.state === SSRStateEnum.waitingForFirstRender && timestamp > ssrState.renderStart && targetNode === ssrState.reactRootElement) {
108
173
  var _this$intersectionObs2;
174
+ ssrState.state = SSRStateEnum.ignoring;
175
+ if (ssrState.renderStop === -1) {
176
+ // arbitrary 500ms DOM update window
177
+ ssrState.renderStop = timestamp + 500;
178
+ }
109
179
  (_this$intersectionObs2 = this.intersectionObserver) === null || _this$intersectionObs2 === void 0 ? void 0 : _this$intersectionObs2.watchAndTag(element, 'ssr-hydration');
110
180
  continue;
111
- } else {
112
- ssrState.state = SSRStateEnum.normal;
113
181
  }
114
- }
115
- }
116
-
117
- // SSR placeholder logic - check and handle with await
118
- if (this.getSSRPlaceholderHandler) {
119
- const ssrPlaceholderHandler = this.getSSRPlaceholderHandler();
120
- if (ssrPlaceholderHandler) {
121
- if (ssrPlaceholderHandler.isPlaceholder(element) || ssrPlaceholderHandler.isPlaceholderIgnored(element)) {
122
- if (ssrPlaceholderHandler.checkIfExistedAndSizeMatchingV3(element)) {
182
+ if (ssrState.state === SSRStateEnum.ignoring && timestamp > ssrState.renderStart && targetNode === ssrState.reactRootElement) {
183
+ if (timestamp <= ssrState.renderStop) {
123
184
  var _this$intersectionObs3;
124
- (_this$intersectionObs3 = this.intersectionObserver) === null || _this$intersectionObs3 === void 0 ? void 0 : _this$intersectionObs3.watchAndTag(element, 'mutation:ssr-placeholder');
185
+ (_this$intersectionObs3 = this.intersectionObserver) === null || _this$intersectionObs3 === void 0 ? void 0 : _this$intersectionObs3.watchAndTag(element, 'ssr-hydration');
125
186
  continue;
187
+ } else {
188
+ ssrState.state = SSRStateEnum.normal;
126
189
  }
127
- // If result is false, continue to normal mutation logic below
128
190
  }
129
- if (ssrPlaceholderHandler.isPlaceholderReplacement(element) || ssrPlaceholderHandler.isPlaceholderIgnored(element)) {
130
- const result = await ssrPlaceholderHandler.validateReactComponentMatchToPlaceholder(element);
131
- if (result !== false) {
132
- var _this$intersectionObs4;
133
- (_this$intersectionObs4 = this.intersectionObserver) === null || _this$intersectionObs4 === void 0 ? void 0 : _this$intersectionObs4.watchAndTag(element, 'mutation:ssr-placeholder');
134
- continue;
191
+ }
192
+
193
+ // SSR placeholder logic - check and handle with await
194
+ if (this.getSSRPlaceholderHandler) {
195
+ const ssrPlaceholderHandler = this.getSSRPlaceholderHandler();
196
+ if (ssrPlaceholderHandler) {
197
+ if (ssrPlaceholderHandler.isPlaceholder(element) || ssrPlaceholderHandler.isPlaceholderIgnored(element)) {
198
+ if (ssrPlaceholderHandler.checkIfExistedAndSizeMatchingV3(element)) {
199
+ var _this$intersectionObs4;
200
+ (_this$intersectionObs4 = this.intersectionObserver) === null || _this$intersectionObs4 === void 0 ? void 0 : _this$intersectionObs4.watchAndTag(element, 'mutation:ssr-placeholder');
201
+ continue;
202
+ }
203
+ // If result is false, continue to normal mutation logic below
204
+ }
205
+ if (ssrPlaceholderHandler.isPlaceholderReplacement(element) || ssrPlaceholderHandler.isPlaceholderIgnored(element)) {
206
+ const result = await ssrPlaceholderHandler.validateReactComponentMatchToPlaceholder(element);
207
+ if (result !== false) {
208
+ var _this$intersectionObs5;
209
+ (_this$intersectionObs5 = this.intersectionObserver) === null || _this$intersectionObs5 === void 0 ? void 0 : _this$intersectionObs5.watchAndTag(element, 'mutation:ssr-placeholder');
210
+ continue;
211
+ }
212
+ // If result is false, continue to normal mutation logic below
135
213
  }
136
- // If result is false, continue to normal mutation logic below
137
214
  }
138
215
  }
139
- }
140
- const sameDeletedNode = removedNodes.find(ref => {
141
- const n = ref.deref();
142
- if (!n || !element) {
143
- return false;
216
+ const sameDeletedNode = removedNodes.find(ref => {
217
+ const n = ref.deref();
218
+ if (!n || !element) {
219
+ return false;
220
+ }
221
+ return n.isEqualNode(element);
222
+ });
223
+ const isInIgnoreLsMarker = element instanceof HTMLElement ? isInVCIgnoreIfNoLayoutShiftMarker(element) : false;
224
+ if (sameDeletedNode && isInIgnoreLsMarker) {
225
+ var _this$intersectionObs6;
226
+ (_this$intersectionObs6 = this.intersectionObserver) === null || _this$intersectionObs6 === void 0 ? void 0 : _this$intersectionObs6.watchAndTag(element, 'mutation:remount');
227
+ continue;
228
+ }
229
+ if (isContainedWithinMediaWrapper(element)) {
230
+ var _this$intersectionObs7;
231
+ (_this$intersectionObs7 = this.intersectionObserver) === null || _this$intersectionObs7 === void 0 ? void 0 : _this$intersectionObs7.watchAndTag(element, 'mutation:media');
232
+ continue;
233
+ }
234
+ const {
235
+ isWithin: isWithinThirdPartySegment
236
+ } = element instanceof HTMLElement ? checkWithinComponent(element, 'UFOThirdPartySegment', this.mapIs3pResult) : {
237
+ isWithin: false
238
+ };
239
+ if (isWithinThirdPartySegment) {
240
+ var _this$intersectionObs8;
241
+ (_this$intersectionObs8 = this.intersectionObserver) === null || _this$intersectionObs8 === void 0 ? void 0 : _this$intersectionObs8.watchAndTag(element, 'mutation:third-party-element');
242
+ continue;
243
+ }
244
+ if (isDisplayContentsElementChildren) {
245
+ var _this$intersectionObs9;
246
+ (_this$intersectionObs9 = this.intersectionObserver) === null || _this$intersectionObs9 === void 0 ? void 0 : _this$intersectionObs9.watchAndTag(element, 'mutation:display-contents-children-element');
247
+ } else {
248
+ var _this$intersectionObs0;
249
+ (_this$intersectionObs0 = this.intersectionObserver) === null || _this$intersectionObs0 === void 0 ? void 0 : _this$intersectionObs0.watchAndTag(element, createElementMutationsWatcher(removedNodeRects));
144
250
  }
145
- return n.isEqualNode(element);
146
- });
147
- const isInIgnoreLsMarker = element instanceof HTMLElement ? isInVCIgnoreIfNoLayoutShiftMarker(element) : false;
148
- if (sameDeletedNode && isInIgnoreLsMarker) {
149
- var _this$intersectionObs5;
150
- (_this$intersectionObs5 = this.intersectionObserver) === null || _this$intersectionObs5 === void 0 ? void 0 : _this$intersectionObs5.watchAndTag(element, 'mutation:remount');
151
- continue;
152
- }
153
- if (isContainedWithinMediaWrapper(element)) {
154
- var _this$intersectionObs6;
155
- (_this$intersectionObs6 = this.intersectionObserver) === null || _this$intersectionObs6 === void 0 ? void 0 : _this$intersectionObs6.watchAndTag(element, 'mutation:media');
156
- continue;
157
- }
158
- const {
159
- isWithin: isWithinThirdPartySegment
160
- } = element instanceof HTMLElement ? checkWithinComponent(element, 'UFOThirdPartySegment', this.mapIs3pResult) : {
161
- isWithin: false
162
- };
163
- if (isWithinThirdPartySegment) {
164
- var _this$intersectionObs7;
165
- (_this$intersectionObs7 = this.intersectionObserver) === null || _this$intersectionObs7 === void 0 ? void 0 : _this$intersectionObs7.watchAndTag(element, 'mutation:third-party-element');
166
- continue;
167
- }
168
- if (isDisplayContentsElementChildren) {
169
- var _this$intersectionObs8;
170
- (_this$intersectionObs8 = this.intersectionObserver) === null || _this$intersectionObs8 === void 0 ? void 0 : _this$intersectionObs8.watchAndTag(element, 'mutation:display-contents-children-element');
171
- } else {
172
- var _this$intersectionObs9;
173
- (_this$intersectionObs9 = this.intersectionObserver) === null || _this$intersectionObs9 === void 0 ? void 0 : _this$intersectionObs9.watchAndTag(element, createElementMutationsWatcher(removedNodeRects));
174
251
  }
175
252
  }
176
253
  }
@@ -181,8 +258,8 @@ export default class ViewportObserver {
181
258
  oldValue,
182
259
  newValue
183
260
  }) => {
184
- var _this$intersectionObs0;
185
- (_this$intersectionObs0 = this.intersectionObserver) === null || _this$intersectionObs0 === void 0 ? void 0 : _this$intersectionObs0.watchAndTag(target, ({
261
+ var _this$intersectionObs1;
262
+ (_this$intersectionObs1 = this.intersectionObserver) === null || _this$intersectionObs1 === void 0 ? void 0 : _this$intersectionObs1.watchAndTag(target, ({
186
263
  target,
187
264
  rect
188
265
  }) => {
@@ -339,12 +416,12 @@ export default class ViewportObserver {
339
416
  this.isStarted = true;
340
417
  }
341
418
  stop() {
342
- var _this$mutationObserve2, _this$intersectionObs1, _this$performanceObse2;
419
+ var _this$mutationObserve2, _this$intersectionObs10, _this$performanceObse2;
343
420
  if (!this.isStarted) {
344
421
  return;
345
422
  }
346
423
  (_this$mutationObserve2 = this.mutationObserver) === null || _this$mutationObserve2 === void 0 ? void 0 : _this$mutationObserve2.disconnect();
347
- (_this$intersectionObs1 = this.intersectionObserver) === null || _this$intersectionObs1 === void 0 ? void 0 : _this$intersectionObs1.disconnect();
424
+ (_this$intersectionObs10 = this.intersectionObserver) === null || _this$intersectionObs10 === void 0 ? void 0 : _this$intersectionObs10.disconnect();
348
425
  (_this$performanceObse2 = this.performanceObserver) === null || _this$performanceObse2 === void 0 ? void 0 : _this$performanceObse2.disconnect();
349
426
  this.isStarted = false;
350
427
  // Clean up caches when stopping
@@ -1,3 +1,5 @@
1
+ import { fg } from '@atlaskit/platform-feature-flags';
2
+ import { isZeroDimensionRectangle } from '../utils/is-zero-dimension-rectangle';
1
3
  function isValidEntry(entry) {
2
4
  return entry.isIntersecting && entry.intersectionRect.width > 0 && entry.intersectionRect.height > 0;
3
5
  }
@@ -14,12 +16,41 @@ export function createIntersectionObserver({
14
16
  const startTime = performance.now();
15
17
  entries.forEach(entry => {
16
18
  var _mutationTag;
17
- if (!(entry.target instanceof HTMLElement) || !isValidEntry(entry)) {
19
+ if (!(entry.target instanceof HTMLElement)) {
20
+ return;
21
+ }
22
+ const tagOrCallback = callbacksPerElement.get(entry.target);
23
+ if (fg('platform_ufo_detect_zero_dimension_rectangles')) {
24
+ if (isZeroDimensionRectangle(entry.intersectionRect)) {
25
+ const zeroDimensionRectangleTagCallback = props => {
26
+ const tagOrCallbackResult = typeof tagOrCallback === 'function' ? tagOrCallback(props) : tagOrCallback;
27
+
28
+ // override as display-contents mutation
29
+ if (tagOrCallbackResult === 'mutation:element') {
30
+ return 'mutation:display-contents-children-element';
31
+ }
32
+
33
+ // override as display-contents mutation
34
+ if (tagOrCallbackResult && typeof tagOrCallbackResult !== 'string' && tagOrCallbackResult.type === 'mutation:attribute') {
35
+ return {
36
+ type: 'mutation:display-contents-children-attribute',
37
+ mutationData: tagOrCallbackResult.mutationData
38
+ };
39
+ }
40
+ return tagOrCallbackResult;
41
+ };
42
+ for (const child of entry.target.children) {
43
+ observer.observe(child);
44
+ callbacksPerElement.set(child, zeroDimensionRectangleTagCallback);
45
+ }
46
+ return;
47
+ }
48
+ }
49
+ if (!isValidEntry(entry)) {
18
50
  return;
19
51
  }
20
52
  let mutationTag = null;
21
53
  let mutationData = null;
22
- const tagOrCallback = callbacksPerElement.get(entry.target);
23
54
  if (typeof tagOrCallback === 'function') {
24
55
  const tagOrCallbackResult = tagOrCallback({
25
56
  target: entry.target,
@@ -1,19 +1,30 @@
1
1
  import { fg } from '@atlaskit/platform-feature-flags';
2
+ function isElementStyledWithDisplayContents(element) {
3
+ var _window2, _window2$getComputedS;
4
+ // To minimise calling `getComputedStyle`, we are making an assumption that if an element is from the Entrypoints framework, then it will have `display: contents` styling
5
+ // as per https://bitbucket.org/atlassian/atlassian-frontend-monorepo/src/e4ccf437262ef4c0fd3c651ffb7ad4770b15aed4/jira/src/packages/platform/entry-points/entry-point-placeholder/src/index.tsx#lines-136
6
+ if (fg('platform_ufo_detect_entrypoint_parent')) {
7
+ var _window, _window$getComputedSt;
8
+ if (element.hasAttribute('data-ep-placeholder-id')) {
9
+ return true;
10
+ }
11
+ return ((_window = window) === null || _window === void 0 ? void 0 : (_window$getComputedSt = _window.getComputedStyle(element)) === null || _window$getComputedSt === void 0 ? void 0 : _window$getComputedSt.display) === 'contents';
12
+ }
13
+ return ((_window2 = window) === null || _window2 === void 0 ? void 0 : (_window2$getComputedS = _window2.getComputedStyle(element)) === null || _window2$getComputedS === void 0 ? void 0 : _window2$getComputedS.display) === 'contents';
14
+ }
2
15
  const MAX_NESTED_LEVELS_OF_DISPLAY_CONTENT_ELEMENTS_HANDLED = 3;
3
16
  export function getMutatedElements(element, depthLevel = 0) {
4
- var _window, _window$getComputedSt;
5
17
  if (fg('platform_ufo_disable_vcnext_observations')) {
6
18
  return [{
7
19
  element,
8
20
  isDisplayContentsElementChildren: false
9
21
  }];
10
22
  }
11
- if (((_window = window) === null || _window === void 0 ? void 0 : (_window$getComputedSt = _window.getComputedStyle(element)) === null || _window$getComputedSt === void 0 ? void 0 : _window$getComputedSt.display) === 'contents') {
23
+ if (isElementStyledWithDisplayContents(element)) {
12
24
  const mutatedElements = [];
13
25
  const nestedDisplayContentsElementChildren = [];
14
26
  for (const child of element.children) {
15
- var _window2, _window2$getComputedS;
16
- if (((_window2 = window) === null || _window2 === void 0 ? void 0 : (_window2$getComputedS = _window2.getComputedStyle(child)) === null || _window2$getComputedS === void 0 ? void 0 : _window2$getComputedS.display) === 'contents') {
27
+ if (isElementStyledWithDisplayContents(child)) {
17
28
  nestedDisplayContentsElementChildren.push(child);
18
29
  }
19
30
  mutatedElements.push({
@@ -0,0 +1,3 @@
1
+ export function isZeroDimensionRectangle(rect) {
2
+ return rect.bottom === 0 && rect.top === 0 && rect.left === 0 && rect.right === 0 && rect.x === 0 && rect.y === 0 && rect.width === 0 && rect.height === 0;
3
+ }
@@ -6,6 +6,7 @@ import React, { Profiler, useCallback, useContext, useEffect, useMemo, useRef }
6
6
  import { unstable_NormalPriority as NormalPriority, unstable_scheduleCallback as scheduleCallback } from 'scheduler';
7
7
  // eslint-disable-next-line @atlaskit/platform/prefer-crypto-random-uuid -- Use crypto.randomUUID instead
8
8
  import { v4 as createUUID } from 'uuid';
9
+ import { fg } from '@atlaskit/platform-feature-flags';
9
10
  import coinflip from '../coinflip';
10
11
  import { getConfig, getDoNotAbortActivePressInteraction, getInteractionRate, getMinorInteractions } from '../config';
11
12
  import { getActiveTrace, setInteractionActiveTrace } from '../experience-trace-id-context';
@@ -15,6 +16,7 @@ import { abortByNewInteraction, addApdex as _addApdex, addCustomData as _addCust
15
16
  import UFORouteName from '../route-name-context';
16
17
  import generateId from '../short-id';
17
18
  import scheduleOnPaint from './schedule-on-paint';
19
+ import SsrRenderProfiler from './ssr-render-profiler';
18
20
  var tryCompleteHandle;
19
21
  var hasMarkedFirstSegmentLoad = false;
20
22
 
@@ -269,11 +271,18 @@ export default function UFOSegment(_ref) {
269
271
  return l.name;
270
272
  }).join('/');
271
273
  }, [labelStack]);
272
- return /*#__PURE__*/React.createElement(UFOInteractionContext.Provider, {
274
+ var ufoSegment = /*#__PURE__*/React.createElement(UFOInteractionContext.Provider, {
273
275
  value: interactionContext
274
276
  }, /*#__PURE__*/React.createElement(Profiler, {
275
277
  id: reactProfilerId,
276
278
  onRender: onRender
277
279
  }, children));
280
+ if (fg('platform_ufo_ssr_render_profiler')) {
281
+ return /*#__PURE__*/React.createElement(SsrRenderProfiler, {
282
+ labelStack: labelStack,
283
+ onRender: interactionContext.onRender
284
+ }, ufoSegment);
285
+ }
286
+ return ufoSegment;
278
287
  }
279
288
  UFOSegment.displayName = 'UFOSegment';