Next.js website for Rocky Mountain Vending company featuring: - Product catalog with Stripe integration - Service areas and parts pages - Admin dashboard with Clerk authentication - SEO optimized pages with JSON-LD structured data Co-authored-by: Cursor <cursoragent@cursor.com>
517 lines
No EOL
23 KiB
Text
517 lines
No EOL
23 KiB
Text
// Copyright 2024 The Chromium Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
// import * as i18n from '../../../core/i18n/i18n.js';
|
|
import * as Platform from '../../../core/platform/platform.js';
|
|
import * as Handlers from '../handlers/handlers.js';
|
|
import * as Helpers from '../helpers/helpers.js';
|
|
import * as Types from '../types/types.js';
|
|
import { InsightCategory, InsightKeys, } from './types.js';
|
|
export const UIStrings = {
|
|
/** Title of an insight that provides details about why elements shift/move on the page. The causes for these shifts are referred to as culprits ("reasons"). */
|
|
title: 'Layout shift culprits',
|
|
/**
|
|
* @description Description of a DevTools insight that identifies the reasons that elements shift on the page.
|
|
* This is displayed after a user expands the section to see more. No character length limits.
|
|
*/
|
|
description: 'Layout shifts occur when elements move absent any user interaction. [Investigate the causes of layout shifts](https://web.dev/articles/optimize-cls), such as elements being added, removed, or their fonts changing as the page loads.',
|
|
/**
|
|
* @description Text indicating the worst layout shift cluster.
|
|
*/
|
|
worstLayoutShiftCluster: 'Worst layout shift cluster',
|
|
/**
|
|
* @description Text indicating the worst layout shift cluster.
|
|
*/
|
|
worstCluster: 'Worst cluster',
|
|
/**
|
|
* @description Text indicating a layout shift cluster and its start time.
|
|
* @example {32 ms} PH1
|
|
*/
|
|
layoutShiftCluster: 'Layout shift cluster @ {PH1}',
|
|
/**
|
|
* @description Text indicating the biggest reasons for the layout shifts.
|
|
*/
|
|
topCulprits: 'Top layout shift culprits',
|
|
/**
|
|
* @description Text for a culprit type of Injected iframe.
|
|
*/
|
|
injectedIframe: 'Injected iframe',
|
|
/**
|
|
* @description Text for a culprit type of web font request.
|
|
*/
|
|
webFont: 'Web font',
|
|
/**
|
|
* @description Text for a culprit type of Animation.
|
|
*/
|
|
animation: 'Animation',
|
|
/**
|
|
* @description Text for a culprit type of Unsized image.
|
|
*/
|
|
unsizedImage: 'Unsized image element',
|
|
/**
|
|
* @description Text status when there were no layout shifts detected.
|
|
*/
|
|
noLayoutShifts: 'No layout shifts',
|
|
/**
|
|
* @description Text status when there no layout shifts culprits/root causes were found.
|
|
*/
|
|
noCulprits: 'Could not detect any layout shift culprits',
|
|
};
|
|
// const str_ = i18n.i18n.registerUIStrings('models/trace/insights/CLSCulprits.ts', UIStrings);
|
|
export const i18nString = (i18nId, values) => ({i18nId, values}); // i18n.i18n.getLocalizedString.bind(undefined, str_);
|
|
export var AnimationFailureReasons;
|
|
(function (AnimationFailureReasons) {
|
|
AnimationFailureReasons["ACCELERATED_ANIMATIONS_DISABLED"] = "ACCELERATED_ANIMATIONS_DISABLED";
|
|
AnimationFailureReasons["EFFECT_SUPPRESSED_BY_DEVTOOLS"] = "EFFECT_SUPPRESSED_BY_DEVTOOLS";
|
|
AnimationFailureReasons["INVALID_ANIMATION_OR_EFFECT"] = "INVALID_ANIMATION_OR_EFFECT";
|
|
AnimationFailureReasons["EFFECT_HAS_UNSUPPORTED_TIMING_PARAMS"] = "EFFECT_HAS_UNSUPPORTED_TIMING_PARAMS";
|
|
AnimationFailureReasons["EFFECT_HAS_NON_REPLACE_COMPOSITE_MODE"] = "EFFECT_HAS_NON_REPLACE_COMPOSITE_MODE";
|
|
AnimationFailureReasons["TARGET_HAS_INVALID_COMPOSITING_STATE"] = "TARGET_HAS_INVALID_COMPOSITING_STATE";
|
|
AnimationFailureReasons["TARGET_HAS_INCOMPATIBLE_ANIMATIONS"] = "TARGET_HAS_INCOMPATIBLE_ANIMATIONS";
|
|
AnimationFailureReasons["TARGET_HAS_CSS_OFFSET"] = "TARGET_HAS_CSS_OFFSET";
|
|
AnimationFailureReasons["ANIMATION_AFFECTS_NON_CSS_PROPERTIES"] = "ANIMATION_AFFECTS_NON_CSS_PROPERTIES";
|
|
AnimationFailureReasons["TRANSFORM_RELATED_PROPERTY_CANNOT_BE_ACCELERATED_ON_TARGET"] = "TRANSFORM_RELATED_PROPERTY_CANNOT_BE_ACCELERATED_ON_TARGET";
|
|
AnimationFailureReasons["TRANSFROM_BOX_SIZE_DEPENDENT"] = "TRANSFROM_BOX_SIZE_DEPENDENT";
|
|
AnimationFailureReasons["FILTER_RELATED_PROPERTY_MAY_MOVE_PIXELS"] = "FILTER_RELATED_PROPERTY_MAY_MOVE_PIXELS";
|
|
AnimationFailureReasons["UNSUPPORTED_CSS_PROPERTY"] = "UNSUPPORTED_CSS_PROPERTY";
|
|
AnimationFailureReasons["MIXED_KEYFRAME_VALUE_TYPES"] = "MIXED_KEYFRAME_VALUE_TYPES";
|
|
AnimationFailureReasons["TIMELINE_SOURCE_HAS_INVALID_COMPOSITING_STATE"] = "TIMELINE_SOURCE_HAS_INVALID_COMPOSITING_STATE";
|
|
AnimationFailureReasons["ANIMATION_HAS_NO_VISIBLE_CHANGE"] = "ANIMATION_HAS_NO_VISIBLE_CHANGE";
|
|
AnimationFailureReasons["AFFECTS_IMPORTANT_PROPERTY"] = "AFFECTS_IMPORTANT_PROPERTY";
|
|
AnimationFailureReasons["SVG_TARGET_HAS_INDEPENDENT_TRANSFORM_PROPERTY"] = "SVG_TARGET_HAS_INDEPENDENT_TRANSFORM_PROPERTY";
|
|
})(AnimationFailureReasons || (AnimationFailureReasons = {}));
|
|
export var LayoutShiftType;
|
|
(function (LayoutShiftType) {
|
|
LayoutShiftType[LayoutShiftType["WEB_FONT"] = 0] = "WEB_FONT";
|
|
LayoutShiftType[LayoutShiftType["IFRAMES"] = 1] = "IFRAMES";
|
|
LayoutShiftType[LayoutShiftType["ANIMATIONS"] = 2] = "ANIMATIONS";
|
|
LayoutShiftType[LayoutShiftType["UNSIZED_IMAGE"] = 3] = "UNSIZED_IMAGE";
|
|
})(LayoutShiftType || (LayoutShiftType = {}));
|
|
/**
|
|
* Each failure reason is represented by a bit flag. The bit shift operator '<<' is used to define
|
|
* which bit corresponds to each failure reason.
|
|
* https://source.chromium.org/search?q=f:compositor_animations.h%20%22enum%20FailureReason%22
|
|
*/
|
|
const ACTIONABLE_FAILURE_REASONS = [
|
|
{
|
|
flag: 1 << 0,
|
|
failure: AnimationFailureReasons.ACCELERATED_ANIMATIONS_DISABLED,
|
|
},
|
|
{
|
|
flag: 1 << 1,
|
|
failure: AnimationFailureReasons.EFFECT_SUPPRESSED_BY_DEVTOOLS,
|
|
},
|
|
{
|
|
flag: 1 << 2,
|
|
failure: AnimationFailureReasons.INVALID_ANIMATION_OR_EFFECT,
|
|
},
|
|
{
|
|
flag: 1 << 3,
|
|
failure: AnimationFailureReasons.EFFECT_HAS_UNSUPPORTED_TIMING_PARAMS,
|
|
},
|
|
{
|
|
flag: 1 << 4,
|
|
failure: AnimationFailureReasons.EFFECT_HAS_NON_REPLACE_COMPOSITE_MODE,
|
|
},
|
|
{
|
|
flag: 1 << 5,
|
|
failure: AnimationFailureReasons.TARGET_HAS_INVALID_COMPOSITING_STATE,
|
|
},
|
|
{
|
|
flag: 1 << 6,
|
|
failure: AnimationFailureReasons.TARGET_HAS_INCOMPATIBLE_ANIMATIONS,
|
|
},
|
|
{
|
|
flag: 1 << 7,
|
|
failure: AnimationFailureReasons.TARGET_HAS_CSS_OFFSET,
|
|
},
|
|
// The failure 1 << 8 is marked as obsolete in Blink
|
|
{
|
|
flag: 1 << 9,
|
|
failure: AnimationFailureReasons.ANIMATION_AFFECTS_NON_CSS_PROPERTIES,
|
|
},
|
|
{
|
|
flag: 1 << 10,
|
|
failure: AnimationFailureReasons.TRANSFORM_RELATED_PROPERTY_CANNOT_BE_ACCELERATED_ON_TARGET,
|
|
},
|
|
{
|
|
flag: 1 << 11,
|
|
failure: AnimationFailureReasons.TRANSFROM_BOX_SIZE_DEPENDENT,
|
|
},
|
|
{
|
|
flag: 1 << 12,
|
|
failure: AnimationFailureReasons.FILTER_RELATED_PROPERTY_MAY_MOVE_PIXELS,
|
|
},
|
|
{
|
|
flag: 1 << 13,
|
|
failure: AnimationFailureReasons.UNSUPPORTED_CSS_PROPERTY,
|
|
},
|
|
// The failure 1 << 14 is marked as obsolete in Blink
|
|
{
|
|
flag: 1 << 15,
|
|
failure: AnimationFailureReasons.MIXED_KEYFRAME_VALUE_TYPES,
|
|
},
|
|
{
|
|
flag: 1 << 16,
|
|
failure: AnimationFailureReasons.TIMELINE_SOURCE_HAS_INVALID_COMPOSITING_STATE,
|
|
},
|
|
{
|
|
flag: 1 << 17,
|
|
failure: AnimationFailureReasons.ANIMATION_HAS_NO_VISIBLE_CHANGE,
|
|
},
|
|
{
|
|
flag: 1 << 18,
|
|
failure: AnimationFailureReasons.AFFECTS_IMPORTANT_PROPERTY,
|
|
},
|
|
{
|
|
flag: 1 << 19,
|
|
failure: AnimationFailureReasons.SVG_TARGET_HAS_INDEPENDENT_TRANSFORM_PROPERTY,
|
|
},
|
|
];
|
|
// 500ms window.
|
|
// Use this window to consider events and requests that may have caused a layout shift.
|
|
const ROOT_CAUSE_WINDOW = Helpers.Timing.secondsToMicro(Types.Timing.Seconds(0.5));
|
|
/**
|
|
* Returns if an event happens within the root cause window, before the target event.
|
|
* ROOT_CAUSE_WINDOW v target event
|
|
* |------------------------|=======================
|
|
*/
|
|
function isInRootCauseWindow(event, targetEvent) {
|
|
const eventEnd = event.dur ? event.ts + event.dur : event.ts;
|
|
return eventEnd < targetEvent.ts && eventEnd >= targetEvent.ts - ROOT_CAUSE_WINDOW;
|
|
}
|
|
export function getNonCompositedFailure(animationEvent) {
|
|
const failures = [];
|
|
const beginEvent = animationEvent.args.data.beginEvent;
|
|
const instantEvents = animationEvent.args.data.instantEvents || [];
|
|
/**
|
|
* Animation events containing composite information are ASYNC_NESTABLE_INSTANT ('n').
|
|
* An animation may also contain multiple 'n' events, so we look through those with useful non-composited data.
|
|
*/
|
|
for (const event of instantEvents) {
|
|
const failureMask = event.args.data.compositeFailed;
|
|
const unsupportedProperties = event.args.data.unsupportedProperties;
|
|
if (!failureMask) {
|
|
continue;
|
|
}
|
|
const failureReasons = ACTIONABLE_FAILURE_REASONS.filter(reason => failureMask & reason.flag).map(reason => reason.failure);
|
|
const failure = {
|
|
name: beginEvent.args.data.displayName,
|
|
failureReasons,
|
|
unsupportedProperties,
|
|
animation: animationEvent,
|
|
};
|
|
failures.push(failure);
|
|
}
|
|
return failures;
|
|
}
|
|
function getNonCompositedFailureRootCauses(animationEvents, prePaintEvents, shiftsByPrePaint, rootCausesByShift) {
|
|
const allAnimationFailures = [];
|
|
for (const animation of animationEvents) {
|
|
/**
|
|
* Animation events containing composite information are ASYNC_NESTABLE_INSTANT ('n').
|
|
* An animation may also contain multiple 'n' events, so we look through those with useful non-composited data.
|
|
*/
|
|
const failures = getNonCompositedFailure(animation);
|
|
if (!failures) {
|
|
continue;
|
|
}
|
|
allAnimationFailures.push(...failures);
|
|
const nextPrePaint = getNextEvent(prePaintEvents, animation);
|
|
// If no following prePaint, this is not a root cause.
|
|
if (!nextPrePaint) {
|
|
continue;
|
|
}
|
|
// If the animation event is outside the ROOT_CAUSE_WINDOW, it could not be a root cause.
|
|
if (!isInRootCauseWindow(animation, nextPrePaint)) {
|
|
continue;
|
|
}
|
|
const shifts = shiftsByPrePaint.get(nextPrePaint);
|
|
// if no layout shift(s), this is not a root cause.
|
|
if (!shifts) {
|
|
continue;
|
|
}
|
|
for (const shift of shifts) {
|
|
const rootCausesForShift = rootCausesByShift.get(shift);
|
|
if (!rootCausesForShift) {
|
|
throw new Error('Unaccounted shift');
|
|
}
|
|
rootCausesForShift.nonCompositedAnimations.push(...failures);
|
|
}
|
|
}
|
|
return allAnimationFailures;
|
|
}
|
|
/**
|
|
* Given an array of layout shift and PrePaint events, returns a mapping from
|
|
* PrePaint events to layout shifts dispatched within it.
|
|
*/
|
|
function getShiftsByPrePaintEvents(layoutShifts, prePaintEvents) {
|
|
// Maps from PrePaint events to LayoutShifts that occurred in each one.
|
|
const shiftsByPrePaint = new Map();
|
|
// Associate all shifts to their corresponding PrePaint.
|
|
for (const prePaintEvent of prePaintEvents) {
|
|
const firstShiftIndex = Platform.ArrayUtilities.nearestIndexFromBeginning(layoutShifts, shift => shift.ts >= prePaintEvent.ts);
|
|
if (firstShiftIndex === null) {
|
|
// No layout shifts registered after this PrePaint start. Continue.
|
|
continue;
|
|
}
|
|
for (let i = firstShiftIndex; i < layoutShifts.length; i++) {
|
|
const shift = layoutShifts[i];
|
|
if (shift.ts >= prePaintEvent.ts && shift.ts <= prePaintEvent.ts + prePaintEvent.dur) {
|
|
const shiftsInPrePaint = Platform.MapUtilities.getWithDefault(shiftsByPrePaint, prePaintEvent, () => []);
|
|
shiftsInPrePaint.push(shift);
|
|
}
|
|
if (shift.ts > prePaintEvent.ts + prePaintEvent.dur) {
|
|
// Reached all layoutShifts of this PrePaint. Break out to continue with the next prePaint event.
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return shiftsByPrePaint;
|
|
}
|
|
/**
|
|
* Given a source event list, this returns the first event of that list that directly follows the target event.
|
|
*/
|
|
function getNextEvent(sourceEvents, targetEvent) {
|
|
const index = Platform.ArrayUtilities.nearestIndexFromBeginning(sourceEvents, source => source.ts > targetEvent.ts + (targetEvent.dur || 0));
|
|
// No PrePaint event registered after this event
|
|
if (index === null) {
|
|
return undefined;
|
|
}
|
|
return sourceEvents[index];
|
|
}
|
|
/**
|
|
* An Iframe is considered a root cause if the iframe event occurs before a prePaint event
|
|
* and within this prePaint event a layout shift(s) occurs.
|
|
*/
|
|
function getIframeRootCauses(parsedTrace, iframeCreatedEvents, prePaintEvents, shiftsByPrePaint, rootCausesByShift, domLoadingEvents) {
|
|
for (const iframeEvent of iframeCreatedEvents) {
|
|
const nextPrePaint = getNextEvent(prePaintEvents, iframeEvent);
|
|
// If no following prePaint, this is not a root cause.
|
|
if (!nextPrePaint) {
|
|
continue;
|
|
}
|
|
const shifts = shiftsByPrePaint.get(nextPrePaint);
|
|
// if no layout shift(s), this is not a root cause.
|
|
if (!shifts) {
|
|
continue;
|
|
}
|
|
for (const shift of shifts) {
|
|
const rootCausesForShift = rootCausesByShift.get(shift);
|
|
if (!rootCausesForShift) {
|
|
throw new Error('Unaccounted shift');
|
|
}
|
|
// Look for the first dom event that occurs within the bounds of the iframe event.
|
|
// This contains the frame id.
|
|
const domEvent = domLoadingEvents.find(e => {
|
|
const maxIframe = Types.Timing.Micro(iframeEvent.ts + (iframeEvent.dur ?? 0));
|
|
return e.ts >= iframeEvent.ts && e.ts <= maxIframe;
|
|
});
|
|
if (domEvent?.args.frame) {
|
|
const frame = domEvent.args.frame;
|
|
let url;
|
|
const processes = parsedTrace.Meta.rendererProcessesByFrame.get(frame);
|
|
if (processes && processes.size > 0) {
|
|
url = [...processes.values()][0]?.[0].frame.url;
|
|
}
|
|
rootCausesForShift.iframes.push({ frame, url });
|
|
}
|
|
}
|
|
}
|
|
return rootCausesByShift;
|
|
}
|
|
/**
|
|
* An unsized image is considered a root cause if its PaintImage can be correlated to a
|
|
* layout shift. We can correlate PaintImages with unsized images by their matching nodeIds.
|
|
* X <- layout shift
|
|
* |----------------|
|
|
* ^ PrePaint event |-----|
|
|
* ^ PaintImage
|
|
*/
|
|
function getUnsizedImageRootCauses(unsizedImageEvents, paintImageEvents, shiftsByPrePaint, rootCausesByShift) {
|
|
shiftsByPrePaint.forEach((shifts, prePaint) => {
|
|
const paintImage = getNextEvent(paintImageEvents, prePaint);
|
|
if (!paintImage) {
|
|
return;
|
|
}
|
|
// The unsized image corresponds to this PaintImage.
|
|
const matchingNode = unsizedImageEvents.find(unsizedImage => unsizedImage.args.data.nodeId === paintImage.args.data.nodeId);
|
|
if (!matchingNode) {
|
|
return;
|
|
}
|
|
// The unsized image is a potential root cause of all the shifts of this prePaint.
|
|
for (const shift of shifts) {
|
|
const rootCausesForShift = rootCausesByShift.get(shift);
|
|
if (!rootCausesForShift) {
|
|
throw new Error('Unaccounted shift');
|
|
}
|
|
rootCausesForShift.unsizedImages.push({
|
|
backendNodeId: matchingNode.args.data.nodeId,
|
|
paintImageEvent: paintImage,
|
|
});
|
|
}
|
|
});
|
|
return rootCausesByShift;
|
|
}
|
|
export function isCLSCulprits(insight) {
|
|
return insight.insightKey === InsightKeys.CLS_CULPRITS;
|
|
}
|
|
/**
|
|
* A font request is considered a root cause if the request occurs before a prePaint event
|
|
* and within this prePaint event a layout shift(s) occurs. Additionally, this font request should
|
|
* happen within the ROOT_CAUSE_WINDOW of the prePaint event.
|
|
*/
|
|
function getFontRootCauses(networkRequests, prePaintEvents, shiftsByPrePaint, rootCausesByShift) {
|
|
const fontRequests = networkRequests.filter(req => req.args.data.resourceType === 'Font' && req.args.data.mimeType.startsWith('font'));
|
|
for (const req of fontRequests) {
|
|
const nextPrePaint = getNextEvent(prePaintEvents, req);
|
|
if (!nextPrePaint) {
|
|
continue;
|
|
}
|
|
// If the req is outside the ROOT_CAUSE_WINDOW, it could not be a root cause.
|
|
if (!isInRootCauseWindow(req, nextPrePaint)) {
|
|
continue;
|
|
}
|
|
// Get the shifts that belong to this prepaint
|
|
const shifts = shiftsByPrePaint.get(nextPrePaint);
|
|
// if no layout shift(s) in this prePaint, the request is not a root cause.
|
|
if (!shifts) {
|
|
continue;
|
|
}
|
|
// Include the root cause to the shifts in this prePaint.
|
|
for (const shift of shifts) {
|
|
const rootCausesForShift = rootCausesByShift.get(shift);
|
|
if (!rootCausesForShift) {
|
|
throw new Error('Unaccounted shift');
|
|
}
|
|
rootCausesForShift.webFonts.push(req);
|
|
}
|
|
}
|
|
return rootCausesByShift;
|
|
}
|
|
/**
|
|
* Returns the top 3 shift root causes based on the given cluster.
|
|
*/
|
|
function getTopCulprits(cluster, culpritsByShift) {
|
|
const MAX_TOP_CULPRITS = 3;
|
|
const causes = [];
|
|
const shifts = cluster.events;
|
|
for (const shift of shifts) {
|
|
const culprits = culpritsByShift.get(shift);
|
|
if (!culprits) {
|
|
continue;
|
|
}
|
|
const fontReq = culprits.webFonts;
|
|
const iframes = culprits.iframes;
|
|
const animations = culprits.nonCompositedAnimations;
|
|
const unsizedImages = culprits.unsizedImages;
|
|
for (let i = 0; i < fontReq.length && causes.length < MAX_TOP_CULPRITS; i++) {
|
|
causes.push({ type: LayoutShiftType.WEB_FONT, description: i18nString(UIStrings.webFont) });
|
|
}
|
|
for (let i = 0; i < iframes.length && causes.length < MAX_TOP_CULPRITS; i++) {
|
|
causes.push({ type: LayoutShiftType.IFRAMES, description: i18nString(UIStrings.injectedIframe) });
|
|
}
|
|
for (let i = 0; i < animations.length && causes.length < MAX_TOP_CULPRITS; i++) {
|
|
causes.push({ type: LayoutShiftType.ANIMATIONS, description: i18nString(UIStrings.animation) });
|
|
}
|
|
for (let i = 0; i < unsizedImages.length && causes.length < MAX_TOP_CULPRITS; i++) {
|
|
causes.push({
|
|
type: LayoutShiftType.UNSIZED_IMAGE,
|
|
description: i18nString(UIStrings.unsizedImage),
|
|
url: unsizedImages[i].paintImageEvent.args.data.url || '',
|
|
backendNodeId: unsizedImages[i].backendNodeId,
|
|
frame: unsizedImages[i].paintImageEvent.args.data.frame || '',
|
|
});
|
|
}
|
|
if (causes.length >= MAX_TOP_CULPRITS) {
|
|
break;
|
|
}
|
|
}
|
|
return causes.slice(0, MAX_TOP_CULPRITS);
|
|
}
|
|
function finalize(partialModel) {
|
|
let state = 'pass';
|
|
if (partialModel.worstCluster) {
|
|
const classification = Handlers.ModelHandlers.LayoutShifts.scoreClassificationForLayoutShift(partialModel.worstCluster.clusterCumulativeScore);
|
|
if (classification === Handlers.ModelHandlers.PageLoadMetrics.ScoreClassification.GOOD) {
|
|
state = 'informative';
|
|
}
|
|
else {
|
|
state = 'fail';
|
|
}
|
|
}
|
|
return {
|
|
insightKey: InsightKeys.CLS_CULPRITS,
|
|
strings: UIStrings,
|
|
title: i18nString(UIStrings.title),
|
|
description: i18nString(UIStrings.description),
|
|
category: InsightCategory.CLS,
|
|
state,
|
|
...partialModel,
|
|
};
|
|
}
|
|
export function generateInsight(parsedTrace, context) {
|
|
const isWithinContext = (event) => Helpers.Timing.eventIsInBounds(event, context.bounds);
|
|
const compositeAnimationEvents = parsedTrace.Animations.animations.filter(isWithinContext);
|
|
const iframeEvents = parsedTrace.LayoutShifts.renderFrameImplCreateChildFrameEvents.filter(isWithinContext);
|
|
const networkRequests = parsedTrace.NetworkRequests.byTime.filter(isWithinContext);
|
|
const domLoadingEvents = parsedTrace.LayoutShifts.domLoadingEvents.filter(isWithinContext);
|
|
const unsizedImageEvents = parsedTrace.LayoutShifts.layoutImageUnsizedEvents.filter(isWithinContext);
|
|
const clusterKey = context.navigation ? context.navigationId : Types.Events.NO_NAVIGATION;
|
|
const clusters = parsedTrace.LayoutShifts.clustersByNavigationId.get(clusterKey) ?? [];
|
|
const clustersByScore = [...clusters].sort((a, b) => b.clusterCumulativeScore - a.clusterCumulativeScore);
|
|
const worstCluster = clustersByScore.at(0);
|
|
const layoutShifts = clusters.flatMap(cluster => cluster.events);
|
|
const prePaintEvents = parsedTrace.LayoutShifts.prePaintEvents.filter(isWithinContext);
|
|
const paintImageEvents = parsedTrace.LayoutShifts.paintImageEvents.filter(isWithinContext);
|
|
// Get root causes.
|
|
const rootCausesByShift = new Map();
|
|
const shiftsByPrePaint = getShiftsByPrePaintEvents(layoutShifts, prePaintEvents);
|
|
for (const shift of layoutShifts) {
|
|
rootCausesByShift.set(shift, { iframes: [], webFonts: [], nonCompositedAnimations: [], unsizedImages: [] });
|
|
}
|
|
// Populate root causes for rootCausesByShift.
|
|
getIframeRootCauses(parsedTrace, iframeEvents, prePaintEvents, shiftsByPrePaint, rootCausesByShift, domLoadingEvents);
|
|
getFontRootCauses(networkRequests, prePaintEvents, shiftsByPrePaint, rootCausesByShift);
|
|
getUnsizedImageRootCauses(unsizedImageEvents, paintImageEvents, shiftsByPrePaint, rootCausesByShift);
|
|
const animationFailures = getNonCompositedFailureRootCauses(compositeAnimationEvents, prePaintEvents, shiftsByPrePaint, rootCausesByShift);
|
|
const relatedEvents = [...layoutShifts];
|
|
if (worstCluster) {
|
|
relatedEvents.push(worstCluster);
|
|
}
|
|
const topCulpritsByCluster = new Map();
|
|
for (const cluster of clusters) {
|
|
topCulpritsByCluster.set(cluster, getTopCulprits(cluster, rootCausesByShift));
|
|
}
|
|
return finalize({
|
|
relatedEvents,
|
|
animationFailures,
|
|
shifts: rootCausesByShift,
|
|
clusters,
|
|
worstCluster,
|
|
topCulpritsByCluster,
|
|
});
|
|
}
|
|
export function createOverlays(model) {
|
|
const clustersByScore = model.clusters.toSorted((a, b) => b.clusterCumulativeScore - a.clusterCumulativeScore) ?? [];
|
|
const worstCluster = clustersByScore[0];
|
|
if (!worstCluster) {
|
|
return [];
|
|
}
|
|
const range = Types.Timing.Micro(worstCluster.dur ?? 0);
|
|
const max = Types.Timing.Micro(worstCluster.ts + range);
|
|
return [{
|
|
type: 'TIMESPAN_BREAKDOWN',
|
|
sections: [
|
|
{
|
|
bounds: { min: worstCluster.ts, range, max },
|
|
label: i18nString(UIStrings.worstLayoutShiftCluster),
|
|
showDuration: false,
|
|
},
|
|
],
|
|
// This allows for the overlay to sit over the layout shift.
|
|
entry: worstCluster.events[0],
|
|
renderLocation: 'ABOVE_EVENT',
|
|
}];
|
|
}
|
|
//# sourceMappingURL=CLSCulprits.js.map |