web-vitals
Advanced tools
| import { ReportOpts } from '../types.js'; | ||
| export declare const checkSoftNavsEnabled: (opts?: ReportOpts) => boolean | undefined; |
| /* | ||
| * Copyright 2023 Google LLC | ||
| * | ||
| * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| * you may not use this file except in compliance with the License. | ||
| * You may obtain a copy of the License at | ||
| * | ||
| * https://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| */ | ||
| export const checkSoftNavsEnabled = (opts) => { | ||
| return (PerformanceObserver.supportedEntryTypes.includes('soft-navigation') && | ||
| opts && | ||
| opts.reportSoftNavs); | ||
| }; |
| /* | ||
| * Copyright 2023 Google LLC | ||
| * | ||
| * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| * you may not use this file except in compliance with the License. | ||
| * You may obtain a copy of the License at | ||
| * | ||
| * https://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| */ | ||
| import {ReportOpts} from '../types.js'; | ||
| export const checkSoftNavsEnabled = (opts?: ReportOpts) => { | ||
| return ( | ||
| PerformanceObserver.supportedEntryTypes.includes('soft-navigation') && | ||
| opts && | ||
| opts.reportSoftNavs | ||
| ); | ||
| }; |
@@ -21,2 +21,3 @@ /* | ||
| const attributeFCP = (metric) => { | ||
| const hardNavId = getNavigationEntry()?.navigationId || 0; | ||
| // Use a default object if no other attribution has been set. | ||
@@ -29,7 +30,15 @@ let attribution = { | ||
| if (metric.entries.length) { | ||
| const navigationEntry = getNavigationEntry(); | ||
| let navigationEntry; | ||
| const fcpEntry = metric.entries.at(-1); | ||
| let ttfb = 0; | ||
| // For hard navs use an actual TTFB | ||
| if (!metric.navigationId || metric.navigationId === hardNavId) { | ||
| navigationEntry = getNavigationEntry(); | ||
| if (navigationEntry) { | ||
| const responseStart = navigationEntry.responseStart; | ||
| const activationStart = navigationEntry.activationStart || 0; | ||
| ttfb = Math.max(0, responseStart - activationStart); | ||
| } | ||
| } | ||
| if (navigationEntry) { | ||
| const activationStart = navigationEntry.activationStart || 0; | ||
| const ttfb = Math.max(0, navigationEntry.responseStart - activationStart); | ||
| attribution = { | ||
@@ -36,0 +45,0 @@ timeToFirstByte: ttfb, |
@@ -98,3 +98,4 @@ /* | ||
| if (!interactionTargetMap.get(interaction)) { | ||
| const node = interaction.entries[0].target; | ||
| // Use find to get first selector | ||
| const node = interaction.entries.find((e) => e.target)?.target; | ||
| if (node) { | ||
@@ -104,2 +105,9 @@ const customTarget = opts.generateTarget?.(node) ?? getSelector(node); | ||
| } | ||
| else { | ||
| // Fall back to targetSelector | ||
| const selector = interaction.entries.find((e) => e.targetSelector)?.targetSelector; | ||
| if (selector) { | ||
| interactionTargetMap.set(interaction, selector); | ||
| } | ||
| } | ||
| } | ||
@@ -301,2 +309,33 @@ }; | ||
| const attributeINP = (metric) => { | ||
| // Soft navs can have a dummy INP as no first-input entry to fall back on | ||
| // See https://github.com/GoogleChrome/web-vitals/issues/724 | ||
| if (metric.entries.length === 0 && | ||
| metric.navigationType === 'soft-navigation') { | ||
| const softNavEntryStartTime = metric.navigationStartTime; | ||
| if (softNavEntryStartTime) { | ||
| // For simplicity make some assumptions for values we can't get | ||
| // to avoid undefined/null values which would be unexpected. | ||
| // - Assume interactionType as pointer as the most common | ||
| // - Assume interactionTime of soft nav start time | ||
| // - Assume nextPaintTime as interactionTime + length | ||
| const attribution = { | ||
| interactionTarget: '', | ||
| interactionType: 'pointer', | ||
| interactionTime: softNavEntryStartTime, | ||
| nextPaintTime: softNavEntryStartTime + metric.value, | ||
| processedEventEntries: [], | ||
| longAnimationFrameEntries: [], | ||
| inputDelay: 0, | ||
| processingDuration: 0, | ||
| presentationDelay: metric.value, | ||
| loadState: getLoadState(softNavEntryStartTime), | ||
| longestScript: undefined, | ||
| totalScriptDuration: undefined, | ||
| totalStyleAndLayoutDuration: undefined, | ||
| totalPaintDuration: undefined, | ||
| totalUnattributedDuration: undefined, | ||
| }; | ||
| return Object.assign(metric, { attribution }); | ||
| } | ||
| } | ||
| const firstEntry = metric.entries[0]; | ||
@@ -348,3 +387,3 @@ const group = entryToEntriesGroupMap.get(firstEntry); | ||
| // Start observing LoAF entries for attribution. | ||
| observe('long-animation-frame', handleLoAFEntries); | ||
| observe('long-animation-frame', handleLoAFEntries, opts); | ||
| unattributedOnINP((metric) => { | ||
@@ -351,0 +390,0 @@ onReport(attributeINP(metric)); |
@@ -53,2 +53,3 @@ /* | ||
| const attributeLCP = (metric) => { | ||
| const hardNavId = getNavigationEntry()?.navigationId || 0; | ||
| // Use a default object if no other attribution has been set. | ||
@@ -79,6 +80,22 @@ let attribution = { | ||
| // Safari seems to fail to find a navigation entry. | ||
| const navigationEntry = getNavigationEntry(); | ||
| let navigationEntry; | ||
| let activationStart = 0; | ||
| let responseStart = 0; | ||
| if (!metric.navigationId || metric.navigationId === hardNavId) { | ||
| navigationEntry = getNavigationEntry(); | ||
| activationStart = | ||
| navigationEntry && navigationEntry.activationStart | ||
| ? navigationEntry.activationStart | ||
| : 0; | ||
| responseStart = | ||
| navigationEntry && navigationEntry.responseStart | ||
| ? navigationEntry.responseStart | ||
| : 0; | ||
| } | ||
| else { | ||
| // Set activationStart to the SoftNav start time | ||
| activationStart = metric.navigationStartTime || 0; | ||
| } | ||
| if (navigationEntry) { | ||
| const activationStart = navigationEntry.activationStart || 0; | ||
| const ttfb = Math.max(0, navigationEntry.responseStart - activationStart); | ||
| const ttfb = Math.max(0, responseStart - activationStart); | ||
| const lcpRequestStart = Math.max(ttfb, | ||
@@ -92,5 +109,5 @@ // Prefer `requestStart` (if TOA is set), otherwise use `startTime`. | ||
| // Cap at LCP time (videos continue downloading after LCP for example) | ||
| metric.value, Math.max(lcpRequestStart, lcpResourceEntry | ||
| metric.value, Math.max(lcpRequestStart - activationStart, lcpResourceEntry | ||
| ? lcpResourceEntry.responseEnd - activationStart | ||
| : 0)); | ||
| : 0, 0)); | ||
| attribution = { | ||
@@ -97,0 +114,0 @@ ...attribution, |
@@ -27,2 +27,4 @@ /* | ||
| if (metric.entries.length) { | ||
| // Is there a better way to check if this is a soft nav entry or not? | ||
| // Refuses to build without this as soft navs don't have activationStart | ||
| const navigationEntry = metric.entries[0]; | ||
@@ -33,7 +35,7 @@ const activationStart = navigationEntry.activationStart || 0; | ||
| // anyway, that cannot be accurately split out cross-browser). | ||
| const waitEnd = Math.max((navigationEntry.workerStart || navigationEntry.fetchStart) - | ||
| const waitEnd = Math.max((navigationEntry.workerStart || navigationEntry.fetchStart || 0) - | ||
| activationStart, 0); | ||
| const dnsStart = Math.max(navigationEntry.domainLookupStart - activationStart, 0); | ||
| const connectStart = Math.max(navigationEntry.connectStart - activationStart, 0); | ||
| const connectEnd = Math.max(navigationEntry.connectEnd - activationStart, 0); | ||
| const dnsStart = Math.max(navigationEntry.domainLookupStart - activationStart || 0, 0); | ||
| const connectStart = Math.max(navigationEntry.connectStart - activationStart || 0, 0); | ||
| const connectEnd = Math.max(navigationEntry.connectEnd - activationStart || 0, 0); | ||
| attribution = { | ||
@@ -40,0 +42,0 @@ waitingDuration: waitEnd, |
@@ -23,9 +23,9 @@ /* | ||
| } | ||
| const navigationEntry = getNavigationEntry(); | ||
| if (navigationEntry) { | ||
| if (timestamp < navigationEntry.domInteractive) { | ||
| const hardNavEntry = getNavigationEntry(); | ||
| if (hardNavEntry) { | ||
| if (timestamp < hardNavEntry.domInteractive) { | ||
| return 'loading'; | ||
| } | ||
| if (navigationEntry.domContentLoadedEventStart === 0 || | ||
| timestamp < navigationEntry.domContentLoadedEventStart) { | ||
| else if (hardNavEntry.domContentLoadedEventStart === 0 || | ||
| timestamp < hardNavEntry.domContentLoadedEventStart) { | ||
| // If the `domContentLoadedEventStart` timestamp has not yet been | ||
@@ -35,4 +35,4 @@ // set, or if the given timestamp is less than that value. | ||
| } | ||
| if (navigationEntry.domComplete === 0 || | ||
| timestamp < navigationEntry.domComplete) { | ||
| else if (hardNavEntry.domComplete === 0 || | ||
| timestamp < hardNavEntry.domComplete) { | ||
| // If the `domComplete` timestamp has not yet been | ||
@@ -39,0 +39,0 @@ // set, or if the given timestamp is less than that value. |
@@ -1,4 +0,4 @@ | ||
| export declare const getVisibilityWatcher: () => { | ||
| export declare const getVisibilityWatcher: (reset?: boolean) => { | ||
| readonly firstHiddenTime: number; | ||
| onHidden(cb: () => void): void; | ||
| }; |
@@ -56,3 +56,6 @@ /* | ||
| }; | ||
| export const getVisibilityWatcher = () => { | ||
| export const getVisibilityWatcher = (reset = false) => { | ||
| if (reset) { | ||
| firstHiddenTime = Infinity; | ||
| } | ||
| if (firstHiddenTime < 0) { | ||
@@ -59,0 +62,0 @@ // Check if we have a previous hidden `visibility-state` performance entry. |
| import { MetricType } from '../types.js'; | ||
| export declare const initMetric: <MetricName extends MetricType["name"]>(name: MetricName, value?: number) => { | ||
| export declare const initMetric: <MetricName extends MetricType["name"]>(name: MetricName, value?: number, interactionId?: number, navigation?: MetricType["navigationType"], navigationId?: number, navigationURL?: string, navigationStartTime?: number) => { | ||
| name: MetricName; | ||
@@ -19,3 +19,7 @@ value: number; | ||
| id: string; | ||
| navigationType: "navigate" | "reload" | "back-forward" | "back-forward-cache" | "prerender" | "restore"; | ||
| navigationType: "navigate" | "reload" | "back-forward" | "back-forward-cache" | "prerender" | "restore" | "soft-navigation"; | ||
| navigationId: number; | ||
| interactionId: number | undefined; | ||
| navigationURL: string | undefined; | ||
| navigationStartTime: number; | ||
| }; |
@@ -20,9 +20,14 @@ /* | ||
| import { getNavigationEntry } from './getNavigationEntry.js'; | ||
| export const initMetric = (name, value = -1) => { | ||
| const navEntry = getNavigationEntry(); | ||
| export const initMetric = (name, value = -1, interactionId, navigation, navigationId = 0, navigationURL, navigationStartTime) => { | ||
| const hardNavId = getNavigationEntry()?.navigationId || 0; | ||
| const hardNavEntry = getNavigationEntry(); | ||
| let navigationType = 'navigate'; | ||
| if (getBFCacheRestoreTime() >= 0) { | ||
| if (navigation) { | ||
| // If it was passed in, then use that | ||
| navigationType = navigation; | ||
| } | ||
| else if (getBFCacheRestoreTime() >= 0) { | ||
| navigationType = 'back-forward-cache'; | ||
| } | ||
| else if (navEntry) { | ||
| else if (hardNavEntry) { | ||
| if (document.prerendering || getActivationStart() > 0) { | ||
@@ -34,4 +39,4 @@ navigationType = 'prerender'; | ||
| } | ||
| else if (navEntry.type) { | ||
| navigationType = navEntry.type.replace(/_/g, '-'); | ||
| else if (hardNavEntry.type) { | ||
| navigationType = hardNavEntry.type.replace(/_/g, '-'); | ||
| } | ||
@@ -49,3 +54,7 @@ } | ||
| navigationType, | ||
| navigationId: navigationId || hardNavId, | ||
| interactionId: interactionId, | ||
| navigationURL: navigationURL || hardNavEntry?.name, | ||
| navigationStartTime: navigationStartTime || 0, | ||
| }; | ||
| }; |
@@ -23,6 +23,11 @@ /* | ||
| export function initUnique(identityObj, ClassObj) { | ||
| if (!instanceMap.get(identityObj)) { | ||
| instanceMap.set(identityObj, new ClassObj()); | ||
| let classInstances = instanceMap.get(ClassObj); | ||
| if (!classInstances) { | ||
| classInstances = new WeakMap(); | ||
| instanceMap.set(ClassObj, classInstances); | ||
| } | ||
| return instanceMap.get(identityObj); | ||
| if (!classInstances.get(identityObj)) { | ||
| classInstances.set(identityObj, new ClassObj()); | ||
| } | ||
| return classInstances.get(identityObj); | ||
| } |
@@ -0,1 +1,2 @@ | ||
| import { Metric } from '../types.js'; | ||
| export interface Interaction { | ||
@@ -25,3 +26,3 @@ _latency: number; | ||
| */ | ||
| _estimateP98LongestInteraction(): Interaction; | ||
| _estimateP98LongestInteraction(navigationType: Metric['navigationType']): Interaction; | ||
| /** | ||
@@ -28,0 +29,0 @@ * Takes a performance entry and adds it to the list of worst interactions |
@@ -53,4 +53,17 @@ /* | ||
| */ | ||
| _estimateP98LongestInteraction() { | ||
| const candidateInteractionIndex = Math.min(this._longestInteractionList.length - 1, Math.floor(getInteractionCountForNavigation() / 50)); | ||
| _estimateP98LongestInteraction(navigationType) { | ||
| const interactionCountForNavigation = getInteractionCountForNavigation(); | ||
| const candidateInteractionIndex = Math.min(this._longestInteractionList.length - 1, Math.floor(interactionCountForNavigation / 50)); | ||
| // If we have a non-zero interactionCountForNavigation but no | ||
| // candidateInteractionIndex, then it's below the 16ms limit | ||
| // so report a dummy 8ms interaction. | ||
| if (interactionCountForNavigation && | ||
| candidateInteractionIndex === -1 && | ||
| navigationType === 'soft-navigation') { | ||
| return { | ||
| _latency: 8, | ||
| id: -1, | ||
| entries: [], | ||
| }; | ||
| } | ||
| return this._longestInteractionList[candidateInteractionIndex]; | ||
@@ -57,0 +70,0 @@ } |
| interface PerformanceEntryMap { | ||
| 'event': PerformanceEventTiming[]; | ||
| 'first-input': PerformanceEventTiming[]; | ||
| 'interaction-contentful-paint': InteractionContentfulPaint[]; | ||
| 'layout-shift': LayoutShift[]; | ||
@@ -10,2 +11,3 @@ 'largest-contentful-paint': LargestContentfulPaint[]; | ||
| 'resource': PerformanceResourceTiming[]; | ||
| 'soft-navigation': SoftNavigationEntry[]; | ||
| } | ||
@@ -12,0 +14,0 @@ /** |
@@ -16,2 +16,3 @@ /* | ||
| */ | ||
| import { checkSoftNavsEnabled } from './softNavs.js'; | ||
| /** | ||
@@ -26,2 +27,20 @@ * Takes a performance entry type and a callback function, and creates a | ||
| export const observe = (type, callback, opts = {}) => { | ||
| // Observe extra types if using SoftNavs | ||
| const typesToMonitor = [type]; | ||
| const reportSoftNavs = checkSoftNavsEnabled(opts); | ||
| if (type === 'event') { | ||
| // Also observe entries of type `first-input`. This is useful in cases | ||
| // where the first interaction is less than the `durationThreshold`. | ||
| typesToMonitor.push('first-input'); | ||
| } | ||
| if (reportSoftNavs) { | ||
| if (type === 'largest-contentful-paint' || | ||
| type === 'event' || | ||
| type === 'layout-shift') { | ||
| typesToMonitor.push('soft-navigation'); | ||
| } | ||
| if (type === 'largest-contentful-paint') { | ||
| typesToMonitor.push('interaction-contentful-paint'); | ||
| } | ||
| } | ||
| try { | ||
@@ -34,6 +53,21 @@ if (PerformanceObserver.supportedEntryTypes.includes(type)) { | ||
| queueMicrotask(() => { | ||
| callback(list.getEntries()); | ||
| // sort entries to ensure they're in the right order | ||
| const entries = list.getEntries(); | ||
| // Best to sort by end time (startTime + duration) | ||
| // See: https://github.com/w3c/performance-timeline/issues/224 | ||
| entries.sort((a, b) => { | ||
| const scoreA = a.startTime + a.duration; | ||
| const scoreB = b.startTime + b.duration; | ||
| return scoreA - scoreB; | ||
| }); | ||
| callback(entries); | ||
| }); | ||
| }); | ||
| po.observe({ type, buffered: true, ...opts }); | ||
| for (const t of typesToMonitor) { | ||
| po.observe(Object.assign({ | ||
| type: t, | ||
| buffered: true, | ||
| ...opts, | ||
| }, opts || {})); | ||
| } | ||
| return po; | ||
@@ -40,0 +74,0 @@ } |
@@ -19,2 +19,3 @@ /* | ||
| import { doubleRAF } from './lib/doubleRAF.js'; | ||
| import { getVisibilityWatcher } from './lib/getVisibilityWatcher.js'; | ||
| import { initMetric } from './lib/initMetric.js'; | ||
@@ -26,3 +27,2 @@ import { initUnique } from './lib/initUnique.js'; | ||
| import { onFCP } from './onFCP.js'; | ||
| import { getVisibilityWatcher } from './lib/getVisibilityWatcher.js'; | ||
| /** Thresholds for CLS. See https://web.dev/articles/cls#what_is_a_good_cls_score */ | ||
@@ -59,4 +59,18 @@ export const CLSThresholds = [0.1, 0.25]; | ||
| const layoutShiftManager = initUnique(opts, LayoutShiftManager); | ||
| const initNewCLSMetric = (interactionId, navigationType, navigationId, navigationURL, navigationStartTime) => { | ||
| metric = initMetric('CLS', 0, interactionId, navigationType, navigationId, navigationURL, navigationStartTime); | ||
| layoutShiftManager._sessionValue = 0; | ||
| report = bindReporter(onReport, metric, CLSThresholds, opts.reportAllChanges); | ||
| }; | ||
| const handleSoftNavEntry = (entry) => { | ||
| handleEntries(po?.takeRecords()); | ||
| report(true); | ||
| initNewCLSMetric(entry.interactionId, 'soft-navigation', entry.navigationId, entry.name, entry.startTime); | ||
| }; | ||
| const handleEntries = (entries) => { | ||
| for (const entry of entries) { | ||
| if ('largestInteractionContentfulPaint' in entry) { | ||
| handleSoftNavEntry(entry); | ||
| continue; | ||
| } | ||
| layoutShiftManager._processEntry(entry); | ||
@@ -72,3 +86,3 @@ } | ||
| }; | ||
| const po = observe('layout-shift', handleEntries); | ||
| const po = observe('layout-shift', handleEntries, opts); | ||
| if (po) { | ||
@@ -83,5 +97,3 @@ report = bindReporter(onReport, metric, CLSThresholds, opts.reportAllChanges); | ||
| onBFCacheRestore(() => { | ||
| layoutShiftManager._sessionValue = 0; | ||
| metric = initMetric('CLS', 0); | ||
| report = bindReporter(onReport, metric, CLSThresholds, opts.reportAllChanges); | ||
| initNewCLSMetric(metric.interactionId, 'back-forward-cache', metric.navigationId, metric.navigationURL, metric.navigationStartTime); | ||
| doubleRAF(report); | ||
@@ -88,0 +100,0 @@ }); |
@@ -16,4 +16,4 @@ /* | ||
| */ | ||
| import { onBFCacheRestore } from './lib/bfcache.js'; | ||
| import { bindReporter } from './lib/bindReporter.js'; | ||
| import { checkSoftNavsEnabled } from './lib/softNavs.js'; | ||
| import { doubleRAF } from './lib/doubleRAF.js'; | ||
@@ -24,2 +24,3 @@ import { getActivationStart } from './lib/getActivationStart.js'; | ||
| import { observe } from './lib/observe.js'; | ||
| import { onBFCacheRestore } from './lib/bfcache.js'; | ||
| import { whenActivated } from './lib/whenActivated.js'; | ||
@@ -35,2 +36,5 @@ /** Thresholds for FCP. See https://web.dev/articles/fcp#what_is_a_good_fcp_score */ | ||
| export const onFCP = (onReport, opts = {}) => { | ||
| // Set defaults | ||
| opts = opts || {}; | ||
| const softNavsEnabled = checkSoftNavsEnabled(opts); | ||
| whenActivated(() => { | ||
@@ -44,3 +48,3 @@ const visibilityWatcher = getVisibilityWatcher(); | ||
| po.disconnect(); | ||
| // Only report if the page wasn't hidden prior to the first paint. | ||
| // Only report if the page wasn't hidden prior to FCP. | ||
| if (entry.startTime < visibilityWatcher.firstHiddenTime) { | ||
@@ -53,2 +57,4 @@ // The activationStart reference is used because FCP should be | ||
| metric.entries.push(entry); | ||
| metric.navigationId = entry.navigationId || 0; | ||
| // FCP should only be reported once so can report right away | ||
| report(true); | ||
@@ -59,3 +65,3 @@ } | ||
| }; | ||
| const po = observe('paint', handleEntries); | ||
| const po = observe('paint', handleEntries, opts); | ||
| if (po) { | ||
@@ -66,3 +72,3 @@ report = bindReporter(onReport, metric, FCPThresholds, opts.reportAllChanges); | ||
| onBFCacheRestore((event) => { | ||
| metric = initMetric('FCP'); | ||
| metric = initMetric('FCP', 0, metric.interactionId, 'back-forward-cache', metric.navigationId, metric.navigationURL, metric.navigationStartTime); | ||
| report = bindReporter(onReport, metric, FCPThresholds, opts.reportAllChanges); | ||
@@ -75,3 +81,18 @@ doubleRAF(() => { | ||
| } | ||
| if (softNavsEnabled) { | ||
| // As first-contentful-paint is only reported once, we can handle soft | ||
| // navigations afterwards on their own for simplicity, as no need to | ||
| // observe both and sort the entries like for the other metrics | ||
| const handleSoftNavEntries = (entries) => { | ||
| entries.forEach((entry) => { | ||
| handleEntries(po.takeRecords()); | ||
| const FCPTime = (entry.presentationTime || entry.paintTime || 0) - entry.startTime; | ||
| metric = initMetric('FCP', FCPTime, entry.interactionId, 'soft-navigation', entry.navigationId, entry.name, entry.startTime); | ||
| report = bindReporter(onReport, metric, FCPThresholds, opts.reportAllChanges); | ||
| report(true); | ||
| }); | ||
| }; | ||
| observe('soft-navigation', handleSoftNavEntries, opts); | ||
| } | ||
| }); | ||
| }; |
+41
-18
@@ -30,2 +30,8 @@ /* | ||
| // `event` entries via PerformanceObserver. | ||
| // Event Timing entries have their durations rounded to the nearest 8ms, | ||
| // so a duration of 40ms would be any event that spans 2.5 or more frames | ||
| // at 60Hz. This threshold is chosen to strike a balance between usefulness | ||
| // and performance. Running this callback for any interaction that spans | ||
| // just one or two frames is likely not worth the insight that could be | ||
| // gained. | ||
| const DEFAULT_DURATION_THRESHOLD = 40; | ||
@@ -74,2 +80,21 @@ /** | ||
| const interactionManager = initUnique(opts, InteractionManager); | ||
| const initNewINPMetric = (interactionId, navigationType, navigationId, navigationURL, navigationStartTime) => { | ||
| interactionManager._resetInteractions(); | ||
| metric = initMetric('INP', -1, interactionId, navigationType, navigationId, navigationURL, navigationStartTime); | ||
| report = bindReporter(onReport, metric, INPThresholds, opts.reportAllChanges); | ||
| }; | ||
| const updateINPMetric = () => { | ||
| const inp = interactionManager._estimateP98LongestInteraction(metric.navigationType); | ||
| if (inp && inp._latency !== metric.value) { | ||
| metric.value = inp._latency; | ||
| metric.entries = inp.entries; | ||
| report(); | ||
| } | ||
| }; | ||
| const handleSoftNavEntry = (entry) => { | ||
| handleEntries(po?.takeRecords()); | ||
| updateINPMetric(); | ||
| report(true); | ||
| initNewINPMetric(entry.interactionId, 'soft-navigation', entry.navigationId, entry.name, entry.startTime); | ||
| }; | ||
| const handleEntries = (entries) => { | ||
@@ -83,20 +108,18 @@ // Queue the `handleEntries()` callback in the next idle task. | ||
| whenIdleOrHidden(() => { | ||
| // Only process entries, if at least some of them have interaction ids | ||
| // (otherwise run into lots of errors later for empty INP entries) | ||
| if (!entries.some((entry) => entry.interactionId)) | ||
| return; | ||
| for (const entry of entries) { | ||
| if ('largestInteractionContentfulPaint' in entry) { | ||
| handleSoftNavEntry(entry); | ||
| continue; | ||
| } | ||
| interactionManager._processEntry(entry); | ||
| } | ||
| const inp = interactionManager._estimateP98LongestInteraction(); | ||
| if (inp && inp._latency !== metric.value) { | ||
| metric.value = inp._latency; | ||
| metric.entries = inp.entries; | ||
| report(); | ||
| } | ||
| updateINPMetric(); | ||
| }); | ||
| }; | ||
| const po = observe('event', handleEntries, { | ||
| // Event Timing entries have their durations rounded to the nearest 8ms, | ||
| // so a duration of 40ms would be any event that spans 2.5 or more frames | ||
| // at 60Hz. This threshold is chosen to strike a balance between usefulness | ||
| // and performance. Running this callback for any interaction that spans | ||
| // just one or two frames is likely not worth the insight that could be | ||
| // gained. | ||
| ...opts, | ||
| durationThreshold: opts.durationThreshold ?? DEFAULT_DURATION_THRESHOLD, | ||
@@ -106,7 +129,9 @@ }); | ||
| if (po) { | ||
| // Also observe entries of type `first-input`. This is useful in cases | ||
| // where the first interaction is less than the `durationThreshold`. | ||
| po.observe({ type: 'first-input', buffered: true }); | ||
| visibilityWatcher.onHidden(() => { | ||
| handleEntries(po.takeRecords()); | ||
| // For soft navigations we may also need to report on hidden, even | ||
| // if there are no entries if the only interactions are < 16ms. | ||
| if (metric.navigationType === 'soft-navigation') { | ||
| updateINPMetric(); | ||
| } | ||
| report(true); | ||
@@ -117,5 +142,3 @@ }); | ||
| onBFCacheRestore(() => { | ||
| interactionManager._resetInteractions(); | ||
| metric = initMetric('INP'); | ||
| report = bindReporter(onReport, metric, INPThresholds, opts.reportAllChanges); | ||
| initNewINPMetric(metric.interactionId, 'back-forward-cache', metric.navigationId, metric.navigationURL, metric.navigationStartTime); | ||
| }); | ||
@@ -122,0 +145,0 @@ } |
+97
-24
@@ -21,2 +21,3 @@ /* | ||
| import { getActivationStart } from './lib/getActivationStart.js'; | ||
| import { checkSoftNavsEnabled } from './lib/softNavs.js'; | ||
| import { getVisibilityWatcher } from './lib/getVisibilityWatcher.js'; | ||
@@ -26,3 +27,2 @@ import { initMetric } from './lib/initMetric.js'; | ||
| import { observe } from './lib/observe.js'; | ||
| import { runOnce } from './lib/runOnce.js'; | ||
| import { whenActivated } from './lib/whenActivated.js'; | ||
@@ -44,15 +44,89 @@ import { whenIdleOrHidden } from './lib/whenIdleOrHidden.js'; | ||
| export const onLCP = (onReport, opts = {}) => { | ||
| // As InteractionContentfulPaint entries used by soft navs can emit after | ||
| // LCP is finalized, we need a flag to know to ignore them. | ||
| let isFinalized = false; | ||
| const softNavsEnabled = checkSoftNavsEnabled(opts); | ||
| whenActivated(() => { | ||
| const visibilityWatcher = getVisibilityWatcher(); | ||
| let visibilityWatcher = getVisibilityWatcher(); | ||
| let metric = initMetric('LCP'); | ||
| let report; | ||
| const lcpEntryManager = initUnique(opts, LCPEntryManager); | ||
| const initNewLCPMetric = (navigation, interactionId, navigationId, navigationURL, navigationStartTime) => { | ||
| metric = initMetric('LCP', 0, interactionId, navigation, navigationId, navigationURL, navigationStartTime); | ||
| report = bindReporter(onReport, metric, LCPThresholds, opts.reportAllChanges); | ||
| // Reset the finalized flag | ||
| isFinalized = false; | ||
| // If it's a soft nav, then need to reset the visibilityWatcher | ||
| if (navigation === 'soft-navigation') { | ||
| visibilityWatcher = getVisibilityWatcher(true); | ||
| } | ||
| }; | ||
| const handleSoftNavEntry = (entry) => { | ||
| handleEntries(po.takeRecords()); | ||
| if (!isFinalized) | ||
| report(true); | ||
| initNewLCPMetric('soft-navigation', entry.interactionId, entry.navigationId, entry.name, entry.startTime); | ||
| // Soft Navs should contain the largest paint until now, so handle that | ||
| // as if it just happened, then listen for more. | ||
| // It can however be null in rare circumstances | ||
| // (see https://github.com/GoogleChrome/web-vitals/issues/725) | ||
| const largestInteractionContentfulPaint = entry.getLargestInteractionContentfulPaint?.() || | ||
| // TODO to remove this, after OT ends as this has been replaced | ||
| // with above function | ||
| entry.largestInteractionContentfulPaint; | ||
| if (largestInteractionContentfulPaint) { | ||
| handleEntries([largestInteractionContentfulPaint]); | ||
| } | ||
| }; | ||
| const handleEntries = (entries) => { | ||
| // If reportAllChanges is set then call this function for each entry, | ||
| // otherwise only consider the last one. | ||
| if (!opts.reportAllChanges) { | ||
| // otherwise only consider the last one, unless soft navs are enabled. | ||
| if (!opts.reportAllChanges && !softNavsEnabled) { | ||
| entries = entries.slice(-1); | ||
| } | ||
| for (const entry of entries) { | ||
| lcpEntryManager._processEntry(entry); | ||
| if (!entry) | ||
| continue; | ||
| if ('largestInteractionContentfulPaint' in entry) { | ||
| handleSoftNavEntry(entry); | ||
| continue; | ||
| } | ||
| let value = 0; | ||
| let entries = []; | ||
| if (entry instanceof LargestContentfulPaint) { | ||
| // The startTime attribute returns the value of the renderTime if it is | ||
| // not 0, and the value of the loadTime otherwise. The activationStart | ||
| // reference is used because LCP should be relative to page activation | ||
| // rather than navigation start if the page was prerendered. But in cases | ||
| // where `activationStart` occurs after the LCP, this time should be | ||
| // clamped at 0. | ||
| value = Math.max(entry.startTime - getActivationStart(), 0); | ||
| lcpEntryManager._processEntry(entry); | ||
| entries = [entry]; | ||
| } | ||
| else { | ||
| // InteractionContentfulPaints should only happen after a | ||
| // SoftNavigationEntry so the metric should have been set | ||
| // with a non-zero navigationId mapping to a soft nav. | ||
| if (!metric.navigationId) | ||
| continue; | ||
| // Ignore interactions not for this soft nav | ||
| // (either paints that have bled into this interaction or paints when | ||
| // we should have already finalized) | ||
| if ('interactionId' in entry && | ||
| entry.interactionId != metric.interactionId) { | ||
| continue; | ||
| } | ||
| // We're changing the shape to nest LCP entries within | ||
| // interaction-contentful-paint so handle both for now | ||
| const renderTime = entry?.largestContentfulPaint?.renderTime || entry.renderTime || 0; | ||
| // Paints should never be less than 0 but add cap just in case | ||
| value = Math.max(renderTime - entry.startTime, 0); | ||
| if (entry.largestContentfulPaint) { | ||
| lcpEntryManager._processEntry(entry.largestContentfulPaint); | ||
| } | ||
| if (entry.largestContentfulPaint) { | ||
| entries = [entry.largestContentfulPaint]; | ||
| } | ||
| } | ||
| // Only report if the page wasn't hidden prior to LCP. | ||
@@ -66,4 +140,4 @@ if (entry.startTime < visibilityWatcher.firstHiddenTime) { | ||
| // clamped at 0. | ||
| metric.value = Math.max(entry.startTime - getActivationStart(), 0); | ||
| metric.entries = [entry]; | ||
| metric.value = value; | ||
| metric.entries = entries; | ||
| report(); | ||
@@ -73,22 +147,20 @@ } | ||
| }; | ||
| const po = observe('largest-contentful-paint', handleEntries); | ||
| const po = observe('largest-contentful-paint', handleEntries, opts); | ||
| if (po) { | ||
| report = bindReporter(onReport, metric, LCPThresholds, opts.reportAllChanges); | ||
| // Ensure this logic only runs once, since it can be triggered from | ||
| // any of three different event listeners below. | ||
| const stopListening = runOnce(() => { | ||
| handleEntries(po.takeRecords()); | ||
| po.disconnect(); | ||
| report(true); | ||
| }); | ||
| // Need a separate wrapper to ensure the `runOnce` function above is | ||
| // common for all three functions | ||
| const stopListeningWrapper = (event) => { | ||
| if (event.isTrusted) { | ||
| const finalizeLCP = (event) => { | ||
| if (event.isTrusted && !isFinalized) { | ||
| // Wrap the listener in an idle callback so it's run in a separate | ||
| // task to reduce potential INP impact. | ||
| // https://github.com/GoogleChrome/web-vitals/issues/383 | ||
| whenIdleOrHidden(stopListening); | ||
| removeEventListener(event.type, stopListeningWrapper, { | ||
| capture: true, | ||
| whenIdleOrHidden(() => { | ||
| if (!isFinalized) { | ||
| handleEntries(po.takeRecords()); | ||
| if (!softNavsEnabled) { | ||
| po.disconnect(); | ||
| removeEventListener(event.type, finalizeLCP); | ||
| } | ||
| isFinalized = true; | ||
| report(true); | ||
| } | ||
| }); | ||
@@ -102,3 +174,3 @@ } | ||
| for (const type of ['keydown', 'click', 'visibilitychange']) { | ||
| addEventListener(type, stopListeningWrapper, { | ||
| addEventListener(type, finalizeLCP, { | ||
| capture: true, | ||
@@ -110,6 +182,7 @@ }); | ||
| onBFCacheRestore((event) => { | ||
| metric = initMetric('LCP'); | ||
| initNewLCPMetric('back-forward-cache', metric.interactionId, metric.navigationId, metric.navigationURL, metric.navigationStartTime); | ||
| report = bindReporter(onReport, metric, LCPThresholds, opts.reportAllChanges); | ||
| doubleRAF(() => { | ||
| metric.value = performance.now() - event.timeStamp; | ||
| isFinalized = true; | ||
| report(true); | ||
@@ -116,0 +189,0 @@ }); |
@@ -17,6 +17,8 @@ /* | ||
| import { bindReporter } from './lib/bindReporter.js'; | ||
| import { checkSoftNavsEnabled } from './lib/softNavs.js'; | ||
| import { getNavigationEntry } from './lib/getNavigationEntry.js'; | ||
| import { getActivationStart } from './lib/getActivationStart.js'; | ||
| import { initMetric } from './lib/initMetric.js'; | ||
| import { observe } from './lib/observe.js'; | ||
| import { onBFCacheRestore } from './lib/bfcache.js'; | ||
| import { getNavigationEntry } from './lib/getNavigationEntry.js'; | ||
| import { getActivationStart } from './lib/getActivationStart.js'; | ||
| import { whenActivated } from './lib/whenActivated.js'; | ||
@@ -57,7 +59,11 @@ /** Thresholds for TTFB. See https://web.dev/articles/ttfb#what_is_a_good_ttfb_score */ | ||
| export const onTTFB = (onReport, opts = {}) => { | ||
| // Set defaults | ||
| opts = opts || {}; | ||
| const softNavsEnabled = checkSoftNavsEnabled(opts); | ||
| let metric = initMetric('TTFB'); | ||
| let report = bindReporter(onReport, metric, TTFBThresholds, opts.reportAllChanges); | ||
| whenReady(() => { | ||
| const navigationEntry = getNavigationEntry(); | ||
| if (navigationEntry) { | ||
| const hardNavEntry = getNavigationEntry(); | ||
| if (hardNavEntry) { | ||
| const responseStart = hardNavEntry.responseStart; | ||
| // The activationStart reference is used because TTFB should be | ||
@@ -67,4 +73,4 @@ // relative to page activation rather than navigation start if the | ||
| // after the first byte is received, this time should be clamped at 0. | ||
| metric.value = Math.max(navigationEntry.responseStart - getActivationStart(), 0); | ||
| metric.entries = [navigationEntry]; | ||
| metric.value = Math.max(responseStart - getActivationStart(), 0); | ||
| metric.entries = [hardNavEntry]; | ||
| report(true); | ||
@@ -74,8 +80,22 @@ // Only report TTFB after bfcache restores if a `navigation` entry | ||
| onBFCacheRestore(() => { | ||
| metric = initMetric('TTFB', 0); | ||
| metric = initMetric('TTFB', 0, metric.interactionId, 'back-forward-cache', metric.navigationId, metric.navigationURL, metric.navigationStartTime); | ||
| report = bindReporter(onReport, metric, TTFBThresholds, opts.reportAllChanges); | ||
| report(true); | ||
| }); | ||
| // Listen for soft-navigation entries and emit a dummy 0 TTFB entry | ||
| if (softNavsEnabled) { | ||
| const reportSoftNavTTFBs = (entries) => { | ||
| entries.forEach((entry) => { | ||
| if (entry.navigationId) { | ||
| metric = initMetric('TTFB', 0, entry.interactionId, 'soft-navigation', entry.navigationId, entry.name, entry.startTime); | ||
| metric.entries = [entry]; | ||
| report = bindReporter(onReport, metric, TTFBThresholds, opts.reportAllChanges); | ||
| report(true); | ||
| } | ||
| }); | ||
| }; | ||
| observe('soft-navigation', reportSoftNavTTFBs, opts); | ||
| } | ||
| } | ||
| }); | ||
| }; |
@@ -8,5 +8,9 @@ export * from './types/base.js'; | ||
| interface PerformanceEntryMap { | ||
| navigation: PerformanceNavigationTiming; | ||
| resource: PerformanceResourceTiming; | ||
| paint: PerformancePaintTiming; | ||
| 'event': PerformanceEventTiming; | ||
| 'interaction-contentful-paint': InteractionContentfulPaint; | ||
| 'layout-shift': LayoutShift; | ||
| 'navigation': PerformanceNavigationTiming; | ||
| 'paint': PerformancePaintTiming; | ||
| 'resource': PerformanceResourceTiming; | ||
| 'soft-navigation': SoftNavigationEntry; | ||
| } | ||
@@ -21,6 +25,9 @@ declare global { | ||
| } | ||
| interface PerformanceEntry { | ||
| navigationId?: number; | ||
| } | ||
| interface PerformanceObserverInit { | ||
| durationThreshold?: number; | ||
| } | ||
| interface PerformanceNavigationTiming { | ||
| interface PerformanceNavigationTiming extends PerformanceEntry { | ||
| activationStart?: number; | ||
@@ -31,2 +38,3 @@ } | ||
| readonly interactionId: number; | ||
| readonly targetSelector: string; | ||
| } | ||
@@ -51,2 +59,15 @@ interface LayoutShiftAttribution { | ||
| } | ||
| interface InteractionContentfulPaint extends PerformanceEntry { | ||
| readonly interactionId: number; | ||
| readonly largestContentfulPaint?: LargestContentfulPaint; | ||
| readonly renderTime?: DOMHighResTimeStamp; | ||
| } | ||
| interface SoftNavigationEntry extends PerformanceEntry { | ||
| readonly interactionId: number; | ||
| readonly navigationType?: NavigationType; | ||
| readonly paintTime?: number; | ||
| readonly presentationTime?: number; | ||
| readonly largestInteractionContentfulPaint: InteractionContentfulPaint; | ||
| readonly getLargestInteractionContentfulPaint?: () => InteractionContentfulPaint | null; | ||
| } | ||
| export type ScriptInvokerType = 'classic-script' | 'module-script' | 'event-listener' | 'user-callback' | 'resolve-promise' | 'reject-promise'; | ||
@@ -53,0 +74,0 @@ export type ScriptWindowAttribution = 'self' | 'descendant' | 'ancestor' | 'same-page' | 'other'; |
@@ -36,2 +36,6 @@ import type { CLSMetric, CLSMetricWithAttribution } from './cls.js'; | ||
| /** | ||
| * The interactionId that started this interaction for soft navigations | ||
| */ | ||
| interactionId?: number; | ||
| /** | ||
| * Any performance entries relevant to the metric value calculation. | ||
@@ -53,4 +57,20 @@ * The array may also be empty if the metric value was not based on any | ||
| * restored by the user. | ||
| * - 'soft-navigation': for soft navigations. | ||
| */ | ||
| navigationType: 'navigate' | 'reload' | 'back-forward' | 'back-forward-cache' | 'prerender' | 'restore'; | ||
| navigationType: 'navigate' | 'reload' | 'back-forward' | 'back-forward-cache' | 'prerender' | 'restore' | 'soft-navigation'; | ||
| /** | ||
| * The navigationId the metric happened for. This is particularly relevant for soft navigations where | ||
| * the metric may be reported for a previous URL. | ||
| */ | ||
| navigationId: number; | ||
| /** | ||
| * The navigation startTime the metric is based from. This is particularly | ||
| * relevant for soft navigations where time origin is not 0. | ||
| */ | ||
| navigationStartTime?: number; | ||
| /** | ||
| * The navigation URL the metric happened for. This is particularly relevant for soft navigations where | ||
| * the metric may be reported for a previous URL. | ||
| */ | ||
| navigationURL?: string; | ||
| } | ||
@@ -85,2 +105,4 @@ /** The union of supported metric types. */ | ||
| reportAllChanges?: boolean; | ||
| durationThreshold?: number; | ||
| reportSoftNavs?: boolean; | ||
| } | ||
@@ -87,0 +109,0 @@ export interface AttributionReportOpts extends ReportOpts { |
@@ -39,3 +39,3 @@ import type { LoadState, Metric } from './base.js'; | ||
| */ | ||
| navigationEntry?: PerformanceNavigationTiming; | ||
| navigationEntry?: PerformanceNavigationTiming | SoftNavigationEntry; | ||
| } | ||
@@ -42,0 +42,0 @@ /** |
@@ -57,3 +57,3 @@ import type { Metric } from './base.js'; | ||
| */ | ||
| navigationEntry?: PerformanceNavigationTiming; | ||
| navigationEntry?: PerformanceNavigationTiming | SoftNavigationEntry; | ||
| /** | ||
@@ -65,5 +65,6 @@ * The `resource` entry for the LCP resource (if applicable), which is useful | ||
| /** | ||
| * The `LargestContentfulPaint` entry corresponding to LCP. | ||
| * The `LargestContentfulPaint` entry corresponding to LCP | ||
| * (or `InteractionContentfulPaint` for soft navigations). | ||
| */ | ||
| lcpEntry?: LargestContentfulPaint; | ||
| lcpEntry?: LargestContentfulPaint | InteractionContentfulPaint; | ||
| } | ||
@@ -70,0 +71,0 @@ /** |
@@ -7,3 +7,3 @@ import type { Metric } from './base.js'; | ||
| name: 'TTFB'; | ||
| entries: PerformanceNavigationTiming[]; | ||
| entries: PerformanceNavigationTiming[] | SoftNavigationEntry[]; | ||
| } | ||
@@ -53,3 +53,3 @@ /** | ||
| */ | ||
| navigationEntry?: PerformanceNavigationTiming; | ||
| navigationEntry?: PerformanceNavigationTiming | SoftNavigationEntry; | ||
| } | ||
@@ -56,0 +56,0 @@ /** |
@@ -1,1 +0,1 @@ | ||
| var webVitals=function(t){"use strict";class e{t;i=0;o=[];u(t){if(t.hadRecentInput)return;const e=this.o[0],n=this.o.at(-1);this.i&&e&&n&&t.startTime-n.startTime<1e3&&t.startTime-e.startTime<5e3?(this.i+=t.value,this.o.push(t)):(this.i=t.value,this.o=[t]),this.t?.(t)}}const n=()=>{const t=performance.getEntriesByType("navigation")[0];if(t&&t.responseStart>0&&t.responseStart<performance.now())return t},i=t=>{if("loading"===document.readyState)return"loading";const e=n();if(e){if(t<e.domInteractive)return"loading";if(0===e.domContentLoadedEventStart||t<e.domContentLoadedEventStart)return"dom-interactive";if(0===e.domComplete||t<e.domComplete)return"dom-content-loaded"}return"complete"},o=t=>{const e=t.nodeName;return 1===t.nodeType?e.toLowerCase():e.toUpperCase().replace(/^#/,"")},r=t=>{let e="";try{for(;9!==t?.nodeType;){const n=t,i=n.id?"#"+n.id:[o(n),...Array.from(n.classList).sort()].join(".");if(e.length+i.length>99)return e||i;if(e=e?i+">"+e:i,n.id)break;t=n.parentNode}}catch{}return e},s=new WeakMap;function a(t,e){return s.get(t)||s.set(t,new e),s.get(t)}let c=-1;const u=()=>c,f=t=>{addEventListener("pageshow",e=>{e.persisted&&(c=e.timeStamp,t(e))},!0)},d=(t,e,n,i)=>{let o,r;return s=>{e.value>=0&&(s||i)&&(r=e.value-(o??0),(r||void 0===o)&&(o=e.value,e.delta=r,e.rating=((t,e)=>t>e[1]?"poor":t>e[0]?"needs-improvement":"good")(e.value,n),t(e)))}},l=t=>{requestAnimationFrame(()=>requestAnimationFrame(t))},h=()=>n()?.activationStart??0,m=(t,e=-1)=>{const i=n();let o="navigate";u()>=0?o="back-forward-cache":i&&(document.prerendering||h()>0?o="prerender":document.wasDiscarded?o="restore":i.type&&(o=i.type.replace(/_/g,"-")));return{name:t,value:e,rating:"good",delta:0,entries:[],id:`v5-${Date.now()}-${Math.floor(8999999999999*Math.random())+1e12}`,navigationType:o}},g=(t,e,n={})=>{try{if(PerformanceObserver.supportedEntryTypes.includes(t)){const i=new PerformanceObserver(t=>{queueMicrotask(()=>{e(t.getEntries())})});return i.observe({type:t,buffered:!0,...n}),i}}catch{}},p=t=>{let e=!1;return()=>{e||(t(),e=!0)}};let y=-1;const v=new Set,b=()=>"hidden"!==document.visibilityState||document.prerendering?1/0:0,M=t=>{if("hidden"===document.visibilityState){if("visibilitychange"===t.type)for(const t of v)t();isFinite(y)||(y="visibilitychange"===t.type?t.timeStamp:0,removeEventListener("prerenderingchange",M,!0))}},T=()=>{if(y<0){const t=h(),e=document.prerendering?void 0:globalThis.performance.getEntriesByType("visibility-state").find(e=>"hidden"===e.name&&e.startTime>=t)?.startTime;y=e??b(),addEventListener("visibilitychange",M,!0),addEventListener("prerenderingchange",M,!0),f(()=>{setTimeout(()=>{y=b()})})}return{get firstHiddenTime(){return y},onHidden(t){v.add(t)}}},E=t=>{document.prerendering?addEventListener("prerenderingchange",t,!0):t()},D=[1800,3e3],L=(t,e={})=>{E(()=>{const n=T();let i,o=m("FCP");const r=g("paint",t=>{for(const e of t)"first-contentful-paint"===e.name&&(r.disconnect(),e.startTime<n.firstHiddenTime&&(o.value=Math.max(e.startTime-h(),0),o.entries.push(e),i(!0)))});r&&(i=d(t,o,D,e.reportAllChanges),f(n=>{o=m("FCP"),i=d(t,o,D,e.reportAllChanges),l(()=>{o.value=performance.now()-n.timeStamp,i(!0)})}))})},S=[.1,.25],P=t=>t.find(t=>1===t.node?.nodeType)||t[0];let w=0,_=1/0,k=0;const F=t=>{for(const e of t)e.interactionId&&(_=Math.min(_,e.interactionId),k=Math.max(k,e.interactionId),w=k?(k-_)/7+1:0)};let B;const C=()=>B?w:performance.interactionCount??0,O=()=>{"interactionCount"in performance||B||(B=g("event",F,{durationThreshold:0}))};let j=0;class I{l=[];h=new Map;m;p;v(){j=C(),this.l.length=0,this.h.clear()}M(){const t=Math.min(this.l.length-1,Math.floor((C()-j)/50));return this.l[t]}u(t){if(this.m?.(t),!t.interactionId&&"first-input"!==t.entryType)return;const e=this.l.at(-1);let n=this.h.get(t.interactionId);if(n||this.l.length<10||t.duration>e.T){if(n?t.duration>n.T?(n.entries=[t],n.T=t.duration):t.duration===n.T&&t.startTime===n.entries[0].startTime&&n.entries.push(t):(n={id:t.interactionId,entries:[t],T:t.duration},this.h.set(n.id,n),this.l.push(n)),this.l.sort((t,e)=>e.T-t.T),this.l.length>10){const t=this.l.splice(10);for(const e of t)this.h.delete(e.id)}this.p?.(n)}}}const A=t=>{const e=globalThis.requestIdleCallback||setTimeout,n=globalThis.cancelIdleCallback||clearTimeout;if("hidden"===document.visibilityState)t();else{const i=p(t);let o=-1;const r=()=>{n(o),i()};addEventListener("visibilitychange",r,{once:!0,capture:!0}),o=e(()=>{removeEventListener("visibilitychange",r,{capture:!0}),i()})}},q=[200,500];class W{m;u(t){this.m?.(t)}}const N=[2500,4e3],V=[800,1800],$=t=>{document.prerendering?E(()=>$(t)):"complete"!==document.readyState?addEventListener("load",()=>$(t),!0):setTimeout(t)};return t.CLSThresholds=S,t.FCPThresholds=D,t.INPThresholds=q,t.LCPThresholds=N,t.TTFBThresholds=V,t.onCLS=(t,n={})=>{const o=a(n=Object.assign({},n),e),s=new WeakMap;o.t=t=>{if(t?.sources?.length){const e=P(t.sources),i=e?.node;if(i){const t=n.generateTarget?.(i)??r(i);s.set(e,t)}}};((t,n={})=>{const i=T();L(p(()=>{let o,r=m("CLS",0);const s=a(n,e),c=t=>{for(const e of t)s.u(e);s.i>r.value&&(r.value=s.i,r.entries=s.o,o())},u=g("layout-shift",c);u&&(o=d(t,r,S,n.reportAllChanges),i.onHidden(()=>{c(u.takeRecords()),o(!0)}),f(()=>{s.i=0,r=m("CLS",0),o=d(t,r,S,n.reportAllChanges),l(o)}),setTimeout(o))}))})(e=>{t((t=>{let e={};if(t.entries.length){const n=t.entries.reduce((t,e)=>t.value>e.value?t:e);if(n?.sources?.length){const t=P(n.sources);t&&(e={largestShiftTarget:s.get(t),largestShiftTime:n.startTime,largestShiftValue:n.value,largestShiftSource:t,largestShiftEntry:n,loadState:i(n.startTime)})}}return Object.assign(t,{attribution:e})})(e))},n)},t.onFCP=(t,e={})=>{L(e=>{t((t=>{let e={timeToFirstByte:0,firstByteToFCP:t.value,loadState:i(u())};if(t.entries.length){const o=n(),r=t.entries.at(-1);if(o){const n=o.activationStart||0,s=Math.max(0,o.responseStart-n);e={timeToFirstByte:s,firstByteToFCP:t.value-s,loadState:i(t.entries[0].startTime),navigationEntry:o,fcpEntry:r}}}return Object.assign(t,{attribution:e})})(e))},e)},t.onINP=(t,e={})=>{const n=a(e=Object.assign({},e),I);let o=[],s=[],c=0;const u=new WeakMap,l=new WeakMap;let h=!1;const p=()=>{h||(A(y),h=!0)},y=()=>{const t=new Set(n.l.map(t=>u.get(t.entries[0]))),e=s.length-10;s=s.filter((n,i)=>i>=e||t.has(n));const i=new Set;for(const t of s){const e=v(t.startTime,t.processingEnd);for(const t of e)i.add(t)}o=o.filter(t=>t.startTime>c||i.has(t)),h=!1};n.m=t=>{const n=t.startTime+t.duration;let i;c=Math.max(c,t.processingEnd);for(let o=s.length-1;o>=0;o--){const r=s[o];if(Math.abs(n-r.renderTime)<=8){i=r,i.startTime=Math.min(t.startTime,i.startTime),i.processingStart=Math.min(t.processingStart,i.processingStart),i.processingEnd=Math.max(t.processingEnd,i.processingEnd),!1!==e.includeProcessedEventEntries&&i.entries.push(t);break}}i||(i={startTime:t.startTime,processingStart:t.processingStart,processingEnd:t.processingEnd,renderTime:n,entries:!1!==e.includeProcessedEventEntries?[t]:[]},s.push(i)),(t.interactionId||"first-input"===t.entryType)&&u.set(t,i),p()},n.p=t=>{if(!l.get(t)){const n=t.entries[0].target;if(n){const i=e.generateTarget?.(n)??r(n);l.set(t,i)}}};const v=(t,e)=>{const n=[];for(const i of o)if(!(i.startTime+i.duration<t)){if(i.startTime>e)break;n.push(i)}return n},b=t=>{const e=t.entries[0],o=u.get(e),r=e.processingStart,s=Math.max(e.startTime+e.duration,r),a=Math.min(o.processingEnd,s),c=o.entries.sort((t,e)=>t.processingStart-e.processingStart),f=v(e.startTime,a),d=n.h.get(e.interactionId),h={interactionTarget:l.get(d),interactionType:e.name.startsWith("key")?"keyboard":"pointer",interactionTime:e.startTime,nextPaintTime:s,processedEventEntries:c,longAnimationFrameEntries:f,inputDelay:r-e.startTime,processingDuration:a-r,presentationDelay:s-a,loadState:i(e.startTime),longestScript:void 0,totalScriptDuration:void 0,totalStyleAndLayoutDuration:void 0,totalPaintDuration:void 0,totalUnattributedDuration:void 0};return(t=>{if(!t.longAnimationFrameEntries?.length)return;const e=t.interactionTime,n=t.inputDelay,i=t.processingDuration;let o,r,s=0,a=0,c=0,u=0;for(const c of t.longAnimationFrameEntries){a=a+c.startTime+c.duration-c.styleAndLayoutStart;for(const t of c.scripts){const c=t.startTime+t.duration;if(c<e)continue;const f=c-Math.max(e,t.startTime),d=t.duration?f/t.duration*t.forcedStyleAndLayoutDuration:0;s+=f-d,a+=d,f>u&&(r=t.startTime<e+n?"input-delay":t.startTime>=e+n+i?"presentation-delay":"processing-duration",o=t,u=f)}}const f=t.longAnimationFrameEntries.at(-1),d=f?f.startTime+f.duration:0;d>=e+n+i&&(c=t.nextPaintTime-d),o&&r&&(t.longestScript={entry:o,subpart:r,intersectingDuration:u}),t.totalScriptDuration=s,t.totalStyleAndLayoutDuration=a,t.totalPaintDuration=c,t.totalUnattributedDuration=t.nextPaintTime-e-s-a-c})(h),Object.assign(t,{attribution:h})};g("long-animation-frame",t=>{o=o.concat(t),p()}),((t,e={})=>{if(!globalThis.PerformanceEventTiming||!("interactionId"in PerformanceEventTiming.prototype))return;const n=T();E(()=>{O();let i,o=m("INP");const r=a(e,I),s=t=>{A(()=>{for(const e of t)r.u(e);const e=r.M();e&&e.T!==o.value&&(o.value=e.T,o.entries=e.entries,i())})},c=g("event",s,{durationThreshold:e.durationThreshold??40});i=d(t,o,q,e.reportAllChanges),c&&(c.observe({type:"first-input",buffered:!0}),n.onHidden(()=>{s(c.takeRecords()),i(!0)}),f(()=>{r.v(),o=m("INP"),i=d(t,o,q,e.reportAllChanges)}))})})(e=>{t(b(e))},e)},t.onLCP=(t,e={})=>{const i=a(e=Object.assign({},e),W),o=new WeakMap;i.m=t=>{const n=t.element;if(n){const i=e.generateTarget?.(n)??r(n);o.set(t,i)}else t.id&&o.set(t,`#${t.id}`)};((t,e={})=>{E(()=>{const n=T();let i,o=m("LCP");const r=a(e,W),s=t=>{e.reportAllChanges||(t=t.slice(-1));for(const e of t)r.u(e),e.startTime<n.firstHiddenTime&&(o.value=Math.max(e.startTime-h(),0),o.entries=[e],i())},c=g("largest-contentful-paint",s);if(c){i=d(t,o,N,e.reportAllChanges);const n=p(()=>{s(c.takeRecords()),c.disconnect(),i(!0)}),r=t=>{t.isTrusted&&(A(n),removeEventListener(t.type,r,{capture:!0}))};for(const t of["keydown","click","visibilitychange"])addEventListener(t,r,{capture:!0});f(n=>{o=m("LCP"),i=d(t,o,N,e.reportAllChanges),l(()=>{o.value=performance.now()-n.timeStamp,i(!0)})})}})})(e=>{t((t=>{let e={timeToFirstByte:0,resourceLoadDelay:0,resourceLoadDuration:0,elementRenderDelay:t.value};if(t.entries.length){const i=t.entries.at(-1),r=i.url&&performance.getEntriesByType("resource").find(t=>t.name===i.url);e.target=o.get(i),e.lcpEntry=i,i.url&&(e.url=i.url),r&&(e.lcpResourceEntry=r);const s=n();if(s){const n=s.activationStart||0,i=Math.max(0,s.responseStart-n),o=Math.max(i,r?(r.requestStart||r.startTime)-n:0),a=Math.min(t.value,Math.max(o,r?r.responseEnd-n:0));e={...e,timeToFirstByte:i,resourceLoadDelay:o-i,resourceLoadDuration:a-o,elementRenderDelay:t.value-a,navigationEntry:s}}}return Object.assign(t,{attribution:e})})(e))},e)},t.onTTFB=(t,e={})=>{((t,e={})=>{let i=m("TTFB"),o=d(t,i,V,e.reportAllChanges);$(()=>{const r=n();r&&(i.value=Math.max(r.responseStart-h(),0),i.entries=[r],o(!0),f(()=>{i=m("TTFB",0),o=d(t,i,V,e.reportAllChanges),o(!0)}))})})(e=>{t((t=>{let e={waitingDuration:0,cacheDuration:0,dnsDuration:0,connectionDuration:0,requestDuration:0};if(t.entries.length){const n=t.entries[0],i=n.activationStart||0,o=Math.max((n.workerStart||n.fetchStart)-i,0),r=Math.max(n.domainLookupStart-i,0),s=Math.max(n.connectStart-i,0),a=Math.max(n.connectEnd-i,0);e={waitingDuration:o,cacheDuration:r-o,dnsDuration:s-r,connectionDuration:a-s,requestDuration:t.value-a,navigationEntry:n}}return Object.assign(t,{attribution:e})})(e))},e)},t}({}); | ||
| var webVitals=function(t){"use strict";class n{t;i=0;o=[];l(t){if(t.hadRecentInput)return;const n=this.o[0],e=this.o.at(-1);this.i&&n&&e&&t.startTime-e.startTime<1e3&&t.startTime-n.startTime<5e3?(this.i+=t.value,this.o.push(t)):(this.i=t.value,this.o=[t]),this.t?.(t)}}const e=()=>{const t=performance.getEntriesByType("navigation")[0];if(t&&t.responseStart>0&&t.responseStart<performance.now())return t},i=t=>{if("loading"===document.readyState)return"loading";const n=e();if(n){if(t<n.domInteractive)return"loading";if(0===n.domContentLoadedEventStart||t<n.domContentLoadedEventStart)return"dom-interactive";if(0===n.domComplete||t<n.domComplete)return"dom-content-loaded"}return"complete"},o=t=>{const n=t.nodeName;return 1===t.nodeType?n.toLowerCase():n.toUpperCase().replace(/^#/,"")},r=t=>{let n="";try{for(;9!==t?.nodeType;){const e=t,i=e.id?"#"+e.id:[o(e),...Array.from(e.classList).sort()].join(".");if(n.length+i.length>99)return n||i;if(n=n?i+">"+n:i,e.id)break;t=e.parentNode}}catch{}return n},a=new WeakMap;function s(t,n){let e=a.get(n);return e||(e=new WeakMap,a.set(n,e)),e.get(t)||e.set(t,new n),e.get(t)}let c=-1;const f=()=>c,l=t=>{addEventListener("pageshow",n=>{n.persisted&&(c=n.timeStamp,t(n))},!0)},u=(t,n,e,i)=>{let o,r;return a=>{n.value>=0&&(a||i)&&(r=n.value-(o??0),(r||void 0===o)&&(o=n.value,n.delta=r,n.rating=((t,n)=>t>n[1]?"poor":t>n[0]?"needs-improvement":"good")(n.value,e),t(n)))}},d=t=>{requestAnimationFrame(()=>requestAnimationFrame(t))},h=()=>e()?.activationStart??0;let g=-1;const v=new Set,p=()=>"hidden"!==document.visibilityState||document.prerendering?1/0:0,m=t=>{if("hidden"===document.visibilityState){if("visibilitychange"===t.type)for(const t of v)t();isFinite(g)||(g="visibilitychange"===t.type?t.timeStamp:0,removeEventListener("prerenderingchange",m,!0))}},y=(t=!1)=>{if(t&&(g=1/0),g<0){const t=h(),n=document.prerendering?void 0:globalThis.performance.getEntriesByType("visibility-state").find(n=>"hidden"===n.name&&n.startTime>=t)?.startTime;g=n??p(),addEventListener("visibilitychange",m,!0),addEventListener("prerenderingchange",m,!0),l(()=>{setTimeout(()=>{g=p()})})}return{get firstHiddenTime(){return g},onHidden(t){v.add(t)}}},b=(t,n=-1,i,o,r=0,a,s)=>{const c=e()?.navigationId||0,l=e();let u="navigate";o?u=o:f()>=0?u="back-forward-cache":l&&(document.prerendering||h()>0?u="prerender":document.wasDiscarded?u="restore":l.type&&(u=l.type.replace(/_/g,"-")));return{name:t,value:n,rating:"good",delta:0,entries:[],id:`v5-${Date.now()}-${Math.floor(8999999999999*Math.random())+1e12}`,navigationType:u,navigationId:r||c,interactionId:i,navigationURL:a||l?.name,navigationStartTime:s||0}},T=t=>PerformanceObserver.supportedEntryTypes.includes("soft-navigation")&&t&&t.reportSoftNavs,M=(t,n,e={})=>{const i=[t],o=T(e);"event"===t&&i.push("first-input"),o&&("largest-contentful-paint"!==t&&"event"!==t&&"layout-shift"!==t||i.push("soft-navigation"),"largest-contentful-paint"===t&&i.push("interaction-contentful-paint"));try{if(PerformanceObserver.supportedEntryTypes.includes(t)){const t=new PerformanceObserver(t=>{queueMicrotask(()=>{const e=t.getEntries();e.sort((t,n)=>t.startTime+t.duration-(n.startTime+n.duration)),n(e)})});for(const n of i)t.observe(Object.assign({type:n,buffered:!0,...e},e||{}));return t}}catch{}},D=t=>{let n=!1;return()=>{n||(t(),n=!0)}},E=t=>{document.prerendering?addEventListener("prerenderingchange",t,!0):t()},P=[1800,3e3],S=(t,n={})=>{const e=T(n=n||{});E(()=>{const i=y();let o,r=b("FCP");const a=t=>{for(const n of t)"first-contentful-paint"===n.name&&(s.disconnect(),n.startTime<i.firstHiddenTime&&(r.value=Math.max(n.startTime-h(),0),r.entries.push(n),r.navigationId=n.navigationId||0,o(!0)))},s=M("paint",a,n);if(s&&(o=u(t,r,P,n.reportAllChanges),l(e=>{r=b("FCP",0,r.interactionId,"back-forward-cache",r.navigationId,r.navigationURL,r.navigationStartTime),o=u(t,r,P,n.reportAllChanges),d(()=>{r.value=performance.now()-e.timeStamp,o(!0)})})),e){M("soft-navigation",e=>{e.forEach(e=>{a(s.takeRecords());const i=(e.presentationTime||e.paintTime||0)-e.startTime;r=b("FCP",i,e.interactionId,"soft-navigation",e.navigationId,e.name,e.startTime),o=u(t,r,P,n.reportAllChanges),o(!0)})},n)}})},L=[.1,.25],w=t=>t.find(t=>1===t.node?.nodeType)||t[0];let k=0,F=1/0,_=0;const C=t=>{for(const n of t)n.interactionId&&(F=Math.min(F,n.interactionId),_=Math.max(_,n.interactionId),k=_?(_-F)/7+1:0)};let I;const O=()=>I?k:performance.interactionCount??0,B=()=>{"interactionCount"in performance||I||(I=M("event",C,{durationThreshold:0}))};let j=0;class A{u=[];h=new Map;v;p;m(){j=O(),this.u.length=0,this.h.clear()}T(t){const n=O()-j,e=Math.min(this.u.length-1,Math.floor(n/50));return n&&-1===e&&"soft-navigation"===t?{M:8,id:-1,entries:[]}:this.u[e]}l(t){if(this.v?.(t),!t.interactionId&&"first-input"!==t.entryType)return;const n=this.u.at(-1);let e=this.h.get(t.interactionId);if(e||this.u.length<10||t.duration>n.M){if(e?t.duration>e.M?(e.entries=[t],e.M=t.duration):t.duration===e.M&&t.startTime===e.entries[0].startTime&&e.entries.push(t):(e={id:t.interactionId,entries:[t],M:t.duration},this.h.set(e.id,e),this.u.push(e)),this.u.sort((t,n)=>n.M-t.M),this.u.length>10){const t=this.u.splice(10);for(const n of t)this.h.delete(n.id)}this.p?.(e)}}}const W=t=>{const n=globalThis.requestIdleCallback||setTimeout,e=globalThis.cancelIdleCallback||clearTimeout;if("hidden"===document.visibilityState)t();else{const i=D(t);let o=-1;const r=()=>{e(o),i()};addEventListener("visibilitychange",r,{once:!0,capture:!0}),o=n(()=>{removeEventListener("visibilitychange",r,{capture:!0}),i()})}},q=[200,500];class U{v;l(t){this.v?.(t)}}const x=[2500,4e3],N=[800,1800],R=t=>{document.prerendering?E(()=>R(t)):"complete"!==document.readyState?addEventListener("load",()=>R(t),!0):setTimeout(t)};return t.CLSThresholds=L,t.FCPThresholds=P,t.INPThresholds=q,t.LCPThresholds=x,t.TTFBThresholds=N,t.onCLS=(t,e={})=>{const o=s(e=Object.assign({},e),n),a=new WeakMap;o.t=t=>{if(t?.sources?.length){const n=w(t.sources),i=n?.node;if(i){const t=e.generateTarget?.(i)??r(i);a.set(n,t)}}};((t,e={})=>{const i=y();S(D(()=>{let o,r=b("CLS",0);const a=s(e,n),c=(n,i,s,c,f)=>{r=b("CLS",0,n,i,s,c,f),a.i=0,o=u(t,r,L,e.reportAllChanges)},f=t=>{h(g?.takeRecords()),o(!0),c(t.interactionId,"soft-navigation",t.navigationId,t.name,t.startTime)},h=t=>{for(const n of t)"largestInteractionContentfulPaint"in n?f(n):a.l(n);a.i>r.value&&(r.value=a.i,r.entries=a.o,o())},g=M("layout-shift",h,e);g&&(o=u(t,r,L,e.reportAllChanges),i.onHidden(()=>{h(g.takeRecords()),o(!0)}),l(()=>{c(r.interactionId,"back-forward-cache",r.navigationId,r.navigationURL,r.navigationStartTime),d(o)}),setTimeout(o))}))})(n=>{t((t=>{let n={};if(t.entries.length){const e=t.entries.reduce((t,n)=>t.value>n.value?t:n);if(e?.sources?.length){const t=w(e.sources);t&&(n={largestShiftTarget:a.get(t),largestShiftTime:e.startTime,largestShiftValue:e.value,largestShiftSource:t,largestShiftEntry:e,loadState:i(e.startTime)})}}return Object.assign(t,{attribution:n})})(n))},e)},t.onFCP=(t,n={})=>{S(n=>{t((t=>{const n=e()?.navigationId||0;let o={timeToFirstByte:0,firstByteToFCP:t.value,loadState:i(f())};if(t.entries.length){let r;const a=t.entries.at(-1);let s=0;if((!t.navigationId||t.navigationId===n)&&(r=e(),r)){const t=r.responseStart,n=r.activationStart||0;s=Math.max(0,t-n)}r&&(o={timeToFirstByte:s,firstByteToFCP:t.value-s,loadState:i(t.entries[0].startTime),navigationEntry:r,fcpEntry:a})}return Object.assign(t,{attribution:o})})(n))},n)},t.onINP=(t,n={})=>{const e=s(n=Object.assign({},n),A);let o=[],a=[],c=0;const f=new WeakMap,d=new WeakMap;let h=!1;const g=()=>{h||(W(v),h=!0)},v=()=>{const t=new Set(e.u.map(t=>f.get(t.entries[0]))),n=a.length-10;a=a.filter((e,i)=>i>=n||t.has(e));const i=new Set;for(const t of a){const n=p(t.startTime,t.processingEnd);for(const t of n)i.add(t)}o=o.filter(t=>t.startTime>c||i.has(t)),h=!1};e.v=t=>{const e=t.startTime+t.duration;let i;c=Math.max(c,t.processingEnd);for(let o=a.length-1;o>=0;o--){const r=a[o];if(Math.abs(e-r.renderTime)<=8){i=r,i.startTime=Math.min(t.startTime,i.startTime),i.processingStart=Math.min(t.processingStart,i.processingStart),i.processingEnd=Math.max(t.processingEnd,i.processingEnd),!1!==n.includeProcessedEventEntries&&i.entries.push(t);break}}i||(i={startTime:t.startTime,processingStart:t.processingStart,processingEnd:t.processingEnd,renderTime:e,entries:!1!==n.includeProcessedEventEntries?[t]:[]},a.push(i)),(t.interactionId||"first-input"===t.entryType)&&f.set(t,i),g()},e.p=t=>{if(!d.get(t)){const e=t.entries.find(t=>t.target)?.target;if(e){const i=n.generateTarget?.(e)??r(e);d.set(t,i)}else{const n=t.entries.find(t=>t.targetSelector)?.targetSelector;n&&d.set(t,n)}}};const p=(t,n)=>{const e=[];for(const i of o)if(!(i.startTime+i.duration<t)){if(i.startTime>n)break;e.push(i)}return e},m=t=>{if(0===t.entries.length&&"soft-navigation"===t.navigationType){const n=t.navigationStartTime;if(n){const e={interactionTarget:"",interactionType:"pointer",interactionTime:n,nextPaintTime:n+t.value,processedEventEntries:[],longAnimationFrameEntries:[],inputDelay:0,processingDuration:0,presentationDelay:t.value,loadState:i(n),longestScript:void 0,totalScriptDuration:void 0,totalStyleAndLayoutDuration:void 0,totalPaintDuration:void 0,totalUnattributedDuration:void 0};return Object.assign(t,{attribution:e})}}const n=t.entries[0],o=f.get(n),r=n.processingStart,a=Math.max(n.startTime+n.duration,r),s=Math.min(o.processingEnd,a),c=o.entries.sort((t,n)=>t.processingStart-n.processingStart),l=p(n.startTime,s),u=e.h.get(n.interactionId),h={interactionTarget:d.get(u),interactionType:n.name.startsWith("key")?"keyboard":"pointer",interactionTime:n.startTime,nextPaintTime:a,processedEventEntries:c,longAnimationFrameEntries:l,inputDelay:r-n.startTime,processingDuration:s-r,presentationDelay:a-s,loadState:i(n.startTime),longestScript:void 0,totalScriptDuration:void 0,totalStyleAndLayoutDuration:void 0,totalPaintDuration:void 0,totalUnattributedDuration:void 0};return(t=>{if(!t.longAnimationFrameEntries?.length)return;const n=t.interactionTime,e=t.inputDelay,i=t.processingDuration;let o,r,a=0,s=0,c=0,f=0;for(const c of t.longAnimationFrameEntries){s=s+c.startTime+c.duration-c.styleAndLayoutStart;for(const t of c.scripts){const c=t.startTime+t.duration;if(c<n)continue;const l=c-Math.max(n,t.startTime),u=t.duration?l/t.duration*t.forcedStyleAndLayoutDuration:0;a+=l-u,s+=u,l>f&&(r=t.startTime<n+e?"input-delay":t.startTime>=n+e+i?"presentation-delay":"processing-duration",o=t,f=l)}}const l=t.longAnimationFrameEntries.at(-1),u=l?l.startTime+l.duration:0;u>=n+e+i&&(c=t.nextPaintTime-u),o&&r&&(t.longestScript={entry:o,subpart:r,intersectingDuration:f}),t.totalScriptDuration=a,t.totalStyleAndLayoutDuration=s,t.totalPaintDuration=c,t.totalUnattributedDuration=t.nextPaintTime-n-a-s-c})(h),Object.assign(t,{attribution:h})};M("long-animation-frame",t=>{o=o.concat(t),g()},n),((t,n={})=>{if(!globalThis.PerformanceEventTiming||!("interactionId"in PerformanceEventTiming.prototype))return;const e=y();E(()=>{B();let i,o=b("INP");const r=s(n,A),a=(e,a,s,c,f)=>{r.m(),o=b("INP",-1,e,a,s,c,f),i=u(t,o,q,n.reportAllChanges)},c=()=>{const t=r.T(o.navigationType);t&&t.M!==o.value&&(o.value=t.M,o.entries=t.entries,i())},f=t=>{d(h?.takeRecords()),c(),i(!0),a(t.interactionId,"soft-navigation",t.navigationId,t.name,t.startTime)},d=t=>{W(()=>{if(t.some(t=>t.interactionId)){for(const n of t)"largestInteractionContentfulPaint"in n?f(n):r.l(n);c()}})},h=M("event",d,{...n,durationThreshold:n.durationThreshold??40});i=u(t,o,q,n.reportAllChanges),h&&(e.onHidden(()=>{d(h.takeRecords()),"soft-navigation"===o.navigationType&&c(),i(!0)}),l(()=>{a(o.interactionId,"back-forward-cache",o.navigationId,o.navigationURL,o.navigationStartTime)}))})})(n=>{t(m(n))},n)},t.onLCP=(t,n={})=>{const i=s(n=Object.assign({},n),U),o=new WeakMap;i.v=t=>{const e=t.element;if(e){const i=n.generateTarget?.(e)??r(e);o.set(t,i)}else t.id&&o.set(t,`#${t.id}`)};((t,n={})=>{let e=!1;const i=T(n);E(()=>{let o,r=y(),a=b("LCP");const c=s(n,U),f=(i,s,c,f,l)=>{a=b("LCP",0,s,i,c,f,l),o=u(t,a,x,n.reportAllChanges),e=!1,"soft-navigation"===i&&(r=y(!0))},g=t=>{v(p.takeRecords()),e||o(!0),f("soft-navigation",t.interactionId,t.navigationId,t.name,t.startTime);const n=t.getLargestInteractionContentfulPaint?.()||t.largestInteractionContentfulPaint;n&&v([n])},v=t=>{n.reportAllChanges||i||(t=t.slice(-1));for(const n of t){if(!n)continue;if("largestInteractionContentfulPaint"in n){g(n);continue}let t=0,e=[];if(n instanceof LargestContentfulPaint)t=Math.max(n.startTime-h(),0),c.l(n),e=[n];else{if(!a.navigationId)continue;if("interactionId"in n&&n.interactionId!=a.interactionId)continue;const i=n?.largestContentfulPaint?.renderTime||n.renderTime||0;t=Math.max(i-n.startTime,0),n.largestContentfulPaint&&c.l(n.largestContentfulPaint),n.largestContentfulPaint&&(e=[n.largestContentfulPaint])}n.startTime<r.firstHiddenTime&&(a.value=t,a.entries=e,o())}},p=M("largest-contentful-paint",v,n);if(p){o=u(t,a,x,n.reportAllChanges);const r=t=>{t.isTrusted&&!e&&W(()=>{e||(v(p.takeRecords()),i||(p.disconnect(),removeEventListener(t.type,r)),e=!0,o(!0))})};for(const t of["keydown","click","visibilitychange"])addEventListener(t,r,{capture:!0});l(i=>{f("back-forward-cache",a.interactionId,a.navigationId,a.navigationURL,a.navigationStartTime),o=u(t,a,x,n.reportAllChanges),d(()=>{a.value=performance.now()-i.timeStamp,e=!0,o(!0)})})}})})(n=>{t((t=>{const n=e()?.navigationId||0;let i={timeToFirstByte:0,resourceLoadDelay:0,resourceLoadDuration:0,elementRenderDelay:t.value};if(t.entries.length){const r=t.entries.at(-1),a=r.url&&performance.getEntriesByType("resource").find(t=>t.name===r.url);let s;i.target=o.get(r),i.lcpEntry=r,r.url&&(i.url=r.url),a&&(i.lcpResourceEntry=a);let c=0,f=0;if(t.navigationId&&t.navigationId!==n?c=t.navigationStartTime||0:(s=e(),c=s&&s.activationStart?s.activationStart:0,f=s&&s.responseStart?s.responseStart:0),s){const n=Math.max(0,f-c),e=Math.max(n,a?(a.requestStart||a.startTime)-c:0),o=Math.min(t.value,Math.max(e-c,a?a.responseEnd-c:0,0));i={...i,timeToFirstByte:n,resourceLoadDelay:e-n,resourceLoadDuration:o-e,elementRenderDelay:t.value-o,navigationEntry:s}}}return Object.assign(t,{attribution:i})})(n))},n)},t.onTTFB=(t,n={})=>{((t,n={})=>{const i=T(n=n||{});let o=b("TTFB"),r=u(t,o,N,n.reportAllChanges);R(()=>{const a=e();if(a){const e=a.responseStart;o.value=Math.max(e-h(),0),o.entries=[a],r(!0),l(()=>{o=b("TTFB",0,o.interactionId,"back-forward-cache",o.navigationId,o.navigationURL,o.navigationStartTime),r=u(t,o,N,n.reportAllChanges),r(!0)}),i&&M("soft-navigation",e=>{e.forEach(e=>{e.navigationId&&(o=b("TTFB",0,e.interactionId,"soft-navigation",e.navigationId,e.name,e.startTime),o.entries=[e],r=u(t,o,N,n.reportAllChanges),r(!0))})},n)}})})(n=>{t((t=>{let n={waitingDuration:0,cacheDuration:0,dnsDuration:0,connectionDuration:0,requestDuration:0};if(t.entries.length){const e=t.entries[0],i=e.activationStart||0,o=Math.max((e.workerStart||e.fetchStart||0)-i,0),r=Math.max(e.domainLookupStart-i||0,0),a=Math.max(e.connectStart-i||0,0),s=Math.max(e.connectEnd-i||0,0);n={waitingDuration:o,cacheDuration:r-o,dnsDuration:a-r,connectionDuration:s-a,requestDuration:t.value-s,navigationEntry:e}}return Object.assign(t,{attribution:n})})(n))},n)},t}({}); |
@@ -1,1 +0,1 @@ | ||
| class t{t;o=0;i=[];u(t){if(t.hadRecentInput)return;const e=this.i[0],n=this.i.at(-1);this.o&&e&&n&&t.startTime-n.startTime<1e3&&t.startTime-e.startTime<5e3?(this.o+=t.value,this.i.push(t)):(this.o=t.value,this.i=[t]),this.t?.(t)}}const e=()=>{const t=performance.getEntriesByType("navigation")[0];if(t&&t.responseStart>0&&t.responseStart<performance.now())return t},n=t=>{if("loading"===document.readyState)return"loading";const n=e();if(n){if(t<n.domInteractive)return"loading";if(0===n.domContentLoadedEventStart||t<n.domContentLoadedEventStart)return"dom-interactive";if(0===n.domComplete||t<n.domComplete)return"dom-content-loaded"}return"complete"},o=t=>{const e=t.nodeName;return 1===t.nodeType?e.toLowerCase():e.toUpperCase().replace(/^#/,"")},i=t=>{let e="";try{for(;9!==t?.nodeType;){const n=t,i=n.id?"#"+n.id:[o(n),...Array.from(n.classList).sort()].join(".");if(e.length+i.length>99)return e||i;if(e=e?i+">"+e:i,n.id)break;t=n.parentNode}}catch{}return e},r=new WeakMap;function s(t,e){return r.get(t)||r.set(t,new e),r.get(t)}let a=-1;const c=()=>a,u=t=>{addEventListener("pageshow",e=>{e.persisted&&(a=e.timeStamp,t(e))},!0)},f=(t,e,n,o)=>{let i,r;return s=>{e.value>=0&&(s||o)&&(r=e.value-(i??0),(r||void 0===i)&&(i=e.value,e.delta=r,e.rating=((t,e)=>t>e[1]?"poor":t>e[0]?"needs-improvement":"good")(e.value,n),t(e)))}},d=t=>{requestAnimationFrame(()=>requestAnimationFrame(t))},l=()=>e()?.activationStart??0,h=(t,n=-1)=>{const o=e();let i="navigate";c()>=0?i="back-forward-cache":o&&(document.prerendering||l()>0?i="prerender":document.wasDiscarded?i="restore":o.type&&(i=o.type.replace(/_/g,"-")));return{name:t,value:n,rating:"good",delta:0,entries:[],id:`v5-${Date.now()}-${Math.floor(8999999999999*Math.random())+1e12}`,navigationType:i}},m=(t,e,n={})=>{try{if(PerformanceObserver.supportedEntryTypes.includes(t)){const o=new PerformanceObserver(t=>{queueMicrotask(()=>{e(t.getEntries())})});return o.observe({type:t,buffered:!0,...n}),o}}catch{}},g=t=>{let e=!1;return()=>{e||(t(),e=!0)}};let p=-1;const y=new Set,v=()=>"hidden"!==document.visibilityState||document.prerendering?1/0:0,b=t=>{if("hidden"===document.visibilityState){if("visibilitychange"===t.type)for(const t of y)t();isFinite(p)||(p="visibilitychange"===t.type?t.timeStamp:0,removeEventListener("prerenderingchange",b,!0))}},M=()=>{if(p<0){const t=l(),e=document.prerendering?void 0:globalThis.performance.getEntriesByType("visibility-state").find(e=>"hidden"===e.name&&e.startTime>=t)?.startTime;p=e??v(),addEventListener("visibilitychange",b,!0),addEventListener("prerenderingchange",b,!0),u(()=>{setTimeout(()=>{p=v()})})}return{get firstHiddenTime(){return p},onHidden(t){y.add(t)}}},T=t=>{document.prerendering?addEventListener("prerenderingchange",t,!0):t()},E=[1800,3e3],D=(t,e={})=>{T(()=>{const n=M();let o,i=h("FCP");const r=m("paint",t=>{for(const e of t)"first-contentful-paint"===e.name&&(r.disconnect(),e.startTime<n.firstHiddenTime&&(i.value=Math.max(e.startTime-l(),0),i.entries.push(e),o(!0)))});r&&(o=f(t,i,E,e.reportAllChanges),u(n=>{i=h("FCP"),o=f(t,i,E,e.reportAllChanges),d(()=>{i.value=performance.now()-n.timeStamp,o(!0)})}))})},L=[.1,.25],S=t=>t.find(t=>1===t.node?.nodeType)||t[0],P=(e,o={})=>{const r=s(o=Object.assign({},o),t),a=new WeakMap;r.t=t=>{if(t?.sources?.length){const e=S(t.sources),n=e?.node;if(n){const t=o.generateTarget?.(n)??i(n);a.set(e,t)}}};((e,n={})=>{const o=M();D(g(()=>{let i,r=h("CLS",0);const a=s(n,t),c=t=>{for(const e of t)a.u(e);a.o>r.value&&(r.value=a.o,r.entries=a.i,i())},l=m("layout-shift",c);l&&(i=f(e,r,L,n.reportAllChanges),o.onHidden(()=>{c(l.takeRecords()),i(!0)}),u(()=>{a.o=0,r=h("CLS",0),i=f(e,r,L,n.reportAllChanges),d(i)}),setTimeout(i))}))})(t=>{e((t=>{let e={};if(t.entries.length){const o=t.entries.reduce((t,e)=>t.value>e.value?t:e);if(o?.sources?.length){const t=S(o.sources);t&&(e={largestShiftTarget:a.get(t),largestShiftTime:o.startTime,largestShiftValue:o.value,largestShiftSource:t,largestShiftEntry:o,loadState:n(o.startTime)})}}return Object.assign(t,{attribution:e})})(t))},o)},w=(t,o={})=>{D(o=>{t((t=>{let o={timeToFirstByte:0,firstByteToFCP:t.value,loadState:n(c())};if(t.entries.length){const i=e(),r=t.entries.at(-1);if(i){const e=i.activationStart||0,s=Math.max(0,i.responseStart-e);o={timeToFirstByte:s,firstByteToFCP:t.value-s,loadState:n(t.entries[0].startTime),navigationEntry:i,fcpEntry:r}}}return Object.assign(t,{attribution:o})})(o))},o)};let _=0,k=1/0,F=0;const B=t=>{for(const e of t)e.interactionId&&(k=Math.min(k,e.interactionId),F=Math.max(F,e.interactionId),_=F?(F-k)/7+1:0)};let C;const O=()=>C?_:performance.interactionCount??0,j=()=>{"interactionCount"in performance||C||(C=m("event",B,{durationThreshold:0}))};let I=0;class A{l=[];h=new Map;m;p;v(){I=O(),this.l.length=0,this.h.clear()}M(){const t=Math.min(this.l.length-1,Math.floor((O()-I)/50));return this.l[t]}u(t){if(this.m?.(t),!t.interactionId&&"first-input"!==t.entryType)return;const e=this.l.at(-1);let n=this.h.get(t.interactionId);if(n||this.l.length<10||t.duration>e.T){if(n?t.duration>n.T?(n.entries=[t],n.T=t.duration):t.duration===n.T&&t.startTime===n.entries[0].startTime&&n.entries.push(t):(n={id:t.interactionId,entries:[t],T:t.duration},this.h.set(n.id,n),this.l.push(n)),this.l.sort((t,e)=>e.T-t.T),this.l.length>10){const t=this.l.splice(10);for(const e of t)this.h.delete(e.id)}this.p?.(n)}}}const q=t=>{const e=globalThis.requestIdleCallback||setTimeout,n=globalThis.cancelIdleCallback||clearTimeout;if("hidden"===document.visibilityState)t();else{const o=g(t);let i=-1;const r=()=>{n(i),o()};addEventListener("visibilitychange",r,{once:!0,capture:!0}),i=e(()=>{removeEventListener("visibilitychange",r,{capture:!0}),o()})}},W=[200,500],x=(t,e={})=>{const o=s(e=Object.assign({},e),A);let r=[],a=[],c=0;const d=new WeakMap,l=new WeakMap;let g=!1;const p=()=>{g||(q(y),g=!0)},y=()=>{const t=new Set(o.l.map(t=>d.get(t.entries[0]))),e=a.length-10;a=a.filter((n,o)=>o>=e||t.has(n));const n=new Set;for(const t of a){const e=v(t.startTime,t.processingEnd);for(const t of e)n.add(t)}r=r.filter(t=>t.startTime>c||n.has(t)),g=!1};o.m=t=>{const n=t.startTime+t.duration;let o;c=Math.max(c,t.processingEnd);for(let i=a.length-1;i>=0;i--){const r=a[i];if(Math.abs(n-r.renderTime)<=8){o=r,o.startTime=Math.min(t.startTime,o.startTime),o.processingStart=Math.min(t.processingStart,o.processingStart),o.processingEnd=Math.max(t.processingEnd,o.processingEnd),!1!==e.includeProcessedEventEntries&&o.entries.push(t);break}}o||(o={startTime:t.startTime,processingStart:t.processingStart,processingEnd:t.processingEnd,renderTime:n,entries:!1!==e.includeProcessedEventEntries?[t]:[]},a.push(o)),(t.interactionId||"first-input"===t.entryType)&&d.set(t,o),p()},o.p=t=>{if(!l.get(t)){const n=t.entries[0].target;if(n){const o=e.generateTarget?.(n)??i(n);l.set(t,o)}}};const v=(t,e)=>{const n=[];for(const o of r)if(!(o.startTime+o.duration<t)){if(o.startTime>e)break;n.push(o)}return n},b=t=>{const e=t.entries[0],i=d.get(e),r=e.processingStart,s=Math.max(e.startTime+e.duration,r),a=Math.min(i.processingEnd,s),c=i.entries.sort((t,e)=>t.processingStart-e.processingStart),u=v(e.startTime,a),f=o.h.get(e.interactionId),h={interactionTarget:l.get(f),interactionType:e.name.startsWith("key")?"keyboard":"pointer",interactionTime:e.startTime,nextPaintTime:s,processedEventEntries:c,longAnimationFrameEntries:u,inputDelay:r-e.startTime,processingDuration:a-r,presentationDelay:s-a,loadState:n(e.startTime),longestScript:void 0,totalScriptDuration:void 0,totalStyleAndLayoutDuration:void 0,totalPaintDuration:void 0,totalUnattributedDuration:void 0};return(t=>{if(!t.longAnimationFrameEntries?.length)return;const e=t.interactionTime,n=t.inputDelay,o=t.processingDuration;let i,r,s=0,a=0,c=0,u=0;for(const c of t.longAnimationFrameEntries){a=a+c.startTime+c.duration-c.styleAndLayoutStart;for(const t of c.scripts){const c=t.startTime+t.duration;if(c<e)continue;const f=c-Math.max(e,t.startTime),d=t.duration?f/t.duration*t.forcedStyleAndLayoutDuration:0;s+=f-d,a+=d,f>u&&(r=t.startTime<e+n?"input-delay":t.startTime>=e+n+o?"presentation-delay":"processing-duration",i=t,u=f)}}const f=t.longAnimationFrameEntries.at(-1),d=f?f.startTime+f.duration:0;d>=e+n+o&&(c=t.nextPaintTime-d),i&&r&&(t.longestScript={entry:i,subpart:r,intersectingDuration:u}),t.totalScriptDuration=s,t.totalStyleAndLayoutDuration=a,t.totalPaintDuration=c,t.totalUnattributedDuration=t.nextPaintTime-e-s-a-c})(h),Object.assign(t,{attribution:h})};m("long-animation-frame",t=>{r=r.concat(t),p()}),((t,e={})=>{if(!globalThis.PerformanceEventTiming||!("interactionId"in PerformanceEventTiming.prototype))return;const n=M();T(()=>{j();let o,i=h("INP");const r=s(e,A),a=t=>{q(()=>{for(const e of t)r.u(e);const e=r.M();e&&e.T!==i.value&&(i.value=e.T,i.entries=e.entries,o())})},c=m("event",a,{durationThreshold:e.durationThreshold??40});o=f(t,i,W,e.reportAllChanges),c&&(c.observe({type:"first-input",buffered:!0}),n.onHidden(()=>{a(c.takeRecords()),o(!0)}),u(()=>{r.v(),i=h("INP"),o=f(t,i,W,e.reportAllChanges)}))})})(e=>{t(b(e))},e)};class N{m;u(t){this.m?.(t)}}const $=[2500,4e3],H=(t,n={})=>{const o=s(n=Object.assign({},n),N),r=new WeakMap;o.m=t=>{const e=t.element;if(e){const o=n.generateTarget?.(e)??i(e);r.set(t,o)}else t.id&&r.set(t,`#${t.id}`)};((t,e={})=>{T(()=>{const n=M();let o,i=h("LCP");const r=s(e,N),a=t=>{e.reportAllChanges||(t=t.slice(-1));for(const e of t)r.u(e),e.startTime<n.firstHiddenTime&&(i.value=Math.max(e.startTime-l(),0),i.entries=[e],o())},c=m("largest-contentful-paint",a);if(c){o=f(t,i,$,e.reportAllChanges);const n=g(()=>{a(c.takeRecords()),c.disconnect(),o(!0)}),r=t=>{t.isTrusted&&(q(n),removeEventListener(t.type,r,{capture:!0}))};for(const t of["keydown","click","visibilitychange"])addEventListener(t,r,{capture:!0});u(n=>{i=h("LCP"),o=f(t,i,$,e.reportAllChanges),d(()=>{i.value=performance.now()-n.timeStamp,o(!0)})})}})})(n=>{t((t=>{let n={timeToFirstByte:0,resourceLoadDelay:0,resourceLoadDuration:0,elementRenderDelay:t.value};if(t.entries.length){const o=t.entries.at(-1),i=o.url&&performance.getEntriesByType("resource").find(t=>t.name===o.url);n.target=r.get(o),n.lcpEntry=o,o.url&&(n.url=o.url),i&&(n.lcpResourceEntry=i);const s=e();if(s){const e=s.activationStart||0,o=Math.max(0,s.responseStart-e),r=Math.max(o,i?(i.requestStart||i.startTime)-e:0),a=Math.min(t.value,Math.max(r,i?i.responseEnd-e:0));n={...n,timeToFirstByte:o,resourceLoadDelay:r-o,resourceLoadDuration:a-r,elementRenderDelay:t.value-a,navigationEntry:s}}}return Object.assign(t,{attribution:n})})(n))},n)},R=[800,1800],U=t=>{document.prerendering?T(()=>U(t)):"complete"!==document.readyState?addEventListener("load",()=>U(t),!0):setTimeout(t)},V=(t,n={})=>{((t,n={})=>{let o=h("TTFB"),i=f(t,o,R,n.reportAllChanges);U(()=>{const r=e();r&&(o.value=Math.max(r.responseStart-l(),0),o.entries=[r],i(!0),u(()=>{o=h("TTFB",0),i=f(t,o,R,n.reportAllChanges),i(!0)}))})})(e=>{t((t=>{let e={waitingDuration:0,cacheDuration:0,dnsDuration:0,connectionDuration:0,requestDuration:0};if(t.entries.length){const n=t.entries[0],o=n.activationStart||0,i=Math.max((n.workerStart||n.fetchStart)-o,0),r=Math.max(n.domainLookupStart-o,0),s=Math.max(n.connectStart-o,0),a=Math.max(n.connectEnd-o,0);e={waitingDuration:i,cacheDuration:r-i,dnsDuration:s-r,connectionDuration:a-s,requestDuration:t.value-a,navigationEntry:n}}return Object.assign(t,{attribution:e})})(e))},n)};export{L as CLSThresholds,E as FCPThresholds,W as INPThresholds,$ as LCPThresholds,R as TTFBThresholds,P as onCLS,w as onFCP,x as onINP,H as onLCP,V as onTTFB}; | ||
| class t{t;i=0;o=[];l(t){if(t.hadRecentInput)return;const n=this.o[0],e=this.o.at(-1);this.i&&n&&e&&t.startTime-e.startTime<1e3&&t.startTime-n.startTime<5e3?(this.i+=t.value,this.o.push(t)):(this.i=t.value,this.o=[t]),this.t?.(t)}}const n=()=>{const t=performance.getEntriesByType("navigation")[0];if(t&&t.responseStart>0&&t.responseStart<performance.now())return t},e=t=>{if("loading"===document.readyState)return"loading";const e=n();if(e){if(t<e.domInteractive)return"loading";if(0===e.domContentLoadedEventStart||t<e.domContentLoadedEventStart)return"dom-interactive";if(0===e.domComplete||t<e.domComplete)return"dom-content-loaded"}return"complete"},i=t=>{const n=t.nodeName;return 1===t.nodeType?n.toLowerCase():n.toUpperCase().replace(/^#/,"")},o=t=>{let n="";try{for(;9!==t?.nodeType;){const e=t,o=e.id?"#"+e.id:[i(e),...Array.from(e.classList).sort()].join(".");if(n.length+o.length>99)return n||o;if(n=n?o+">"+n:o,e.id)break;t=e.parentNode}}catch{}return n},r=new WeakMap;function a(t,n){let e=r.get(n);return e||(e=new WeakMap,r.set(n,e)),e.get(t)||e.set(t,new n),e.get(t)}let s=-1;const c=()=>s,f=t=>{addEventListener("pageshow",n=>{n.persisted&&(s=n.timeStamp,t(n))},!0)},l=(t,n,e,i)=>{let o,r;return a=>{n.value>=0&&(a||i)&&(r=n.value-(o??0),(r||void 0===o)&&(o=n.value,n.delta=r,n.rating=((t,n)=>t>n[1]?"poor":t>n[0]?"needs-improvement":"good")(n.value,e),t(n)))}},u=t=>{requestAnimationFrame(()=>requestAnimationFrame(t))},d=()=>n()?.activationStart??0;let h=-1;const g=new Set,v=()=>"hidden"!==document.visibilityState||document.prerendering?1/0:0,p=t=>{if("hidden"===document.visibilityState){if("visibilitychange"===t.type)for(const t of g)t();isFinite(h)||(h="visibilitychange"===t.type?t.timeStamp:0,removeEventListener("prerenderingchange",p,!0))}},m=(t=!1)=>{if(t&&(h=1/0),h<0){const t=d(),n=document.prerendering?void 0:globalThis.performance.getEntriesByType("visibility-state").find(n=>"hidden"===n.name&&n.startTime>=t)?.startTime;h=n??v(),addEventListener("visibilitychange",p,!0),addEventListener("prerenderingchange",p,!0),f(()=>{setTimeout(()=>{h=v()})})}return{get firstHiddenTime(){return h},onHidden(t){g.add(t)}}},y=(t,e=-1,i,o,r=0,a,s)=>{const f=n()?.navigationId||0,l=n();let u="navigate";o?u=o:c()>=0?u="back-forward-cache":l&&(document.prerendering||d()>0?u="prerender":document.wasDiscarded?u="restore":l.type&&(u=l.type.replace(/_/g,"-")));return{name:t,value:e,rating:"good",delta:0,entries:[],id:`v5-${Date.now()}-${Math.floor(8999999999999*Math.random())+1e12}`,navigationType:u,navigationId:r||f,interactionId:i,navigationURL:a||l?.name,navigationStartTime:s||0}},b=t=>PerformanceObserver.supportedEntryTypes.includes("soft-navigation")&&t&&t.reportSoftNavs,T=(t,n,e={})=>{const i=[t],o=b(e);"event"===t&&i.push("first-input"),o&&("largest-contentful-paint"!==t&&"event"!==t&&"layout-shift"!==t||i.push("soft-navigation"),"largest-contentful-paint"===t&&i.push("interaction-contentful-paint"));try{if(PerformanceObserver.supportedEntryTypes.includes(t)){const t=new PerformanceObserver(t=>{queueMicrotask(()=>{const e=t.getEntries();e.sort((t,n)=>t.startTime+t.duration-(n.startTime+n.duration)),n(e)})});for(const n of i)t.observe(Object.assign({type:n,buffered:!0,...e},e||{}));return t}}catch{}},M=t=>{let n=!1;return()=>{n||(t(),n=!0)}},D=t=>{document.prerendering?addEventListener("prerenderingchange",t,!0):t()},E=[1800,3e3],P=(t,n={})=>{const e=b(n=n||{});D(()=>{const i=m();let o,r=y("FCP");const a=t=>{for(const n of t)"first-contentful-paint"===n.name&&(s.disconnect(),n.startTime<i.firstHiddenTime&&(r.value=Math.max(n.startTime-d(),0),r.entries.push(n),r.navigationId=n.navigationId||0,o(!0)))},s=T("paint",a,n);if(s&&(o=l(t,r,E,n.reportAllChanges),f(e=>{r=y("FCP",0,r.interactionId,"back-forward-cache",r.navigationId,r.navigationURL,r.navigationStartTime),o=l(t,r,E,n.reportAllChanges),u(()=>{r.value=performance.now()-e.timeStamp,o(!0)})})),e){T("soft-navigation",e=>{e.forEach(e=>{a(s.takeRecords());const i=(e.presentationTime||e.paintTime||0)-e.startTime;r=y("FCP",i,e.interactionId,"soft-navigation",e.navigationId,e.name,e.startTime),o=l(t,r,E,n.reportAllChanges),o(!0)})},n)}})},S=[.1,.25],L=t=>t.find(t=>1===t.node?.nodeType)||t[0],w=(n,i={})=>{const r=a(i=Object.assign({},i),t),s=new WeakMap;r.t=t=>{if(t?.sources?.length){const n=L(t.sources),e=n?.node;if(e){const t=i.generateTarget?.(e)??o(e);s.set(n,t)}}};((n,e={})=>{const i=m();P(M(()=>{let o,r=y("CLS",0);const s=a(e,t),c=(t,i,a,c,f)=>{r=y("CLS",0,t,i,a,c,f),s.i=0,o=l(n,r,S,e.reportAllChanges)},d=t=>{h(g?.takeRecords()),o(!0),c(t.interactionId,"soft-navigation",t.navigationId,t.name,t.startTime)},h=t=>{for(const n of t)"largestInteractionContentfulPaint"in n?d(n):s.l(n);s.i>r.value&&(r.value=s.i,r.entries=s.o,o())},g=T("layout-shift",h,e);g&&(o=l(n,r,S,e.reportAllChanges),i.onHidden(()=>{h(g.takeRecords()),o(!0)}),f(()=>{c(r.interactionId,"back-forward-cache",r.navigationId,r.navigationURL,r.navigationStartTime),u(o)}),setTimeout(o))}))})(t=>{n((t=>{let n={};if(t.entries.length){const i=t.entries.reduce((t,n)=>t.value>n.value?t:n);if(i?.sources?.length){const t=L(i.sources);t&&(n={largestShiftTarget:s.get(t),largestShiftTime:i.startTime,largestShiftValue:i.value,largestShiftSource:t,largestShiftEntry:i,loadState:e(i.startTime)})}}return Object.assign(t,{attribution:n})})(t))},i)},k=(t,i={})=>{P(i=>{t((t=>{const i=n()?.navigationId||0;let o={timeToFirstByte:0,firstByteToFCP:t.value,loadState:e(c())};if(t.entries.length){let r;const a=t.entries.at(-1);let s=0;if((!t.navigationId||t.navigationId===i)&&(r=n(),r)){const t=r.responseStart,n=r.activationStart||0;s=Math.max(0,t-n)}r&&(o={timeToFirstByte:s,firstByteToFCP:t.value-s,loadState:e(t.entries[0].startTime),navigationEntry:r,fcpEntry:a})}return Object.assign(t,{attribution:o})})(i))},i)};let F=0,_=1/0,C=0;const I=t=>{for(const n of t)n.interactionId&&(_=Math.min(_,n.interactionId),C=Math.max(C,n.interactionId),F=C?(C-_)/7+1:0)};let O;const B=()=>O?F:performance.interactionCount??0,j=()=>{"interactionCount"in performance||O||(O=T("event",I,{durationThreshold:0}))};let A=0;class W{u=[];h=new Map;v;p;m(){A=B(),this.u.length=0,this.h.clear()}T(t){const n=B()-A,e=Math.min(this.u.length-1,Math.floor(n/50));return n&&-1===e&&"soft-navigation"===t?{M:8,id:-1,entries:[]}:this.u[e]}l(t){if(this.v?.(t),!t.interactionId&&"first-input"!==t.entryType)return;const n=this.u.at(-1);let e=this.h.get(t.interactionId);if(e||this.u.length<10||t.duration>n.M){if(e?t.duration>e.M?(e.entries=[t],e.M=t.duration):t.duration===e.M&&t.startTime===e.entries[0].startTime&&e.entries.push(t):(e={id:t.interactionId,entries:[t],M:t.duration},this.h.set(e.id,e),this.u.push(e)),this.u.sort((t,n)=>n.M-t.M),this.u.length>10){const t=this.u.splice(10);for(const n of t)this.h.delete(n.id)}this.p?.(e)}}}const q=t=>{const n=globalThis.requestIdleCallback||setTimeout,e=globalThis.cancelIdleCallback||clearTimeout;if("hidden"===document.visibilityState)t();else{const i=M(t);let o=-1;const r=()=>{e(o),i()};addEventListener("visibilitychange",r,{once:!0,capture:!0}),o=n(()=>{removeEventListener("visibilitychange",r,{capture:!0}),i()})}},x=[200,500],U=(t,n={})=>{const i=a(n=Object.assign({},n),W);let r=[],s=[],c=0;const u=new WeakMap,d=new WeakMap;let h=!1;const g=()=>{h||(q(v),h=!0)},v=()=>{const t=new Set(i.u.map(t=>u.get(t.entries[0]))),n=s.length-10;s=s.filter((e,i)=>i>=n||t.has(e));const e=new Set;for(const t of s){const n=p(t.startTime,t.processingEnd);for(const t of n)e.add(t)}r=r.filter(t=>t.startTime>c||e.has(t)),h=!1};i.v=t=>{const e=t.startTime+t.duration;let i;c=Math.max(c,t.processingEnd);for(let o=s.length-1;o>=0;o--){const r=s[o];if(Math.abs(e-r.renderTime)<=8){i=r,i.startTime=Math.min(t.startTime,i.startTime),i.processingStart=Math.min(t.processingStart,i.processingStart),i.processingEnd=Math.max(t.processingEnd,i.processingEnd),!1!==n.includeProcessedEventEntries&&i.entries.push(t);break}}i||(i={startTime:t.startTime,processingStart:t.processingStart,processingEnd:t.processingEnd,renderTime:e,entries:!1!==n.includeProcessedEventEntries?[t]:[]},s.push(i)),(t.interactionId||"first-input"===t.entryType)&&u.set(t,i),g()},i.p=t=>{if(!d.get(t)){const e=t.entries.find(t=>t.target)?.target;if(e){const i=n.generateTarget?.(e)??o(e);d.set(t,i)}else{const n=t.entries.find(t=>t.targetSelector)?.targetSelector;n&&d.set(t,n)}}};const p=(t,n)=>{const e=[];for(const i of r)if(!(i.startTime+i.duration<t)){if(i.startTime>n)break;e.push(i)}return e},b=t=>{if(0===t.entries.length&&"soft-navigation"===t.navigationType){const n=t.navigationStartTime;if(n){const i={interactionTarget:"",interactionType:"pointer",interactionTime:n,nextPaintTime:n+t.value,processedEventEntries:[],longAnimationFrameEntries:[],inputDelay:0,processingDuration:0,presentationDelay:t.value,loadState:e(n),longestScript:void 0,totalScriptDuration:void 0,totalStyleAndLayoutDuration:void 0,totalPaintDuration:void 0,totalUnattributedDuration:void 0};return Object.assign(t,{attribution:i})}}const n=t.entries[0],o=u.get(n),r=n.processingStart,a=Math.max(n.startTime+n.duration,r),s=Math.min(o.processingEnd,a),c=o.entries.sort((t,n)=>t.processingStart-n.processingStart),f=p(n.startTime,s),l=i.h.get(n.interactionId),h={interactionTarget:d.get(l),interactionType:n.name.startsWith("key")?"keyboard":"pointer",interactionTime:n.startTime,nextPaintTime:a,processedEventEntries:c,longAnimationFrameEntries:f,inputDelay:r-n.startTime,processingDuration:s-r,presentationDelay:a-s,loadState:e(n.startTime),longestScript:void 0,totalScriptDuration:void 0,totalStyleAndLayoutDuration:void 0,totalPaintDuration:void 0,totalUnattributedDuration:void 0};return(t=>{if(!t.longAnimationFrameEntries?.length)return;const n=t.interactionTime,e=t.inputDelay,i=t.processingDuration;let o,r,a=0,s=0,c=0,f=0;for(const c of t.longAnimationFrameEntries){s=s+c.startTime+c.duration-c.styleAndLayoutStart;for(const t of c.scripts){const c=t.startTime+t.duration;if(c<n)continue;const l=c-Math.max(n,t.startTime),u=t.duration?l/t.duration*t.forcedStyleAndLayoutDuration:0;a+=l-u,s+=u,l>f&&(r=t.startTime<n+e?"input-delay":t.startTime>=n+e+i?"presentation-delay":"processing-duration",o=t,f=l)}}const l=t.longAnimationFrameEntries.at(-1),u=l?l.startTime+l.duration:0;u>=n+e+i&&(c=t.nextPaintTime-u),o&&r&&(t.longestScript={entry:o,subpart:r,intersectingDuration:f}),t.totalScriptDuration=a,t.totalStyleAndLayoutDuration=s,t.totalPaintDuration=c,t.totalUnattributedDuration=t.nextPaintTime-n-a-s-c})(h),Object.assign(t,{attribution:h})};T("long-animation-frame",t=>{r=r.concat(t),g()},n),((t,n={})=>{if(!globalThis.PerformanceEventTiming||!("interactionId"in PerformanceEventTiming.prototype))return;const e=m();D(()=>{j();let i,o=y("INP");const r=a(n,W),s=(e,a,s,c,f)=>{r.m(),o=y("INP",-1,e,a,s,c,f),i=l(t,o,x,n.reportAllChanges)},c=()=>{const t=r.T(o.navigationType);t&&t.M!==o.value&&(o.value=t.M,o.entries=t.entries,i())},u=t=>{d(h?.takeRecords()),c(),i(!0),s(t.interactionId,"soft-navigation",t.navigationId,t.name,t.startTime)},d=t=>{q(()=>{if(t.some(t=>t.interactionId)){for(const n of t)"largestInteractionContentfulPaint"in n?u(n):r.l(n);c()}})},h=T("event",d,{...n,durationThreshold:n.durationThreshold??40});i=l(t,o,x,n.reportAllChanges),h&&(e.onHidden(()=>{d(h.takeRecords()),"soft-navigation"===o.navigationType&&c(),i(!0)}),f(()=>{s(o.interactionId,"back-forward-cache",o.navigationId,o.navigationURL,o.navigationStartTime)}))})})(n=>{t(b(n))},n)};class N{v;l(t){this.v?.(t)}}const R=[2500,4e3],$=(t,e={})=>{const i=a(e=Object.assign({},e),N),r=new WeakMap;i.v=t=>{const n=t.element;if(n){const i=e.generateTarget?.(n)??o(n);r.set(t,i)}else t.id&&r.set(t,`#${t.id}`)};((t,n={})=>{let e=!1;const i=b(n);D(()=>{let o,r=m(),s=y("LCP");const c=a(n,N),h=(i,a,c,f,u)=>{s=y("LCP",0,a,i,c,f,u),o=l(t,s,R,n.reportAllChanges),e=!1,"soft-navigation"===i&&(r=m(!0))},g=t=>{v(p.takeRecords()),e||o(!0),h("soft-navigation",t.interactionId,t.navigationId,t.name,t.startTime);const n=t.getLargestInteractionContentfulPaint?.()||t.largestInteractionContentfulPaint;n&&v([n])},v=t=>{n.reportAllChanges||i||(t=t.slice(-1));for(const n of t){if(!n)continue;if("largestInteractionContentfulPaint"in n){g(n);continue}let t=0,e=[];if(n instanceof LargestContentfulPaint)t=Math.max(n.startTime-d(),0),c.l(n),e=[n];else{if(!s.navigationId)continue;if("interactionId"in n&&n.interactionId!=s.interactionId)continue;const i=n?.largestContentfulPaint?.renderTime||n.renderTime||0;t=Math.max(i-n.startTime,0),n.largestContentfulPaint&&c.l(n.largestContentfulPaint),n.largestContentfulPaint&&(e=[n.largestContentfulPaint])}n.startTime<r.firstHiddenTime&&(s.value=t,s.entries=e,o())}},p=T("largest-contentful-paint",v,n);if(p){o=l(t,s,R,n.reportAllChanges);const r=t=>{t.isTrusted&&!e&&q(()=>{e||(v(p.takeRecords()),i||(p.disconnect(),removeEventListener(t.type,r)),e=!0,o(!0))})};for(const t of["keydown","click","visibilitychange"])addEventListener(t,r,{capture:!0});f(i=>{h("back-forward-cache",s.interactionId,s.navigationId,s.navigationURL,s.navigationStartTime),o=l(t,s,R,n.reportAllChanges),u(()=>{s.value=performance.now()-i.timeStamp,e=!0,o(!0)})})}})})(e=>{t((t=>{const e=n()?.navigationId||0;let i={timeToFirstByte:0,resourceLoadDelay:0,resourceLoadDuration:0,elementRenderDelay:t.value};if(t.entries.length){const o=t.entries.at(-1),a=o.url&&performance.getEntriesByType("resource").find(t=>t.name===o.url);let s;i.target=r.get(o),i.lcpEntry=o,o.url&&(i.url=o.url),a&&(i.lcpResourceEntry=a);let c=0,f=0;if(t.navigationId&&t.navigationId!==e?c=t.navigationStartTime||0:(s=n(),c=s&&s.activationStart?s.activationStart:0,f=s&&s.responseStart?s.responseStart:0),s){const n=Math.max(0,f-c),e=Math.max(n,a?(a.requestStart||a.startTime)-c:0),o=Math.min(t.value,Math.max(e-c,a?a.responseEnd-c:0,0));i={...i,timeToFirstByte:n,resourceLoadDelay:e-n,resourceLoadDuration:o-e,elementRenderDelay:t.value-o,navigationEntry:s}}}return Object.assign(t,{attribution:i})})(e))},e)},H=[800,1800],V=t=>{document.prerendering?D(()=>V(t)):"complete"!==document.readyState?addEventListener("load",()=>V(t),!0):setTimeout(t)},z=(t,e={})=>{((t,e={})=>{const i=b(e=e||{});let o=y("TTFB"),r=l(t,o,H,e.reportAllChanges);V(()=>{const a=n();if(a){const n=a.responseStart;o.value=Math.max(n-d(),0),o.entries=[a],r(!0),f(()=>{o=y("TTFB",0,o.interactionId,"back-forward-cache",o.navigationId,o.navigationURL,o.navigationStartTime),r=l(t,o,H,e.reportAllChanges),r(!0)}),i&&T("soft-navigation",n=>{n.forEach(n=>{n.navigationId&&(o=y("TTFB",0,n.interactionId,"soft-navigation",n.navigationId,n.name,n.startTime),o.entries=[n],r=l(t,o,H,e.reportAllChanges),r(!0))})},e)}})})(n=>{t((t=>{let n={waitingDuration:0,cacheDuration:0,dnsDuration:0,connectionDuration:0,requestDuration:0};if(t.entries.length){const e=t.entries[0],i=e.activationStart||0,o=Math.max((e.workerStart||e.fetchStart||0)-i,0),r=Math.max(e.domainLookupStart-i||0,0),a=Math.max(e.connectStart-i||0,0),s=Math.max(e.connectEnd-i||0,0);n={waitingDuration:o,cacheDuration:r-o,dnsDuration:a-r,connectionDuration:s-a,requestDuration:t.value-s,navigationEntry:e}}return Object.assign(t,{attribution:n})})(n))},e)};export{S as CLSThresholds,E as FCPThresholds,x as INPThresholds,R as LCPThresholds,H as TTFBThresholds,w as onCLS,k as onFCP,U as onINP,$ as onLCP,z as onTTFB}; |
@@ -1,1 +0,1 @@ | ||
| !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).webVitals={})}(this,function(t){"use strict";class e{t;o=0;i=[];u(t){if(t.hadRecentInput)return;const e=this.i[0],n=this.i.at(-1);this.o&&e&&n&&t.startTime-n.startTime<1e3&&t.startTime-e.startTime<5e3?(this.o+=t.value,this.i.push(t)):(this.o=t.value,this.i=[t]),this.t?.(t)}}const n=()=>{const t=performance.getEntriesByType("navigation")[0];if(t&&t.responseStart>0&&t.responseStart<performance.now())return t},o=t=>{if("loading"===document.readyState)return"loading";const e=n();if(e){if(t<e.domInteractive)return"loading";if(0===e.domContentLoadedEventStart||t<e.domContentLoadedEventStart)return"dom-interactive";if(0===e.domComplete||t<e.domComplete)return"dom-content-loaded"}return"complete"},i=t=>{const e=t.nodeName;return 1===t.nodeType?e.toLowerCase():e.toUpperCase().replace(/^#/,"")},r=t=>{let e="";try{for(;9!==t?.nodeType;){const n=t,o=n.id?"#"+n.id:[i(n),...Array.from(n.classList).sort()].join(".");if(e.length+o.length>99)return e||o;if(e=e?o+">"+e:o,n.id)break;t=n.parentNode}}catch{}return e},s=new WeakMap;function a(t,e){return s.get(t)||s.set(t,new e),s.get(t)}let c=-1;const f=()=>c,u=t=>{addEventListener("pageshow",e=>{e.persisted&&(c=e.timeStamp,t(e))},!0)},d=(t,e,n,o)=>{let i,r;return s=>{e.value>=0&&(s||o)&&(r=e.value-(i??0),(r||void 0===i)&&(i=e.value,e.delta=r,e.rating=((t,e)=>t>e[1]?"poor":t>e[0]?"needs-improvement":"good")(e.value,n),t(e)))}},l=t=>{requestAnimationFrame(()=>requestAnimationFrame(t))},h=()=>n()?.activationStart??0,p=(t,e=-1)=>{const o=n();let i="navigate";f()>=0?i="back-forward-cache":o&&(document.prerendering||h()>0?i="prerender":document.wasDiscarded?i="restore":o.type&&(i=o.type.replace(/_/g,"-")));return{name:t,value:e,rating:"good",delta:0,entries:[],id:`v5-${Date.now()}-${Math.floor(8999999999999*Math.random())+1e12}`,navigationType:i}},g=(t,e,n={})=>{try{if(PerformanceObserver.supportedEntryTypes.includes(t)){const o=new PerformanceObserver(t=>{queueMicrotask(()=>{e(t.getEntries())})});return o.observe({type:t,buffered:!0,...n}),o}}catch{}},m=t=>{let e=!1;return()=>{e||(t(),e=!0)}};let y=-1;const v=new Set,b=()=>"hidden"!==document.visibilityState||document.prerendering?1/0:0,M=t=>{if("hidden"===document.visibilityState){if("visibilitychange"===t.type)for(const t of v)t();isFinite(y)||(y="visibilitychange"===t.type?t.timeStamp:0,removeEventListener("prerenderingchange",M,!0))}},T=()=>{if(y<0){const t=h(),e=document.prerendering?void 0:globalThis.performance.getEntriesByType("visibility-state").find(e=>"hidden"===e.name&&e.startTime>=t)?.startTime;y=e??b(),addEventListener("visibilitychange",M,!0),addEventListener("prerenderingchange",M,!0),u(()=>{setTimeout(()=>{y=b()})})}return{get firstHiddenTime(){return y},onHidden(t){v.add(t)}}},E=t=>{document.prerendering?addEventListener("prerenderingchange",t,!0):t()},D=[1800,3e3],L=(t,e={})=>{E(()=>{const n=T();let o,i=p("FCP");const r=g("paint",t=>{for(const e of t)"first-contentful-paint"===e.name&&(r.disconnect(),e.startTime<n.firstHiddenTime&&(i.value=Math.max(e.startTime-h(),0),i.entries.push(e),o(!0)))});r&&(o=d(t,i,D,e.reportAllChanges),u(n=>{i=p("FCP"),o=d(t,i,D,e.reportAllChanges),l(()=>{i.value=performance.now()-n.timeStamp,o(!0)})}))})},S=[.1,.25],P=t=>t.find(t=>1===t.node?.nodeType)||t[0];let w=0,_=1/0,k=0;const F=t=>{for(const e of t)e.interactionId&&(_=Math.min(_,e.interactionId),k=Math.max(k,e.interactionId),w=k?(k-_)/7+1:0)};let B;const C=()=>B?w:performance.interactionCount??0,O=()=>{"interactionCount"in performance||B||(B=g("event",F,{durationThreshold:0}))};let j=0;class I{l=[];h=new Map;p;m;v(){j=C(),this.l.length=0,this.h.clear()}M(){const t=Math.min(this.l.length-1,Math.floor((C()-j)/50));return this.l[t]}u(t){if(this.p?.(t),!t.interactionId&&"first-input"!==t.entryType)return;const e=this.l.at(-1);let n=this.h.get(t.interactionId);if(n||this.l.length<10||t.duration>e.T){if(n?t.duration>n.T?(n.entries=[t],n.T=t.duration):t.duration===n.T&&t.startTime===n.entries[0].startTime&&n.entries.push(t):(n={id:t.interactionId,entries:[t],T:t.duration},this.h.set(n.id,n),this.l.push(n)),this.l.sort((t,e)=>e.T-t.T),this.l.length>10){const t=this.l.splice(10);for(const e of t)this.h.delete(e.id)}this.m?.(n)}}}const A=t=>{const e=globalThis.requestIdleCallback||setTimeout,n=globalThis.cancelIdleCallback||clearTimeout;if("hidden"===document.visibilityState)t();else{const o=m(t);let i=-1;const r=()=>{n(i),o()};addEventListener("visibilitychange",r,{once:!0,capture:!0}),i=e(()=>{removeEventListener("visibilitychange",r,{capture:!0}),o()})}},q=[200,500];class x{p;u(t){this.p?.(t)}}const W=[2500,4e3],N=[800,1800],$=t=>{document.prerendering?E(()=>$(t)):"complete"!==document.readyState?addEventListener("load",()=>$(t),!0):setTimeout(t)};t.CLSThresholds=S,t.FCPThresholds=D,t.INPThresholds=q,t.LCPThresholds=W,t.TTFBThresholds=N,t.onCLS=(t,n={})=>{const i=a(n=Object.assign({},n),e),s=new WeakMap;i.t=t=>{if(t?.sources?.length){const e=P(t.sources),o=e?.node;if(o){const t=n.generateTarget?.(o)??r(o);s.set(e,t)}}};((t,n={})=>{const o=T();L(m(()=>{let i,r=p("CLS",0);const s=a(n,e),c=t=>{for(const e of t)s.u(e);s.o>r.value&&(r.value=s.o,r.entries=s.i,i())},f=g("layout-shift",c);f&&(i=d(t,r,S,n.reportAllChanges),o.onHidden(()=>{c(f.takeRecords()),i(!0)}),u(()=>{s.o=0,r=p("CLS",0),i=d(t,r,S,n.reportAllChanges),l(i)}),setTimeout(i))}))})(e=>{t((t=>{let e={};if(t.entries.length){const n=t.entries.reduce((t,e)=>t.value>e.value?t:e);if(n?.sources?.length){const t=P(n.sources);t&&(e={largestShiftTarget:s.get(t),largestShiftTime:n.startTime,largestShiftValue:n.value,largestShiftSource:t,largestShiftEntry:n,loadState:o(n.startTime)})}}return Object.assign(t,{attribution:e})})(e))},n)},t.onFCP=(t,e={})=>{L(e=>{t((t=>{let e={timeToFirstByte:0,firstByteToFCP:t.value,loadState:o(f())};if(t.entries.length){const i=n(),r=t.entries.at(-1);if(i){const n=i.activationStart||0,s=Math.max(0,i.responseStart-n);e={timeToFirstByte:s,firstByteToFCP:t.value-s,loadState:o(t.entries[0].startTime),navigationEntry:i,fcpEntry:r}}}return Object.assign(t,{attribution:e})})(e))},e)},t.onINP=(t,e={})=>{const n=a(e=Object.assign({},e),I);let i=[],s=[],c=0;const f=new WeakMap,l=new WeakMap;let h=!1;const m=()=>{h||(A(y),h=!0)},y=()=>{const t=new Set(n.l.map(t=>f.get(t.entries[0]))),e=s.length-10;s=s.filter((n,o)=>o>=e||t.has(n));const o=new Set;for(const t of s){const e=v(t.startTime,t.processingEnd);for(const t of e)o.add(t)}i=i.filter(t=>t.startTime>c||o.has(t)),h=!1};n.p=t=>{const n=t.startTime+t.duration;let o;c=Math.max(c,t.processingEnd);for(let i=s.length-1;i>=0;i--){const r=s[i];if(Math.abs(n-r.renderTime)<=8){o=r,o.startTime=Math.min(t.startTime,o.startTime),o.processingStart=Math.min(t.processingStart,o.processingStart),o.processingEnd=Math.max(t.processingEnd,o.processingEnd),!1!==e.includeProcessedEventEntries&&o.entries.push(t);break}}o||(o={startTime:t.startTime,processingStart:t.processingStart,processingEnd:t.processingEnd,renderTime:n,entries:!1!==e.includeProcessedEventEntries?[t]:[]},s.push(o)),(t.interactionId||"first-input"===t.entryType)&&f.set(t,o),m()},n.m=t=>{if(!l.get(t)){const n=t.entries[0].target;if(n){const o=e.generateTarget?.(n)??r(n);l.set(t,o)}}};const v=(t,e)=>{const n=[];for(const o of i)if(!(o.startTime+o.duration<t)){if(o.startTime>e)break;n.push(o)}return n},b=t=>{const e=t.entries[0],i=f.get(e),r=e.processingStart,s=Math.max(e.startTime+e.duration,r),a=Math.min(i.processingEnd,s),c=i.entries.sort((t,e)=>t.processingStart-e.processingStart),u=v(e.startTime,a),d=n.h.get(e.interactionId),h={interactionTarget:l.get(d),interactionType:e.name.startsWith("key")?"keyboard":"pointer",interactionTime:e.startTime,nextPaintTime:s,processedEventEntries:c,longAnimationFrameEntries:u,inputDelay:r-e.startTime,processingDuration:a-r,presentationDelay:s-a,loadState:o(e.startTime),longestScript:void 0,totalScriptDuration:void 0,totalStyleAndLayoutDuration:void 0,totalPaintDuration:void 0,totalUnattributedDuration:void 0};return(t=>{if(!t.longAnimationFrameEntries?.length)return;const e=t.interactionTime,n=t.inputDelay,o=t.processingDuration;let i,r,s=0,a=0,c=0,f=0;for(const c of t.longAnimationFrameEntries){a=a+c.startTime+c.duration-c.styleAndLayoutStart;for(const t of c.scripts){const c=t.startTime+t.duration;if(c<e)continue;const u=c-Math.max(e,t.startTime),d=t.duration?u/t.duration*t.forcedStyleAndLayoutDuration:0;s+=u-d,a+=d,u>f&&(r=t.startTime<e+n?"input-delay":t.startTime>=e+n+o?"presentation-delay":"processing-duration",i=t,f=u)}}const u=t.longAnimationFrameEntries.at(-1),d=u?u.startTime+u.duration:0;d>=e+n+o&&(c=t.nextPaintTime-d),i&&r&&(t.longestScript={entry:i,subpart:r,intersectingDuration:f}),t.totalScriptDuration=s,t.totalStyleAndLayoutDuration=a,t.totalPaintDuration=c,t.totalUnattributedDuration=t.nextPaintTime-e-s-a-c})(h),Object.assign(t,{attribution:h})};g("long-animation-frame",t=>{i=i.concat(t),m()}),((t,e={})=>{if(!globalThis.PerformanceEventTiming||!("interactionId"in PerformanceEventTiming.prototype))return;const n=T();E(()=>{O();let o,i=p("INP");const r=a(e,I),s=t=>{A(()=>{for(const e of t)r.u(e);const e=r.M();e&&e.T!==i.value&&(i.value=e.T,i.entries=e.entries,o())})},c=g("event",s,{durationThreshold:e.durationThreshold??40});o=d(t,i,q,e.reportAllChanges),c&&(c.observe({type:"first-input",buffered:!0}),n.onHidden(()=>{s(c.takeRecords()),o(!0)}),u(()=>{r.v(),i=p("INP"),o=d(t,i,q,e.reportAllChanges)}))})})(e=>{t(b(e))},e)},t.onLCP=(t,e={})=>{const o=a(e=Object.assign({},e),x),i=new WeakMap;o.p=t=>{const n=t.element;if(n){const o=e.generateTarget?.(n)??r(n);i.set(t,o)}else t.id&&i.set(t,`#${t.id}`)};((t,e={})=>{E(()=>{const n=T();let o,i=p("LCP");const r=a(e,x),s=t=>{e.reportAllChanges||(t=t.slice(-1));for(const e of t)r.u(e),e.startTime<n.firstHiddenTime&&(i.value=Math.max(e.startTime-h(),0),i.entries=[e],o())},c=g("largest-contentful-paint",s);if(c){o=d(t,i,W,e.reportAllChanges);const n=m(()=>{s(c.takeRecords()),c.disconnect(),o(!0)}),r=t=>{t.isTrusted&&(A(n),removeEventListener(t.type,r,{capture:!0}))};for(const t of["keydown","click","visibilitychange"])addEventListener(t,r,{capture:!0});u(n=>{i=p("LCP"),o=d(t,i,W,e.reportAllChanges),l(()=>{i.value=performance.now()-n.timeStamp,o(!0)})})}})})(e=>{t((t=>{let e={timeToFirstByte:0,resourceLoadDelay:0,resourceLoadDuration:0,elementRenderDelay:t.value};if(t.entries.length){const o=t.entries.at(-1),r=o.url&&performance.getEntriesByType("resource").find(t=>t.name===o.url);e.target=i.get(o),e.lcpEntry=o,o.url&&(e.url=o.url),r&&(e.lcpResourceEntry=r);const s=n();if(s){const n=s.activationStart||0,o=Math.max(0,s.responseStart-n),i=Math.max(o,r?(r.requestStart||r.startTime)-n:0),a=Math.min(t.value,Math.max(i,r?r.responseEnd-n:0));e={...e,timeToFirstByte:o,resourceLoadDelay:i-o,resourceLoadDuration:a-i,elementRenderDelay:t.value-a,navigationEntry:s}}}return Object.assign(t,{attribution:e})})(e))},e)},t.onTTFB=(t,e={})=>{((t,e={})=>{let o=p("TTFB"),i=d(t,o,N,e.reportAllChanges);$(()=>{const r=n();r&&(o.value=Math.max(r.responseStart-h(),0),o.entries=[r],i(!0),u(()=>{o=p("TTFB",0),i=d(t,o,N,e.reportAllChanges),i(!0)}))})})(e=>{t((t=>{let e={waitingDuration:0,cacheDuration:0,dnsDuration:0,connectionDuration:0,requestDuration:0};if(t.entries.length){const n=t.entries[0],o=n.activationStart||0,i=Math.max((n.workerStart||n.fetchStart)-o,0),r=Math.max(n.domainLookupStart-o,0),s=Math.max(n.connectStart-o,0),a=Math.max(n.connectEnd-o,0);e={waitingDuration:i,cacheDuration:r-i,dnsDuration:s-r,connectionDuration:a-s,requestDuration:t.value-a,navigationEntry:n}}return Object.assign(t,{attribution:e})})(e))},e)}}); | ||
| !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).webVitals={})}(this,function(t){"use strict";class e{t;i=0;o=[];l(t){if(t.hadRecentInput)return;const e=this.o[0],n=this.o.at(-1);this.i&&e&&n&&t.startTime-n.startTime<1e3&&t.startTime-e.startTime<5e3?(this.i+=t.value,this.o.push(t)):(this.i=t.value,this.o=[t]),this.t?.(t)}}const n=()=>{const t=performance.getEntriesByType("navigation")[0];if(t&&t.responseStart>0&&t.responseStart<performance.now())return t},i=t=>{if("loading"===document.readyState)return"loading";const e=n();if(e){if(t<e.domInteractive)return"loading";if(0===e.domContentLoadedEventStart||t<e.domContentLoadedEventStart)return"dom-interactive";if(0===e.domComplete||t<e.domComplete)return"dom-content-loaded"}return"complete"},o=t=>{const e=t.nodeName;return 1===t.nodeType?e.toLowerCase():e.toUpperCase().replace(/^#/,"")},r=t=>{let e="";try{for(;9!==t?.nodeType;){const n=t,i=n.id?"#"+n.id:[o(n),...Array.from(n.classList).sort()].join(".");if(e.length+i.length>99)return e||i;if(e=e?i+">"+e:i,n.id)break;t=n.parentNode}}catch{}return e},a=new WeakMap;function s(t,e){let n=a.get(e);return n||(n=new WeakMap,a.set(e,n)),n.get(t)||n.set(t,new e),n.get(t)}let c=-1;const f=()=>c,l=t=>{addEventListener("pageshow",e=>{e.persisted&&(c=e.timeStamp,t(e))},!0)},u=(t,e,n,i)=>{let o,r;return a=>{e.value>=0&&(a||i)&&(r=e.value-(o??0),(r||void 0===o)&&(o=e.value,e.delta=r,e.rating=((t,e)=>t>e[1]?"poor":t>e[0]?"needs-improvement":"good")(e.value,n),t(e)))}},d=t=>{requestAnimationFrame(()=>requestAnimationFrame(t))},h=()=>n()?.activationStart??0;let g=-1;const p=new Set,v=()=>"hidden"!==document.visibilityState||document.prerendering?1/0:0,m=t=>{if("hidden"===document.visibilityState){if("visibilitychange"===t.type)for(const t of p)t();isFinite(g)||(g="visibilitychange"===t.type?t.timeStamp:0,removeEventListener("prerenderingchange",m,!0))}},y=(t=!1)=>{if(t&&(g=1/0),g<0){const t=h(),e=document.prerendering?void 0:globalThis.performance.getEntriesByType("visibility-state").find(e=>"hidden"===e.name&&e.startTime>=t)?.startTime;g=e??v(),addEventListener("visibilitychange",m,!0),addEventListener("prerenderingchange",m,!0),l(()=>{setTimeout(()=>{g=v()})})}return{get firstHiddenTime(){return g},onHidden(t){p.add(t)}}},b=(t,e=-1,i,o,r=0,a,s)=>{const c=n()?.navigationId||0,l=n();let u="navigate";o?u=o:f()>=0?u="back-forward-cache":l&&(document.prerendering||h()>0?u="prerender":document.wasDiscarded?u="restore":l.type&&(u=l.type.replace(/_/g,"-")));return{name:t,value:e,rating:"good",delta:0,entries:[],id:`v5-${Date.now()}-${Math.floor(8999999999999*Math.random())+1e12}`,navigationType:u,navigationId:r||c,interactionId:i,navigationURL:a||l?.name,navigationStartTime:s||0}},T=t=>PerformanceObserver.supportedEntryTypes.includes("soft-navigation")&&t&&t.reportSoftNavs,M=(t,e,n={})=>{const i=[t],o=T(n);"event"===t&&i.push("first-input"),o&&("largest-contentful-paint"!==t&&"event"!==t&&"layout-shift"!==t||i.push("soft-navigation"),"largest-contentful-paint"===t&&i.push("interaction-contentful-paint"));try{if(PerformanceObserver.supportedEntryTypes.includes(t)){const t=new PerformanceObserver(t=>{queueMicrotask(()=>{const n=t.getEntries();n.sort((t,e)=>t.startTime+t.duration-(e.startTime+e.duration)),e(n)})});for(const e of i)t.observe(Object.assign({type:e,buffered:!0,...n},n||{}));return t}}catch{}},D=t=>{let e=!1;return()=>{e||(t(),e=!0)}},E=t=>{document.prerendering?addEventListener("prerenderingchange",t,!0):t()},P=[1800,3e3],S=(t,e={})=>{const n=T(e=e||{});E(()=>{const i=y();let o,r=b("FCP");const a=t=>{for(const e of t)"first-contentful-paint"===e.name&&(s.disconnect(),e.startTime<i.firstHiddenTime&&(r.value=Math.max(e.startTime-h(),0),r.entries.push(e),r.navigationId=e.navigationId||0,o(!0)))},s=M("paint",a,e);if(s&&(o=u(t,r,P,e.reportAllChanges),l(n=>{r=b("FCP",0,r.interactionId,"back-forward-cache",r.navigationId,r.navigationURL,r.navigationStartTime),o=u(t,r,P,e.reportAllChanges),d(()=>{r.value=performance.now()-n.timeStamp,o(!0)})})),n){M("soft-navigation",n=>{n.forEach(n=>{a(s.takeRecords());const i=(n.presentationTime||n.paintTime||0)-n.startTime;r=b("FCP",i,n.interactionId,"soft-navigation",n.navigationId,n.name,n.startTime),o=u(t,r,P,e.reportAllChanges),o(!0)})},e)}})},L=[.1,.25],w=t=>t.find(t=>1===t.node?.nodeType)||t[0];let k=0,F=1/0,_=0;const C=t=>{for(const e of t)e.interactionId&&(F=Math.min(F,e.interactionId),_=Math.max(_,e.interactionId),k=_?(_-F)/7+1:0)};let I;const O=()=>I?k:performance.interactionCount??0,j=()=>{"interactionCount"in performance||I||(I=M("event",C,{durationThreshold:0}))};let B=0;class A{u=[];h=new Map;p;v;m(){B=O(),this.u.length=0,this.h.clear()}T(t){const e=O()-B,n=Math.min(this.u.length-1,Math.floor(e/50));return e&&-1===n&&"soft-navigation"===t?{M:8,id:-1,entries:[]}:this.u[n]}l(t){if(this.p?.(t),!t.interactionId&&"first-input"!==t.entryType)return;const e=this.u.at(-1);let n=this.h.get(t.interactionId);if(n||this.u.length<10||t.duration>e.M){if(n?t.duration>n.M?(n.entries=[t],n.M=t.duration):t.duration===n.M&&t.startTime===n.entries[0].startTime&&n.entries.push(t):(n={id:t.interactionId,entries:[t],M:t.duration},this.h.set(n.id,n),this.u.push(n)),this.u.sort((t,e)=>e.M-t.M),this.u.length>10){const t=this.u.splice(10);for(const e of t)this.h.delete(e.id)}this.v?.(n)}}}const x=t=>{const e=globalThis.requestIdleCallback||setTimeout,n=globalThis.cancelIdleCallback||clearTimeout;if("hidden"===document.visibilityState)t();else{const i=D(t);let o=-1;const r=()=>{n(o),i()};addEventListener("visibilitychange",r,{once:!0,capture:!0}),o=e(()=>{removeEventListener("visibilitychange",r,{capture:!0}),i()})}},W=[200,500];class q{p;l(t){this.p?.(t)}}const U=[2500,4e3],N=[800,1800],R=t=>{document.prerendering?E(()=>R(t)):"complete"!==document.readyState?addEventListener("load",()=>R(t),!0):setTimeout(t)};t.CLSThresholds=L,t.FCPThresholds=P,t.INPThresholds=W,t.LCPThresholds=U,t.TTFBThresholds=N,t.onCLS=(t,n={})=>{const o=s(n=Object.assign({},n),e),a=new WeakMap;o.t=t=>{if(t?.sources?.length){const e=w(t.sources),i=e?.node;if(i){const t=n.generateTarget?.(i)??r(i);a.set(e,t)}}};((t,n={})=>{const i=y();S(D(()=>{let o,r=b("CLS",0);const a=s(n,e),c=(e,i,s,c,f)=>{r=b("CLS",0,e,i,s,c,f),a.i=0,o=u(t,r,L,n.reportAllChanges)},f=t=>{h(g?.takeRecords()),o(!0),c(t.interactionId,"soft-navigation",t.navigationId,t.name,t.startTime)},h=t=>{for(const e of t)"largestInteractionContentfulPaint"in e?f(e):a.l(e);a.i>r.value&&(r.value=a.i,r.entries=a.o,o())},g=M("layout-shift",h,n);g&&(o=u(t,r,L,n.reportAllChanges),i.onHidden(()=>{h(g.takeRecords()),o(!0)}),l(()=>{c(r.interactionId,"back-forward-cache",r.navigationId,r.navigationURL,r.navigationStartTime),d(o)}),setTimeout(o))}))})(e=>{t((t=>{let e={};if(t.entries.length){const n=t.entries.reduce((t,e)=>t.value>e.value?t:e);if(n?.sources?.length){const t=w(n.sources);t&&(e={largestShiftTarget:a.get(t),largestShiftTime:n.startTime,largestShiftValue:n.value,largestShiftSource:t,largestShiftEntry:n,loadState:i(n.startTime)})}}return Object.assign(t,{attribution:e})})(e))},n)},t.onFCP=(t,e={})=>{S(e=>{t((t=>{const e=n()?.navigationId||0;let o={timeToFirstByte:0,firstByteToFCP:t.value,loadState:i(f())};if(t.entries.length){let r;const a=t.entries.at(-1);let s=0;if((!t.navigationId||t.navigationId===e)&&(r=n(),r)){const t=r.responseStart,e=r.activationStart||0;s=Math.max(0,t-e)}r&&(o={timeToFirstByte:s,firstByteToFCP:t.value-s,loadState:i(t.entries[0].startTime),navigationEntry:r,fcpEntry:a})}return Object.assign(t,{attribution:o})})(e))},e)},t.onINP=(t,e={})=>{const n=s(e=Object.assign({},e),A);let o=[],a=[],c=0;const f=new WeakMap,d=new WeakMap;let h=!1;const g=()=>{h||(x(p),h=!0)},p=()=>{const t=new Set(n.u.map(t=>f.get(t.entries[0]))),e=a.length-10;a=a.filter((n,i)=>i>=e||t.has(n));const i=new Set;for(const t of a){const e=v(t.startTime,t.processingEnd);for(const t of e)i.add(t)}o=o.filter(t=>t.startTime>c||i.has(t)),h=!1};n.p=t=>{const n=t.startTime+t.duration;let i;c=Math.max(c,t.processingEnd);for(let o=a.length-1;o>=0;o--){const r=a[o];if(Math.abs(n-r.renderTime)<=8){i=r,i.startTime=Math.min(t.startTime,i.startTime),i.processingStart=Math.min(t.processingStart,i.processingStart),i.processingEnd=Math.max(t.processingEnd,i.processingEnd),!1!==e.includeProcessedEventEntries&&i.entries.push(t);break}}i||(i={startTime:t.startTime,processingStart:t.processingStart,processingEnd:t.processingEnd,renderTime:n,entries:!1!==e.includeProcessedEventEntries?[t]:[]},a.push(i)),(t.interactionId||"first-input"===t.entryType)&&f.set(t,i),g()},n.v=t=>{if(!d.get(t)){const n=t.entries.find(t=>t.target)?.target;if(n){const i=e.generateTarget?.(n)??r(n);d.set(t,i)}else{const e=t.entries.find(t=>t.targetSelector)?.targetSelector;e&&d.set(t,e)}}};const v=(t,e)=>{const n=[];for(const i of o)if(!(i.startTime+i.duration<t)){if(i.startTime>e)break;n.push(i)}return n},m=t=>{if(0===t.entries.length&&"soft-navigation"===t.navigationType){const e=t.navigationStartTime;if(e){const n={interactionTarget:"",interactionType:"pointer",interactionTime:e,nextPaintTime:e+t.value,processedEventEntries:[],longAnimationFrameEntries:[],inputDelay:0,processingDuration:0,presentationDelay:t.value,loadState:i(e),longestScript:void 0,totalScriptDuration:void 0,totalStyleAndLayoutDuration:void 0,totalPaintDuration:void 0,totalUnattributedDuration:void 0};return Object.assign(t,{attribution:n})}}const e=t.entries[0],o=f.get(e),r=e.processingStart,a=Math.max(e.startTime+e.duration,r),s=Math.min(o.processingEnd,a),c=o.entries.sort((t,e)=>t.processingStart-e.processingStart),l=v(e.startTime,s),u=n.h.get(e.interactionId),h={interactionTarget:d.get(u),interactionType:e.name.startsWith("key")?"keyboard":"pointer",interactionTime:e.startTime,nextPaintTime:a,processedEventEntries:c,longAnimationFrameEntries:l,inputDelay:r-e.startTime,processingDuration:s-r,presentationDelay:a-s,loadState:i(e.startTime),longestScript:void 0,totalScriptDuration:void 0,totalStyleAndLayoutDuration:void 0,totalPaintDuration:void 0,totalUnattributedDuration:void 0};return(t=>{if(!t.longAnimationFrameEntries?.length)return;const e=t.interactionTime,n=t.inputDelay,i=t.processingDuration;let o,r,a=0,s=0,c=0,f=0;for(const c of t.longAnimationFrameEntries){s=s+c.startTime+c.duration-c.styleAndLayoutStart;for(const t of c.scripts){const c=t.startTime+t.duration;if(c<e)continue;const l=c-Math.max(e,t.startTime),u=t.duration?l/t.duration*t.forcedStyleAndLayoutDuration:0;a+=l-u,s+=u,l>f&&(r=t.startTime<e+n?"input-delay":t.startTime>=e+n+i?"presentation-delay":"processing-duration",o=t,f=l)}}const l=t.longAnimationFrameEntries.at(-1),u=l?l.startTime+l.duration:0;u>=e+n+i&&(c=t.nextPaintTime-u),o&&r&&(t.longestScript={entry:o,subpart:r,intersectingDuration:f}),t.totalScriptDuration=a,t.totalStyleAndLayoutDuration=s,t.totalPaintDuration=c,t.totalUnattributedDuration=t.nextPaintTime-e-a-s-c})(h),Object.assign(t,{attribution:h})};M("long-animation-frame",t=>{o=o.concat(t),g()},e),((t,e={})=>{if(!globalThis.PerformanceEventTiming||!("interactionId"in PerformanceEventTiming.prototype))return;const n=y();E(()=>{j();let i,o=b("INP");const r=s(e,A),a=(n,a,s,c,f)=>{r.m(),o=b("INP",-1,n,a,s,c,f),i=u(t,o,W,e.reportAllChanges)},c=()=>{const t=r.T(o.navigationType);t&&t.M!==o.value&&(o.value=t.M,o.entries=t.entries,i())},f=t=>{d(h?.takeRecords()),c(),i(!0),a(t.interactionId,"soft-navigation",t.navigationId,t.name,t.startTime)},d=t=>{x(()=>{if(t.some(t=>t.interactionId)){for(const e of t)"largestInteractionContentfulPaint"in e?f(e):r.l(e);c()}})},h=M("event",d,{...e,durationThreshold:e.durationThreshold??40});i=u(t,o,W,e.reportAllChanges),h&&(n.onHidden(()=>{d(h.takeRecords()),"soft-navigation"===o.navigationType&&c(),i(!0)}),l(()=>{a(o.interactionId,"back-forward-cache",o.navigationId,o.navigationURL,o.navigationStartTime)}))})})(e=>{t(m(e))},e)},t.onLCP=(t,e={})=>{const i=s(e=Object.assign({},e),q),o=new WeakMap;i.p=t=>{const n=t.element;if(n){const i=e.generateTarget?.(n)??r(n);o.set(t,i)}else t.id&&o.set(t,`#${t.id}`)};((t,e={})=>{let n=!1;const i=T(e);E(()=>{let o,r=y(),a=b("LCP");const c=s(e,q),f=(i,s,c,f,l)=>{a=b("LCP",0,s,i,c,f,l),o=u(t,a,U,e.reportAllChanges),n=!1,"soft-navigation"===i&&(r=y(!0))},g=t=>{p(v.takeRecords()),n||o(!0),f("soft-navigation",t.interactionId,t.navigationId,t.name,t.startTime);const e=t.getLargestInteractionContentfulPaint?.()||t.largestInteractionContentfulPaint;e&&p([e])},p=t=>{e.reportAllChanges||i||(t=t.slice(-1));for(const e of t){if(!e)continue;if("largestInteractionContentfulPaint"in e){g(e);continue}let t=0,n=[];if(e instanceof LargestContentfulPaint)t=Math.max(e.startTime-h(),0),c.l(e),n=[e];else{if(!a.navigationId)continue;if("interactionId"in e&&e.interactionId!=a.interactionId)continue;const i=e?.largestContentfulPaint?.renderTime||e.renderTime||0;t=Math.max(i-e.startTime,0),e.largestContentfulPaint&&c.l(e.largestContentfulPaint),e.largestContentfulPaint&&(n=[e.largestContentfulPaint])}e.startTime<r.firstHiddenTime&&(a.value=t,a.entries=n,o())}},v=M("largest-contentful-paint",p,e);if(v){o=u(t,a,U,e.reportAllChanges);const r=t=>{t.isTrusted&&!n&&x(()=>{n||(p(v.takeRecords()),i||(v.disconnect(),removeEventListener(t.type,r)),n=!0,o(!0))})};for(const t of["keydown","click","visibilitychange"])addEventListener(t,r,{capture:!0});l(i=>{f("back-forward-cache",a.interactionId,a.navigationId,a.navigationURL,a.navigationStartTime),o=u(t,a,U,e.reportAllChanges),d(()=>{a.value=performance.now()-i.timeStamp,n=!0,o(!0)})})}})})(e=>{t((t=>{const e=n()?.navigationId||0;let i={timeToFirstByte:0,resourceLoadDelay:0,resourceLoadDuration:0,elementRenderDelay:t.value};if(t.entries.length){const r=t.entries.at(-1),a=r.url&&performance.getEntriesByType("resource").find(t=>t.name===r.url);let s;i.target=o.get(r),i.lcpEntry=r,r.url&&(i.url=r.url),a&&(i.lcpResourceEntry=a);let c=0,f=0;if(t.navigationId&&t.navigationId!==e?c=t.navigationStartTime||0:(s=n(),c=s&&s.activationStart?s.activationStart:0,f=s&&s.responseStart?s.responseStart:0),s){const e=Math.max(0,f-c),n=Math.max(e,a?(a.requestStart||a.startTime)-c:0),o=Math.min(t.value,Math.max(n-c,a?a.responseEnd-c:0,0));i={...i,timeToFirstByte:e,resourceLoadDelay:n-e,resourceLoadDuration:o-n,elementRenderDelay:t.value-o,navigationEntry:s}}}return Object.assign(t,{attribution:i})})(e))},e)},t.onTTFB=(t,e={})=>{((t,e={})=>{const i=T(e=e||{});let o=b("TTFB"),r=u(t,o,N,e.reportAllChanges);R(()=>{const a=n();if(a){const n=a.responseStart;o.value=Math.max(n-h(),0),o.entries=[a],r(!0),l(()=>{o=b("TTFB",0,o.interactionId,"back-forward-cache",o.navigationId,o.navigationURL,o.navigationStartTime),r=u(t,o,N,e.reportAllChanges),r(!0)}),i&&M("soft-navigation",n=>{n.forEach(n=>{n.navigationId&&(o=b("TTFB",0,n.interactionId,"soft-navigation",n.navigationId,n.name,n.startTime),o.entries=[n],r=u(t,o,N,e.reportAllChanges),r(!0))})},e)}})})(e=>{t((t=>{let e={waitingDuration:0,cacheDuration:0,dnsDuration:0,connectionDuration:0,requestDuration:0};if(t.entries.length){const n=t.entries[0],i=n.activationStart||0,o=Math.max((n.workerStart||n.fetchStart||0)-i,0),r=Math.max(n.domainLookupStart-i||0,0),a=Math.max(n.connectStart-i||0,0),s=Math.max(n.connectEnd-i||0,0);e={waitingDuration:o,cacheDuration:r-o,dnsDuration:a-r,connectionDuration:s-a,requestDuration:t.value-s,navigationEntry:n}}return Object.assign(t,{attribution:e})})(e))},e)}}); |
@@ -1,1 +0,1 @@ | ||
| var webVitals=function(e){"use strict";let t=-1;const n=e=>{addEventListener("pageshow",n=>{n.persisted&&(t=n.timeStamp,e(n))},!0)},i=(e,t,n,i)=>{let s,o;return r=>{t.value>=0&&(r||i)&&(o=t.value-(s??0),(o||void 0===s)&&(s=t.value,t.delta=o,t.rating=((e,t)=>e>t[1]?"poor":e>t[0]?"needs-improvement":"good")(t.value,n),e(t)))}},s=e=>{requestAnimationFrame(()=>requestAnimationFrame(e))},o=()=>{const e=performance.getEntriesByType("navigation")[0];if(e&&e.responseStart>0&&e.responseStart<performance.now())return e},r=()=>o()?.activationStart??0,c=(e,n=-1)=>{const i=o();let s="navigate";t>=0?s="back-forward-cache":i&&(document.prerendering||r()>0?s="prerender":document.wasDiscarded?s="restore":i.type&&(s=i.type.replace(/_/g,"-")));return{name:e,value:n,rating:"good",delta:0,entries:[],id:`v5-${Date.now()}-${Math.floor(8999999999999*Math.random())+1e12}`,navigationType:s}},a=new WeakMap;function d(e,t){return a.get(e)||a.set(e,new t),a.get(e)}class h{t;i=0;o=[];h(e){if(e.hadRecentInput)return;const t=this.o[0],n=this.o.at(-1);this.i&&t&&n&&e.startTime-n.startTime<1e3&&e.startTime-t.startTime<5e3?(this.i+=e.value,this.o.push(e)):(this.i=e.value,this.o=[e]),this.t?.(e)}}const f=(e,t,n={})=>{try{if(PerformanceObserver.supportedEntryTypes.includes(e)){const i=new PerformanceObserver(e=>{queueMicrotask(()=>{t(e.getEntries())})});return i.observe({type:e,buffered:!0,...n}),i}}catch{}},l=e=>{let t=!1;return()=>{t||(e(),t=!0)}};let u=-1;const m=new Set,g=()=>"hidden"!==document.visibilityState||document.prerendering?1/0:0,v=e=>{if("hidden"===document.visibilityState){if("visibilitychange"===e.type)for(const e of m)e();isFinite(u)||(u="visibilitychange"===e.type?e.timeStamp:0,removeEventListener("prerenderingchange",v,!0))}},p=()=>{if(u<0){const e=r(),t=document.prerendering?void 0:globalThis.performance.getEntriesByType("visibility-state").find(t=>"hidden"===t.name&&t.startTime>=e)?.startTime;u=t??g(),addEventListener("visibilitychange",v,!0),addEventListener("prerenderingchange",v,!0),n(()=>{setTimeout(()=>{u=g()})})}return{get firstHiddenTime(){return u},onHidden(e){m.add(e)}}},y=e=>{document.prerendering?addEventListener("prerenderingchange",e,!0):e()},T=[1800,3e3],b=(e,t={})=>{y(()=>{const o=p();let a,d=c("FCP");const h=f("paint",e=>{for(const t of e)"first-contentful-paint"===t.name&&(h.disconnect(),t.startTime<o.firstHiddenTime&&(d.value=Math.max(t.startTime-r(),0),d.entries.push(t),a(!0)))});h&&(a=i(e,d,T,t.reportAllChanges),n(n=>{d=c("FCP"),a=i(e,d,T,t.reportAllChanges),s(()=>{d.value=performance.now()-n.timeStamp,a(!0)})}))})},E=[.1,.25];let L=0,P=1/0,_=0;const M=e=>{for(const t of e)t.interactionId&&(P=Math.min(P,t.interactionId),_=Math.max(_,t.interactionId),L=_?(_-P)/7+1:0)};let w;const C=()=>w?L:performance.interactionCount??0,I=()=>{"interactionCount"in performance||w||(w=f("event",M,{durationThreshold:0}))};let F=0;class k{l=[];u=new Map;m;v;p(){F=C(),this.l.length=0,this.u.clear()}T(){const e=Math.min(this.l.length-1,Math.floor((C()-F)/50));return this.l[e]}h(e){if(this.m?.(e),!e.interactionId&&"first-input"!==e.entryType)return;const t=this.l.at(-1);let n=this.u.get(e.interactionId);if(n||this.l.length<10||e.duration>t.L){if(n?e.duration>n.L?(n.entries=[e],n.L=e.duration):e.duration===n.L&&e.startTime===n.entries[0].startTime&&n.entries.push(e):(n={id:e.interactionId,entries:[e],L:e.duration},this.u.set(n.id,n),this.l.push(n)),this.l.sort((e,t)=>t.L-e.L),this.l.length>10){const e=this.l.splice(10);for(const t of e)this.u.delete(t.id)}this.v?.(n)}}}const A=e=>{const t=globalThis.requestIdleCallback||setTimeout,n=globalThis.cancelIdleCallback||clearTimeout;if("hidden"===document.visibilityState)e();else{const i=l(e);let s=-1;const o=()=>{n(s),i()};addEventListener("visibilitychange",o,{once:!0,capture:!0}),s=t(()=>{removeEventListener("visibilitychange",o,{capture:!0}),i()})}},B=[200,500];class S{m;h(e){this.m?.(e)}}const q=[2500,4e3],N=[800,1800],H=e=>{document.prerendering?y(()=>H(e)):"complete"!==document.readyState?addEventListener("load",()=>H(e),!0):setTimeout(e)};return e.CLSThresholds=E,e.FCPThresholds=T,e.INPThresholds=B,e.LCPThresholds=q,e.TTFBThresholds=N,e.onCLS=(e,t={})=>{const o=p();b(l(()=>{let r,a=c("CLS",0);const l=d(t,h),u=e=>{for(const t of e)l.h(t);l.i>a.value&&(a.value=l.i,a.entries=l.o,r())},m=f("layout-shift",u);m&&(r=i(e,a,E,t.reportAllChanges),o.onHidden(()=>{u(m.takeRecords()),r(!0)}),n(()=>{l.i=0,a=c("CLS",0),r=i(e,a,E,t.reportAllChanges),s(r)}),setTimeout(r))}))},e.onFCP=b,e.onINP=(e,t={})=>{if(!globalThis.PerformanceEventTiming||!("interactionId"in PerformanceEventTiming.prototype))return;const s=p();y(()=>{I();let o,r=c("INP");const a=d(t,k),h=e=>{A(()=>{for(const t of e)a.h(t);const t=a.T();t&&t.L!==r.value&&(r.value=t.L,r.entries=t.entries,o())})},l=f("event",h,{durationThreshold:t.durationThreshold??40});o=i(e,r,B,t.reportAllChanges),l&&(l.observe({type:"first-input",buffered:!0}),s.onHidden(()=>{h(l.takeRecords()),o(!0)}),n(()=>{a.p(),r=c("INP"),o=i(e,r,B,t.reportAllChanges)}))})},e.onLCP=(e,t={})=>{y(()=>{const o=p();let a,h=c("LCP");const u=d(t,S),m=e=>{t.reportAllChanges||(e=e.slice(-1));for(const t of e)u.h(t),t.startTime<o.firstHiddenTime&&(h.value=Math.max(t.startTime-r(),0),h.entries=[t],a())},g=f("largest-contentful-paint",m);if(g){a=i(e,h,q,t.reportAllChanges);const o=l(()=>{m(g.takeRecords()),g.disconnect(),a(!0)}),r=e=>{e.isTrusted&&(A(o),removeEventListener(e.type,r,{capture:!0}))};for(const e of["keydown","click","visibilitychange"])addEventListener(e,r,{capture:!0});n(n=>{h=c("LCP"),a=i(e,h,q,t.reportAllChanges),s(()=>{h.value=performance.now()-n.timeStamp,a(!0)})})}})},e.onTTFB=(e,t={})=>{let s=c("TTFB"),a=i(e,s,N,t.reportAllChanges);H(()=>{const d=o();d&&(s.value=Math.max(d.responseStart-r(),0),s.entries=[d],a(!0),n(()=>{s=c("TTFB",0),a=i(e,s,N,t.reportAllChanges),a(!0)}))})},e}({}); | ||
| var webVitals=function(t){"use strict";let e=-1;const n=t=>{addEventListener("pageshow",n=>{n.persisted&&(e=n.timeStamp,t(n))},!0)},i=(t,e,n,i)=>{let o,s;return r=>{e.value>=0&&(r||i)&&(s=e.value-(o??0),(s||void 0===o)&&(o=e.value,e.delta=s,e.rating=((t,e)=>t>e[1]?"poor":t>e[0]?"needs-improvement":"good")(e.value,n),t(e)))}},o=t=>{requestAnimationFrame(()=>requestAnimationFrame(t))},s=()=>{const t=performance.getEntriesByType("navigation")[0];if(t&&t.responseStart>0&&t.responseStart<performance.now())return t},r=()=>s()?.activationStart??0;let a=-1;const c=new Set,f=()=>"hidden"!==document.visibilityState||document.prerendering?1/0:0,d=t=>{if("hidden"===document.visibilityState){if("visibilitychange"===t.type)for(const t of c)t();isFinite(a)||(a="visibilitychange"===t.type?t.timeStamp:0,removeEventListener("prerenderingchange",d,!0))}},l=(t=!1)=>{if(t&&(a=1/0),a<0){const t=r(),e=document.prerendering?void 0:globalThis.performance.getEntriesByType("visibility-state").find(e=>"hidden"===e.name&&e.startTime>=t)?.startTime;a=e??f(),addEventListener("visibilitychange",d,!0),addEventListener("prerenderingchange",d,!0),n(()=>{setTimeout(()=>{a=f()})})}return{get firstHiddenTime(){return a},onHidden(t){c.add(t)}}},h=(t,n=-1,i,o,a=0,c,f)=>{const d=s()?.navigationId||0,l=s();let h="navigate";o?h=o:e>=0?h="back-forward-cache":l&&(document.prerendering||r()>0?h="prerender":document.wasDiscarded?h="restore":l.type&&(h=l.type.replace(/_/g,"-")));return{name:t,value:n,rating:"good",delta:0,entries:[],id:`v5-${Date.now()}-${Math.floor(8999999999999*Math.random())+1e12}`,navigationType:h,navigationId:a||d,interactionId:i,navigationURL:c||l?.name,navigationStartTime:f||0}},u=new WeakMap;function g(t,e){let n=u.get(e);return n||(n=new WeakMap,u.set(e,n)),n.get(t)||n.set(t,new e),n.get(t)}class v{t;i=0;o=[];l(t){if(t.hadRecentInput)return;const e=this.o[0],n=this.o.at(-1);this.i&&e&&n&&t.startTime-n.startTime<1e3&&t.startTime-e.startTime<5e3?(this.i+=t.value,this.o.push(t)):(this.i=t.value,this.o=[t]),this.t?.(t)}}const m=t=>PerformanceObserver.supportedEntryTypes.includes("soft-navigation")&&t&&t.reportSoftNavs,p=(t,e,n={})=>{const i=[t],o=m(n);"event"===t&&i.push("first-input"),o&&("largest-contentful-paint"!==t&&"event"!==t&&"layout-shift"!==t||i.push("soft-navigation"),"largest-contentful-paint"===t&&i.push("interaction-contentful-paint"));try{if(PerformanceObserver.supportedEntryTypes.includes(t)){const t=new PerformanceObserver(t=>{queueMicrotask(()=>{const n=t.getEntries();n.sort((t,e)=>t.startTime+t.duration-(e.startTime+e.duration)),e(n)})});for(const e of i)t.observe(Object.assign({type:e,buffered:!0,...n},n||{}));return t}}catch{}},b=t=>{let e=!1;return()=>{e||(t(),e=!0)}},P=t=>{document.prerendering?addEventListener("prerenderingchange",t,!0):t()},T=[1800,3e3],y=(t,e={})=>{const s=m(e=e||{});P(()=>{const a=l();let c,f=h("FCP");const d=t=>{for(const e of t)"first-contentful-paint"===e.name&&(u.disconnect(),e.startTime<a.firstHiddenTime&&(f.value=Math.max(e.startTime-r(),0),f.entries.push(e),f.navigationId=e.navigationId||0,c(!0)))},u=p("paint",d,e);if(u&&(c=i(t,f,T,e.reportAllChanges),n(n=>{f=h("FCP",0,f.interactionId,"back-forward-cache",f.navigationId,f.navigationURL,f.navigationStartTime),c=i(t,f,T,e.reportAllChanges),o(()=>{f.value=performance.now()-n.timeStamp,c(!0)})})),s){p("soft-navigation",n=>{n.forEach(n=>{d(u.takeRecords());const o=(n.presentationTime||n.paintTime||0)-n.startTime;f=h("FCP",o,n.interactionId,"soft-navigation",n.navigationId,n.name,n.startTime),c=i(t,f,T,e.reportAllChanges),c(!0)})},e)}})},L=[.1,.25];let E=0,_=1/0,w=0;const M=t=>{for(const e of t)e.interactionId&&(_=Math.min(_,e.interactionId),w=Math.max(w,e.interactionId),E=w?(w-_)/7+1:0)};let I;const C=()=>I?E:performance.interactionCount??0,k=()=>{"interactionCount"in performance||I||(I=p("event",M,{durationThreshold:0}))};let F=0;class B{h=[];u=new Map;v;m;p(){F=C(),this.h.length=0,this.u.clear()}P(t){const e=C()-F,n=Math.min(this.h.length-1,Math.floor(e/50));return e&&-1===n&&"soft-navigation"===t?{T:8,id:-1,entries:[]}:this.h[n]}l(t){if(this.v?.(t),!t.interactionId&&"first-input"!==t.entryType)return;const e=this.h.at(-1);let n=this.u.get(t.interactionId);if(n||this.h.length<10||t.duration>e.T){if(n?t.duration>n.T?(n.entries=[t],n.T=t.duration):t.duration===n.T&&t.startTime===n.entries[0].startTime&&n.entries.push(t):(n={id:t.interactionId,entries:[t],T:t.duration},this.u.set(n.id,n),this.h.push(n)),this.h.sort((t,e)=>e.T-t.T),this.h.length>10){const t=this.h.splice(10);for(const e of t)this.u.delete(e.id)}this.m?.(n)}}}const S=t=>{const e=globalThis.requestIdleCallback||setTimeout,n=globalThis.cancelIdleCallback||clearTimeout;if("hidden"===document.visibilityState)t();else{const i=b(t);let o=-1;const s=()=>{n(o),i()};addEventListener("visibilitychange",s,{once:!0,capture:!0}),o=e(()=>{removeEventListener("visibilitychange",s,{capture:!0}),i()})}},A=[200,500];class O{v;l(t){this.v?.(t)}}const q=[2500,4e3],N=[800,1800],H=t=>{document.prerendering?P(()=>H(t)):"complete"!==document.readyState?addEventListener("load",()=>H(t),!0):setTimeout(t)};return t.CLSThresholds=L,t.FCPThresholds=T,t.INPThresholds=A,t.LCPThresholds=q,t.TTFBThresholds=N,t.onCLS=(t,e={})=>{const s=l();y(b(()=>{let r,a=h("CLS",0);const c=g(e,v),f=(n,o,s,f,d)=>{a=h("CLS",0,n,o,s,f,d),c.i=0,r=i(t,a,L,e.reportAllChanges)},d=t=>{l(u?.takeRecords()),r(!0),f(t.interactionId,"soft-navigation",t.navigationId,t.name,t.startTime)},l=t=>{for(const e of t)"largestInteractionContentfulPaint"in e?d(e):c.l(e);c.i>a.value&&(a.value=c.i,a.entries=c.o,r())},u=p("layout-shift",l,e);u&&(r=i(t,a,L,e.reportAllChanges),s.onHidden(()=>{l(u.takeRecords()),r(!0)}),n(()=>{f(a.interactionId,"back-forward-cache",a.navigationId,a.navigationURL,a.navigationStartTime),o(r)}),setTimeout(r))}))},t.onFCP=y,t.onINP=(t,e={})=>{if(!globalThis.PerformanceEventTiming||!("interactionId"in PerformanceEventTiming.prototype))return;const o=l();P(()=>{k();let s,r=h("INP");const a=g(e,B),c=(n,o,c,f,d)=>{a.p(),r=h("INP",-1,n,o,c,f,d),s=i(t,r,A,e.reportAllChanges)},f=()=>{const t=a.P(r.navigationType);t&&t.T!==r.value&&(r.value=t.T,r.entries=t.entries,s())},d=t=>{l(u?.takeRecords()),f(),s(!0),c(t.interactionId,"soft-navigation",t.navigationId,t.name,t.startTime)},l=t=>{S(()=>{if(t.some(t=>t.interactionId)){for(const e of t)"largestInteractionContentfulPaint"in e?d(e):a.l(e);f()}})},u=p("event",l,{...e,durationThreshold:e.durationThreshold??40});s=i(t,r,A,e.reportAllChanges),u&&(o.onHidden(()=>{l(u.takeRecords()),"soft-navigation"===r.navigationType&&f(),s(!0)}),n(()=>{c(r.interactionId,"back-forward-cache",r.navigationId,r.navigationURL,r.navigationStartTime)}))})},t.onLCP=(t,e={})=>{let s=!1;const a=m(e);P(()=>{let c,f=l(),d=h("LCP");const u=g(e,O),v=(n,o,r,a,u)=>{d=h("LCP",0,o,n,r,a,u),c=i(t,d,q,e.reportAllChanges),s=!1,"soft-navigation"===n&&(f=l(!0))},m=t=>{b(P.takeRecords()),s||c(!0),v("soft-navigation",t.interactionId,t.navigationId,t.name,t.startTime);const e=t.getLargestInteractionContentfulPaint?.()||t.largestInteractionContentfulPaint;e&&b([e])},b=t=>{e.reportAllChanges||a||(t=t.slice(-1));for(const e of t){if(!e)continue;if("largestInteractionContentfulPaint"in e){m(e);continue}let t=0,n=[];if(e instanceof LargestContentfulPaint)t=Math.max(e.startTime-r(),0),u.l(e),n=[e];else{if(!d.navigationId)continue;if("interactionId"in e&&e.interactionId!=d.interactionId)continue;const i=e?.largestContentfulPaint?.renderTime||e.renderTime||0;t=Math.max(i-e.startTime,0),e.largestContentfulPaint&&u.l(e.largestContentfulPaint),e.largestContentfulPaint&&(n=[e.largestContentfulPaint])}e.startTime<f.firstHiddenTime&&(d.value=t,d.entries=n,c())}},P=p("largest-contentful-paint",b,e);if(P){c=i(t,d,q,e.reportAllChanges);const r=t=>{t.isTrusted&&!s&&S(()=>{s||(b(P.takeRecords()),a||(P.disconnect(),removeEventListener(t.type,r)),s=!0,c(!0))})};for(const t of["keydown","click","visibilitychange"])addEventListener(t,r,{capture:!0});n(n=>{v("back-forward-cache",d.interactionId,d.navigationId,d.navigationURL,d.navigationStartTime),c=i(t,d,q,e.reportAllChanges),o(()=>{d.value=performance.now()-n.timeStamp,s=!0,c(!0)})})}})},t.onTTFB=(t,e={})=>{const o=m(e=e||{});let a=h("TTFB"),c=i(t,a,N,e.reportAllChanges);H(()=>{const f=s();if(f){const s=f.responseStart;if(a.value=Math.max(s-r(),0),a.entries=[f],c(!0),n(()=>{a=h("TTFB",0,a.interactionId,"back-forward-cache",a.navigationId,a.navigationURL,a.navigationStartTime),c=i(t,a,N,e.reportAllChanges),c(!0)}),o){p("soft-navigation",n=>{n.forEach(n=>{n.navigationId&&(a=h("TTFB",0,n.interactionId,"soft-navigation",n.navigationId,n.name,n.startTime),a.entries=[n],c=i(t,a,N,e.reportAllChanges),c(!0))})},e)}}})},t}({}); |
@@ -1,1 +0,1 @@ | ||
| let e=-1;const t=t=>{addEventListener("pageshow",n=>{n.persisted&&(e=n.timeStamp,t(n))},!0)},n=(e,t,n,i)=>{let s,o;return r=>{t.value>=0&&(r||i)&&(o=t.value-(s??0),(o||void 0===s)&&(s=t.value,t.delta=o,t.rating=((e,t)=>e>t[1]?"poor":e>t[0]?"needs-improvement":"good")(t.value,n),e(t)))}},i=e=>{requestAnimationFrame(()=>requestAnimationFrame(e))},s=()=>{const e=performance.getEntriesByType("navigation")[0];if(e&&e.responseStart>0&&e.responseStart<performance.now())return e},o=()=>s()?.activationStart??0,r=(t,n=-1)=>{const i=s();let r="navigate";e>=0?r="back-forward-cache":i&&(document.prerendering||o()>0?r="prerender":document.wasDiscarded?r="restore":i.type&&(r=i.type.replace(/_/g,"-")));return{name:t,value:n,rating:"good",delta:0,entries:[],id:`v5-${Date.now()}-${Math.floor(8999999999999*Math.random())+1e12}`,navigationType:r}},c=new WeakMap;function a(e,t){return c.get(e)||c.set(e,new t),c.get(e)}class d{t;i=0;o=[];h(e){if(e.hadRecentInput)return;const t=this.o[0],n=this.o.at(-1);this.i&&t&&n&&e.startTime-n.startTime<1e3&&e.startTime-t.startTime<5e3?(this.i+=e.value,this.o.push(e)):(this.i=e.value,this.o=[e]),this.t?.(e)}}const h=(e,t,n={})=>{try{if(PerformanceObserver.supportedEntryTypes.includes(e)){const i=new PerformanceObserver(e=>{queueMicrotask(()=>{t(e.getEntries())})});return i.observe({type:e,buffered:!0,...n}),i}}catch{}},f=e=>{let t=!1;return()=>{t||(e(),t=!0)}};let l=-1;const u=new Set,m=()=>"hidden"!==document.visibilityState||document.prerendering?1/0:0,g=e=>{if("hidden"===document.visibilityState){if("visibilitychange"===e.type)for(const e of u)e();isFinite(l)||(l="visibilitychange"===e.type?e.timeStamp:0,removeEventListener("prerenderingchange",g,!0))}},p=()=>{if(l<0){const e=o(),n=document.prerendering?void 0:globalThis.performance.getEntriesByType("visibility-state").find(t=>"hidden"===t.name&&t.startTime>=e)?.startTime;l=n??m(),addEventListener("visibilitychange",g,!0),addEventListener("prerenderingchange",g,!0),t(()=>{setTimeout(()=>{l=m()})})}return{get firstHiddenTime(){return l},onHidden(e){u.add(e)}}},v=e=>{document.prerendering?addEventListener("prerenderingchange",e,!0):e()},y=[1800,3e3],T=(e,s={})=>{v(()=>{const c=p();let a,d=r("FCP");const f=h("paint",e=>{for(const t of e)"first-contentful-paint"===t.name&&(f.disconnect(),t.startTime<c.firstHiddenTime&&(d.value=Math.max(t.startTime-o(),0),d.entries.push(t),a(!0)))});f&&(a=n(e,d,y,s.reportAllChanges),t(t=>{d=r("FCP"),a=n(e,d,y,s.reportAllChanges),i(()=>{d.value=performance.now()-t.timeStamp,a(!0)})}))})},E=[.1,.25],b=(e,s={})=>{const o=p();T(f(()=>{let c,f=r("CLS",0);const l=a(s,d),u=e=>{for(const t of e)l.h(t);l.i>f.value&&(f.value=l.i,f.entries=l.o,c())},m=h("layout-shift",u);m&&(c=n(e,f,E,s.reportAllChanges),o.onHidden(()=>{u(m.takeRecords()),c(!0)}),t(()=>{l.i=0,f=r("CLS",0),c=n(e,f,E,s.reportAllChanges),i(c)}),setTimeout(c))}))};let L=0,P=1/0,_=0;const M=e=>{for(const t of e)t.interactionId&&(P=Math.min(P,t.interactionId),_=Math.max(_,t.interactionId),L=_?(_-P)/7+1:0)};let w;const C=()=>w?L:performance.interactionCount??0,I=()=>{"interactionCount"in performance||w||(w=h("event",M,{durationThreshold:0}))};let F=0;class k{l=[];u=new Map;m;p;v(){F=C(),this.l.length=0,this.u.clear()}T(){const e=Math.min(this.l.length-1,Math.floor((C()-F)/50));return this.l[e]}h(e){if(this.m?.(e),!e.interactionId&&"first-input"!==e.entryType)return;const t=this.l.at(-1);let n=this.u.get(e.interactionId);if(n||this.l.length<10||e.duration>t.L){if(n?e.duration>n.L?(n.entries=[e],n.L=e.duration):e.duration===n.L&&e.startTime===n.entries[0].startTime&&n.entries.push(e):(n={id:e.interactionId,entries:[e],L:e.duration},this.u.set(n.id,n),this.l.push(n)),this.l.sort((e,t)=>t.L-e.L),this.l.length>10){const e=this.l.splice(10);for(const t of e)this.u.delete(t.id)}this.p?.(n)}}}const A=e=>{const t=globalThis.requestIdleCallback||setTimeout,n=globalThis.cancelIdleCallback||clearTimeout;if("hidden"===document.visibilityState)e();else{const i=f(e);let s=-1;const o=()=>{n(s),i()};addEventListener("visibilitychange",o,{once:!0,capture:!0}),s=t(()=>{removeEventListener("visibilitychange",o,{capture:!0}),i()})}},B=[200,500],S=(e,i={})=>{if(!globalThis.PerformanceEventTiming||!("interactionId"in PerformanceEventTiming.prototype))return;const s=p();v(()=>{I();let o,c=r("INP");const d=a(i,k),f=e=>{A(()=>{for(const t of e)d.h(t);const t=d.T();t&&t.L!==c.value&&(c.value=t.L,c.entries=t.entries,o())})},l=h("event",f,{durationThreshold:i.durationThreshold??40});o=n(e,c,B,i.reportAllChanges),l&&(l.observe({type:"first-input",buffered:!0}),s.onHidden(()=>{f(l.takeRecords()),o(!0)}),t(()=>{d.v(),c=r("INP"),o=n(e,c,B,i.reportAllChanges)}))})};class q{m;h(e){this.m?.(e)}}const N=[2500,4e3],x=(e,s={})=>{v(()=>{const c=p();let d,l=r("LCP");const u=a(s,q),m=e=>{s.reportAllChanges||(e=e.slice(-1));for(const t of e)u.h(t),t.startTime<c.firstHiddenTime&&(l.value=Math.max(t.startTime-o(),0),l.entries=[t],d())},g=h("largest-contentful-paint",m);if(g){d=n(e,l,N,s.reportAllChanges);const o=f(()=>{m(g.takeRecords()),g.disconnect(),d(!0)}),c=e=>{e.isTrusted&&(A(o),removeEventListener(e.type,c,{capture:!0}))};for(const e of["keydown","click","visibilitychange"])addEventListener(e,c,{capture:!0});t(t=>{l=r("LCP"),d=n(e,l,N,s.reportAllChanges),i(()=>{l.value=performance.now()-t.timeStamp,d(!0)})})}})},H=[800,1800],O=e=>{document.prerendering?v(()=>O(e)):"complete"!==document.readyState?addEventListener("load",()=>O(e),!0):setTimeout(e)},$=(e,i={})=>{let c=r("TTFB"),a=n(e,c,H,i.reportAllChanges);O(()=>{const d=s();d&&(c.value=Math.max(d.responseStart-o(),0),c.entries=[d],a(!0),t(()=>{c=r("TTFB",0),a=n(e,c,H,i.reportAllChanges),a(!0)}))})};export{E as CLSThresholds,y as FCPThresholds,B as INPThresholds,N as LCPThresholds,H as TTFBThresholds,b as onCLS,T as onFCP,S as onINP,x as onLCP,$ as onTTFB}; | ||
| let t=-1;const e=e=>{addEventListener("pageshow",n=>{n.persisted&&(t=n.timeStamp,e(n))},!0)},n=(t,e,n,i)=>{let o,s;return a=>{e.value>=0&&(a||i)&&(s=e.value-(o??0),(s||void 0===o)&&(o=e.value,e.delta=s,e.rating=((t,e)=>t>e[1]?"poor":t>e[0]?"needs-improvement":"good")(e.value,n),t(e)))}},i=t=>{requestAnimationFrame(()=>requestAnimationFrame(t))},o=()=>{const t=performance.getEntriesByType("navigation")[0];if(t&&t.responseStart>0&&t.responseStart<performance.now())return t},s=()=>o()?.activationStart??0;let a=-1;const r=new Set,c=()=>"hidden"!==document.visibilityState||document.prerendering?1/0:0,f=t=>{if("hidden"===document.visibilityState){if("visibilitychange"===t.type)for(const t of r)t();isFinite(a)||(a="visibilitychange"===t.type?t.timeStamp:0,removeEventListener("prerenderingchange",f,!0))}},d=(t=!1)=>{if(t&&(a=1/0),a<0){const t=s(),n=document.prerendering?void 0:globalThis.performance.getEntriesByType("visibility-state").find(e=>"hidden"===e.name&&e.startTime>=t)?.startTime;a=n??c(),addEventListener("visibilitychange",f,!0),addEventListener("prerenderingchange",f,!0),e(()=>{setTimeout(()=>{a=c()})})}return{get firstHiddenTime(){return a},onHidden(t){r.add(t)}}},l=(e,n=-1,i,a,r=0,c,f)=>{const d=o()?.navigationId||0,l=o();let h="navigate";a?h=a:t>=0?h="back-forward-cache":l&&(document.prerendering||s()>0?h="prerender":document.wasDiscarded?h="restore":l.type&&(h=l.type.replace(/_/g,"-")));return{name:e,value:n,rating:"good",delta:0,entries:[],id:`v5-${Date.now()}-${Math.floor(8999999999999*Math.random())+1e12}`,navigationType:h,navigationId:r||d,interactionId:i,navigationURL:c||l?.name,navigationStartTime:f||0}},h=new WeakMap;function u(t,e){let n=h.get(e);return n||(n=new WeakMap,h.set(e,n)),n.get(t)||n.set(t,new e),n.get(t)}class g{t;i=0;o=[];l(t){if(t.hadRecentInput)return;const e=this.o[0],n=this.o.at(-1);this.i&&e&&n&&t.startTime-n.startTime<1e3&&t.startTime-e.startTime<5e3?(this.i+=t.value,this.o.push(t)):(this.i=t.value,this.o=[t]),this.t?.(t)}}const v=t=>PerformanceObserver.supportedEntryTypes.includes("soft-navigation")&&t&&t.reportSoftNavs,m=(t,e,n={})=>{const i=[t],o=v(n);"event"===t&&i.push("first-input"),o&&("largest-contentful-paint"!==t&&"event"!==t&&"layout-shift"!==t||i.push("soft-navigation"),"largest-contentful-paint"===t&&i.push("interaction-contentful-paint"));try{if(PerformanceObserver.supportedEntryTypes.includes(t)){const t=new PerformanceObserver(t=>{queueMicrotask(()=>{const n=t.getEntries();n.sort((t,e)=>t.startTime+t.duration-(e.startTime+e.duration)),e(n)})});for(const e of i)t.observe(Object.assign({type:e,buffered:!0,...n},n||{}));return t}}catch{}},p=t=>{let e=!1;return()=>{e||(t(),e=!0)}},b=t=>{document.prerendering?addEventListener("prerenderingchange",t,!0):t()},P=[1800,3e3],T=(t,o={})=>{const a=v(o=o||{});b(()=>{const r=d();let c,f=l("FCP");const h=t=>{for(const e of t)"first-contentful-paint"===e.name&&(u.disconnect(),e.startTime<r.firstHiddenTime&&(f.value=Math.max(e.startTime-s(),0),f.entries.push(e),f.navigationId=e.navigationId||0,c(!0)))},u=m("paint",h,o);if(u&&(c=n(t,f,P,o.reportAllChanges),e(e=>{f=l("FCP",0,f.interactionId,"back-forward-cache",f.navigationId,f.navigationURL,f.navigationStartTime),c=n(t,f,P,o.reportAllChanges),i(()=>{f.value=performance.now()-e.timeStamp,c(!0)})})),a){m("soft-navigation",e=>{e.forEach(e=>{h(u.takeRecords());const i=(e.presentationTime||e.paintTime||0)-e.startTime;f=l("FCP",i,e.interactionId,"soft-navigation",e.navigationId,e.name,e.startTime),c=n(t,f,P,o.reportAllChanges),c(!0)})},o)}})},y=[.1,.25],L=(t,o={})=>{const s=d();T(p(()=>{let a,r=l("CLS",0);const c=u(o,g),f=(e,i,s,f,d)=>{r=l("CLS",0,e,i,s,f,d),c.i=0,a=n(t,r,y,o.reportAllChanges)},d=t=>{h(v?.takeRecords()),a(!0),f(t.interactionId,"soft-navigation",t.navigationId,t.name,t.startTime)},h=t=>{for(const e of t)"largestInteractionContentfulPaint"in e?d(e):c.l(e);c.i>r.value&&(r.value=c.i,r.entries=c.o,a())},v=m("layout-shift",h,o);v&&(a=n(t,r,y,o.reportAllChanges),s.onHidden(()=>{h(v.takeRecords()),a(!0)}),e(()=>{f(r.interactionId,"back-forward-cache",r.navigationId,r.navigationURL,r.navigationStartTime),i(a)}),setTimeout(a))}))};let E=0,_=1/0,M=0;const w=t=>{for(const e of t)e.interactionId&&(_=Math.min(_,e.interactionId),M=Math.max(M,e.interactionId),E=M?(M-_)/7+1:0)};let I;const C=()=>I?E:performance.interactionCount??0,k=()=>{"interactionCount"in performance||I||(I=m("event",w,{durationThreshold:0}))};let F=0;class B{h=[];u=new Map;v;m;p(){F=C(),this.h.length=0,this.u.clear()}P(t){const e=C()-F,n=Math.min(this.h.length-1,Math.floor(e/50));return e&&-1===n&&"soft-navigation"===t?{T:8,id:-1,entries:[]}:this.h[n]}l(t){if(this.v?.(t),!t.interactionId&&"first-input"!==t.entryType)return;const e=this.h.at(-1);let n=this.u.get(t.interactionId);if(n||this.h.length<10||t.duration>e.T){if(n?t.duration>n.T?(n.entries=[t],n.T=t.duration):t.duration===n.T&&t.startTime===n.entries[0].startTime&&n.entries.push(t):(n={id:t.interactionId,entries:[t],T:t.duration},this.u.set(n.id,n),this.h.push(n)),this.h.sort((t,e)=>e.T-t.T),this.h.length>10){const t=this.h.splice(10);for(const e of t)this.u.delete(e.id)}this.m?.(n)}}}const S=t=>{const e=globalThis.requestIdleCallback||setTimeout,n=globalThis.cancelIdleCallback||clearTimeout;if("hidden"===document.visibilityState)t();else{const i=p(t);let o=-1;const s=()=>{n(o),i()};addEventListener("visibilitychange",s,{once:!0,capture:!0}),o=e(()=>{removeEventListener("visibilitychange",s,{capture:!0}),i()})}},A=[200,500],O=(t,i={})=>{if(!globalThis.PerformanceEventTiming||!("interactionId"in PerformanceEventTiming.prototype))return;const o=d();b(()=>{k();let s,a=l("INP");const r=u(i,B),c=(e,o,c,f,d)=>{r.p(),a=l("INP",-1,e,o,c,f,d),s=n(t,a,A,i.reportAllChanges)},f=()=>{const t=r.P(a.navigationType);t&&t.T!==a.value&&(a.value=t.T,a.entries=t.entries,s())},d=t=>{h(g?.takeRecords()),f(),s(!0),c(t.interactionId,"soft-navigation",t.navigationId,t.name,t.startTime)},h=t=>{S(()=>{if(t.some(t=>t.interactionId)){for(const e of t)"largestInteractionContentfulPaint"in e?d(e):r.l(e);f()}})},g=m("event",h,{...i,durationThreshold:i.durationThreshold??40});s=n(t,a,A,i.reportAllChanges),g&&(o.onHidden(()=>{h(g.takeRecords()),"soft-navigation"===a.navigationType&&f(),s(!0)}),e(()=>{c(a.interactionId,"back-forward-cache",a.navigationId,a.navigationURL,a.navigationStartTime)}))})};class q{v;l(t){this.v?.(t)}}const N=[2500,4e3],x=(t,o={})=>{let a=!1;const r=v(o);b(()=>{let c,f=d(),h=l("LCP");const g=u(o,q),v=(e,i,s,r,u)=>{h=l("LCP",0,i,e,s,r,u),c=n(t,h,N,o.reportAllChanges),a=!1,"soft-navigation"===e&&(f=d(!0))},p=t=>{b(P.takeRecords()),a||c(!0),v("soft-navigation",t.interactionId,t.navigationId,t.name,t.startTime);const e=t.getLargestInteractionContentfulPaint?.()||t.largestInteractionContentfulPaint;e&&b([e])},b=t=>{o.reportAllChanges||r||(t=t.slice(-1));for(const e of t){if(!e)continue;if("largestInteractionContentfulPaint"in e){p(e);continue}let t=0,n=[];if(e instanceof LargestContentfulPaint)t=Math.max(e.startTime-s(),0),g.l(e),n=[e];else{if(!h.navigationId)continue;if("interactionId"in e&&e.interactionId!=h.interactionId)continue;const i=e?.largestContentfulPaint?.renderTime||e.renderTime||0;t=Math.max(i-e.startTime,0),e.largestContentfulPaint&&g.l(e.largestContentfulPaint),e.largestContentfulPaint&&(n=[e.largestContentfulPaint])}e.startTime<f.firstHiddenTime&&(h.value=t,h.entries=n,c())}},P=m("largest-contentful-paint",b,o);if(P){c=n(t,h,N,o.reportAllChanges);const s=t=>{t.isTrusted&&!a&&S(()=>{a||(b(P.takeRecords()),r||(P.disconnect(),removeEventListener(t.type,s)),a=!0,c(!0))})};for(const t of["keydown","click","visibilitychange"])addEventListener(t,s,{capture:!0});e(e=>{v("back-forward-cache",h.interactionId,h.navigationId,h.navigationURL,h.navigationStartTime),c=n(t,h,N,o.reportAllChanges),i(()=>{h.value=performance.now()-e.timeStamp,a=!0,c(!0)})})}})},H=[800,1800],U=t=>{document.prerendering?b(()=>U(t)):"complete"!==document.readyState?addEventListener("load",()=>U(t),!0):setTimeout(t)},W=(t,i={})=>{const a=v(i=i||{});let r=l("TTFB"),c=n(t,r,H,i.reportAllChanges);U(()=>{const f=o();if(f){const o=f.responseStart;if(r.value=Math.max(o-s(),0),r.entries=[f],c(!0),e(()=>{r=l("TTFB",0,r.interactionId,"back-forward-cache",r.navigationId,r.navigationURL,r.navigationStartTime),c=n(t,r,H,i.reportAllChanges),c(!0)}),a){m("soft-navigation",e=>{e.forEach(e=>{e.navigationId&&(r=l("TTFB",0,e.interactionId,"soft-navigation",e.navigationId,e.name,e.startTime),r.entries=[e],c=n(t,r,H,i.reportAllChanges),c(!0))})},i)}}})};export{y as CLSThresholds,P as FCPThresholds,A as INPThresholds,N as LCPThresholds,H as TTFBThresholds,L as onCLS,T as onFCP,O as onINP,x as onLCP,W as onTTFB}; |
@@ -1,1 +0,1 @@ | ||
| !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).webVitals={})}(this,function(e){"use strict";let t=-1;const n=e=>{addEventListener("pageshow",n=>{n.persisted&&(t=n.timeStamp,e(n))},!0)},i=(e,t,n,i)=>{let o,s;return r=>{t.value>=0&&(r||i)&&(s=t.value-(o??0),(s||void 0===o)&&(o=t.value,t.delta=s,t.rating=((e,t)=>e>t[1]?"poor":e>t[0]?"needs-improvement":"good")(t.value,n),e(t)))}},o=e=>{requestAnimationFrame(()=>requestAnimationFrame(e))},s=()=>{const e=performance.getEntriesByType("navigation")[0];if(e&&e.responseStart>0&&e.responseStart<performance.now())return e},r=()=>s()?.activationStart??0,c=(e,n=-1)=>{const i=s();let o="navigate";t>=0?o="back-forward-cache":i&&(document.prerendering||r()>0?o="prerender":document.wasDiscarded?o="restore":i.type&&(o=i.type.replace(/_/g,"-")));return{name:e,value:n,rating:"good",delta:0,entries:[],id:`v5-${Date.now()}-${Math.floor(8999999999999*Math.random())+1e12}`,navigationType:o}},a=new WeakMap;function d(e,t){return a.get(e)||a.set(e,new t),a.get(e)}class f{t;i=0;o=[];h(e){if(e.hadRecentInput)return;const t=this.o[0],n=this.o.at(-1);this.i&&t&&n&&e.startTime-n.startTime<1e3&&e.startTime-t.startTime<5e3?(this.i+=e.value,this.o.push(e)):(this.i=e.value,this.o=[e]),this.t?.(e)}}const h=(e,t,n={})=>{try{if(PerformanceObserver.supportedEntryTypes.includes(e)){const i=new PerformanceObserver(e=>{queueMicrotask(()=>{t(e.getEntries())})});return i.observe({type:e,buffered:!0,...n}),i}}catch{}},l=e=>{let t=!1;return()=>{t||(e(),t=!0)}};let u=-1;const p=new Set,m=()=>"hidden"!==document.visibilityState||document.prerendering?1/0:0,g=e=>{if("hidden"===document.visibilityState){if("visibilitychange"===e.type)for(const e of p)e();isFinite(u)||(u="visibilitychange"===e.type?e.timeStamp:0,removeEventListener("prerenderingchange",g,!0))}},v=()=>{if(u<0){const e=r(),t=document.prerendering?void 0:globalThis.performance.getEntriesByType("visibility-state").find(t=>"hidden"===t.name&&t.startTime>=e)?.startTime;u=t??m(),addEventListener("visibilitychange",g,!0),addEventListener("prerenderingchange",g,!0),n(()=>{setTimeout(()=>{u=m()})})}return{get firstHiddenTime(){return u},onHidden(e){p.add(e)}}},y=e=>{document.prerendering?addEventListener("prerenderingchange",e,!0):e()},T=[1800,3e3],b=(e,t={})=>{y(()=>{const s=v();let a,d=c("FCP");const f=h("paint",e=>{for(const t of e)"first-contentful-paint"===t.name&&(f.disconnect(),t.startTime<s.firstHiddenTime&&(d.value=Math.max(t.startTime-r(),0),d.entries.push(t),a(!0)))});f&&(a=i(e,d,T,t.reportAllChanges),n(n=>{d=c("FCP"),a=i(e,d,T,t.reportAllChanges),o(()=>{d.value=performance.now()-n.timeStamp,a(!0)})}))})},E=[.1,.25];let L=0,P=1/0,_=0;const M=e=>{for(const t of e)t.interactionId&&(P=Math.min(P,t.interactionId),_=Math.max(_,t.interactionId),L=_?(_-P)/7+1:0)};let w;const C=()=>w?L:performance.interactionCount??0,I=()=>{"interactionCount"in performance||w||(w=h("event",M,{durationThreshold:0}))};let F=0;class k{l=[];u=new Map;p;m;v(){F=C(),this.l.length=0,this.u.clear()}T(){const e=Math.min(this.l.length-1,Math.floor((C()-F)/50));return this.l[e]}h(e){if(this.p?.(e),!e.interactionId&&"first-input"!==e.entryType)return;const t=this.l.at(-1);let n=this.u.get(e.interactionId);if(n||this.l.length<10||e.duration>t.L){if(n?e.duration>n.L?(n.entries=[e],n.L=e.duration):e.duration===n.L&&e.startTime===n.entries[0].startTime&&n.entries.push(e):(n={id:e.interactionId,entries:[e],L:e.duration},this.u.set(n.id,n),this.l.push(n)),this.l.sort((e,t)=>t.L-e.L),this.l.length>10){const e=this.l.splice(10);for(const t of e)this.u.delete(t.id)}this.m?.(n)}}}const x=e=>{const t=globalThis.requestIdleCallback||setTimeout,n=globalThis.cancelIdleCallback||clearTimeout;if("hidden"===document.visibilityState)e();else{const i=l(e);let o=-1;const s=()=>{n(o),i()};addEventListener("visibilitychange",s,{once:!0,capture:!0}),o=t(()=>{removeEventListener("visibilitychange",s,{capture:!0}),i()})}},A=[200,500];class B{p;h(e){this.p?.(e)}}const S=[2500,4e3],q=[800,1800],N=e=>{document.prerendering?y(()=>N(e)):"complete"!==document.readyState?addEventListener("load",()=>N(e),!0):setTimeout(e)};e.CLSThresholds=E,e.FCPThresholds=T,e.INPThresholds=A,e.LCPThresholds=S,e.TTFBThresholds=q,e.onCLS=(e,t={})=>{const s=v();b(l(()=>{let r,a=c("CLS",0);const l=d(t,f),u=e=>{for(const t of e)l.h(t);l.i>a.value&&(a.value=l.i,a.entries=l.o,r())},p=h("layout-shift",u);p&&(r=i(e,a,E,t.reportAllChanges),s.onHidden(()=>{u(p.takeRecords()),r(!0)}),n(()=>{l.i=0,a=c("CLS",0),r=i(e,a,E,t.reportAllChanges),o(r)}),setTimeout(r))}))},e.onFCP=b,e.onINP=(e,t={})=>{if(!globalThis.PerformanceEventTiming||!("interactionId"in PerformanceEventTiming.prototype))return;const o=v();y(()=>{I();let s,r=c("INP");const a=d(t,k),f=e=>{x(()=>{for(const t of e)a.h(t);const t=a.T();t&&t.L!==r.value&&(r.value=t.L,r.entries=t.entries,s())})},l=h("event",f,{durationThreshold:t.durationThreshold??40});s=i(e,r,A,t.reportAllChanges),l&&(l.observe({type:"first-input",buffered:!0}),o.onHidden(()=>{f(l.takeRecords()),s(!0)}),n(()=>{a.v(),r=c("INP"),s=i(e,r,A,t.reportAllChanges)}))})},e.onLCP=(e,t={})=>{y(()=>{const s=v();let a,f=c("LCP");const u=d(t,B),p=e=>{t.reportAllChanges||(e=e.slice(-1));for(const t of e)u.h(t),t.startTime<s.firstHiddenTime&&(f.value=Math.max(t.startTime-r(),0),f.entries=[t],a())},m=h("largest-contentful-paint",p);if(m){a=i(e,f,S,t.reportAllChanges);const s=l(()=>{p(m.takeRecords()),m.disconnect(),a(!0)}),r=e=>{e.isTrusted&&(x(s),removeEventListener(e.type,r,{capture:!0}))};for(const e of["keydown","click","visibilitychange"])addEventListener(e,r,{capture:!0});n(n=>{f=c("LCP"),a=i(e,f,S,t.reportAllChanges),o(()=>{f.value=performance.now()-n.timeStamp,a(!0)})})}})},e.onTTFB=(e,t={})=>{let o=c("TTFB"),a=i(e,o,q,t.reportAllChanges);N(()=>{const d=s();d&&(o.value=Math.max(d.responseStart-r(),0),o.entries=[d],a(!0),n(()=>{o=c("TTFB",0),a=i(e,o,q,t.reportAllChanges),a(!0)}))})}}); | ||
| !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).webVitals={})}(this,function(t){"use strict";let e=-1;const n=t=>{addEventListener("pageshow",n=>{n.persisted&&(e=n.timeStamp,t(n))},!0)},i=(t,e,n,i)=>{let o,s;return r=>{e.value>=0&&(r||i)&&(s=e.value-(o??0),(s||void 0===o)&&(o=e.value,e.delta=s,e.rating=((t,e)=>t>e[1]?"poor":t>e[0]?"needs-improvement":"good")(e.value,n),t(e)))}},o=t=>{requestAnimationFrame(()=>requestAnimationFrame(t))},s=()=>{const t=performance.getEntriesByType("navigation")[0];if(t&&t.responseStart>0&&t.responseStart<performance.now())return t},r=()=>s()?.activationStart??0;let a=-1;const c=new Set,f=()=>"hidden"!==document.visibilityState||document.prerendering?1/0:0,d=t=>{if("hidden"===document.visibilityState){if("visibilitychange"===t.type)for(const t of c)t();isFinite(a)||(a="visibilitychange"===t.type?t.timeStamp:0,removeEventListener("prerenderingchange",d,!0))}},l=(t=!1)=>{if(t&&(a=1/0),a<0){const t=r(),e=document.prerendering?void 0:globalThis.performance.getEntriesByType("visibility-state").find(e=>"hidden"===e.name&&e.startTime>=t)?.startTime;a=e??f(),addEventListener("visibilitychange",d,!0),addEventListener("prerenderingchange",d,!0),n(()=>{setTimeout(()=>{a=f()})})}return{get firstHiddenTime(){return a},onHidden(t){c.add(t)}}},h=(t,n=-1,i,o,a=0,c,f)=>{const d=s()?.navigationId||0,l=s();let h="navigate";o?h=o:e>=0?h="back-forward-cache":l&&(document.prerendering||r()>0?h="prerender":document.wasDiscarded?h="restore":l.type&&(h=l.type.replace(/_/g,"-")));return{name:t,value:n,rating:"good",delta:0,entries:[],id:`v5-${Date.now()}-${Math.floor(8999999999999*Math.random())+1e12}`,navigationType:h,navigationId:a||d,interactionId:i,navigationURL:c||l?.name,navigationStartTime:f||0}},u=new WeakMap;function g(t,e){let n=u.get(e);return n||(n=new WeakMap,u.set(e,n)),n.get(t)||n.set(t,new e),n.get(t)}class v{t;i=0;o=[];l(t){if(t.hadRecentInput)return;const e=this.o[0],n=this.o.at(-1);this.i&&e&&n&&t.startTime-n.startTime<1e3&&t.startTime-e.startTime<5e3?(this.i+=t.value,this.o.push(t)):(this.i=t.value,this.o=[t]),this.t?.(t)}}const p=t=>PerformanceObserver.supportedEntryTypes.includes("soft-navigation")&&t&&t.reportSoftNavs,m=(t,e,n={})=>{const i=[t],o=p(n);"event"===t&&i.push("first-input"),o&&("largest-contentful-paint"!==t&&"event"!==t&&"layout-shift"!==t||i.push("soft-navigation"),"largest-contentful-paint"===t&&i.push("interaction-contentful-paint"));try{if(PerformanceObserver.supportedEntryTypes.includes(t)){const t=new PerformanceObserver(t=>{queueMicrotask(()=>{const n=t.getEntries();n.sort((t,e)=>t.startTime+t.duration-(e.startTime+e.duration)),e(n)})});for(const e of i)t.observe(Object.assign({type:e,buffered:!0,...n},n||{}));return t}}catch{}},b=t=>{let e=!1;return()=>{e||(t(),e=!0)}},y=t=>{document.prerendering?addEventListener("prerenderingchange",t,!0):t()},T=[1800,3e3],P=(t,e={})=>{const s=p(e=e||{});y(()=>{const a=l();let c,f=h("FCP");const d=t=>{for(const e of t)"first-contentful-paint"===e.name&&(u.disconnect(),e.startTime<a.firstHiddenTime&&(f.value=Math.max(e.startTime-r(),0),f.entries.push(e),f.navigationId=e.navigationId||0,c(!0)))},u=m("paint",d,e);if(u&&(c=i(t,f,T,e.reportAllChanges),n(n=>{f=h("FCP",0,f.interactionId,"back-forward-cache",f.navigationId,f.navigationURL,f.navigationStartTime),c=i(t,f,T,e.reportAllChanges),o(()=>{f.value=performance.now()-n.timeStamp,c(!0)})})),s){m("soft-navigation",n=>{n.forEach(n=>{d(u.takeRecords());const o=(n.presentationTime||n.paintTime||0)-n.startTime;f=h("FCP",o,n.interactionId,"soft-navigation",n.navigationId,n.name,n.startTime),c=i(t,f,T,e.reportAllChanges),c(!0)})},e)}})},L=[.1,.25];let E=0,_=1/0,M=0;const w=t=>{for(const e of t)e.interactionId&&(_=Math.min(_,e.interactionId),M=Math.max(M,e.interactionId),E=M?(M-_)/7+1:0)};let I;const C=()=>I?E:performance.interactionCount??0,k=()=>{"interactionCount"in performance||I||(I=m("event",w,{durationThreshold:0}))};let F=0;class B{h=[];u=new Map;v;p;m(){F=C(),this.h.length=0,this.u.clear()}T(t){const e=C()-F,n=Math.min(this.h.length-1,Math.floor(e/50));return e&&-1===n&&"soft-navigation"===t?{P:8,id:-1,entries:[]}:this.h[n]}l(t){if(this.v?.(t),!t.interactionId&&"first-input"!==t.entryType)return;const e=this.h.at(-1);let n=this.u.get(t.interactionId);if(n||this.h.length<10||t.duration>e.P){if(n?t.duration>n.P?(n.entries=[t],n.P=t.duration):t.duration===n.P&&t.startTime===n.entries[0].startTime&&n.entries.push(t):(n={id:t.interactionId,entries:[t],P:t.duration},this.u.set(n.id,n),this.h.push(n)),this.h.sort((t,e)=>e.P-t.P),this.h.length>10){const t=this.h.splice(10);for(const e of t)this.u.delete(e.id)}this.p?.(n)}}}const S=t=>{const e=globalThis.requestIdleCallback||setTimeout,n=globalThis.cancelIdleCallback||clearTimeout;if("hidden"===document.visibilityState)t();else{const i=b(t);let o=-1;const s=()=>{n(o),i()};addEventListener("visibilitychange",s,{once:!0,capture:!0}),o=e(()=>{removeEventListener("visibilitychange",s,{capture:!0}),i()})}},x=[200,500];class A{v;l(t){this.v?.(t)}}const O=[2500,4e3],q=[800,1800],N=t=>{document.prerendering?y(()=>N(t)):"complete"!==document.readyState?addEventListener("load",()=>N(t),!0):setTimeout(t)};t.CLSThresholds=L,t.FCPThresholds=T,t.INPThresholds=x,t.LCPThresholds=O,t.TTFBThresholds=q,t.onCLS=(t,e={})=>{const s=l();P(b(()=>{let r,a=h("CLS",0);const c=g(e,v),f=(n,o,s,f,d)=>{a=h("CLS",0,n,o,s,f,d),c.i=0,r=i(t,a,L,e.reportAllChanges)},d=t=>{l(u?.takeRecords()),r(!0),f(t.interactionId,"soft-navigation",t.navigationId,t.name,t.startTime)},l=t=>{for(const e of t)"largestInteractionContentfulPaint"in e?d(e):c.l(e);c.i>a.value&&(a.value=c.i,a.entries=c.o,r())},u=m("layout-shift",l,e);u&&(r=i(t,a,L,e.reportAllChanges),s.onHidden(()=>{l(u.takeRecords()),r(!0)}),n(()=>{f(a.interactionId,"back-forward-cache",a.navigationId,a.navigationURL,a.navigationStartTime),o(r)}),setTimeout(r))}))},t.onFCP=P,t.onINP=(t,e={})=>{if(!globalThis.PerformanceEventTiming||!("interactionId"in PerformanceEventTiming.prototype))return;const o=l();y(()=>{k();let s,r=h("INP");const a=g(e,B),c=(n,o,c,f,d)=>{a.m(),r=h("INP",-1,n,o,c,f,d),s=i(t,r,x,e.reportAllChanges)},f=()=>{const t=a.T(r.navigationType);t&&t.P!==r.value&&(r.value=t.P,r.entries=t.entries,s())},d=t=>{l(u?.takeRecords()),f(),s(!0),c(t.interactionId,"soft-navigation",t.navigationId,t.name,t.startTime)},l=t=>{S(()=>{if(t.some(t=>t.interactionId)){for(const e of t)"largestInteractionContentfulPaint"in e?d(e):a.l(e);f()}})},u=m("event",l,{...e,durationThreshold:e.durationThreshold??40});s=i(t,r,x,e.reportAllChanges),u&&(o.onHidden(()=>{l(u.takeRecords()),"soft-navigation"===r.navigationType&&f(),s(!0)}),n(()=>{c(r.interactionId,"back-forward-cache",r.navigationId,r.navigationURL,r.navigationStartTime)}))})},t.onLCP=(t,e={})=>{let s=!1;const a=p(e);y(()=>{let c,f=l(),d=h("LCP");const u=g(e,A),v=(n,o,r,a,u)=>{d=h("LCP",0,o,n,r,a,u),c=i(t,d,O,e.reportAllChanges),s=!1,"soft-navigation"===n&&(f=l(!0))},p=t=>{b(y.takeRecords()),s||c(!0),v("soft-navigation",t.interactionId,t.navigationId,t.name,t.startTime);const e=t.getLargestInteractionContentfulPaint?.()||t.largestInteractionContentfulPaint;e&&b([e])},b=t=>{e.reportAllChanges||a||(t=t.slice(-1));for(const e of t){if(!e)continue;if("largestInteractionContentfulPaint"in e){p(e);continue}let t=0,n=[];if(e instanceof LargestContentfulPaint)t=Math.max(e.startTime-r(),0),u.l(e),n=[e];else{if(!d.navigationId)continue;if("interactionId"in e&&e.interactionId!=d.interactionId)continue;const i=e?.largestContentfulPaint?.renderTime||e.renderTime||0;t=Math.max(i-e.startTime,0),e.largestContentfulPaint&&u.l(e.largestContentfulPaint),e.largestContentfulPaint&&(n=[e.largestContentfulPaint])}e.startTime<f.firstHiddenTime&&(d.value=t,d.entries=n,c())}},y=m("largest-contentful-paint",b,e);if(y){c=i(t,d,O,e.reportAllChanges);const r=t=>{t.isTrusted&&!s&&S(()=>{s||(b(y.takeRecords()),a||(y.disconnect(),removeEventListener(t.type,r)),s=!0,c(!0))})};for(const t of["keydown","click","visibilitychange"])addEventListener(t,r,{capture:!0});n(n=>{v("back-forward-cache",d.interactionId,d.navigationId,d.navigationURL,d.navigationStartTime),c=i(t,d,O,e.reportAllChanges),o(()=>{d.value=performance.now()-n.timeStamp,s=!0,c(!0)})})}})},t.onTTFB=(t,e={})=>{const o=p(e=e||{});let a=h("TTFB"),c=i(t,a,q,e.reportAllChanges);N(()=>{const f=s();if(f){const s=f.responseStart;if(a.value=Math.max(s-r(),0),a.entries=[f],c(!0),n(()=>{a=h("TTFB",0,a.interactionId,"back-forward-cache",a.navigationId,a.navigationURL,a.navigationStartTime),c=i(t,a,q,e.reportAllChanges),c(!0)}),o){m("soft-navigation",n=>{n.forEach(n=>{n.navigationId&&(a=h("TTFB",0,n.interactionId,"soft-navigation",n.navigationId,n.name,n.startTime),a.entries=[n],c=i(t,a,q,e.reportAllChanges),c(!0))})},e)}}})}}); |
+2
-6
| { | ||
| "name": "web-vitals", | ||
| "version": "5.2.0", | ||
| "version": "5.3.0-soft-navs", | ||
| "description": "Easily measure performance metrics in JavaScript", | ||
@@ -93,2 +93,3 @@ "publishConfig": { | ||
| "release:rc": "npm version prerelease --preid=rc -m 'Release v%s' && npm publish --tag next", | ||
| "release:soft-navs": "npm run build && npm publish --tag soft-navs", | ||
| "test": "npm-run-all build test:unit -p -r test:e2e test:server", | ||
@@ -129,7 +130,2 @@ "test:e2e": "wdio", | ||
| }, | ||
| "husky": { | ||
| "hooks": { | ||
| "pre-commit": "npm run lint" | ||
| } | ||
| }, | ||
| "prettier": { | ||
@@ -136,0 +132,0 @@ "arrowParens": "always", |
+91
-4
@@ -255,2 +255,62 @@ # `web-vitals` | ||
| ### Report metrics for soft navigations (experimental) | ||
| _**Note:** this is experimental and subject to change._ | ||
| Currently Core Web Vitals are only tracked for full page navigations, which can affect how [Single Page Applications](https://web.dev/vitals-spa-faq/) that use so called "soft navigations" to update the browser URL and history outside of the normal browser's handling of this. The Chrome team are experimenting with being able to [measure these soft navigations](https://github.com/WICG/soft-navigations) separately and report on Core Web Vitals separately for them. | ||
| This experimental support allows sites to measure how their Core Web Vitals might be measured differently should this happen. | ||
| At present a "soft navigation" is defined as happening after the following three things happen: | ||
| - A user interaction occurs | ||
| - The URL changes | ||
| - Content is added to the DOM | ||
| - Something is painted to screen. | ||
| For some sites, these heuristics may lead to false positives (that users would not really consider a "navigation"), or false negatives (where the user does consider a navigation to have happened despite not missing the above criteria). We welcome feedback at https://github.com/WICG/soft-navigations/issues on the heuristics, at https://crbug.com for bugs in the Chrome implementation, and on [https://github.com/GoogleChrome/web-vitals/pull/308](this pull request) for implementation issues with web-vitals.js. | ||
| _**Note:** At this time it is not known if this experiment will be something we want to move forward with. Until such time, this support will likely remain in a separate branch of this project, rather than be included in any production builds. If we decide not to move forward with this, the support of this will likely be removed from this project since this library is intended to mirror the Core Web Vitals as much as possible._ | ||
| Some important points to note: | ||
| - TTFB is reported as 0, and not the time of the first network call (if any) after the soft navigation. | ||
| - FCP and LCP are the first and largest contentful paints after the soft navigation. Prior reported paint times will not be counted for these metrics, even though these elements may remain between soft navigations, and may be the first or largest contentful item. | ||
| - INP is reset to measure only interactions after the the soft navigation. | ||
| - CLS is reset to measure again separate to the first page. | ||
| _**Note:** It is not known at this time whether soft navigations will be weighted the same as full navigations. No weighting is included in this library at present and metrics are reported in the same way as full page load metrics._ | ||
| The metrics can be reported for Soft Navigations using the `reportSoftNavs: true` reporting option: | ||
| ```js | ||
| import { | ||
| onCLS, | ||
| onINP, | ||
| onLCP, | ||
| } from 'https://unpkg.com/web-vitals@soft-navs/dist/web-vitals.js?module'; | ||
| onCLS(console.log, {reportSoftNavs: true}); | ||
| onINP(console.log, {reportSoftNavs: true}); | ||
| onLCP(console.log, {reportSoftNavs: true}); | ||
| ``` | ||
| Note that this will change the way the first page loads are measured as the metrics for the inital URL will be finalized once the first soft nav occurs. To measure both you need to register two callbacks: | ||
| ```js | ||
| import { | ||
| onCLS, | ||
| onINP, | ||
| onLCP, | ||
| } from 'https://unpkg.com/web-vitals@soft-navs/dist/web-vitals.js?module'; | ||
| onCLS(doTraditionalProcessing); | ||
| onINP(doTraditionalProcessing); | ||
| onLCP(doTraditionalProcessing); | ||
| onCLS(doSoftNavProcessing, {reportSoftNavs: true}); | ||
| onINP(doSoftNavProcessing, {reportSoftNavs: true}); | ||
| onLCP(doSoftNavProcessing, {reportSoftNavs: true}); | ||
| ``` | ||
| ### Send the results to an analytics endpoint | ||
@@ -533,2 +593,7 @@ | ||
| /** | ||
| * The interactionId that started this interaction for soft navigations | ||
| */ | ||
| interactionId?: number; | ||
| /** | ||
| * Any performance entries relevant to the metric value calculation. | ||
@@ -551,2 +616,3 @@ * The array may also be empty if the metric value was not based on any | ||
| * restored by the user. | ||
| * - 'soft-navigation': for soft navigations. | ||
| */ | ||
@@ -559,3 +625,22 @@ navigationType: | ||
| | 'prerender' | ||
| | 'restore'; | ||
| | 'restore' | ||
| | 'soft-navigation'; | ||
| /** | ||
| * The navigationId the metric happened for. This is particularly relevant for soft navigations where | ||
| * the metric may be reported for a previous URL. | ||
| */ | ||
| navigationId: number; | ||
| /** | ||
| * The navigation startTime the metric is based from. This is particularly | ||
| * relevant for soft navigations where time origin is not 0. | ||
| */ | ||
| navigationStartTime?: number; | ||
| /** | ||
| * The navigation URL the metric happened for. This is particularly relevant for soft navigations where | ||
| * the metric may be reported for a previous URL. | ||
| */ | ||
| navigationURL?: string; | ||
| } | ||
@@ -598,3 +683,3 @@ ``` | ||
| name: 'LCP'; | ||
| entries: LargestContentfulPaint[]; | ||
| entries: (LargestContentfulPaint | InteractionContentfulPaint)[]; | ||
| } | ||
@@ -668,2 +753,3 @@ ``` | ||
| includeProcessedEventEntries?: boolean; | ||
| reportSoftNavs?: boolean; | ||
| } | ||
@@ -1100,5 +1186,6 @@ ``` | ||
| /** | ||
| * The `LargestContentfulPaint` entry corresponding to LCP. | ||
| * The `LargestContentfulPaint` entry corresponding to LCP | ||
| * (or `InteractionContentfulPaint` for soft navigations). | ||
| */ | ||
| lcpEntry?: LargestContentfulPaint; | ||
| lcpEntry?: LargestContentfulPaint | InteractionContentfulPaint; | ||
| } | ||
@@ -1105,0 +1192,0 @@ ``` |
@@ -29,2 +29,3 @@ /* | ||
| const attributeFCP = (metric: FCPMetric): FCPMetricWithAttribution => { | ||
| const hardNavId = getNavigationEntry()?.navigationId || 0; | ||
| // Use a default object if no other attribution has been set. | ||
@@ -38,9 +39,17 @@ let attribution: FCPAttribution = { | ||
| if (metric.entries.length) { | ||
| const navigationEntry = getNavigationEntry(); | ||
| let navigationEntry; | ||
| const fcpEntry = metric.entries.at(-1); | ||
| let ttfb = 0; | ||
| // For hard navs use an actual TTFB | ||
| if (!metric.navigationId || metric.navigationId === hardNavId) { | ||
| navigationEntry = getNavigationEntry(); | ||
| if (navigationEntry) { | ||
| const responseStart = navigationEntry.responseStart; | ||
| const activationStart = navigationEntry.activationStart || 0; | ||
| ttfb = Math.max(0, responseStart - activationStart); | ||
| } | ||
| } | ||
| if (navigationEntry) { | ||
| const activationStart = navigationEntry.activationStart || 0; | ||
| const ttfb = Math.max(0, navigationEntry.responseStart - activationStart); | ||
| attribution = { | ||
@@ -47,0 +56,0 @@ timeToFirstByte: ttfb, |
@@ -133,6 +133,15 @@ /* | ||
| if (!interactionTargetMap.get(interaction)) { | ||
| const node = interaction.entries[0].target; | ||
| // Use find to get first selector | ||
| const node = interaction.entries.find((e) => e.target)?.target; | ||
| if (node) { | ||
| const customTarget = opts.generateTarget?.(node) ?? getSelector(node); | ||
| interactionTargetMap.set(interaction, customTarget); | ||
| } else { | ||
| // Fall back to targetSelector | ||
| const selector = interaction.entries.find( | ||
| (e) => e.targetSelector, | ||
| )?.targetSelector; | ||
| if (selector) { | ||
| interactionTargetMap.set(interaction, selector); | ||
| } | ||
| } | ||
@@ -378,2 +387,36 @@ } | ||
| const attributeINP = (metric: INPMetric): INPMetricWithAttribution => { | ||
| // Soft navs can have a dummy INP as no first-input entry to fall back on | ||
| // See https://github.com/GoogleChrome/web-vitals/issues/724 | ||
| if ( | ||
| metric.entries.length === 0 && | ||
| metric.navigationType === 'soft-navigation' | ||
| ) { | ||
| const softNavEntryStartTime = metric.navigationStartTime; | ||
| if (softNavEntryStartTime) { | ||
| // For simplicity make some assumptions for values we can't get | ||
| // to avoid undefined/null values which would be unexpected. | ||
| // - Assume interactionType as pointer as the most common | ||
| // - Assume interactionTime of soft nav start time | ||
| // - Assume nextPaintTime as interactionTime + length | ||
| const attribution: INPAttribution = { | ||
| interactionTarget: '', | ||
| interactionType: 'pointer', | ||
| interactionTime: softNavEntryStartTime, | ||
| nextPaintTime: softNavEntryStartTime + metric.value, | ||
| processedEventEntries: [], | ||
| longAnimationFrameEntries: [], | ||
| inputDelay: 0, | ||
| processingDuration: 0, | ||
| presentationDelay: metric.value, | ||
| loadState: getLoadState(softNavEntryStartTime), | ||
| longestScript: undefined, | ||
| totalScriptDuration: undefined, | ||
| totalStyleAndLayoutDuration: undefined, | ||
| totalPaintDuration: undefined, | ||
| totalUnattributedDuration: undefined, | ||
| }; | ||
| return Object.assign(metric, {attribution}); | ||
| } | ||
| } | ||
| const firstEntry = metric.entries[0]; | ||
@@ -441,3 +484,3 @@ const group = entryToEntriesGroupMap.get(firstEntry)!; | ||
| // Start observing LoAF entries for attribution. | ||
| observe('long-animation-frame', handleLoAFEntries); | ||
| observe('long-animation-frame', handleLoAFEntries, opts); | ||
@@ -444,0 +487,0 @@ unattributedOnINP((metric: INPMetric) => { |
@@ -51,3 +51,6 @@ /* | ||
| const lcpEntryManager = initUnique(opts, LCPEntryManager); | ||
| const lcpTargetMap: WeakMap<LargestContentfulPaint, string> = new WeakMap(); | ||
| const lcpTargetMap: WeakMap< | ||
| LargestContentfulPaint | InteractionContentfulPaint, | ||
| string | ||
| > = new WeakMap(); | ||
@@ -69,2 +72,3 @@ lcpEntryManager._onBeforeProcessingEntry = ( | ||
| const attributeLCP = (metric: LCPMetric): LCPMetricWithAttribution => { | ||
| const hardNavId = getNavigationEntry()?.navigationId || 0; | ||
| // Use a default object if no other attribution has been set. | ||
@@ -99,11 +103,24 @@ let attribution: LCPAttribution = { | ||
| // Safari seems to fail to find a navigation entry. | ||
| const navigationEntry = getNavigationEntry(); | ||
| let navigationEntry; | ||
| let activationStart = 0; | ||
| let responseStart = 0; | ||
| if (!metric.navigationId || metric.navigationId === hardNavId) { | ||
| navigationEntry = getNavigationEntry(); | ||
| activationStart = | ||
| navigationEntry && navigationEntry.activationStart | ||
| ? navigationEntry.activationStart | ||
| : 0; | ||
| responseStart = | ||
| navigationEntry && navigationEntry.responseStart | ||
| ? navigationEntry.responseStart | ||
| : 0; | ||
| } else { | ||
| // Set activationStart to the SoftNav start time | ||
| activationStart = metric.navigationStartTime || 0; | ||
| } | ||
| if (navigationEntry) { | ||
| const activationStart = navigationEntry.activationStart || 0; | ||
| const ttfb = Math.max(0, responseStart - activationStart); | ||
| const ttfb = Math.max( | ||
| 0, | ||
| navigationEntry.responseStart - activationStart, | ||
| ); | ||
| const lcpRequestStart = Math.max( | ||
@@ -121,6 +138,7 @@ ttfb, | ||
| Math.max( | ||
| lcpRequestStart, | ||
| lcpRequestStart - activationStart, | ||
| lcpResourceEntry | ||
| ? lcpResourceEntry.responseEnd - activationStart | ||
| : 0, | ||
| 0, | ||
| ), | ||
@@ -127,0 +145,0 @@ ); |
@@ -36,3 +36,6 @@ /* | ||
| if (metric.entries.length) { | ||
| const navigationEntry = metric.entries[0]; | ||
| // Is there a better way to check if this is a soft nav entry or not? | ||
| // Refuses to build without this as soft navs don't have activationStart | ||
| const navigationEntry = <PerformanceNavigationTiming>metric.entries[0]; | ||
| const activationStart = navigationEntry.activationStart || 0; | ||
@@ -44,3 +47,3 @@ | ||
| const waitEnd = Math.max( | ||
| (navigationEntry.workerStart || navigationEntry.fetchStart) - | ||
| (navigationEntry.workerStart || navigationEntry.fetchStart || 0) - | ||
| activationStart, | ||
@@ -50,11 +53,11 @@ 0, | ||
| const dnsStart = Math.max( | ||
| navigationEntry.domainLookupStart - activationStart, | ||
| navigationEntry.domainLookupStart - activationStart || 0, | ||
| 0, | ||
| ); | ||
| const connectStart = Math.max( | ||
| navigationEntry.connectStart - activationStart, | ||
| navigationEntry.connectStart - activationStart || 0, | ||
| 0, | ||
| ); | ||
| const connectEnd = Math.max( | ||
| navigationEntry.connectEnd - activationStart, | ||
| navigationEntry.connectEnd - activationStart || 0, | ||
| 0, | ||
@@ -61,0 +64,0 @@ ); |
+10
-11
@@ -26,10 +26,10 @@ /* | ||
| } | ||
| const navigationEntry = getNavigationEntry(); | ||
| if (navigationEntry) { | ||
| if (timestamp < navigationEntry.domInteractive) { | ||
| const hardNavEntry = getNavigationEntry(); | ||
| if (hardNavEntry) { | ||
| if (timestamp < hardNavEntry.domInteractive) { | ||
| return 'loading'; | ||
| } | ||
| if ( | ||
| navigationEntry.domContentLoadedEventStart === 0 || | ||
| timestamp < navigationEntry.domContentLoadedEventStart | ||
| } else if ( | ||
| hardNavEntry.domContentLoadedEventStart === 0 || | ||
| timestamp < hardNavEntry.domContentLoadedEventStart | ||
| ) { | ||
@@ -39,6 +39,5 @@ // If the `domContentLoadedEventStart` timestamp has not yet been | ||
| return 'dom-interactive'; | ||
| } | ||
| if ( | ||
| navigationEntry.domComplete === 0 || | ||
| timestamp < navigationEntry.domComplete | ||
| } else if ( | ||
| hardNavEntry.domComplete === 0 || | ||
| timestamp < hardNavEntry.domComplete | ||
| ) { | ||
@@ -45,0 +44,0 @@ // If the `domComplete` timestamp has not yet been |
@@ -63,3 +63,6 @@ /* | ||
| export const getVisibilityWatcher = () => { | ||
| export const getVisibilityWatcher = (reset = false) => { | ||
| if (reset) { | ||
| firstHiddenTime = Infinity; | ||
| } | ||
| if (firstHiddenTime < 0) { | ||
@@ -66,0 +69,0 @@ // Check if we have a previous hidden `visibility-state` performance entry. |
@@ -26,9 +26,18 @@ /* | ||
| value: number = -1, | ||
| interactionId?: number, | ||
| navigation?: MetricType['navigationType'], | ||
| navigationId: number = 0, | ||
| navigationURL?: string, | ||
| navigationStartTime?: number, | ||
| ) => { | ||
| const navEntry = getNavigationEntry(); | ||
| const hardNavId = getNavigationEntry()?.navigationId || 0; | ||
| const hardNavEntry = getNavigationEntry(); | ||
| let navigationType: MetricType['navigationType'] = 'navigate'; | ||
| if (getBFCacheRestoreTime() >= 0) { | ||
| if (navigation) { | ||
| // If it was passed in, then use that | ||
| navigationType = navigation; | ||
| } else if (getBFCacheRestoreTime() >= 0) { | ||
| navigationType = 'back-forward-cache'; | ||
| } else if (navEntry) { | ||
| } else if (hardNavEntry) { | ||
| if (document.prerendering || getActivationStart() > 0) { | ||
@@ -38,4 +47,4 @@ navigationType = 'prerender'; | ||
| navigationType = 'restore'; | ||
| } else if (navEntry.type) { | ||
| navigationType = navEntry.type.replace( | ||
| } else if (hardNavEntry.type) { | ||
| navigationType = hardNavEntry.type.replace( | ||
| /_/g, | ||
@@ -58,3 +67,7 @@ '-', | ||
| navigationType, | ||
| navigationId: navigationId || hardNavId, | ||
| interactionId: interactionId, | ||
| navigationURL: navigationURL || hardNavEntry?.name, | ||
| navigationStartTime: navigationStartTime || 0, | ||
| }; | ||
| }; |
@@ -17,3 +17,6 @@ /* | ||
| const instanceMap: WeakMap<object, unknown> = new WeakMap(); | ||
| const instanceMap: WeakMap< | ||
| new () => unknown, | ||
| WeakMap<object, unknown> | ||
| > = new WeakMap(); | ||
@@ -26,6 +29,11 @@ /** | ||
| export function initUnique<T>(identityObj: object, ClassObj: new () => T): T { | ||
| if (!instanceMap.get(identityObj)) { | ||
| instanceMap.set(identityObj, new ClassObj()); | ||
| let classInstances = instanceMap.get(ClassObj); | ||
| if (!classInstances) { | ||
| classInstances = new WeakMap(); | ||
| instanceMap.set(ClassObj, classInstances); | ||
| } | ||
| return instanceMap.get(identityObj)! as T; | ||
| if (!classInstances.get(identityObj)) { | ||
| classInstances.set(identityObj, new ClassObj()); | ||
| } | ||
| return classInstances.get(identityObj)! as T; | ||
| } |
@@ -18,2 +18,3 @@ /* | ||
| import {getInteractionCount} from './polyfills/interactionCountPolyfill.js'; | ||
| import {Metric} from '../types.js'; | ||
@@ -74,8 +75,24 @@ export interface Interaction { | ||
| */ | ||
| _estimateP98LongestInteraction() { | ||
| _estimateP98LongestInteraction(navigationType: Metric['navigationType']) { | ||
| const interactionCountForNavigation = getInteractionCountForNavigation(); | ||
| const candidateInteractionIndex = Math.min( | ||
| this._longestInteractionList.length - 1, | ||
| Math.floor(getInteractionCountForNavigation() / 50), | ||
| Math.floor(interactionCountForNavigation / 50), | ||
| ); | ||
| // If we have a non-zero interactionCountForNavigation but no | ||
| // candidateInteractionIndex, then it's below the 16ms limit | ||
| // so report a dummy 8ms interaction. | ||
| if ( | ||
| interactionCountForNavigation && | ||
| candidateInteractionIndex === -1 && | ||
| navigationType === 'soft-navigation' | ||
| ) { | ||
| return { | ||
| _latency: 8, | ||
| id: -1, | ||
| entries: [], | ||
| }; | ||
| } | ||
| return this._longestInteractionList[candidateInteractionIndex]; | ||
@@ -82,0 +99,0 @@ } |
+49
-2
@@ -17,5 +17,8 @@ /* | ||
| import {checkSoftNavsEnabled} from './softNavs.js'; | ||
| interface PerformanceEntryMap { | ||
| 'event': PerformanceEventTiming[]; | ||
| 'first-input': PerformanceEventTiming[]; | ||
| 'interaction-contentful-paint': InteractionContentfulPaint[]; | ||
| 'layout-shift': LayoutShift[]; | ||
@@ -27,2 +30,3 @@ 'largest-contentful-paint': LargestContentfulPaint[]; | ||
| 'resource': PerformanceResourceTiming[]; | ||
| 'soft-navigation': SoftNavigationEntry[]; | ||
| } | ||
@@ -43,2 +47,23 @@ | ||
| ): PerformanceObserver | undefined => { | ||
| // Observe extra types if using SoftNavs | ||
| const typesToMonitor: (keyof PerformanceEntryMap)[] = [type]; | ||
| const reportSoftNavs = checkSoftNavsEnabled(opts); | ||
| if (type === 'event') { | ||
| // Also observe entries of type `first-input`. This is useful in cases | ||
| // where the first interaction is less than the `durationThreshold`. | ||
| typesToMonitor.push('first-input'); | ||
| } | ||
| if (reportSoftNavs) { | ||
| if ( | ||
| type === 'largest-contentful-paint' || | ||
| type === 'event' || | ||
| type === 'layout-shift' | ||
| ) { | ||
| typesToMonitor.push('soft-navigation'); | ||
| } | ||
| if (type === 'largest-contentful-paint') { | ||
| typesToMonitor.push('interaction-contentful-paint'); | ||
| } | ||
| } | ||
| try { | ||
@@ -51,6 +76,28 @@ if (PerformanceObserver.supportedEntryTypes.includes(type)) { | ||
| queueMicrotask(() => { | ||
| callback(list.getEntries() as PerformanceEntryMap[K]); | ||
| // sort entries to ensure they're in the right order | ||
| const entries = list.getEntries(); | ||
| // Best to sort by end time (startTime + duration) | ||
| // See: https://github.com/w3c/performance-timeline/issues/224 | ||
| entries.sort((a, b) => { | ||
| const scoreA = a.startTime + a.duration; | ||
| const scoreB = b.startTime + b.duration; | ||
| return scoreA - scoreB; | ||
| }); | ||
| callback(entries as PerformanceEntryMap[K]); | ||
| }); | ||
| }); | ||
| po.observe({type, buffered: true, ...opts}); | ||
| for (const t of typesToMonitor) { | ||
| po.observe( | ||
| Object.assign( | ||
| { | ||
| type: t, | ||
| buffered: true, | ||
| ...opts, | ||
| }, | ||
| opts || {}, | ||
| ) as PerformanceObserverInit, | ||
| ); | ||
| } | ||
| return po; | ||
@@ -57,0 +104,0 @@ } |
+58
-11
@@ -20,2 +20,3 @@ /* | ||
| import {doubleRAF} from './lib/doubleRAF.js'; | ||
| import {getVisibilityWatcher} from './lib/getVisibilityWatcher.js'; | ||
| import {initMetric} from './lib/initMetric.js'; | ||
@@ -27,4 +28,8 @@ import {initUnique} from './lib/initUnique.js'; | ||
| import {onFCP} from './onFCP.js'; | ||
| import {getVisibilityWatcher} from './lib/getVisibilityWatcher.js'; | ||
| import {CLSMetric, MetricRatingThresholds, ReportOpts} from './types.js'; | ||
| import { | ||
| CLSMetric, | ||
| Metric, | ||
| MetricRatingThresholds, | ||
| ReportOpts, | ||
| } from './types.js'; | ||
@@ -69,4 +74,47 @@ /** Thresholds for CLS. See https://web.dev/articles/cls#what_is_a_good_cls_score */ | ||
| const handleEntries = (entries: LayoutShift[]) => { | ||
| const initNewCLSMetric = ( | ||
| interactionId?: number, | ||
| navigationType?: Metric['navigationType'], | ||
| navigationId?: number, | ||
| navigationURL?: string, | ||
| navigationStartTime?: number, | ||
| ) => { | ||
| metric = initMetric( | ||
| 'CLS', | ||
| 0, | ||
| interactionId, | ||
| navigationType, | ||
| navigationId, | ||
| navigationURL, | ||
| navigationStartTime, | ||
| ); | ||
| layoutShiftManager._sessionValue = 0; | ||
| report = bindReporter( | ||
| onReport, | ||
| metric, | ||
| CLSThresholds, | ||
| opts!.reportAllChanges, | ||
| ); | ||
| }; | ||
| const handleSoftNavEntry = (entry: SoftNavigationEntry) => { | ||
| handleEntries(po?.takeRecords() as CLSMetric['entries']); | ||
| report(true); | ||
| initNewCLSMetric( | ||
| entry.interactionId, | ||
| 'soft-navigation', | ||
| entry.navigationId, | ||
| entry.name, | ||
| entry.startTime, | ||
| ); | ||
| }; | ||
| const handleEntries = ( | ||
| entries: (LayoutShift | SoftNavigationEntry)[], | ||
| ) => { | ||
| for (const entry of entries) { | ||
| if ('largestInteractionContentfulPaint' in entry) { | ||
| handleSoftNavEntry(entry); | ||
| continue; | ||
| } | ||
| layoutShiftManager._processEntry(entry); | ||
@@ -84,3 +132,3 @@ } | ||
| const po = observe('layout-shift', handleEntries); | ||
| const po = observe('layout-shift', handleEntries, opts); | ||
| if (po) { | ||
@@ -102,9 +150,8 @@ report = bindReporter( | ||
| onBFCacheRestore(() => { | ||
| layoutShiftManager._sessionValue = 0; | ||
| metric = initMetric('CLS', 0); | ||
| report = bindReporter( | ||
| onReport, | ||
| metric, | ||
| CLSThresholds, | ||
| opts!.reportAllChanges, | ||
| initNewCLSMetric( | ||
| metric.interactionId, | ||
| 'back-forward-cache', | ||
| metric.navigationId, | ||
| metric.navigationURL, | ||
| metric.navigationStartTime, | ||
| ); | ||
@@ -111,0 +158,0 @@ |
+49
-4
@@ -17,4 +17,4 @@ /* | ||
| import {onBFCacheRestore} from './lib/bfcache.js'; | ||
| import {bindReporter} from './lib/bindReporter.js'; | ||
| import {checkSoftNavsEnabled} from './lib/softNavs.js'; | ||
| import {doubleRAF} from './lib/doubleRAF.js'; | ||
@@ -25,2 +25,3 @@ import {getActivationStart} from './lib/getActivationStart.js'; | ||
| import {observe} from './lib/observe.js'; | ||
| import {onBFCacheRestore} from './lib/bfcache.js'; | ||
| import {whenActivated} from './lib/whenActivated.js'; | ||
@@ -42,2 +43,6 @@ import {FCPMetric, MetricRatingThresholds, ReportOpts} from './types.js'; | ||
| ) => { | ||
| // Set defaults | ||
| opts = opts || {}; | ||
| const softNavsEnabled = checkSoftNavsEnabled(opts); | ||
| whenActivated(() => { | ||
@@ -53,3 +58,3 @@ const visibilityWatcher = getVisibilityWatcher(); | ||
| // Only report if the page wasn't hidden prior to the first paint. | ||
| // Only report if the page wasn't hidden prior to FCP. | ||
| if (entry.startTime < visibilityWatcher.firstHiddenTime) { | ||
@@ -62,2 +67,4 @@ // The activationStart reference is used because FCP should be | ||
| metric.entries.push(entry); | ||
| metric.navigationId = entry.navigationId || 0; | ||
| // FCP should only be reported once so can report right away | ||
| report(true); | ||
@@ -69,3 +76,3 @@ } | ||
| const po = observe('paint', handleEntries); | ||
| const po = observe('paint', handleEntries, opts); | ||
@@ -83,3 +90,11 @@ if (po) { | ||
| onBFCacheRestore((event) => { | ||
| metric = initMetric('FCP'); | ||
| metric = initMetric( | ||
| 'FCP', | ||
| 0, | ||
| metric.interactionId, | ||
| 'back-forward-cache', | ||
| metric.navigationId, | ||
| metric.navigationURL, | ||
| metric.navigationStartTime, | ||
| ); | ||
| report = bindReporter( | ||
@@ -98,3 +113,33 @@ onReport, | ||
| } | ||
| if (softNavsEnabled) { | ||
| // As first-contentful-paint is only reported once, we can handle soft | ||
| // navigations afterwards on their own for simplicity, as no need to | ||
| // observe both and sort the entries like for the other metrics | ||
| const handleSoftNavEntries = (entries: SoftNavigationEntry[]) => { | ||
| entries.forEach((entry) => { | ||
| handleEntries(po!.takeRecords() as FCPMetric['entries']); | ||
| const FCPTime = | ||
| (entry.presentationTime || entry.paintTime || 0) - entry.startTime; | ||
| metric = initMetric( | ||
| 'FCP', | ||
| FCPTime, | ||
| entry.interactionId, | ||
| 'soft-navigation', | ||
| entry.navigationId, | ||
| entry.name, | ||
| entry.startTime, | ||
| ); | ||
| report = bindReporter( | ||
| onReport, | ||
| metric, | ||
| FCPThresholds, | ||
| opts!.reportAllChanges, | ||
| ); | ||
| report(true); | ||
| }); | ||
| }; | ||
| observe('soft-navigation', handleSoftNavEntries, opts); | ||
| } | ||
| }); | ||
| }; |
+86
-28
@@ -28,3 +28,8 @@ /* | ||
| import {INPMetric, MetricRatingThresholds, INPReportOpts} from './types.js'; | ||
| import { | ||
| INPMetric, | ||
| Metric, | ||
| MetricRatingThresholds, | ||
| INPReportOpts, | ||
| } from './types.js'; | ||
@@ -36,2 +41,8 @@ /** Thresholds for INP. See https://web.dev/articles/inp#what_is_a_good_inp_score */ | ||
| // `event` entries via PerformanceObserver. | ||
| // Event Timing entries have their durations rounded to the nearest 8ms, | ||
| // so a duration of 40ms would be any event that spans 2.5 or more frames | ||
| // at 60Hz. This threshold is chosen to strike a balance between usefulness | ||
| // and performance. Running this callback for any interaction that spans | ||
| // just one or two frames is likely not worth the insight that could be | ||
| // gained. | ||
| const DEFAULT_DURATION_THRESHOLD = 40; | ||
@@ -93,3 +104,55 @@ | ||
| const handleEntries = (entries: INPMetric['entries']) => { | ||
| const initNewINPMetric = ( | ||
| interactionId?: number, | ||
| navigationType?: Metric['navigationType'], | ||
| navigationId?: number, | ||
| navigationURL?: string, | ||
| navigationStartTime?: number, | ||
| ) => { | ||
| interactionManager._resetInteractions(); | ||
| metric = initMetric( | ||
| 'INP', | ||
| -1, | ||
| interactionId, | ||
| navigationType, | ||
| navigationId, | ||
| navigationURL, | ||
| navigationStartTime, | ||
| ); | ||
| report = bindReporter( | ||
| onReport, | ||
| metric, | ||
| INPThresholds, | ||
| opts!.reportAllChanges, | ||
| ); | ||
| }; | ||
| const updateINPMetric = () => { | ||
| const inp = interactionManager._estimateP98LongestInteraction( | ||
| metric.navigationType, | ||
| ); | ||
| if (inp && inp._latency !== metric.value) { | ||
| metric.value = inp._latency; | ||
| metric.entries = inp.entries; | ||
| report(); | ||
| } | ||
| }; | ||
| const handleSoftNavEntry = (entry: SoftNavigationEntry) => { | ||
| handleEntries(po?.takeRecords() as INPMetric['entries']); | ||
| updateINPMetric(); | ||
| report(true); | ||
| initNewINPMetric( | ||
| entry.interactionId, | ||
| 'soft-navigation', | ||
| entry.navigationId, | ||
| entry.name, | ||
| entry.startTime, | ||
| ); | ||
| }; | ||
| const handleEntries = ( | ||
| entries: (PerformanceEventTiming | SoftNavigationEntry)[], | ||
| ) => { | ||
| // Queue the `handleEntries()` callback in the next idle task. | ||
@@ -102,13 +165,14 @@ // This is needed to increase the chances that all event entries that | ||
| whenIdleOrHidden(() => { | ||
| // Only process entries, if at least some of them have interaction ids | ||
| // (otherwise run into lots of errors later for empty INP entries) | ||
| if (!entries.some((entry) => entry.interactionId)) return; | ||
| for (const entry of entries) { | ||
| if ('largestInteractionContentfulPaint' in entry) { | ||
| handleSoftNavEntry(entry); | ||
| continue; | ||
| } | ||
| interactionManager._processEntry(entry); | ||
| } | ||
| const inp = interactionManager._estimateP98LongestInteraction(); | ||
| if (inp && inp._latency !== metric.value) { | ||
| metric.value = inp._latency; | ||
| metric.entries = inp.entries; | ||
| report(); | ||
| } | ||
| updateINPMetric(); | ||
| }); | ||
@@ -118,10 +182,5 @@ }; | ||
| const po = observe('event', handleEntries, { | ||
| // Event Timing entries have their durations rounded to the nearest 8ms, | ||
| // so a duration of 40ms would be any event that spans 2.5 or more frames | ||
| // at 60Hz. This threshold is chosen to strike a balance between usefulness | ||
| // and performance. Running this callback for any interaction that spans | ||
| // just one or two frames is likely not worth the insight that could be | ||
| // gained. | ||
| ...opts, | ||
| durationThreshold: opts.durationThreshold ?? DEFAULT_DURATION_THRESHOLD, | ||
| }); | ||
| } as PerformanceObserverInit); | ||
@@ -136,8 +195,9 @@ report = bindReporter( | ||
| if (po) { | ||
| // Also observe entries of type `first-input`. This is useful in cases | ||
| // where the first interaction is less than the `durationThreshold`. | ||
| po.observe({type: 'first-input', buffered: true}); | ||
| visibilityWatcher.onHidden(() => { | ||
| handleEntries(po.takeRecords() as INPMetric['entries']); | ||
| // For soft navigations we may also need to report on hidden, even | ||
| // if there are no entries if the only interactions are < 16ms. | ||
| if (metric.navigationType === 'soft-navigation') { | ||
| updateINPMetric(); | ||
| } | ||
| report(true); | ||
@@ -149,10 +209,8 @@ }); | ||
| onBFCacheRestore(() => { | ||
| interactionManager._resetInteractions(); | ||
| metric = initMetric('INP'); | ||
| report = bindReporter( | ||
| onReport, | ||
| metric, | ||
| INPThresholds, | ||
| opts.reportAllChanges, | ||
| initNewINPMetric( | ||
| metric.interactionId, | ||
| 'back-forward-cache', | ||
| metric.navigationId, | ||
| metric.navigationURL, | ||
| metric.navigationStartTime, | ||
| ); | ||
@@ -159,0 +217,0 @@ }); |
+151
-27
@@ -22,2 +22,3 @@ /* | ||
| import {getActivationStart} from './lib/getActivationStart.js'; | ||
| import {checkSoftNavsEnabled} from './lib/softNavs.js'; | ||
| import {getVisibilityWatcher} from './lib/getVisibilityWatcher.js'; | ||
@@ -27,6 +28,10 @@ import {initMetric} from './lib/initMetric.js'; | ||
| import {observe} from './lib/observe.js'; | ||
| import {runOnce} from './lib/runOnce.js'; | ||
| import {whenActivated} from './lib/whenActivated.js'; | ||
| import {whenIdleOrHidden} from './lib/whenIdleOrHidden.js'; | ||
| import {LCPMetric, MetricRatingThresholds, ReportOpts} from './types.js'; | ||
| import { | ||
| LCPMetric, | ||
| Metric, | ||
| MetricRatingThresholds, | ||
| ReportOpts, | ||
| } from './types.js'; | ||
@@ -51,4 +56,9 @@ /** Thresholds for LCP. See https://web.dev/articles/lcp#what_is_a_good_lcp_score */ | ||
| ) => { | ||
| // As InteractionContentfulPaint entries used by soft navs can emit after | ||
| // LCP is finalized, we need a flag to know to ignore them. | ||
| let isFinalized = false; | ||
| const softNavsEnabled = checkSoftNavsEnabled(opts); | ||
| whenActivated(() => { | ||
| const visibilityWatcher = getVisibilityWatcher(); | ||
| let visibilityWatcher = getVisibilityWatcher(); | ||
| let metric = initMetric('LCP'); | ||
@@ -59,6 +69,66 @@ let report: ReturnType<typeof bindReporter>; | ||
| const handleEntries = (entries: LCPMetric['entries']) => { | ||
| const initNewLCPMetric = ( | ||
| navigation?: Metric['navigationType'], | ||
| interactionId?: number, | ||
| navigationId?: number, | ||
| navigationURL?: string, | ||
| navigationStartTime?: number, | ||
| ) => { | ||
| metric = initMetric( | ||
| 'LCP', | ||
| 0, | ||
| interactionId, | ||
| navigation, | ||
| navigationId, | ||
| navigationURL, | ||
| navigationStartTime, | ||
| ); | ||
| report = bindReporter( | ||
| onReport, | ||
| metric, | ||
| LCPThresholds, | ||
| opts!.reportAllChanges, | ||
| ); | ||
| // Reset the finalized flag | ||
| isFinalized = false; | ||
| // If it's a soft nav, then need to reset the visibilityWatcher | ||
| if (navigation === 'soft-navigation') { | ||
| visibilityWatcher = getVisibilityWatcher(true); | ||
| } | ||
| }; | ||
| const handleSoftNavEntry = (entry: SoftNavigationEntry) => { | ||
| handleEntries(po!.takeRecords() as LCPMetric['entries']); | ||
| if (!isFinalized) report(true); | ||
| initNewLCPMetric( | ||
| 'soft-navigation', | ||
| entry.interactionId, | ||
| entry.navigationId, | ||
| entry.name, | ||
| entry.startTime, | ||
| ); | ||
| // Soft Navs should contain the largest paint until now, so handle that | ||
| // as if it just happened, then listen for more. | ||
| // It can however be null in rare circumstances | ||
| // (see https://github.com/GoogleChrome/web-vitals/issues/725) | ||
| const largestInteractionContentfulPaint = | ||
| entry.getLargestInteractionContentfulPaint?.() || | ||
| // TODO to remove this, after OT ends as this has been replaced | ||
| // with above function | ||
| entry.largestInteractionContentfulPaint; | ||
| if (largestInteractionContentfulPaint) { | ||
| handleEntries([largestInteractionContentfulPaint]); | ||
| } | ||
| }; | ||
| const handleEntries = ( | ||
| entries: ( | ||
| | LargestContentfulPaint | ||
| | InteractionContentfulPaint | ||
| | SoftNavigationEntry | ||
| )[], | ||
| ) => { | ||
| // If reportAllChanges is set then call this function for each entry, | ||
| // otherwise only consider the last one. | ||
| if (!opts!.reportAllChanges) { | ||
| // otherwise only consider the last one, unless soft navs are enabled. | ||
| if (!opts!.reportAllChanges && !softNavsEnabled) { | ||
| entries = entries.slice(-1); | ||
@@ -68,4 +138,54 @@ } | ||
| for (const entry of entries) { | ||
| lcpEntryManager._processEntry(entry); | ||
| if (!entry) continue; | ||
| if ('largestInteractionContentfulPaint' in entry) { | ||
| handleSoftNavEntry(entry); | ||
| continue; | ||
| } | ||
| let value = 0; | ||
| let entries: LargestContentfulPaint[] = []; | ||
| if (entry instanceof LargestContentfulPaint) { | ||
| // The startTime attribute returns the value of the renderTime if it is | ||
| // not 0, and the value of the loadTime otherwise. The activationStart | ||
| // reference is used because LCP should be relative to page activation | ||
| // rather than navigation start if the page was prerendered. But in cases | ||
| // where `activationStart` occurs after the LCP, this time should be | ||
| // clamped at 0. | ||
| value = Math.max(entry.startTime - getActivationStart(), 0); | ||
| lcpEntryManager._processEntry(entry); | ||
| entries = [entry]; | ||
| } else { | ||
| // InteractionContentfulPaints should only happen after a | ||
| // SoftNavigationEntry so the metric should have been set | ||
| // with a non-zero navigationId mapping to a soft nav. | ||
| if (!metric.navigationId) continue; | ||
| // Ignore interactions not for this soft nav | ||
| // (either paints that have bled into this interaction or paints when | ||
| // we should have already finalized) | ||
| if ( | ||
| 'interactionId' in entry && | ||
| entry.interactionId != metric.interactionId | ||
| ) { | ||
| continue; | ||
| } | ||
| // We're changing the shape to nest LCP entries within | ||
| // interaction-contentful-paint so handle both for now | ||
| const renderTime = | ||
| entry?.largestContentfulPaint?.renderTime || entry.renderTime || 0; | ||
| // Paints should never be less than 0 but add cap just in case | ||
| value = Math.max(renderTime - entry.startTime, 0); | ||
| if (entry.largestContentfulPaint) { | ||
| lcpEntryManager._processEntry(entry.largestContentfulPaint); | ||
| } | ||
| if (entry.largestContentfulPaint) { | ||
| entries = [entry.largestContentfulPaint]; | ||
| } | ||
| } | ||
| // Only report if the page wasn't hidden prior to LCP. | ||
@@ -79,4 +199,4 @@ if (entry.startTime < visibilityWatcher.firstHiddenTime) { | ||
| // clamped at 0. | ||
| metric.value = Math.max(entry.startTime - getActivationStart(), 0); | ||
| metric.entries = [entry]; | ||
| metric.value = value; | ||
| metric.entries = entries; | ||
| report(); | ||
@@ -87,3 +207,3 @@ } | ||
| const po = observe('largest-contentful-paint', handleEntries); | ||
| const po = observe('largest-contentful-paint', handleEntries, opts); | ||
@@ -98,20 +218,17 @@ if (po) { | ||
| // Ensure this logic only runs once, since it can be triggered from | ||
| // any of three different event listeners below. | ||
| const stopListening = runOnce(() => { | ||
| handleEntries(po!.takeRecords() as LCPMetric['entries']); | ||
| po!.disconnect(); | ||
| report(true); | ||
| }); | ||
| // Need a separate wrapper to ensure the `runOnce` function above is | ||
| // common for all three functions | ||
| const stopListeningWrapper = (event: Event) => { | ||
| if (event.isTrusted) { | ||
| const finalizeLCP = (event: Event) => { | ||
| if (event.isTrusted && !isFinalized) { | ||
| // Wrap the listener in an idle callback so it's run in a separate | ||
| // task to reduce potential INP impact. | ||
| // https://github.com/GoogleChrome/web-vitals/issues/383 | ||
| whenIdleOrHidden(stopListening); | ||
| removeEventListener(event.type, stopListeningWrapper, { | ||
| capture: true, | ||
| whenIdleOrHidden(() => { | ||
| if (!isFinalized) { | ||
| handleEntries(po!.takeRecords() as LCPMetric['entries']); | ||
| if (!softNavsEnabled) { | ||
| po!.disconnect(); | ||
| removeEventListener(event.type, finalizeLCP); | ||
| } | ||
| isFinalized = true; | ||
| report(true); | ||
| } | ||
| }); | ||
@@ -126,3 +243,3 @@ } | ||
| for (const type of ['keydown', 'click', 'visibilitychange']) { | ||
| addEventListener(type, stopListeningWrapper, { | ||
| addEventListener(type, finalizeLCP, { | ||
| capture: true, | ||
@@ -135,3 +252,9 @@ }); | ||
| onBFCacheRestore((event) => { | ||
| metric = initMetric('LCP'); | ||
| initNewLCPMetric( | ||
| 'back-forward-cache', | ||
| metric.interactionId, | ||
| metric.navigationId, | ||
| metric.navigationURL, | ||
| metric.navigationStartTime, | ||
| ); | ||
| report = bindReporter( | ||
@@ -146,2 +269,3 @@ onReport, | ||
| metric.value = performance.now() - event.timeStamp; | ||
| isFinalized = true; | ||
| report(true); | ||
@@ -148,0 +272,0 @@ }); |
+51
-12
@@ -18,8 +18,10 @@ /* | ||
| import {bindReporter} from './lib/bindReporter.js'; | ||
| import {checkSoftNavsEnabled} from './lib/softNavs.js'; | ||
| import {getNavigationEntry} from './lib/getNavigationEntry.js'; | ||
| import {getActivationStart} from './lib/getActivationStart.js'; | ||
| import {initMetric} from './lib/initMetric.js'; | ||
| import {observe} from './lib/observe.js'; | ||
| import {onBFCacheRestore} from './lib/bfcache.js'; | ||
| import {getNavigationEntry} from './lib/getNavigationEntry.js'; | ||
| import {whenActivated} from './lib/whenActivated.js'; | ||
| import {MetricRatingThresholds, ReportOpts, TTFBMetric} from './types.js'; | ||
| import {getActivationStart} from './lib/getActivationStart.js'; | ||
| import {whenActivated} from './lib/whenActivated.js'; | ||
@@ -63,2 +65,6 @@ /** Thresholds for TTFB. See https://web.dev/articles/ttfb#what_is_a_good_ttfb_score */ | ||
| ) => { | ||
| // Set defaults | ||
| opts = opts || {}; | ||
| const softNavsEnabled = checkSoftNavsEnabled(opts); | ||
| let metric = initMetric('TTFB'); | ||
@@ -73,5 +79,5 @@ let report = bindReporter( | ||
| whenReady(() => { | ||
| const navigationEntry = getNavigationEntry(); | ||
| if (navigationEntry) { | ||
| const hardNavEntry = getNavigationEntry(); | ||
| if (hardNavEntry) { | ||
| const responseStart = hardNavEntry.responseStart; | ||
| // The activationStart reference is used because TTFB should be | ||
@@ -81,8 +87,5 @@ // relative to page activation rather than navigation start if the | ||
| // after the first byte is received, this time should be clamped at 0. | ||
| metric.value = Math.max( | ||
| navigationEntry.responseStart - getActivationStart(), | ||
| 0, | ||
| ); | ||
| metric.value = Math.max(responseStart - getActivationStart(), 0); | ||
| metric.entries = [navigationEntry]; | ||
| metric.entries = [hardNavEntry]; | ||
| report(true); | ||
@@ -93,3 +96,11 @@ | ||
| onBFCacheRestore(() => { | ||
| metric = initMetric('TTFB', 0); | ||
| metric = initMetric( | ||
| 'TTFB', | ||
| 0, | ||
| metric.interactionId, | ||
| 'back-forward-cache', | ||
| metric.navigationId, | ||
| metric.navigationURL, | ||
| metric.navigationStartTime, | ||
| ); | ||
| report = bindReporter( | ||
@@ -104,4 +115,32 @@ onReport, | ||
| }); | ||
| // Listen for soft-navigation entries and emit a dummy 0 TTFB entry | ||
| if (softNavsEnabled) { | ||
| const reportSoftNavTTFBs = (entries: SoftNavigationEntry[]) => { | ||
| entries.forEach((entry) => { | ||
| if (entry.navigationId) { | ||
| metric = initMetric( | ||
| 'TTFB', | ||
| 0, | ||
| entry.interactionId, | ||
| 'soft-navigation', | ||
| entry.navigationId, | ||
| entry.name, | ||
| entry.startTime, | ||
| ); | ||
| metric.entries = [entry]; | ||
| report = bindReporter( | ||
| onReport, | ||
| metric, | ||
| TTFBThresholds, | ||
| opts!.reportAllChanges, | ||
| ); | ||
| report(true); | ||
| } | ||
| }); | ||
| }; | ||
| observe('soft-navigation', reportSoftNavTTFBs, opts); | ||
| } | ||
| } | ||
| }); | ||
| }; |
+30
-4
@@ -30,5 +30,9 @@ /* | ||
| interface PerformanceEntryMap { | ||
| navigation: PerformanceNavigationTiming; | ||
| resource: PerformanceResourceTiming; | ||
| paint: PerformancePaintTiming; | ||
| 'event': PerformanceEventTiming; | ||
| 'interaction-contentful-paint': InteractionContentfulPaint; | ||
| 'layout-shift': LayoutShift; | ||
| 'navigation': PerformanceNavigationTiming; | ||
| 'paint': PerformancePaintTiming; | ||
| 'resource': PerformanceResourceTiming; | ||
| 'soft-navigation': SoftNavigationEntry; | ||
| } | ||
@@ -52,2 +56,7 @@ | ||
| // https://w3c.github.io/event-timing/#sec-modifications-perf-timeline | ||
| interface PerformanceEntry { | ||
| navigationId?: number; | ||
| } | ||
| // https://w3c.github.io/event-timing/#sec-modifications-perf-timeline | ||
| interface PerformanceObserverInit { | ||
@@ -58,3 +67,3 @@ durationThreshold?: number; | ||
| // https://wicg.github.io/nav-speculation/prerendering.html#performance-navigation-timing-extension | ||
| interface PerformanceNavigationTiming { | ||
| interface PerformanceNavigationTiming extends PerformanceEntry { | ||
| activationStart?: number; | ||
@@ -67,2 +76,3 @@ } | ||
| readonly interactionId: number; | ||
| readonly targetSelector: string; | ||
| } | ||
@@ -94,2 +104,18 @@ | ||
| // https://github.com/WICG/soft-navigations | ||
| interface InteractionContentfulPaint extends PerformanceEntry { | ||
| readonly interactionId: number; | ||
| readonly largestContentfulPaint?: LargestContentfulPaint; | ||
| readonly renderTime?: DOMHighResTimeStamp; // TODO: remove | ||
| } | ||
| interface SoftNavigationEntry extends PerformanceEntry { | ||
| readonly interactionId: number; | ||
| readonly navigationType?: NavigationType; | ||
| readonly paintTime?: number; | ||
| readonly presentationTime?: number; | ||
| // TODO Remove the largestInteractionContentfulPaint when OT ends as moved to function | ||
| readonly largestInteractionContentfulPaint: InteractionContentfulPaint; | ||
| readonly getLargestInteractionContentfulPaint?: () => InteractionContentfulPaint | null; | ||
| } | ||
| // https://w3c.github.io/long-animation-frame/#sec-PerformanceLongAnimationFrameTiming | ||
@@ -96,0 +122,0 @@ export type ScriptInvokerType = |
+28
-1
@@ -58,2 +58,7 @@ /* | ||
| /** | ||
| * The interactionId that started this interaction for soft navigations | ||
| */ | ||
| interactionId?: number; | ||
| /** | ||
| * Any performance entries relevant to the metric value calculation. | ||
@@ -76,2 +81,3 @@ * The array may also be empty if the metric value was not based on any | ||
| * restored by the user. | ||
| * - 'soft-navigation': for soft navigations. | ||
| */ | ||
@@ -84,3 +90,22 @@ navigationType: | ||
| | 'prerender' | ||
| | 'restore'; | ||
| | 'restore' | ||
| | 'soft-navigation'; | ||
| /** | ||
| * The navigationId the metric happened for. This is particularly relevant for soft navigations where | ||
| * the metric may be reported for a previous URL. | ||
| */ | ||
| navigationId: number; | ||
| /** | ||
| * The navigation startTime the metric is based from. This is particularly | ||
| * relevant for soft navigations where time origin is not 0. | ||
| */ | ||
| navigationStartTime?: number; | ||
| /** | ||
| * The navigation URL the metric happened for. This is particularly relevant for soft navigations where | ||
| * the metric may be reported for a previous URL. | ||
| */ | ||
| navigationURL?: string; | ||
| } | ||
@@ -130,2 +155,4 @@ | ||
| reportAllChanges?: boolean; | ||
| durationThreshold?: number; | ||
| reportSoftNavs?: boolean; | ||
| } | ||
@@ -132,0 +159,0 @@ |
+1
-1
@@ -57,3 +57,3 @@ /* | ||
| */ | ||
| navigationEntry?: PerformanceNavigationTiming; | ||
| navigationEntry?: PerformanceNavigationTiming | SoftNavigationEntry; | ||
| } | ||
@@ -60,0 +60,0 @@ |
+4
-3
@@ -75,3 +75,3 @@ /* | ||
| */ | ||
| navigationEntry?: PerformanceNavigationTiming; | ||
| navigationEntry?: PerformanceNavigationTiming | SoftNavigationEntry; | ||
| /** | ||
@@ -83,5 +83,6 @@ * The `resource` entry for the LCP resource (if applicable), which is useful | ||
| /** | ||
| * The `LargestContentfulPaint` entry corresponding to LCP. | ||
| * The `LargestContentfulPaint` entry corresponding to LCP | ||
| * (or `InteractionContentfulPaint` for soft navigations). | ||
| */ | ||
| lcpEntry?: LargestContentfulPaint; | ||
| lcpEntry?: LargestContentfulPaint | InteractionContentfulPaint; | ||
| } | ||
@@ -88,0 +89,0 @@ |
@@ -24,3 +24,3 @@ /* | ||
| name: 'TTFB'; | ||
| entries: PerformanceNavigationTiming[]; | ||
| entries: PerformanceNavigationTiming[] | SoftNavigationEntry[]; | ||
| } | ||
@@ -71,3 +71,3 @@ | ||
| */ | ||
| navigationEntry?: PerformanceNavigationTiming; | ||
| navigationEntry?: PerformanceNavigationTiming | SoftNavigationEntry; | ||
| } | ||
@@ -74,0 +74,0 @@ |
| export declare const getFirstHiddenTime: () => number; |
| /* | ||
| * Copyright 2020 Google LLC | ||
| * | ||
| * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| * you may not use this file except in compliance with the License. | ||
| * You may obtain a copy of the License at | ||
| * | ||
| * https://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| */ | ||
| let firstHiddenTime = document.visibilityState === 'hidden' ? 0 : Infinity; | ||
| const onVisibilityChange = (event) => { | ||
| if (document.visibilityState === 'hidden') { | ||
| firstHiddenTime = event.timeStamp; | ||
| removeEventListener('visibilitychange', onVisibilityChange, true); | ||
| } | ||
| }; | ||
| // Note: do not add event listeners unconditionally (outside of polyfills). | ||
| addEventListener('visibilitychange', onVisibilityChange, true); | ||
| export const getFirstHiddenTime = () => firstHiddenTime; |
| /* | ||
| * Copyright 2020 Google LLC | ||
| * | ||
| * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| * you may not use this file except in compliance with the License. | ||
| * You may obtain a copy of the License at | ||
| * | ||
| * https://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| */ | ||
| let firstHiddenTime = document.visibilityState === 'hidden' ? 0 : Infinity; | ||
| const onVisibilityChange = (event: Event) => { | ||
| if (document.visibilityState === 'hidden') { | ||
| firstHiddenTime = event.timeStamp; | ||
| removeEventListener('visibilitychange', onVisibilityChange, true); | ||
| } | ||
| }; | ||
| // Note: do not add event listeners unconditionally (outside of polyfills). | ||
| addEventListener('visibilitychange', onVisibilityChange, true); | ||
| export const getFirstHiddenTime = () => firstHiddenTime; |
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
439405
15.29%7146
14.35%1294
7.21%8
14.29%