@atlaskit/react-ufo 3.4.1 → 3.4.3
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/additional-payload/utils/lighthouse-metrics/utils/observer/index.js +8 -2
- package/dist/cjs/create-payload/index.js +136 -76
- package/dist/cjs/segment/segment.js +29 -4
- package/dist/cjs/vc/index.js +22 -2
- package/dist/cjs/vc/no-op-vc-observer.js +44 -0
- package/dist/cjs/vc/vc-observer/index.js +74 -46
- package/dist/cjs/vc/vc-observer/observers/ssr-placeholders/index.js +18 -11
- package/dist/cjs/vc/vc-observer-new/get-element-name.js +52 -64
- package/dist/cjs/vc/vc-observer-new/get-unique-element-name.js +80 -0
- package/dist/cjs/vc/vc-observer-new/metric-calculator/fy25_03/index.js +7 -2
- package/dist/cjs/vc/vc-observer-new/metric-calculator/percentile-calc/canvas-heatmap/canvas-pixel.js +6 -4
- package/dist/cjs/vc/vc-observer-new/viewport-observer/index.js +17 -9
- package/dist/es2019/additional-payload/utils/lighthouse-metrics/utils/observer/index.js +8 -2
- package/dist/es2019/create-payload/index.js +39 -3
- package/dist/es2019/segment/segment.js +29 -3
- package/dist/es2019/vc/index.js +22 -2
- package/dist/es2019/vc/no-op-vc-observer.js +17 -0
- package/dist/es2019/vc/vc-observer/index.js +42 -12
- package/dist/es2019/vc/vc-observer/observers/ssr-placeholders/index.js +14 -7
- package/dist/es2019/vc/vc-observer-new/get-element-name.js +51 -64
- package/dist/es2019/vc/vc-observer-new/get-unique-element-name.js +74 -0
- package/dist/es2019/vc/vc-observer-new/metric-calculator/fy25_03/index.js +6 -1
- package/dist/es2019/vc/vc-observer-new/metric-calculator/percentile-calc/canvas-heatmap/canvas-pixel.js +6 -4
- package/dist/es2019/vc/vc-observer-new/viewport-observer/index.js +17 -9
- package/dist/esm/additional-payload/utils/lighthouse-metrics/utils/observer/index.js +8 -2
- package/dist/esm/create-payload/index.js +136 -76
- package/dist/esm/segment/segment.js +29 -4
- package/dist/esm/vc/index.js +22 -2
- package/dist/esm/vc/no-op-vc-observer.js +37 -0
- package/dist/esm/vc/vc-observer/index.js +74 -46
- package/dist/esm/vc/vc-observer/observers/ssr-placeholders/index.js +18 -11
- package/dist/esm/vc/vc-observer-new/get-element-name.js +52 -64
- package/dist/esm/vc/vc-observer-new/get-unique-element-name.js +74 -0
- package/dist/esm/vc/vc-observer-new/metric-calculator/fy25_03/index.js +6 -1
- package/dist/esm/vc/vc-observer-new/metric-calculator/percentile-calc/canvas-heatmap/canvas-pixel.js +6 -4
- package/dist/esm/vc/vc-observer-new/viewport-observer/index.js +17 -9
- package/dist/types/interaction-context/index.d.ts +2 -0
- package/dist/types/segment/segment.d.ts +2 -1
- package/dist/types/vc/no-op-vc-observer.d.ts +13 -0
- package/dist/types/vc/vc-observer-new/get-unique-element-name.d.ts +8 -0
- package/dist/types/vc/vc-observer-new/metric-calculator/fy25_03/index.d.ts +1 -0
- package/dist/types-ts4.5/interaction-context/index.d.ts +2 -0
- package/dist/types-ts4.5/segment/segment.d.ts +2 -1
- package/dist/types-ts4.5/vc/no-op-vc-observer.d.ts +13 -0
- package/dist/types-ts4.5/vc/vc-observer-new/get-unique-element-name.d.ts +8 -0
- package/dist/types-ts4.5/vc/vc-observer-new/metric-calculator/fy25_03/index.d.ts +1 -0
- package/package.json +7 -1
|
@@ -130,7 +130,37 @@ const getResourceTimings = (start, end) => {
|
|
|
130
130
|
const getBundleEvalTimings = start => bundleEvalTiming.getBundleEvalTimings(start);
|
|
131
131
|
const getSSRSuccess = type => type === 'page_load' ? ssr.getSSRSuccess() : undefined;
|
|
132
132
|
const getSSRFeatureFlags = type => type === 'page_load' ? ssr.getSSRFeatureFlags() : undefined;
|
|
133
|
-
const
|
|
133
|
+
const getLCP = end => {
|
|
134
|
+
return new Promise(resolve => {
|
|
135
|
+
let observer;
|
|
136
|
+
const timeout = setTimeout(() => {
|
|
137
|
+
var _observer;
|
|
138
|
+
(_observer = observer) === null || _observer === void 0 ? void 0 : _observer.disconnect();
|
|
139
|
+
resolve(null);
|
|
140
|
+
}, 200);
|
|
141
|
+
observer = new PerformanceObserver(list => {
|
|
142
|
+
const entries = Array.from(list.getEntries());
|
|
143
|
+
const lastEntry = entries.reduce((agg, entry) => {
|
|
144
|
+
// Use the latest LCP candidate before TTAI
|
|
145
|
+
if (entry.startTime <= end && (agg === null || agg.startTime < entry.startTime)) {
|
|
146
|
+
return entry;
|
|
147
|
+
}
|
|
148
|
+
return agg;
|
|
149
|
+
}, null);
|
|
150
|
+
clearTimeout(timeout);
|
|
151
|
+
if (!lastEntry || lastEntry === null) {
|
|
152
|
+
resolve(null);
|
|
153
|
+
} else {
|
|
154
|
+
resolve(lastEntry.startTime);
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
observer.observe({
|
|
158
|
+
type: 'largest-contentful-paint',
|
|
159
|
+
buffered: true
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
};
|
|
163
|
+
const getPaintMetrics = async (type, end) => {
|
|
134
164
|
if (type !== 'page_load') {
|
|
135
165
|
return {};
|
|
136
166
|
}
|
|
@@ -143,6 +173,12 @@ const getPaintMetrics = type => {
|
|
|
143
173
|
metrics['metric:fcp'] = Math.round(entry.startTime);
|
|
144
174
|
}
|
|
145
175
|
});
|
|
176
|
+
if (fg('ufo_lcp')) {
|
|
177
|
+
const lcp = await getLCP(end);
|
|
178
|
+
if (lcp) {
|
|
179
|
+
metrics['metric:lcp'] = Math.round(lcp);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
146
182
|
return metrics;
|
|
147
183
|
};
|
|
148
184
|
const getTTAI = interaction => {
|
|
@@ -728,7 +764,7 @@ async function createInteractionMetricsPayload(interaction, interactionId, exper
|
|
|
728
764
|
}
|
|
729
765
|
const newUFOName = sanitizeUfoName(ufoName);
|
|
730
766
|
const resourceTimings = getResourceTimings(start, end);
|
|
731
|
-
const [vcMetrics, experimentalMetrics] = await Promise.all([getVCMetrics(interaction), experimental ? getExperimentalVCMetrics(interaction) : Promise.resolve(undefined)]);
|
|
767
|
+
const [vcMetrics, experimentalMetrics, paintMetrics] = await Promise.all([getVCMetrics(interaction), experimental ? getExperimentalVCMetrics(interaction) : Promise.resolve(undefined), getPaintMetrics(type, end)]);
|
|
732
768
|
const payload = {
|
|
733
769
|
actionSubject: 'experience',
|
|
734
770
|
action: 'measured',
|
|
@@ -754,7 +790,7 @@ async function createInteractionMetricsPayload(interaction, interactionId, exper
|
|
|
754
790
|
...getSSRProperties(type),
|
|
755
791
|
...getAssetsMetrics(interaction, pageLoadInteractionMetrics === null || pageLoadInteractionMetrics === void 0 ? void 0 : pageLoadInteractionMetrics.SSRDoneTime),
|
|
756
792
|
...getPPSMetrics(interaction),
|
|
757
|
-
...
|
|
793
|
+
...paintMetrics,
|
|
758
794
|
...getNavigationMetrics(type),
|
|
759
795
|
...vcMetrics,
|
|
760
796
|
...experimentalMetrics,
|
|
@@ -15,15 +15,40 @@ let tryCompleteHandle;
|
|
|
15
15
|
const AsyncSegmentHighlight = /*#__PURE__*/lazy(() => import( /* webpackChunkName: "@atlaskit-internal_ufo-segment-highlight" */'./segment-highlight').then(module => ({
|
|
16
16
|
default: module.SegmentHighlight
|
|
17
17
|
})));
|
|
18
|
+
const noopIdMap = new Map();
|
|
18
19
|
|
|
19
20
|
/** A portion of the page we apply measurement to */
|
|
20
21
|
export default function UFOSegment({
|
|
21
22
|
name: segmentName,
|
|
22
|
-
children
|
|
23
|
+
children,
|
|
24
|
+
mode = 'single'
|
|
23
25
|
}) {
|
|
24
26
|
var _getConfig2;
|
|
25
27
|
const parentContext = useContext(UFOInteractionContext);
|
|
26
|
-
const
|
|
28
|
+
const segmentIdMap = useMemo(() => {
|
|
29
|
+
if (!fg('platform_ufo_segment_list_mode')) {
|
|
30
|
+
// just in case we cause rerender issues, use noop map
|
|
31
|
+
return noopIdMap;
|
|
32
|
+
}
|
|
33
|
+
if (!(parentContext !== null && parentContext !== void 0 && parentContext.segmentIdMap)) {
|
|
34
|
+
return new Map();
|
|
35
|
+
}
|
|
36
|
+
return parentContext.segmentIdMap;
|
|
37
|
+
}, [parentContext]);
|
|
38
|
+
const segmentId = useMemo(() => {
|
|
39
|
+
if (!fg('platform_ufo_segment_list_mode')) {
|
|
40
|
+
return generateId();
|
|
41
|
+
}
|
|
42
|
+
if (mode === 'single') {
|
|
43
|
+
return generateId();
|
|
44
|
+
}
|
|
45
|
+
if (segmentIdMap.has(segmentName)) {
|
|
46
|
+
return segmentIdMap.get(segmentName);
|
|
47
|
+
}
|
|
48
|
+
const newSegmentId = generateId();
|
|
49
|
+
segmentIdMap.set(segmentName, newSegmentId);
|
|
50
|
+
return newSegmentId;
|
|
51
|
+
}, [mode, segmentName, segmentIdMap]);
|
|
27
52
|
const labelStack = useMemo(() => parentContext !== null && parentContext !== void 0 && parentContext.labelStack ? [...parentContext.labelStack, {
|
|
28
53
|
name: segmentName,
|
|
29
54
|
segmentId
|
|
@@ -108,6 +133,7 @@ export default function UFOSegment({
|
|
|
108
133
|
}
|
|
109
134
|
return {
|
|
110
135
|
labelStack,
|
|
136
|
+
segmentIdMap: segmentIdMap,
|
|
111
137
|
hold(name = 'unknown') {
|
|
112
138
|
return this._internalHold(this.labelStack, name);
|
|
113
139
|
},
|
|
@@ -204,7 +230,7 @@ export default function UFOSegment({
|
|
|
204
230
|
_internalHoldByID,
|
|
205
231
|
complete
|
|
206
232
|
};
|
|
207
|
-
}, [parentContext, labelStack, interactionId]);
|
|
233
|
+
}, [parentContext, labelStack, segmentIdMap, interactionId]);
|
|
208
234
|
const hasMounted = useRef(false);
|
|
209
235
|
const onRender = useCallback((_id, phase, actualDuration, baseDuration, startTime, commitTime) => {
|
|
210
236
|
var _getConfig;
|
package/dist/es2019/vc/index.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { fg } from '@atlaskit/platform-feature-flags';
|
|
2
2
|
import { getConfig } from '../config';
|
|
3
|
+
import { VCObserverNOOP } from './no-op-vc-observer';
|
|
3
4
|
import { VCObserver } from './vc-observer';
|
|
4
5
|
import VCObserverNew from './vc-observer-new';
|
|
5
6
|
class VCObserverWrapper {
|
|
6
7
|
constructor(opts = {}) {
|
|
7
8
|
var _getConfig, _getConfig$vc;
|
|
8
|
-
this.oldVCObserver = new VCObserver(opts);
|
|
9
9
|
this.newVCObserver = null;
|
|
10
10
|
const isNewVCObserverEnabled = fg('platform_ufo_vc_observer_new') || ((_getConfig = getConfig()) === null || _getConfig === void 0 ? void 0 : (_getConfig$vc = _getConfig.vc) === null || _getConfig$vc === void 0 ? void 0 : _getConfig$vc.enableVCObserverNew);
|
|
11
11
|
if (isNewVCObserverEnabled) {
|
|
@@ -13,6 +13,7 @@ class VCObserverWrapper {
|
|
|
13
13
|
selectorConfig: opts.selectorConfig
|
|
14
14
|
});
|
|
15
15
|
}
|
|
16
|
+
this.oldVCObserver = new VCObserver(opts);
|
|
16
17
|
}
|
|
17
18
|
start(startArg) {
|
|
18
19
|
var _this$oldVCObserver, _this$newVCObserver;
|
|
@@ -58,9 +59,28 @@ class VCObserverWrapper {
|
|
|
58
59
|
(_this$oldVCObserver7 = this.oldVCObserver) === null || _this$oldVCObserver7 === void 0 ? void 0 : _this$oldVCObserver7.setReactRootRenderStop(stopTime || performance.now());
|
|
59
60
|
}
|
|
60
61
|
}
|
|
62
|
+
const isReactSSR = Boolean(process.env.REACT_SSR);
|
|
63
|
+
const isServer = Boolean(globalThis === null || globalThis === void 0 ? void 0 : globalThis.__SERVER__);
|
|
64
|
+
function isEnvironmentSupported() {
|
|
65
|
+
// SSR environment aren't supported
|
|
66
|
+
if (isReactSSR || isServer) {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Legacy browsers that doesn't support WeakRef
|
|
71
|
+
// aren't valid
|
|
72
|
+
if (typeof (globalThis === null || globalThis === void 0 ? void 0 : globalThis.WeakRef) !== 'function') {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
if (typeof (globalThis === null || globalThis === void 0 ? void 0 : globalThis.MutationObserver) !== 'function' || typeof (globalThis === null || globalThis === void 0 ? void 0 : globalThis.IntersectionObserver) !== 'function' || typeof (globalThis === null || globalThis === void 0 ? void 0 : globalThis.PerformanceObserver) !== 'function') {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
61
80
|
export const getVCObserver = (opts = {}) => {
|
|
62
81
|
if (!globalThis.__vcObserver) {
|
|
63
|
-
|
|
82
|
+
const shouldMockVCObserver = !isEnvironmentSupported();
|
|
83
|
+
globalThis.__vcObserver = shouldMockVCObserver ? new VCObserverNOOP() : new VCObserverWrapper(opts);
|
|
64
84
|
}
|
|
65
85
|
return globalThis.__vcObserver;
|
|
66
86
|
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// This class should be used on scenarios where VC can not be calculate
|
|
2
|
+
// such as: SSR environment and legacy browsers
|
|
3
|
+
export class VCObserverNOOP {
|
|
4
|
+
start(startArg) {}
|
|
5
|
+
stop() {}
|
|
6
|
+
getVCRawData() {
|
|
7
|
+
return null;
|
|
8
|
+
}
|
|
9
|
+
getVCResult(param) {
|
|
10
|
+
return Promise.resolve({
|
|
11
|
+
'ufo:vc:noop': true
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
setSSRElement(element) {}
|
|
15
|
+
setReactRootRenderStart(startTime) {}
|
|
16
|
+
setReactRootRenderStop(stopTime) {}
|
|
17
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import _defineProperty from "@babel/runtime/helpers/defineProperty";
|
|
2
2
|
import { fg } from '@atlaskit/platform-feature-flags';
|
|
3
|
+
import { getPageVisibilityState } from '../../hidden-timing';
|
|
3
4
|
import { attachAbortListeners } from './attachAbortListeners';
|
|
4
5
|
import { getViewportHeight, getViewportWidth } from './getViewport';
|
|
5
6
|
import { MultiRevisionHeatmap } from './heatmap/heatmap';
|
|
@@ -250,8 +251,35 @@ export class VCObserver {
|
|
|
250
251
|
} catch (e) {
|
|
251
252
|
/* do nothing */
|
|
252
253
|
}
|
|
254
|
+
const isVCClean = !abortReasonInfo;
|
|
253
255
|
const isMultiHeatmapEnabled = !fg('platform_ufo_multiheatmap_killswitch');
|
|
254
|
-
const
|
|
256
|
+
const pageVisibilityUpToTTAI = getPageVisibilityState(start, stop);
|
|
257
|
+
const shouldHaveVCmetric = isVCClean && !isEventAborted && pageVisibilityUpToTTAI;
|
|
258
|
+
const revisionsData = isMultiHeatmapEnabled ? fg('platform_ufo_vc_observer_new') ? {
|
|
259
|
+
[`${fullPrefix}vc:rev`]: [{
|
|
260
|
+
revision: 'fy25.01',
|
|
261
|
+
clean: isVCClean,
|
|
262
|
+
'metric:vc90': shouldHaveVCmetric ? VC['90'] : null,
|
|
263
|
+
vcDetails: shouldHaveVCmetric ? Object.fromEntries(VCObserver.VCParts.map(key => {
|
|
264
|
+
var _VCBox$key;
|
|
265
|
+
return [key, {
|
|
266
|
+
t: VC[key],
|
|
267
|
+
e: (_VCBox$key = VCBox[key]) !== null && _VCBox$key !== void 0 ? _VCBox$key : []
|
|
268
|
+
}];
|
|
269
|
+
})) : []
|
|
270
|
+
}, {
|
|
271
|
+
revision: 'fy25.02',
|
|
272
|
+
clean: isVCClean,
|
|
273
|
+
'metric:vc90': shouldHaveVCmetric ? VC['90'] : null,
|
|
274
|
+
vcDetails: shouldHaveVCmetric ? Object.fromEntries(VCObserver.VCParts.map(key => {
|
|
275
|
+
var _vcNext$VCBox$key;
|
|
276
|
+
return [key, {
|
|
277
|
+
t: vcNext.VC[key],
|
|
278
|
+
e: (_vcNext$VCBox$key = vcNext.VCBox[key]) !== null && _vcNext$VCBox$key !== void 0 ? _vcNext$VCBox$key : []
|
|
279
|
+
}];
|
|
280
|
+
})) : []
|
|
281
|
+
}]
|
|
282
|
+
} : multiHeatmap !== null ? {
|
|
255
283
|
[`${fullPrefix}vc:rev`]: multiHeatmap === null || multiHeatmap === void 0 ? void 0 : multiHeatmap.getPayloadShapedData({
|
|
256
284
|
VCParts: VCObserver.VCParts.map(v => parseInt(v)),
|
|
257
285
|
VCCalculationMethods: getRevisions().map(({
|
|
@@ -264,9 +292,9 @@ export class VCObserver {
|
|
|
264
292
|
interactionStart: start,
|
|
265
293
|
ttai: stop,
|
|
266
294
|
ssr,
|
|
267
|
-
clean:
|
|
295
|
+
clean: isVCClean
|
|
268
296
|
})
|
|
269
|
-
} : null;
|
|
297
|
+
} : null : null;
|
|
270
298
|
// eslint-disable-next-line @atlaskit/platform/ensure-feature-flag-prefix
|
|
271
299
|
const isCalcSpeedIndexEnabled = fg('ufo-calc-speed-index');
|
|
272
300
|
const speedIndex = {
|
|
@@ -276,7 +304,7 @@ export class VCObserver {
|
|
|
276
304
|
return {
|
|
277
305
|
'metrics:vc': VC,
|
|
278
306
|
[`${fullPrefix}vc:state`]: true,
|
|
279
|
-
[`${fullPrefix}vc:clean`]:
|
|
307
|
+
[`${fullPrefix}vc:clean`]: isVCClean,
|
|
280
308
|
[`${fullPrefix}vc:dom`]: VCBox,
|
|
281
309
|
[`${fullPrefix}vc:updates`]: VCEntries.rel.slice(0, 50),
|
|
282
310
|
// max 50
|
|
@@ -297,14 +325,16 @@ export class VCObserver {
|
|
|
297
325
|
_defineProperty(this, "handleUpdate", (rawTime, intersectionRect, targetName, element, type, ignoreReason) => {
|
|
298
326
|
this.measureStart();
|
|
299
327
|
this.legacyHandleUpdate(rawTime, intersectionRect, targetName, element, type, ignoreReason);
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
328
|
+
if (!fg('platform_ufo_vc_observer_new')) {
|
|
329
|
+
this.onViewportChangeDetected({
|
|
330
|
+
timestamp: rawTime,
|
|
331
|
+
intersectionRect,
|
|
332
|
+
targetName,
|
|
333
|
+
element,
|
|
334
|
+
type,
|
|
335
|
+
ignoreReason
|
|
336
|
+
});
|
|
337
|
+
}
|
|
308
338
|
this.measureStop();
|
|
309
339
|
});
|
|
310
340
|
_defineProperty(this, "legacyHandleUpdate", (rawTime, intersectionRect, targetName, element, type, ignoreReason) => {
|
|
@@ -11,7 +11,8 @@ export class SSRPlaceholderHandlers {
|
|
|
11
11
|
target,
|
|
12
12
|
boundingClientRect
|
|
13
13
|
}) => {
|
|
14
|
-
|
|
14
|
+
var _this$intersectionObs;
|
|
15
|
+
(_this$intersectionObs = this.intersectionObserver) === null || _this$intersectionObs === void 0 ? void 0 : _this$intersectionObs.unobserve(target);
|
|
15
16
|
if (!(target instanceof HTMLElement)) {
|
|
16
17
|
// impossible case - keep typescript healthy
|
|
17
18
|
return;
|
|
@@ -58,14 +59,17 @@ export class SSRPlaceholderHandlers {
|
|
|
58
59
|
this.reactValidateCallbacks.delete(staticKey);
|
|
59
60
|
}
|
|
60
61
|
});
|
|
61
|
-
|
|
62
|
+
if (typeof IntersectionObserver === 'function') {
|
|
63
|
+
// Only instantiate the IntersectionObserver if it's supported
|
|
64
|
+
this.intersectionObserver = new IntersectionObserver(entries => entries.filter(entry => entry.intersectionRatio > 0).forEach(this.intersectionObserverCallback));
|
|
65
|
+
}
|
|
62
66
|
if (window.document) {
|
|
63
67
|
try {
|
|
64
68
|
const existingElements = document.querySelectorAll('[data-ssr-placeholder]');
|
|
65
69
|
existingElements.forEach(el => {
|
|
66
70
|
var _el$dataset;
|
|
67
71
|
if (el instanceof HTMLElement && el !== null && el !== void 0 && (_el$dataset = el.dataset) !== null && _el$dataset !== void 0 && _el$dataset.ssrPlaceholder) {
|
|
68
|
-
var _window$__SSR_PLACEHO;
|
|
72
|
+
var _window$__SSR_PLACEHO, _this$intersectionObs2;
|
|
69
73
|
let width = -1;
|
|
70
74
|
let height = -1;
|
|
71
75
|
let x = -1;
|
|
@@ -83,7 +87,7 @@ export class SSRPlaceholderHandlers {
|
|
|
83
87
|
x,
|
|
84
88
|
y
|
|
85
89
|
});
|
|
86
|
-
this.intersectionObserver.observe(el);
|
|
90
|
+
(_this$intersectionObs2 = this.intersectionObserver) === null || _this$intersectionObs2 === void 0 ? void 0 : _this$intersectionObs2.observe(el);
|
|
87
91
|
}
|
|
88
92
|
});
|
|
89
93
|
} catch (e) {} finally {
|
|
@@ -130,15 +134,17 @@ export class SSRPlaceholderHandlers {
|
|
|
130
134
|
resolve(false);
|
|
131
135
|
return;
|
|
132
136
|
} else {
|
|
137
|
+
var _this$intersectionObs3;
|
|
133
138
|
this.callbacks.set(id, resolve);
|
|
134
|
-
this.intersectionObserver.observe(el);
|
|
139
|
+
(_this$intersectionObs3 = this.intersectionObserver) === null || _this$intersectionObs3 === void 0 ? void 0 : _this$intersectionObs3.observe(el);
|
|
135
140
|
}
|
|
136
141
|
});
|
|
137
142
|
}
|
|
138
143
|
getSize(el) {
|
|
139
144
|
return new Promise(resolve => {
|
|
145
|
+
var _this$intersectionObs4;
|
|
140
146
|
this.getSizeCallbacks.set(el.dataset.ssrPlaceholder || '', resolve);
|
|
141
|
-
this.intersectionObserver.observe(el);
|
|
147
|
+
(_this$intersectionObs4 = this.intersectionObserver) === null || _this$intersectionObs4 === void 0 ? void 0 : _this$intersectionObs4.observe(el);
|
|
142
148
|
});
|
|
143
149
|
}
|
|
144
150
|
validateReactComponentMatchToPlaceholder(el) {
|
|
@@ -149,8 +155,9 @@ export class SSRPlaceholderHandlers {
|
|
|
149
155
|
resolve(false);
|
|
150
156
|
return;
|
|
151
157
|
} else {
|
|
158
|
+
var _this$intersectionObs5;
|
|
152
159
|
this.reactValidateCallbacks.set(id, resolve);
|
|
153
|
-
this.intersectionObserver.observe(el);
|
|
160
|
+
(_this$intersectionObs5 = this.intersectionObserver) === null || _this$intersectionObs5 === void 0 ? void 0 : _this$intersectionObs5.observe(el);
|
|
154
161
|
}
|
|
155
162
|
});
|
|
156
163
|
}
|
|
@@ -1,65 +1,4 @@
|
|
|
1
1
|
const nameCache = new WeakMap();
|
|
2
|
-
function getAttributeSelector(element, attributeName) {
|
|
3
|
-
const attrValue = element.getAttribute(attributeName);
|
|
4
|
-
if (!attrValue) {
|
|
5
|
-
return '';
|
|
6
|
-
}
|
|
7
|
-
return `[${attributeName}="${attrValue}"]`;
|
|
8
|
-
}
|
|
9
|
-
function isValidSelector(selector) {
|
|
10
|
-
try {
|
|
11
|
-
document.querySelector(selector);
|
|
12
|
-
return true;
|
|
13
|
-
} catch (err) {
|
|
14
|
-
return false;
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
function isSelectorUnique(selector) {
|
|
18
|
-
return document.querySelectorAll(selector).length === 1;
|
|
19
|
-
}
|
|
20
|
-
function getUniqueSelector(selectorConfig, element) {
|
|
21
|
-
let currentElement = element;
|
|
22
|
-
const parts = [];
|
|
23
|
-
const MAX_DEPTH = 3;
|
|
24
|
-
let currentDepth = 0;
|
|
25
|
-
while (currentElement && currentElement.localName !== 'body' && currentDepth <= MAX_DEPTH) {
|
|
26
|
-
const tagName = currentElement.localName;
|
|
27
|
-
let selectorPart = tagName;
|
|
28
|
-
if (selectorConfig.id && currentElement.id && isValidSelector(`#${currentElement.id}`)) {
|
|
29
|
-
selectorPart += `#${currentElement.id}`;
|
|
30
|
-
} else if (selectorConfig.dataVC) {
|
|
31
|
-
selectorPart += getAttributeSelector(currentElement, 'data-vc');
|
|
32
|
-
} else if (selectorConfig.testId) {
|
|
33
|
-
selectorPart += getAttributeSelector(currentElement, 'data-testid') || getAttributeSelector(currentElement, 'data-test-id');
|
|
34
|
-
} else if (selectorConfig.role) {
|
|
35
|
-
selectorPart += getAttributeSelector(currentElement, 'role');
|
|
36
|
-
} else if (selectorConfig.className && currentElement.className) {
|
|
37
|
-
const classNames = Array.from(currentElement.classList).join('.');
|
|
38
|
-
if (classNames) {
|
|
39
|
-
if (isValidSelector(`.${classNames}`)) {
|
|
40
|
-
selectorPart += `.${classNames}`;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
parts.unshift(selectorPart);
|
|
45
|
-
const potentialSelector = parts.join(' > ').trim();
|
|
46
|
-
if (potentialSelector && isSelectorUnique(potentialSelector)) {
|
|
47
|
-
return potentialSelector;
|
|
48
|
-
}
|
|
49
|
-
currentElement = currentElement.parentElement;
|
|
50
|
-
currentDepth++;
|
|
51
|
-
}
|
|
52
|
-
const potentialSelector = parts.join(' > ').trim();
|
|
53
|
-
if (!potentialSelector) {
|
|
54
|
-
return 'unknown';
|
|
55
|
-
} else if (!isSelectorUnique(potentialSelector)) {
|
|
56
|
-
const parentElement = element.parentElement;
|
|
57
|
-
if (parentElement) {
|
|
58
|
-
return `${potentialSelector}:nth-child`; // NOTE: invalid DOM selector, but enough information for VC
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
return potentialSelector;
|
|
62
|
-
}
|
|
63
2
|
export default function getElementName(selectorConfig, element) {
|
|
64
3
|
if (!(element instanceof HTMLElement)) {
|
|
65
4
|
return 'error';
|
|
@@ -68,7 +7,55 @@ export default function getElementName(selectorConfig, element) {
|
|
|
68
7
|
if (cachedName) {
|
|
69
8
|
return cachedName;
|
|
70
9
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
10
|
+
// Get the tag name of the element.
|
|
11
|
+
const tagName = element.localName;
|
|
12
|
+
const encodeValue = s => {
|
|
13
|
+
try {
|
|
14
|
+
return encodeURIComponent(s);
|
|
15
|
+
} catch (e) {
|
|
16
|
+
return 'malformed_value';
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
// Helper function to construct attribute selectors.
|
|
21
|
+
const getAttributeSelector = (attributeName, prefix = '') => {
|
|
22
|
+
const attrValue = element.getAttribute(attributeName);
|
|
23
|
+
if (!attrValue) {
|
|
24
|
+
return '';
|
|
25
|
+
}
|
|
26
|
+
const encondedAttrValue = encodeValue(attrValue);
|
|
27
|
+
return `${prefix}[${attributeName}="${encondedAttrValue}"]`;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
// Construct the data-vc attribute selector if specified in the config.
|
|
31
|
+
const dataVC = selectorConfig.dataVC ? getAttributeSelector('data-vc') : '';
|
|
32
|
+
|
|
33
|
+
// Construct the ID selector if specified in the config and the element has an ID.
|
|
34
|
+
const id = selectorConfig.id && element.id ? `#${encodeValue(element.id)}` : '';
|
|
35
|
+
|
|
36
|
+
// Construct the test ID selector if specified in the config.
|
|
37
|
+
const testId = selectorConfig.testId ? getAttributeSelector('data-testid') || getAttributeSelector('data-test-id') : '';
|
|
38
|
+
|
|
39
|
+
// Construct the role selector if specified in the config.
|
|
40
|
+
const role = selectorConfig.role ? getAttributeSelector('role') : '';
|
|
41
|
+
const classNames = Array.from(element.classList).map(encodeValue).join('.');
|
|
42
|
+
// Construct the class list selector if specified in the config.
|
|
43
|
+
const classList = selectorConfig.className && classNames ? `.${classNames}` : '';
|
|
44
|
+
|
|
45
|
+
// Combine primary attribute selectors (id, testId, role) into a single string.
|
|
46
|
+
const primaryAttributes = [id, testId, role].filter(Boolean).join('');
|
|
47
|
+
|
|
48
|
+
// Use dataVC if available, otherwise use the primary attributes.
|
|
49
|
+
const attributes = dataVC || primaryAttributes;
|
|
50
|
+
|
|
51
|
+
// If no attributes or class list, recursively get the parent's name.
|
|
52
|
+
if (!attributes && !classList) {
|
|
53
|
+
const parentName = element.parentElement ? getElementName(selectorConfig, element.parentElement) : 'unknown';
|
|
54
|
+
return `${parentName} > ${tagName}`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Return the final constructed name: tagName + attributes or classList.
|
|
58
|
+
const name = `${tagName}${attributes || classList}`;
|
|
59
|
+
nameCache.set(element, name);
|
|
60
|
+
return name;
|
|
74
61
|
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
const nameCache = new WeakMap();
|
|
2
|
+
function getAttributeSelector(element, attributeName) {
|
|
3
|
+
const attrValue = element.getAttribute(attributeName);
|
|
4
|
+
if (!attrValue) {
|
|
5
|
+
return '';
|
|
6
|
+
}
|
|
7
|
+
return `[${attributeName}="${attrValue}"]`;
|
|
8
|
+
}
|
|
9
|
+
function isValidSelector(selector) {
|
|
10
|
+
try {
|
|
11
|
+
document.querySelector(selector);
|
|
12
|
+
return true;
|
|
13
|
+
} catch (err) {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function isSelectorUnique(selector) {
|
|
18
|
+
return document.querySelectorAll(selector).length === 1;
|
|
19
|
+
}
|
|
20
|
+
function getUniqueSelector(selectorConfig, element) {
|
|
21
|
+
let currentElement = element;
|
|
22
|
+
const parts = [];
|
|
23
|
+
const MAX_DEPTH = 3;
|
|
24
|
+
let currentDepth = 0;
|
|
25
|
+
while (currentElement && currentElement.localName !== 'body' && currentDepth <= MAX_DEPTH) {
|
|
26
|
+
const tagName = currentElement.localName;
|
|
27
|
+
let selectorPart = tagName;
|
|
28
|
+
if (selectorConfig.id && currentElement.id && isValidSelector(`#${currentElement.id}`)) {
|
|
29
|
+
selectorPart += `#${currentElement.id}`;
|
|
30
|
+
} else if (selectorConfig.dataVC) {
|
|
31
|
+
selectorPart += getAttributeSelector(currentElement, 'data-vc');
|
|
32
|
+
} else if (selectorConfig.testId) {
|
|
33
|
+
selectorPart += getAttributeSelector(currentElement, 'data-testid') || getAttributeSelector(currentElement, 'data-test-id');
|
|
34
|
+
} else if (selectorConfig.role) {
|
|
35
|
+
selectorPart += getAttributeSelector(currentElement, 'role');
|
|
36
|
+
} else if (selectorConfig.className && currentElement.className) {
|
|
37
|
+
const classNames = Array.from(currentElement.classList).join('.');
|
|
38
|
+
if (classNames) {
|
|
39
|
+
if (isValidSelector(`.${classNames}`)) {
|
|
40
|
+
selectorPart += `.${classNames}`;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
parts.unshift(selectorPart);
|
|
45
|
+
const potentialSelector = parts.join(' > ').trim();
|
|
46
|
+
if (potentialSelector && isSelectorUnique(potentialSelector)) {
|
|
47
|
+
return potentialSelector;
|
|
48
|
+
}
|
|
49
|
+
currentElement = currentElement.parentElement;
|
|
50
|
+
currentDepth++;
|
|
51
|
+
}
|
|
52
|
+
const potentialSelector = parts.join(' > ').trim();
|
|
53
|
+
if (!potentialSelector) {
|
|
54
|
+
return 'unknown';
|
|
55
|
+
} else if (!isSelectorUnique(potentialSelector)) {
|
|
56
|
+
const parentElement = element.parentElement;
|
|
57
|
+
if (parentElement) {
|
|
58
|
+
return `${potentialSelector}:nth-child`; // NOTE: invalid DOM selector, but enough information for VC
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return potentialSelector;
|
|
62
|
+
}
|
|
63
|
+
export default function getElementName(selectorConfig, element) {
|
|
64
|
+
if (!(element instanceof HTMLElement)) {
|
|
65
|
+
return 'error';
|
|
66
|
+
}
|
|
67
|
+
const cachedName = nameCache.get(element);
|
|
68
|
+
if (cachedName) {
|
|
69
|
+
return cachedName;
|
|
70
|
+
}
|
|
71
|
+
const uniqueSelector = getUniqueSelector(selectorConfig, element);
|
|
72
|
+
nameCache.set(element, uniqueSelector);
|
|
73
|
+
return uniqueSelector;
|
|
74
|
+
}
|
|
@@ -3,6 +3,11 @@ import isViewportEntryData from '../utils/is-viewport-entry-data';
|
|
|
3
3
|
const ABORTING_WINDOW_EVENT = ['wheel', 'scroll', 'keydown', 'resize'];
|
|
4
4
|
const REVISION_NO = 'fy25.03';
|
|
5
5
|
const CONSIDERED_ENTRY_TYPE = ['mutation:child-element', 'mutation:element', 'mutation:attribute', 'layout-shift', 'window:event'];
|
|
6
|
+
|
|
7
|
+
// TODO: AFO-3523
|
|
8
|
+
// Those are the attributes we have found when testing the 'fy25.03' manually.
|
|
9
|
+
// We still need to replace this hardcoded list with a proper automation
|
|
10
|
+
export const KNOWN_ATTRIBUTES_THAT_DOES_NOT_CAUSE_LAYOUT_SHIFTS = ['data-drop-target-for-element', 'draggable'];
|
|
6
11
|
export default class VCCalculator_FY25_03 extends AbstractVCCalculatorBase {
|
|
7
12
|
constructor() {
|
|
8
13
|
super(REVISION_NO);
|
|
@@ -14,7 +19,7 @@ export default class VCCalculator_FY25_03 extends AbstractVCCalculatorBase {
|
|
|
14
19
|
if (entry.type === 'mutation:attribute') {
|
|
15
20
|
const entryData = entry.data;
|
|
16
21
|
const attributeName = entryData.attributeName;
|
|
17
|
-
if (!attributeName) {
|
|
22
|
+
if (!attributeName || KNOWN_ATTRIBUTES_THAT_DOES_NOT_CAUSE_LAYOUT_SHIFTS.includes(attributeName)) {
|
|
18
23
|
return false;
|
|
19
24
|
}
|
|
20
25
|
return true;
|
|
@@ -28,12 +28,14 @@ export class ViewportCanvas {
|
|
|
28
28
|
this.scaleFactor = scaleFactor;
|
|
29
29
|
this.colorCounter = 1;
|
|
30
30
|
this.colorTimeMap = new Map();
|
|
31
|
+
const safeViewportWidth = Math.max(viewport.width, 1);
|
|
32
|
+
const safeViewportHeight = Math.max(viewport.height, 1);
|
|
31
33
|
|
|
32
34
|
// Calculate scaled dimensions
|
|
33
|
-
this.scaledWidth = Math.ceil(
|
|
34
|
-
this.scaledHeight = Math.ceil(
|
|
35
|
-
this.scaleX = this.scaledWidth /
|
|
36
|
-
this.scaleY = this.scaledHeight /
|
|
35
|
+
this.scaledWidth = Math.max(Math.ceil(safeViewportWidth * scaleFactor), 1);
|
|
36
|
+
this.scaledHeight = Math.max(Math.ceil(safeViewportHeight * scaleFactor), 1);
|
|
37
|
+
this.scaleX = this.scaledWidth / safeViewportWidth;
|
|
38
|
+
this.scaleY = this.scaledHeight / safeViewportHeight;
|
|
37
39
|
|
|
38
40
|
// Initialize OffscreenCanvas with scaled dimensions
|
|
39
41
|
this.canvas = document.createElement('canvas');
|
|
@@ -9,7 +9,9 @@ function isElementVisible(element) {
|
|
|
9
9
|
try {
|
|
10
10
|
const visible = element.checkVisibility({
|
|
11
11
|
// @ts-expect-error
|
|
12
|
-
visibilityProperty: true
|
|
12
|
+
visibilityProperty: true,
|
|
13
|
+
contentVisibilityAuto: true,
|
|
14
|
+
opacityProperty: true
|
|
13
15
|
});
|
|
14
16
|
return visible;
|
|
15
17
|
} catch (e) {
|
|
@@ -42,6 +44,9 @@ export default class ViewportObserver {
|
|
|
42
44
|
type,
|
|
43
45
|
mutationData
|
|
44
46
|
}) => {
|
|
47
|
+
if (!target) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
45
50
|
const visible = isElementVisible(target);
|
|
46
51
|
const lastElementRect = this.mapVisibleNodeRects.get(target);
|
|
47
52
|
this.mapVisibleNodeRects.set(target, rect);
|
|
@@ -132,14 +137,17 @@ export default class ViewportObserver {
|
|
|
132
137
|
changedRects
|
|
133
138
|
}) => {
|
|
134
139
|
for (const changedRect of changedRects) {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
140
|
+
const target = changedRect.node;
|
|
141
|
+
if (target) {
|
|
142
|
+
onChange({
|
|
143
|
+
time,
|
|
144
|
+
elementRef: new WeakRef(target),
|
|
145
|
+
visible: true,
|
|
146
|
+
rect: changedRect.rect,
|
|
147
|
+
previousRect: changedRect.previousRect,
|
|
148
|
+
type: 'layout-shift'
|
|
149
|
+
});
|
|
150
|
+
}
|
|
143
151
|
}
|
|
144
152
|
}
|
|
145
153
|
});
|
|
@@ -2,6 +2,10 @@ import { PerformanceObserverEntryTypes } from '../../const';
|
|
|
2
2
|
import { EntriesBuffer } from '../buffer';
|
|
3
3
|
var pe = null;
|
|
4
4
|
var getObserver = function getObserver() {
|
|
5
|
+
if (typeof PerformanceObserver !== 'function') {
|
|
6
|
+
// Only instantiate the IntersectionObserver if it's supported
|
|
7
|
+
return null;
|
|
8
|
+
}
|
|
5
9
|
if (pe !== null) {
|
|
6
10
|
return pe;
|
|
7
11
|
}
|
|
@@ -18,13 +22,15 @@ var getObserver = function getObserver() {
|
|
|
18
22
|
return pe;
|
|
19
23
|
};
|
|
20
24
|
export var startLSObserver = function startLSObserver() {
|
|
21
|
-
|
|
25
|
+
var _getObserver;
|
|
26
|
+
(_getObserver = getObserver()) === null || _getObserver === void 0 || _getObserver.observe({
|
|
22
27
|
type: PerformanceObserverEntryTypes.LayoutShift,
|
|
23
28
|
buffered: true
|
|
24
29
|
});
|
|
25
30
|
};
|
|
26
31
|
export var startLTObserver = function startLTObserver() {
|
|
27
|
-
|
|
32
|
+
var _getObserver2;
|
|
33
|
+
(_getObserver2 = getObserver()) === null || _getObserver2 === void 0 || _getObserver2.observe({
|
|
28
34
|
type: PerformanceObserverEntryTypes.LongTask,
|
|
29
35
|
buffered: true
|
|
30
36
|
});
|