@atlaskit/react-ufo 3.14.5 → 3.14.7
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.
- package/CHANGELOG.md +16 -0
- package/dist/cjs/vc/index.js +39 -6
- package/dist/cjs/vc/vc-observer/index.js +10 -2
- package/dist/cjs/vc/vc-observer/observers/index.js +12 -7
- package/dist/cjs/vc/vc-observer/observers/ssr-placeholders/index.js +76 -40
- package/dist/cjs/vc/vc-observer-new/index.js +84 -0
- package/dist/cjs/vc/vc-observer-new/viewport-observer/index.js +227 -69
- package/dist/cjs/vc/vc-observer-new/viewport-observer/mutation-observer/index.js +103 -44
- package/dist/cjs/vc/vc-observer-new/viewport-observer/utils/is-in-vc-ignore-if-no-layout-shift-marker.js +17 -0
- package/dist/es2019/vc/index.js +37 -5
- package/dist/es2019/vc/vc-observer/index.js +8 -2
- package/dist/es2019/vc/vc-observer/observers/index.js +11 -5
- package/dist/es2019/vc/vc-observer/observers/ssr-placeholders/index.js +57 -26
- package/dist/es2019/vc/vc-observer-new/index.js +67 -1
- package/dist/es2019/vc/vc-observer-new/viewport-observer/index.js +105 -25
- package/dist/es2019/vc/vc-observer-new/viewport-observer/mutation-observer/index.js +44 -8
- package/dist/es2019/vc/vc-observer-new/viewport-observer/utils/is-in-vc-ignore-if-no-layout-shift-marker.js +11 -0
- package/dist/esm/vc/index.js +39 -6
- package/dist/esm/vc/vc-observer/index.js +10 -2
- package/dist/esm/vc/vc-observer/observers/index.js +12 -7
- package/dist/esm/vc/vc-observer/observers/ssr-placeholders/index.js +76 -40
- package/dist/esm/vc/vc-observer-new/index.js +84 -0
- package/dist/esm/vc/vc-observer-new/viewport-observer/index.js +227 -69
- package/dist/esm/vc/vc-observer-new/viewport-observer/mutation-observer/index.js +103 -44
- package/dist/esm/vc/vc-observer-new/viewport-observer/utils/is-in-vc-ignore-if-no-layout-shift-marker.js +11 -0
- package/dist/types/vc/index.d.ts +2 -0
- package/dist/types/vc/types.d.ts +2 -0
- package/dist/types/vc/vc-observer/index.d.ts +1 -0
- package/dist/types/vc/vc-observer/observers/index.d.ts +2 -0
- package/dist/types/vc/vc-observer/observers/ssr-placeholders/index.d.ts +6 -0
- package/dist/types/vc/vc-observer-new/index.d.ts +30 -0
- package/dist/types/vc/vc-observer-new/types.d.ts +1 -1
- package/dist/types/vc/vc-observer-new/viewport-observer/index.d.ts +5 -1
- package/dist/types/vc/vc-observer-new/viewport-observer/mutation-observer/index.d.ts +2 -0
- package/dist/types/vc/vc-observer-new/viewport-observer/utils/is-in-vc-ignore-if-no-layout-shift-marker.d.ts +6 -0
- package/dist/types-ts4.5/vc/index.d.ts +2 -0
- package/dist/types-ts4.5/vc/types.d.ts +2 -0
- package/dist/types-ts4.5/vc/vc-observer/index.d.ts +1 -0
- package/dist/types-ts4.5/vc/vc-observer/observers/index.d.ts +2 -0
- package/dist/types-ts4.5/vc/vc-observer/observers/ssr-placeholders/index.d.ts +6 -0
- package/dist/types-ts4.5/vc/vc-observer-new/index.d.ts +30 -0
- package/dist/types-ts4.5/vc/vc-observer-new/types.d.ts +1 -1
- package/dist/types-ts4.5/vc/vc-observer-new/viewport-observer/index.d.ts +5 -1
- package/dist/types-ts4.5/vc/vc-observer-new/viewport-observer/mutation-observer/index.d.ts +2 -0
- package/dist/types-ts4.5/vc/vc-observer-new/viewport-observer/utils/is-in-vc-ignore-if-no-layout-shift-marker.d.ts +6 -0
- package/package.json +8 -3
|
@@ -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
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
-
|
|
100
|
-
|
|
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
|
}
|
|
@@ -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({
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import _defineProperty from "@babel/runtime/helpers/defineProperty";
|
|
2
|
+
import { fg } from '@atlaskit/platform-feature-flags';
|
|
2
3
|
import { isContainedWithinMediaWrapper } from '../../vc-observer/media-wrapper/vc-utils';
|
|
3
4
|
import isNonVisualStyleMutation from '../../vc-observer/observers/non-visual-styles/is-non-visual-style-mutation';
|
|
4
5
|
import { RLLPlaceholderHandlers } from '../../vc-observer/observers/rll-placeholders';
|
|
@@ -6,13 +7,14 @@ import { createIntersectionObserver } from './intersection-observer';
|
|
|
6
7
|
import createMutationObserver from './mutation-observer';
|
|
7
8
|
import createPerformanceObserver from './performance-observer';
|
|
8
9
|
import { checkThirdPartySegmentWithIgnoreReason, createMutationTypeWithIgnoredReason } from './utils/get-component-name-and-child-props';
|
|
10
|
+
import isInVCIgnoreIfNoLayoutShiftMarker from './utils/is-in-vc-ignore-if-no-layout-shift-marker';
|
|
9
11
|
function isElementVisible(element) {
|
|
10
12
|
if (!(element instanceof HTMLElement)) {
|
|
11
13
|
return true;
|
|
12
14
|
}
|
|
13
15
|
try {
|
|
14
16
|
const visible = element.checkVisibility({
|
|
15
|
-
// @ts-
|
|
17
|
+
// @ts-ignore - visibilityProperty may not exist in all TS environments
|
|
16
18
|
visibilityProperty: true,
|
|
17
19
|
contentVisibilityAuto: true,
|
|
18
20
|
opacityProperty: true
|
|
@@ -36,21 +38,33 @@ function sameRectDimensions(a, b) {
|
|
|
36
38
|
return a.width === b.width && a.height === b.height && a.x === b.x && a.y === b.y;
|
|
37
39
|
}
|
|
38
40
|
const createElementMutationsWatcher = removedNodeRects => ({
|
|
41
|
+
target,
|
|
39
42
|
rect
|
|
40
43
|
}) => {
|
|
44
|
+
const isNoLsMarkerEnabled = fg('platform_vc_ignore_no_ls_mutation_marker');
|
|
45
|
+
const isInIgnoreLsMarker = isInVCIgnoreIfNoLayoutShiftMarker(target);
|
|
46
|
+
if (!isInIgnoreLsMarker && isNoLsMarkerEnabled) {
|
|
47
|
+
return 'mutation:element';
|
|
48
|
+
}
|
|
41
49
|
const isRLLPlaceholder = RLLPlaceholderHandlers.getInstance().isRLLPlaceholderHydration(rect);
|
|
42
|
-
if (isRLLPlaceholder) {
|
|
50
|
+
if (isRLLPlaceholder && (!isNoLsMarkerEnabled || isInIgnoreLsMarker)) {
|
|
43
51
|
return 'mutation:rll-placeholder';
|
|
44
52
|
}
|
|
45
53
|
const wasDeleted = removedNodeRects.some(nr => sameRectDimensions(nr, rect));
|
|
46
|
-
|
|
54
|
+
// When fg('platform_vc_ignore_no_ls_mutation_marker') is not enabled,
|
|
55
|
+
// no layout shift mutation is excluded as per existing fy25.03 logic
|
|
56
|
+
if (wasDeleted && (!isNoLsMarkerEnabled || isInIgnoreLsMarker)) {
|
|
47
57
|
return 'mutation:element-replacement';
|
|
48
58
|
}
|
|
49
59
|
return 'mutation:element';
|
|
50
60
|
};
|
|
51
61
|
export default class ViewportObserver {
|
|
62
|
+
// SSR context functions
|
|
63
|
+
|
|
52
64
|
constructor({
|
|
53
|
-
onChange
|
|
65
|
+
onChange,
|
|
66
|
+
getSSRState,
|
|
67
|
+
getSSRPlaceholderHandler
|
|
54
68
|
}) {
|
|
55
69
|
_defineProperty(this, "handleIntersectionEntry", ({
|
|
56
70
|
target,
|
|
@@ -75,9 +89,11 @@ export default class ViewportObserver {
|
|
|
75
89
|
mutationData
|
|
76
90
|
});
|
|
77
91
|
});
|
|
78
|
-
_defineProperty(this, "handleChildListMutation", ({
|
|
92
|
+
_defineProperty(this, "handleChildListMutation", async ({
|
|
93
|
+
target,
|
|
79
94
|
addedNodes,
|
|
80
|
-
removedNodes
|
|
95
|
+
removedNodes,
|
|
96
|
+
timestamp
|
|
81
97
|
}) => {
|
|
82
98
|
const removedNodeRects = removedNodes.map(ref => {
|
|
83
99
|
const n = ref.deref();
|
|
@@ -86,11 +102,66 @@ export default class ViewportObserver {
|
|
|
86
102
|
}
|
|
87
103
|
return this.mapVisibleNodeRects.get(n);
|
|
88
104
|
});
|
|
89
|
-
|
|
90
|
-
|
|
105
|
+
const targetNode = target.deref();
|
|
106
|
+
for (const addedNodeRef of addedNodes) {
|
|
107
|
+
var _this$intersectionObs8;
|
|
91
108
|
const addedNode = addedNodeRef.deref();
|
|
92
109
|
if (!addedNode) {
|
|
93
|
-
|
|
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
|
+
}
|
|
94
165
|
}
|
|
95
166
|
const sameDeletedNode = removedNodes.find(ref => {
|
|
96
167
|
const n = ref.deref();
|
|
@@ -99,28 +170,33 @@ export default class ViewportObserver {
|
|
|
99
170
|
}
|
|
100
171
|
return n.isEqualNode(addedNode);
|
|
101
172
|
});
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
173
|
+
const isInIgnoreLsMarker = isInVCIgnoreIfNoLayoutShiftMarker(addedNode);
|
|
174
|
+
const isNoLsMarkerEnabled = fg('platform_vc_ignore_no_ls_mutation_marker');
|
|
175
|
+
|
|
176
|
+
// When fg('platform_vc_ignore_no_ls_mutation_marker') is not enabled,
|
|
177
|
+
// no layout shift mutation is excluded as per existing fy25.03 logic
|
|
178
|
+
if (sameDeletedNode && (!isNoLsMarkerEnabled || isInIgnoreLsMarker)) {
|
|
179
|
+
var _this$intersectionObs5;
|
|
180
|
+
(_this$intersectionObs5 = this.intersectionObserver) === null || _this$intersectionObs5 === void 0 ? void 0 : _this$intersectionObs5.watchAndTag(addedNode, 'mutation:remount');
|
|
181
|
+
continue;
|
|
106
182
|
}
|
|
107
183
|
if (isContainedWithinMediaWrapper(addedNode)) {
|
|
108
|
-
var _this$
|
|
109
|
-
(_this$
|
|
110
|
-
|
|
184
|
+
var _this$intersectionObs6;
|
|
185
|
+
(_this$intersectionObs6 = this.intersectionObserver) === null || _this$intersectionObs6 === void 0 ? void 0 : _this$intersectionObs6.watchAndTag(addedNode, 'mutation:media');
|
|
186
|
+
continue;
|
|
111
187
|
}
|
|
112
188
|
const {
|
|
113
189
|
isWithinThirdPartySegment,
|
|
114
190
|
ignoredReason
|
|
115
191
|
} = checkThirdPartySegmentWithIgnoreReason(addedNode);
|
|
116
192
|
if (isWithinThirdPartySegment) {
|
|
117
|
-
var _this$
|
|
193
|
+
var _this$intersectionObs7;
|
|
118
194
|
const assignedReason = createMutationTypeWithIgnoredReason(ignoredReason || 'third-party-element');
|
|
119
|
-
(_this$
|
|
120
|
-
|
|
195
|
+
(_this$intersectionObs7 = this.intersectionObserver) === null || _this$intersectionObs7 === void 0 ? void 0 : _this$intersectionObs7.watchAndTag(addedNode, assignedReason);
|
|
196
|
+
continue;
|
|
121
197
|
}
|
|
122
|
-
(_this$
|
|
123
|
-
}
|
|
198
|
+
(_this$intersectionObs8 = this.intersectionObserver) === null || _this$intersectionObs8 === void 0 ? void 0 : _this$intersectionObs8.watchAndTag(addedNode, createElementMutationsWatcher(removedNodeRects));
|
|
199
|
+
}
|
|
124
200
|
});
|
|
125
201
|
_defineProperty(this, "handleAttributeMutation", ({
|
|
126
202
|
target,
|
|
@@ -128,8 +204,8 @@ export default class ViewportObserver {
|
|
|
128
204
|
oldValue,
|
|
129
205
|
newValue
|
|
130
206
|
}) => {
|
|
131
|
-
var _this$
|
|
132
|
-
(_this$
|
|
207
|
+
var _this$intersectionObs9;
|
|
208
|
+
(_this$intersectionObs9 = this.intersectionObserver) === null || _this$intersectionObs9 === void 0 ? void 0 : _this$intersectionObs9.watchAndTag(target, ({
|
|
133
209
|
target,
|
|
134
210
|
rect
|
|
135
211
|
}) => {
|
|
@@ -228,6 +304,10 @@ export default class ViewportObserver {
|
|
|
228
304
|
this.intersectionObserver = null;
|
|
229
305
|
this.mutationObserver = null;
|
|
230
306
|
this.performanceObserver = null;
|
|
307
|
+
|
|
308
|
+
// Initialize SSR context functions
|
|
309
|
+
this.getSSRState = getSSRState;
|
|
310
|
+
this.getSSRPlaceholderHandler = getSSRPlaceholderHandler;
|
|
231
311
|
}
|
|
232
312
|
initializeObservers() {
|
|
233
313
|
if (this.isStarted) {
|
|
@@ -265,12 +345,12 @@ export default class ViewportObserver {
|
|
|
265
345
|
this.isStarted = true;
|
|
266
346
|
}
|
|
267
347
|
stop() {
|
|
268
|
-
var _this$mutationObserve2, _this$
|
|
348
|
+
var _this$mutationObserve2, _this$intersectionObs0, _this$performanceObse2;
|
|
269
349
|
if (!this.isStarted) {
|
|
270
350
|
return;
|
|
271
351
|
}
|
|
272
352
|
(_this$mutationObserve2 = this.mutationObserver) === null || _this$mutationObserve2 === void 0 ? void 0 : _this$mutationObserve2.disconnect();
|
|
273
|
-
(_this$
|
|
353
|
+
(_this$intersectionObs0 = this.intersectionObserver) === null || _this$intersectionObs0 === void 0 ? void 0 : _this$intersectionObs0.disconnect();
|
|
274
354
|
(_this$performanceObse2 = this.performanceObserver) === null || _this$performanceObse2 === void 0 ? void 0 : _this$performanceObse2.disconnect();
|
|
275
355
|
this.isStarted = false;
|
|
276
356
|
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// Batched mutation data for performance optimization
|
|
2
|
+
|
|
1
3
|
function createMutationObserver({
|
|
2
4
|
onAttributeMutation,
|
|
3
5
|
onChildListMutation,
|
|
@@ -7,9 +9,10 @@ function createMutationObserver({
|
|
|
7
9
|
return null;
|
|
8
10
|
}
|
|
9
11
|
const mutationObserverCallback = mutations => {
|
|
10
|
-
const addedNodes = [];
|
|
11
|
-
const removedNodes = [];
|
|
12
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();
|
|
13
16
|
for (const mut of mutations) {
|
|
14
17
|
if (!(mut.target instanceof HTMLElement)) {
|
|
15
18
|
continue;
|
|
@@ -38,23 +41,56 @@ function createMutationObserver({
|
|
|
38
41
|
continue;
|
|
39
42
|
} else if (mut.type === 'childList') {
|
|
40
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
|
|
41
67
|
((_mut$addedNodes = mut.addedNodes) !== null && _mut$addedNodes !== void 0 ? _mut$addedNodes : []).forEach(node => {
|
|
42
68
|
if (node instanceof HTMLElement) {
|
|
43
|
-
addedNodes.push(new WeakRef(node));
|
|
69
|
+
batch.addedNodes.push(new WeakRef(node));
|
|
44
70
|
}
|
|
45
71
|
});
|
|
72
|
+
|
|
73
|
+
// Accumulate removed nodes
|
|
46
74
|
((_mut$removedNodes = mut.removedNodes) !== null && _mut$removedNodes !== void 0 ? _mut$removedNodes : []).forEach(node => {
|
|
47
75
|
if (node instanceof HTMLElement) {
|
|
48
|
-
removedNodes.push(new WeakRef(node));
|
|
76
|
+
batch.removedNodes.push(new WeakRef(node));
|
|
49
77
|
}
|
|
50
78
|
});
|
|
51
79
|
}
|
|
52
80
|
targets.push(mut.target);
|
|
53
81
|
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
+
}
|
|
93
|
+
}
|
|
58
94
|
onMutationFinished === null || onMutationFinished === void 0 ? void 0 : onMutationFinished({
|
|
59
95
|
targets
|
|
60
96
|
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Check if the element is in the VC ignore if no layout shift marker.
|
|
3
|
+
* @param element - The element to check.
|
|
4
|
+
* @returns True if the element has the data-vc-ignore-if-no-layout-shift attribute == 'true or its parent has it, false otherwise.
|
|
5
|
+
*/
|
|
6
|
+
export default function isInVCIgnoreIfNoLayoutShiftMarker(element) {
|
|
7
|
+
if (!element) {
|
|
8
|
+
return false;
|
|
9
|
+
}
|
|
10
|
+
return element.getAttribute('data-vc-ignore-if-no-layout-shift') === 'true' || isInVCIgnoreIfNoLayoutShiftMarker(element.parentElement);
|
|
11
|
+
}
|