@atlaskit/react-ufo 5.2.9 → 5.3.0
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/create-payload/index.js +6 -5
- package/dist/cjs/hidden-timing/index.js +52 -0
- package/dist/cjs/interaction-metrics/index.js +1 -0
- package/dist/cjs/interaction-metrics-init/index.js +2 -1
- package/dist/cjs/vc/index.js +4 -2
- package/dist/cjs/vc/vc-observer-new/index.js +10 -4
- package/dist/cjs/vc/vc-observer-new/metric-calculator/abstract-base-vc-calculator.js +84 -29
- package/dist/cjs/vc/vc-observer-new/metric-calculator/utils/detect-layout-shift-cause.js +164 -0
- package/dist/cjs/vc/vc-observer-new/viewport-observer/index.js +173 -34
- package/dist/cjs/vc/vc-observer-new/viewport-observer/intersection-observer/index.js +1 -1
- package/dist/cjs/vc/vc-observer-new/viewport-observer/mutation-observer/index.js +2 -1
- package/dist/es2019/create-payload/index.js +5 -3
- package/dist/es2019/hidden-timing/index.js +51 -0
- package/dist/es2019/interaction-metrics/index.js +1 -0
- package/dist/es2019/interaction-metrics-init/index.js +2 -1
- package/dist/es2019/vc/index.js +4 -2
- package/dist/es2019/vc/vc-observer-new/index.js +10 -5
- package/dist/es2019/vc/vc-observer-new/metric-calculator/abstract-base-vc-calculator.js +61 -7
- package/dist/es2019/vc/vc-observer-new/metric-calculator/utils/detect-layout-shift-cause.js +138 -0
- package/dist/es2019/vc/vc-observer-new/viewport-observer/index.js +145 -10
- package/dist/es2019/vc/vc-observer-new/viewport-observer/intersection-observer/index.js +1 -1
- package/dist/es2019/vc/vc-observer-new/viewport-observer/mutation-observer/index.js +2 -1
- package/dist/esm/create-payload/index.js +7 -6
- package/dist/esm/hidden-timing/index.js +51 -0
- package/dist/esm/interaction-metrics/index.js +1 -0
- package/dist/esm/interaction-metrics-init/index.js +2 -1
- package/dist/esm/vc/index.js +4 -2
- package/dist/esm/vc/vc-observer-new/index.js +10 -4
- package/dist/esm/vc/vc-observer-new/metric-calculator/abstract-base-vc-calculator.js +84 -29
- package/dist/esm/vc/vc-observer-new/metric-calculator/utils/detect-layout-shift-cause.js +158 -0
- package/dist/esm/vc/vc-observer-new/viewport-observer/index.js +173 -34
- package/dist/esm/vc/vc-observer-new/viewport-observer/intersection-observer/index.js +1 -1
- package/dist/esm/vc/vc-observer-new/viewport-observer/mutation-observer/index.js +2 -1
- package/dist/types/common/react-ufo-payload-schema.d.ts +2 -0
- package/dist/types/common/vc/types.d.ts +55 -0
- package/dist/types/config/index.d.ts +1 -0
- package/dist/types/hidden-timing/index.d.ts +9 -0
- package/dist/types/vc/types.d.ts +2 -0
- package/dist/types/vc/vc-observer-new/index.d.ts +2 -0
- package/dist/types/vc/vc-observer-new/metric-calculator/abstract-base-vc-calculator.d.ts +1 -1
- package/dist/types/vc/vc-observer-new/metric-calculator/types.d.ts +1 -0
- package/dist/types/vc/vc-observer-new/metric-calculator/utils/detect-layout-shift-cause.d.ts +31 -0
- package/dist/types/vc/vc-observer-new/types.d.ts +2 -0
- package/dist/types/vc/vc-observer-new/viewport-observer/index.d.ts +3 -1
- package/dist/types/vc/vc-observer-new/viewport-observer/mutation-observer/index.d.ts +1 -0
- package/dist/types/vc/vc-observer-new/viewport-observer/types.d.ts +5 -2
- package/dist/types-ts4.5/common/react-ufo-payload-schema.d.ts +2 -0
- package/dist/types-ts4.5/common/vc/types.d.ts +55 -0
- package/dist/types-ts4.5/config/index.d.ts +1 -0
- package/dist/types-ts4.5/hidden-timing/index.d.ts +9 -0
- package/dist/types-ts4.5/vc/types.d.ts +2 -0
- package/dist/types-ts4.5/vc/vc-observer-new/index.d.ts +2 -0
- package/dist/types-ts4.5/vc/vc-observer-new/metric-calculator/abstract-base-vc-calculator.d.ts +1 -1
- package/dist/types-ts4.5/vc/vc-observer-new/metric-calculator/types.d.ts +1 -0
- package/dist/types-ts4.5/vc/vc-observer-new/metric-calculator/utils/detect-layout-shift-cause.d.ts +31 -0
- package/dist/types-ts4.5/vc/vc-observer-new/types.d.ts +2 -0
- package/dist/types-ts4.5/vc/vc-observer-new/viewport-observer/index.d.ts +3 -1
- package/dist/types-ts4.5/vc/vc-observer-new/viewport-observer/mutation-observer/index.d.ts +1 -0
- package/dist/types-ts4.5/vc/vc-observer-new/viewport-observer/types.d.ts +5 -2
- package/package.json +4 -1
package/dist/es2019/vc/index.js
CHANGED
|
@@ -16,7 +16,7 @@ export class VCObserverWrapper {
|
|
|
16
16
|
enablePageLayoutPlaceholder: (_opts$ssrEnablePageLa = opts.ssrEnablePageLayoutPlaceholder) !== null && _opts$ssrEnablePageLa !== void 0 ? _opts$ssrEnablePageLa : false
|
|
17
17
|
});
|
|
18
18
|
if (isVCRevisionEnabled('fy25.03') || isVCRevisionEnabled('fy26.04')) {
|
|
19
|
-
var _opts$ssrEnablePageLa2;
|
|
19
|
+
var _opts$ssrEnablePageLa2, _opts$trackLayoutShif;
|
|
20
20
|
this.newVCObserver = new VCObserverNew({
|
|
21
21
|
selectorConfig: opts.selectorConfig,
|
|
22
22
|
isPostInteraction: opts.isPostInteraction,
|
|
@@ -24,6 +24,7 @@ export class VCObserverWrapper {
|
|
|
24
24
|
enablePageLayoutPlaceholder: (_opts$ssrEnablePageLa2 = opts.ssrEnablePageLayoutPlaceholder) !== null && _opts$ssrEnablePageLa2 !== void 0 ? _opts$ssrEnablePageLa2 : false
|
|
25
25
|
},
|
|
26
26
|
ssrPlaceholderHandler: this.ssrPlaceholderHandler,
|
|
27
|
+
trackLayoutShiftOffenders: (_opts$trackLayoutShif = opts.trackLayoutShiftOffenders) !== null && _opts$trackLayoutShif !== void 0 ? _opts$trackLayoutShif : false,
|
|
27
28
|
searchPageConfig: opts.searchPageConfig
|
|
28
29
|
});
|
|
29
30
|
}
|
|
@@ -115,7 +116,8 @@ export class VCObserverWrapper {
|
|
|
115
116
|
interactionAbortReason: param.interactionAbortReason,
|
|
116
117
|
includeRawData,
|
|
117
118
|
includeSSRInV3: param.includeSSRInV3,
|
|
118
|
-
rawDataStopTime: param.rawDataStopTime
|
|
119
|
+
rawDataStopTime: param.rawDataStopTime,
|
|
120
|
+
reportLayoutShiftOffenders: param.reportLayoutShiftOffenders
|
|
119
121
|
})) : [];
|
|
120
122
|
if (!v3v4Result || v3v4Result.length === 0) {
|
|
121
123
|
return v1v2Result !== null && v1v2Result !== void 0 ? v1v2Result : {};
|
|
@@ -31,7 +31,7 @@ export function getHasAbortingEventDuringSSR() {
|
|
|
31
31
|
}
|
|
32
32
|
export default class VCObserverNew {
|
|
33
33
|
constructor(config) {
|
|
34
|
-
var _config$isPostInterac, _config$selectorConfi;
|
|
34
|
+
var _config$isPostInterac, _config$selectorConfi, _config$trackLayoutSh;
|
|
35
35
|
_defineProperty(this, "viewportObserver", null);
|
|
36
36
|
_defineProperty(this, "windowEventObserver", null);
|
|
37
37
|
// SSR related properties
|
|
@@ -55,6 +55,7 @@ export default class VCObserverNew {
|
|
|
55
55
|
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
|
|
56
56
|
});
|
|
57
57
|
}
|
|
58
|
+
this.trackLayoutShiftOffenders = (_config$trackLayoutSh = config.trackLayoutShiftOffenders) !== null && _config$trackLayoutSh !== void 0 ? _config$trackLayoutSh : false;
|
|
58
59
|
this.viewportObserver = new ViewportObserver({
|
|
59
60
|
onChange: onChangeArg => {
|
|
60
61
|
const {
|
|
@@ -79,7 +80,8 @@ export default class VCObserverNew {
|
|
|
79
80
|
visible,
|
|
80
81
|
attributeName: mutationData === null || mutationData === void 0 ? void 0 : mutationData.attributeName,
|
|
81
82
|
oldValue: mutationData === null || mutationData === void 0 ? void 0 : mutationData.oldValue,
|
|
82
|
-
newValue: mutationData === null || mutationData === void 0 ? void 0 : mutationData.newValue
|
|
83
|
+
newValue: mutationData === null || mutationData === void 0 ? void 0 : mutationData.newValue,
|
|
84
|
+
originalMutationTimestamp: mutationData === null || mutationData === void 0 ? void 0 : mutationData.timestamp
|
|
83
85
|
};
|
|
84
86
|
if (element) {
|
|
85
87
|
const labelStacks = getLabelStacks(element);
|
|
@@ -97,7 +99,8 @@ export default class VCObserverNew {
|
|
|
97
99
|
// Pass SSR context to ViewportObserver
|
|
98
100
|
getSSRState: () => this.getSSRState(),
|
|
99
101
|
getSSRPlaceholderHandler: () => this.getSSRPlaceholderHandler(),
|
|
100
|
-
searchPageConfig: config.searchPageConfig
|
|
102
|
+
searchPageConfig: config.searchPageConfig,
|
|
103
|
+
trackLayoutShiftOffenders: this.trackLayoutShiftOffenders
|
|
101
104
|
});
|
|
102
105
|
this.windowEventObserver = new WindowEventObserver({
|
|
103
106
|
onEvent: ({
|
|
@@ -271,7 +274,8 @@ export default class VCObserverNew {
|
|
|
271
274
|
excludeSmartAnswersInSearch,
|
|
272
275
|
includeSSRRatio,
|
|
273
276
|
isPageVisible,
|
|
274
|
-
interactionAbortReason
|
|
277
|
+
interactionAbortReason,
|
|
278
|
+
reportLayoutShiftOffenders: this.trackLayoutShiftOffenders
|
|
275
279
|
}) : null;
|
|
276
280
|
if (fy25_03) {
|
|
277
281
|
results.push(fy25_03);
|
|
@@ -294,7 +298,8 @@ export default class VCObserverNew {
|
|
|
294
298
|
excludeSmartAnswersInSearch,
|
|
295
299
|
includeSSRRatio,
|
|
296
300
|
isPageVisible,
|
|
297
|
-
interactionAbortReason
|
|
301
|
+
interactionAbortReason,
|
|
302
|
+
reportLayoutShiftOffenders: this.trackLayoutShiftOffenders
|
|
298
303
|
};
|
|
299
304
|
const [fy26_04, vcNext] = await Promise.all([isVCRevisionEnabled('fy26.04') ? calculator_fy26_04.calculate(calculatorParams) : null, isVCRevisionEnabled('next') ? calculator_next.calculate(calculatorParams) : null]);
|
|
300
305
|
if (fy26_04) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { fg } from '@atlaskit/platform-feature-flags';
|
|
2
2
|
import { calculateTTVCPercentilesWithDebugInfo } from './percentile-calc';
|
|
3
|
+
import { detectLayoutShiftCause } from './utils/detect-layout-shift-cause';
|
|
3
4
|
import getViewportHeight from './utils/get-viewport-height';
|
|
4
5
|
import getViewportWidth from './utils/get-viewport-width';
|
|
5
6
|
|
|
@@ -60,7 +61,7 @@ export default class AbstractVCCalculatorBase {
|
|
|
60
61
|
}
|
|
61
62
|
return labelStacks;
|
|
62
63
|
}
|
|
63
|
-
async calculateWithDebugInfo(filteredEntries, startTime, stopTime, isPostInteraction, isVCClean, interactionType, isPageVisible, interactionId, dirtyReason, allEntries, include3p, excludeSmartAnswersInSearch, interactionAbortReason, includeSSRRatio) {
|
|
64
|
+
async calculateWithDebugInfo(filteredEntries, startTime, stopTime, isPostInteraction, isVCClean, interactionType, isPageVisible, interactionId, dirtyReason, allEntries, include3p, excludeSmartAnswersInSearch, interactionAbortReason, includeSSRRatio, reportLayoutShiftOffenders) {
|
|
64
65
|
var _window, _window2, _window3, _window4, _window6;
|
|
65
66
|
const percentiles = [25, 50, 75, 80, 85, 90, 95, 98, 99, 100];
|
|
66
67
|
const viewportEntries = this.filterViewportEntries(filteredEntries);
|
|
@@ -80,6 +81,8 @@ export default class AbstractVCCalculatorBase {
|
|
|
80
81
|
let percentileIndex = 0;
|
|
81
82
|
const entryDataBuffer = new Set();
|
|
82
83
|
let ssrRatio = -1;
|
|
84
|
+
let layoutShiftInsights = null;
|
|
85
|
+
let previousViewportPercentage = 0;
|
|
83
86
|
if (vcLogs) {
|
|
84
87
|
for (const entry of vcLogs) {
|
|
85
88
|
const {
|
|
@@ -106,6 +109,19 @@ export default class AbstractVCCalculatorBase {
|
|
|
106
109
|
t: Math.round(time),
|
|
107
110
|
e: elementNames
|
|
108
111
|
};
|
|
112
|
+
if (reportLayoutShiftOffenders && percentiles[percentileIndex] === 90 && entries.some(e => e.type === 'layout-shift')) {
|
|
113
|
+
const layoutShiftEntries = entries.filter(e => e.type === 'layout-shift');
|
|
114
|
+
layoutShiftInsights = {
|
|
115
|
+
layoutShiftOffendersResult: detectLayoutShiftCause({
|
|
116
|
+
viewportEntries: viewportEntries,
|
|
117
|
+
layoutShiftEntries,
|
|
118
|
+
time,
|
|
119
|
+
startTime
|
|
120
|
+
}),
|
|
121
|
+
layoutShiftEntriesCount: layoutShiftEntries.length,
|
|
122
|
+
layoutShiftImpact: viewportPercentage - previousViewportPercentage
|
|
123
|
+
};
|
|
124
|
+
}
|
|
109
125
|
percentileIndex++;
|
|
110
126
|
}
|
|
111
127
|
|
|
@@ -115,6 +131,7 @@ export default class AbstractVCCalculatorBase {
|
|
|
115
131
|
// Only add to buffer if we haven't reached all percentiles
|
|
116
132
|
entries.forEach(e => entryDataBuffer.add(e));
|
|
117
133
|
}
|
|
134
|
+
previousViewportPercentage = viewportPercentage;
|
|
118
135
|
}
|
|
119
136
|
}
|
|
120
137
|
|
|
@@ -262,7 +279,8 @@ export default class AbstractVCCalculatorBase {
|
|
|
262
279
|
return {
|
|
263
280
|
vcDetails,
|
|
264
281
|
ssrRatio,
|
|
265
|
-
speedIndex
|
|
282
|
+
speedIndex,
|
|
283
|
+
VC90layoutShiftInsights: layoutShiftInsights
|
|
266
284
|
};
|
|
267
285
|
}
|
|
268
286
|
async calculate({
|
|
@@ -276,9 +294,10 @@ export default class AbstractVCCalculatorBase {
|
|
|
276
294
|
includeSSRRatio,
|
|
277
295
|
interactionType,
|
|
278
296
|
isPageVisible,
|
|
279
|
-
interactionAbortReason
|
|
297
|
+
interactionAbortReason,
|
|
298
|
+
reportLayoutShiftOffenders
|
|
280
299
|
}) {
|
|
281
|
-
var _vcDetails$90$t, _vcDetails
|
|
300
|
+
var _vcDetails$90$t, _vcDetails$, _layoutShiftInsightsP;
|
|
282
301
|
const filteredEntries = orderedEntries.filter(entry => {
|
|
283
302
|
return this.isEntryIncluded(entry, include3p, excludeSmartAnswersInSearch);
|
|
284
303
|
});
|
|
@@ -299,13 +318,48 @@ export default class AbstractVCCalculatorBase {
|
|
|
299
318
|
const {
|
|
300
319
|
vcDetails,
|
|
301
320
|
ssrRatio,
|
|
302
|
-
speedIndex
|
|
303
|
-
|
|
321
|
+
speedIndex,
|
|
322
|
+
VC90layoutShiftInsights
|
|
323
|
+
} = await this.calculateWithDebugInfo(filteredEntries, startTime, stopTime, isPostInteraction, isVCClean, interactionType, isPageVisible, interactionId, dirtyReason, orderedEntries, include3p, excludeSmartAnswersInSearch, interactionAbortReason, includeSSRRatio, reportLayoutShiftOffenders);
|
|
324
|
+
let layoutShiftInsightsPayload;
|
|
325
|
+
if (VC90layoutShiftInsights !== null && reportLayoutShiftOffenders) {
|
|
326
|
+
var _layoutShiftOffenders, _layoutShiftOffenders2, _layoutShiftOffenders3;
|
|
327
|
+
const {
|
|
328
|
+
layoutShiftOffendersResult,
|
|
329
|
+
layoutShiftEntriesCount,
|
|
330
|
+
layoutShiftImpact
|
|
331
|
+
} = VC90layoutShiftInsights;
|
|
332
|
+
layoutShiftInsightsPayload = {
|
|
333
|
+
impact: layoutShiftImpact,
|
|
334
|
+
sources: layoutShiftEntriesCount !== null && layoutShiftEntriesCount !== void 0 ? layoutShiftEntriesCount : 0,
|
|
335
|
+
same: {
|
|
336
|
+
dir: (_layoutShiftOffenders = layoutShiftOffendersResult === null || layoutShiftOffendersResult === void 0 ? void 0 : layoutShiftOffendersResult.layoutShiftVariables.allMovedSameWay) !== null && _layoutShiftOffenders !== void 0 ? _layoutShiftOffenders : false,
|
|
337
|
+
dist: (_layoutShiftOffenders2 = layoutShiftOffendersResult === null || layoutShiftOffendersResult === void 0 ? void 0 : layoutShiftOffendersResult.layoutShiftVariables.allMovedSameAmount) !== null && _layoutShiftOffenders2 !== void 0 ? _layoutShiftOffenders2 : false
|
|
338
|
+
},
|
|
339
|
+
total_mut: (_layoutShiftOffenders3 = layoutShiftOffendersResult === null || layoutShiftOffendersResult === void 0 ? void 0 : layoutShiftOffendersResult.layoutShiftOffenders.length) !== null && _layoutShiftOffenders3 !== void 0 ? _layoutShiftOffenders3 : 0,
|
|
340
|
+
mut: layoutShiftOffendersResult === null || layoutShiftOffendersResult === void 0 ? void 0 : layoutShiftOffendersResult.layoutShiftOffenders.sort((a, b) => Math.abs(a.distanceToLS) - Math.abs(b.distanceToLS)).slice(0, 5).map(offender => ({
|
|
341
|
+
e: offender.offender,
|
|
342
|
+
size: -1,
|
|
343
|
+
// @todo: calculate size
|
|
344
|
+
attr: {
|
|
345
|
+
t_before: offender.happenedBefore,
|
|
346
|
+
t_distance: offender.distanceToLS,
|
|
347
|
+
p_above: offender.isAbove,
|
|
348
|
+
p_left: offender.isLeft,
|
|
349
|
+
p_right: offender.isRight,
|
|
350
|
+
p_h_overlap: offender.hasHorizontalOverlap,
|
|
351
|
+
p_v_overlap: offender.hasVerticalOverlap,
|
|
352
|
+
p_same_offset: offender.matchesLayoutShiftDelta ? 'all' : 'none'
|
|
353
|
+
}
|
|
354
|
+
}))
|
|
355
|
+
};
|
|
356
|
+
}
|
|
304
357
|
const result = {
|
|
305
358
|
revision: this.revisionNo,
|
|
306
359
|
clean: true,
|
|
307
360
|
'metric:vc90': (_vcDetails$90$t = vcDetails === null || vcDetails === void 0 ? void 0 : (_vcDetails$ = vcDetails['90']) === null || _vcDetails$ === void 0 ? void 0 : _vcDetails$.t) !== null && _vcDetails$90$t !== void 0 ? _vcDetails$90$t : null,
|
|
308
|
-
vcDetails: vcDetails !== null && vcDetails !== void 0 ? vcDetails : undefined
|
|
361
|
+
vcDetails: vcDetails !== null && vcDetails !== void 0 ? vcDetails : undefined,
|
|
362
|
+
'vc90:ls': (_layoutShiftInsightsP = layoutShiftInsightsPayload) !== null && _layoutShiftInsightsP !== void 0 ? _layoutShiftInsightsP : undefined
|
|
309
363
|
};
|
|
310
364
|
result.ratios = this.calculateRatios(filteredEntries);
|
|
311
365
|
if (ssrRatio !== -1) {
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
const calculateOffenderState = (current, matchesCurrentEntry) => {
|
|
2
|
+
// Once we know it's mixed across entries, we can short-circuit.
|
|
3
|
+
if (current === 'some') {
|
|
4
|
+
return 'some';
|
|
5
|
+
}
|
|
6
|
+
const entryState = matchesCurrentEntry ? 'all' : 'none';
|
|
7
|
+
if (current == null) {
|
|
8
|
+
return entryState;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// If the state flips between entries (all <-> none), it's "some".
|
|
12
|
+
if (current !== entryState) {
|
|
13
|
+
return 'some';
|
|
14
|
+
}
|
|
15
|
+
return current;
|
|
16
|
+
};
|
|
17
|
+
const calculateLayoutShiftMovement = layoutShiftEntries => {
|
|
18
|
+
const movements = layoutShiftEntries.map(lsEntry => {
|
|
19
|
+
const rect = lsEntry.rect;
|
|
20
|
+
const previousRect = lsEntry.previousRect;
|
|
21
|
+
if (!rect || !previousRect) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
const dx = rect.x - previousRect.x;
|
|
25
|
+
const dy = rect.y - previousRect.y;
|
|
26
|
+
return {
|
|
27
|
+
dx,
|
|
28
|
+
dy,
|
|
29
|
+
movedX: dx !== 0,
|
|
30
|
+
movedY: dy !== 0,
|
|
31
|
+
dirX: Math.sign(dx),
|
|
32
|
+
dirY: Math.sign(dy),
|
|
33
|
+
absDx: Math.abs(dx),
|
|
34
|
+
absDy: Math.abs(dy)
|
|
35
|
+
};
|
|
36
|
+
});
|
|
37
|
+
const hasMovement = m => m !== null;
|
|
38
|
+
const validMovements = movements.filter(hasMovement);
|
|
39
|
+
const allHaveRects = layoutShiftEntries.length > 0 && validMovements.length === layoutShiftEntries.length;
|
|
40
|
+
if (!allHaveRects) {
|
|
41
|
+
return {
|
|
42
|
+
allHaveRects,
|
|
43
|
+
allMovedSameWay: false,
|
|
44
|
+
allMovedSameAmount: false
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
const first = validMovements[0];
|
|
48
|
+
const allMovedSameWay = validMovements.every(m => m.movedX === first.movedX && m.movedY === first.movedY && m.dirX === first.dirX && m.dirY === first.dirY);
|
|
49
|
+
const allMovedSameAmount = allMovedSameWay && validMovements.every(m => m.absDx === first.absDx && m.absDy === first.absDy);
|
|
50
|
+
return {
|
|
51
|
+
allHaveRects,
|
|
52
|
+
allMovedSameWay,
|
|
53
|
+
allMovedSameAmount,
|
|
54
|
+
deltaX: first.dx,
|
|
55
|
+
deltaY: first.dy
|
|
56
|
+
};
|
|
57
|
+
};
|
|
58
|
+
export const detectLayoutShiftCause = ({
|
|
59
|
+
viewportEntries,
|
|
60
|
+
layoutShiftEntries,
|
|
61
|
+
time,
|
|
62
|
+
startTime,
|
|
63
|
+
offenderWindowMs = 250
|
|
64
|
+
}) => {
|
|
65
|
+
const checkpointTimestamp = startTime + time;
|
|
66
|
+
const filteredLSPotentialOffenders = viewportEntries.filter(viewportEntry => (viewportEntry.time < checkpointTimestamp && viewportEntry.time > checkpointTimestamp - offenderWindowMs || viewportEntry.time > checkpointTimestamp && viewportEntry.time < checkpointTimestamp + offenderWindowMs) && viewportEntry.data.type !== 'layout-shift');
|
|
67
|
+
|
|
68
|
+
// Summarize whether all layout-shift entries moved in a consistent direction and by the same amount.
|
|
69
|
+
const layoutShiftVariables = calculateLayoutShiftMovement(layoutShiftEntries);
|
|
70
|
+
|
|
71
|
+
// Classify each offender against *all* layout-shift entries when the layout shift is consistently moving in a
|
|
72
|
+
// single axis (pure X or pure Y) so we can reason about whether an offender could have caused the movement.
|
|
73
|
+
const layoutShiftOffenders = filteredLSPotentialOffenders.reduce((acc, offender) => {
|
|
74
|
+
var _offenderData$origina, _isAbove, _isLeft, _isRight, _hasHorizontalOverlap, _hasVerticalOverlap;
|
|
75
|
+
const offenderData = offender.data;
|
|
76
|
+
const offenderRect = offenderData.rect;
|
|
77
|
+
if (!offenderRect) {
|
|
78
|
+
return acc;
|
|
79
|
+
}
|
|
80
|
+
const offenderTimestamp = (_offenderData$origina = offenderData.originalMutationTimestamp) !== null && _offenderData$origina !== void 0 ? _offenderData$origina : offender.time;
|
|
81
|
+
const happenedBefore = offenderTimestamp <= checkpointTimestamp;
|
|
82
|
+
const offenderLeft = offenderRect.x;
|
|
83
|
+
const offenderRight = offenderRect.x + offenderRect.width;
|
|
84
|
+
const offenderTop = offenderRect.y;
|
|
85
|
+
const offenderBottom = offenderRect.y + offenderRect.height;
|
|
86
|
+
const shouldClassifyAgainstAllEntries = layoutShiftVariables.allMovedSameWay && 'deltaX' in layoutShiftVariables && 'deltaY' in layoutShiftVariables && layoutShiftVariables.deltaX === 0 !== (layoutShiftVariables.deltaY === 0);
|
|
87
|
+
let isAbove = null;
|
|
88
|
+
let isLeft = null;
|
|
89
|
+
let isRight = null;
|
|
90
|
+
let hasHorizontalOverlap = null;
|
|
91
|
+
let hasVerticalOverlap = null;
|
|
92
|
+
if (shouldClassifyAgainstAllEntries) {
|
|
93
|
+
for (const lsEntry of layoutShiftEntries) {
|
|
94
|
+
const lsRect = lsEntry.rect;
|
|
95
|
+
if (!lsRect) {
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
const lsLeft = lsRect.x;
|
|
99
|
+
const lsRight = lsRect.x + lsRect.width;
|
|
100
|
+
const lsTop = lsRect.y;
|
|
101
|
+
const lsBottom = lsRect.y + lsRect.height;
|
|
102
|
+
isAbove = calculateOffenderState(isAbove, offenderBottom <= lsTop);
|
|
103
|
+
isLeft = calculateOffenderState(isLeft, offenderRight <= lsLeft);
|
|
104
|
+
isRight = calculateOffenderState(isRight, offenderLeft >= lsRight);
|
|
105
|
+
hasHorizontalOverlap = calculateOffenderState(hasHorizontalOverlap, offenderLeft < lsRight && offenderRight > lsLeft);
|
|
106
|
+
hasVerticalOverlap = calculateOffenderState(hasVerticalOverlap, offenderTop < lsBottom && offenderBottom > lsTop);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
let matchesLayoutShiftDelta = false;
|
|
110
|
+
if (shouldClassifyAgainstAllEntries && layoutShiftVariables.allMovedSameAmount && offenderData.previousRect) {
|
|
111
|
+
const dx = offenderRect.x - offenderData.previousRect.x;
|
|
112
|
+
const dy = offenderRect.y - offenderData.previousRect.y;
|
|
113
|
+
const isPureX = layoutShiftVariables.deltaX !== 0 && layoutShiftVariables.deltaY === 0;
|
|
114
|
+
const isPureY = layoutShiftVariables.deltaY !== 0 && layoutShiftVariables.deltaX === 0;
|
|
115
|
+
if (isPureX) {
|
|
116
|
+
matchesLayoutShiftDelta = dx === layoutShiftVariables.deltaX && dy === 0;
|
|
117
|
+
} else if (isPureY) {
|
|
118
|
+
matchesLayoutShiftDelta = dy === layoutShiftVariables.deltaY && dx === 0;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
acc.push({
|
|
122
|
+
offender: offenderData.elementName,
|
|
123
|
+
happenedBefore,
|
|
124
|
+
distanceToLS: Math.round(offenderTimestamp - checkpointTimestamp),
|
|
125
|
+
isAbove: (_isAbove = isAbove) !== null && _isAbove !== void 0 ? _isAbove : 'none',
|
|
126
|
+
isLeft: (_isLeft = isLeft) !== null && _isLeft !== void 0 ? _isLeft : 'none',
|
|
127
|
+
isRight: (_isRight = isRight) !== null && _isRight !== void 0 ? _isRight : 'none',
|
|
128
|
+
hasHorizontalOverlap: (_hasHorizontalOverlap = hasHorizontalOverlap) !== null && _hasHorizontalOverlap !== void 0 ? _hasHorizontalOverlap : 'none',
|
|
129
|
+
hasVerticalOverlap: (_hasVerticalOverlap = hasVerticalOverlap) !== null && _hasVerticalOverlap !== void 0 ? _hasVerticalOverlap : 'none',
|
|
130
|
+
matchesLayoutShiftDelta
|
|
131
|
+
});
|
|
132
|
+
return acc;
|
|
133
|
+
}, []);
|
|
134
|
+
return {
|
|
135
|
+
layoutShiftVariables,
|
|
136
|
+
layoutShiftOffenders
|
|
137
|
+
};
|
|
138
|
+
};
|
|
@@ -78,6 +78,130 @@ const createElementMutationsWatcher = (removedNodeRects, isWithinThirdPartySegme
|
|
|
78
78
|
}
|
|
79
79
|
return 'mutation:element';
|
|
80
80
|
};
|
|
81
|
+
const createElementMutationsWatcherNew = (removedNodeRects, isWithinThirdPartySegment, isWithinSmartAnswersSegment, hasSameDeletedNode, timestamp, isTargetReactRoot, getSSRState, getSSRPlaceholderHandler) => ({
|
|
82
|
+
target,
|
|
83
|
+
rect
|
|
84
|
+
}) => {
|
|
85
|
+
if (getSSRState) {
|
|
86
|
+
const ssrState = getSSRState();
|
|
87
|
+
const SSRStateEnum = {
|
|
88
|
+
normal: 1,
|
|
89
|
+
waitingForFirstRender: 2,
|
|
90
|
+
ignoring: 3
|
|
91
|
+
};
|
|
92
|
+
if (ssrState.state === SSRStateEnum.waitingForFirstRender && timestamp > ssrState.renderStart && isTargetReactRoot) {
|
|
93
|
+
ssrState.state = SSRStateEnum.ignoring;
|
|
94
|
+
if (ssrState.renderStop === -1) {
|
|
95
|
+
// arbitrary 500ms DOM update window
|
|
96
|
+
ssrState.renderStop = timestamp + 500;
|
|
97
|
+
}
|
|
98
|
+
return {
|
|
99
|
+
type: 'ssr-hydration',
|
|
100
|
+
mutationData: {
|
|
101
|
+
timestamp
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
if (ssrState.state === SSRStateEnum.ignoring && timestamp > ssrState.renderStart && isTargetReactRoot) {
|
|
106
|
+
if (timestamp <= ssrState.renderStop) {
|
|
107
|
+
return {
|
|
108
|
+
type: 'ssr-hydration',
|
|
109
|
+
mutationData: {
|
|
110
|
+
timestamp
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
} else {
|
|
114
|
+
ssrState.state = SSRStateEnum.normal;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if (getSSRPlaceholderHandler) {
|
|
119
|
+
const ssrPlaceholderHandler = getSSRPlaceholderHandler();
|
|
120
|
+
if (ssrPlaceholderHandler) {
|
|
121
|
+
if ((ssrPlaceholderHandler.isPlaceholderV4(target) || ssrPlaceholderHandler.isPlaceholderIgnored(target)) && ssrPlaceholderHandler.checkIfExistedAndSizeMatchingV3(target)) {
|
|
122
|
+
return {
|
|
123
|
+
type: 'mutation:ssr-placeholder',
|
|
124
|
+
mutationData: {
|
|
125
|
+
timestamp
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
if ((ssrPlaceholderHandler.isPlaceholderReplacementV4(target) || ssrPlaceholderHandler.isPlaceholderIgnored(target)) && ssrPlaceholderHandler.validateReactComponentMatchToPlaceholderV4(target)) {
|
|
130
|
+
return {
|
|
131
|
+
type: 'mutation:ssr-placeholder',
|
|
132
|
+
mutationData: {
|
|
133
|
+
timestamp
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
if (hasSameDeletedNode && isInVCIgnoreIfNoLayoutShiftMarker(target)) {
|
|
140
|
+
return {
|
|
141
|
+
type: 'mutation:remount',
|
|
142
|
+
mutationData: {
|
|
143
|
+
timestamp
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
if (isContainedWithinMediaWrapper(target)) {
|
|
148
|
+
return {
|
|
149
|
+
type: 'mutation:media',
|
|
150
|
+
mutationData: {
|
|
151
|
+
timestamp
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
if (isWithinThirdPartySegment) {
|
|
156
|
+
return {
|
|
157
|
+
type: 'mutation:third-party-element',
|
|
158
|
+
mutationData: {
|
|
159
|
+
timestamp
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
if (isWithinSmartAnswersSegment) {
|
|
164
|
+
return {
|
|
165
|
+
type: 'mutation:smart-answers-element',
|
|
166
|
+
mutationData: {
|
|
167
|
+
timestamp
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
const isInIgnoreLsMarker = isInVCIgnoreIfNoLayoutShiftMarker(target);
|
|
172
|
+
if (!isInIgnoreLsMarker) {
|
|
173
|
+
return {
|
|
174
|
+
type: 'mutation:element',
|
|
175
|
+
mutationData: {
|
|
176
|
+
timestamp
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
const isRLLPlaceholder = RLLPlaceholderHandlers.getInstance().isRLLPlaceholderHydration(rect);
|
|
181
|
+
if (isRLLPlaceholder && isInIgnoreLsMarker) {
|
|
182
|
+
return {
|
|
183
|
+
type: 'mutation:rll-placeholder',
|
|
184
|
+
mutationData: {
|
|
185
|
+
timestamp
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
const wasDeleted = removedNodeRects.some(nr => isSameRectDimensions(nr, rect));
|
|
190
|
+
if (wasDeleted && isInIgnoreLsMarker) {
|
|
191
|
+
return {
|
|
192
|
+
type: 'mutation:element-replacement',
|
|
193
|
+
mutationData: {
|
|
194
|
+
timestamp
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
return {
|
|
199
|
+
type: 'mutation:element',
|
|
200
|
+
mutationData: {
|
|
201
|
+
timestamp
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
};
|
|
81
205
|
export default class ViewportObserver {
|
|
82
206
|
// SSR context functions
|
|
83
207
|
|
|
@@ -85,6 +209,7 @@ export default class ViewportObserver {
|
|
|
85
209
|
onChange,
|
|
86
210
|
getSSRState,
|
|
87
211
|
getSSRPlaceholderHandler,
|
|
212
|
+
trackLayoutShiftOffenders = false,
|
|
88
213
|
searchPageConfig
|
|
89
214
|
}) {
|
|
90
215
|
_defineProperty(this, "handleIntersectionEntry", ({
|
|
@@ -142,14 +267,15 @@ export default class ViewportObserver {
|
|
|
142
267
|
} = checkWithinComponent(addedNode, 'UFOThirdPartySegment', this.mapIs3pResult);
|
|
143
268
|
const isWithinSmartAnswersSegment = Boolean(this.shouldCheckSmartAnswersMutations() && isContainedWithinSmartAnswers(addedNode));
|
|
144
269
|
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);
|
|
145
|
-
(_this$intersectionObs = this.intersectionObserver) === null || _this$intersectionObs === void 0 ? void 0 : _this$intersectionObs.watchAndTag(addedNode, createElementMutationsWatcher(removedNodeRects, isWithinThirdPartySegment, isWithinSmartAnswersSegment, !!hasSameDeletedNode, timestamp, isTargetReactRoot, this.getSSRState, this.getSSRPlaceholderHandler));
|
|
270
|
+
(_this$intersectionObs = this.intersectionObserver) === null || _this$intersectionObs === void 0 ? void 0 : _this$intersectionObs.watchAndTag(addedNode, (this.trackLayoutShiftOffenders ? createElementMutationsWatcherNew : createElementMutationsWatcher)(removedNodeRects, isWithinThirdPartySegment, isWithinSmartAnswersSegment, !!hasSameDeletedNode, timestamp, isTargetReactRoot, this.getSSRState, this.getSSRPlaceholderHandler));
|
|
146
271
|
}
|
|
147
272
|
});
|
|
148
273
|
_defineProperty(this, "handleAttributeMutation", ({
|
|
149
274
|
target,
|
|
150
275
|
attributeName,
|
|
151
276
|
oldValue,
|
|
152
|
-
newValue
|
|
277
|
+
newValue,
|
|
278
|
+
timestamp
|
|
153
279
|
}) => {
|
|
154
280
|
var _this$intersectionObs2;
|
|
155
281
|
(_this$intersectionObs2 = this.intersectionObserver) === null || _this$intersectionObs2 === void 0 ? void 0 : _this$intersectionObs2.watchAndTag(target, ({
|
|
@@ -162,7 +288,8 @@ export default class ViewportObserver {
|
|
|
162
288
|
mutationData: {
|
|
163
289
|
attributeName,
|
|
164
290
|
oldValue,
|
|
165
|
-
newValue
|
|
291
|
+
newValue,
|
|
292
|
+
timestamp
|
|
166
293
|
}
|
|
167
294
|
};
|
|
168
295
|
}
|
|
@@ -176,7 +303,8 @@ export default class ViewportObserver {
|
|
|
176
303
|
mutationData: {
|
|
177
304
|
attributeName,
|
|
178
305
|
oldValue,
|
|
179
|
-
newValue
|
|
306
|
+
newValue,
|
|
307
|
+
timestamp
|
|
180
308
|
}
|
|
181
309
|
};
|
|
182
310
|
}
|
|
@@ -202,7 +330,8 @@ export default class ViewportObserver {
|
|
|
202
330
|
mutationData: {
|
|
203
331
|
attributeName,
|
|
204
332
|
oldValue,
|
|
205
|
-
newValue
|
|
333
|
+
newValue,
|
|
334
|
+
timestamp
|
|
206
335
|
}
|
|
207
336
|
};
|
|
208
337
|
}
|
|
@@ -216,7 +345,8 @@ export default class ViewportObserver {
|
|
|
216
345
|
mutationData: {
|
|
217
346
|
attributeName,
|
|
218
347
|
oldValue,
|
|
219
|
-
newValue
|
|
348
|
+
newValue,
|
|
349
|
+
timestamp
|
|
220
350
|
}
|
|
221
351
|
};
|
|
222
352
|
}
|
|
@@ -231,7 +361,8 @@ export default class ViewportObserver {
|
|
|
231
361
|
mutationData: {
|
|
232
362
|
attributeName,
|
|
233
363
|
oldValue,
|
|
234
|
-
newValue
|
|
364
|
+
newValue,
|
|
365
|
+
timestamp
|
|
235
366
|
}
|
|
236
367
|
};
|
|
237
368
|
}
|
|
@@ -242,7 +373,8 @@ export default class ViewportObserver {
|
|
|
242
373
|
mutationData: {
|
|
243
374
|
attributeName,
|
|
244
375
|
oldValue,
|
|
245
|
-
newValue
|
|
376
|
+
newValue,
|
|
377
|
+
timestamp
|
|
246
378
|
}
|
|
247
379
|
};
|
|
248
380
|
}
|
|
@@ -253,7 +385,8 @@ export default class ViewportObserver {
|
|
|
253
385
|
mutationData: {
|
|
254
386
|
attributeName,
|
|
255
387
|
oldValue,
|
|
256
|
-
newValue
|
|
388
|
+
newValue,
|
|
389
|
+
timestamp
|
|
257
390
|
}
|
|
258
391
|
};
|
|
259
392
|
}
|
|
@@ -262,7 +395,8 @@ export default class ViewportObserver {
|
|
|
262
395
|
mutationData: {
|
|
263
396
|
attributeName,
|
|
264
397
|
oldValue,
|
|
265
|
-
newValue
|
|
398
|
+
newValue,
|
|
399
|
+
timestamp
|
|
266
400
|
}
|
|
267
401
|
};
|
|
268
402
|
});
|
|
@@ -297,6 +431,7 @@ export default class ViewportObserver {
|
|
|
297
431
|
this.intersectionObserver = null;
|
|
298
432
|
this.mutationObserver = null;
|
|
299
433
|
this.performanceObserver = null;
|
|
434
|
+
this.trackLayoutShiftOffenders = trackLayoutShiftOffenders;
|
|
300
435
|
|
|
301
436
|
// Initialize SSR context functions
|
|
302
437
|
this.getSSRState = getSSRState;
|
|
@@ -29,7 +29,7 @@ export function createIntersectionObserver({
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
// override as display-contents mutation
|
|
32
|
-
if (tagOrCallbackResult && typeof tagOrCallbackResult !== 'string' && tagOrCallbackResult.type === 'mutation:attribute') {
|
|
32
|
+
if (tagOrCallbackResult && typeof tagOrCallbackResult !== 'string' && tagOrCallbackResult.type === 'mutation:attribute' && 'oldValue' in tagOrCallbackResult.mutationData) {
|
|
33
33
|
const {
|
|
34
34
|
attributeName,
|
|
35
35
|
oldValue,
|
|
@@ -45,7 +45,8 @@ function createMutationObserver({
|
|
|
45
45
|
target: mut.target,
|
|
46
46
|
attributeName: (_mut$attributeName = mut.attributeName) !== null && _mut$attributeName !== void 0 ? _mut$attributeName : 'unknown',
|
|
47
47
|
oldValue,
|
|
48
|
-
newValue
|
|
48
|
+
newValue,
|
|
49
|
+
timestamp: mut.timestamp || performance.now()
|
|
49
50
|
});
|
|
50
51
|
}
|
|
51
52
|
continue;
|