web-vitals
Advanced tools
Comparing version 4.2.1 to 4.2.2-soft-navs
@@ -18,3 +18,4 @@ /* | ||
import { getLoadState } from '../lib/getLoadState.js'; | ||
import { getNavigationEntry } from '../lib/getNavigationEntry.js'; | ||
import { getNavigationEntry, hardNavId } from '../lib/getNavigationEntry.js'; | ||
import { getSoftNavigationEntry } from '../lib/softNavs.js'; | ||
import { onFCP as unattributedOnFCP } from '../onFCP.js'; | ||
@@ -29,7 +30,21 @@ const attributeFCP = (metric) => { | ||
if (metric.entries.length) { | ||
const navigationEntry = getNavigationEntry(); | ||
let navigationEntry; | ||
const fcpEntry = metric.entries[metric.entries.length - 1]; | ||
let ttfb = 0; | ||
let softNavStart = 0; | ||
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); | ||
} | ||
} | ||
else { | ||
navigationEntry = getSoftNavigationEntry(metric.navigationId); | ||
// Set ttfb to the SoftNav start time | ||
softNavStart = navigationEntry ? navigationEntry.startTime : 0; | ||
ttfb = softNavStart; | ||
} | ||
if (navigationEntry) { | ||
const activationStart = navigationEntry.activationStart || 0; | ||
const ttfb = Math.max(0, navigationEntry.responseStart - activationStart); | ||
attribution = { | ||
@@ -36,0 +51,0 @@ timeToFirstByte: ttfb, |
@@ -16,3 +16,4 @@ /* | ||
*/ | ||
import { getNavigationEntry } from '../lib/getNavigationEntry.js'; | ||
import { getNavigationEntry, hardNavId } from '../lib/getNavigationEntry.js'; | ||
import { getSoftNavigationEntry } from '../lib/softNavs.js'; | ||
import { getSelector } from '../lib/getSelector.js'; | ||
@@ -29,5 +30,24 @@ import { onLCP as unattributedOnLCP } from '../onLCP.js'; | ||
if (metric.entries.length) { | ||
const navigationEntry = getNavigationEntry(); | ||
let navigationEntry; | ||
let activationStart = 0; | ||
let responseStart = 0; | ||
let softNavStart = 0; | ||
if (!metric.navigationId || metric.navigationId === hardNavId) { | ||
navigationEntry = getNavigationEntry(); | ||
activationStart = | ||
navigationEntry && navigationEntry.activationStart | ||
? navigationEntry.activationStart | ||
: 0; | ||
responseStart = | ||
navigationEntry && navigationEntry.responseStart | ||
? navigationEntry.responseStart | ||
: 0; | ||
} | ||
else { | ||
navigationEntry = getSoftNavigationEntry(metric.navigationId); | ||
// Set activationStart to the SoftNav start time | ||
softNavStart = navigationEntry ? navigationEntry.startTime : 0; | ||
activationStart = softNavStart; | ||
} | ||
if (navigationEntry) { | ||
const activationStart = navigationEntry.activationStart || 0; | ||
const lcpEntry = metric.entries[metric.entries.length - 1]; | ||
@@ -38,3 +58,3 @@ const lcpResourceEntry = lcpEntry.url && | ||
.filter((e) => e.name === lcpEntry.url)[0]; | ||
const ttfb = Math.max(0, navigationEntry.responseStart - activationStart); | ||
const ttfb = Math.max(0, responseStart - activationStart); | ||
const lcpRequestStart = Math.max(ttfb, | ||
@@ -46,4 +66,4 @@ // Prefer `requestStart` (if TOA is set), otherwise use `startTime`. | ||
: 0); | ||
const lcpResponseEnd = Math.max(lcpRequestStart, lcpResourceEntry ? lcpResourceEntry.responseEnd - activationStart : 0); | ||
const lcpRenderTime = Math.max(lcpResponseEnd, lcpEntry.startTime - activationStart); | ||
const lcpResponseEnd = Math.max(lcpRequestStart - softNavStart, lcpResourceEntry ? lcpResourceEntry.responseEnd - activationStart : 0, 0); | ||
const lcpRenderTime = Math.max(lcpResponseEnd - softNavStart, lcpEntry ? lcpEntry.startTime - activationStart : 0, 0); | ||
attribution = { | ||
@@ -50,0 +70,0 @@ element: getSelector(lcpEntry.element), |
@@ -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]; | ||
@@ -35,5 +37,5 @@ const activationStart = navigationEntry.activationStart || 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, |
@@ -18,4 +18,4 @@ /* | ||
export const getActivationStart = () => { | ||
const navEntry = getNavigationEntry(); | ||
return (navEntry && navEntry.activationStart) || 0; | ||
const hardNavEntry = getNavigationEntry(); | ||
return (hardNavEntry && hardNavEntry.activationStart) || 0; | ||
}; |
@@ -24,9 +24,9 @@ /* | ||
else { | ||
const navigationEntry = getNavigationEntry(); | ||
if (navigationEntry) { | ||
if (timestamp < navigationEntry.domInteractive) { | ||
const hardNavEntry = getNavigationEntry(); | ||
if (hardNavEntry) { | ||
if (timestamp < hardNavEntry.domInteractive) { | ||
return 'loading'; | ||
} | ||
else if (navigationEntry.domContentLoadedEventStart === 0 || | ||
timestamp < navigationEntry.domContentLoadedEventStart) { | ||
else if (hardNavEntry.domContentLoadedEventStart === 0 || | ||
timestamp < hardNavEntry.domContentLoadedEventStart) { | ||
// If the `domContentLoadedEventStart` timestamp has not yet been | ||
@@ -36,4 +36,4 @@ // set, or if the given timestamp is less than that value. | ||
} | ||
else if (navigationEntry.domComplete === 0 || | ||
timestamp < navigationEntry.domComplete) { | ||
else if (hardNavEntry.domComplete === 0 || | ||
timestamp < hardNavEntry.domComplete) { | ||
// If the `domComplete` timestamp has not yet been | ||
@@ -40,0 +40,0 @@ // set, or if the given timestamp is less than that value. |
export declare const getNavigationEntry: () => PerformanceNavigationTiming | void; | ||
export declare const hardNavId: string; |
@@ -33,1 +33,2 @@ /* | ||
}; | ||
export const hardNavId = getNavigationEntry()?.navigationId || '1'; |
@@ -1,3 +0,3 @@ | ||
export declare const getVisibilityWatcher: () => { | ||
export declare const getVisibilityWatcher: (reset?: boolean) => { | ||
readonly firstHiddenTime: number; | ||
}; |
@@ -56,3 +56,6 @@ /* | ||
}; | ||
export const getVisibilityWatcher = () => { | ||
export const getVisibilityWatcher = (reset = false) => { | ||
if (reset) { | ||
firstHiddenTime = -1; | ||
} | ||
if (firstHiddenTime < 0) { | ||
@@ -59,0 +62,0 @@ // If the document is hidden when this code runs, assume it was hidden |
@@ -1,2 +0,3 @@ | ||
export declare const initMetric: <MetricName extends "CLS" | "FCP" | "FID" | "INP" | "LCP" | "TTFB">(name: MetricName, value?: number) => { | ||
import { MetricType } from '../types.js'; | ||
export declare const initMetric: <MetricName extends "CLS" | "FCP" | "FID" | "INP" | "LCP" | "TTFB">(name: MetricName, value?: number, navigation?: MetricType['navigationType'], navigationId?: string) => { | ||
name: MetricName; | ||
@@ -20,3 +21,4 @@ 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: string; | ||
}; |
@@ -19,10 +19,14 @@ /* | ||
import { getActivationStart } from './getActivationStart.js'; | ||
import { getNavigationEntry } from './getNavigationEntry.js'; | ||
export const initMetric = (name, value) => { | ||
const navEntry = getNavigationEntry(); | ||
import { getNavigationEntry, hardNavId } from './getNavigationEntry.js'; | ||
export const initMetric = (name, value, navigation, navigationId) => { | ||
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 +38,4 @@ navigationType = 'prerender'; | ||
} | ||
else if (navEntry.type) { | ||
navigationType = navEntry.type.replace(/_/g, '-'); | ||
else if (hardNavEntry.type) { | ||
navigationType = hardNavEntry.type.replace(/_/g, '-'); | ||
} | ||
@@ -49,3 +53,4 @@ } | ||
navigationType, | ||
navigationId: navigationId || hardNavId, | ||
}; | ||
}; |
@@ -37,3 +37,3 @@ /* | ||
export const resetInteractions = () => { | ||
prevInteractionCount = 0; | ||
prevInteractionCount = getInteractionCount(); | ||
longestInteractionList.length = 0; | ||
@@ -40,0 +40,0 @@ longestInteractionMap.clear(); |
@@ -10,2 +10,3 @@ interface PerformanceEntryMap { | ||
'resource': PerformanceResourceTiming[]; | ||
'soft-navigation': SoftNavigationEntry[]; | ||
} | ||
@@ -12,0 +13,0 @@ /** |
@@ -16,2 +16,3 @@ /* | ||
*/ | ||
import { softNavs } from './softNavs.js'; | ||
/** | ||
@@ -26,2 +27,3 @@ * Takes a performance entry type and a callback function, and creates a | ||
export const observe = (type, callback, opts) => { | ||
const includeSoftNavigationObservations = softNavs(opts); | ||
try { | ||
@@ -40,2 +42,3 @@ if (PerformanceObserver.supportedEntryTypes.includes(type)) { | ||
buffered: true, | ||
includeSoftNavigationObservations: includeSoftNavigationObservations, | ||
}, opts || {})); | ||
@@ -42,0 +45,0 @@ return po; |
@@ -71,3 +71,3 @@ /* | ||
callbacks.forEach(function (callback) { | ||
callback(entry); | ||
callback([entry]); | ||
}); | ||
@@ -74,0 +74,0 @@ callbacks = []; |
@@ -14,2 +14,2 @@ declare global { | ||
*/ | ||
export declare const initInteractionCountPolyfill: () => void; | ||
export declare const initInteractionCountPolyfill: (softNavs?: boolean) => void; |
@@ -16,2 +16,3 @@ /* | ||
*/ | ||
import { hardNavId } from '../getNavigationEntry.js'; | ||
import { observe } from '../observe.js'; | ||
@@ -21,5 +22,15 @@ let interactionCountEstimate = 0; | ||
let maxKnownInteractionId = 0; | ||
let currentNavId = hardNavId; | ||
let softNavsEnabled = false; | ||
const updateEstimate = (entries) => { | ||
entries.forEach((e) => { | ||
if (e.interactionId) { | ||
if (softNavsEnabled && | ||
e.navigationId && | ||
e.navigationId !== currentNavId) { | ||
currentNavId = e.navigationId; | ||
interactionCountEstimate = 0; | ||
minKnownInteractionId = Infinity; | ||
maxKnownInteractionId = 0; | ||
} | ||
minKnownInteractionId = Math.min(minKnownInteractionId, e.interactionId); | ||
@@ -44,5 +55,6 @@ maxKnownInteractionId = Math.max(maxKnownInteractionId, e.interactionId); | ||
*/ | ||
export const initInteractionCountPolyfill = () => { | ||
export const initInteractionCountPolyfill = (softNavs) => { | ||
if ('interactionCount' in performance || po) | ||
return; | ||
softNavsEnabled = softNavs || false; | ||
po = observe('event', updateEstimate, { | ||
@@ -52,3 +64,4 @@ type: 'event', | ||
durationThreshold: 0, | ||
includeSoftNavigationObservations: softNavsEnabled, | ||
}); | ||
}; |
@@ -16,9 +16,10 @@ /* | ||
*/ | ||
import { onBFCacheRestore } from './lib/bfcache.js'; | ||
import { bindReporter } from './lib/bindReporter.js'; | ||
import { doubleRAF } from './lib/doubleRAF.js'; | ||
import { initMetric } from './lib/initMetric.js'; | ||
import { observe } from './lib/observe.js'; | ||
import { bindReporter } from './lib/bindReporter.js'; | ||
import { doubleRAF } from './lib/doubleRAF.js'; | ||
import { onBFCacheRestore } from './lib/bfcache.js'; | ||
import { onHidden } from './lib/onHidden.js'; | ||
import { runOnce } from './lib/runOnce.js'; | ||
import { getSoftNavigationEntry, softNavs } from './lib/softNavs.js'; | ||
import { onFCP } from './onFCP.js'; | ||
@@ -51,2 +52,5 @@ /** Thresholds for CLS. See https://web.dev/articles/cls#what_is_a_good_cls_score */ | ||
opts = opts || {}; | ||
const softNavsEnabled = softNavs(opts); | ||
let reportedMetric = false; | ||
let metricNavStartTime = 0; | ||
// Start monitoring FCP so we can only report CLS if FCP is also reported. | ||
@@ -59,4 +63,29 @@ // Note: this is done to match the current behavior of CrUX. | ||
let sessionEntries = []; | ||
const initNewCLSMetric = (navigation, navigationId) => { | ||
metric = initMetric('CLS', 0, navigation, navigationId); | ||
report = bindReporter(onReport, metric, CLSThresholds, opts.reportAllChanges); | ||
sessionValue = 0; | ||
reportedMetric = false; | ||
if (navigation === 'soft-navigation') { | ||
const softNavEntry = getSoftNavigationEntry(navigationId); | ||
metricNavStartTime = softNavEntry ? softNavEntry.startTime || 0 : 0; | ||
} | ||
}; | ||
const handleEntries = (entries) => { | ||
entries.forEach((entry) => { | ||
// If the entry is for a new navigationId than previous, then we have | ||
// entered a new soft nav, so emit the final LCP and reinitialize the | ||
// metric. | ||
if (softNavsEnabled && | ||
entry.navigationId && | ||
entry.navigationId !== metric.navigationId) { | ||
// If the current session value is larger than the current CLS value, | ||
// update CLS and the entries contributing to it. | ||
if (sessionValue > metric.value) { | ||
metric.value = sessionValue; | ||
metric.entries = sessionEntries; | ||
} | ||
report(true); | ||
initNewCLSMetric('soft-navigation', entry.navigationId); | ||
} | ||
// Only count layout shifts without recent user input. | ||
@@ -90,3 +119,3 @@ if (!entry.hadRecentInput) { | ||
}; | ||
const po = observe('layout-shift', handleEntries); | ||
const po = observe('layout-shift', handleEntries, opts); | ||
if (po) { | ||
@@ -97,2 +126,3 @@ report = bindReporter(onReport, metric, CLSThresholds, opts.reportAllChanges); | ||
report(true); | ||
reportedMetric = true; | ||
}); | ||
@@ -102,7 +132,33 @@ // Only report after a bfcache restore if the `PerformanceObserver` | ||
onBFCacheRestore(() => { | ||
sessionValue = 0; | ||
metric = initMetric('CLS', 0); | ||
report = bindReporter(onReport, metric, CLSThresholds, opts.reportAllChanges); | ||
initNewCLSMetric('back-forward-cache', metric.navigationId); | ||
doubleRAF(() => report()); | ||
}); | ||
// Soft navs may be detected by navigationId changes in metrics above | ||
// But where no metric is issued we need to also listen for soft nav | ||
// entries, then emit the final metric for the previous navigation and | ||
// reset the metric for the new navigation. | ||
// | ||
// As PO is ordered by time, these should not happen before metrics. | ||
// | ||
// We add a check on startTime as we may be processing many entries that | ||
// are already dealt with so just checking navigationId differs from | ||
// current metric's navigation id, as we did above, is not sufficient. | ||
const handleSoftNavEntries = (entries) => { | ||
entries.forEach((entry) => { | ||
const navId = entry.navigationId; | ||
const softNavEntry = navId ? getSoftNavigationEntry(navId) : null; | ||
if (navId && | ||
navId !== metric.navigationId && | ||
softNavEntry && | ||
(softNavEntry.startTime || 0) > metricNavStartTime) { | ||
if (!reportedMetric) | ||
report(true); | ||
initNewCLSMetric('soft-navigation', entry.navigationId); | ||
report = bindReporter(onReport, metric, CLSThresholds, opts.reportAllChanges); | ||
} | ||
}); | ||
}; | ||
if (softNavsEnabled) { | ||
observe('soft-navigation', handleSoftNavEntries, opts); | ||
} | ||
// Queue a task to report (if nothing else triggers a report first). | ||
@@ -109,0 +165,0 @@ // This allows CLS to be reported as soon as FCP fires when |
@@ -16,9 +16,11 @@ /* | ||
*/ | ||
import { onBFCacheRestore } from './lib/bfcache.js'; | ||
import { bindReporter } from './lib/bindReporter.js'; | ||
import { doubleRAF } from './lib/doubleRAF.js'; | ||
import { getActivationStart } from './lib/getActivationStart.js'; | ||
import { getSoftNavigationEntry, softNavs } from './lib/softNavs.js'; | ||
import { getVisibilityWatcher } from './lib/getVisibilityWatcher.js'; | ||
import { hardNavId } from './lib/getNavigationEntry.js'; | ||
import { initMetric } from './lib/initMetric.js'; | ||
import { observe } from './lib/observe.js'; | ||
import { onBFCacheRestore } from './lib/bfcache.js'; | ||
import { whenActivated } from './lib/whenActivated.js'; | ||
@@ -36,12 +38,36 @@ /** Thresholds for FCP. See https://web.dev/articles/fcp#what_is_a_good_fcp_score */ | ||
opts = opts || {}; | ||
const softNavsEnabled = softNavs(opts); | ||
let metricNavStartTime = 0; | ||
whenActivated(() => { | ||
const visibilityWatcher = getVisibilityWatcher(); | ||
let visibilityWatcher = getVisibilityWatcher(); | ||
let metric = initMetric('FCP'); | ||
let report; | ||
const initNewFCPMetric = (navigation, navigationId) => { | ||
metric = initMetric('FCP', 0, navigation, navigationId); | ||
report = bindReporter(onReport, metric, FCPThresholds, opts.reportAllChanges); | ||
if (navigation === 'soft-navigation') { | ||
visibilityWatcher = getVisibilityWatcher(true); | ||
const softNavEntry = navigationId | ||
? getSoftNavigationEntry(navigationId) | ||
: null; | ||
metricNavStartTime = softNavEntry ? softNavEntry.startTime || 0 : 0; | ||
} | ||
}; | ||
const handleEntries = (entries) => { | ||
entries.forEach((entry) => { | ||
if (entry.name === 'first-contentful-paint') { | ||
po.disconnect(); | ||
// Only report if the page wasn't hidden prior to the first paint. | ||
if (entry.startTime < visibilityWatcher.firstHiddenTime) { | ||
if (!softNavsEnabled) { | ||
// If we're not using soft navs monitoring, we should not see | ||
// any more FCPs so can discconnect the performance observer | ||
po.disconnect(); | ||
} | ||
else if (entry.navigationId && | ||
entry.navigationId !== metric.navigationId) { | ||
// If the entry is for a new navigationId than previous, then we have | ||
// entered a new soft nav, so reinitialize the metric. | ||
initNewFCPMetric('soft-navigation', entry.navigationId); | ||
} | ||
let value = 0; | ||
if (!entry.navigationId || entry.navigationId === hardNavId) { | ||
// Only report if the page wasn't hidden prior to the first paint. | ||
// The activationStart reference is used because FCP should be | ||
@@ -51,4 +77,29 @@ // relative to page activation rather than navigation start if the | ||
// after the FCP, this time should be clamped at 0. | ||
metric.value = Math.max(entry.startTime - getActivationStart(), 0); | ||
value = Math.max(entry.startTime - getActivationStart(), 0); | ||
} | ||
else { | ||
const softNavEntry = getSoftNavigationEntry(entry.navigationId); | ||
const softNavStartTime = softNavEntry && softNavEntry.startTime | ||
? softNavEntry.startTime | ||
: 0; | ||
// As a soft nav needs an interaction, it should never be before | ||
// getActivationStart so can just cap to 0 | ||
value = Math.max(entry.startTime - softNavStartTime, 0); | ||
} | ||
// Only report if the page wasn't hidden prior to FCP. | ||
// Or it's a soft nav FCP | ||
const softNavEntry = softNavsEnabled && entry.navigationId | ||
? getSoftNavigationEntry(entry.navigationId) | ||
: null; | ||
const softNavEntryStartTime = softNavEntry && softNavEntry.startTime ? softNavEntry.startTime : 0; | ||
if (entry.startTime < visibilityWatcher.firstHiddenTime || | ||
(softNavsEnabled && | ||
entry.navigationId && | ||
entry.navigationId !== metric.navigationId && | ||
entry.navigationId !== hardNavId && | ||
softNavEntryStartTime > metricNavStartTime)) { | ||
metric.value = value; | ||
metric.entries.push(entry); | ||
metric.navigationId = entry.navigationId || '1'; | ||
// FCP should only be reported once so can report right | ||
report(true); | ||
@@ -59,3 +110,3 @@ } | ||
}; | ||
const po = observe('paint', handleEntries); | ||
const po = observe('paint', handleEntries, opts); | ||
if (po) { | ||
@@ -66,3 +117,3 @@ report = bindReporter(onReport, metric, FCPThresholds, opts.reportAllChanges); | ||
onBFCacheRestore((event) => { | ||
metric = initMetric('FCP'); | ||
metric = initMetric('FCP', 0, 'back-forward-cache', metric.navigationId); | ||
report = bindReporter(onReport, metric, FCPThresholds, opts.reportAllChanges); | ||
@@ -69,0 +120,0 @@ doubleRAF(() => { |
@@ -19,2 +19,3 @@ /* | ||
import { getVisibilityWatcher } from './lib/getVisibilityWatcher.js'; | ||
import { hardNavId } from './lib/getNavigationEntry.js'; | ||
import { initMetric } from './lib/initMetric.js'; | ||
@@ -24,3 +25,3 @@ import { observe } from './lib/observe.js'; | ||
import { firstInputPolyfill, resetFirstInputPolyfill, } from './lib/polyfills/firstInputPolyfill.js'; | ||
import { runOnce } from './lib/runOnce.js'; | ||
import { softNavs } from './lib/softNavs.js'; | ||
import { whenActivated } from './lib/whenActivated.js'; | ||
@@ -41,30 +42,48 @@ /** Thresholds for FID. See https://web.dev/articles/fid#what_is_a_good_fid_score */ | ||
opts = opts || {}; | ||
const softNavsEnabled = softNavs(opts); | ||
whenActivated(() => { | ||
const visibilityWatcher = getVisibilityWatcher(); | ||
let visibilityWatcher = getVisibilityWatcher(); | ||
let metric = initMetric('FID'); | ||
let report; | ||
const handleEntry = (entry) => { | ||
// Only report if the page wasn't hidden prior to the first input. | ||
if (entry.startTime < visibilityWatcher.firstHiddenTime) { | ||
metric.value = entry.processingStart - entry.startTime; | ||
metric.entries.push(entry); | ||
report(true); | ||
const initNewFIDMetric = (navigation, navigationId) => { | ||
if (navigation === 'soft-navigation') { | ||
visibilityWatcher = getVisibilityWatcher(true); | ||
} | ||
metric = initMetric('FID', 0, navigation, navigationId); | ||
report = bindReporter(onReport, metric, FIDThresholds, opts.reportAllChanges); | ||
}; | ||
const handleEntries = (entries) => { | ||
entries.forEach(handleEntry); | ||
entries.forEach((entry) => { | ||
if (!softNavsEnabled) { | ||
po.disconnect(); | ||
} | ||
else if (entry.navigationId && | ||
entry.navigationId !== metric.navigationId) { | ||
// If the entry is for a new navigationId than previous, then we have | ||
// entered a new soft nav, so reinitialize the metric. | ||
initNewFIDMetric('soft-navigation', entry.navigationId); | ||
} | ||
// Only report if the page wasn't hidden prior to the first input. | ||
if (entry.startTime < visibilityWatcher.firstHiddenTime) { | ||
metric.value = entry.processingStart - entry.startTime; | ||
metric.entries.push(entry); | ||
metric.navigationId = entry.navigationId || hardNavId; | ||
report(true); | ||
} | ||
}); | ||
}; | ||
const po = observe('first-input', handleEntries); | ||
const po = observe('first-input', handleEntries, opts); | ||
report = bindReporter(onReport, metric, FIDThresholds, opts.reportAllChanges); | ||
if (po) { | ||
onHidden(runOnce(() => { | ||
onHidden(() => { | ||
handleEntries(po.takeRecords()); | ||
po.disconnect(); | ||
})); | ||
if (!softNavsEnabled) | ||
po.disconnect(); | ||
}); | ||
onBFCacheRestore(() => { | ||
metric = initMetric('FID'); | ||
metric = initMetric('FID', 0, 'back-forward-cache', metric.navigationId); | ||
report = bindReporter(onReport, metric, FIDThresholds, opts.reportAllChanges); | ||
// Browsers don't re-emit FID on bfcache restore so fake it until you make it | ||
resetFirstInputPolyfill(); | ||
firstInputPolyfill(handleEntry); | ||
firstInputPolyfill(handleEntries); | ||
}); | ||
@@ -71,0 +90,0 @@ } |
@@ -18,2 +18,3 @@ /* | ||
import { bindReporter } from './lib/bindReporter.js'; | ||
import { doubleRAF } from './lib/doubleRAF.js'; | ||
import { initMetric } from './lib/initMetric.js'; | ||
@@ -24,2 +25,3 @@ import { DEFAULT_DURATION_THRESHOLD, processInteractionEntry, estimateP98LongestInteraction, resetInteractions, } from './lib/interactions.js'; | ||
import { initInteractionCountPolyfill } from './lib/polyfills/interactionCountPolyfill.js'; | ||
import { getSoftNavigationEntry, softNavs } from './lib/softNavs.js'; | ||
import { whenActivated } from './lib/whenActivated.js'; | ||
@@ -64,7 +66,29 @@ import { whenIdle } from './lib/whenIdle.js'; | ||
opts = opts || {}; | ||
const softNavsEnabled = softNavs(opts); | ||
let reportedMetric = false; | ||
let metricNavStartTime = 0; | ||
whenActivated(() => { | ||
// TODO(philipwalton): remove once the polyfill is no longer needed. | ||
initInteractionCountPolyfill(); | ||
initInteractionCountPolyfill(softNavsEnabled); | ||
let metric = initMetric('INP'); | ||
let report; | ||
const initNewINPMetric = (navigation, navigationId) => { | ||
resetInteractions(); | ||
metric = initMetric('INP', 0, navigation, navigationId); | ||
report = bindReporter(onReport, metric, INPThresholds, opts.reportAllChanges); | ||
reportedMetric = false; | ||
if (navigation === 'soft-navigation') { | ||
const softNavEntry = getSoftNavigationEntry(navigationId); | ||
metricNavStartTime = | ||
softNavEntry && softNavEntry.startTime ? softNavEntry.startTime : 0; | ||
} | ||
}; | ||
const updateINPMetric = () => { | ||
const inp = estimateP98LongestInteraction(); | ||
if (inp && | ||
(inp.latency !== metric.value || (opts && opts.reportAllChanges))) { | ||
metric.value = inp.latency; | ||
metric.entries = inp.entries; | ||
} | ||
}; | ||
const handleEntries = (entries) => { | ||
@@ -79,8 +103,4 @@ // Queue the `handleEntries()` callback in the next idle task. | ||
entries.forEach(processInteractionEntry); | ||
const inp = estimateP98LongestInteraction(); | ||
if (inp && inp.latency !== metric.value) { | ||
metric.value = inp.latency; | ||
metric.entries = inp.entries; | ||
report(); | ||
} | ||
updateINPMetric(); | ||
report(); | ||
}); | ||
@@ -96,2 +116,3 @@ }; | ||
durationThreshold: opts.durationThreshold ?? DEFAULT_DURATION_THRESHOLD, | ||
opts, | ||
}); | ||
@@ -102,3 +123,7 @@ report = bindReporter(onReport, metric, INPThresholds, opts.reportAllChanges); | ||
// where the first interaction is less than the `durationThreshold`. | ||
po.observe({ type: 'first-input', buffered: true }); | ||
po.observe({ | ||
type: 'first-input', | ||
buffered: true, | ||
includeSoftNavigationObservations: softNavsEnabled, | ||
}); | ||
onHidden(() => { | ||
@@ -112,7 +137,34 @@ handleEntries(po.takeRecords()); | ||
resetInteractions(); | ||
metric = initMetric('INP'); | ||
report = bindReporter(onReport, metric, INPThresholds, opts.reportAllChanges); | ||
initNewINPMetric('back-forward-cache', metric.navigationId); | ||
doubleRAF(() => report()); | ||
}); | ||
// Soft navs may be detected by navigationId changes in metrics above | ||
// But where no metric is issued we need to also listen for soft nav | ||
// entries, then emit the final metric for the previous navigation and | ||
// reset the metric for the new navigation. | ||
// | ||
// As PO is ordered by time, these should not happen before metrics. | ||
// | ||
// We add a check on startTime as we may be processing many entries that | ||
// are already dealt with so just checking navigationId differs from | ||
// current metric's navigation id, as we did above, is not sufficient. | ||
const handleSoftNavEntries = (entries) => { | ||
entries.forEach((entry) => { | ||
const softNavEntry = getSoftNavigationEntry(entry.navigationId); | ||
const softNavEntryStartTime = softNavEntry && softNavEntry.startTime ? softNavEntry.startTime : 0; | ||
if (entry.navigationId && | ||
entry.navigationId !== metric.navigationId && | ||
softNavEntryStartTime > metricNavStartTime) { | ||
if (!reportedMetric && metric.value > 0) | ||
report(true); | ||
initNewINPMetric('soft-navigation', entry.navigationId); | ||
report = bindReporter(onReport, metric, INPThresholds, opts.reportAllChanges); | ||
} | ||
}); | ||
}; | ||
if (softNavsEnabled) { | ||
observe('soft-navigation', handleSoftNavEntries, opts); | ||
} | ||
} | ||
}); | ||
}; |
@@ -21,6 +21,7 @@ /* | ||
import { getVisibilityWatcher } from './lib/getVisibilityWatcher.js'; | ||
import { hardNavId } from './lib/getNavigationEntry.js'; | ||
import { initMetric } from './lib/initMetric.js'; | ||
import { observe } from './lib/observe.js'; | ||
import { onHidden } from './lib/onHidden.js'; | ||
import { runOnce } from './lib/runOnce.js'; | ||
import { getSoftNavigationEntry, softNavs } from './lib/softNavs.js'; | ||
import { whenActivated } from './lib/whenActivated.js'; | ||
@@ -30,3 +31,2 @@ import { whenIdle } from './lib/whenIdle.js'; | ||
export const LCPThresholds = [2500, 4000]; | ||
const reportedMetricIDs = {}; | ||
/** | ||
@@ -45,39 +45,20 @@ * Calculates the [LCP](https://web.dev/articles/lcp) value for the current page and | ||
// Set defaults | ||
let reportedMetric = false; | ||
opts = opts || {}; | ||
const softNavsEnabled = softNavs(opts); | ||
let metricNavStartTime = 0; | ||
whenActivated(() => { | ||
const visibilityWatcher = getVisibilityWatcher(); | ||
let visibilityWatcher = getVisibilityWatcher(); | ||
let metric = initMetric('LCP'); | ||
let report; | ||
const handleEntries = (entries) => { | ||
// If reportAllChanges is set then call this function for each entry, | ||
// otherwise only consider the last one. | ||
if (!opts.reportAllChanges) { | ||
entries = entries.slice(-1); | ||
const initNewLCPMetric = (navigation, navigationId) => { | ||
metric = initMetric('LCP', 0, navigation, navigationId); | ||
report = bindReporter(onReport, metric, LCPThresholds, opts.reportAllChanges); | ||
reportedMetric = false; | ||
if (navigation === 'soft-navigation') { | ||
visibilityWatcher = getVisibilityWatcher(true); | ||
const softNavEntry = getSoftNavigationEntry(navigationId); | ||
metricNavStartTime = | ||
softNavEntry && softNavEntry.startTime ? softNavEntry.startTime : 0; | ||
} | ||
entries.forEach((entry) => { | ||
// Only report if the page wasn't hidden prior to LCP. | ||
if (entry.startTime < visibilityWatcher.firstHiddenTime) { | ||
// 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. | ||
metric.value = Math.max(entry.startTime - getActivationStart(), 0); | ||
metric.entries = [entry]; | ||
report(); | ||
} | ||
}); | ||
}; | ||
const po = observe('largest-contentful-paint', handleEntries); | ||
if (po) { | ||
report = bindReporter(onReport, metric, LCPThresholds, opts.reportAllChanges); | ||
const stopListening = runOnce(() => { | ||
if (!reportedMetricIDs[metric.id]) { | ||
handleEntries(po.takeRecords()); | ||
po.disconnect(); | ||
reportedMetricIDs[metric.id] = true; | ||
report(true); | ||
} | ||
}); | ||
// Stop listening after input. Note: while scrolling is an input that | ||
@@ -90,18 +71,101 @@ // stops LCP observation, it's unreliable since it can be programmatically | ||
// https://github.com/GoogleChrome/web-vitals/issues/383 | ||
addEventListener(type, () => whenIdle(stopListening), true); | ||
addEventListener(type, () => whenIdle(finalizeAllLCPs), { once: true }); | ||
}); | ||
onHidden(stopListening); | ||
}; | ||
const handleEntries = (entries) => { | ||
entries.forEach((entry) => { | ||
if (entry) { | ||
if (softNavsEnabled && | ||
entry.navigationId && | ||
entry.navigationId !== metric.navigationId) { | ||
// If the entry is for a new navigationId than previous, then we have | ||
// entered a new soft nav, so emit the final LCP and reinitialize the | ||
// metric. | ||
if (!reportedMetric) | ||
report(true); | ||
initNewLCPMetric('soft-navigation', entry.navigationId); | ||
} | ||
let value = 0; | ||
if (!entry.navigationId || entry.navigationId === hardNavId) { | ||
// 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); | ||
} | ||
else { | ||
// As a soft nav needs an interaction, it should never be before | ||
// getActivationStart so can just cap to 0 | ||
const softNavEntry = getSoftNavigationEntry(entry.navigationId); | ||
const softNavEntryStartTime = softNavEntry && softNavEntry.startTime | ||
? softNavEntry.startTime | ||
: 0; | ||
value = Math.max(entry.startTime - softNavEntryStartTime, 0); | ||
} | ||
// Only report if the page wasn't hidden prior to LCP. | ||
// We do allow soft navs to be reported, even if hard nav was not. | ||
if (entry.startTime < visibilityWatcher.firstHiddenTime) { | ||
metric.value = value; | ||
metric.entries = [entry]; | ||
metric.navigationId = entry.navigationId || hardNavId; | ||
report(); | ||
} | ||
} | ||
}); | ||
}; | ||
const finalizeAllLCPs = () => { | ||
if (!reportedMetric) { | ||
handleEntries(po.takeRecords()); | ||
if (!softNavsEnabled) | ||
po.disconnect(); | ||
reportedMetric = true; | ||
report(true); | ||
} | ||
}; | ||
const po = observe('largest-contentful-paint', handleEntries, opts); | ||
if (po) { | ||
report = bindReporter(onReport, metric, LCPThresholds, opts.reportAllChanges); | ||
onHidden(finalizeAllLCPs); | ||
// Only report after a bfcache restore if the `PerformanceObserver` | ||
// successfully registered. | ||
onBFCacheRestore((event) => { | ||
metric = initMetric('LCP'); | ||
report = bindReporter(onReport, metric, LCPThresholds, opts.reportAllChanges); | ||
initNewLCPMetric('back-forward-cache', metric.navigationId); | ||
doubleRAF(() => { | ||
metric.value = performance.now() - event.timeStamp; | ||
reportedMetricIDs[metric.id] = true; | ||
reportedMetric = true; | ||
report(true); | ||
}); | ||
}); | ||
// Soft navs may be detected by navigationId changes in metrics above | ||
// But where no metric is issued we need to also listen for soft nav | ||
// entries, then emit the final metric for the previous navigation and | ||
// reset the metric for the new navigation. | ||
// | ||
// As PO is ordered by time, these should not happen before metrics. | ||
// | ||
// We add a check on startTime as we may be processing many entries that | ||
// are already dealt with so just checking navigationId differs from | ||
// current metric's navigation id, as we did above, is not sufficient. | ||
const handleSoftNavEntries = (entries) => { | ||
entries.forEach((entry) => { | ||
const softNavEntry = entry.navigationId | ||
? getSoftNavigationEntry(entry.navigationId) | ||
: null; | ||
if (entry.navigationId && | ||
entry.navigationId !== metric.navigationId && | ||
softNavEntry && | ||
(softNavEntry.startTime || 0) > metricNavStartTime) { | ||
if (!reportedMetric) | ||
report(true); | ||
initNewLCPMetric('soft-navigation', entry.navigationId); | ||
} | ||
}); | ||
}; | ||
if (softNavsEnabled) { | ||
observe('soft-navigation', handleSoftNavEntries, opts); | ||
} | ||
} | ||
}); | ||
}; |
@@ -17,9 +17,12 @@ /* | ||
import { bindReporter } from './lib/bindReporter.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 { softNavs } from './lib/softNavs.js'; | ||
import { whenActivated } from './lib/whenActivated.js'; | ||
/** Thresholds for TTFB. See https://web.dev/articles/ttfb#what_is_a_good_ttfb_score */ | ||
export const TTFBThresholds = [800, 1800]; | ||
const hardNavEntry = getNavigationEntry(); | ||
/** | ||
@@ -59,7 +62,8 @@ * Runs in the next task after the page is done loading and/or prerendering. | ||
opts = opts || {}; | ||
const softNavsEnabled = softNavs(opts); | ||
let metric = initMetric('TTFB'); | ||
let report = bindReporter(onReport, metric, TTFBThresholds, opts.reportAllChanges); | ||
whenReady(() => { | ||
const navigationEntry = getNavigationEntry(); | ||
if (navigationEntry) { | ||
if (hardNavEntry) { | ||
const responseStart = hardNavEntry.responseStart; | ||
// The activationStart reference is used because TTFB should be | ||
@@ -69,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); | ||
@@ -76,8 +80,22 @@ // Only report TTFB after bfcache restores if a `navigation` entry | ||
onBFCacheRestore(() => { | ||
metric = initMetric('TTFB', 0); | ||
metric = initMetric('TTFB', 0, 'back-forward-cache', metric.navigationId); | ||
report = bindReporter(onReport, metric, TTFBThresholds, opts.reportAllChanges); | ||
report(true); | ||
}); | ||
// Listen for soft-navigation entries and emit a dummy 0 TTFB entry | ||
const reportSoftNavTTFBs = (entries) => { | ||
entries.forEach((entry) => { | ||
if (entry.navigationId) { | ||
metric = initMetric('TTFB', 0, 'soft-navigation', entry.navigationId); | ||
metric.entries = [entry]; | ||
report = bindReporter(onReport, metric, TTFBThresholds, opts.reportAllChanges); | ||
report(true); | ||
} | ||
}); | ||
}; | ||
if (softNavsEnabled) { | ||
observe('soft-navigation', reportSoftNavTTFBs, opts); | ||
} | ||
} | ||
}); | ||
}; |
@@ -13,2 +13,3 @@ export * from './types/base.js'; | ||
paint: PerformancePaintTiming; | ||
'soft-navigation': SoftNavigationEntry; | ||
} | ||
@@ -23,11 +24,20 @@ declare global { | ||
} | ||
interface PerformancePaintTiming extends PerformanceEntry { | ||
navigationId?: string; | ||
} | ||
interface PerformanceObserverInit { | ||
durationThreshold?: number; | ||
includeSoftNavigationObservations?: boolean; | ||
} | ||
interface PerformanceNavigationTiming { | ||
activationStart?: number; | ||
navigationId?: string; | ||
} | ||
interface SoftNavigationEntry extends PerformanceEntry { | ||
navigationId?: string; | ||
} | ||
interface PerformanceEventTiming extends PerformanceEntry { | ||
duration: DOMHighResTimeStamp; | ||
interactionId: number; | ||
navigationId?: string; | ||
} | ||
@@ -38,2 +48,3 @@ interface LayoutShiftAttribution { | ||
currentRect: DOMRectReadOnly; | ||
navigationId?: string; | ||
} | ||
@@ -44,2 +55,3 @@ interface LayoutShift extends PerformanceEntry { | ||
hadRecentInput: boolean; | ||
navigationId?: string; | ||
} | ||
@@ -53,2 +65,3 @@ interface LargestContentfulPaint extends PerformanceEntry { | ||
readonly element: Element | null; | ||
navigationId?: string; | ||
} | ||
@@ -55,0 +68,0 @@ interface PerformanceLongAnimationFrameTiming extends PerformanceEntry { |
@@ -53,4 +53,12 @@ import type { CLSMetric, CLSMetricWithAttribution } from './cls.js'; | ||
* 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 relevent for soft navigations where | ||
* the metric may be reported for a previous URL. | ||
* | ||
* navigationIds are UUID strings. | ||
*/ | ||
navigationId: string; | ||
} | ||
@@ -86,2 +94,3 @@ /** The union of supported metric types. */ | ||
durationThreshold?: number; | ||
reportSoftNavs?: boolean; | ||
} | ||
@@ -88,0 +97,0 @@ /** |
@@ -37,5 +37,5 @@ import type { LoadState, Metric } from './base.js'; | ||
* general page load issues. This can be used to access `serverTiming` for example: | ||
* navigationEntry?.serverTiming | ||
* navigationEntry.serverTiming | ||
*/ | ||
navigationEntry?: PerformanceNavigationTiming; | ||
navigationEntry?: PerformanceNavigationTiming | SoftNavigationEntry; | ||
} | ||
@@ -42,0 +42,0 @@ /** |
import type { LoadState, Metric } from './base.js'; | ||
import { FirstInputPolyfillEntry } from './polyfills.js'; | ||
/** | ||
@@ -7,3 +8,3 @@ * An FID-specific version of the Metric object. | ||
name: 'FID'; | ||
entries: PerformanceEventTiming[]; | ||
entries: (PerformanceEventTiming | FirstInputPolyfillEntry)[]; | ||
} | ||
@@ -32,4 +33,5 @@ /** | ||
* The `PerformanceEventTiming` entry corresponding to FID. | ||
* Polyfill is still used for bfcache restore and soft navs. | ||
*/ | ||
eventEntry: PerformanceEventTiming; | ||
eventEntry: PerformanceEventTiming | FirstInputPolyfillEntry; | ||
/** | ||
@@ -36,0 +38,0 @@ * The loading state of the document at the time when the first interaction |
@@ -51,5 +51,5 @@ import type { Metric } from './base.js'; | ||
* general page load issues. This can be used to access `serverTiming` for example: | ||
* navigationEntry?.serverTiming | ||
* navigationEntry.serverTiming | ||
*/ | ||
navigationEntry?: PerformanceNavigationTiming; | ||
navigationEntry?: PerformanceNavigationTiming | SoftNavigationEntry; | ||
/** | ||
@@ -56,0 +56,0 @@ * The `resource` entry for the LCP resource (if applicable), which is useful |
@@ -1,4 +0,6 @@ | ||
export type FirstInputPolyfillEntry = Omit<PerformanceEventTiming, 'processingEnd'>; | ||
export type FirstInputPolyfillEntry = Omit<PerformanceEventTiming, 'processingEnd'> & { | ||
navigationId: PerformanceNavigationTiming['navigationId']; | ||
}; | ||
export interface FirstInputPolyfillCallback { | ||
(entry: FirstInputPolyfillEntry): void; | ||
(entries: [FirstInputPolyfillEntry]): void; | ||
} |
@@ -7,3 +7,3 @@ import type { Metric } from './base.js'; | ||
name: 'TTFB'; | ||
entries: PerformanceNavigationTiming[]; | ||
entries: PerformanceNavigationTiming[] | SoftNavigationEntry[]; | ||
} | ||
@@ -51,5 +51,5 @@ /** | ||
* general page load issues. This can be used to access `serverTiming` for | ||
* example: navigationEntry?.serverTiming | ||
* example: navigationEntry.serverTiming | ||
*/ | ||
navigationEntry?: PerformanceNavigationTiming; | ||
navigationEntry?: PerformanceNavigationTiming | SoftNavigationEntry; | ||
} | ||
@@ -56,0 +56,0 @@ /** |
@@ -1,1 +0,1 @@ | ||
var webVitals=function(t){"use strict";var e,n,r,i=function(){var t=self.performance&&performance.getEntriesByType&&performance.getEntriesByType("navigation")[0];if(t&&t.responseStart>0&&t.responseStart<performance.now())return t},a=function(t){if("loading"===document.readyState)return"loading";var e=i();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=function(t){var e=t.nodeName;return 1===t.nodeType?e.toLowerCase():e.toUpperCase().replace(/^#/,"")},c=function(t,e){var n="";try{for(;t&&9!==t.nodeType;){var r=t,i=r.id?"#"+r.id:o(r)+(r.classList&&r.classList.value&&r.classList.value.trim()&&r.classList.value.trim().length?"."+r.classList.value.trim().replace(/\s+/g,"."):"");if(n.length+i.length>(e||100)-1)return n||i;if(n=n?i+">"+n:i,r.id)break;t=r.parentNode}}catch(t){}return n},s=-1,u=function(){return s},f=function(t){addEventListener("pageshow",(function(e){e.persisted&&(s=e.timeStamp,t(e))}),!0)},d=function(){var t=i();return t&&t.activationStart||0},l=function(t,e){var n=i(),r="navigate";u()>=0?r="back-forward-cache":n&&(document.prerendering||d()>0?r="prerender":document.wasDiscarded?r="restore":n.type&&(r=n.type.replace(/_/g,"-")));return{name:t,value:void 0===e?-1:e,rating:"good",delta:0,entries:[],id:"v4-".concat(Date.now(),"-").concat(Math.floor(8999999999999*Math.random())+1e12),navigationType:r}},m=function(t,e,n){try{if(PerformanceObserver.supportedEntryTypes.includes(t)){var r=new PerformanceObserver((function(t){Promise.resolve().then((function(){e(t.getEntries())}))}));return r.observe(Object.assign({type:t,buffered:!0},n||{})),r}}catch(t){}},v=function(t,e,n,r){var i,a;return function(o){e.value>=0&&(o||r)&&((a=e.value-(i||0))||void 0===i)&&(i=e.value,e.delta=a,e.rating=function(t,e){return t>e[1]?"poor":t>e[0]?"needs-improvement":"good"}(e.value,n),t(e))}},p=function(t){requestAnimationFrame((function(){return requestAnimationFrame((function(){return t()}))}))},h=function(t){document.addEventListener("visibilitychange",(function(){"hidden"===document.visibilityState&&t()}))},g=function(t){var e=!1;return function(){e||(t(),e=!0)}},T=-1,y=function(){return"hidden"!==document.visibilityState||document.prerendering?1/0:0},E=function(t){"hidden"===document.visibilityState&&T>-1&&(T="visibilitychange"===t.type?t.timeStamp:0,b())},S=function(){addEventListener("visibilitychange",E,!0),addEventListener("prerenderingchange",E,!0)},b=function(){removeEventListener("visibilitychange",E,!0),removeEventListener("prerenderingchange",E,!0)},C=function(){return T<0&&(T=y(),S(),f((function(){setTimeout((function(){T=y(),S()}),0)}))),{get firstHiddenTime(){return T}}},L=function(t){document.prerendering?addEventListener("prerenderingchange",(function(){return t()}),!0):t()},M=[1800,3e3],D=function(t,e){e=e||{},L((function(){var n,r=C(),i=l("FCP"),a=m("paint",(function(t){t.forEach((function(t){"first-contentful-paint"===t.name&&(a.disconnect(),t.startTime<r.firstHiddenTime&&(i.value=Math.max(t.startTime-d(),0),i.entries.push(t),n(!0)))}))}));a&&(n=v(t,i,M,e.reportAllChanges),f((function(r){i=l("FCP"),n=v(t,i,M,e.reportAllChanges),p((function(){i.value=performance.now()-r.timeStamp,n(!0)}))})))}))},w=[.1,.25],I=0,F=1/0,P=0,k=function(t){t.forEach((function(t){t.interactionId&&(F=Math.min(F,t.interactionId),P=Math.max(P,t.interactionId),I=P?(P-F)/7+1:0)}))},x=function(){"interactionCount"in performance||e||(e=m("event",k,{type:"event",buffered:!0,durationThreshold:0}))},A=[],B=new Map,O=0,R=function(){return(e?I:performance.interactionCount||0)-O},j=[],q=function(t){if(j.forEach((function(e){return e(t)})),t.interactionId||"first-input"===t.entryType){var e=A[A.length-1],n=B.get(t.interactionId);if(n||A.length<10||t.duration>e.latency){if(n)t.duration>n.latency?(n.entries=[t],n.latency=t.duration):t.duration===n.latency&&t.startTime===n.entries[0].startTime&&n.entries.push(t);else{var r={id:t.interactionId,latency:t.duration,entries:[t]};B.set(r.id,r),A.push(r)}A.sort((function(t,e){return e.latency-t.latency})),A.length>10&&A.splice(10).forEach((function(t){return B.delete(t.id)}))}}},N=function(t){var e=self.requestIdleCallback||self.setTimeout,n=-1;return t=g(t),"hidden"===document.visibilityState?t():(n=e(t),h(t)),n},H=[200,500],V=function(t,e){"PerformanceEventTiming"in self&&"interactionId"in PerformanceEventTiming.prototype&&(e=e||{},L((function(){var n;x();var r,i=l("INP"),a=function(t){N((function(){t.forEach(q);var e,n=(e=Math.min(A.length-1,Math.floor(R()/50)),A[e]);n&&n.latency!==i.value&&(i.value=n.latency,i.entries=n.entries,r())}))},o=m("event",a,{durationThreshold:null!==(n=e.durationThreshold)&&void 0!==n?n:40});r=v(t,i,H,e.reportAllChanges),o&&(o.observe({type:"first-input",buffered:!0}),h((function(){a(o.takeRecords()),r(!0)})),f((function(){O=0,A.length=0,B.clear(),i=l("INP"),r=v(t,i,H,e.reportAllChanges)})))})))},W=[],z=[],U=new WeakMap,_=new Map,G=-1,J=function(t){W=W.concat(t),K()},K=function(){G<0&&(G=N(Q))},Q=function(){_.size>10&&_.forEach((function(t,e){B.has(e)||_.delete(e)}));var t=A.map((function(t){return U.get(t.entries[0])})),e=z.length-50;z=z.filter((function(n,r){return r>=e||t.includes(n)}));for(var n=new Set,i=0;i<z.length;i++){var a=z[i];tt(a.startTime,a.processingEnd).forEach((function(t){n.add(t)}))}for(var o=0;o<50;o++){var c=W[W.length-1-o];if(!c||c.startTime<r)break;n.add(c)}W=Array.from(n),G=-1};j.push((function(t){t.interactionId&&t.target&&!_.has(t.interactionId)&&_.set(t.interactionId,t.target)}),(function(t){var e,n=t.startTime+t.duration;r=Math.max(r,t.processingEnd);for(var i=z.length-1;i>=0;i--){var a=z[i];if(Math.abs(n-a.renderTime)<=8){(e=a).startTime=Math.min(t.startTime,e.startTime),e.processingStart=Math.min(t.processingStart,e.processingStart),e.processingEnd=Math.max(t.processingEnd,e.processingEnd),e.entries.push(t);break}}e||(e={startTime:t.startTime,processingStart:t.processingStart,processingEnd:t.processingEnd,renderTime:n,entries:[t]},z.push(e)),(t.interactionId||"first-input"===t.entryType)&&U.set(t,e),K()}));var X,Y,Z,$,tt=function(t,e){for(var n,r=[],i=0;n=W[i];i++)if(!(n.startTime+n.duration<t)){if(n.startTime>e)break;r.push(n)}return r},et=[2500,4e3],nt={},rt=[800,1800],it=function t(e){document.prerendering?L((function(){return t(e)})):"complete"!==document.readyState?addEventListener("load",(function(){return t(e)}),!0):setTimeout(e,0)},at=function(t,e){e=e||{};var n=l("TTFB"),r=v(t,n,rt,e.reportAllChanges);it((function(){var a=i();a&&(n.value=Math.max(a.responseStart-d(),0),n.entries=[a],r(!0),f((function(){n=l("TTFB",0),(r=v(t,n,rt,e.reportAllChanges))(!0)})))}))},ot={passive:!0,capture:!0},ct=new Date,st=function(t,e){X||(X=e,Y=t,Z=new Date,dt(removeEventListener),ut())},ut=function(){if(Y>=0&&Y<Z-ct){var t={entryType:"first-input",name:X.type,target:X.target,cancelable:X.cancelable,startTime:X.timeStamp,processingStart:X.timeStamp+Y};$.forEach((function(e){e(t)})),$=[]}},ft=function(t){if(t.cancelable){var e=(t.timeStamp>1e12?new Date:performance.now())-t.timeStamp;"pointerdown"==t.type?function(t,e){var n=function(){st(t,e),i()},r=function(){i()},i=function(){removeEventListener("pointerup",n,ot),removeEventListener("pointercancel",r,ot)};addEventListener("pointerup",n,ot),addEventListener("pointercancel",r,ot)}(e,t):st(e,t)}},dt=function(t){["mousedown","keydown","touchstart","pointerdown"].forEach((function(e){return t(e,ft,ot)}))},lt=[100,300],mt=function(t,e){e=e||{},L((function(){var n,r=C(),i=l("FID"),a=function(t){t.startTime<r.firstHiddenTime&&(i.value=t.processingStart-t.startTime,i.entries.push(t),n(!0))},o=function(t){t.forEach(a)},c=m("first-input",o);n=v(t,i,lt,e.reportAllChanges),c&&(h(g((function(){o(c.takeRecords()),c.disconnect()}))),f((function(){var r;i=l("FID"),n=v(t,i,lt,e.reportAllChanges),$=[],Y=-1,X=null,dt(addEventListener),r=a,$.push(r),ut()})))}))};return t.CLSThresholds=w,t.FCPThresholds=M,t.FIDThresholds=lt,t.INPThresholds=H,t.LCPThresholds=et,t.TTFBThresholds=rt,t.onCLS=function(t,e){!function(t,e){e=e||{},D(g((function(){var n,r=l("CLS",0),i=0,a=[],o=function(t){t.forEach((function(t){if(!t.hadRecentInput){var e=a[0],n=a[a.length-1];i&&t.startTime-n.startTime<1e3&&t.startTime-e.startTime<5e3?(i+=t.value,a.push(t)):(i=t.value,a=[t])}})),i>r.value&&(r.value=i,r.entries=a,n())},c=m("layout-shift",o);c&&(n=v(t,r,w,e.reportAllChanges),h((function(){o(c.takeRecords()),n(!0)})),f((function(){i=0,r=l("CLS",0),n=v(t,r,w,e.reportAllChanges),p((function(){return n()}))})),setTimeout(n,0))})))}((function(e){var n=function(t){var e,n={};if(t.entries.length){var r=t.entries.reduce((function(t,e){return t&&t.value>e.value?t:e}));if(r&&r.sources&&r.sources.length){var i=(e=r.sources).find((function(t){return t.node&&1===t.node.nodeType}))||e[0];i&&(n={largestShiftTarget:c(i.node),largestShiftTime:r.startTime,largestShiftValue:r.value,largestShiftSource:i,largestShiftEntry:r,loadState:a(r.startTime)})}}return Object.assign(t,{attribution:n})}(e);t(n)}),e)},t.onFCP=function(t,e){D((function(e){var n=function(t){var e={timeToFirstByte:0,firstByteToFCP:t.value,loadState:a(u())};if(t.entries.length){var n=i(),r=t.entries[t.entries.length-1];if(n){var o=n.activationStart||0,c=Math.max(0,n.responseStart-o);e={timeToFirstByte:c,firstByteToFCP:t.value-c,loadState:a(t.entries[0].startTime),navigationEntry:n,fcpEntry:r}}}return Object.assign(t,{attribution:e})}(e);t(n)}),e)},t.onFID=function(t,e){mt((function(e){var n=function(t){var e=t.entries[0],n={eventTarget:c(e.target),eventType:e.name,eventTime:e.startTime,eventEntry:e,loadState:a(e.startTime)};return Object.assign(t,{attribution:n})}(e);t(n)}),e)},t.onINP=function(t,e){n||(n=m("long-animation-frame",J)),V((function(e){var n=function(t){var e=t.entries[0],n=U.get(e),r=e.processingStart,i=n.processingEnd,o=n.entries.sort((function(t,e){return t.processingStart-e.processingStart})),s=tt(e.startTime,i),u=t.entries.find((function(t){return t.target})),f=u&&u.target||_.get(e.interactionId),d=[e.startTime+e.duration,i].concat(s.map((function(t){return t.startTime+t.duration}))),l=Math.max.apply(Math,d),m={interactionTarget:c(f),interactionTargetElement:f,interactionType:e.name.startsWith("key")?"keyboard":"pointer",interactionTime:e.startTime,nextPaintTime:l,processedEventEntries:o,longAnimationFrameEntries:s,inputDelay:r-e.startTime,processingDuration:i-r,presentationDelay:Math.max(l-i,0),loadState:a(e.startTime)};return Object.assign(t,{attribution:m})}(e);t(n)}),e)},t.onLCP=function(t,e){!function(t,e){e=e||{},L((function(){var n,r=C(),i=l("LCP"),a=function(t){e.reportAllChanges||(t=t.slice(-1)),t.forEach((function(t){t.startTime<r.firstHiddenTime&&(i.value=Math.max(t.startTime-d(),0),i.entries=[t],n())}))},o=m("largest-contentful-paint",a);if(o){n=v(t,i,et,e.reportAllChanges);var c=g((function(){nt[i.id]||(a(o.takeRecords()),o.disconnect(),nt[i.id]=!0,n(!0))}));["keydown","click"].forEach((function(t){addEventListener(t,(function(){return N(c)}),!0)})),h(c),f((function(r){i=l("LCP"),n=v(t,i,et,e.reportAllChanges),p((function(){i.value=performance.now()-r.timeStamp,nt[i.id]=!0,n(!0)}))}))}}))}((function(e){var n=function(t){var e={timeToFirstByte:0,resourceLoadDelay:0,resourceLoadDuration:0,elementRenderDelay:t.value};if(t.entries.length){var n=i();if(n){var r=n.activationStart||0,a=t.entries[t.entries.length-1],o=a.url&&performance.getEntriesByType("resource").filter((function(t){return t.name===a.url}))[0],s=Math.max(0,n.responseStart-r),u=Math.max(s,o?(o.requestStart||o.startTime)-r:0),f=Math.max(u,o?o.responseEnd-r:0),d=Math.max(f,a.startTime-r);e={element:c(a.element),timeToFirstByte:s,resourceLoadDelay:u-s,resourceLoadDuration:f-u,elementRenderDelay:d-f,navigationEntry:n,lcpEntry:a},a.url&&(e.url=a.url),o&&(e.lcpResourceEntry=o)}}return Object.assign(t,{attribution:e})}(e);t(n)}),e)},t.onTTFB=function(t,e){at((function(e){var n=function(t){var e={waitingDuration:0,cacheDuration:0,dnsDuration:0,connectionDuration:0,requestDuration:0};if(t.entries.length){var n=t.entries[0],r=n.activationStart||0,i=Math.max((n.workerStart||n.fetchStart)-r,0),a=Math.max(n.domainLookupStart-r,0),o=Math.max(n.connectStart-r,0),c=Math.max(n.connectEnd-r,0);e={waitingDuration:i,cacheDuration:a-i,dnsDuration:o-a,connectionDuration:c-o,requestDuration:t.value-c,navigationEntry:n}}return Object.assign(t,{attribution:e})}(e);t(n)}),e)},t}({}); | ||
var webVitals=function(t){"use strict";var n,e,i,a,r=function(){var t=self.performance&&performance.getEntriesByType&&performance.getEntriesByType("navigation")[0];if(t&&t.responseStart>0&&t.responseStart<performance.now())return t},o=(null===(n=r())||void 0===n?void 0:n.navigationId)||"1",s=function(t){if("loading"===document.readyState)return"loading";var n=r();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"},c=function(t){var n=t.nodeName;return 1===t.nodeType?n.toLowerCase():n.toUpperCase().replace(/^#/,"")},u=function(t,n){var e="";try{for(;t&&9!==t.nodeType;){var i=t,a=i.id?"#"+i.id:c(i)+(i.classList&&i.classList.value&&i.classList.value.trim()&&i.classList.value.trim().length?"."+i.classList.value.trim().replace(/\s+/g,"."):"");if(e.length+a.length>(n||100)-1)return e||a;if(e=e?a+">"+e:a,i.id)break;t=i.parentNode}}catch(t){}return e},f=function(t,n,e,i){var a,r;return function(o){n.value>=0&&(o||i)&&((r=n.value-(a||0))||void 0===a)&&(a=n.value,n.delta=r,n.rating=function(t,n){return t>n[1]?"poor":t>n[0]?"needs-improvement":"good"}(n.value,e),t(n))}},d=function(t){requestAnimationFrame((function(){return requestAnimationFrame((function(){return t()}))}))},v=-1,l=function(){return v},g=function(t){addEventListener("pageshow",(function(n){n.persisted&&(v=n.timeStamp,t(n))}),!0)},m=function(){var t=r();return t&&t.activationStart||0},p=function(t,n,e,i){var a=r(),s="navigate";e?s=e:l()>=0?s="back-forward-cache":a&&(document.prerendering||m()>0?s="prerender":document.wasDiscarded?s="restore":a.type&&(s=a.type.replace(/_/g,"-")));return{name:t,value:void 0===n?-1:n,rating:"good",delta:0,entries:[],id:"v4-".concat(Date.now(),"-").concat(Math.floor(8999999999999*Math.random())+1e12),navigationType:s,navigationId:i||o}},h=function(t){return PerformanceObserver.supportedEntryTypes.includes("soft-navigation")&&t&&t.reportSoftNavs},T=function(t){if(t){var n=window.performance.getEntriesByType("soft-navigation").filter((function(n){return n.navigationId===t}));return n?n[0]:void 0}},I=function(t,n,e){var i=h(e);try{if(PerformanceObserver.supportedEntryTypes.includes(t)){var a=new PerformanceObserver((function(t){Promise.resolve().then((function(){n(t.getEntries())}))}));return a.observe(Object.assign({type:t,buffered:!0,includeSoftNavigationObservations:i},e||{})),a}}catch(t){}},y=function(t){document.addEventListener("visibilitychange",(function(){"hidden"===document.visibilityState&&t()}))},E=function(t){var n=!1;return function(){n||(t(),n=!0)}},S=-1,b=function(){return"hidden"!==document.visibilityState||document.prerendering?1/0:0},C=function(t){"hidden"===document.visibilityState&&S>-1&&(S="visibilitychange"===t.type?t.timeStamp:0,w())},L=function(){addEventListener("visibilitychange",C,!0),addEventListener("prerenderingchange",C,!0)},w=function(){removeEventListener("visibilitychange",C,!0),removeEventListener("prerenderingchange",C,!0)},M=function(){return arguments.length>0&&void 0!==arguments[0]&&arguments[0]&&(S=-1),S<0&&(S=b(),L(),g((function(){setTimeout((function(){S=b(),L()}),0)}))),{get firstHiddenTime(){return S}}},D=function(t){document.prerendering?addEventListener("prerenderingchange",(function(){return t()}),!0):t()},k=[1800,3e3],F=function(t,n){var e=h(n=n||{}),i=0;D((function(){var a,r=M(),s=p("FCP"),c=I("paint",(function(u){u.forEach((function(u){if("first-contentful-paint"===u.name){e?u.navigationId&&u.navigationId!==s.navigationId&&function(e,o){if(s=p("FCP",0,e,o),a=f(t,s,k,n.reportAllChanges),"soft-navigation"===e){r=M(!0);var c=o?T(o):null;i=c&&c.startTime||0}}("soft-navigation",u.navigationId):c.disconnect();var d=0;if(u.navigationId&&u.navigationId!==o){var v=T(u.navigationId),l=v&&v.startTime?v.startTime:0;d=Math.max(u.startTime-l,0)}else d=Math.max(u.startTime-m(),0);var g=e&&u.navigationId?T(u.navigationId):null,h=g&&g.startTime?g.startTime:0;(u.startTime<r.firstHiddenTime||e&&u.navigationId&&u.navigationId!==s.navigationId&&u.navigationId!==o&&h>i)&&(s.value=d,s.entries.push(u),s.navigationId=u.navigationId||"1",a(!0))}}))}),n);c&&(a=f(t,s,k,n.reportAllChanges),g((function(e){s=p("FCP",0,"back-forward-cache",s.navigationId),a=f(t,s,k,n.reportAllChanges),d((function(){s.value=performance.now()-e.timeStamp,a(!0)}))})))}))},A=[.1,.25],P=0,x=1/0,B=0,O=o,N=!1,R=function(t){t.forEach((function(t){t.interactionId&&(N&&t.navigationId&&t.navigationId!==O&&(O=t.navigationId,P=0,x=1/0,B=0),x=Math.min(x,t.interactionId),B=Math.max(B,t.interactionId),P=B?(B-x)/7+1:0)}))},j=function(){return e?P:performance.interactionCount||0},q=function(t){"interactionCount"in performance||e||(e=I("event",R,{type:"event",buffered:!0,durationThreshold:0,includeSoftNavigationObservations:N=t||!1}))},H=[],V=new Map,W=0,z=function(){W=j(),H.length=0,V.clear()},U=function(){var t=Math.min(H.length-1,Math.floor((j()-W)/50));return H[t]},_=[],G=function(t){if(_.forEach((function(n){return n(t)})),t.interactionId||"first-input"===t.entryType){var n=H[H.length-1],e=V.get(t.interactionId);if(e||H.length<10||t.duration>n.latency){if(e)t.duration>e.latency?(e.entries=[t],e.latency=t.duration):t.duration===e.latency&&t.startTime===e.entries[0].startTime&&e.entries.push(t);else{var i={id:t.interactionId,latency:t.duration,entries:[t]};V.set(i.id,i),H.push(i)}H.sort((function(t,n){return n.latency-t.latency})),H.length>10&&H.splice(10).forEach((function(t){return V.delete(t.id)}))}}},J=function(t){var n=self.requestIdleCallback||self.setTimeout,e=-1;return t=E(t),"hidden"===document.visibilityState?t():(e=n(t),y(t)),e},K=[200,500],Q=function(t,n){if("PerformanceEventTiming"in self&&"interactionId"in PerformanceEventTiming.prototype){var e=h(n=n||{}),i=!1,a=0;D((function(){var r;q(e);var o,s=p("INP"),c=function(e,r){if(z(),s=p("INP",0,e,r),o=f(t,s,K,n.reportAllChanges),i=!1,"soft-navigation"===e){var c=T(r);a=c&&c.startTime?c.startTime:0}},u=function(t){J((function(){var e;t.forEach(G),(e=U())&&(e.latency!==s.value||n&&n.reportAllChanges)&&(s.value=e.latency,s.entries=e.entries),o()}))},v=I("event",u,{durationThreshold:null!==(r=n.durationThreshold)&&void 0!==r?r:40,opts:n});if(o=f(t,s,K,n.reportAllChanges),v){v.observe({type:"first-input",buffered:!0,includeSoftNavigationObservations:e}),y((function(){u(v.takeRecords()),o(!0)})),g((function(){z(),c("back-forward-cache",s.navigationId),d((function(){return o()}))}));e&&I("soft-navigation",(function(e){e.forEach((function(e){var r=T(e.navigationId),u=r&&r.startTime?r.startTime:0;e.navigationId&&e.navigationId!==s.navigationId&&u>a&&(!i&&s.value>0&&o(!0),c("soft-navigation",e.navigationId),o=f(t,s,K,n.reportAllChanges))}))}),n)}}))}},X=[],Y=[],Z=new WeakMap,$=new Map,tt=-1,nt=function(t){X=X.concat(t),et()},et=function(){tt<0&&(tt=J(it))},it=function(){$.size>10&&$.forEach((function(t,n){V.has(n)||$.delete(n)}));var t=H.map((function(t){return Z.get(t.entries[0])})),n=Y.length-50;Y=Y.filter((function(e,i){return i>=n||t.includes(e)}));for(var e=new Set,i=0;i<Y.length;i++){var r=Y[i];ct(r.startTime,r.processingEnd).forEach((function(t){e.add(t)}))}for(var o=0;o<50;o++){var s=X[X.length-1-o];if(!s||s.startTime<a)break;e.add(s)}X=Array.from(e),tt=-1};_.push((function(t){t.interactionId&&t.target&&!$.has(t.interactionId)&&$.set(t.interactionId,t.target)}),(function(t){var n,e=t.startTime+t.duration;a=Math.max(a,t.processingEnd);for(var i=Y.length-1;i>=0;i--){var r=Y[i];if(Math.abs(e-r.renderTime)<=8){(n=r).startTime=Math.min(t.startTime,n.startTime),n.processingStart=Math.min(t.processingStart,n.processingStart),n.processingEnd=Math.max(t.processingEnd,n.processingEnd),n.entries.push(t);break}}n||(n={startTime:t.startTime,processingStart:t.processingStart,processingEnd:t.processingEnd,renderTime:e,entries:[t]},Y.push(n)),(t.interactionId||"first-input"===t.entryType)&&Z.set(t,n),et()}));var at,rt,ot,st,ct=function(t,n){for(var e,i=[],a=0;e=X[a];a++)if(!(e.startTime+e.duration<t)){if(e.startTime>n)break;i.push(e)}return i},ut=[2500,4e3],ft=[800,1800],dt=r(),vt=function t(n){document.prerendering?D((function(){return t(n)})):"complete"!==document.readyState?addEventListener("load",(function(){return t(n)}),!0):setTimeout(n,0)},lt=function(t,n){var e=h(n=n||{}),i=p("TTFB"),a=f(t,i,ft,n.reportAllChanges);vt((function(){if(dt){var r=dt.responseStart;i.value=Math.max(r-m(),0),i.entries=[dt],a(!0),g((function(){i=p("TTFB",0,"back-forward-cache",i.navigationId),(a=f(t,i,ft,n.reportAllChanges))(!0)}));e&&I("soft-navigation",(function(e){e.forEach((function(e){e.navigationId&&((i=p("TTFB",0,"soft-navigation",e.navigationId)).entries=[e],(a=f(t,i,ft,n.reportAllChanges))(!0))}))}),n)}}))},gt={passive:!0,capture:!0},mt=new Date,pt=function(t,n){at||(at=n,rt=t,ot=new Date,It(removeEventListener),ht())},ht=function(){if(rt>=0&&rt<ot-mt){var t={entryType:"first-input",name:at.type,target:at.target,cancelable:at.cancelable,startTime:at.timeStamp,processingStart:at.timeStamp+rt};st.forEach((function(n){n([t])})),st=[]}},Tt=function(t){if(t.cancelable){var n=(t.timeStamp>1e12?new Date:performance.now())-t.timeStamp;"pointerdown"==t.type?function(t,n){var e=function(){pt(t,n),a()},i=function(){a()},a=function(){removeEventListener("pointerup",e,gt),removeEventListener("pointercancel",i,gt)};addEventListener("pointerup",e,gt),addEventListener("pointercancel",i,gt)}(n,t):pt(n,t)}},It=function(t){["mousedown","keydown","touchstart","pointerdown"].forEach((function(n){return t(n,Tt,gt)}))},yt=[100,300],Et=function(t,n){var e=h(n=n||{});D((function(){var i,a=M(),r=p("FID"),s=function(s){s.forEach((function(s){var u,d;e?s.navigationId&&s.navigationId!==r.navigationId&&(u="soft-navigation",d=s.navigationId,"soft-navigation"===u&&(a=M(!0)),r=p("FID",0,u,d),i=f(t,r,yt,n.reportAllChanges)):c.disconnect(),s.startTime<a.firstHiddenTime&&(r.value=s.processingStart-s.startTime,r.entries.push(s),r.navigationId=s.navigationId||o,i(!0))}))},c=I("first-input",s,n);i=f(t,r,yt,n.reportAllChanges),c&&(y((function(){s(c.takeRecords()),e||c.disconnect()})),g((function(){var e;r=p("FID",0,"back-forward-cache",r.navigationId),i=f(t,r,yt,n.reportAllChanges),st=[],rt=-1,at=null,It(addEventListener),e=s,st.push(e),ht()})))}))};return t.CLSThresholds=A,t.FCPThresholds=k,t.FIDThresholds=yt,t.INPThresholds=K,t.LCPThresholds=ut,t.TTFBThresholds=ft,t.onCLS=function(t,n){!function(t,n){var e=h(n=n||{}),i=!1,a=0;F(E((function(){var r,o=p("CLS",0),s=0,c=[],u=function(e,c){if(o=p("CLS",0,e,c),r=f(t,o,A,n.reportAllChanges),s=0,i=!1,"soft-navigation"===e){var u=T(c);a=u&&u.startTime||0}},v=function(t){t.forEach((function(t){if(e&&t.navigationId&&t.navigationId!==o.navigationId&&(s>o.value&&(o.value=s,o.entries=c),r(!0),u("soft-navigation",t.navigationId)),!t.hadRecentInput){var n=c[0],i=c[c.length-1];s&&t.startTime-i.startTime<1e3&&t.startTime-n.startTime<5e3?(s+=t.value,c.push(t)):(s=t.value,c=[t])}})),s>o.value&&(o.value=s,o.entries=c,r())},l=I("layout-shift",v,n);l&&(r=f(t,o,A,n.reportAllChanges),y((function(){v(l.takeRecords()),r(!0),i=!0})),g((function(){u("back-forward-cache",o.navigationId),d((function(){return r()}))})),e&&I("soft-navigation",(function(e){e.forEach((function(e){var s=e.navigationId,c=s?T(s):null;s&&s!==o.navigationId&&c&&(c.startTime||0)>a&&(i||r(!0),u("soft-navigation",e.navigationId),r=f(t,o,A,n.reportAllChanges))}))}),n),setTimeout(r,0))})))}((function(n){var e=function(t){var n,e={};if(t.entries.length){var i=t.entries.reduce((function(t,n){return t&&t.value>n.value?t:n}));if(i&&i.sources&&i.sources.length){var a=(n=i.sources).find((function(t){return t.node&&1===t.node.nodeType}))||n[0];a&&(e={largestShiftTarget:u(a.node),largestShiftTime:i.startTime,largestShiftValue:i.value,largestShiftSource:a,largestShiftEntry:i,loadState:s(i.startTime)})}}return Object.assign(t,{attribution:e})}(n);t(e)}),n)},t.onFCP=function(t,n){F((function(n){var e=function(t){var n={timeToFirstByte:0,firstByteToFCP:t.value,loadState:s(l())};if(t.entries.length){var e,i=t.entries[t.entries.length-1],a=0;if(t.navigationId&&t.navigationId!==o)a=(e=T(t.navigationId))?e.startTime:0;else if(e=r()){var c=e.responseStart,u=e.activationStart||0;a=Math.max(0,c-u)}e&&(n={timeToFirstByte:a,firstByteToFCP:t.value-a,loadState:s(t.entries[0].startTime),navigationEntry:e,fcpEntry:i})}return Object.assign(t,{attribution:n})}(n);t(e)}),n)},t.onFID=function(t,n){Et((function(n){var e=function(t){var n=t.entries[0],e={eventTarget:u(n.target),eventType:n.name,eventTime:n.startTime,eventEntry:n,loadState:s(n.startTime)};return Object.assign(t,{attribution:e})}(n);t(e)}),n)},t.onINP=function(t,n){i||(i=I("long-animation-frame",nt)),Q((function(n){var e=function(t){var n=t.entries[0],e=Z.get(n),i=n.processingStart,a=e.processingEnd,r=e.entries.sort((function(t,n){return t.processingStart-n.processingStart})),o=ct(n.startTime,a),c=t.entries.find((function(t){return t.target})),f=c&&c.target||$.get(n.interactionId),d=[n.startTime+n.duration,a].concat(o.map((function(t){return t.startTime+t.duration}))),v=Math.max.apply(Math,d),l={interactionTarget:u(f),interactionTargetElement:f,interactionType:n.name.startsWith("key")?"keyboard":"pointer",interactionTime:n.startTime,nextPaintTime:v,processedEventEntries:r,longAnimationFrameEntries:o,inputDelay:i-n.startTime,processingDuration:a-i,presentationDelay:Math.max(v-a,0),loadState:s(n.startTime)};return Object.assign(t,{attribution:l})}(n);t(e)}),n)},t.onLCP=function(t,n){!function(t,n){var e=!1,i=h(n=n||{}),a=0;D((function(){var r,s=M(),c=p("LCP"),u=function(i,o){if(c=p("LCP",0,i,o),r=f(t,c,ut,n.reportAllChanges),e=!1,"soft-navigation"===i){s=M(!0);var u=T(o);a=u&&u.startTime?u.startTime:0}["keydown","click"].forEach((function(t){addEventListener(t,(function(){return J(l)}),{once:!0})}))},v=function(t){t.forEach((function(t){if(t){i&&t.navigationId&&t.navigationId!==c.navigationId&&(e||r(!0),u("soft-navigation",t.navigationId));var n=0;if(t.navigationId&&t.navigationId!==o){var a=T(t.navigationId),f=a&&a.startTime?a.startTime:0;n=Math.max(t.startTime-f,0)}else n=Math.max(t.startTime-m(),0);t.startTime<s.firstHiddenTime&&(c.value=n,c.entries=[t],c.navigationId=t.navigationId||o,r())}}))},l=function(){e||(v(h.takeRecords()),i||h.disconnect(),e=!0,r(!0))},h=I("largest-contentful-paint",v,n);h&&(r=f(t,c,ut,n.reportAllChanges),y(l),g((function(t){u("back-forward-cache",c.navigationId),d((function(){c.value=performance.now()-t.timeStamp,e=!0,r(!0)}))})),i&&I("soft-navigation",(function(t){t.forEach((function(t){var n=t.navigationId?T(t.navigationId):null;t.navigationId&&t.navigationId!==c.navigationId&&n&&(n.startTime||0)>a&&(e||r(!0),u("soft-navigation",t.navigationId))}))}),n))}))}((function(n){var e=function(t){var n={timeToFirstByte:0,resourceLoadDelay:0,resourceLoadDuration:0,elementRenderDelay:t.value};if(t.entries.length){var e,i=0,a=0,s=0;if(t.navigationId&&t.navigationId!==o?i=s=(e=T(t.navigationId))?e.startTime:0:(i=(e=r())&&e.activationStart?e.activationStart:0,a=e&&e.responseStart?e.responseStart:0),e){var c=t.entries[t.entries.length-1],f=c.url&&performance.getEntriesByType("resource").filter((function(t){return t.name===c.url}))[0],d=Math.max(0,a-i),v=Math.max(d,f?(f.requestStart||f.startTime)-i:0),l=Math.max(v-s,f?f.responseEnd-i:0,0),g=Math.max(l-s,c?c.startTime-i:0,0);n={element:u(c.element),timeToFirstByte:d,resourceLoadDelay:v-d,resourceLoadDuration:l-v,elementRenderDelay:g-l,navigationEntry:e,lcpEntry:c},c.url&&(n.url=c.url),f&&(n.lcpResourceEntry=f)}}return Object.assign(t,{attribution:n})}(n);t(e)}),n)},t.onTTFB=function(t,n){lt((function(n){var e=function(t){var n={waitingDuration:0,cacheDuration:0,dnsDuration:0,connectionDuration:0,requestDuration:0};if(t.entries.length){var e=t.entries[0],i=e.activationStart||0,a=Math.max((e.workerStart||e.fetchStart)-i,0),r=Math.max(e.domainLookupStart-i||0,0),o=Math.max(e.connectStart-i||0,0),s=Math.max(e.connectEnd-i||0,0);n={waitingDuration:a,cacheDuration:r-a,dnsDuration:o-r,connectionDuration:s-o,requestDuration:t.value-s,navigationEntry:e}}return Object.assign(t,{attribution:n})}(n);t(e)}),n)},t}({}); |
@@ -1,1 +0,1 @@ | ||
var t,e,n,r=function(){var t=self.performance&&performance.getEntriesByType&&performance.getEntriesByType("navigation")[0];if(t&&t.responseStart>0&&t.responseStart<performance.now())return t},i=function(t){if("loading"===document.readyState)return"loading";var e=r();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"},a=function(t){var e=t.nodeName;return 1===t.nodeType?e.toLowerCase():e.toUpperCase().replace(/^#/,"")},o=function(t,e){var n="";try{for(;t&&9!==t.nodeType;){var r=t,i=r.id?"#"+r.id:a(r)+(r.classList&&r.classList.value&&r.classList.value.trim()&&r.classList.value.trim().length?"."+r.classList.value.trim().replace(/\s+/g,"."):"");if(n.length+i.length>(e||100)-1)return n||i;if(n=n?i+">"+n:i,r.id)break;t=r.parentNode}}catch(t){}return n},c=-1,u=function(){return c},s=function(t){addEventListener("pageshow",(function(e){e.persisted&&(c=e.timeStamp,t(e))}),!0)},f=function(){var t=r();return t&&t.activationStart||0},d=function(t,e){var n=r(),i="navigate";u()>=0?i="back-forward-cache":n&&(document.prerendering||f()>0?i="prerender":document.wasDiscarded?i="restore":n.type&&(i=n.type.replace(/_/g,"-")));return{name:t,value:void 0===e?-1:e,rating:"good",delta:0,entries:[],id:"v4-".concat(Date.now(),"-").concat(Math.floor(8999999999999*Math.random())+1e12),navigationType:i}},l=function(t,e,n){try{if(PerformanceObserver.supportedEntryTypes.includes(t)){var r=new PerformanceObserver((function(t){Promise.resolve().then((function(){e(t.getEntries())}))}));return r.observe(Object.assign({type:t,buffered:!0},n||{})),r}}catch(t){}},m=function(t,e,n,r){var i,a;return function(o){e.value>=0&&(o||r)&&((a=e.value-(i||0))||void 0===i)&&(i=e.value,e.delta=a,e.rating=function(t,e){return t>e[1]?"poor":t>e[0]?"needs-improvement":"good"}(e.value,n),t(e))}},p=function(t){requestAnimationFrame((function(){return requestAnimationFrame((function(){return t()}))}))},v=function(t){document.addEventListener("visibilitychange",(function(){"hidden"===document.visibilityState&&t()}))},g=function(t){var e=!1;return function(){e||(t(),e=!0)}},h=-1,T=function(){return"hidden"!==document.visibilityState||document.prerendering?1/0:0},y=function(t){"hidden"===document.visibilityState&&h>-1&&(h="visibilitychange"===t.type?t.timeStamp:0,S())},E=function(){addEventListener("visibilitychange",y,!0),addEventListener("prerenderingchange",y,!0)},S=function(){removeEventListener("visibilitychange",y,!0),removeEventListener("prerenderingchange",y,!0)},b=function(){return h<0&&(h=T(),E(),s((function(){setTimeout((function(){h=T(),E()}),0)}))),{get firstHiddenTime(){return h}}},L=function(t){document.prerendering?addEventListener("prerenderingchange",(function(){return t()}),!0):t()},C=[1800,3e3],M=function(t,e){e=e||{},L((function(){var n,r=b(),i=d("FCP"),a=l("paint",(function(t){t.forEach((function(t){"first-contentful-paint"===t.name&&(a.disconnect(),t.startTime<r.firstHiddenTime&&(i.value=Math.max(t.startTime-f(),0),i.entries.push(t),n(!0)))}))}));a&&(n=m(t,i,C,e.reportAllChanges),s((function(r){i=d("FCP"),n=m(t,i,C,e.reportAllChanges),p((function(){i.value=performance.now()-r.timeStamp,n(!0)}))})))}))},D=[.1,.25],w=function(t,e){!function(t,e){e=e||{},M(g((function(){var n,r=d("CLS",0),i=0,a=[],o=function(t){t.forEach((function(t){if(!t.hadRecentInput){var e=a[0],n=a[a.length-1];i&&t.startTime-n.startTime<1e3&&t.startTime-e.startTime<5e3?(i+=t.value,a.push(t)):(i=t.value,a=[t])}})),i>r.value&&(r.value=i,r.entries=a,n())},c=l("layout-shift",o);c&&(n=m(t,r,D,e.reportAllChanges),v((function(){o(c.takeRecords()),n(!0)})),s((function(){i=0,r=d("CLS",0),n=m(t,r,D,e.reportAllChanges),p((function(){return n()}))})),setTimeout(n,0))})))}((function(e){var n=function(t){var e,n={};if(t.entries.length){var r=t.entries.reduce((function(t,e){return t&&t.value>e.value?t:e}));if(r&&r.sources&&r.sources.length){var a=(e=r.sources).find((function(t){return t.node&&1===t.node.nodeType}))||e[0];a&&(n={largestShiftTarget:o(a.node),largestShiftTime:r.startTime,largestShiftValue:r.value,largestShiftSource:a,largestShiftEntry:r,loadState:i(r.startTime)})}}return Object.assign(t,{attribution:n})}(e);t(n)}),e)},x=function(t,e){M((function(e){var n=function(t){var e={timeToFirstByte:0,firstByteToFCP:t.value,loadState:i(u())};if(t.entries.length){var n=r(),a=t.entries[t.entries.length-1];if(n){var o=n.activationStart||0,c=Math.max(0,n.responseStart-o);e={timeToFirstByte:c,firstByteToFCP:t.value-c,loadState:i(t.entries[0].startTime),navigationEntry:n,fcpEntry:a}}}return Object.assign(t,{attribution:e})}(e);t(n)}),e)},I=0,k=1/0,A=0,F=function(t){t.forEach((function(t){t.interactionId&&(k=Math.min(k,t.interactionId),A=Math.max(A,t.interactionId),I=A?(A-k)/7+1:0)}))},P=function(){"interactionCount"in performance||t||(t=l("event",F,{type:"event",buffered:!0,durationThreshold:0}))},B=[],O=new Map,R=0,j=function(){return(t?I:performance.interactionCount||0)-R},q=[],H=function(t){if(q.forEach((function(e){return e(t)})),t.interactionId||"first-input"===t.entryType){var e=B[B.length-1],n=O.get(t.interactionId);if(n||B.length<10||t.duration>e.latency){if(n)t.duration>n.latency?(n.entries=[t],n.latency=t.duration):t.duration===n.latency&&t.startTime===n.entries[0].startTime&&n.entries.push(t);else{var r={id:t.interactionId,latency:t.duration,entries:[t]};O.set(r.id,r),B.push(r)}B.sort((function(t,e){return e.latency-t.latency})),B.length>10&&B.splice(10).forEach((function(t){return O.delete(t.id)}))}}},N=function(t){var e=self.requestIdleCallback||self.setTimeout,n=-1;return t=g(t),"hidden"===document.visibilityState?t():(n=e(t),v(t)),n},W=[200,500],z=function(t,e){"PerformanceEventTiming"in self&&"interactionId"in PerformanceEventTiming.prototype&&(e=e||{},L((function(){var n;P();var r,i=d("INP"),a=function(t){N((function(){t.forEach(H);var e,n=(e=Math.min(B.length-1,Math.floor(j()/50)),B[e]);n&&n.latency!==i.value&&(i.value=n.latency,i.entries=n.entries,r())}))},o=l("event",a,{durationThreshold:null!==(n=e.durationThreshold)&&void 0!==n?n:40});r=m(t,i,W,e.reportAllChanges),o&&(o.observe({type:"first-input",buffered:!0}),v((function(){a(o.takeRecords()),r(!0)})),s((function(){R=0,B.length=0,O.clear(),i=d("INP"),r=m(t,i,W,e.reportAllChanges)})))})))},U=[],V=[],_=new WeakMap,G=new Map,J=-1,K=function(t){U=U.concat(t),Q()},Q=function(){J<0&&(J=N(X))},X=function(){G.size>10&&G.forEach((function(t,e){O.has(e)||G.delete(e)}));var t=B.map((function(t){return _.get(t.entries[0])})),e=V.length-50;V=V.filter((function(n,r){return r>=e||t.includes(n)}));for(var r=new Set,i=0;i<V.length;i++){var a=V[i];et(a.startTime,a.processingEnd).forEach((function(t){r.add(t)}))}for(var o=0;o<50;o++){var c=U[U.length-1-o];if(!c||c.startTime<n)break;r.add(c)}U=Array.from(r),J=-1};q.push((function(t){t.interactionId&&t.target&&!G.has(t.interactionId)&&G.set(t.interactionId,t.target)}),(function(t){var e,r=t.startTime+t.duration;n=Math.max(n,t.processingEnd);for(var i=V.length-1;i>=0;i--){var a=V[i];if(Math.abs(r-a.renderTime)<=8){(e=a).startTime=Math.min(t.startTime,e.startTime),e.processingStart=Math.min(t.processingStart,e.processingStart),e.processingEnd=Math.max(t.processingEnd,e.processingEnd),e.entries.push(t);break}}e||(e={startTime:t.startTime,processingStart:t.processingStart,processingEnd:t.processingEnd,renderTime:r,entries:[t]},V.push(e)),(t.interactionId||"first-input"===t.entryType)&&_.set(t,e),Q()}));var Y,Z,$,tt,et=function(t,e){for(var n,r=[],i=0;n=U[i];i++)if(!(n.startTime+n.duration<t)){if(n.startTime>e)break;r.push(n)}return r},nt=function(t,n){e||(e=l("long-animation-frame",K)),z((function(e){var n=function(t){var e=t.entries[0],n=_.get(e),r=e.processingStart,a=n.processingEnd,c=n.entries.sort((function(t,e){return t.processingStart-e.processingStart})),u=et(e.startTime,a),s=t.entries.find((function(t){return t.target})),f=s&&s.target||G.get(e.interactionId),d=[e.startTime+e.duration,a].concat(u.map((function(t){return t.startTime+t.duration}))),l=Math.max.apply(Math,d),m={interactionTarget:o(f),interactionTargetElement:f,interactionType:e.name.startsWith("key")?"keyboard":"pointer",interactionTime:e.startTime,nextPaintTime:l,processedEventEntries:c,longAnimationFrameEntries:u,inputDelay:r-e.startTime,processingDuration:a-r,presentationDelay:Math.max(l-a,0),loadState:i(e.startTime)};return Object.assign(t,{attribution:m})}(e);t(n)}),n)},rt=[2500,4e3],it={},at=function(t,e){!function(t,e){e=e||{},L((function(){var n,r=b(),i=d("LCP"),a=function(t){e.reportAllChanges||(t=t.slice(-1)),t.forEach((function(t){t.startTime<r.firstHiddenTime&&(i.value=Math.max(t.startTime-f(),0),i.entries=[t],n())}))},o=l("largest-contentful-paint",a);if(o){n=m(t,i,rt,e.reportAllChanges);var c=g((function(){it[i.id]||(a(o.takeRecords()),o.disconnect(),it[i.id]=!0,n(!0))}));["keydown","click"].forEach((function(t){addEventListener(t,(function(){return N(c)}),!0)})),v(c),s((function(r){i=d("LCP"),n=m(t,i,rt,e.reportAllChanges),p((function(){i.value=performance.now()-r.timeStamp,it[i.id]=!0,n(!0)}))}))}}))}((function(e){var n=function(t){var e={timeToFirstByte:0,resourceLoadDelay:0,resourceLoadDuration:0,elementRenderDelay:t.value};if(t.entries.length){var n=r();if(n){var i=n.activationStart||0,a=t.entries[t.entries.length-1],c=a.url&&performance.getEntriesByType("resource").filter((function(t){return t.name===a.url}))[0],u=Math.max(0,n.responseStart-i),s=Math.max(u,c?(c.requestStart||c.startTime)-i:0),f=Math.max(s,c?c.responseEnd-i:0),d=Math.max(f,a.startTime-i);e={element:o(a.element),timeToFirstByte:u,resourceLoadDelay:s-u,resourceLoadDuration:f-s,elementRenderDelay:d-f,navigationEntry:n,lcpEntry:a},a.url&&(e.url=a.url),c&&(e.lcpResourceEntry=c)}}return Object.assign(t,{attribution:e})}(e);t(n)}),e)},ot=[800,1800],ct=function t(e){document.prerendering?L((function(){return t(e)})):"complete"!==document.readyState?addEventListener("load",(function(){return t(e)}),!0):setTimeout(e,0)},ut=function(t,e){e=e||{};var n=d("TTFB"),i=m(t,n,ot,e.reportAllChanges);ct((function(){var a=r();a&&(n.value=Math.max(a.responseStart-f(),0),n.entries=[a],i(!0),s((function(){n=d("TTFB",0),(i=m(t,n,ot,e.reportAllChanges))(!0)})))}))},st=function(t,e){ut((function(e){var n=function(t){var e={waitingDuration:0,cacheDuration:0,dnsDuration:0,connectionDuration:0,requestDuration:0};if(t.entries.length){var n=t.entries[0],r=n.activationStart||0,i=Math.max((n.workerStart||n.fetchStart)-r,0),a=Math.max(n.domainLookupStart-r,0),o=Math.max(n.connectStart-r,0),c=Math.max(n.connectEnd-r,0);e={waitingDuration:i,cacheDuration:a-i,dnsDuration:o-a,connectionDuration:c-o,requestDuration:t.value-c,navigationEntry:n}}return Object.assign(t,{attribution:e})}(e);t(n)}),e)},ft={passive:!0,capture:!0},dt=new Date,lt=function(t,e){Y||(Y=e,Z=t,$=new Date,vt(removeEventListener),mt())},mt=function(){if(Z>=0&&Z<$-dt){var t={entryType:"first-input",name:Y.type,target:Y.target,cancelable:Y.cancelable,startTime:Y.timeStamp,processingStart:Y.timeStamp+Z};tt.forEach((function(e){e(t)})),tt=[]}},pt=function(t){if(t.cancelable){var e=(t.timeStamp>1e12?new Date:performance.now())-t.timeStamp;"pointerdown"==t.type?function(t,e){var n=function(){lt(t,e),i()},r=function(){i()},i=function(){removeEventListener("pointerup",n,ft),removeEventListener("pointercancel",r,ft)};addEventListener("pointerup",n,ft),addEventListener("pointercancel",r,ft)}(e,t):lt(e,t)}},vt=function(t){["mousedown","keydown","touchstart","pointerdown"].forEach((function(e){return t(e,pt,ft)}))},gt=[100,300],ht=function(t,e){e=e||{},L((function(){var n,r=b(),i=d("FID"),a=function(t){t.startTime<r.firstHiddenTime&&(i.value=t.processingStart-t.startTime,i.entries.push(t),n(!0))},o=function(t){t.forEach(a)},c=l("first-input",o);n=m(t,i,gt,e.reportAllChanges),c&&(v(g((function(){o(c.takeRecords()),c.disconnect()}))),s((function(){var r;i=d("FID"),n=m(t,i,gt,e.reportAllChanges),tt=[],Z=-1,Y=null,vt(addEventListener),r=a,tt.push(r),mt()})))}))},Tt=function(t,e){ht((function(e){var n=function(t){var e=t.entries[0],n={eventTarget:o(e.target),eventType:e.name,eventTime:e.startTime,eventEntry:e,loadState:i(e.startTime)};return Object.assign(t,{attribution:n})}(e);t(n)}),e)};export{D as CLSThresholds,C as FCPThresholds,gt as FIDThresholds,W as INPThresholds,rt as LCPThresholds,ot as TTFBThresholds,w as onCLS,x as onFCP,Tt as onFID,nt as onINP,at as onLCP,st as onTTFB}; | ||
var t,n,e,i,a=function(){var t=self.performance&&performance.getEntriesByType&&performance.getEntriesByType("navigation")[0];if(t&&t.responseStart>0&&t.responseStart<performance.now())return t},r=(null===(t=a())||void 0===t?void 0:t.navigationId)||"1",o=function(t){if("loading"===document.readyState)return"loading";var n=a();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"},c=function(t){var n=t.nodeName;return 1===t.nodeType?n.toLowerCase():n.toUpperCase().replace(/^#/,"")},s=function(t,n){var e="";try{for(;t&&9!==t.nodeType;){var i=t,a=i.id?"#"+i.id:c(i)+(i.classList&&i.classList.value&&i.classList.value.trim()&&i.classList.value.trim().length?"."+i.classList.value.trim().replace(/\s+/g,"."):"");if(e.length+a.length>(n||100)-1)return e||a;if(e=e?a+">"+e:a,i.id)break;t=i.parentNode}}catch(t){}return e},u=function(t,n,e,i){var a,r;return function(o){n.value>=0&&(o||i)&&((r=n.value-(a||0))||void 0===a)&&(a=n.value,n.delta=r,n.rating=function(t,n){return t>n[1]?"poor":t>n[0]?"needs-improvement":"good"}(n.value,e),t(n))}},f=function(t){requestAnimationFrame((function(){return requestAnimationFrame((function(){return t()}))}))},d=-1,v=function(){return d},g=function(t){addEventListener("pageshow",(function(n){n.persisted&&(d=n.timeStamp,t(n))}),!0)},l=function(){var t=a();return t&&t.activationStart||0},m=function(t,n,e,i){var o=a(),c="navigate";e?c=e:v()>=0?c="back-forward-cache":o&&(document.prerendering||l()>0?c="prerender":document.wasDiscarded?c="restore":o.type&&(c=o.type.replace(/_/g,"-")));return{name:t,value:void 0===n?-1:n,rating:"good",delta:0,entries:[],id:"v4-".concat(Date.now(),"-").concat(Math.floor(8999999999999*Math.random())+1e12),navigationType:c,navigationId:i||r}},p=function(t){return PerformanceObserver.supportedEntryTypes.includes("soft-navigation")&&t&&t.reportSoftNavs},h=function(t){if(t){var n=window.performance.getEntriesByType("soft-navigation").filter((function(n){return n.navigationId===t}));return n?n[0]:void 0}},T=function(t,n,e){var i=p(e);try{if(PerformanceObserver.supportedEntryTypes.includes(t)){var a=new PerformanceObserver((function(t){Promise.resolve().then((function(){n(t.getEntries())}))}));return a.observe(Object.assign({type:t,buffered:!0,includeSoftNavigationObservations:i},e||{})),a}}catch(t){}},I=function(t){document.addEventListener("visibilitychange",(function(){"hidden"===document.visibilityState&&t()}))},y=function(t){var n=!1;return function(){n||(t(),n=!0)}},E=-1,S=function(){return"hidden"!==document.visibilityState||document.prerendering?1/0:0},b=function(t){"hidden"===document.visibilityState&&E>-1&&(E="visibilitychange"===t.type?t.timeStamp:0,w())},C=function(){addEventListener("visibilitychange",b,!0),addEventListener("prerenderingchange",b,!0)},w=function(){removeEventListener("visibilitychange",b,!0),removeEventListener("prerenderingchange",b,!0)},L=function(){return arguments.length>0&&void 0!==arguments[0]&&arguments[0]&&(E=-1),E<0&&(E=S(),C(),g((function(){setTimeout((function(){E=S(),C()}),0)}))),{get firstHiddenTime(){return E}}},M=function(t){document.prerendering?addEventListener("prerenderingchange",(function(){return t()}),!0):t()},D=[1800,3e3],k=function(t,n){var e=p(n=n||{}),i=0;M((function(){var a,o=L(),c=m("FCP"),s=T("paint",(function(f){f.forEach((function(f){if("first-contentful-paint"===f.name){e?f.navigationId&&f.navigationId!==c.navigationId&&function(e,r){if(c=m("FCP",0,e,r),a=u(t,c,D,n.reportAllChanges),"soft-navigation"===e){o=L(!0);var s=r?h(r):null;i=s&&s.startTime||0}}("soft-navigation",f.navigationId):s.disconnect();var d=0;if(f.navigationId&&f.navigationId!==r){var v=h(f.navigationId),g=v&&v.startTime?v.startTime:0;d=Math.max(f.startTime-g,0)}else d=Math.max(f.startTime-l(),0);var p=e&&f.navigationId?h(f.navigationId):null,T=p&&p.startTime?p.startTime:0;(f.startTime<o.firstHiddenTime||e&&f.navigationId&&f.navigationId!==c.navigationId&&f.navigationId!==r&&T>i)&&(c.value=d,c.entries.push(f),c.navigationId=f.navigationId||"1",a(!0))}}))}),n);s&&(a=u(t,c,D,n.reportAllChanges),g((function(e){c=m("FCP",0,"back-forward-cache",c.navigationId),a=u(t,c,D,n.reportAllChanges),f((function(){c.value=performance.now()-e.timeStamp,a(!0)}))})))}))},A=[.1,.25],x=function(t,n){!function(t,n){var e=p(n=n||{}),i=!1,a=0;k(y((function(){var r,o=m("CLS",0),c=0,s=[],d=function(e,s){if(o=m("CLS",0,e,s),r=u(t,o,A,n.reportAllChanges),c=0,i=!1,"soft-navigation"===e){var f=h(s);a=f&&f.startTime||0}},v=function(t){t.forEach((function(t){if(e&&t.navigationId&&t.navigationId!==o.navigationId&&(c>o.value&&(o.value=c,o.entries=s),r(!0),d("soft-navigation",t.navigationId)),!t.hadRecentInput){var n=s[0],i=s[s.length-1];c&&t.startTime-i.startTime<1e3&&t.startTime-n.startTime<5e3?(c+=t.value,s.push(t)):(c=t.value,s=[t])}})),c>o.value&&(o.value=c,o.entries=s,r())},l=T("layout-shift",v,n);l&&(r=u(t,o,A,n.reportAllChanges),I((function(){v(l.takeRecords()),r(!0),i=!0})),g((function(){d("back-forward-cache",o.navigationId),f((function(){return r()}))})),e&&T("soft-navigation",(function(e){e.forEach((function(e){var c=e.navigationId,s=c?h(c):null;c&&c!==o.navigationId&&s&&(s.startTime||0)>a&&(i||r(!0),d("soft-navigation",e.navigationId),r=u(t,o,A,n.reportAllChanges))}))}),n),setTimeout(r,0))})))}((function(n){var e=function(t){var n,e={};if(t.entries.length){var i=t.entries.reduce((function(t,n){return t&&t.value>n.value?t:n}));if(i&&i.sources&&i.sources.length){var a=(n=i.sources).find((function(t){return t.node&&1===t.node.nodeType}))||n[0];a&&(e={largestShiftTarget:s(a.node),largestShiftTime:i.startTime,largestShiftValue:i.value,largestShiftSource:a,largestShiftEntry:i,loadState:o(i.startTime)})}}return Object.assign(t,{attribution:e})}(n);t(e)}),n)},F=function(t,n){k((function(n){var e=function(t){var n={timeToFirstByte:0,firstByteToFCP:t.value,loadState:o(v())};if(t.entries.length){var e,i=t.entries[t.entries.length-1],c=0;if(t.navigationId&&t.navigationId!==r)c=(e=h(t.navigationId))?e.startTime:0;else if(e=a()){var s=e.responseStart,u=e.activationStart||0;c=Math.max(0,s-u)}e&&(n={timeToFirstByte:c,firstByteToFCP:t.value-c,loadState:o(t.entries[0].startTime),navigationEntry:e,fcpEntry:i})}return Object.assign(t,{attribution:n})}(n);t(e)}),n)},P=0,B=1/0,O=0,N=r,R=!1,j=function(t){t.forEach((function(t){t.interactionId&&(R&&t.navigationId&&t.navigationId!==N&&(N=t.navigationId,P=0,B=1/0,O=0),B=Math.min(B,t.interactionId),O=Math.max(O,t.interactionId),P=O?(O-B)/7+1:0)}))},q=function(){return n?P:performance.interactionCount||0},H=function(t){"interactionCount"in performance||n||(n=T("event",j,{type:"event",buffered:!0,durationThreshold:0,includeSoftNavigationObservations:R=t||!1}))},W=[],z=new Map,U=0,V=function(){U=q(),W.length=0,z.clear()},_=function(){var t=Math.min(W.length-1,Math.floor((q()-U)/50));return W[t]},G=[],J=function(t){if(G.forEach((function(n){return n(t)})),t.interactionId||"first-input"===t.entryType){var n=W[W.length-1],e=z.get(t.interactionId);if(e||W.length<10||t.duration>n.latency){if(e)t.duration>e.latency?(e.entries=[t],e.latency=t.duration):t.duration===e.latency&&t.startTime===e.entries[0].startTime&&e.entries.push(t);else{var i={id:t.interactionId,latency:t.duration,entries:[t]};z.set(i.id,i),W.push(i)}W.sort((function(t,n){return n.latency-t.latency})),W.length>10&&W.splice(10).forEach((function(t){return z.delete(t.id)}))}}},K=function(t){var n=self.requestIdleCallback||self.setTimeout,e=-1;return t=y(t),"hidden"===document.visibilityState?t():(e=n(t),I(t)),e},Q=[200,500],X=function(t,n){if("PerformanceEventTiming"in self&&"interactionId"in PerformanceEventTiming.prototype){var e=p(n=n||{}),i=!1,a=0;M((function(){var r;H(e);var o,c=m("INP"),s=function(e,r){if(V(),c=m("INP",0,e,r),o=u(t,c,Q,n.reportAllChanges),i=!1,"soft-navigation"===e){var s=h(r);a=s&&s.startTime?s.startTime:0}},d=function(t){K((function(){var e;t.forEach(J),(e=_())&&(e.latency!==c.value||n&&n.reportAllChanges)&&(c.value=e.latency,c.entries=e.entries),o()}))},v=T("event",d,{durationThreshold:null!==(r=n.durationThreshold)&&void 0!==r?r:40,opts:n});if(o=u(t,c,Q,n.reportAllChanges),v){v.observe({type:"first-input",buffered:!0,includeSoftNavigationObservations:e}),I((function(){d(v.takeRecords()),o(!0)})),g((function(){V(),s("back-forward-cache",c.navigationId),f((function(){return o()}))}));e&&T("soft-navigation",(function(e){e.forEach((function(e){var r=h(e.navigationId),f=r&&r.startTime?r.startTime:0;e.navigationId&&e.navigationId!==c.navigationId&&f>a&&(!i&&c.value>0&&o(!0),s("soft-navigation",e.navigationId),o=u(t,c,Q,n.reportAllChanges))}))}),n)}}))}},Y=[],Z=[],$=new WeakMap,tt=new Map,nt=-1,et=function(t){Y=Y.concat(t),it()},it=function(){nt<0&&(nt=K(at))},at=function(){tt.size>10&&tt.forEach((function(t,n){z.has(n)||tt.delete(n)}));var t=W.map((function(t){return $.get(t.entries[0])})),n=Z.length-50;Z=Z.filter((function(e,i){return i>=n||t.includes(e)}));for(var e=new Set,a=0;a<Z.length;a++){var r=Z[a];ut(r.startTime,r.processingEnd).forEach((function(t){e.add(t)}))}for(var o=0;o<50;o++){var c=Y[Y.length-1-o];if(!c||c.startTime<i)break;e.add(c)}Y=Array.from(e),nt=-1};G.push((function(t){t.interactionId&&t.target&&!tt.has(t.interactionId)&&tt.set(t.interactionId,t.target)}),(function(t){var n,e=t.startTime+t.duration;i=Math.max(i,t.processingEnd);for(var a=Z.length-1;a>=0;a--){var r=Z[a];if(Math.abs(e-r.renderTime)<=8){(n=r).startTime=Math.min(t.startTime,n.startTime),n.processingStart=Math.min(t.processingStart,n.processingStart),n.processingEnd=Math.max(t.processingEnd,n.processingEnd),n.entries.push(t);break}}n||(n={startTime:t.startTime,processingStart:t.processingStart,processingEnd:t.processingEnd,renderTime:e,entries:[t]},Z.push(n)),(t.interactionId||"first-input"===t.entryType)&&$.set(t,n),it()}));var rt,ot,ct,st,ut=function(t,n){for(var e,i=[],a=0;e=Y[a];a++)if(!(e.startTime+e.duration<t)){if(e.startTime>n)break;i.push(e)}return i},ft=function(t,n){e||(e=T("long-animation-frame",et)),X((function(n){var e=function(t){var n=t.entries[0],e=$.get(n),i=n.processingStart,a=e.processingEnd,r=e.entries.sort((function(t,n){return t.processingStart-n.processingStart})),c=ut(n.startTime,a),u=t.entries.find((function(t){return t.target})),f=u&&u.target||tt.get(n.interactionId),d=[n.startTime+n.duration,a].concat(c.map((function(t){return t.startTime+t.duration}))),v=Math.max.apply(Math,d),g={interactionTarget:s(f),interactionTargetElement:f,interactionType:n.name.startsWith("key")?"keyboard":"pointer",interactionTime:n.startTime,nextPaintTime:v,processedEventEntries:r,longAnimationFrameEntries:c,inputDelay:i-n.startTime,processingDuration:a-i,presentationDelay:Math.max(v-a,0),loadState:o(n.startTime)};return Object.assign(t,{attribution:g})}(n);t(e)}),n)},dt=[2500,4e3],vt=function(t,n){!function(t,n){var e=!1,i=p(n=n||{}),a=0;M((function(){var o,c=L(),s=m("LCP"),d=function(i,r){if(s=m("LCP",0,i,r),o=u(t,s,dt,n.reportAllChanges),e=!1,"soft-navigation"===i){c=L(!0);var f=h(r);a=f&&f.startTime?f.startTime:0}["keydown","click"].forEach((function(t){addEventListener(t,(function(){return K(p)}),{once:!0})}))},v=function(t){t.forEach((function(t){if(t){i&&t.navigationId&&t.navigationId!==s.navigationId&&(e||o(!0),d("soft-navigation",t.navigationId));var n=0;if(t.navigationId&&t.navigationId!==r){var a=h(t.navigationId),u=a&&a.startTime?a.startTime:0;n=Math.max(t.startTime-u,0)}else n=Math.max(t.startTime-l(),0);t.startTime<c.firstHiddenTime&&(s.value=n,s.entries=[t],s.navigationId=t.navigationId||r,o())}}))},p=function(){e||(v(y.takeRecords()),i||y.disconnect(),e=!0,o(!0))},y=T("largest-contentful-paint",v,n);y&&(o=u(t,s,dt,n.reportAllChanges),I(p),g((function(t){d("back-forward-cache",s.navigationId),f((function(){s.value=performance.now()-t.timeStamp,e=!0,o(!0)}))})),i&&T("soft-navigation",(function(t){t.forEach((function(t){var n=t.navigationId?h(t.navigationId):null;t.navigationId&&t.navigationId!==s.navigationId&&n&&(n.startTime||0)>a&&(e||o(!0),d("soft-navigation",t.navigationId))}))}),n))}))}((function(n){var e=function(t){var n={timeToFirstByte:0,resourceLoadDelay:0,resourceLoadDuration:0,elementRenderDelay:t.value};if(t.entries.length){var e,i=0,o=0,c=0;if(t.navigationId&&t.navigationId!==r?i=c=(e=h(t.navigationId))?e.startTime:0:(i=(e=a())&&e.activationStart?e.activationStart:0,o=e&&e.responseStart?e.responseStart:0),e){var u=t.entries[t.entries.length-1],f=u.url&&performance.getEntriesByType("resource").filter((function(t){return t.name===u.url}))[0],d=Math.max(0,o-i),v=Math.max(d,f?(f.requestStart||f.startTime)-i:0),g=Math.max(v-c,f?f.responseEnd-i:0,0),l=Math.max(g-c,u?u.startTime-i:0,0);n={element:s(u.element),timeToFirstByte:d,resourceLoadDelay:v-d,resourceLoadDuration:g-v,elementRenderDelay:l-g,navigationEntry:e,lcpEntry:u},u.url&&(n.url=u.url),f&&(n.lcpResourceEntry=f)}}return Object.assign(t,{attribution:n})}(n);t(e)}),n)},gt=[800,1800],lt=a(),mt=function t(n){document.prerendering?M((function(){return t(n)})):"complete"!==document.readyState?addEventListener("load",(function(){return t(n)}),!0):setTimeout(n,0)},pt=function(t,n){var e=p(n=n||{}),i=m("TTFB"),a=u(t,i,gt,n.reportAllChanges);mt((function(){if(lt){var r=lt.responseStart;i.value=Math.max(r-l(),0),i.entries=[lt],a(!0),g((function(){i=m("TTFB",0,"back-forward-cache",i.navigationId),(a=u(t,i,gt,n.reportAllChanges))(!0)}));e&&T("soft-navigation",(function(e){e.forEach((function(e){e.navigationId&&((i=m("TTFB",0,"soft-navigation",e.navigationId)).entries=[e],(a=u(t,i,gt,n.reportAllChanges))(!0))}))}),n)}}))},ht=function(t,n){pt((function(n){var e=function(t){var n={waitingDuration:0,cacheDuration:0,dnsDuration:0,connectionDuration:0,requestDuration:0};if(t.entries.length){var e=t.entries[0],i=e.activationStart||0,a=Math.max((e.workerStart||e.fetchStart)-i,0),r=Math.max(e.domainLookupStart-i||0,0),o=Math.max(e.connectStart-i||0,0),c=Math.max(e.connectEnd-i||0,0);n={waitingDuration:a,cacheDuration:r-a,dnsDuration:o-r,connectionDuration:c-o,requestDuration:t.value-c,navigationEntry:e}}return Object.assign(t,{attribution:n})}(n);t(e)}),n)},Tt={passive:!0,capture:!0},It=new Date,yt=function(t,n){rt||(rt=n,ot=t,ct=new Date,bt(removeEventListener),Et())},Et=function(){if(ot>=0&&ot<ct-It){var t={entryType:"first-input",name:rt.type,target:rt.target,cancelable:rt.cancelable,startTime:rt.timeStamp,processingStart:rt.timeStamp+ot};st.forEach((function(n){n([t])})),st=[]}},St=function(t){if(t.cancelable){var n=(t.timeStamp>1e12?new Date:performance.now())-t.timeStamp;"pointerdown"==t.type?function(t,n){var e=function(){yt(t,n),a()},i=function(){a()},a=function(){removeEventListener("pointerup",e,Tt),removeEventListener("pointercancel",i,Tt)};addEventListener("pointerup",e,Tt),addEventListener("pointercancel",i,Tt)}(n,t):yt(n,t)}},bt=function(t){["mousedown","keydown","touchstart","pointerdown"].forEach((function(n){return t(n,St,Tt)}))},Ct=[100,300],wt=function(t,n){var e=p(n=n||{});M((function(){var i,a=L(),o=m("FID"),c=function(c){c.forEach((function(c){var f,d;e?c.navigationId&&c.navigationId!==o.navigationId&&(f="soft-navigation",d=c.navigationId,"soft-navigation"===f&&(a=L(!0)),o=m("FID",0,f,d),i=u(t,o,Ct,n.reportAllChanges)):s.disconnect(),c.startTime<a.firstHiddenTime&&(o.value=c.processingStart-c.startTime,o.entries.push(c),o.navigationId=c.navigationId||r,i(!0))}))},s=T("first-input",c,n);i=u(t,o,Ct,n.reportAllChanges),s&&(I((function(){c(s.takeRecords()),e||s.disconnect()})),g((function(){var e;o=m("FID",0,"back-forward-cache",o.navigationId),i=u(t,o,Ct,n.reportAllChanges),st=[],ot=-1,rt=null,bt(addEventListener),e=c,st.push(e),Et()})))}))},Lt=function(t,n){wt((function(n){var e=function(t){var n=t.entries[0],e={eventTarget:s(n.target),eventType:n.name,eventTime:n.startTime,eventEntry:n,loadState:o(n.startTime)};return Object.assign(t,{attribution:e})}(n);t(e)}),n)};export{A as CLSThresholds,D as FCPThresholds,Ct as FIDThresholds,Q as INPThresholds,dt as LCPThresholds,gt as TTFBThresholds,x as onCLS,F as onFCP,Lt as onFID,ft as onINP,vt as onLCP,ht as onTTFB}; |
@@ -1,1 +0,1 @@ | ||
var webVitals=function(e){"use strict";var n,t,r,i,o,a=-1,c=function(e){addEventListener("pageshow",(function(n){n.persisted&&(a=n.timeStamp,e(n))}),!0)},u=function(){var e=self.performance&&performance.getEntriesByType&&performance.getEntriesByType("navigation")[0];if(e&&e.responseStart>0&&e.responseStart<performance.now())return e},s=function(){var e=u();return e&&e.activationStart||0},f=function(e,n){var t=u(),r="navigate";a>=0?r="back-forward-cache":t&&(document.prerendering||s()>0?r="prerender":document.wasDiscarded?r="restore":t.type&&(r=t.type.replace(/_/g,"-")));return{name:e,value:void 0===n?-1:n,rating:"good",delta:0,entries:[],id:"v4-".concat(Date.now(),"-").concat(Math.floor(8999999999999*Math.random())+1e12),navigationType:r}},d=function(e,n,t){try{if(PerformanceObserver.supportedEntryTypes.includes(e)){var r=new PerformanceObserver((function(e){Promise.resolve().then((function(){n(e.getEntries())}))}));return r.observe(Object.assign({type:e,buffered:!0},t||{})),r}}catch(e){}},l=function(e,n,t,r){var i,o;return function(a){n.value>=0&&(a||r)&&((o=n.value-(i||0))||void 0===i)&&(i=n.value,n.delta=o,n.rating=function(e,n){return e>n[1]?"poor":e>n[0]?"needs-improvement":"good"}(n.value,t),e(n))}},p=function(e){requestAnimationFrame((function(){return requestAnimationFrame((function(){return e()}))}))},v=function(e){document.addEventListener("visibilitychange",(function(){"hidden"===document.visibilityState&&e()}))},m=function(e){var n=!1;return function(){n||(e(),n=!0)}},h=-1,g=function(){return"hidden"!==document.visibilityState||document.prerendering?1/0:0},T=function(e){"hidden"===document.visibilityState&&h>-1&&(h="visibilitychange"===e.type?e.timeStamp:0,E())},y=function(){addEventListener("visibilitychange",T,!0),addEventListener("prerenderingchange",T,!0)},E=function(){removeEventListener("visibilitychange",T,!0),removeEventListener("prerenderingchange",T,!0)},C=function(){return h<0&&(h=g(),y(),c((function(){setTimeout((function(){h=g(),y()}),0)}))),{get firstHiddenTime(){return h}}},L=function(e){document.prerendering?addEventListener("prerenderingchange",(function(){return e()}),!0):e()},S=[1800,3e3],b=function(e,n){n=n||{},L((function(){var t,r=C(),i=f("FCP"),o=d("paint",(function(e){e.forEach((function(e){"first-contentful-paint"===e.name&&(o.disconnect(),e.startTime<r.firstHiddenTime&&(i.value=Math.max(e.startTime-s(),0),i.entries.push(e),t(!0)))}))}));o&&(t=l(e,i,S,n.reportAllChanges),c((function(r){i=f("FCP"),t=l(e,i,S,n.reportAllChanges),p((function(){i.value=performance.now()-r.timeStamp,t(!0)}))})))}))},w=[.1,.25],I=0,P=1/0,A=0,F=function(e){e.forEach((function(e){e.interactionId&&(P=Math.min(P,e.interactionId),A=Math.max(A,e.interactionId),I=A?(A-P)/7+1:0)}))},M=function(){"interactionCount"in performance||n||(n=d("event",F,{type:"event",buffered:!0,durationThreshold:0}))},k=[],D=new Map,B=0,R=function(){return(n?I:performance.interactionCount||0)-B},x=[],H=function(e){if(x.forEach((function(n){return n(e)})),e.interactionId||"first-input"===e.entryType){var n=k[k.length-1],t=D.get(e.interactionId);if(t||k.length<10||e.duration>n.latency){if(t)e.duration>t.latency?(t.entries=[e],t.latency=e.duration):e.duration===t.latency&&e.startTime===t.entries[0].startTime&&t.entries.push(e);else{var r={id:e.interactionId,latency:e.duration,entries:[e]};D.set(r.id,r),k.push(r)}k.sort((function(e,n){return n.latency-e.latency})),k.length>10&&k.splice(10).forEach((function(e){return D.delete(e.id)}))}}},N=function(e){var n=self.requestIdleCallback||self.setTimeout,t=-1;return e=m(e),"hidden"===document.visibilityState?e():(t=n(e),v(e)),t},q=[200,500],O=[2500,4e3],j={},V=[800,1800],_=function e(n){document.prerendering?L((function(){return e(n)})):"complete"!==document.readyState?addEventListener("load",(function(){return e(n)}),!0):setTimeout(n,0)},z={passive:!0,capture:!0},G=new Date,J=function(e,n){t||(t=n,r=e,i=new Date,U(removeEventListener),K())},K=function(){if(r>=0&&r<i-G){var e={entryType:"first-input",name:t.type,target:t.target,cancelable:t.cancelable,startTime:t.timeStamp,processingStart:t.timeStamp+r};o.forEach((function(n){n(e)})),o=[]}},Q=function(e){if(e.cancelable){var n=(e.timeStamp>1e12?new Date:performance.now())-e.timeStamp;"pointerdown"==e.type?function(e,n){var t=function(){J(e,n),i()},r=function(){i()},i=function(){removeEventListener("pointerup",t,z),removeEventListener("pointercancel",r,z)};addEventListener("pointerup",t,z),addEventListener("pointercancel",r,z)}(n,e):J(n,e)}},U=function(e){["mousedown","keydown","touchstart","pointerdown"].forEach((function(n){return e(n,Q,z)}))},W=[100,300];return e.CLSThresholds=w,e.FCPThresholds=S,e.FIDThresholds=W,e.INPThresholds=q,e.LCPThresholds=O,e.TTFBThresholds=V,e.onCLS=function(e,n){n=n||{},b(m((function(){var t,r=f("CLS",0),i=0,o=[],a=function(e){e.forEach((function(e){if(!e.hadRecentInput){var n=o[0],t=o[o.length-1];i&&e.startTime-t.startTime<1e3&&e.startTime-n.startTime<5e3?(i+=e.value,o.push(e)):(i=e.value,o=[e])}})),i>r.value&&(r.value=i,r.entries=o,t())},u=d("layout-shift",a);u&&(t=l(e,r,w,n.reportAllChanges),v((function(){a(u.takeRecords()),t(!0)})),c((function(){i=0,r=f("CLS",0),t=l(e,r,w,n.reportAllChanges),p((function(){return t()}))})),setTimeout(t,0))})))},e.onFCP=b,e.onFID=function(e,n){n=n||{},L((function(){var i,a=C(),u=f("FID"),s=function(e){e.startTime<a.firstHiddenTime&&(u.value=e.processingStart-e.startTime,u.entries.push(e),i(!0))},p=function(e){e.forEach(s)},h=d("first-input",p);i=l(e,u,W,n.reportAllChanges),h&&(v(m((function(){p(h.takeRecords()),h.disconnect()}))),c((function(){var a;u=f("FID"),i=l(e,u,W,n.reportAllChanges),o=[],r=-1,t=null,U(addEventListener),a=s,o.push(a),K()})))}))},e.onINP=function(e,n){"PerformanceEventTiming"in self&&"interactionId"in PerformanceEventTiming.prototype&&(n=n||{},L((function(){var t;M();var r,i=f("INP"),o=function(e){N((function(){e.forEach(H);var n,t=(n=Math.min(k.length-1,Math.floor(R()/50)),k[n]);t&&t.latency!==i.value&&(i.value=t.latency,i.entries=t.entries,r())}))},a=d("event",o,{durationThreshold:null!==(t=n.durationThreshold)&&void 0!==t?t:40});r=l(e,i,q,n.reportAllChanges),a&&(a.observe({type:"first-input",buffered:!0}),v((function(){o(a.takeRecords()),r(!0)})),c((function(){B=0,k.length=0,D.clear(),i=f("INP"),r=l(e,i,q,n.reportAllChanges)})))})))},e.onLCP=function(e,n){n=n||{},L((function(){var t,r=C(),i=f("LCP"),o=function(e){n.reportAllChanges||(e=e.slice(-1)),e.forEach((function(e){e.startTime<r.firstHiddenTime&&(i.value=Math.max(e.startTime-s(),0),i.entries=[e],t())}))},a=d("largest-contentful-paint",o);if(a){t=l(e,i,O,n.reportAllChanges);var u=m((function(){j[i.id]||(o(a.takeRecords()),a.disconnect(),j[i.id]=!0,t(!0))}));["keydown","click"].forEach((function(e){addEventListener(e,(function(){return N(u)}),!0)})),v(u),c((function(r){i=f("LCP"),t=l(e,i,O,n.reportAllChanges),p((function(){i.value=performance.now()-r.timeStamp,j[i.id]=!0,t(!0)}))}))}}))},e.onTTFB=function(e,n){n=n||{};var t=f("TTFB"),r=l(e,t,V,n.reportAllChanges);_((function(){var i=u();i&&(t.value=Math.max(i.responseStart-s(),0),t.entries=[i],r(!0),c((function(){t=f("TTFB",0),(r=l(e,t,V,n.reportAllChanges))(!0)})))}))},e}({}); | ||
var webVitals=function(n){"use strict";var t,i,e,a,o,r,c=function(n,t,i,e){var a,o;return function(r){t.value>=0&&(r||e)&&((o=t.value-(a||0))||void 0===a)&&(a=t.value,t.delta=o,t.rating=function(n,t){return n>t[1]?"poor":n>t[0]?"needs-improvement":"good"}(t.value,i),n(t))}},u=function(n){requestAnimationFrame((function(){return requestAnimationFrame((function(){return n()}))}))},s=-1,f=function(n){addEventListener("pageshow",(function(t){t.persisted&&(s=t.timeStamp,n(t))}),!0)},v=function(){var n=self.performance&&performance.getEntriesByType&&performance.getEntriesByType("navigation")[0];if(n&&n.responseStart>0&&n.responseStart<performance.now())return n},d=(null===(t=v())||void 0===t?void 0:t.navigationId)||"1",g=function(){var n=v();return n&&n.activationStart||0},l=function(n,t,i,e){var a=v(),o="navigate";i?o=i:s>=0?o="back-forward-cache":a&&(document.prerendering||g()>0?o="prerender":document.wasDiscarded?o="restore":a.type&&(o=a.type.replace(/_/g,"-")));return{name:n,value:void 0===t?-1:t,rating:"good",delta:0,entries:[],id:"v4-".concat(Date.now(),"-").concat(Math.floor(8999999999999*Math.random())+1e12),navigationType:o,navigationId:e||d}},m=function(n){return PerformanceObserver.supportedEntryTypes.includes("soft-navigation")&&n&&n.reportSoftNavs},p=function(n){if(n){var t=window.performance.getEntriesByType("soft-navigation").filter((function(t){return t.navigationId===n}));return t?t[0]:void 0}},h=function(n,t,i){var e=m(i);try{if(PerformanceObserver.supportedEntryTypes.includes(n)){var a=new PerformanceObserver((function(n){Promise.resolve().then((function(){t(n.getEntries())}))}));return a.observe(Object.assign({type:n,buffered:!0,includeSoftNavigationObservations:e},i||{})),a}}catch(n){}},I=function(n){document.addEventListener("visibilitychange",(function(){"hidden"===document.visibilityState&&n()}))},T=function(n){var t=!1;return function(){t||(n(),t=!0)}},y=-1,E=function(){return"hidden"!==document.visibilityState||document.prerendering?1/0:0},C=function(n){"hidden"===document.visibilityState&&y>-1&&(y="visibilitychange"===n.type?n.timeStamp:0,w())},b=function(){addEventListener("visibilitychange",C,!0),addEventListener("prerenderingchange",C,!0)},w=function(){removeEventListener("visibilitychange",C,!0),removeEventListener("prerenderingchange",C,!0)},S=function(){return arguments.length>0&&void 0!==arguments[0]&&arguments[0]&&(y=-1),y<0&&(y=E(),b(),f((function(){setTimeout((function(){y=E(),b()}),0)}))),{get firstHiddenTime(){return y}}},L=function(n){document.prerendering?addEventListener("prerenderingchange",(function(){return n()}),!0):n()},A=[1800,3e3],P=function(n,t){var i=m(t=t||{}),e=0;L((function(){var a,o=S(),r=l("FCP"),s=h("paint",(function(u){u.forEach((function(u){if("first-contentful-paint"===u.name){i?u.navigationId&&u.navigationId!==r.navigationId&&function(i,u){if(r=l("FCP",0,i,u),a=c(n,r,A,t.reportAllChanges),"soft-navigation"===i){o=S(!0);var s=u?p(u):null;e=s&&s.startTime||0}}("soft-navigation",u.navigationId):s.disconnect();var f=0;if(u.navigationId&&u.navigationId!==d){var v=p(u.navigationId),m=v&&v.startTime?v.startTime:0;f=Math.max(u.startTime-m,0)}else f=Math.max(u.startTime-g(),0);var h=i&&u.navigationId?p(u.navigationId):null,I=h&&h.startTime?h.startTime:0;(u.startTime<o.firstHiddenTime||i&&u.navigationId&&u.navigationId!==r.navigationId&&u.navigationId!==d&&I>e)&&(r.value=f,r.entries.push(u),r.navigationId=u.navigationId||"1",a(!0))}}))}),t);s&&(a=c(n,r,A,t.reportAllChanges),f((function(i){r=l("FCP",0,"back-forward-cache",r.navigationId),a=c(n,r,A,t.reportAllChanges),u((function(){r.value=performance.now()-i.timeStamp,a(!0)}))})))}))},F=[.1,.25],k=0,M=1/0,D=0,B=d,N=!1,O=function(n){n.forEach((function(n){n.interactionId&&(N&&n.navigationId&&n.navigationId!==B&&(B=n.navigationId,k=0,M=1/0,D=0),M=Math.min(M,n.interactionId),D=Math.max(D,n.interactionId),k=D?(D-M)/7+1:0)}))},x=function(){return i?k:performance.interactionCount||0},R=function(n){"interactionCount"in performance||i||(i=h("event",O,{type:"event",buffered:!0,durationThreshold:0,includeSoftNavigationObservations:N=n||!1}))},H=[],q=new Map,j=0,V=function(){j=x(),H.length=0,q.clear()},_=function(){var n=Math.min(H.length-1,Math.floor((x()-j)/50));return H[n]},z=[],G=function(n){if(z.forEach((function(t){return t(n)})),n.interactionId||"first-input"===n.entryType){var t=H[H.length-1],i=q.get(n.interactionId);if(i||H.length<10||n.duration>t.latency){if(i)n.duration>i.latency?(i.entries=[n],i.latency=n.duration):n.duration===i.latency&&n.startTime===i.entries[0].startTime&&i.entries.push(n);else{var e={id:n.interactionId,latency:n.duration,entries:[n]};q.set(e.id,e),H.push(e)}H.sort((function(n,t){return t.latency-n.latency})),H.length>10&&H.splice(10).forEach((function(n){return q.delete(n.id)}))}}},J=function(n){var t=self.requestIdleCallback||self.setTimeout,i=-1;return n=T(n),"hidden"===document.visibilityState?n():(i=t(n),I(n)),i},K=[200,500],Q=[2500,4e3],U=[800,1800],W=v(),X=function n(t){document.prerendering?L((function(){return n(t)})):"complete"!==document.readyState?addEventListener("load",(function(){return n(t)}),!0):setTimeout(t,0)},Y={passive:!0,capture:!0},Z=new Date,$=function(n,t){e||(e=t,a=n,o=new Date,en(removeEventListener),nn())},nn=function(){if(a>=0&&a<o-Z){var n={entryType:"first-input",name:e.type,target:e.target,cancelable:e.cancelable,startTime:e.timeStamp,processingStart:e.timeStamp+a};r.forEach((function(t){t([n])})),r=[]}},tn=function(n){if(n.cancelable){var t=(n.timeStamp>1e12?new Date:performance.now())-n.timeStamp;"pointerdown"==n.type?function(n,t){var i=function(){$(n,t),a()},e=function(){a()},a=function(){removeEventListener("pointerup",i,Y),removeEventListener("pointercancel",e,Y)};addEventListener("pointerup",i,Y),addEventListener("pointercancel",e,Y)}(t,n):$(t,n)}},en=function(n){["mousedown","keydown","touchstart","pointerdown"].forEach((function(t){return n(t,tn,Y)}))},an=[100,300];return n.CLSThresholds=F,n.FCPThresholds=A,n.FIDThresholds=an,n.INPThresholds=K,n.LCPThresholds=Q,n.TTFBThresholds=U,n.onCLS=function(n,t){var i=m(t=t||{}),e=!1,a=0;P(T((function(){var o,r=l("CLS",0),s=0,v=[],d=function(i,u){if(r=l("CLS",0,i,u),o=c(n,r,F,t.reportAllChanges),s=0,e=!1,"soft-navigation"===i){var f=p(u);a=f&&f.startTime||0}},g=function(n){n.forEach((function(n){if(i&&n.navigationId&&n.navigationId!==r.navigationId&&(s>r.value&&(r.value=s,r.entries=v),o(!0),d("soft-navigation",n.navigationId)),!n.hadRecentInput){var t=v[0],e=v[v.length-1];s&&n.startTime-e.startTime<1e3&&n.startTime-t.startTime<5e3?(s+=n.value,v.push(n)):(s=n.value,v=[n])}})),s>r.value&&(r.value=s,r.entries=v,o())},m=h("layout-shift",g,t);if(m){o=c(n,r,F,t.reportAllChanges),I((function(){g(m.takeRecords()),o(!0),e=!0})),f((function(){d("back-forward-cache",r.navigationId),u((function(){return o()}))}));i&&h("soft-navigation",(function(i){i.forEach((function(i){var u=i.navigationId,s=u?p(u):null;u&&u!==r.navigationId&&s&&(s.startTime||0)>a&&(e||o(!0),d("soft-navigation",i.navigationId),o=c(n,r,F,t.reportAllChanges))}))}),t),setTimeout(o,0)}})))},n.onFCP=P,n.onFID=function(n,t){var i=m(t=t||{});L((function(){var o,u=S(),s=l("FID"),v=function(e){e.forEach((function(e){var a,r;i?e.navigationId&&e.navigationId!==s.navigationId&&(a="soft-navigation",r=e.navigationId,"soft-navigation"===a&&(u=S(!0)),s=l("FID",0,a,r),o=c(n,s,an,t.reportAllChanges)):g.disconnect(),e.startTime<u.firstHiddenTime&&(s.value=e.processingStart-e.startTime,s.entries.push(e),s.navigationId=e.navigationId||d,o(!0))}))},g=h("first-input",v,t);o=c(n,s,an,t.reportAllChanges),g&&(I((function(){v(g.takeRecords()),i||g.disconnect()})),f((function(){var i;s=l("FID",0,"back-forward-cache",s.navigationId),o=c(n,s,an,t.reportAllChanges),r=[],a=-1,e=null,en(addEventListener),i=v,r.push(i),nn()})))}))},n.onINP=function(n,t){if("PerformanceEventTiming"in self&&"interactionId"in PerformanceEventTiming.prototype){var i=m(t=t||{}),e=!1,a=0;L((function(){var o;R(i);var r,s=l("INP"),v=function(i,o){if(V(),s=l("INP",0,i,o),r=c(n,s,K,t.reportAllChanges),e=!1,"soft-navigation"===i){var u=p(o);a=u&&u.startTime?u.startTime:0}},d=function(n){J((function(){var i;n.forEach(G),(i=_())&&(i.latency!==s.value||t&&t.reportAllChanges)&&(s.value=i.latency,s.entries=i.entries),r()}))},g=h("event",d,{durationThreshold:null!==(o=t.durationThreshold)&&void 0!==o?o:40,opts:t});if(r=c(n,s,K,t.reportAllChanges),g){g.observe({type:"first-input",buffered:!0,includeSoftNavigationObservations:i}),I((function(){d(g.takeRecords()),r(!0)})),f((function(){V(),v("back-forward-cache",s.navigationId),u((function(){return r()}))}));i&&h("soft-navigation",(function(i){i.forEach((function(i){var o=p(i.navigationId),u=o&&o.startTime?o.startTime:0;i.navigationId&&i.navigationId!==s.navigationId&&u>a&&(!e&&s.value>0&&r(!0),v("soft-navigation",i.navigationId),r=c(n,s,K,t.reportAllChanges))}))}),t)}}))}},n.onLCP=function(n,t){var i=!1,e=m(t=t||{}),a=0;L((function(){var o,r=S(),s=l("LCP"),v=function(e,u){if(s=l("LCP",0,e,u),o=c(n,s,Q,t.reportAllChanges),i=!1,"soft-navigation"===e){r=S(!0);var f=p(u);a=f&&f.startTime?f.startTime:0}["keydown","click"].forEach((function(n){addEventListener(n,(function(){return J(T)}),{once:!0})}))},m=function(n){n.forEach((function(n){if(n){e&&n.navigationId&&n.navigationId!==s.navigationId&&(i||o(!0),v("soft-navigation",n.navigationId));var t=0;if(n.navigationId&&n.navigationId!==d){var a=p(n.navigationId),c=a&&a.startTime?a.startTime:0;t=Math.max(n.startTime-c,0)}else t=Math.max(n.startTime-g(),0);n.startTime<r.firstHiddenTime&&(s.value=t,s.entries=[n],s.navigationId=n.navigationId||d,o())}}))},T=function(){i||(m(y.takeRecords()),e||y.disconnect(),i=!0,o(!0))},y=h("largest-contentful-paint",m,t);if(y){o=c(n,s,Q,t.reportAllChanges),I(T),f((function(n){v("back-forward-cache",s.navigationId),u((function(){s.value=performance.now()-n.timeStamp,i=!0,o(!0)}))}));e&&h("soft-navigation",(function(n){n.forEach((function(n){var t=n.navigationId?p(n.navigationId):null;n.navigationId&&n.navigationId!==s.navigationId&&t&&(t.startTime||0)>a&&(i||o(!0),v("soft-navigation",n.navigationId))}))}),t)}}))},n.onTTFB=function(n,t){var i=m(t=t||{}),e=l("TTFB"),a=c(n,e,U,t.reportAllChanges);X((function(){if(W){var o=W.responseStart;e.value=Math.max(o-g(),0),e.entries=[W],a(!0),f((function(){e=l("TTFB",0,"back-forward-cache",e.navigationId),(a=c(n,e,U,t.reportAllChanges))(!0)}));i&&h("soft-navigation",(function(i){i.forEach((function(i){i.navigationId&&((e=l("TTFB",0,"soft-navigation",i.navigationId)).entries=[i],(a=c(n,e,U,t.reportAllChanges))(!0))}))}),t)}}))},n}({}); |
@@ -1,1 +0,1 @@ | ||
var e,n,t,r,i,o=-1,a=function(e){addEventListener("pageshow",(function(n){n.persisted&&(o=n.timeStamp,e(n))}),!0)},c=function(){var e=self.performance&&performance.getEntriesByType&&performance.getEntriesByType("navigation")[0];if(e&&e.responseStart>0&&e.responseStart<performance.now())return e},u=function(){var e=c();return e&&e.activationStart||0},f=function(e,n){var t=c(),r="navigate";o>=0?r="back-forward-cache":t&&(document.prerendering||u()>0?r="prerender":document.wasDiscarded?r="restore":t.type&&(r=t.type.replace(/_/g,"-")));return{name:e,value:void 0===n?-1:n,rating:"good",delta:0,entries:[],id:"v4-".concat(Date.now(),"-").concat(Math.floor(8999999999999*Math.random())+1e12),navigationType:r}},s=function(e,n,t){try{if(PerformanceObserver.supportedEntryTypes.includes(e)){var r=new PerformanceObserver((function(e){Promise.resolve().then((function(){n(e.getEntries())}))}));return r.observe(Object.assign({type:e,buffered:!0},t||{})),r}}catch(e){}},d=function(e,n,t,r){var i,o;return function(a){n.value>=0&&(a||r)&&((o=n.value-(i||0))||void 0===i)&&(i=n.value,n.delta=o,n.rating=function(e,n){return e>n[1]?"poor":e>n[0]?"needs-improvement":"good"}(n.value,t),e(n))}},l=function(e){requestAnimationFrame((function(){return requestAnimationFrame((function(){return e()}))}))},p=function(e){document.addEventListener("visibilitychange",(function(){"hidden"===document.visibilityState&&e()}))},v=function(e){var n=!1;return function(){n||(e(),n=!0)}},m=-1,h=function(){return"hidden"!==document.visibilityState||document.prerendering?1/0:0},g=function(e){"hidden"===document.visibilityState&&m>-1&&(m="visibilitychange"===e.type?e.timeStamp:0,T())},y=function(){addEventListener("visibilitychange",g,!0),addEventListener("prerenderingchange",g,!0)},T=function(){removeEventListener("visibilitychange",g,!0),removeEventListener("prerenderingchange",g,!0)},E=function(){return m<0&&(m=h(),y(),a((function(){setTimeout((function(){m=h(),y()}),0)}))),{get firstHiddenTime(){return m}}},C=function(e){document.prerendering?addEventListener("prerenderingchange",(function(){return e()}),!0):e()},b=[1800,3e3],S=function(e,n){n=n||{},C((function(){var t,r=E(),i=f("FCP"),o=s("paint",(function(e){e.forEach((function(e){"first-contentful-paint"===e.name&&(o.disconnect(),e.startTime<r.firstHiddenTime&&(i.value=Math.max(e.startTime-u(),0),i.entries.push(e),t(!0)))}))}));o&&(t=d(e,i,b,n.reportAllChanges),a((function(r){i=f("FCP"),t=d(e,i,b,n.reportAllChanges),l((function(){i.value=performance.now()-r.timeStamp,t(!0)}))})))}))},L=[.1,.25],w=function(e,n){n=n||{},S(v((function(){var t,r=f("CLS",0),i=0,o=[],c=function(e){e.forEach((function(e){if(!e.hadRecentInput){var n=o[0],t=o[o.length-1];i&&e.startTime-t.startTime<1e3&&e.startTime-n.startTime<5e3?(i+=e.value,o.push(e)):(i=e.value,o=[e])}})),i>r.value&&(r.value=i,r.entries=o,t())},u=s("layout-shift",c);u&&(t=d(e,r,L,n.reportAllChanges),p((function(){c(u.takeRecords()),t(!0)})),a((function(){i=0,r=f("CLS",0),t=d(e,r,L,n.reportAllChanges),l((function(){return t()}))})),setTimeout(t,0))})))},A=0,I=1/0,P=0,M=function(e){e.forEach((function(e){e.interactionId&&(I=Math.min(I,e.interactionId),P=Math.max(P,e.interactionId),A=P?(P-I)/7+1:0)}))},k=function(){"interactionCount"in performance||e||(e=s("event",M,{type:"event",buffered:!0,durationThreshold:0}))},F=[],D=new Map,x=0,R=function(){return(e?A:performance.interactionCount||0)-x},B=[],H=function(e){if(B.forEach((function(n){return n(e)})),e.interactionId||"first-input"===e.entryType){var n=F[F.length-1],t=D.get(e.interactionId);if(t||F.length<10||e.duration>n.latency){if(t)e.duration>t.latency?(t.entries=[e],t.latency=e.duration):e.duration===t.latency&&e.startTime===t.entries[0].startTime&&t.entries.push(e);else{var r={id:e.interactionId,latency:e.duration,entries:[e]};D.set(r.id,r),F.push(r)}F.sort((function(e,n){return n.latency-e.latency})),F.length>10&&F.splice(10).forEach((function(e){return D.delete(e.id)}))}}},q=function(e){var n=self.requestIdleCallback||self.setTimeout,t=-1;return e=v(e),"hidden"===document.visibilityState?e():(t=n(e),p(e)),t},O=[200,500],N=function(e,n){"PerformanceEventTiming"in self&&"interactionId"in PerformanceEventTiming.prototype&&(n=n||{},C((function(){var t;k();var r,i=f("INP"),o=function(e){q((function(){e.forEach(H);var n,t=(n=Math.min(F.length-1,Math.floor(R()/50)),F[n]);t&&t.latency!==i.value&&(i.value=t.latency,i.entries=t.entries,r())}))},c=s("event",o,{durationThreshold:null!==(t=n.durationThreshold)&&void 0!==t?t:40});r=d(e,i,O,n.reportAllChanges),c&&(c.observe({type:"first-input",buffered:!0}),p((function(){o(c.takeRecords()),r(!0)})),a((function(){x=0,F.length=0,D.clear(),i=f("INP"),r=d(e,i,O,n.reportAllChanges)})))})))},j=[2500,4e3],_={},z=function(e,n){n=n||{},C((function(){var t,r=E(),i=f("LCP"),o=function(e){n.reportAllChanges||(e=e.slice(-1)),e.forEach((function(e){e.startTime<r.firstHiddenTime&&(i.value=Math.max(e.startTime-u(),0),i.entries=[e],t())}))},c=s("largest-contentful-paint",o);if(c){t=d(e,i,j,n.reportAllChanges);var m=v((function(){_[i.id]||(o(c.takeRecords()),c.disconnect(),_[i.id]=!0,t(!0))}));["keydown","click"].forEach((function(e){addEventListener(e,(function(){return q(m)}),!0)})),p(m),a((function(r){i=f("LCP"),t=d(e,i,j,n.reportAllChanges),l((function(){i.value=performance.now()-r.timeStamp,_[i.id]=!0,t(!0)}))}))}}))},G=[800,1800],J=function e(n){document.prerendering?C((function(){return e(n)})):"complete"!==document.readyState?addEventListener("load",(function(){return e(n)}),!0):setTimeout(n,0)},K=function(e,n){n=n||{};var t=f("TTFB"),r=d(e,t,G,n.reportAllChanges);J((function(){var i=c();i&&(t.value=Math.max(i.responseStart-u(),0),t.entries=[i],r(!0),a((function(){t=f("TTFB",0),(r=d(e,t,G,n.reportAllChanges))(!0)})))}))},Q={passive:!0,capture:!0},U=new Date,V=function(e,i){n||(n=i,t=e,r=new Date,Y(removeEventListener),W())},W=function(){if(t>=0&&t<r-U){var e={entryType:"first-input",name:n.type,target:n.target,cancelable:n.cancelable,startTime:n.timeStamp,processingStart:n.timeStamp+t};i.forEach((function(n){n(e)})),i=[]}},X=function(e){if(e.cancelable){var n=(e.timeStamp>1e12?new Date:performance.now())-e.timeStamp;"pointerdown"==e.type?function(e,n){var t=function(){V(e,n),i()},r=function(){i()},i=function(){removeEventListener("pointerup",t,Q),removeEventListener("pointercancel",r,Q)};addEventListener("pointerup",t,Q),addEventListener("pointercancel",r,Q)}(n,e):V(n,e)}},Y=function(e){["mousedown","keydown","touchstart","pointerdown"].forEach((function(n){return e(n,X,Q)}))},Z=[100,300],$=function(e,r){r=r||{},C((function(){var o,c=E(),u=f("FID"),l=function(e){e.startTime<c.firstHiddenTime&&(u.value=e.processingStart-e.startTime,u.entries.push(e),o(!0))},m=function(e){e.forEach(l)},h=s("first-input",m);o=d(e,u,Z,r.reportAllChanges),h&&(p(v((function(){m(h.takeRecords()),h.disconnect()}))),a((function(){var a;u=f("FID"),o=d(e,u,Z,r.reportAllChanges),i=[],t=-1,n=null,Y(addEventListener),a=l,i.push(a),W()})))}))};export{L as CLSThresholds,b as FCPThresholds,Z as FIDThresholds,O as INPThresholds,j as LCPThresholds,G as TTFBThresholds,w as onCLS,S as onFCP,$ as onFID,N as onINP,z as onLCP,K as onTTFB}; | ||
var n,t,i,e,a,r,o=function(n,t,i,e){var a,r;return function(o){t.value>=0&&(o||e)&&((r=t.value-(a||0))||void 0===a)&&(a=t.value,t.delta=r,t.rating=function(n,t){return n>t[1]?"poor":n>t[0]?"needs-improvement":"good"}(t.value,i),n(t))}},c=function(n){requestAnimationFrame((function(){return requestAnimationFrame((function(){return n()}))}))},u=-1,f=function(n){addEventListener("pageshow",(function(t){t.persisted&&(u=t.timeStamp,n(t))}),!0)},v=function(){var n=self.performance&&performance.getEntriesByType&&performance.getEntriesByType("navigation")[0];if(n&&n.responseStart>0&&n.responseStart<performance.now())return n},s=(null===(n=v())||void 0===n?void 0:n.navigationId)||"1",d=function(){var n=v();return n&&n.activationStart||0},g=function(n,t,i,e){var a=v(),r="navigate";i?r=i:u>=0?r="back-forward-cache":a&&(document.prerendering||d()>0?r="prerender":document.wasDiscarded?r="restore":a.type&&(r=a.type.replace(/_/g,"-")));return{name:n,value:void 0===t?-1:t,rating:"good",delta:0,entries:[],id:"v4-".concat(Date.now(),"-").concat(Math.floor(8999999999999*Math.random())+1e12),navigationType:r,navigationId:e||s}},l=function(n){return PerformanceObserver.supportedEntryTypes.includes("soft-navigation")&&n&&n.reportSoftNavs},m=function(n){if(n){var t=window.performance.getEntriesByType("soft-navigation").filter((function(t){return t.navigationId===n}));return t?t[0]:void 0}},p=function(n,t,i){var e=l(i);try{if(PerformanceObserver.supportedEntryTypes.includes(n)){var a=new PerformanceObserver((function(n){Promise.resolve().then((function(){t(n.getEntries())}))}));return a.observe(Object.assign({type:n,buffered:!0,includeSoftNavigationObservations:e},i||{})),a}}catch(n){}},h=function(n){document.addEventListener("visibilitychange",(function(){"hidden"===document.visibilityState&&n()}))},I=function(n){var t=!1;return function(){t||(n(),t=!0)}},T=-1,y=function(){return"hidden"!==document.visibilityState||document.prerendering?1/0:0},E=function(n){"hidden"===document.visibilityState&&T>-1&&(T="visibilitychange"===n.type?n.timeStamp:0,C())},b=function(){addEventListener("visibilitychange",E,!0),addEventListener("prerenderingchange",E,!0)},C=function(){removeEventListener("visibilitychange",E,!0),removeEventListener("prerenderingchange",E,!0)},w=function(){return arguments.length>0&&void 0!==arguments[0]&&arguments[0]&&(T=-1),T<0&&(T=y(),b(),f((function(){setTimeout((function(){T=y(),b()}),0)}))),{get firstHiddenTime(){return T}}},S=function(n){document.prerendering?addEventListener("prerenderingchange",(function(){return n()}),!0):n()},A=[1800,3e3],L=function(n,t){var i=l(t=t||{}),e=0;S((function(){var a,r=w(),u=g("FCP"),v=p("paint",(function(c){c.forEach((function(c){if("first-contentful-paint"===c.name){i?c.navigationId&&c.navigationId!==u.navigationId&&function(i,c){if(u=g("FCP",0,i,c),a=o(n,u,A,t.reportAllChanges),"soft-navigation"===i){r=w(!0);var f=c?m(c):null;e=f&&f.startTime||0}}("soft-navigation",c.navigationId):v.disconnect();var f=0;if(c.navigationId&&c.navigationId!==s){var l=m(c.navigationId),p=l&&l.startTime?l.startTime:0;f=Math.max(c.startTime-p,0)}else f=Math.max(c.startTime-d(),0);var h=i&&c.navigationId?m(c.navigationId):null,I=h&&h.startTime?h.startTime:0;(c.startTime<r.firstHiddenTime||i&&c.navigationId&&c.navigationId!==u.navigationId&&c.navigationId!==s&&I>e)&&(u.value=f,u.entries.push(c),u.navigationId=c.navigationId||"1",a(!0))}}))}),t);v&&(a=o(n,u,A,t.reportAllChanges),f((function(i){u=g("FCP",0,"back-forward-cache",u.navigationId),a=o(n,u,A,t.reportAllChanges),c((function(){u.value=performance.now()-i.timeStamp,a(!0)}))})))}))},k=[.1,.25],P=function(n,t){var i=l(t=t||{}),e=!1,a=0;L(I((function(){var r,u=g("CLS",0),v=0,s=[],d=function(i,c){if(u=g("CLS",0,i,c),r=o(n,u,k,t.reportAllChanges),v=0,e=!1,"soft-navigation"===i){var f=m(c);a=f&&f.startTime||0}},l=function(n){n.forEach((function(n){if(i&&n.navigationId&&n.navigationId!==u.navigationId&&(v>u.value&&(u.value=v,u.entries=s),r(!0),d("soft-navigation",n.navigationId)),!n.hadRecentInput){var t=s[0],e=s[s.length-1];v&&n.startTime-e.startTime<1e3&&n.startTime-t.startTime<5e3?(v+=n.value,s.push(n)):(v=n.value,s=[n])}})),v>u.value&&(u.value=v,u.entries=s,r())},I=p("layout-shift",l,t);if(I){r=o(n,u,k,t.reportAllChanges),h((function(){l(I.takeRecords()),r(!0),e=!0})),f((function(){d("back-forward-cache",u.navigationId),c((function(){return r()}))}));i&&p("soft-navigation",(function(i){i.forEach((function(i){var c=i.navigationId,f=c?m(c):null;c&&c!==u.navigationId&&f&&(f.startTime||0)>a&&(e||r(!0),d("soft-navigation",i.navigationId),r=o(n,u,k,t.reportAllChanges))}))}),t),setTimeout(r,0)}})))},M=0,F=1/0,D=0,x=s,O=!1,B=function(n){n.forEach((function(n){n.interactionId&&(O&&n.navigationId&&n.navigationId!==x&&(x=n.navigationId,M=0,F=1/0,D=0),F=Math.min(F,n.interactionId),D=Math.max(D,n.interactionId),M=D?(D-F)/7+1:0)}))},N=function(){return t?M:performance.interactionCount||0},R=function(n){"interactionCount"in performance||t||(t=p("event",B,{type:"event",buffered:!0,durationThreshold:0,includeSoftNavigationObservations:O=n||!1}))},H=[],q=new Map,j=0,_=function(){j=N(),H.length=0,q.clear()},z=function(){var n=Math.min(H.length-1,Math.floor((N()-j)/50));return H[n]},G=[],J=function(n){if(G.forEach((function(t){return t(n)})),n.interactionId||"first-input"===n.entryType){var t=H[H.length-1],i=q.get(n.interactionId);if(i||H.length<10||n.duration>t.latency){if(i)n.duration>i.latency?(i.entries=[n],i.latency=n.duration):n.duration===i.latency&&n.startTime===i.entries[0].startTime&&i.entries.push(n);else{var e={id:n.interactionId,latency:n.duration,entries:[n]};q.set(e.id,e),H.push(e)}H.sort((function(n,t){return t.latency-n.latency})),H.length>10&&H.splice(10).forEach((function(n){return q.delete(n.id)}))}}},K=function(n){var t=self.requestIdleCallback||self.setTimeout,i=-1;return n=I(n),"hidden"===document.visibilityState?n():(i=t(n),h(n)),i},Q=[200,500],U=function(n,t){if("PerformanceEventTiming"in self&&"interactionId"in PerformanceEventTiming.prototype){var i=l(t=t||{}),e=!1,a=0;S((function(){var r;R(i);var u,v=g("INP"),s=function(i,r){if(_(),v=g("INP",0,i,r),u=o(n,v,Q,t.reportAllChanges),e=!1,"soft-navigation"===i){var c=m(r);a=c&&c.startTime?c.startTime:0}},d=function(n){K((function(){var i;n.forEach(J),(i=z())&&(i.latency!==v.value||t&&t.reportAllChanges)&&(v.value=i.latency,v.entries=i.entries),u()}))},l=p("event",d,{durationThreshold:null!==(r=t.durationThreshold)&&void 0!==r?r:40,opts:t});if(u=o(n,v,Q,t.reportAllChanges),l){l.observe({type:"first-input",buffered:!0,includeSoftNavigationObservations:i}),h((function(){d(l.takeRecords()),u(!0)})),f((function(){_(),s("back-forward-cache",v.navigationId),c((function(){return u()}))}));i&&p("soft-navigation",(function(i){i.forEach((function(i){var r=m(i.navigationId),c=r&&r.startTime?r.startTime:0;i.navigationId&&i.navigationId!==v.navigationId&&c>a&&(!e&&v.value>0&&u(!0),s("soft-navigation",i.navigationId),u=o(n,v,Q,t.reportAllChanges))}))}),t)}}))}},V=[2500,4e3],W=function(n,t){var i=!1,e=l(t=t||{}),a=0;S((function(){var r,u=w(),v=g("LCP"),l=function(e,c){if(v=g("LCP",0,e,c),r=o(n,v,V,t.reportAllChanges),i=!1,"soft-navigation"===e){u=w(!0);var f=m(c);a=f&&f.startTime?f.startTime:0}["keydown","click"].forEach((function(n){addEventListener(n,(function(){return K(T)}),{once:!0})}))},I=function(n){n.forEach((function(n){if(n){e&&n.navigationId&&n.navigationId!==v.navigationId&&(i||r(!0),l("soft-navigation",n.navigationId));var t=0;if(n.navigationId&&n.navigationId!==s){var a=m(n.navigationId),o=a&&a.startTime?a.startTime:0;t=Math.max(n.startTime-o,0)}else t=Math.max(n.startTime-d(),0);n.startTime<u.firstHiddenTime&&(v.value=t,v.entries=[n],v.navigationId=n.navigationId||s,r())}}))},T=function(){i||(I(y.takeRecords()),e||y.disconnect(),i=!0,r(!0))},y=p("largest-contentful-paint",I,t);if(y){r=o(n,v,V,t.reportAllChanges),h(T),f((function(n){l("back-forward-cache",v.navigationId),c((function(){v.value=performance.now()-n.timeStamp,i=!0,r(!0)}))}));e&&p("soft-navigation",(function(n){n.forEach((function(n){var t=n.navigationId?m(n.navigationId):null;n.navigationId&&n.navigationId!==v.navigationId&&t&&(t.startTime||0)>a&&(i||r(!0),l("soft-navigation",n.navigationId))}))}),t)}}))},X=[800,1800],Y=v(),Z=function n(t){document.prerendering?S((function(){return n(t)})):"complete"!==document.readyState?addEventListener("load",(function(){return n(t)}),!0):setTimeout(t,0)},$=function(n,t){var i=l(t=t||{}),e=g("TTFB"),a=o(n,e,X,t.reportAllChanges);Z((function(){if(Y){var r=Y.responseStart;e.value=Math.max(r-d(),0),e.entries=[Y],a(!0),f((function(){e=g("TTFB",0,"back-forward-cache",e.navigationId),(a=o(n,e,X,t.reportAllChanges))(!0)}));i&&p("soft-navigation",(function(i){i.forEach((function(i){i.navigationId&&((e=g("TTFB",0,"soft-navigation",i.navigationId)).entries=[i],(a=o(n,e,X,t.reportAllChanges))(!0))}))}),t)}}))},nn={passive:!0,capture:!0},tn=new Date,en=function(n,t){i||(i=t,e=n,a=new Date,on(removeEventListener),an())},an=function(){if(e>=0&&e<a-tn){var n={entryType:"first-input",name:i.type,target:i.target,cancelable:i.cancelable,startTime:i.timeStamp,processingStart:i.timeStamp+e};r.forEach((function(t){t([n])})),r=[]}},rn=function(n){if(n.cancelable){var t=(n.timeStamp>1e12?new Date:performance.now())-n.timeStamp;"pointerdown"==n.type?function(n,t){var i=function(){en(n,t),a()},e=function(){a()},a=function(){removeEventListener("pointerup",i,nn),removeEventListener("pointercancel",e,nn)};addEventListener("pointerup",i,nn),addEventListener("pointercancel",e,nn)}(t,n):en(t,n)}},on=function(n){["mousedown","keydown","touchstart","pointerdown"].forEach((function(t){return n(t,rn,nn)}))},cn=[100,300],un=function(n,t){var a=l(t=t||{});S((function(){var c,u=w(),v=g("FID"),d=function(i){i.forEach((function(i){var e,r;a?i.navigationId&&i.navigationId!==v.navigationId&&(e="soft-navigation",r=i.navigationId,"soft-navigation"===e&&(u=w(!0)),v=g("FID",0,e,r),c=o(n,v,cn,t.reportAllChanges)):l.disconnect(),i.startTime<u.firstHiddenTime&&(v.value=i.processingStart-i.startTime,v.entries.push(i),v.navigationId=i.navigationId||s,c(!0))}))},l=p("first-input",d,t);c=o(n,v,cn,t.reportAllChanges),l&&(h((function(){d(l.takeRecords()),a||l.disconnect()})),f((function(){var a;v=g("FID",0,"back-forward-cache",v.navigationId),c=o(n,v,cn,t.reportAllChanges),r=[],e=-1,i=null,on(addEventListener),a=d,r.push(a),an()})))}))};export{k as CLSThresholds,A as FCPThresholds,cn as FIDThresholds,Q as INPThresholds,V as LCPThresholds,X as TTFBThresholds,P as onCLS,L as onFCP,un as onFID,U as onINP,W as onLCP,$ as onTTFB}; |
{ | ||
"name": "web-vitals", | ||
"version": "4.2.1", | ||
"version": "4.2.2-soft-navs", | ||
"description": "Easily measure performance metrics in JavaScript", | ||
@@ -5,0 +5,0 @@ "type": "module", |
137
README.md
@@ -45,4 +45,7 @@ # `web-vitals` | ||
- [Time to First Byte (TTFB)](https://web.dev/articles/ttfb) | ||
- [First Input Delay (FID)](https://web.dev/articles/fid) _Deprecated and will be removed in next major release_ | ||
- [First Input Delay (FID)](https://web.dev/articles/fid) | ||
> [!CAUTION] | ||
> FID is deprecated and will be removed in the next major release. | ||
<a name="installation"><a> | ||
@@ -67,3 +70,4 @@ <a name="load-the-library"><a> | ||
_**Note:** If you're not using npm, you can still load `web-vitals` via `<script>` tags from a CDN like [unpkg.com](https://unpkg.com). See the [load `web-vitals` from a CDN](#load-web-vitals-from-a-cdn) usage example below for details._ | ||
> [!NOTE] | ||
> If you're not using npm, you can still load `web-vitals` via `<script>` tags from a CDN like [unpkg.com](https://unpkg.com). See the [load `web-vitals` from a CDN](#load-web-vitals-from-a-cdn) usage example below for details. | ||
@@ -86,3 +90,4 @@ There are a few different builds of the `web-vitals` library, and how you load the library depends on which build you want to use. | ||
_**Note:** in version 2, these functions were named `getXXX()` rather than `onXXX()`. They've [been renamed](https://github.com/GoogleChrome/web-vitals/pull/222) in version 3 to reduce confusion (see [#217](https://github.com/GoogleChrome/web-vitals/pull/217) for details) and will continue to be available using the `getXXX()` until at least version 4. Users are encouraged to switch to the new names, though, for future compatibility._ | ||
> [!NOTE] | ||
> In version 2, these functions were named `getXXX()` rather than `onXXX()`. They've [been renamed](https://github.com/GoogleChrome/web-vitals/pull/222) in version 3 to reduce confusion (see [#217](https://github.com/GoogleChrome/web-vitals/pull/217) for details) and will continue to be available using the `getXXX()` until at least version 4. Users are encouraged to switch to the new names, though, for future compatibility. | ||
@@ -219,3 +224,4 @@ <a name="attribution-build"><a> | ||
_**Warning:** do not call any of the Web Vitals functions (e.g. `onCLS()`, `onINP()`, `onLCP()`) more than once per page load. Each of these functions creates a `PerformanceObserver` instance and registers event listeners for the lifetime of the page. While the overhead of calling these functions once is negligible, calling them repeatedly on the same page may eventually result in a memory leak._ | ||
> [!WARNING] | ||
> Do not call any of the Web Vitals functions (e.g. `onCLS()`, `onINP()`, `onLCP()`) more than once per page load. Each of these functions creates a `PerformanceObserver` instance and registers event listeners for the lifetime of the page. While the overhead of calling these functions once is negligible, calling them repeatedly on the same page may eventually result in a memory leak. | ||
@@ -226,3 +232,4 @@ ### Report the value on every change | ||
_**Important:** `reportAllChanges` only reports when the **metric changes**, not for each **input to the metric**. For example, a new layout shift that does not increase the CLS metric will not be reported even with `reportAllChanges` set to `true` because the CLS metric has not changed. Similarly, for INP, each interaction is not reported even with `reportAllChanges` set to `true`—just when an interaction causes an increase to INP._ | ||
> [!IMPORTANT] | ||
> `reportAllChanges` only reports when the **metric changes**, not for each **input to the metric**. For example, a new layout shift that does not increase the CLS metric will not be reported even with `reportAllChanges` set to `true` because the CLS metric has not changed. Similarly, for INP, each interaction is not reported even with `reportAllChanges` set to `true`—just when an interaction causes an increase to INP. | ||
@@ -258,6 +265,68 @@ This can be useful when debugging, but in general using `reportAllChanges` is not needed (or recommended) for measuring these metrics in production. | ||
_**Note:** the first time the `callback` function is called, its `value` and `delta` properties will be the same._ | ||
> [!NOTE] | ||
> The first time the `callback` function is called, its `value` and `delta` properties will be the same. | ||
In addition to using the `id` field to group multiple deltas for the same metric, it can also be used to differentiate different metrics reported on the same page. For example, after a back/forward cache restore, a new metric object is created with a new `id` (since back/forward cache restores are considered separate page visits). | ||
### 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. | ||
- FID is reset to measure the first interaction after the soft navigation. | ||
- 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 | ||
@@ -370,3 +439,4 @@ | ||
_**Note:** this example relies on custom [event parameters](https://support.google.com/analytics/answer/11396839) in Google Analytics 4._ | ||
> [!NOTE] | ||
> This example relies on custom [event parameters](https://support.google.com/analytics/answer/11396839) in Google Analytics 4. | ||
@@ -415,10 +485,6 @@ See [Debug performance in the field](https://web.dev/articles/debug-performance-in-the-field) for more information and examples. | ||
}); | ||
// NOTE: Safari does not reliably fire the `visibilitychange` event when the | ||
// page is being unloaded. If Safari support is needed, you should also flush | ||
// the queue in the `pagehide` event. | ||
addEventListener('pagehide', flushQueue); | ||
``` | ||
_**Note:** see [the Page Lifecycle guide](https://developers.google.com/web/updates/2018/07/page-lifecycle-api#legacy-lifecycle-apis-to-avoid) for an explanation of why `visibilitychange` and `pagehide` are recommended over events like `beforeunload` and `unload`._ | ||
> [!NOTE] | ||
> See [the Page Lifecycle guide](https://developers.google.com/web/updates/2018/07/page-lifecycle-api#legacy-lifecycle-apis-to-avoid) for an explanation of why `visibilitychange` is recommended over events like `beforeunload` and `unload`. | ||
@@ -558,2 +624,3 @@ <a name="bundle-versions"><a> | ||
* restored by the user. | ||
* - 'soft-navigation': for soft navigations. | ||
*/ | ||
@@ -566,3 +633,10 @@ navigationType: | ||
| 'prerender' | ||
| 'restore'; | ||
| 'restore' | ||
| 'soft-navigation'; | ||
/** | ||
* The navigatonId the metric happened for. This is particularly relevent for soft navigations where | ||
* the metric may be reported for a previous URL. | ||
*/ | ||
navigatonId: number; | ||
} | ||
@@ -593,3 +667,4 @@ ``` | ||
_This interface is deprecated and will be removed in next major release_ | ||
> [!CAUTION] | ||
> This interface is deprecated and will be removed in the next major release. | ||
@@ -656,2 +731,3 @@ ```ts | ||
durationThreshold?: number; | ||
reportSoftNavs?: boolean; | ||
} | ||
@@ -699,3 +775,4 @@ ``` | ||
_**Important:** CLS should be continually monitored for changes throughout the entire lifespan of a page—including if the user returns to the page after it's been hidden/backgrounded. However, since browsers often [will not fire additional callbacks once the user has backgrounded a page](https://developer.chrome.com/blog/page-lifecycle-api/#advice-hidden), `callback` is always called when the page's visibility state changes to hidden. As a result, the `callback` function might be called multiple times during the same page load (see [Reporting only the delta of changes](#report-only-the-delta-of-changes) for how to manage this)._ | ||
> [!IMPORTANT] | ||
> CLS should be continually monitored for changes throughout the entire lifespan of a page—including if the user returns to the page after it's been hidden/backgrounded. However, since browsers often [will not fire additional callbacks once the user has backgrounded a page](https://developer.chrome.com/blog/page-lifecycle-api/#advice-hidden), `callback` is always called when the page's visibility state changes to hidden. As a result, the `callback` function might be called multiple times during the same page load (see [Reporting only the delta of changes](#report-only-the-delta-of-changes) for how to manage this). | ||
@@ -712,3 +789,4 @@ #### `onFCP()` | ||
_This function is deprecated and will be removed in next major release_ | ||
> [!CAUTION] | ||
> This function is deprecated and will be removed in the next major release. | ||
@@ -721,3 +799,4 @@ ```ts | ||
_**Important:** since FID is only reported after the user interacts with the page, it's possible that it will not be reported for some page loads._ | ||
> [!IMPORTANT] | ||
> Since FID is only reported after the user interacts with the page, it's possible that it will not be reported for some page loads. | ||
@@ -736,3 +815,4 @@ #### `onINP()` | ||
_**Important:** INP should be continually monitored for changes throughout the entire lifespan of a page—including if the user returns to the page after it's been hidden/backgrounded. However, since browsers often [will not fire additional callbacks once the user has backgrounded a page](https://developer.chrome.com/blog/page-lifecycle-api/#advice-hidden), `callback` is always called when the page's visibility state changes to hidden. As a result, the `callback` function might be called multiple times during the same page load (see [Reporting only the delta of changes](#report-only-the-delta-of-changes) for how to manage this)._ | ||
> [!IMPORTANT] | ||
> INP should be continually monitored for changes throughout the entire lifespan of a page—including if the user returns to the page after it's been hidden/backgrounded. However, since browsers often [will not fire additional callbacks once the user has backgrounded a page](https://developer.chrome.com/blog/page-lifecycle-api/#advice-hidden), `callback` is always called when the page's visibility state changes to hidden. As a result, the `callback` function might be called multiple times during the same page load (see [Reporting only the delta of changes](#report-only-the-delta-of-changes) for how to manage this). | ||
@@ -775,4 +855,4 @@ #### `onLCP()` | ||
_**Note:** browsers that do not support `navigation` entries will fall back to | ||
using `performance.timing` (with the timestamps converted from epoch time to [`DOMHighResTimeStamp`](https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp)). This ensures code referencing these values (like in the example above) will work the same in all browsers._ | ||
> [!NOTE] | ||
> Browsers that do not support `navigation` entries will fall back to using `performance.timing` (with the timestamps converted from epoch time to [`DOMHighResTimeStamp`](https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp)). This ensures code referencing these values (like in the example above) will work the same in all browsers. | ||
@@ -793,3 +873,4 @@ ### Rating Thresholds: | ||
_**Note:** It's typically not necessary (or recommended) to manually calculate metric value ratings using these thresholds. Use the [`Metric['rating']`](#metric) instead._ | ||
> [!NOTE] | ||
> It's typically not necessary (or recommended) to manually calculate metric value ratings using these thresholds. Use the [`Metric['rating']`](#metric) instead. | ||
@@ -871,3 +952,3 @@ ### Attribution: | ||
* general page load issues. This can be used to access `serverTiming` for example: | ||
* navigationEntry?.serverTiming | ||
* navigationEntry.serverTiming | ||
*/ | ||
@@ -880,3 +961,4 @@ navigationEntry?: PerformanceNavigationTiming; | ||
_This interface is deprecated and will be removed in next major release_ | ||
> [!CAUTION] | ||
> This interface is deprecated and will be removed in the next major release. | ||
@@ -1042,3 +1124,3 @@ ```ts | ||
* general page load issues. This can be used to access `serverTiming` for example: | ||
* navigationEntry?.serverTiming | ||
* navigationEntry.serverTiming | ||
*/ | ||
@@ -1093,3 +1175,3 @@ navigationEntry?: PerformanceNavigationTiming; | ||
* general page load issues. This can be used to access `serverTiming` for | ||
* example: navigationEntry?.serverTiming | ||
* example: navigationEntry.serverTiming | ||
*/ | ||
@@ -1121,3 +1203,4 @@ navigationEntry?: PerformanceNavigationTiming; | ||
_**Note:** given the lack of iframe support, the `onCLS()` function technically measures [DCLS](https://github.com/wicg/layout-instability#cumulative-scores) (Document Cumulative Layout Shift) rather than CLS, if the page includes iframes)._ | ||
> [!NOTE] | ||
> Given the lack of iframe support, the `onCLS()` function technically measures [DCLS](https://github.com/wicg/layout-instability#cumulative-scores) (Document Cumulative Layout Shift) rather than CLS, if the page includes iframes). | ||
@@ -1124,0 +1207,0 @@ ## Development |
@@ -19,3 +19,4 @@ /* | ||
import {getLoadState} from '../lib/getLoadState.js'; | ||
import {getNavigationEntry} from '../lib/getNavigationEntry.js'; | ||
import {getNavigationEntry, hardNavId} from '../lib/getNavigationEntry.js'; | ||
import {getSoftNavigationEntry} from '../lib/softNavs.js'; | ||
import {onFCP as unattributedOnFCP} from '../onFCP.js'; | ||
@@ -38,9 +39,22 @@ import { | ||
if (metric.entries.length) { | ||
const navigationEntry = getNavigationEntry(); | ||
let navigationEntry; | ||
const fcpEntry = metric.entries[metric.entries.length - 1]; | ||
let ttfb = 0; | ||
let softNavStart = 0; | ||
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); | ||
} | ||
} else { | ||
navigationEntry = getSoftNavigationEntry(metric.navigationId); | ||
// Set ttfb to the SoftNav start time | ||
softNavStart = navigationEntry ? navigationEntry.startTime : 0; | ||
ttfb = softNavStart; | ||
} | ||
if (navigationEntry) { | ||
const activationStart = navigationEntry.activationStart || 0; | ||
const ttfb = Math.max(0, navigationEntry.responseStart - activationStart); | ||
attribution = { | ||
@@ -47,0 +61,0 @@ timeToFirstByte: ttfb, |
@@ -17,3 +17,4 @@ /* | ||
import {getNavigationEntry} from '../lib/getNavigationEntry.js'; | ||
import {getNavigationEntry, hardNavId} from '../lib/getNavigationEntry.js'; | ||
import {getSoftNavigationEntry} from '../lib/softNavs.js'; | ||
import {getSelector} from '../lib/getSelector.js'; | ||
@@ -38,5 +39,25 @@ import {onLCP as unattributedOnLCP} from '../onLCP.js'; | ||
if (metric.entries.length) { | ||
const navigationEntry = getNavigationEntry(); | ||
let navigationEntry; | ||
let activationStart = 0; | ||
let responseStart = 0; | ||
let softNavStart = 0; | ||
if (!metric.navigationId || metric.navigationId === hardNavId) { | ||
navigationEntry = getNavigationEntry(); | ||
activationStart = | ||
navigationEntry && navigationEntry.activationStart | ||
? navigationEntry.activationStart | ||
: 0; | ||
responseStart = | ||
navigationEntry && navigationEntry.responseStart | ||
? navigationEntry.responseStart | ||
: 0; | ||
} else { | ||
navigationEntry = getSoftNavigationEntry(metric.navigationId); | ||
// Set activationStart to the SoftNav start time | ||
softNavStart = navigationEntry ? navigationEntry.startTime : 0; | ||
activationStart = softNavStart; | ||
} | ||
if (navigationEntry) { | ||
const activationStart = navigationEntry.activationStart || 0; | ||
const lcpEntry = metric.entries[metric.entries.length - 1]; | ||
@@ -49,3 +70,3 @@ const lcpResourceEntry = | ||
const ttfb = Math.max(0, navigationEntry.responseStart - activationStart); | ||
const ttfb = Math.max(0, responseStart - activationStart); | ||
@@ -61,8 +82,10 @@ const lcpRequestStart = Math.max( | ||
const lcpResponseEnd = Math.max( | ||
lcpRequestStart, | ||
lcpRequestStart - softNavStart, | ||
lcpResourceEntry ? lcpResourceEntry.responseEnd - activationStart : 0, | ||
0, | ||
); | ||
const lcpRenderTime = Math.max( | ||
lcpResponseEnd, | ||
lcpEntry.startTime - activationStart, | ||
lcpResponseEnd - softNavStart, | ||
lcpEntry ? lcpEntry.startTime - activationStart : 0, | ||
0, | ||
); | ||
@@ -69,0 +92,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; | ||
@@ -49,11 +52,11 @@ | ||
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, | ||
@@ -60,0 +63,0 @@ ); |
@@ -20,4 +20,4 @@ /* | ||
export const getActivationStart = (): number => { | ||
const navEntry = getNavigationEntry(); | ||
return (navEntry && navEntry.activationStart) || 0; | ||
const hardNavEntry = getNavigationEntry(); | ||
return (hardNavEntry && hardNavEntry.activationStart) || 0; | ||
}; |
@@ -26,9 +26,9 @@ /* | ||
} else { | ||
const navigationEntry = getNavigationEntry(); | ||
if (navigationEntry) { | ||
if (timestamp < navigationEntry.domInteractive) { | ||
const hardNavEntry = getNavigationEntry(); | ||
if (hardNavEntry) { | ||
if (timestamp < hardNavEntry.domInteractive) { | ||
return 'loading'; | ||
} else if ( | ||
navigationEntry.domContentLoadedEventStart === 0 || | ||
timestamp < navigationEntry.domContentLoadedEventStart | ||
hardNavEntry.domContentLoadedEventStart === 0 || | ||
timestamp < hardNavEntry.domContentLoadedEventStart | ||
) { | ||
@@ -39,4 +39,4 @@ // If the `domContentLoadedEventStart` timestamp has not yet been | ||
} else if ( | ||
navigationEntry.domComplete === 0 || | ||
timestamp < navigationEntry.domComplete | ||
hardNavEntry.domComplete === 0 || | ||
timestamp < hardNavEntry.domComplete | ||
) { | ||
@@ -43,0 +43,0 @@ // If the `domComplete` timestamp has not yet been |
@@ -38,1 +38,3 @@ /* | ||
}; | ||
export const hardNavId = getNavigationEntry()?.navigationId || '1'; |
@@ -64,3 +64,6 @@ /* | ||
export const getVisibilityWatcher = () => { | ||
export const getVisibilityWatcher = (reset = false) => { | ||
if (reset) { | ||
firstHiddenTime = -1; | ||
} | ||
if (firstHiddenTime < 0) { | ||
@@ -67,0 +70,0 @@ // If the document is hidden when this code runs, assume it was hidden |
@@ -20,3 +20,3 @@ /* | ||
import {getActivationStart} from './getActivationStart.js'; | ||
import {getNavigationEntry} from './getNavigationEntry.js'; | ||
import {getNavigationEntry, hardNavId} from './getNavigationEntry.js'; | ||
import {MetricType} from '../types.js'; | ||
@@ -27,9 +27,14 @@ | ||
value?: number, | ||
navigation?: MetricType['navigationType'], | ||
navigationId?: string, | ||
) => { | ||
const navEntry = getNavigationEntry(); | ||
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) { | ||
@@ -39,4 +44,4 @@ navigationType = 'prerender'; | ||
navigationType = 'restore'; | ||
} else if (navEntry.type) { | ||
navigationType = navEntry.type.replace( | ||
} else if (hardNavEntry.type) { | ||
navigationType = hardNavEntry.type.replace( | ||
/_/g, | ||
@@ -59,3 +64,4 @@ '-', | ||
navigationType, | ||
navigationId: navigationId || hardNavId, | ||
}; | ||
}; |
@@ -54,3 +54,3 @@ /* | ||
export const resetInteractions = () => { | ||
prevInteractionCount = 0; | ||
prevInteractionCount = getInteractionCount(); | ||
longestInteractionList.length = 0; | ||
@@ -57,0 +57,0 @@ longestInteractionMap.clear(); |
@@ -17,2 +17,4 @@ /* | ||
import {softNavs} from './softNavs.js'; | ||
interface PerformanceEntryMap { | ||
@@ -27,2 +29,3 @@ 'event': PerformanceEventTiming[]; | ||
'resource': PerformanceResourceTiming[]; | ||
'soft-navigation': SoftNavigationEntry[]; | ||
} | ||
@@ -43,2 +46,3 @@ | ||
): PerformanceObserver | undefined => { | ||
const includeSoftNavigationObservations = softNavs(opts); | ||
try { | ||
@@ -59,2 +63,4 @@ if (PerformanceObserver.supportedEntryTypes.includes(type)) { | ||
buffered: true, | ||
includeSoftNavigationObservations: | ||
includeSoftNavigationObservations, | ||
}, | ||
@@ -61,0 +67,0 @@ opts || {}, |
@@ -91,3 +91,3 @@ /* | ||
callbacks.forEach(function (callback) { | ||
callback(entry); | ||
callback([entry]); | ||
}); | ||
@@ -94,0 +94,0 @@ callbacks = []; |
@@ -17,2 +17,3 @@ /* | ||
import {hardNavId} from '../getNavigationEntry.js'; | ||
import {observe} from '../observe.js'; | ||
@@ -29,2 +30,4 @@ | ||
let maxKnownInteractionId = 0; | ||
let currentNavId = hardNavId; | ||
let softNavsEnabled = false; | ||
@@ -34,2 +37,12 @@ const updateEstimate = (entries: PerformanceEventTiming[]) => { | ||
if (e.interactionId) { | ||
if ( | ||
softNavsEnabled && | ||
e.navigationId && | ||
e.navigationId !== currentNavId | ||
) { | ||
currentNavId = e.navigationId; | ||
interactionCountEstimate = 0; | ||
minKnownInteractionId = Infinity; | ||
maxKnownInteractionId = 0; | ||
} | ||
minKnownInteractionId = Math.min(minKnownInteractionId, e.interactionId); | ||
@@ -58,5 +71,7 @@ maxKnownInteractionId = Math.max(maxKnownInteractionId, e.interactionId); | ||
*/ | ||
export const initInteractionCountPolyfill = () => { | ||
export const initInteractionCountPolyfill = (softNavs?: boolean) => { | ||
if ('interactionCount' in performance || po) return; | ||
softNavsEnabled = softNavs || false; | ||
po = observe('event', updateEstimate, { | ||
@@ -66,3 +81,4 @@ type: 'event', | ||
durationThreshold: 0, | ||
includeSoftNavigationObservations: softNavsEnabled, | ||
} as PerformanceObserverInit); | ||
}; |
103
src/onCLS.ts
@@ -17,11 +17,17 @@ /* | ||
import {onBFCacheRestore} from './lib/bfcache.js'; | ||
import {bindReporter} from './lib/bindReporter.js'; | ||
import {doubleRAF} from './lib/doubleRAF.js'; | ||
import {initMetric} from './lib/initMetric.js'; | ||
import {observe} from './lib/observe.js'; | ||
import {bindReporter} from './lib/bindReporter.js'; | ||
import {doubleRAF} from './lib/doubleRAF.js'; | ||
import {onBFCacheRestore} from './lib/bfcache.js'; | ||
import {onHidden} from './lib/onHidden.js'; | ||
import {runOnce} from './lib/runOnce.js'; | ||
import {getSoftNavigationEntry, softNavs} from './lib/softNavs.js'; | ||
import {onFCP} from './onFCP.js'; | ||
import {CLSMetric, MetricRatingThresholds, ReportOpts} from './types.js'; | ||
import { | ||
CLSMetric, | ||
Metric, | ||
MetricRatingThresholds, | ||
ReportOpts, | ||
} from './types.js'; | ||
@@ -58,2 +64,5 @@ /** Thresholds for CLS. See https://web.dev/articles/cls#what_is_a_good_cls_score */ | ||
opts = opts || {}; | ||
const softNavsEnabled = softNavs(opts); | ||
let reportedMetric = false; | ||
let metricNavStartTime = 0; | ||
@@ -70,4 +79,41 @@ // Start monitoring FCP so we can only report CLS if FCP is also reported. | ||
const initNewCLSMetric = ( | ||
navigation?: Metric['navigationType'], | ||
navigationId?: string, | ||
) => { | ||
metric = initMetric('CLS', 0, navigation, navigationId); | ||
report = bindReporter( | ||
onReport, | ||
metric, | ||
CLSThresholds, | ||
opts!.reportAllChanges, | ||
); | ||
sessionValue = 0; | ||
reportedMetric = false; | ||
if (navigation === 'soft-navigation') { | ||
const softNavEntry = getSoftNavigationEntry(navigationId); | ||
metricNavStartTime = softNavEntry ? softNavEntry.startTime || 0 : 0; | ||
} | ||
}; | ||
const handleEntries = (entries: LayoutShift[]) => { | ||
entries.forEach((entry) => { | ||
// If the entry is for a new navigationId than previous, then we have | ||
// entered a new soft nav, so emit the final LCP and reinitialize the | ||
// metric. | ||
if ( | ||
softNavsEnabled && | ||
entry.navigationId && | ||
entry.navigationId !== metric.navigationId | ||
) { | ||
// If the current session value is larger than the current CLS value, | ||
// update CLS and the entries contributing to it. | ||
if (sessionValue > metric.value) { | ||
metric.value = sessionValue; | ||
metric.entries = sessionEntries; | ||
} | ||
report(true); | ||
initNewCLSMetric('soft-navigation', entry.navigationId); | ||
} | ||
// Only count layout shifts without recent user input. | ||
@@ -105,3 +151,3 @@ if (!entry.hadRecentInput) { | ||
const po = observe('layout-shift', handleEntries); | ||
const po = observe('layout-shift', handleEntries, opts); | ||
if (po) { | ||
@@ -118,2 +164,3 @@ report = bindReporter( | ||
report(true); | ||
reportedMetric = true; | ||
}); | ||
@@ -124,14 +171,42 @@ | ||
onBFCacheRestore(() => { | ||
sessionValue = 0; | ||
metric = initMetric('CLS', 0); | ||
report = bindReporter( | ||
onReport, | ||
metric, | ||
CLSThresholds, | ||
opts!.reportAllChanges, | ||
); | ||
initNewCLSMetric('back-forward-cache', metric.navigationId); | ||
doubleRAF(() => report()); | ||
}); | ||
// Soft navs may be detected by navigationId changes in metrics above | ||
// But where no metric is issued we need to also listen for soft nav | ||
// entries, then emit the final metric for the previous navigation and | ||
// reset the metric for the new navigation. | ||
// | ||
// As PO is ordered by time, these should not happen before metrics. | ||
// | ||
// We add a check on startTime as we may be processing many entries that | ||
// are already dealt with so just checking navigationId differs from | ||
// current metric's navigation id, as we did above, is not sufficient. | ||
const handleSoftNavEntries = (entries: SoftNavigationEntry[]) => { | ||
entries.forEach((entry) => { | ||
const navId = entry.navigationId; | ||
const softNavEntry = navId ? getSoftNavigationEntry(navId) : null; | ||
if ( | ||
navId && | ||
navId !== metric.navigationId && | ||
softNavEntry && | ||
(softNavEntry.startTime || 0) > metricNavStartTime | ||
) { | ||
if (!reportedMetric) report(true); | ||
initNewCLSMetric('soft-navigation', entry.navigationId); | ||
report = bindReporter( | ||
onReport, | ||
metric, | ||
CLSThresholds, | ||
opts!.reportAllChanges, | ||
); | ||
} | ||
}); | ||
}; | ||
if (softNavsEnabled) { | ||
observe('soft-navigation', handleSoftNavEntries, opts); | ||
} | ||
// Queue a task to report (if nothing else triggers a report first). | ||
@@ -138,0 +213,0 @@ // This allows CLS to be reported as soon as FCP fires when |
@@ -17,11 +17,18 @@ /* | ||
import {onBFCacheRestore} from './lib/bfcache.js'; | ||
import {bindReporter} from './lib/bindReporter.js'; | ||
import {doubleRAF} from './lib/doubleRAF.js'; | ||
import {getActivationStart} from './lib/getActivationStart.js'; | ||
import {getSoftNavigationEntry, softNavs} from './lib/softNavs.js'; | ||
import {getVisibilityWatcher} from './lib/getVisibilityWatcher.js'; | ||
import {hardNavId} from './lib/getNavigationEntry.js'; | ||
import {initMetric} from './lib/initMetric.js'; | ||
import {observe} from './lib/observe.js'; | ||
import {onBFCacheRestore} from './lib/bfcache.js'; | ||
import {whenActivated} from './lib/whenActivated.js'; | ||
import {FCPMetric, MetricRatingThresholds, ReportOpts} from './types.js'; | ||
import { | ||
FCPMetric, | ||
Metric, | ||
MetricRatingThresholds, | ||
ReportOpts, | ||
} from './types.js'; | ||
@@ -43,15 +50,50 @@ /** Thresholds for FCP. See https://web.dev/articles/fcp#what_is_a_good_fcp_score */ | ||
opts = opts || {}; | ||
const softNavsEnabled = softNavs(opts); | ||
let metricNavStartTime = 0; | ||
whenActivated(() => { | ||
const visibilityWatcher = getVisibilityWatcher(); | ||
let visibilityWatcher = getVisibilityWatcher(); | ||
let metric = initMetric('FCP'); | ||
let report: ReturnType<typeof bindReporter>; | ||
const initNewFCPMetric = ( | ||
navigation?: Metric['navigationType'], | ||
navigationId?: string, | ||
) => { | ||
metric = initMetric('FCP', 0, navigation, navigationId); | ||
report = bindReporter( | ||
onReport, | ||
metric, | ||
FCPThresholds, | ||
opts!.reportAllChanges, | ||
); | ||
if (navigation === 'soft-navigation') { | ||
visibilityWatcher = getVisibilityWatcher(true); | ||
const softNavEntry = navigationId | ||
? getSoftNavigationEntry(navigationId) | ||
: null; | ||
metricNavStartTime = softNavEntry ? softNavEntry.startTime || 0 : 0; | ||
} | ||
}; | ||
const handleEntries = (entries: FCPMetric['entries']) => { | ||
entries.forEach((entry) => { | ||
if (entry.name === 'first-contentful-paint') { | ||
po!.disconnect(); | ||
if (!softNavsEnabled) { | ||
// If we're not using soft navs monitoring, we should not see | ||
// any more FCPs so can discconnect the performance observer | ||
po!.disconnect(); | ||
} else if ( | ||
entry.navigationId && | ||
entry.navigationId !== metric.navigationId | ||
) { | ||
// If the entry is for a new navigationId than previous, then we have | ||
// entered a new soft nav, so reinitialize the metric. | ||
initNewFCPMetric('soft-navigation', entry.navigationId); | ||
} | ||
// Only report if the page wasn't hidden prior to the first paint. | ||
if (entry.startTime < visibilityWatcher.firstHiddenTime) { | ||
let value = 0; | ||
if (!entry.navigationId || entry.navigationId === hardNavId) { | ||
// Only report if the page wasn't hidden prior to the first paint. | ||
// The activationStart reference is used because FCP should be | ||
@@ -61,4 +103,34 @@ // relative to page activation rather than navigation start if the | ||
// after the FCP, this time should be clamped at 0. | ||
metric.value = Math.max(entry.startTime - getActivationStart(), 0); | ||
value = Math.max(entry.startTime - getActivationStart(), 0); | ||
} else { | ||
const softNavEntry = getSoftNavigationEntry(entry.navigationId); | ||
const softNavStartTime = | ||
softNavEntry && softNavEntry.startTime | ||
? softNavEntry.startTime | ||
: 0; | ||
// As a soft nav needs an interaction, it should never be before | ||
// getActivationStart so can just cap to 0 | ||
value = Math.max(entry.startTime - softNavStartTime, 0); | ||
} | ||
// Only report if the page wasn't hidden prior to FCP. | ||
// Or it's a soft nav FCP | ||
const softNavEntry = | ||
softNavsEnabled && entry.navigationId | ||
? getSoftNavigationEntry(entry.navigationId) | ||
: null; | ||
const softNavEntryStartTime = | ||
softNavEntry && softNavEntry.startTime ? softNavEntry.startTime : 0; | ||
if ( | ||
entry.startTime < visibilityWatcher.firstHiddenTime || | ||
(softNavsEnabled && | ||
entry.navigationId && | ||
entry.navigationId !== metric.navigationId && | ||
entry.navigationId !== hardNavId && | ||
softNavEntryStartTime > metricNavStartTime) | ||
) { | ||
metric.value = value; | ||
metric.entries.push(entry); | ||
metric.navigationId = entry.navigationId || '1'; | ||
// FCP should only be reported once so can report right | ||
report(true); | ||
@@ -70,3 +142,3 @@ } | ||
const po = observe('paint', handleEntries); | ||
const po = observe('paint', handleEntries, opts); | ||
@@ -84,3 +156,8 @@ if (po) { | ||
onBFCacheRestore((event) => { | ||
metric = initMetric('FCP'); | ||
metric = initMetric( | ||
'FCP', | ||
0, | ||
'back-forward-cache', | ||
metric.navigationId, | ||
); | ||
report = bindReporter( | ||
@@ -87,0 +164,0 @@ onReport, |
@@ -20,2 +20,3 @@ /* | ||
import {getVisibilityWatcher} from './lib/getVisibilityWatcher.js'; | ||
import {hardNavId} from './lib/getNavigationEntry.js'; | ||
import {initMetric} from './lib/initMetric.js'; | ||
@@ -28,3 +29,3 @@ import {observe} from './lib/observe.js'; | ||
} from './lib/polyfills/firstInputPolyfill.js'; | ||
import {runOnce} from './lib/runOnce.js'; | ||
import {softNavs} from './lib/softNavs.js'; | ||
import {whenActivated} from './lib/whenActivated.js'; | ||
@@ -34,2 +35,3 @@ import { | ||
FirstInputPolyfillCallback, | ||
Metric, | ||
MetricRatingThresholds, | ||
@@ -57,23 +59,48 @@ ReportOpts, | ||
opts = opts || {}; | ||
const softNavsEnabled = softNavs(opts); | ||
whenActivated(() => { | ||
const visibilityWatcher = getVisibilityWatcher(); | ||
let visibilityWatcher = getVisibilityWatcher(); | ||
let metric = initMetric('FID'); | ||
let report: ReturnType<typeof bindReporter>; | ||
const handleEntry = (entry: PerformanceEventTiming) => { | ||
// Only report if the page wasn't hidden prior to the first input. | ||
if (entry.startTime < visibilityWatcher.firstHiddenTime) { | ||
metric.value = entry.processingStart - entry.startTime; | ||
metric.entries.push(entry); | ||
report(true); | ||
const initNewFIDMetric = ( | ||
navigation?: Metric['navigationType'], | ||
navigationId?: string, | ||
) => { | ||
if (navigation === 'soft-navigation') { | ||
visibilityWatcher = getVisibilityWatcher(true); | ||
} | ||
metric = initMetric('FID', 0, navigation, navigationId); | ||
report = bindReporter( | ||
onReport, | ||
metric, | ||
FIDThresholds, | ||
opts!.reportAllChanges, | ||
); | ||
}; | ||
const handleEntries = (entries: FIDMetric['entries']) => { | ||
entries.forEach(handleEntry); | ||
entries.forEach((entry) => { | ||
if (!softNavsEnabled) { | ||
po!.disconnect(); | ||
} else if ( | ||
entry.navigationId && | ||
entry.navigationId !== metric.navigationId | ||
) { | ||
// If the entry is for a new navigationId than previous, then we have | ||
// entered a new soft nav, so reinitialize the metric. | ||
initNewFIDMetric('soft-navigation', entry.navigationId); | ||
} | ||
// Only report if the page wasn't hidden prior to the first input. | ||
if (entry.startTime < visibilityWatcher.firstHiddenTime) { | ||
metric.value = entry.processingStart - entry.startTime; | ||
metric.entries.push(entry); | ||
metric.navigationId = entry.navigationId || hardNavId; | ||
report(true); | ||
} | ||
}); | ||
}; | ||
const po = observe('first-input', handleEntries, opts); | ||
const po = observe('first-input', handleEntries); | ||
report = bindReporter( | ||
@@ -87,11 +114,14 @@ onReport, | ||
if (po) { | ||
onHidden( | ||
runOnce(() => { | ||
handleEntries(po.takeRecords() as FIDMetric['entries']); | ||
po.disconnect(); | ||
}), | ||
); | ||
onHidden(() => { | ||
handleEntries(po!.takeRecords() as FIDMetric['entries']); | ||
if (!softNavsEnabled) po.disconnect(); | ||
}); | ||
onBFCacheRestore(() => { | ||
metric = initMetric('FID'); | ||
metric = initMetric( | ||
'FID', | ||
0, | ||
'back-forward-cache', | ||
metric.navigationId, | ||
); | ||
report = bindReporter( | ||
@@ -106,3 +136,3 @@ onReport, | ||
resetFirstInputPolyfill(); | ||
firstInputPolyfill(handleEntry as FirstInputPolyfillCallback); | ||
firstInputPolyfill(handleEntries as FirstInputPolyfillCallback); | ||
}); | ||
@@ -109,0 +139,0 @@ } |
110
src/onINP.ts
@@ -19,2 +19,3 @@ /* | ||
import {bindReporter} from './lib/bindReporter.js'; | ||
import {doubleRAF} from './lib/doubleRAF.js'; | ||
import {initMetric} from './lib/initMetric.js'; | ||
@@ -30,6 +31,12 @@ import { | ||
import {initInteractionCountPolyfill} from './lib/polyfills/interactionCountPolyfill.js'; | ||
import {getSoftNavigationEntry, softNavs} from './lib/softNavs.js'; | ||
import {whenActivated} from './lib/whenActivated.js'; | ||
import {whenIdle} from './lib/whenIdle.js'; | ||
import {INPMetric, MetricRatingThresholds, ReportOpts} from './types.js'; | ||
import { | ||
INPMetric, | ||
Metric, | ||
MetricRatingThresholds, | ||
ReportOpts, | ||
} from './types.js'; | ||
@@ -82,6 +89,9 @@ /** Thresholds for INP. See https://web.dev/articles/inp#what_is_a_good_inp_score */ | ||
opts = opts || {}; | ||
const softNavsEnabled = softNavs(opts); | ||
let reportedMetric = false; | ||
let metricNavStartTime = 0; | ||
whenActivated(() => { | ||
// TODO(philipwalton): remove once the polyfill is no longer needed. | ||
initInteractionCountPolyfill(); | ||
initInteractionCountPolyfill(softNavsEnabled); | ||
@@ -91,2 +101,34 @@ let metric = initMetric('INP'); | ||
const initNewINPMetric = ( | ||
navigation?: Metric['navigationType'], | ||
navigationId?: string, | ||
) => { | ||
resetInteractions(); | ||
metric = initMetric('INP', 0, navigation, navigationId); | ||
report = bindReporter( | ||
onReport, | ||
metric, | ||
INPThresholds, | ||
opts!.reportAllChanges, | ||
); | ||
reportedMetric = false; | ||
if (navigation === 'soft-navigation') { | ||
const softNavEntry = getSoftNavigationEntry(navigationId); | ||
metricNavStartTime = | ||
softNavEntry && softNavEntry.startTime ? softNavEntry.startTime : 0; | ||
} | ||
}; | ||
const updateINPMetric = () => { | ||
const inp = estimateP98LongestInteraction(); | ||
if ( | ||
inp && | ||
(inp.latency !== metric.value || (opts && opts.reportAllChanges)) | ||
) { | ||
metric.value = inp.latency; | ||
metric.entries = inp.entries; | ||
} | ||
}; | ||
const handleEntries = (entries: INPMetric['entries']) => { | ||
@@ -102,9 +144,4 @@ // Queue the `handleEntries()` callback in the next idle task. | ||
const inp = estimateP98LongestInteraction(); | ||
if (inp && inp.latency !== metric.value) { | ||
metric.value = inp.latency; | ||
metric.entries = inp.entries; | ||
report(); | ||
} | ||
updateINPMetric(); | ||
report(); | ||
}); | ||
@@ -121,3 +158,4 @@ }; | ||
durationThreshold: opts!.durationThreshold ?? DEFAULT_DURATION_THRESHOLD, | ||
}); | ||
opts, | ||
} as PerformanceObserverInit); | ||
@@ -134,3 +172,7 @@ report = bindReporter( | ||
// where the first interaction is less than the `durationThreshold`. | ||
po.observe({type: 'first-input', buffered: true}); | ||
po.observe({ | ||
type: 'first-input', | ||
buffered: true, | ||
includeSoftNavigationObservations: softNavsEnabled, | ||
}); | ||
@@ -146,13 +188,43 @@ onHidden(() => { | ||
resetInteractions(); | ||
initNewINPMetric('back-forward-cache', metric.navigationId); | ||
doubleRAF(() => report()); | ||
}); | ||
metric = initMetric('INP'); | ||
report = bindReporter( | ||
onReport, | ||
metric, | ||
INPThresholds, | ||
opts!.reportAllChanges, | ||
); | ||
}); | ||
// Soft navs may be detected by navigationId changes in metrics above | ||
// But where no metric is issued we need to also listen for soft nav | ||
// entries, then emit the final metric for the previous navigation and | ||
// reset the metric for the new navigation. | ||
// | ||
// As PO is ordered by time, these should not happen before metrics. | ||
// | ||
// We add a check on startTime as we may be processing many entries that | ||
// are already dealt with so just checking navigationId differs from | ||
// current metric's navigation id, as we did above, is not sufficient. | ||
const handleSoftNavEntries = (entries: SoftNavigationEntry[]) => { | ||
entries.forEach((entry) => { | ||
const softNavEntry = getSoftNavigationEntry(entry.navigationId); | ||
const softNavEntryStartTime = | ||
softNavEntry && softNavEntry.startTime ? softNavEntry.startTime : 0; | ||
if ( | ||
entry.navigationId && | ||
entry.navigationId !== metric.navigationId && | ||
softNavEntryStartTime > metricNavStartTime | ||
) { | ||
if (!reportedMetric && metric.value > 0) report(true); | ||
initNewINPMetric('soft-navigation', entry.navigationId); | ||
report = bindReporter( | ||
onReport, | ||
metric, | ||
INPThresholds, | ||
opts!.reportAllChanges, | ||
); | ||
} | ||
}); | ||
}; | ||
if (softNavsEnabled) { | ||
observe('soft-navigation', handleSoftNavEntries, opts); | ||
} | ||
} | ||
}); | ||
}; |
174
src/onLCP.ts
@@ -22,9 +22,15 @@ /* | ||
import {getVisibilityWatcher} from './lib/getVisibilityWatcher.js'; | ||
import {hardNavId} from './lib/getNavigationEntry.js'; | ||
import {initMetric} from './lib/initMetric.js'; | ||
import {observe} from './lib/observe.js'; | ||
import {onHidden} from './lib/onHidden.js'; | ||
import {runOnce} from './lib/runOnce.js'; | ||
import {getSoftNavigationEntry, softNavs} from './lib/softNavs.js'; | ||
import {whenActivated} from './lib/whenActivated.js'; | ||
import {whenIdle} from './lib/whenIdle.js'; | ||
import {LCPMetric, MetricRatingThresholds, ReportOpts} from './types.js'; | ||
import { | ||
LCPMetric, | ||
Metric, | ||
MetricRatingThresholds, | ||
ReportOpts, | ||
} from './types.js'; | ||
@@ -34,4 +40,2 @@ /** Thresholds for LCP. See https://web.dev/articles/lcp#what_is_a_good_lcp_score */ | ||
const reportedMetricIDs: Record<string, boolean> = {}; | ||
/** | ||
@@ -53,28 +57,83 @@ * Calculates the [LCP](https://web.dev/articles/lcp) value for the current page and | ||
// Set defaults | ||
let reportedMetric = false; | ||
opts = opts || {}; | ||
const softNavsEnabled = softNavs(opts); | ||
let metricNavStartTime = 0; | ||
whenActivated(() => { | ||
const visibilityWatcher = getVisibilityWatcher(); | ||
let visibilityWatcher = getVisibilityWatcher(); | ||
let metric = initMetric('LCP'); | ||
let report: ReturnType<typeof bindReporter>; | ||
const handleEntries = (entries: LCPMetric['entries']) => { | ||
// If reportAllChanges is set then call this function for each entry, | ||
// otherwise only consider the last one. | ||
if (!opts!.reportAllChanges) { | ||
entries = entries.slice(-1); | ||
const initNewLCPMetric = ( | ||
navigation?: Metric['navigationType'], | ||
navigationId?: string, | ||
) => { | ||
metric = initMetric('LCP', 0, navigation, navigationId); | ||
report = bindReporter( | ||
onReport, | ||
metric, | ||
LCPThresholds, | ||
opts!.reportAllChanges, | ||
); | ||
reportedMetric = false; | ||
if (navigation === 'soft-navigation') { | ||
visibilityWatcher = getVisibilityWatcher(true); | ||
const softNavEntry = getSoftNavigationEntry(navigationId); | ||
metricNavStartTime = | ||
softNavEntry && softNavEntry.startTime ? softNavEntry.startTime : 0; | ||
} | ||
// Stop listening after input. Note: while scrolling is an input that | ||
// stops LCP observation, it's unreliable since it can be programmatically | ||
// generated. See: https://github.com/GoogleChrome/web-vitals/issues/75 | ||
['keydown', 'click'].forEach((type) => { | ||
// Wrap in a setTimeout so the callback is run in a separate task | ||
// to avoid extending the keyboard/click handler to reduce INP impact | ||
// https://github.com/GoogleChrome/web-vitals/issues/383 | ||
addEventListener(type, () => whenIdle(finalizeAllLCPs), {once: true}); | ||
}); | ||
}; | ||
const handleEntries = (entries: LCPMetric['entries']) => { | ||
entries.forEach((entry) => { | ||
// Only report if the page wasn't hidden prior to LCP. | ||
if (entry.startTime < visibilityWatcher.firstHiddenTime) { | ||
// 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. | ||
metric.value = Math.max(entry.startTime - getActivationStart(), 0); | ||
metric.entries = [entry]; | ||
report(); | ||
if (entry) { | ||
if ( | ||
softNavsEnabled && | ||
entry.navigationId && | ||
entry.navigationId !== metric.navigationId | ||
) { | ||
// If the entry is for a new navigationId than previous, then we have | ||
// entered a new soft nav, so emit the final LCP and reinitialize the | ||
// metric. | ||
if (!reportedMetric) report(true); | ||
initNewLCPMetric('soft-navigation', entry.navigationId); | ||
} | ||
let value = 0; | ||
if (!entry.navigationId || entry.navigationId === hardNavId) { | ||
// 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); | ||
} else { | ||
// As a soft nav needs an interaction, it should never be before | ||
// getActivationStart so can just cap to 0 | ||
const softNavEntry = getSoftNavigationEntry(entry.navigationId); | ||
const softNavEntryStartTime = | ||
softNavEntry && softNavEntry.startTime | ||
? softNavEntry.startTime | ||
: 0; | ||
value = Math.max(entry.startTime - softNavEntryStartTime, 0); | ||
} | ||
// Only report if the page wasn't hidden prior to LCP. | ||
// We do allow soft navs to be reported, even if hard nav was not. | ||
if (entry.startTime < visibilityWatcher.firstHiddenTime) { | ||
metric.value = value; | ||
metric.entries = [entry]; | ||
metric.navigationId = entry.navigationId || hardNavId; | ||
report(); | ||
} | ||
} | ||
@@ -84,4 +143,13 @@ }); | ||
const po = observe('largest-contentful-paint', handleEntries); | ||
const finalizeAllLCPs = () => { | ||
if (!reportedMetric) { | ||
handleEntries(po!.takeRecords() as LCPMetric['entries']); | ||
if (!softNavsEnabled) po!.disconnect(); | ||
reportedMetric = true; | ||
report(true); | ||
} | ||
}; | ||
const po = observe('largest-contentful-paint', handleEntries, opts); | ||
if (po) { | ||
@@ -95,42 +163,48 @@ report = bindReporter( | ||
const stopListening = runOnce(() => { | ||
if (!reportedMetricIDs[metric.id]) { | ||
handleEntries(po!.takeRecords() as LCPMetric['entries']); | ||
po!.disconnect(); | ||
reportedMetricIDs[metric.id] = true; | ||
report(true); | ||
} | ||
}); | ||
onHidden(finalizeAllLCPs); | ||
// Stop listening after input. Note: while scrolling is an input that | ||
// stops LCP observation, it's unreliable since it can be programmatically | ||
// generated. See: https://github.com/GoogleChrome/web-vitals/issues/75 | ||
['keydown', 'click'].forEach((type) => { | ||
// Wrap in a setTimeout so the callback is run in a separate task | ||
// to avoid extending the keyboard/click handler to reduce INP impact | ||
// https://github.com/GoogleChrome/web-vitals/issues/383 | ||
addEventListener(type, () => whenIdle(stopListening), true); | ||
}); | ||
onHidden(stopListening); | ||
// Only report after a bfcache restore if the `PerformanceObserver` | ||
// successfully registered. | ||
onBFCacheRestore((event) => { | ||
metric = initMetric('LCP'); | ||
report = bindReporter( | ||
onReport, | ||
metric, | ||
LCPThresholds, | ||
opts!.reportAllChanges, | ||
); | ||
initNewLCPMetric('back-forward-cache', metric.navigationId); | ||
doubleRAF(() => { | ||
metric.value = performance.now() - event.timeStamp; | ||
reportedMetricIDs[metric.id] = true; | ||
reportedMetric = true; | ||
report(true); | ||
}); | ||
}); | ||
// Soft navs may be detected by navigationId changes in metrics above | ||
// But where no metric is issued we need to also listen for soft nav | ||
// entries, then emit the final metric for the previous navigation and | ||
// reset the metric for the new navigation. | ||
// | ||
// As PO is ordered by time, these should not happen before metrics. | ||
// | ||
// We add a check on startTime as we may be processing many entries that | ||
// are already dealt with so just checking navigationId differs from | ||
// current metric's navigation id, as we did above, is not sufficient. | ||
const handleSoftNavEntries = (entries: SoftNavigationEntry[]) => { | ||
entries.forEach((entry) => { | ||
const softNavEntry = entry.navigationId | ||
? getSoftNavigationEntry(entry.navigationId) | ||
: null; | ||
if ( | ||
entry.navigationId && | ||
entry.navigationId !== metric.navigationId && | ||
softNavEntry && | ||
(softNavEntry.startTime || 0) > metricNavStartTime | ||
) { | ||
if (!reportedMetric) report(true); | ||
initNewLCPMetric('soft-navigation', entry.navigationId); | ||
} | ||
}); | ||
}; | ||
if (softNavsEnabled) { | ||
observe('soft-navigation', handleSoftNavEntries, opts); | ||
} | ||
} | ||
}); | ||
}; |
@@ -18,7 +18,9 @@ /* | ||
import {bindReporter} from './lib/bindReporter.js'; | ||
import {initMetric} from './lib/initMetric.js'; | ||
import {onBFCacheRestore} from './lib/bfcache.js'; | ||
import {getNavigationEntry} from './lib/getNavigationEntry.js'; | ||
import {MetricRatingThresholds, ReportOpts, TTFBMetric} from './types.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 {softNavs} from './lib/softNavs.js'; | ||
import {whenActivated} from './lib/whenActivated.js'; | ||
@@ -29,2 +31,4 @@ | ||
const hardNavEntry = getNavigationEntry(); | ||
/** | ||
@@ -66,2 +70,3 @@ * Runs in the next task after the page is done loading and/or prerendering. | ||
opts = opts || {}; | ||
const softNavsEnabled = softNavs(opts); | ||
@@ -77,5 +82,4 @@ let metric = initMetric('TTFB'); | ||
whenReady(() => { | ||
const navigationEntry = getNavigationEntry(); | ||
if (navigationEntry) { | ||
if (hardNavEntry) { | ||
const responseStart = hardNavEntry.responseStart; | ||
// The activationStart reference is used because TTFB should be | ||
@@ -85,8 +89,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); | ||
@@ -97,3 +98,8 @@ | ||
onBFCacheRestore(() => { | ||
metric = initMetric('TTFB', 0); | ||
metric = initMetric( | ||
'TTFB', | ||
0, | ||
'back-forward-cache', | ||
metric.navigationId, | ||
); | ||
report = bindReporter( | ||
@@ -108,4 +114,30 @@ onReport, | ||
}); | ||
// Listen for soft-navigation entries and emit a dummy 0 TTFB entry | ||
const reportSoftNavTTFBs = (entries: SoftNavigationEntry[]) => { | ||
entries.forEach((entry) => { | ||
if (entry.navigationId) { | ||
metric = initMetric( | ||
'TTFB', | ||
0, | ||
'soft-navigation', | ||
entry.navigationId, | ||
); | ||
metric.entries = [entry]; | ||
report = bindReporter( | ||
onReport, | ||
metric, | ||
TTFBThresholds, | ||
opts!.reportAllChanges, | ||
); | ||
report(true); | ||
} | ||
}); | ||
}; | ||
if (softNavsEnabled) { | ||
observe('soft-navigation', reportSoftNavTTFBs, opts); | ||
} | ||
} | ||
}); | ||
}; |
@@ -35,2 +35,3 @@ /* | ||
paint: PerformancePaintTiming; | ||
'soft-navigation': SoftNavigationEntry; | ||
} | ||
@@ -54,4 +55,10 @@ | ||
// https://w3c.github.io/event-timing/#sec-modifications-perf-timeline | ||
interface PerformancePaintTiming extends PerformanceEntry { | ||
navigationId?: string; | ||
} | ||
// https://w3c.github.io/event-timing/#sec-modifications-perf-timeline | ||
interface PerformanceObserverInit { | ||
durationThreshold?: number; | ||
includeSoftNavigationObservations?: boolean; | ||
} | ||
@@ -62,4 +69,10 @@ | ||
activationStart?: number; | ||
navigationId?: string; | ||
} | ||
// https://github.com/WICG/soft-navigations | ||
interface SoftNavigationEntry extends PerformanceEntry { | ||
navigationId?: string; | ||
} | ||
// https://wicg.github.io/event-timing/#sec-performance-event-timing | ||
@@ -69,2 +82,3 @@ interface PerformanceEventTiming extends PerformanceEntry { | ||
interactionId: number; | ||
navigationId?: string; | ||
} | ||
@@ -77,2 +91,3 @@ | ||
currentRect: DOMRectReadOnly; | ||
navigationId?: string; | ||
} | ||
@@ -85,2 +100,3 @@ | ||
hadRecentInput: boolean; | ||
navigationId?: string; | ||
} | ||
@@ -96,2 +112,3 @@ | ||
readonly element: Element | null; | ||
navigationId?: string; | ||
} | ||
@@ -98,0 +115,0 @@ |
@@ -76,2 +76,3 @@ /* | ||
* restored by the user. | ||
* - 'soft-navigation': for soft navigations. | ||
*/ | ||
@@ -84,3 +85,12 @@ navigationType: | ||
| 'prerender' | ||
| 'restore'; | ||
| 'restore' | ||
| 'soft-navigation'; | ||
/** | ||
* The navigationId the metric happened for. This is particularly relevent for soft navigations where | ||
* the metric may be reported for a previous URL. | ||
* | ||
* navigationIds are UUID strings. | ||
*/ | ||
navigationId: string; | ||
} | ||
@@ -133,2 +143,3 @@ | ||
durationThreshold?: number; | ||
reportSoftNavs?: boolean; | ||
} | ||
@@ -135,0 +146,0 @@ |
@@ -55,5 +55,5 @@ /* | ||
* general page load issues. This can be used to access `serverTiming` for example: | ||
* navigationEntry?.serverTiming | ||
* navigationEntry.serverTiming | ||
*/ | ||
navigationEntry?: PerformanceNavigationTiming; | ||
navigationEntry?: PerformanceNavigationTiming | SoftNavigationEntry; | ||
} | ||
@@ -60,0 +60,0 @@ |
@@ -18,2 +18,3 @@ /* | ||
import type {LoadState, Metric} from './base.js'; | ||
import {FirstInputPolyfillEntry} from './polyfills.js'; | ||
@@ -25,3 +26,4 @@ /** | ||
name: 'FID'; | ||
entries: PerformanceEventTiming[]; | ||
// Polyfill is still used for bfcache restore and soft navs. | ||
entries: (PerformanceEventTiming | FirstInputPolyfillEntry)[]; | ||
} | ||
@@ -51,4 +53,5 @@ | ||
* The `PerformanceEventTiming` entry corresponding to FID. | ||
* Polyfill is still used for bfcache restore and soft navs. | ||
*/ | ||
eventEntry: PerformanceEventTiming; | ||
eventEntry: PerformanceEventTiming | FirstInputPolyfillEntry; | ||
/** | ||
@@ -55,0 +58,0 @@ * The loading state of the document at the time when the first interaction |
@@ -69,5 +69,5 @@ /* | ||
* general page load issues. This can be used to access `serverTiming` for example: | ||
* navigationEntry?.serverTiming | ||
* navigationEntry.serverTiming | ||
*/ | ||
navigationEntry?: PerformanceNavigationTiming; | ||
navigationEntry?: PerformanceNavigationTiming | SoftNavigationEntry; | ||
/** | ||
@@ -74,0 +74,0 @@ * The `resource` entry for the LCP resource (if applicable), which is useful |
@@ -20,6 +20,8 @@ /* | ||
'processingEnd' | ||
>; | ||
> & { | ||
navigationId: PerformanceNavigationTiming['navigationId']; | ||
}; | ||
export interface FirstInputPolyfillCallback { | ||
(entry: FirstInputPolyfillEntry): void; | ||
(entries: [FirstInputPolyfillEntry]): void; | ||
} |
@@ -24,3 +24,3 @@ /* | ||
name: 'TTFB'; | ||
entries: PerformanceNavigationTiming[]; | ||
entries: PerformanceNavigationTiming[] | SoftNavigationEntry[]; | ||
} | ||
@@ -69,5 +69,5 @@ | ||
* general page load issues. This can be used to access `serverTiming` for | ||
* example: navigationEntry?.serverTiming | ||
* example: navigationEntry.serverTiming | ||
*/ | ||
navigationEntry?: PerformanceNavigationTiming; | ||
navigationEntry?: PerformanceNavigationTiming | SoftNavigationEntry; | ||
} | ||
@@ -74,0 +74,0 @@ |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
449737
146
7482
1227
2