web-vitals
Advanced tools
Comparing version 4.2.2 to 4.2.3-soft-navs
@@ -19,4 +19,6 @@ /* | ||
import { getNavigationEntry } from '../lib/getNavigationEntry.js'; | ||
import { getSoftNavigationEntry } from '../lib/softNavs.js'; | ||
import { onFCP as unattributedOnFCP } from '../onFCP.js'; | ||
const attributeFCP = (metric) => { | ||
const hardNavId = getNavigationEntry()?.navigationId || '1'; | ||
// Use a default object if no other attribution has been set. | ||
@@ -29,7 +31,21 @@ let attribution = { | ||
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 +52,0 @@ timeToFirstByte: ttfb, |
@@ -42,3 +42,3 @@ /* | ||
// The `processingEnd` time of most recently-processed event, chronologically. | ||
let latestProcessingEnd; | ||
let latestProcessingEnd = 0; | ||
// A WeakMap to look up the event-timing-entries group of a given entry. | ||
@@ -143,3 +143,3 @@ // Note that this only maps from "important" entries: either the first input or | ||
// 1) intersect with entries in the newly cleaned up `pendingEntriesGroups` | ||
// 2) occur after the most recently-processed event entry. | ||
// 2) occur after the most recently-processed event entry (for up to MAX_PREVIOUS_FRAMES) | ||
const loafsToKeep = new Set(); | ||
@@ -152,12 +152,10 @@ for (let i = 0; i < pendingEntriesGroups.length; i++) { | ||
} | ||
for (let i = 0; i < MAX_PREVIOUS_FRAMES; i++) { | ||
// Look at pending LoAF in reverse order so the most recent are first. | ||
const loaf = pendingLoAFs[pendingLoAFs.length - 1 - i]; | ||
// If we reach LoAFs that overlap with event processing, | ||
// we can assume all previous ones have already been handled. | ||
if (!loaf || loaf.startTime < latestProcessingEnd) | ||
break; | ||
loafsToKeep.add(loaf); | ||
} | ||
pendingLoAFs = Array.from(loafsToKeep); | ||
const prevFrameIndexCutoff = pendingLoAFs.length - 1 - MAX_PREVIOUS_FRAMES; | ||
// Filter `pendingLoAFs` to preserve LoAF order. | ||
pendingLoAFs = pendingLoAFs.filter((loaf, index) => { | ||
if (loaf.startTime > latestProcessingEnd && index > prevFrameIndexCutoff) { | ||
return true; | ||
} | ||
return loafsToKeep.has(loaf); | ||
}); | ||
// Reset the idle callback handle so it can be queued again. | ||
@@ -164,0 +162,0 @@ idleHandle = -1; |
@@ -17,5 +17,7 @@ /* | ||
import { getNavigationEntry } from '../lib/getNavigationEntry.js'; | ||
import { getSoftNavigationEntry } from '../lib/softNavs.js'; | ||
import { getSelector } from '../lib/getSelector.js'; | ||
import { onLCP as unattributedOnLCP } from '../onLCP.js'; | ||
const attributeLCP = (metric) => { | ||
const hardNavId = getNavigationEntry()?.navigationId || '1'; | ||
// Use a default object if no other attribution has been set. | ||
@@ -29,5 +31,24 @@ let attribution = { | ||
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 +59,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 +67,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 +71,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. |
@@ -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; | ||
}; |
@@ -20,9 +20,14 @@ /* | ||
import { getNavigationEntry } from './getNavigationEntry.js'; | ||
export const initMetric = (name, value) => { | ||
const navEntry = getNavigationEntry(); | ||
export const initMetric = (name, value, navigation, navigationId) => { | ||
const hardNavId = getNavigationEntry()?.navigationId || '1'; | ||
const hardNavEntry = getNavigationEntry(); | ||
let navigationType = 'navigate'; | ||
if (getBFCacheRestoreTime() >= 0) { | ||
if (navigation) { | ||
// If it was passed in, then use that | ||
navigationType = navigation; | ||
} | ||
else if (getBFCacheRestoreTime() >= 0) { | ||
navigationType = 'back-forward-cache'; | ||
} | ||
else if (navEntry) { | ||
else if (hardNavEntry) { | ||
if (document.prerendering || getActivationStart() > 0) { | ||
@@ -34,4 +39,4 @@ navigationType = 'prerender'; | ||
} | ||
else if (navEntry.type) { | ||
navigationType = navEntry.type.replace(/_/g, '-'); | ||
else if (hardNavEntry.type) { | ||
navigationType = hardNavEntry.type.replace(/_/g, '-'); | ||
} | ||
@@ -49,3 +54,4 @@ } | ||
navigationType, | ||
navigationId: navigationId || hardNavId, | ||
}; | ||
}; |
@@ -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 { getNavigationEntry } from '../getNavigationEntry.js'; | ||
import { observe } from '../observe.js'; | ||
@@ -21,5 +22,17 @@ let interactionCountEstimate = 0; | ||
let maxKnownInteractionId = 0; | ||
let currentNavId = ''; | ||
let softNavsEnabled = false; | ||
const updateEstimate = (entries) => { | ||
if (!currentNavId) | ||
currentNavId = getNavigationEntry()?.navigationId || '1'; | ||
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 +57,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 +66,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 { getNavigationEntry } 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,37 @@ /** Thresholds for FCP. See https://web.dev/articles/fcp#what_is_a_good_fcp_score */ | ||
opts = opts || {}; | ||
const softNavsEnabled = softNavs(opts); | ||
let metricNavStartTime = 0; | ||
const hardNavId = getNavigationEntry()?.navigationId || '1'; | ||
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 +78,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 +111,3 @@ } | ||
}; | ||
const po = observe('paint', handleEntries); | ||
const po = observe('paint', handleEntries, opts); | ||
if (po) { | ||
@@ -66,3 +118,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 +121,0 @@ doubleRAF(() => { |
@@ -19,2 +19,3 @@ /* | ||
import { getVisibilityWatcher } from './lib/getVisibilityWatcher.js'; | ||
import { getNavigationEntry } 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,49 @@ /** Thresholds for FID. See https://web.dev/articles/fid#what_is_a_good_fid_score */ | ||
opts = opts || {}; | ||
const softNavsEnabled = softNavs(opts); | ||
const hardNavId = getNavigationEntry()?.navigationId || '1'; | ||
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 +91,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 { getNavigationEntry } 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,62 +45,159 @@ * 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; | ||
const hardNavId = getNavigationEntry()?.navigationId || '1'; | ||
let finalizeNavId = ''; | ||
whenActivated(() => { | ||
const visibilityWatcher = getVisibilityWatcher(); | ||
let visibilityWatcher = getVisibilityWatcher(); | ||
let metric = initMetric('LCP'); | ||
let report; | ||
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; | ||
} | ||
addInputListeners(); | ||
}; | ||
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); | ||
} | ||
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(); | ||
} | ||
} | ||
}); | ||
}; | ||
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()); | ||
const finalizeLCPs = () => { | ||
removeInputListeners(); | ||
if (!reportedMetric) { | ||
handleEntries(po.takeRecords()); | ||
if (!softNavsEnabled) | ||
po.disconnect(); | ||
reportedMetricIDs[metric.id] = true; | ||
// As the clicks are handled when idle, check if the current metric was | ||
// for the reported NavId and only if so, then report. | ||
if (metric.navigationId === finalizeNavId) { | ||
reportedMetric = true; | ||
report(true); | ||
} | ||
} | ||
}; | ||
const addInputListeners = () => { | ||
['keydown', 'click'].forEach((type) => { | ||
// 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 | ||
addEventListener(type, () => handleInput(), true); | ||
}); | ||
// 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 | ||
}; | ||
const removeInputListeners = () => { | ||
['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); | ||
// Remove event listeners as no longer required | ||
removeEventListener(type, () => handleInput(), true); | ||
}); | ||
onHidden(stopListening); | ||
}; | ||
const handleInput = () => { | ||
// Since we only finalize whenIdle, we only want to finalize the LCPs | ||
// for the current navigationId at the time of the input and not any | ||
// others that came after, and before it was idle. So note the current | ||
// metric.navigationId. | ||
finalizeNavId = metric.navigationId; | ||
// 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 | ||
whenIdle(finalizeLCPs); | ||
}; | ||
const handleHidden = () => { | ||
// Finalise the current navigationId metric. | ||
finalizeNavId = metric.navigationId; | ||
// 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 | ||
finalizeLCPs(); | ||
}; | ||
const po = observe('largest-contentful-paint', handleEntries, opts); | ||
if (po) { | ||
report = bindReporter(onReport, metric, LCPThresholds, opts.reportAllChanges); | ||
addInputListeners(); | ||
onHidden(handleHidden); | ||
// 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,6 +17,8 @@ /* | ||
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'; | ||
@@ -59,7 +61,9 @@ /** Thresholds for TTFB. See https://web.dev/articles/ttfb#what_is_a_good_ttfb_score */ | ||
opts = opts || {}; | ||
const softNavsEnabled = softNavs(opts); | ||
let metric = initMetric('TTFB'); | ||
let report = bindReporter(onReport, metric, TTFBThresholds, opts.reportAllChanges); | ||
whenReady(() => { | ||
const navigationEntry = getNavigationEntry(); | ||
if (navigationEntry) { | ||
const hardNavEntry = getNavigationEntry(); | ||
if (hardNavEntry) { | ||
const responseStart = hardNavEntry.responseStart; | ||
// The activationStart reference is used because TTFB should be | ||
@@ -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(){return e?I:performance.interactionCount||0},A=function(){"interactionCount"in performance||e||(e=m("event",k,{type:"event",buffered:!0,durationThreshold:0}))},B=[],O=new Map,R=0,j=function(){var t=Math.min(B.length-1,Math.floor((x()-R)/50));return B[t]},q=[],N=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)}))}}},H=function(t){var e=self.requestIdleCallback||self.setTimeout,n=-1;return t=g(t),"hidden"===document.visibilityState?t():(n=e(t),h(t)),n},V=[200,500],W=function(t,e){"PerformanceEventTiming"in self&&"interactionId"in PerformanceEventTiming.prototype&&(e=e||{},L((function(){var n;A();var r,i=l("INP"),a=function(t){H((function(){t.forEach(N);var e=j();e&&e.latency!==i.value&&(i.value=e.latency,i.entries=e.entries,r())}))},o=m("event",a,{durationThreshold:null!==(n=e.durationThreshold)&&void 0!==n?n:40});r=v(t,i,V,e.reportAllChanges),o&&(o.observe({type:"first-input",buffered:!0}),h((function(){a(o.takeRecords()),r(!0)})),f((function(){R=x(),B.length=0,O.clear(),i=l("INP"),r=v(t,i,V,e.reportAllChanges)})))})))},z=[],U=[],_=new WeakMap,G=new Map,J=-1,K=function(t){z=z.concat(t),Q()},Q=function(){J<0&&(J=H(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=U.length-50;U=U.filter((function(n,r){return r>=e||t.includes(n)}));for(var n=new Set,i=0;i<U.length;i++){var a=U[i];et(a.startTime,a.processingEnd).forEach((function(t){n.add(t)}))}for(var o=0;o<50;o++){var c=z[z.length-1-o];if(!c||c.startTime<r)break;n.add(c)}z=Array.from(n),J=-1};q.push((function(t){t.interactionId&&t.target&&!G.has(t.interactionId)&&G.set(t.interactionId,t.target)}),(function(t){var e,n=t.startTime+t.duration;r=Math.max(r,t.processingEnd);for(var i=U.length-1;i>=0;i--){var a=U[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]},U.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=z[i];i++)if(!(n.startTime+n.duration<t)){if(n.startTime>e)break;r.push(n)}return r},nt=[2500,4e3],rt={},it=[800,1800],at=function t(e){document.prerendering?L((function(){return t(e)})):"complete"!==document.readyState?addEventListener("load",(function(){return t(e)}),!0):setTimeout(e,0)},ot=function(t,e){e=e||{};var n=l("TTFB"),r=v(t,n,it,e.reportAllChanges);at((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,it,e.reportAllChanges))(!0)})))}))},ct={passive:!0,capture:!0},st=new Date,ut=function(t,e){Y||(Y=e,Z=t,$=new Date,lt(removeEventListener),ft())},ft=function(){if(Z>=0&&Z<$-st){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=[]}},dt=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(){ut(t,e),i()},r=function(){i()},i=function(){removeEventListener("pointerup",n,ct),removeEventListener("pointercancel",r,ct)};addEventListener("pointerup",n,ct),addEventListener("pointercancel",r,ct)}(e,t):ut(e,t)}},lt=function(t){["mousedown","keydown","touchstart","pointerdown"].forEach((function(e){return t(e,dt,ct)}))},mt=[100,300],vt=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,mt,e.reportAllChanges),c&&(h(g((function(){o(c.takeRecords()),c.disconnect()}))),f((function(){var r;i=l("FID"),n=v(t,i,mt,e.reportAllChanges),tt=[],Z=-1,Y=null,lt(addEventListener),r=a,tt.push(r),ft()})))}))};return t.CLSThresholds=w,t.FCPThresholds=M,t.FIDThresholds=mt,t.INPThresholds=V,t.LCPThresholds=nt,t.TTFBThresholds=it,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){vt((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",K)),W((function(e){var n=function(t){var e=t.entries[0],n=_.get(e),r=e.processingStart,i=n.processingEnd,o=n.entries.sort((function(t,e){return t.processingStart-e.processingStart})),s=et(e.startTime,i),u=t.entries.find((function(t){return t.target})),f=u&&u.target||G.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,nt,e.reportAllChanges);var c=g((function(){rt[i.id]||(a(o.takeRecords()),o.disconnect(),rt[i.id]=!0,n(!0))}));["keydown","click"].forEach((function(t){addEventListener(t,(function(){return H(c)}),!0)})),h(c),f((function(r){i=l("LCP"),n=v(t,i,nt,e.reportAllChanges),p((function(){i.value=performance.now()-r.timeStamp,rt[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){ot((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(n){"use strict";var t,e,i=function(){var n=self.performance&&performance.getEntriesByType&&performance.getEntriesByType("navigation")[0];if(n&&n.responseStart>0&&n.responseStart<performance.now())return n},a=function(n){if("loading"===document.readyState)return"loading";var t=i();if(t){if(n<t.domInteractive)return"loading";if(0===t.domContentLoadedEventStart||n<t.domContentLoadedEventStart)return"dom-interactive";if(0===t.domComplete||n<t.domComplete)return"dom-content-loaded"}return"complete"},r=function(n){var t=n.nodeName;return 1===n.nodeType?t.toLowerCase():t.toUpperCase().replace(/^#/,"")},o=function(n,t){var e="";try{for(;n&&9!==n.nodeType;){var i=n,a=i.id?"#"+i.id:r(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>(t||100)-1)return e||a;if(e=e?a+">"+e:a,i.id)break;n=i.parentNode}}catch(n){}return e},s=function(n,t,e,i){var a,r;return function(o){t.value>=0&&(o||i)&&((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,e),n(t))}},c=function(n){requestAnimationFrame((function(){return requestAnimationFrame((function(){return n()}))}))},u=-1,d=function(){return u},f=function(n){addEventListener("pageshow",(function(t){t.persisted&&(u=t.timeStamp,n(t))}),!0)},v=function(){var n=i();return n&&n.activationStart||0},l=function(n,t,e,a){var r,o=(null===(r=i())||void 0===r?void 0:r.navigationId)||"1",s=i(),c="navigate";e?c=e:d()>=0?c="back-forward-cache":s&&(document.prerendering||v()>0?c="prerender":document.wasDiscarded?c="restore":s.type&&(c=s.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:c,navigationId:a||o}},g=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,e){var i=g(e);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:i},e||{})),a}}catch(n){}},h=function(n){document.addEventListener("visibilitychange",(function(){"hidden"===document.visibilityState&&n()}))},T=function(n){var t=!1;return function(){t||(n(),t=!0)}},I=-1,y=function(){return"hidden"!==document.visibilityState||document.prerendering?1/0:0},E=function(n){"hidden"===document.visibilityState&&I>-1&&(I="visibilitychange"===n.type?n.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 arguments.length>0&&void 0!==arguments[0]&&arguments[0]&&(I=-1),I<0&&(I=y(),S(),f((function(){setTimeout((function(){I=y(),S()}),0)}))),{get firstHiddenTime(){return I}}},L=function(n){document.prerendering?addEventListener("prerenderingchange",(function(){return n()}),!0):n()},w=[1800,3e3],M=function(n,t){var e,a=g(t=t||{}),r=0,o=(null===(e=i())||void 0===e?void 0:e.navigationId)||"1";L((function(){var e,i=C(),u=l("FCP"),d=p("paint",(function(c){c.forEach((function(c){if("first-contentful-paint"===c.name){a?c.navigationId&&c.navigationId!==u.navigationId&&function(a,o){if(u=l("FCP",0,a,o),e=s(n,u,w,t.reportAllChanges),"soft-navigation"===a){i=C(!0);var c=o?m(o):null;r=c&&c.startTime||0}}("soft-navigation",c.navigationId):d.disconnect();var f=0;if(c.navigationId&&c.navigationId!==o){var g=m(c.navigationId),p=g&&g.startTime?g.startTime:0;f=Math.max(c.startTime-p,0)}else f=Math.max(c.startTime-v(),0);var h=a&&c.navigationId?m(c.navigationId):null,T=h&&h.startTime?h.startTime:0;(c.startTime<i.firstHiddenTime||a&&c.navigationId&&c.navigationId!==u.navigationId&&c.navigationId!==o&&T>r)&&(u.value=f,u.entries.push(c),u.navigationId=c.navigationId||"1",e(!0))}}))}),t);d&&(e=s(n,u,w,t.reportAllChanges),f((function(i){u=l("FCP",0,"back-forward-cache",u.navigationId),e=s(n,u,w,t.reportAllChanges),c((function(){u.value=performance.now()-i.timeStamp,e(!0)}))})))}))},D=[.1,.25],k=0,F=1/0,P=0,A="",x=!1,B=function(n){var t;A||(A=(null===(t=i())||void 0===t?void 0:t.navigationId)||"1"),n.forEach((function(n){n.interactionId&&(x&&n.navigationId&&n.navigationId!==A&&(A=n.navigationId,k=0,F=1/0,P=0),F=Math.min(F,n.interactionId),P=Math.max(P,n.interactionId),k=P?(P-F)/7+1:0)}))},O=function(){return t?k:performance.interactionCount||0},N=function(n){"interactionCount"in performance||t||(t=p("event",B,{type:"event",buffered:!0,durationThreshold:0,includeSoftNavigationObservations:x=n||!1}))},R=[],j=new Map,q=0,H=function(){q=O(),R.length=0,j.clear()},V=function(){var n=Math.min(R.length-1,Math.floor((O()-q)/50));return R[n]},W=[],z=function(n){if(W.forEach((function(t){return t(n)})),n.interactionId||"first-input"===n.entryType){var t=R[R.length-1],e=j.get(n.interactionId);if(e||R.length<10||n.duration>t.latency){if(e)n.duration>e.latency?(e.entries=[n],e.latency=n.duration):n.duration===e.latency&&n.startTime===e.entries[0].startTime&&e.entries.push(n);else{var i={id:n.interactionId,latency:n.duration,entries:[n]};j.set(i.id,i),R.push(i)}R.sort((function(n,t){return t.latency-n.latency})),R.length>10&&R.splice(10).forEach((function(n){return j.delete(n.id)}))}}},U=function(n){var t=self.requestIdleCallback||self.setTimeout,e=-1;return n=T(n),"hidden"===document.visibilityState?n():(e=t(n),h(n)),e},_=[200,500],G=function(n,t){if("PerformanceEventTiming"in self&&"interactionId"in PerformanceEventTiming.prototype){var e=g(t=t||{}),i=!1,a=0;L((function(){var r;N(e);var o,u=l("INP"),d=function(e,r){if(H(),u=l("INP",0,e,r),o=s(n,u,_,t.reportAllChanges),i=!1,"soft-navigation"===e){var c=m(r);a=c&&c.startTime?c.startTime:0}},v=function(n){U((function(){var e;n.forEach(z),(e=V())&&(e.latency!==u.value||t&&t.reportAllChanges)&&(u.value=e.latency,u.entries=e.entries),o()}))},g=p("event",v,{durationThreshold:null!==(r=t.durationThreshold)&&void 0!==r?r:40,opts:t});if(o=s(n,u,_,t.reportAllChanges),g){g.observe({type:"first-input",buffered:!0,includeSoftNavigationObservations:e}),h((function(){v(g.takeRecords()),o(!0)})),f((function(){H(),d("back-forward-cache",u.navigationId),c((function(){return o()}))}));e&&p("soft-navigation",(function(e){e.forEach((function(e){var r=m(e.navigationId),c=r&&r.startTime?r.startTime:0;e.navigationId&&e.navigationId!==u.navigationId&&c>a&&(!i&&u.value>0&&o(!0),d("soft-navigation",e.navigationId),o=s(n,u,_,t.reportAllChanges))}))}),t)}}))}},J=[],K=[],Q=0,X=new WeakMap,Y=new Map,Z=-1,$=function(n){J=J.concat(n),nn()},nn=function(){Z<0&&(Z=U(tn))},tn=function(){Y.size>10&&Y.forEach((function(n,t){j.has(t)||Y.delete(t)}));var n=R.map((function(n){return X.get(n.entries[0])})),t=K.length-50;K=K.filter((function(e,i){return i>=t||n.includes(e)}));for(var e=new Set,i=0;i<K.length;i++){var a=K[i];sn(a.startTime,a.processingEnd).forEach((function(n){e.add(n)}))}var r=J.length-1-50;J=J.filter((function(n,t){return n.startTime>Q&&t>r||e.has(n)})),Z=-1};W.push((function(n){n.interactionId&&n.target&&!Y.has(n.interactionId)&&Y.set(n.interactionId,n.target)}),(function(n){var t,e=n.startTime+n.duration;Q=Math.max(Q,n.processingEnd);for(var i=K.length-1;i>=0;i--){var a=K[i];if(Math.abs(e-a.renderTime)<=8){(t=a).startTime=Math.min(n.startTime,t.startTime),t.processingStart=Math.min(n.processingStart,t.processingStart),t.processingEnd=Math.max(n.processingEnd,t.processingEnd),t.entries.push(n);break}}t||(t={startTime:n.startTime,processingStart:n.processingStart,processingEnd:n.processingEnd,renderTime:e,entries:[n]},K.push(t)),(n.interactionId||"first-input"===n.entryType)&&X.set(n,t),nn()}));var en,an,rn,on,sn=function(n,t){for(var e,i=[],a=0;e=J[a];a++)if(!(e.startTime+e.duration<n)){if(e.startTime>t)break;i.push(e)}return i},cn=[2500,4e3],un=[800,1800],dn=function n(t){document.prerendering?L((function(){return n(t)})):"complete"!==document.readyState?addEventListener("load",(function(){return n(t)}),!0):setTimeout(t,0)},fn=function(n,t){var e=g(t=t||{}),a=l("TTFB"),r=s(n,a,un,t.reportAllChanges);dn((function(){var o=i();if(o){var c=o.responseStart;a.value=Math.max(c-v(),0),a.entries=[o],r(!0),f((function(){a=l("TTFB",0,"back-forward-cache",a.navigationId),(r=s(n,a,un,t.reportAllChanges))(!0)}));e&&p("soft-navigation",(function(e){e.forEach((function(e){e.navigationId&&((a=l("TTFB",0,"soft-navigation",e.navigationId)).entries=[e],(r=s(n,a,un,t.reportAllChanges))(!0))}))}),t)}}))},vn={passive:!0,capture:!0},ln=new Date,gn=function(n,t){en||(en=t,an=n,rn=new Date,hn(removeEventListener),mn())},mn=function(){if(an>=0&&an<rn-ln){var n={entryType:"first-input",name:en.type,target:en.target,cancelable:en.cancelable,startTime:en.timeStamp,processingStart:en.timeStamp+an};on.forEach((function(t){t([n])})),on=[]}},pn=function(n){if(n.cancelable){var t=(n.timeStamp>1e12?new Date:performance.now())-n.timeStamp;"pointerdown"==n.type?function(n,t){var e=function(){gn(n,t),a()},i=function(){a()},a=function(){removeEventListener("pointerup",e,vn),removeEventListener("pointercancel",i,vn)};addEventListener("pointerup",e,vn),addEventListener("pointercancel",i,vn)}(t,n):gn(t,n)}},hn=function(n){["mousedown","keydown","touchstart","pointerdown"].forEach((function(t){return n(t,pn,vn)}))},Tn=[100,300],In=function(n,t){var e,a=g(t=t||{}),r=(null===(e=i())||void 0===e?void 0:e.navigationId)||"1";L((function(){var e,i=C(),o=l("FID"),c=function(c){c.forEach((function(c){var d,f;a?c.navigationId&&c.navigationId!==o.navigationId&&(d="soft-navigation",f=c.navigationId,"soft-navigation"===d&&(i=C(!0)),o=l("FID",0,d,f),e=s(n,o,Tn,t.reportAllChanges)):u.disconnect(),c.startTime<i.firstHiddenTime&&(o.value=c.processingStart-c.startTime,o.entries.push(c),o.navigationId=c.navigationId||r,e(!0))}))},u=p("first-input",c,t);e=s(n,o,Tn,t.reportAllChanges),u&&(h((function(){c(u.takeRecords()),a||u.disconnect()})),f((function(){var i;o=l("FID",0,"back-forward-cache",o.navigationId),e=s(n,o,Tn,t.reportAllChanges),on=[],an=-1,en=null,hn(addEventListener),i=c,on.push(i),mn()})))}))};return n.CLSThresholds=D,n.FCPThresholds=w,n.FIDThresholds=Tn,n.INPThresholds=_,n.LCPThresholds=cn,n.TTFBThresholds=un,n.onCLS=function(n,t){!function(n,t){var e=g(t=t||{}),i=!1,a=0;M(T((function(){var r,o=l("CLS",0),u=0,d=[],v=function(e,c){if(o=l("CLS",0,e,c),r=s(n,o,D,t.reportAllChanges),u=0,i=!1,"soft-navigation"===e){var d=m(c);a=d&&d.startTime||0}},g=function(n){n.forEach((function(n){if(e&&n.navigationId&&n.navigationId!==o.navigationId&&(u>o.value&&(o.value=u,o.entries=d),r(!0),v("soft-navigation",n.navigationId)),!n.hadRecentInput){var t=d[0],i=d[d.length-1];u&&n.startTime-i.startTime<1e3&&n.startTime-t.startTime<5e3?(u+=n.value,d.push(n)):(u=n.value,d=[n])}})),u>o.value&&(o.value=u,o.entries=d,r())},T=p("layout-shift",g,t);T&&(r=s(n,o,D,t.reportAllChanges),h((function(){g(T.takeRecords()),r(!0),i=!0})),f((function(){v("back-forward-cache",o.navigationId),c((function(){return r()}))})),e&&p("soft-navigation",(function(e){e.forEach((function(e){var c=e.navigationId,u=c?m(c):null;c&&c!==o.navigationId&&u&&(u.startTime||0)>a&&(i||r(!0),v("soft-navigation",e.navigationId),r=s(n,o,D,t.reportAllChanges))}))}),t),setTimeout(r,0))})))}((function(t){var e=function(n){var t,e={};if(n.entries.length){var i=n.entries.reduce((function(n,t){return n&&n.value>t.value?n:t}));if(i&&i.sources&&i.sources.length){var r=(t=i.sources).find((function(n){return n.node&&1===n.node.nodeType}))||t[0];r&&(e={largestShiftTarget:o(r.node),largestShiftTime:i.startTime,largestShiftValue:i.value,largestShiftSource:r,largestShiftEntry:i,loadState:a(i.startTime)})}}return Object.assign(n,{attribution:e})}(t);n(e)}),t)},n.onFCP=function(n,t){M((function(t){var e=function(n){var t,e=(null===(t=i())||void 0===t?void 0:t.navigationId)||"1",r={timeToFirstByte:0,firstByteToFCP:n.value,loadState:a(d())};if(n.entries.length){var o,s=n.entries[n.entries.length-1],c=0;if(n.navigationId&&n.navigationId!==e)c=(o=m(n.navigationId))?o.startTime:0;else if(o=i()){var u=o.responseStart,f=o.activationStart||0;c=Math.max(0,u-f)}o&&(r={timeToFirstByte:c,firstByteToFCP:n.value-c,loadState:a(n.entries[0].startTime),navigationEntry:o,fcpEntry:s})}return Object.assign(n,{attribution:r})}(t);n(e)}),t)},n.onFID=function(n,t){In((function(t){var e=function(n){var t=n.entries[0],e={eventTarget:o(t.target),eventType:t.name,eventTime:t.startTime,eventEntry:t,loadState:a(t.startTime)};return Object.assign(n,{attribution:e})}(t);n(e)}),t)},n.onINP=function(n,t){e||(e=p("long-animation-frame",$)),G((function(t){var e=function(n){var t=n.entries[0],e=X.get(t),i=t.processingStart,r=e.processingEnd,s=e.entries.sort((function(n,t){return n.processingStart-t.processingStart})),c=sn(t.startTime,r),u=n.entries.find((function(n){return n.target})),d=u&&u.target||Y.get(t.interactionId),f=[t.startTime+t.duration,r].concat(c.map((function(n){return n.startTime+n.duration}))),v=Math.max.apply(Math,f),l={interactionTarget:o(d),interactionTargetElement:d,interactionType:t.name.startsWith("key")?"keyboard":"pointer",interactionTime:t.startTime,nextPaintTime:v,processedEventEntries:s,longAnimationFrameEntries:c,inputDelay:i-t.startTime,processingDuration:r-i,presentationDelay:Math.max(v-r,0),loadState:a(t.startTime)};return Object.assign(n,{attribution:l})}(t);n(e)}),t)},n.onLCP=function(n,t){!function(n,t){var e,a=!1,r=g(t=t||{}),o=0,u=(null===(e=i())||void 0===e?void 0:e.navigationId)||"1",d="";L((function(){var e,i=C(),g=l("LCP"),T=function(r,c){if(g=l("LCP",0,r,c),e=s(n,g,cn,t.reportAllChanges),a=!1,"soft-navigation"===r){i=C(!0);var u=m(c);o=u&&u.startTime?u.startTime:0}E()},I=function(n){n.forEach((function(n){if(n){r&&n.navigationId&&n.navigationId!==g.navigationId&&(a||e(!0),T("soft-navigation",n.navigationId));var t=0;if(n.navigationId&&n.navigationId!==u){var o=m(n.navigationId),s=o&&o.startTime?o.startTime:0;t=Math.max(n.startTime-s,0)}else t=Math.max(n.startTime-v(),0);n.startTime<i.firstHiddenTime&&(g.value=t,g.entries=[n],g.navigationId=n.navigationId||u,e())}}))},y=function(){S(),a||(I(L.takeRecords()),r||L.disconnect(),g.navigationId===d&&(a=!0,e(!0)))},E=function(){["keydown","click"].forEach((function(n){addEventListener(n,(function(){return b()}),!0)}))},S=function(){["keydown","click"].forEach((function(n){removeEventListener(n,(function(){return b()}),!0)}))},b=function(){d=g.navigationId,U(y)},L=p("largest-contentful-paint",I,t);L&&(e=s(n,g,cn,t.reportAllChanges),E(),h((function(){d=g.navigationId,y()})),f((function(n){T("back-forward-cache",g.navigationId),c((function(){g.value=performance.now()-n.timeStamp,a=!0,e(!0)}))})),r&&p("soft-navigation",(function(n){n.forEach((function(n){var t=n.navigationId?m(n.navigationId):null;n.navigationId&&n.navigationId!==g.navigationId&&t&&(t.startTime||0)>o&&(a||e(!0),T("soft-navigation",n.navigationId))}))}),t))}))}((function(t){var e=function(n){var t,e=(null===(t=i())||void 0===t?void 0:t.navigationId)||"1",a={timeToFirstByte:0,resourceLoadDelay:0,resourceLoadDuration:0,elementRenderDelay:n.value};if(n.entries.length){var r,s=0,c=0,u=0;if(n.navigationId&&n.navigationId!==e?s=u=(r=m(n.navigationId))?r.startTime:0:(s=(r=i())&&r.activationStart?r.activationStart:0,c=r&&r.responseStart?r.responseStart:0),r){var d=n.entries[n.entries.length-1],f=d.url&&performance.getEntriesByType("resource").filter((function(n){return n.name===d.url}))[0],v=Math.max(0,c-s),l=Math.max(v,f?(f.requestStart||f.startTime)-s:0),g=Math.max(l-u,f?f.responseEnd-s:0,0),p=Math.max(g-u,d?d.startTime-s:0,0);a={element:o(d.element),timeToFirstByte:v,resourceLoadDelay:l-v,resourceLoadDuration:g-l,elementRenderDelay:p-g,navigationEntry:r,lcpEntry:d},d.url&&(a.url=d.url),f&&(a.lcpResourceEntry=f)}}return Object.assign(n,{attribution:a})}(t);n(e)}),t)},n.onTTFB=function(n,t){fn((function(t){var e=function(n){var t={waitingDuration:0,cacheDuration:0,dnsDuration:0,connectionDuration:0,requestDuration:0};if(n.entries.length){var e=n.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);t={waitingDuration:a,cacheDuration:r-a,dnsDuration:o-r,connectionDuration:s-o,requestDuration:n.value-s,navigationEntry:e}}return Object.assign(n,{attribution:t})}(t);n(e)}),t)},n}({}); |
@@ -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))}},v=function(t){requestAnimationFrame((function(){return requestAnimationFrame((function(){return t()}))}))},p=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),v((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),p((function(){o(c.takeRecords()),n(!0)})),s((function(){i=0,r=d("CLS",0),n=m(t,r,D,e.reportAllChanges),v((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(){return t?I:performance.interactionCount||0},B=function(){"interactionCount"in performance||t||(t=l("event",F,{type:"event",buffered:!0,durationThreshold:0}))},O=[],R=new Map,j=0,q=function(){var t=Math.min(O.length-1,Math.floor((P()-j)/50));return O[t]},H=[],N=function(t){if(H.forEach((function(e){return e(t)})),t.interactionId||"first-input"===t.entryType){var e=O[O.length-1],n=R.get(t.interactionId);if(n||O.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]};R.set(r.id,r),O.push(r)}O.sort((function(t,e){return e.latency-t.latency})),O.length>10&&O.splice(10).forEach((function(t){return R.delete(t.id)}))}}},W=function(t){var e=self.requestIdleCallback||self.setTimeout,n=-1;return t=g(t),"hidden"===document.visibilityState?t():(n=e(t),p(t)),n},z=[200,500],U=function(t,e){"PerformanceEventTiming"in self&&"interactionId"in PerformanceEventTiming.prototype&&(e=e||{},L((function(){var n;B();var r,i=d("INP"),a=function(t){W((function(){t.forEach(N);var e=q();e&&e.latency!==i.value&&(i.value=e.latency,i.entries=e.entries,r())}))},o=l("event",a,{durationThreshold:null!==(n=e.durationThreshold)&&void 0!==n?n:40});r=m(t,i,z,e.reportAllChanges),o&&(o.observe({type:"first-input",buffered:!0}),p((function(){a(o.takeRecords()),r(!0)})),s((function(){j=P(),O.length=0,R.clear(),i=d("INP"),r=m(t,i,z,e.reportAllChanges)})))})))},V=[],_=[],G=new WeakMap,J=new Map,K=-1,Q=function(t){V=V.concat(t),X()},X=function(){K<0&&(K=W(Y))},Y=function(){J.size>10&&J.forEach((function(t,e){R.has(e)||J.delete(e)}));var t=O.map((function(t){return G.get(t.entries[0])})),e=_.length-50;_=_.filter((function(n,r){return r>=e||t.includes(n)}));for(var r=new Set,i=0;i<_.length;i++){var a=_[i];nt(a.startTime,a.processingEnd).forEach((function(t){r.add(t)}))}for(var o=0;o<50;o++){var c=V[V.length-1-o];if(!c||c.startTime<n)break;r.add(c)}V=Array.from(r),K=-1};H.push((function(t){t.interactionId&&t.target&&!J.has(t.interactionId)&&J.set(t.interactionId,t.target)}),(function(t){var e,r=t.startTime+t.duration;n=Math.max(n,t.processingEnd);for(var i=_.length-1;i>=0;i--){var a=_[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]},_.push(e)),(t.interactionId||"first-input"===t.entryType)&&G.set(t,e),X()}));var Z,$,tt,et,nt=function(t,e){for(var n,r=[],i=0;n=V[i];i++)if(!(n.startTime+n.duration<t)){if(n.startTime>e)break;r.push(n)}return r},rt=function(t,n){e||(e=l("long-animation-frame",Q)),U((function(e){var n=function(t){var e=t.entries[0],n=G.get(e),r=e.processingStart,a=n.processingEnd,c=n.entries.sort((function(t,e){return t.processingStart-e.processingStart})),u=nt(e.startTime,a),s=t.entries.find((function(t){return t.target})),f=s&&s.target||J.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)},it=[2500,4e3],at={},ot=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,it,e.reportAllChanges);var c=g((function(){at[i.id]||(a(o.takeRecords()),o.disconnect(),at[i.id]=!0,n(!0))}));["keydown","click"].forEach((function(t){addEventListener(t,(function(){return W(c)}),!0)})),p(c),s((function(r){i=d("LCP"),n=m(t,i,it,e.reportAllChanges),v((function(){i.value=performance.now()-r.timeStamp,at[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)},ct=[800,1800],ut=function t(e){document.prerendering?L((function(){return t(e)})):"complete"!==document.readyState?addEventListener("load",(function(){return t(e)}),!0):setTimeout(e,0)},st=function(t,e){e=e||{};var n=d("TTFB"),i=m(t,n,ct,e.reportAllChanges);ut((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,ct,e.reportAllChanges))(!0)})))}))},ft=function(t,e){st((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)},dt={passive:!0,capture:!0},lt=new Date,mt=function(t,e){Z||(Z=e,$=t,tt=new Date,gt(removeEventListener),vt())},vt=function(){if($>=0&&$<tt-lt){var t={entryType:"first-input",name:Z.type,target:Z.target,cancelable:Z.cancelable,startTime:Z.timeStamp,processingStart:Z.timeStamp+$};et.forEach((function(e){e(t)})),et=[]}},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(){mt(t,e),i()},r=function(){i()},i=function(){removeEventListener("pointerup",n,dt),removeEventListener("pointercancel",r,dt)};addEventListener("pointerup",n,dt),addEventListener("pointercancel",r,dt)}(e,t):mt(e,t)}},gt=function(t){["mousedown","keydown","touchstart","pointerdown"].forEach((function(e){return t(e,pt,dt)}))},ht=[100,300],Tt=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,ht,e.reportAllChanges),c&&(p(g((function(){o(c.takeRecords()),c.disconnect()}))),s((function(){var r;i=d("FID"),n=m(t,i,ht,e.reportAllChanges),et=[],$=-1,Z=null,gt(addEventListener),r=a,et.push(r),vt()})))}))},yt=function(t,e){Tt((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,ht as FIDThresholds,z as INPThresholds,it as LCPThresholds,ct as TTFBThresholds,w as onCLS,x as onFCP,yt as onFID,rt as onINP,ot as onLCP,ft as onTTFB}; | ||
var t,n,e=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 n=e();if(n){if(t<n.domInteractive)return"loading";if(0===n.domContentLoadedEventStart||t<n.domContentLoadedEventStart)return"dom-interactive";if(0===n.domComplete||t<n.domComplete)return"dom-content-loaded"}return"complete"},a=function(t){var n=t.nodeName;return 1===t.nodeType?n.toLowerCase():n.toUpperCase().replace(/^#/,"")},r=function(t,n){var e="";try{for(;t&&9!==t.nodeType;){var i=t,r=i.id?"#"+i.id:a(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+r.length>(n||100)-1)return e||r;if(e=e?r+">"+e:r,i.id)break;t=i.parentNode}}catch(t){}return e},o=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))}},c=function(t){requestAnimationFrame((function(){return requestAnimationFrame((function(){return t()}))}))},s=-1,u=function(){return s},f=function(t){addEventListener("pageshow",(function(n){n.persisted&&(s=n.timeStamp,t(n))}),!0)},v=function(){var t=e();return t&&t.activationStart||0},d=function(t,n,i,a){var r,o=(null===(r=e())||void 0===r?void 0:r.navigationId)||"1",c=e(),s="navigate";i?s=i:u()>=0?s="back-forward-cache":c&&(document.prerendering||v()>0?s="prerender":document.wasDiscarded?s="restore":c.type&&(s=c.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:a||o}},g=function(t){return PerformanceObserver.supportedEntryTypes.includes("soft-navigation")&&t&&t.reportSoftNavs},l=function(t){if(t){var n=window.performance.getEntriesByType("soft-navigation").filter((function(n){return n.navigationId===t}));return n?n[0]:void 0}},m=function(t,n,e){var i=g(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){}},p=function(t){document.addEventListener("visibilitychange",(function(){"hidden"===document.visibilityState&&t()}))},h=function(t){var n=!1;return function(){n||(t(),n=!0)}},T=-1,I=function(){return"hidden"!==document.visibilityState||document.prerendering?1/0:0},y=function(t){"hidden"===document.visibilityState&&T>-1&&(T="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 arguments.length>0&&void 0!==arguments[0]&&arguments[0]&&(T=-1),T<0&&(T=I(),E(),f((function(){setTimeout((function(){T=I(),E()}),0)}))),{get firstHiddenTime(){return T}}},C=function(t){document.prerendering?addEventListener("prerenderingchange",(function(){return t()}),!0):t()},w=[1800,3e3],L=function(t,n){var i,a=g(n=n||{}),r=0,s=(null===(i=e())||void 0===i?void 0:i.navigationId)||"1";C((function(){var e,i=b(),u=d("FCP"),g=m("paint",(function(c){c.forEach((function(c){if("first-contentful-paint"===c.name){a?c.navigationId&&c.navigationId!==u.navigationId&&function(a,c){if(u=d("FCP",0,a,c),e=o(t,u,w,n.reportAllChanges),"soft-navigation"===a){i=b(!0);var s=c?l(c):null;r=s&&s.startTime||0}}("soft-navigation",c.navigationId):g.disconnect();var f=0;if(c.navigationId&&c.navigationId!==s){var m=l(c.navigationId),p=m&&m.startTime?m.startTime:0;f=Math.max(c.startTime-p,0)}else f=Math.max(c.startTime-v(),0);var h=a&&c.navigationId?l(c.navigationId):null,T=h&&h.startTime?h.startTime:0;(c.startTime<i.firstHiddenTime||a&&c.navigationId&&c.navigationId!==u.navigationId&&c.navigationId!==s&&T>r)&&(u.value=f,u.entries.push(c),u.navigationId=c.navigationId||"1",e(!0))}}))}),n);g&&(e=o(t,u,w,n.reportAllChanges),f((function(i){u=d("FCP",0,"back-forward-cache",u.navigationId),e=o(t,u,w,n.reportAllChanges),c((function(){u.value=performance.now()-i.timeStamp,e(!0)}))})))}))},M=[.1,.25],D=function(t,n){!function(t,n){var e=g(n=n||{}),i=!1,a=0;L(h((function(){var r,s=d("CLS",0),u=0,v=[],g=function(e,c){if(s=d("CLS",0,e,c),r=o(t,s,M,n.reportAllChanges),u=0,i=!1,"soft-navigation"===e){var f=l(c);a=f&&f.startTime||0}},h=function(t){t.forEach((function(t){if(e&&t.navigationId&&t.navigationId!==s.navigationId&&(u>s.value&&(s.value=u,s.entries=v),r(!0),g("soft-navigation",t.navigationId)),!t.hadRecentInput){var n=v[0],i=v[v.length-1];u&&t.startTime-i.startTime<1e3&&t.startTime-n.startTime<5e3?(u+=t.value,v.push(t)):(u=t.value,v=[t])}})),u>s.value&&(s.value=u,s.entries=v,r())},T=m("layout-shift",h,n);T&&(r=o(t,s,M,n.reportAllChanges),p((function(){h(T.takeRecords()),r(!0),i=!0})),f((function(){g("back-forward-cache",s.navigationId),c((function(){return r()}))})),e&&m("soft-navigation",(function(e){e.forEach((function(e){var c=e.navigationId,u=c?l(c):null;c&&c!==s.navigationId&&u&&(u.startTime||0)>a&&(i||r(!0),g("soft-navigation",e.navigationId),r=o(t,s,M,n.reportAllChanges))}))}),n),setTimeout(r,0))})))}((function(n){var e=function(t){var n,e={};if(t.entries.length){var a=t.entries.reduce((function(t,n){return t&&t.value>n.value?t:n}));if(a&&a.sources&&a.sources.length){var o=(n=a.sources).find((function(t){return t.node&&1===t.node.nodeType}))||n[0];o&&(e={largestShiftTarget:r(o.node),largestShiftTime:a.startTime,largestShiftValue:a.value,largestShiftSource:o,largestShiftEntry:a,loadState:i(a.startTime)})}}return Object.assign(t,{attribution:e})}(n);t(e)}),n)},k=function(t,n){L((function(n){var a=function(t){var n,a=(null===(n=e())||void 0===n?void 0:n.navigationId)||"1",r={timeToFirstByte:0,firstByteToFCP:t.value,loadState:i(u())};if(t.entries.length){var o,c=t.entries[t.entries.length-1],s=0;if(t.navigationId&&t.navigationId!==a)s=(o=l(t.navigationId))?o.startTime:0;else if(o=e()){var f=o.responseStart,v=o.activationStart||0;s=Math.max(0,f-v)}o&&(r={timeToFirstByte:s,firstByteToFCP:t.value-s,loadState:i(t.entries[0].startTime),navigationEntry:o,fcpEntry:c})}return Object.assign(t,{attribution:r})}(n);t(a)}),n)},x=0,A=1/0,F=0,P="",B=!1,O=function(t){var n;P||(P=(null===(n=e())||void 0===n?void 0:n.navigationId)||"1"),t.forEach((function(t){t.interactionId&&(B&&t.navigationId&&t.navigationId!==P&&(P=t.navigationId,x=0,A=1/0,F=0),A=Math.min(A,t.interactionId),F=Math.max(F,t.interactionId),x=F?(F-A)/7+1:0)}))},N=function(){return t?x:performance.interactionCount||0},R=function(n){"interactionCount"in performance||t||(t=m("event",O,{type:"event",buffered:!0,durationThreshold:0,includeSoftNavigationObservations:B=n||!1}))},j=[],q=new Map,H=0,W=function(){H=N(),j.length=0,q.clear()},z=function(){var t=Math.min(j.length-1,Math.floor((N()-H)/50));return j[t]},U=[],V=function(t){if(U.forEach((function(n){return n(t)})),t.interactionId||"first-input"===t.entryType){var n=j[j.length-1],e=q.get(t.interactionId);if(e||j.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]};q.set(i.id,i),j.push(i)}j.sort((function(t,n){return n.latency-t.latency})),j.length>10&&j.splice(10).forEach((function(t){return q.delete(t.id)}))}}},_=function(t){var n=self.requestIdleCallback||self.setTimeout,e=-1;return t=h(t),"hidden"===document.visibilityState?t():(e=n(t),p(t)),e},G=[200,500],J=function(t,n){if("PerformanceEventTiming"in self&&"interactionId"in PerformanceEventTiming.prototype){var e=g(n=n||{}),i=!1,a=0;C((function(){var r;R(e);var s,u=d("INP"),v=function(e,r){if(W(),u=d("INP",0,e,r),s=o(t,u,G,n.reportAllChanges),i=!1,"soft-navigation"===e){var c=l(r);a=c&&c.startTime?c.startTime:0}},g=function(t){_((function(){var e;t.forEach(V),(e=z())&&(e.latency!==u.value||n&&n.reportAllChanges)&&(u.value=e.latency,u.entries=e.entries),s()}))},h=m("event",g,{durationThreshold:null!==(r=n.durationThreshold)&&void 0!==r?r:40,opts:n});if(s=o(t,u,G,n.reportAllChanges),h){h.observe({type:"first-input",buffered:!0,includeSoftNavigationObservations:e}),p((function(){g(h.takeRecords()),s(!0)})),f((function(){W(),v("back-forward-cache",u.navigationId),c((function(){return s()}))}));e&&m("soft-navigation",(function(e){e.forEach((function(e){var r=l(e.navigationId),c=r&&r.startTime?r.startTime:0;e.navigationId&&e.navigationId!==u.navigationId&&c>a&&(!i&&u.value>0&&s(!0),v("soft-navigation",e.navigationId),s=o(t,u,G,n.reportAllChanges))}))}),n)}}))}},K=[],Q=[],X=0,Y=new WeakMap,Z=new Map,$=-1,tt=function(t){K=K.concat(t),nt()},nt=function(){$<0&&($=_(et))},et=function(){Z.size>10&&Z.forEach((function(t,n){q.has(n)||Z.delete(n)}));var t=j.map((function(t){return Y.get(t.entries[0])})),n=Q.length-50;Q=Q.filter((function(e,i){return i>=n||t.includes(e)}));for(var e=new Set,i=0;i<Q.length;i++){var a=Q[i];ct(a.startTime,a.processingEnd).forEach((function(t){e.add(t)}))}var r=K.length-1-50;K=K.filter((function(t,n){return t.startTime>X&&n>r||e.has(t)})),$=-1};U.push((function(t){t.interactionId&&t.target&&!Z.has(t.interactionId)&&Z.set(t.interactionId,t.target)}),(function(t){var n,e=t.startTime+t.duration;X=Math.max(X,t.processingEnd);for(var i=Q.length-1;i>=0;i--){var a=Q[i];if(Math.abs(e-a.renderTime)<=8){(n=a).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]},Q.push(n)),(t.interactionId||"first-input"===t.entryType)&&Y.set(t,n),nt()}));var it,at,rt,ot,ct=function(t,n){for(var e,i=[],a=0;e=K[a];a++)if(!(e.startTime+e.duration<t)){if(e.startTime>n)break;i.push(e)}return i},st=function(t,e){n||(n=m("long-animation-frame",tt)),J((function(n){var e=function(t){var n=t.entries[0],e=Y.get(n),a=n.processingStart,o=e.processingEnd,c=e.entries.sort((function(t,n){return t.processingStart-n.processingStart})),s=ct(n.startTime,o),u=t.entries.find((function(t){return t.target})),f=u&&u.target||Z.get(n.interactionId),v=[n.startTime+n.duration,o].concat(s.map((function(t){return t.startTime+t.duration}))),d=Math.max.apply(Math,v),g={interactionTarget:r(f),interactionTargetElement:f,interactionType:n.name.startsWith("key")?"keyboard":"pointer",interactionTime:n.startTime,nextPaintTime:d,processedEventEntries:c,longAnimationFrameEntries:s,inputDelay:a-n.startTime,processingDuration:o-a,presentationDelay:Math.max(d-o,0),loadState:i(n.startTime)};return Object.assign(t,{attribution:g})}(n);t(e)}),e)},ut=[2500,4e3],ft=function(t,n){!function(t,n){var i,a=!1,r=g(n=n||{}),s=0,u=(null===(i=e())||void 0===i?void 0:i.navigationId)||"1",h="";C((function(){var e,i=b(),g=d("LCP"),T=function(r,c){if(g=d("LCP",0,r,c),e=o(t,g,ut,n.reportAllChanges),a=!1,"soft-navigation"===r){i=b(!0);var u=l(c);s=u&&u.startTime?u.startTime:0}E()},I=function(t){t.forEach((function(t){if(t){r&&t.navigationId&&t.navigationId!==g.navigationId&&(a||e(!0),T("soft-navigation",t.navigationId));var n=0;if(t.navigationId&&t.navigationId!==u){var o=l(t.navigationId),c=o&&o.startTime?o.startTime:0;n=Math.max(t.startTime-c,0)}else n=Math.max(t.startTime-v(),0);t.startTime<i.firstHiddenTime&&(g.value=n,g.entries=[t],g.navigationId=t.navigationId||u,e())}}))},y=function(){S(),a||(I(w.takeRecords()),r||w.disconnect(),g.navigationId===h&&(a=!0,e(!0)))},E=function(){["keydown","click"].forEach((function(t){addEventListener(t,(function(){return C()}),!0)}))},S=function(){["keydown","click"].forEach((function(t){removeEventListener(t,(function(){return C()}),!0)}))},C=function(){h=g.navigationId,_(y)},w=m("largest-contentful-paint",I,n);w&&(e=o(t,g,ut,n.reportAllChanges),E(),p((function(){h=g.navigationId,y()})),f((function(t){T("back-forward-cache",g.navigationId),c((function(){g.value=performance.now()-t.timeStamp,a=!0,e(!0)}))})),r&&m("soft-navigation",(function(t){t.forEach((function(t){var n=t.navigationId?l(t.navigationId):null;t.navigationId&&t.navigationId!==g.navigationId&&n&&(n.startTime||0)>s&&(a||e(!0),T("soft-navigation",t.navigationId))}))}),n))}))}((function(n){var i=function(t){var n,i=(null===(n=e())||void 0===n?void 0:n.navigationId)||"1",a={timeToFirstByte:0,resourceLoadDelay:0,resourceLoadDuration:0,elementRenderDelay:t.value};if(t.entries.length){var o,c=0,s=0,u=0;if(t.navigationId&&t.navigationId!==i?c=u=(o=l(t.navigationId))?o.startTime:0:(c=(o=e())&&o.activationStart?o.activationStart:0,s=o&&o.responseStart?o.responseStart:0),o){var f=t.entries[t.entries.length-1],v=f.url&&performance.getEntriesByType("resource").filter((function(t){return t.name===f.url}))[0],d=Math.max(0,s-c),g=Math.max(d,v?(v.requestStart||v.startTime)-c:0),m=Math.max(g-u,v?v.responseEnd-c:0,0),p=Math.max(m-u,f?f.startTime-c:0,0);a={element:r(f.element),timeToFirstByte:d,resourceLoadDelay:g-d,resourceLoadDuration:m-g,elementRenderDelay:p-m,navigationEntry:o,lcpEntry:f},f.url&&(a.url=f.url),v&&(a.lcpResourceEntry=v)}}return Object.assign(t,{attribution:a})}(n);t(i)}),n)},vt=[800,1800],dt=function t(n){document.prerendering?C((function(){return t(n)})):"complete"!==document.readyState?addEventListener("load",(function(){return t(n)}),!0):setTimeout(n,0)},gt=function(t,n){var i=g(n=n||{}),a=d("TTFB"),r=o(t,a,vt,n.reportAllChanges);dt((function(){var c=e();if(c){var s=c.responseStart;a.value=Math.max(s-v(),0),a.entries=[c],r(!0),f((function(){a=d("TTFB",0,"back-forward-cache",a.navigationId),(r=o(t,a,vt,n.reportAllChanges))(!0)}));i&&m("soft-navigation",(function(e){e.forEach((function(e){e.navigationId&&((a=d("TTFB",0,"soft-navigation",e.navigationId)).entries=[e],(r=o(t,a,vt,n.reportAllChanges))(!0))}))}),n)}}))},lt=function(t,n){gt((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)},mt={passive:!0,capture:!0},pt=new Date,ht=function(t,n){it||(it=n,at=t,rt=new Date,yt(removeEventListener),Tt())},Tt=function(){if(at>=0&&at<rt-pt){var t={entryType:"first-input",name:it.type,target:it.target,cancelable:it.cancelable,startTime:it.timeStamp,processingStart:it.timeStamp+at};ot.forEach((function(n){n([t])})),ot=[]}},It=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(){ht(t,n),a()},i=function(){a()},a=function(){removeEventListener("pointerup",e,mt),removeEventListener("pointercancel",i,mt)};addEventListener("pointerup",e,mt),addEventListener("pointercancel",i,mt)}(n,t):ht(n,t)}},yt=function(t){["mousedown","keydown","touchstart","pointerdown"].forEach((function(n){return t(n,It,mt)}))},Et=[100,300],St=function(t,n){var i,a=g(n=n||{}),r=(null===(i=e())||void 0===i?void 0:i.navigationId)||"1";C((function(){var e,i=b(),c=d("FID"),s=function(s){s.forEach((function(s){var f,v;a?s.navigationId&&s.navigationId!==c.navigationId&&(f="soft-navigation",v=s.navigationId,"soft-navigation"===f&&(i=b(!0)),c=d("FID",0,f,v),e=o(t,c,Et,n.reportAllChanges)):u.disconnect(),s.startTime<i.firstHiddenTime&&(c.value=s.processingStart-s.startTime,c.entries.push(s),c.navigationId=s.navigationId||r,e(!0))}))},u=m("first-input",s,n);e=o(t,c,Et,n.reportAllChanges),u&&(p((function(){s(u.takeRecords()),a||u.disconnect()})),f((function(){var i;c=d("FID",0,"back-forward-cache",c.navigationId),e=o(t,c,Et,n.reportAllChanges),ot=[],at=-1,it=null,yt(addEventListener),i=s,ot.push(i),Tt()})))}))},bt=function(t,n){St((function(n){var e=function(t){var n=t.entries[0],e={eventTarget:r(n.target),eventType:n.name,eventTime:n.startTime,eventEntry:n,loadState:i(n.startTime)};return Object.assign(t,{attribution:e})}(n);t(e)}),n)};export{M as CLSThresholds,w as FCPThresholds,Et as FIDThresholds,G as INPThresholds,ut as LCPThresholds,vt as TTFBThresholds,D as onCLS,k as onFCP,bt as onFID,st as onINP,ft as onLCP,lt 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(){return n?I:performance.interactionCount||0},k=function(){"interactionCount"in performance||n||(n=d("event",F,{type:"event",buffered:!0,durationThreshold:0}))},D=[],B=new Map,R=0,x=function(){var e=Math.min(D.length-1,Math.floor((M()-R)/50));return D[e]},H=[],N=function(e){if(H.forEach((function(n){return n(e)})),e.interactionId||"first-input"===e.entryType){var n=D[D.length-1],t=B.get(e.interactionId);if(t||D.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]};B.set(r.id,r),D.push(r)}D.sort((function(e,n){return n.latency-e.latency})),D.length>10&&D.splice(10).forEach((function(e){return B.delete(e.id)}))}}},q=function(e){var n=self.requestIdleCallback||self.setTimeout,t=-1;return e=m(e),"hidden"===document.visibilityState?e():(t=n(e),v(e)),t},O=[200,500],j=[2500,4e3],V={},_=[800,1800],z=function e(n){document.prerendering?L((function(){return e(n)})):"complete"!==document.readyState?addEventListener("load",(function(){return e(n)}),!0):setTimeout(n,0)},G={passive:!0,capture:!0},J=new Date,K=function(e,n){t||(t=n,r=e,i=new Date,W(removeEventListener),Q())},Q=function(){if(r>=0&&r<i-J){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=[]}},U=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(){K(e,n),i()},r=function(){i()},i=function(){removeEventListener("pointerup",t,G),removeEventListener("pointercancel",r,G)};addEventListener("pointerup",t,G),addEventListener("pointercancel",r,G)}(n,e):K(n,e)}},W=function(e){["mousedown","keydown","touchstart","pointerdown"].forEach((function(n){return e(n,U,G)}))},X=[100,300];return e.CLSThresholds=w,e.FCPThresholds=S,e.FIDThresholds=X,e.INPThresholds=O,e.LCPThresholds=j,e.TTFBThresholds=_,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,X,n.reportAllChanges),h&&(v(m((function(){p(h.takeRecords()),h.disconnect()}))),c((function(){var a;u=f("FID"),i=l(e,u,X,n.reportAllChanges),o=[],r=-1,t=null,W(addEventListener),a=s,o.push(a),Q()})))}))},e.onINP=function(e,n){"PerformanceEventTiming"in self&&"interactionId"in PerformanceEventTiming.prototype&&(n=n||{},L((function(){var t;k();var r,i=f("INP"),o=function(e){q((function(){e.forEach(N);var n=x();n&&n.latency!==i.value&&(i.value=n.latency,i.entries=n.entries,r())}))},a=d("event",o,{durationThreshold:null!==(t=n.durationThreshold)&&void 0!==t?t:40});r=l(e,i,O,n.reportAllChanges),a&&(a.observe({type:"first-input",buffered:!0}),v((function(){o(a.takeRecords()),r(!0)})),c((function(){R=M(),D.length=0,B.clear(),i=f("INP"),r=l(e,i,O,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,j,n.reportAllChanges);var u=m((function(){V[i.id]||(o(a.takeRecords()),a.disconnect(),V[i.id]=!0,t(!0))}));["keydown","click"].forEach((function(e){addEventListener(e,(function(){return q(u)}),!0)})),v(u),c((function(r){i=f("LCP"),t=l(e,i,j,n.reportAllChanges),p((function(){i.value=performance.now()-r.timeStamp,V[i.id]=!0,t(!0)}))}))}}))},e.onTTFB=function(e,n){n=n||{};var t=f("TTFB"),r=l(e,t,_,n.reportAllChanges);z((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,_,n.reportAllChanges))(!0)})))}))},e}({}); | ||
var webVitals=function(n){"use strict";var t,i,e,a,o,r=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))}},c=function(n){requestAnimationFrame((function(){return requestAnimationFrame((function(){return n()}))}))},u=-1,v=function(n){addEventListener("pageshow",(function(t){t.persisted&&(u=t.timeStamp,n(t))}),!0)},f=function(){var n=self.performance&&performance.getEntriesByType&&performance.getEntriesByType("navigation")[0];if(n&&n.responseStart>0&&n.responseStart<performance.now())return n},s=function(){var n=f();return n&&n.activationStart||0},d=function(n,t,i,e){var a,o=(null===(a=f())||void 0===a?void 0:a.navigationId)||"1",r=f(),c="navigate";i?c=i:u>=0?c="back-forward-cache":r&&(document.prerendering||s()>0?c="prerender":document.wasDiscarded?c="restore":r.type&&(c=r.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:c,navigationId:e||o}},g=function(n){return PerformanceObserver.supportedEntryTypes.includes("soft-navigation")&&n&&n.reportSoftNavs},l=function(n){if(n){var t=window.performance.getEntriesByType("soft-navigation").filter((function(t){return t.navigationId===n}));return t?t[0]:void 0}},m=function(n,t,i){var e=g(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){}},p=function(n){document.addEventListener("visibilitychange",(function(){"hidden"===document.visibilityState&&n()}))},h=function(n){var t=!1;return function(){t||(n(),t=!0)}},I=-1,T=function(){return"hidden"!==document.visibilityState||document.prerendering?1/0:0},y=function(n){"hidden"===document.visibilityState&&I>-1&&(I="visibilitychange"===n.type?n.timeStamp:0,C())},E=function(){addEventListener("visibilitychange",y,!0),addEventListener("prerenderingchange",y,!0)},C=function(){removeEventListener("visibilitychange",y,!0),removeEventListener("prerenderingchange",y,!0)},b=function(){return arguments.length>0&&void 0!==arguments[0]&&arguments[0]&&(I=-1),I<0&&(I=T(),E(),v((function(){setTimeout((function(){I=T(),E()}),0)}))),{get firstHiddenTime(){return I}}},w=function(n){document.prerendering?addEventListener("prerenderingchange",(function(){return n()}),!0):n()},S=[1800,3e3],L=function(n,t){var i,e=g(t=t||{}),a=0,o=(null===(i=f())||void 0===i?void 0:i.navigationId)||"1";w((function(){var i,u=b(),f=d("FCP"),g=m("paint",(function(c){c.forEach((function(c){if("first-contentful-paint"===c.name){e?c.navigationId&&c.navigationId!==f.navigationId&&function(e,o){if(f=d("FCP",0,e,o),i=r(n,f,S,t.reportAllChanges),"soft-navigation"===e){u=b(!0);var c=o?l(o):null;a=c&&c.startTime||0}}("soft-navigation",c.navigationId):g.disconnect();var v=0;if(c.navigationId&&c.navigationId!==o){var m=l(c.navigationId),p=m&&m.startTime?m.startTime:0;v=Math.max(c.startTime-p,0)}else v=Math.max(c.startTime-s(),0);var h=e&&c.navigationId?l(c.navigationId):null,I=h&&h.startTime?h.startTime:0;(c.startTime<u.firstHiddenTime||e&&c.navigationId&&c.navigationId!==f.navigationId&&c.navigationId!==o&&I>a)&&(f.value=v,f.entries.push(c),f.navigationId=c.navigationId||"1",i(!0))}}))}),t);g&&(i=r(n,f,S,t.reportAllChanges),v((function(e){f=d("FCP",0,"back-forward-cache",f.navigationId),i=r(n,f,S,t.reportAllChanges),c((function(){f.value=performance.now()-e.timeStamp,i(!0)}))})))}))},A=[.1,.25],P=0,k=1/0,F=0,M="",D=!1,B=function(n){var t;M||(M=(null===(t=f())||void 0===t?void 0:t.navigationId)||"1"),n.forEach((function(n){n.interactionId&&(D&&n.navigationId&&n.navigationId!==M&&(M=n.navigationId,P=0,k=1/0,F=0),k=Math.min(k,n.interactionId),F=Math.max(F,n.interactionId),P=F?(F-k)/7+1:0)}))},N=function(){return t?P:performance.interactionCount||0},O=function(n){"interactionCount"in performance||t||(t=m("event",B,{type:"event",buffered:!0,durationThreshold:0,includeSoftNavigationObservations:D=n||!1}))},x=[],R=new Map,H=0,q=function(){H=N(),x.length=0,R.clear()},j=function(){var n=Math.min(x.length-1,Math.floor((N()-H)/50));return x[n]},V=[],_=function(n){if(V.forEach((function(t){return t(n)})),n.interactionId||"first-input"===n.entryType){var t=x[x.length-1],i=R.get(n.interactionId);if(i||x.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]};R.set(e.id,e),x.push(e)}x.sort((function(n,t){return t.latency-n.latency})),x.length>10&&x.splice(10).forEach((function(n){return R.delete(n.id)}))}}},z=function(n){var t=self.requestIdleCallback||self.setTimeout,i=-1;return n=h(n),"hidden"===document.visibilityState?n():(i=t(n),p(n)),i},G=[200,500],J=[2500,4e3],K=[800,1800],Q=function n(t){document.prerendering?w((function(){return n(t)})):"complete"!==document.readyState?addEventListener("load",(function(){return n(t)}),!0):setTimeout(t,0)},U={passive:!0,capture:!0},W=new Date,X=function(n,t){i||(i=t,e=n,a=new Date,$(removeEventListener),Y())},Y=function(){if(e>=0&&e<a-W){var n={entryType:"first-input",name:i.type,target:i.target,cancelable:i.cancelable,startTime:i.timeStamp,processingStart:i.timeStamp+e};o.forEach((function(t){t([n])})),o=[]}},Z=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(){X(n,t),a()},e=function(){a()},a=function(){removeEventListener("pointerup",i,U),removeEventListener("pointercancel",e,U)};addEventListener("pointerup",i,U),addEventListener("pointercancel",e,U)}(t,n):X(t,n)}},$=function(n){["mousedown","keydown","touchstart","pointerdown"].forEach((function(t){return n(t,Z,U)}))},nn=[100,300];return n.CLSThresholds=A,n.FCPThresholds=S,n.FIDThresholds=nn,n.INPThresholds=G,n.LCPThresholds=J,n.TTFBThresholds=K,n.onCLS=function(n,t){var i=g(t=t||{}),e=!1,a=0;L(h((function(){var o,u=d("CLS",0),f=0,s=[],g=function(i,c){if(u=d("CLS",0,i,c),o=r(n,u,A,t.reportAllChanges),f=0,e=!1,"soft-navigation"===i){var v=l(c);a=v&&v.startTime||0}},h=function(n){n.forEach((function(n){if(i&&n.navigationId&&n.navigationId!==u.navigationId&&(f>u.value&&(u.value=f,u.entries=s),o(!0),g("soft-navigation",n.navigationId)),!n.hadRecentInput){var t=s[0],e=s[s.length-1];f&&n.startTime-e.startTime<1e3&&n.startTime-t.startTime<5e3?(f+=n.value,s.push(n)):(f=n.value,s=[n])}})),f>u.value&&(u.value=f,u.entries=s,o())},I=m("layout-shift",h,t);if(I){o=r(n,u,A,t.reportAllChanges),p((function(){h(I.takeRecords()),o(!0),e=!0})),v((function(){g("back-forward-cache",u.navigationId),c((function(){return o()}))}));i&&m("soft-navigation",(function(i){i.forEach((function(i){var c=i.navigationId,v=c?l(c):null;c&&c!==u.navigationId&&v&&(v.startTime||0)>a&&(e||o(!0),g("soft-navigation",i.navigationId),o=r(n,u,A,t.reportAllChanges))}))}),t),setTimeout(o,0)}})))},n.onFCP=L,n.onFID=function(n,t){var a,c=g(t=t||{}),u=(null===(a=f())||void 0===a?void 0:a.navigationId)||"1";w((function(){var a,f=b(),s=d("FID"),g=function(i){i.forEach((function(i){var e,o;c?i.navigationId&&i.navigationId!==s.navigationId&&(e="soft-navigation",o=i.navigationId,"soft-navigation"===e&&(f=b(!0)),s=d("FID",0,e,o),a=r(n,s,nn,t.reportAllChanges)):l.disconnect(),i.startTime<f.firstHiddenTime&&(s.value=i.processingStart-i.startTime,s.entries.push(i),s.navigationId=i.navigationId||u,a(!0))}))},l=m("first-input",g,t);a=r(n,s,nn,t.reportAllChanges),l&&(p((function(){g(l.takeRecords()),c||l.disconnect()})),v((function(){var c;s=d("FID",0,"back-forward-cache",s.navigationId),a=r(n,s,nn,t.reportAllChanges),o=[],e=-1,i=null,$(addEventListener),c=g,o.push(c),Y()})))}))},n.onINP=function(n,t){if("PerformanceEventTiming"in self&&"interactionId"in PerformanceEventTiming.prototype){var i=g(t=t||{}),e=!1,a=0;w((function(){var o;O(i);var u,f=d("INP"),s=function(i,o){if(q(),f=d("INP",0,i,o),u=r(n,f,G,t.reportAllChanges),e=!1,"soft-navigation"===i){var c=l(o);a=c&&c.startTime?c.startTime:0}},g=function(n){z((function(){var i;n.forEach(_),(i=j())&&(i.latency!==f.value||t&&t.reportAllChanges)&&(f.value=i.latency,f.entries=i.entries),u()}))},h=m("event",g,{durationThreshold:null!==(o=t.durationThreshold)&&void 0!==o?o:40,opts:t});if(u=r(n,f,G,t.reportAllChanges),h){h.observe({type:"first-input",buffered:!0,includeSoftNavigationObservations:i}),p((function(){g(h.takeRecords()),u(!0)})),v((function(){q(),s("back-forward-cache",f.navigationId),c((function(){return u()}))}));i&&m("soft-navigation",(function(i){i.forEach((function(i){var o=l(i.navigationId),c=o&&o.startTime?o.startTime:0;i.navigationId&&i.navigationId!==f.navigationId&&c>a&&(!e&&f.value>0&&u(!0),s("soft-navigation",i.navigationId),u=r(n,f,G,t.reportAllChanges))}))}),t)}}))}},n.onLCP=function(n,t){var i,e=!1,a=g(t=t||{}),o=0,u=(null===(i=f())||void 0===i?void 0:i.navigationId)||"1",h="";w((function(){var i,f=b(),g=d("LCP"),I=function(a,c){if(g=d("LCP",0,a,c),i=r(n,g,J,t.reportAllChanges),e=!1,"soft-navigation"===a){f=b(!0);var u=l(c);o=u&&u.startTime?u.startTime:0}E()},T=function(n){n.forEach((function(n){if(n){a&&n.navigationId&&n.navigationId!==g.navigationId&&(e||i(!0),I("soft-navigation",n.navigationId));var t=0;if(n.navigationId&&n.navigationId!==u){var o=l(n.navigationId),r=o&&o.startTime?o.startTime:0;t=Math.max(n.startTime-r,0)}else t=Math.max(n.startTime-s(),0);n.startTime<f.firstHiddenTime&&(g.value=t,g.entries=[n],g.navigationId=n.navigationId||u,i())}}))},y=function(){C(),e||(T(S.takeRecords()),a||S.disconnect(),g.navigationId===h&&(e=!0,i(!0)))},E=function(){["keydown","click"].forEach((function(n){addEventListener(n,(function(){return w()}),!0)}))},C=function(){["keydown","click"].forEach((function(n){removeEventListener(n,(function(){return w()}),!0)}))},w=function(){h=g.navigationId,z(y)},S=m("largest-contentful-paint",T,t);if(S){i=r(n,g,J,t.reportAllChanges),E(),p((function(){h=g.navigationId,y()})),v((function(n){I("back-forward-cache",g.navigationId),c((function(){g.value=performance.now()-n.timeStamp,e=!0,i(!0)}))}));a&&m("soft-navigation",(function(n){n.forEach((function(n){var t=n.navigationId?l(n.navigationId):null;n.navigationId&&n.navigationId!==g.navigationId&&t&&(t.startTime||0)>o&&(e||i(!0),I("soft-navigation",n.navigationId))}))}),t)}}))},n.onTTFB=function(n,t){var i=g(t=t||{}),e=d("TTFB"),a=r(n,e,K,t.reportAllChanges);Q((function(){var o=f();if(o){var c=o.responseStart;e.value=Math.max(c-s(),0),e.entries=[o],a(!0),v((function(){e=d("TTFB",0,"back-forward-cache",e.navigationId),(a=r(n,e,K,t.reportAllChanges))(!0)}));i&&m("soft-navigation",(function(i){i.forEach((function(i){i.navigationId&&((e=d("TTFB",0,"soft-navigation",i.navigationId)).entries=[i],(a=r(n,e,K,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(){return e?A:performance.interactionCount||0},F=function(){"interactionCount"in performance||e||(e=s("event",M,{type:"event",buffered:!0,durationThreshold:0}))},D=[],x=new Map,R=0,B=function(){var e=Math.min(D.length-1,Math.floor((k()-R)/50));return D[e]},H=[],q=function(e){if(H.forEach((function(n){return n(e)})),e.interactionId||"first-input"===e.entryType){var n=D[D.length-1],t=x.get(e.interactionId);if(t||D.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]};x.set(r.id,r),D.push(r)}D.sort((function(e,n){return n.latency-e.latency})),D.length>10&&D.splice(10).forEach((function(e){return x.delete(e.id)}))}}},O=function(e){var n=self.requestIdleCallback||self.setTimeout,t=-1;return e=v(e),"hidden"===document.visibilityState?e():(t=n(e),p(e)),t},N=[200,500],j=function(e,n){"PerformanceEventTiming"in self&&"interactionId"in PerformanceEventTiming.prototype&&(n=n||{},C((function(){var t;F();var r,i=f("INP"),o=function(e){O((function(){e.forEach(q);var n=B();n&&n.latency!==i.value&&(i.value=n.latency,i.entries=n.entries,r())}))},c=s("event",o,{durationThreshold:null!==(t=n.durationThreshold)&&void 0!==t?t:40});r=d(e,i,N,n.reportAllChanges),c&&(c.observe({type:"first-input",buffered:!0}),p((function(){o(c.takeRecords()),r(!0)})),a((function(){R=k(),D.length=0,x.clear(),i=f("INP"),r=d(e,i,N,n.reportAllChanges)})))})))},_=[2500,4e3],z={},G=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,_,n.reportAllChanges);var m=v((function(){z[i.id]||(o(c.takeRecords()),c.disconnect(),z[i.id]=!0,t(!0))}));["keydown","click"].forEach((function(e){addEventListener(e,(function(){return O(m)}),!0)})),p(m),a((function(r){i=f("LCP"),t=d(e,i,_,n.reportAllChanges),l((function(){i.value=performance.now()-r.timeStamp,z[i.id]=!0,t(!0)}))}))}}))},J=[800,1800],K=function e(n){document.prerendering?C((function(){return e(n)})):"complete"!==document.readyState?addEventListener("load",(function(){return e(n)}),!0):setTimeout(n,0)},Q=function(e,n){n=n||{};var t=f("TTFB"),r=d(e,t,J,n.reportAllChanges);K((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,J,n.reportAllChanges))(!0)})))}))},U={passive:!0,capture:!0},V=new Date,W=function(e,i){n||(n=i,t=e,r=new Date,Z(removeEventListener),X())},X=function(){if(t>=0&&t<r-V){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=[]}},Y=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(){W(e,n),i()},r=function(){i()},i=function(){removeEventListener("pointerup",t,U),removeEventListener("pointercancel",r,U)};addEventListener("pointerup",t,U),addEventListener("pointercancel",r,U)}(n,e):W(n,e)}},Z=function(e){["mousedown","keydown","touchstart","pointerdown"].forEach((function(n){return e(n,Y,U)}))},$=[100,300],ee=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,$,r.reportAllChanges),h&&(p(v((function(){m(h.takeRecords()),h.disconnect()}))),a((function(){var a;u=f("FID"),o=d(e,u,$,r.reportAllChanges),i=[],t=-1,n=null,Z(addEventListener),a=l,i.push(a),X()})))}))};export{L as CLSThresholds,b as FCPThresholds,$ as FIDThresholds,N as INPThresholds,_ as LCPThresholds,J as TTFBThresholds,w as onCLS,S as onFCP,ee as onFID,j as onINP,G as onLCP,Q as onTTFB}; | ||
var n,t,i,e,a,o=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))}},r=function(n){requestAnimationFrame((function(){return requestAnimationFrame((function(){return n()}))}))},c=-1,u=function(n){addEventListener("pageshow",(function(t){t.persisted&&(c=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},f=function(){var n=v();return n&&n.activationStart||0},d=function(n,t,i,e){var a,o=(null===(a=v())||void 0===a?void 0:a.navigationId)||"1",r=v(),u="navigate";i?u=i:c>=0?u="back-forward-cache":r&&(document.prerendering||f()>0?u="prerender":document.wasDiscarded?u="restore":r.type&&(u=r.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:u,navigationId:e||o}},s=function(n){return PerformanceObserver.supportedEntryTypes.includes("soft-navigation")&&n&&n.reportSoftNavs},g=function(n){if(n){var t=window.performance.getEntriesByType("soft-navigation").filter((function(t){return t.navigationId===n}));return t?t[0]:void 0}},l=function(n,t,i){var e=s(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){}},m=function(n){document.addEventListener("visibilitychange",(function(){"hidden"===document.visibilityState&&n()}))},p=function(n){var t=!1;return function(){t||(n(),t=!0)}},h=-1,I=function(){return"hidden"!==document.visibilityState||document.prerendering?1/0:0},T=function(n){"hidden"===document.visibilityState&&h>-1&&(h="visibilitychange"===n.type?n.timeStamp:0,E())},y=function(){addEventListener("visibilitychange",T,!0),addEventListener("prerenderingchange",T,!0)},E=function(){removeEventListener("visibilitychange",T,!0),removeEventListener("prerenderingchange",T,!0)},b=function(){return arguments.length>0&&void 0!==arguments[0]&&arguments[0]&&(h=-1),h<0&&(h=I(),y(),u((function(){setTimeout((function(){h=I(),y()}),0)}))),{get firstHiddenTime(){return h}}},C=function(n){document.prerendering?addEventListener("prerenderingchange",(function(){return n()}),!0):n()},w=[1800,3e3],S=function(n,t){var i,e=s(t=t||{}),a=0,c=(null===(i=v())||void 0===i?void 0:i.navigationId)||"1";C((function(){var i,v=b(),s=d("FCP"),m=l("paint",(function(r){r.forEach((function(r){if("first-contentful-paint"===r.name){e?r.navigationId&&r.navigationId!==s.navigationId&&function(e,r){if(s=d("FCP",0,e,r),i=o(n,s,w,t.reportAllChanges),"soft-navigation"===e){v=b(!0);var c=r?g(r):null;a=c&&c.startTime||0}}("soft-navigation",r.navigationId):m.disconnect();var u=0;if(r.navigationId&&r.navigationId!==c){var l=g(r.navigationId),p=l&&l.startTime?l.startTime:0;u=Math.max(r.startTime-p,0)}else u=Math.max(r.startTime-f(),0);var h=e&&r.navigationId?g(r.navigationId):null,I=h&&h.startTime?h.startTime:0;(r.startTime<v.firstHiddenTime||e&&r.navigationId&&r.navigationId!==s.navigationId&&r.navigationId!==c&&I>a)&&(s.value=u,s.entries.push(r),s.navigationId=r.navigationId||"1",i(!0))}}))}),t);m&&(i=o(n,s,w,t.reportAllChanges),u((function(e){s=d("FCP",0,"back-forward-cache",s.navigationId),i=o(n,s,w,t.reportAllChanges),r((function(){s.value=performance.now()-e.timeStamp,i(!0)}))})))}))},A=[.1,.25],L=function(n,t){var i=s(t=t||{}),e=!1,a=0;S(p((function(){var c,v=d("CLS",0),f=0,s=[],p=function(i,r){if(v=d("CLS",0,i,r),c=o(n,v,A,t.reportAllChanges),f=0,e=!1,"soft-navigation"===i){var u=g(r);a=u&&u.startTime||0}},h=function(n){n.forEach((function(n){if(i&&n.navigationId&&n.navigationId!==v.navigationId&&(f>v.value&&(v.value=f,v.entries=s),c(!0),p("soft-navigation",n.navigationId)),!n.hadRecentInput){var t=s[0],e=s[s.length-1];f&&n.startTime-e.startTime<1e3&&n.startTime-t.startTime<5e3?(f+=n.value,s.push(n)):(f=n.value,s=[n])}})),f>v.value&&(v.value=f,v.entries=s,c())},I=l("layout-shift",h,t);if(I){c=o(n,v,A,t.reportAllChanges),m((function(){h(I.takeRecords()),c(!0),e=!0})),u((function(){p("back-forward-cache",v.navigationId),r((function(){return c()}))}));i&&l("soft-navigation",(function(i){i.forEach((function(i){var r=i.navigationId,u=r?g(r):null;r&&r!==v.navigationId&&u&&(u.startTime||0)>a&&(e||c(!0),p("soft-navigation",i.navigationId),c=o(n,v,A,t.reportAllChanges))}))}),t),setTimeout(c,0)}})))},k=0,P=1/0,M=0,F="",D=!1,x=function(n){var t;F||(F=(null===(t=v())||void 0===t?void 0:t.navigationId)||"1"),n.forEach((function(n){n.interactionId&&(D&&n.navigationId&&n.navigationId!==F&&(F=n.navigationId,k=0,P=1/0,M=0),P=Math.min(P,n.interactionId),M=Math.max(M,n.interactionId),k=M?(M-P)/7+1:0)}))},O=function(){return n?k:performance.interactionCount||0},B=function(t){"interactionCount"in performance||n||(n=l("event",x,{type:"event",buffered:!0,durationThreshold:0,includeSoftNavigationObservations:D=t||!1}))},N=[],R=new Map,H=0,q=function(){H=O(),N.length=0,R.clear()},j=function(){var n=Math.min(N.length-1,Math.floor((O()-H)/50));return N[n]},_=[],z=function(n){if(_.forEach((function(t){return t(n)})),n.interactionId||"first-input"===n.entryType){var t=N[N.length-1],i=R.get(n.interactionId);if(i||N.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]};R.set(e.id,e),N.push(e)}N.sort((function(n,t){return t.latency-n.latency})),N.length>10&&N.splice(10).forEach((function(n){return R.delete(n.id)}))}}},G=function(n){var t=self.requestIdleCallback||self.setTimeout,i=-1;return n=p(n),"hidden"===document.visibilityState?n():(i=t(n),m(n)),i},J=[200,500],K=function(n,t){if("PerformanceEventTiming"in self&&"interactionId"in PerformanceEventTiming.prototype){var i=s(t=t||{}),e=!1,a=0;C((function(){var c;B(i);var v,f=d("INP"),s=function(i,r){if(q(),f=d("INP",0,i,r),v=o(n,f,J,t.reportAllChanges),e=!1,"soft-navigation"===i){var c=g(r);a=c&&c.startTime?c.startTime:0}},p=function(n){G((function(){var i;n.forEach(z),(i=j())&&(i.latency!==f.value||t&&t.reportAllChanges)&&(f.value=i.latency,f.entries=i.entries),v()}))},h=l("event",p,{durationThreshold:null!==(c=t.durationThreshold)&&void 0!==c?c:40,opts:t});if(v=o(n,f,J,t.reportAllChanges),h){h.observe({type:"first-input",buffered:!0,includeSoftNavigationObservations:i}),m((function(){p(h.takeRecords()),v(!0)})),u((function(){q(),s("back-forward-cache",f.navigationId),r((function(){return v()}))}));i&&l("soft-navigation",(function(i){i.forEach((function(i){var r=g(i.navigationId),c=r&&r.startTime?r.startTime:0;i.navigationId&&i.navigationId!==f.navigationId&&c>a&&(!e&&f.value>0&&v(!0),s("soft-navigation",i.navigationId),v=o(n,f,J,t.reportAllChanges))}))}),t)}}))}},Q=[2500,4e3],U=function(n,t){var i,e=!1,a=s(t=t||{}),c=0,p=(null===(i=v())||void 0===i?void 0:i.navigationId)||"1",h="";C((function(){var i,v=b(),s=d("LCP"),I=function(a,r){if(s=d("LCP",0,a,r),i=o(n,s,Q,t.reportAllChanges),e=!1,"soft-navigation"===a){v=b(!0);var u=g(r);c=u&&u.startTime?u.startTime:0}E()},T=function(n){n.forEach((function(n){if(n){a&&n.navigationId&&n.navigationId!==s.navigationId&&(e||i(!0),I("soft-navigation",n.navigationId));var t=0;if(n.navigationId&&n.navigationId!==p){var o=g(n.navigationId),r=o&&o.startTime?o.startTime:0;t=Math.max(n.startTime-r,0)}else t=Math.max(n.startTime-f(),0);n.startTime<v.firstHiddenTime&&(s.value=t,s.entries=[n],s.navigationId=n.navigationId||p,i())}}))},y=function(){C(),e||(T(S.takeRecords()),a||S.disconnect(),s.navigationId===h&&(e=!0,i(!0)))},E=function(){["keydown","click"].forEach((function(n){addEventListener(n,(function(){return w()}),!0)}))},C=function(){["keydown","click"].forEach((function(n){removeEventListener(n,(function(){return w()}),!0)}))},w=function(){h=s.navigationId,G(y)},S=l("largest-contentful-paint",T,t);if(S){i=o(n,s,Q,t.reportAllChanges),E(),m((function(){h=s.navigationId,y()})),u((function(n){I("back-forward-cache",s.navigationId),r((function(){s.value=performance.now()-n.timeStamp,e=!0,i(!0)}))}));a&&l("soft-navigation",(function(n){n.forEach((function(n){var t=n.navigationId?g(n.navigationId):null;n.navigationId&&n.navigationId!==s.navigationId&&t&&(t.startTime||0)>c&&(e||i(!0),I("soft-navigation",n.navigationId))}))}),t)}}))},V=[800,1800],W=function n(t){document.prerendering?C((function(){return n(t)})):"complete"!==document.readyState?addEventListener("load",(function(){return n(t)}),!0):setTimeout(t,0)},X=function(n,t){var i=s(t=t||{}),e=d("TTFB"),a=o(n,e,V,t.reportAllChanges);W((function(){var r=v();if(r){var c=r.responseStart;e.value=Math.max(c-f(),0),e.entries=[r],a(!0),u((function(){e=d("TTFB",0,"back-forward-cache",e.navigationId),(a=o(n,e,V,t.reportAllChanges))(!0)}));i&&l("soft-navigation",(function(i){i.forEach((function(i){i.navigationId&&((e=d("TTFB",0,"soft-navigation",i.navigationId)).entries=[i],(a=o(n,e,V,t.reportAllChanges))(!0))}))}),t)}}))},Y={passive:!0,capture:!0},Z=new Date,$=function(n,a){t||(t=a,i=n,e=new Date,en(removeEventListener),nn())},nn=function(){if(i>=0&&i<e-Z){var n={entryType:"first-input",name:t.type,target:t.target,cancelable:t.cancelable,startTime:t.timeStamp,processingStart:t.timeStamp+i};a.forEach((function(t){t([n])})),a=[]}},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],on=function(n,e){var r,c=s(e=e||{}),f=(null===(r=v())||void 0===r?void 0:r.navigationId)||"1";C((function(){var r,v=b(),s=d("FID"),g=function(t){t.forEach((function(t){var i,a;c?t.navigationId&&t.navigationId!==s.navigationId&&(i="soft-navigation",a=t.navigationId,"soft-navigation"===i&&(v=b(!0)),s=d("FID",0,i,a),r=o(n,s,an,e.reportAllChanges)):p.disconnect(),t.startTime<v.firstHiddenTime&&(s.value=t.processingStart-t.startTime,s.entries.push(t),s.navigationId=t.navigationId||f,r(!0))}))},p=l("first-input",g,e);r=o(n,s,an,e.reportAllChanges),p&&(m((function(){g(p.takeRecords()),c||p.disconnect()})),u((function(){var c;s=d("FID",0,"back-forward-cache",s.navigationId),r=o(n,s,an,e.reportAllChanges),a=[],i=-1,t=null,en(addEventListener),c=g,a.push(c),nn()})))}))};export{A as CLSThresholds,w as FCPThresholds,an as FIDThresholds,J as INPThresholds,Q as LCPThresholds,V as TTFBThresholds,L as onCLS,S as onFCP,on as onFID,K as onINP,U as onLCP,X as onTTFB}; |
{ | ||
"name": "web-vitals", | ||
"version": "4.2.2", | ||
"version": "4.2.3-soft-navs", | ||
"description": "Easily measure performance metrics in JavaScript", | ||
@@ -5,0 +5,0 @@ "type": "module", |
@@ -265,2 +265,63 @@ # `web-vitals` | ||
### Report metrics for soft navigations (experimental) | ||
_**Note:** this is experimental and subject to change._ | ||
Currently Core Web Vitals are only tracked for full page navigations, which can affect how [Single Page Applications](https://web.dev/vitals-spa-faq/) that use so called "soft navigations" to update the browser URL and history outside of the normal browser's handling of this. The Chrome team are experimenting with being able to [measure these soft navigations](https://github.com/WICG/soft-navigations) separately and report on Core Web Vitals separately for them. | ||
This experimental support allows sites to measure how their Core Web Vitals might be measured differently should this happen. | ||
At present a "soft navigation" is defined as happening after the following three things happen: | ||
- A user interaction occurs | ||
- The URL changes | ||
- Content is added to the DOM | ||
- Something is painted to screen. | ||
For some sites, these heuristics may lead to false positives (that users would not really consider a "navigation"), or false negatives (where the user does consider a navigation to have happened despite not missing the above criteria). We welcome feedback at https://github.com/WICG/soft-navigations/issues on the heuristics, at https://crbug.com for bugs in the Chrome implementation, and on [https://github.com/GoogleChrome/web-vitals/pull/308](this pull request) for implementation issues with web-vitals.js. | ||
_**Note:** At this time it is not known if this experiment will be something we want to move forward with. Until such time, this support will likely remain in a separate branch of this project, rather than be included in any production builds. If we decide not to move forward with this, the support of this will likely be removed from this project since this library is intended to mirror the Core Web Vitals as much as possible._ | ||
Some important points to note: | ||
- TTFB is reported as 0, and not the time of the first network call (if any) after the soft navigation. | ||
- FCP and LCP are the first and largest contentful paints after the soft navigation. Prior reported paint times will not be counted for these metrics, even though these elements may remain between soft navigations, and may be the first or largest contentful item. | ||
- 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 | ||
@@ -556,2 +617,3 @@ | ||
* restored by the user. | ||
* - 'soft-navigation': for soft navigations. | ||
*/ | ||
@@ -564,3 +626,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; | ||
} | ||
@@ -654,2 +723,3 @@ ``` | ||
durationThreshold?: number; | ||
reportSoftNavs?: boolean; | ||
} | ||
@@ -868,3 +938,3 @@ ``` | ||
* general page load issues. This can be used to access `serverTiming` for example: | ||
* navigationEntry?.serverTiming | ||
* navigationEntry.serverTiming | ||
*/ | ||
@@ -1039,3 +1109,3 @@ navigationEntry?: PerformanceNavigationTiming; | ||
* general page load issues. This can be used to access `serverTiming` for example: | ||
* navigationEntry?.serverTiming | ||
* navigationEntry.serverTiming | ||
*/ | ||
@@ -1090,3 +1160,3 @@ navigationEntry?: PerformanceNavigationTiming; | ||
* general page load issues. This can be used to access `serverTiming` for | ||
* example: navigationEntry?.serverTiming | ||
* example: navigationEntry.serverTiming | ||
*/ | ||
@@ -1093,0 +1163,0 @@ navigationEntry?: PerformanceNavigationTiming; |
@@ -20,2 +20,3 @@ /* | ||
import {getNavigationEntry} from '../lib/getNavigationEntry.js'; | ||
import {getSoftNavigationEntry} from '../lib/softNavs.js'; | ||
import {onFCP as unattributedOnFCP} from '../onFCP.js'; | ||
@@ -30,2 +31,3 @@ import { | ||
const attributeFCP = (metric: FCPMetric): FCPMetricWithAttribution => { | ||
const hardNavId = getNavigationEntry()?.navigationId || '1'; | ||
// Use a default object if no other attribution has been set. | ||
@@ -39,9 +41,22 @@ let attribution: FCPAttribution = { | ||
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 = { | ||
@@ -48,0 +63,0 @@ timeToFirstByte: ttfb, |
@@ -66,3 +66,3 @@ /* | ||
// The `processingEnd` time of most recently-processed event, chronologically. | ||
let latestProcessingEnd: number; | ||
let latestProcessingEnd: number = 0; | ||
@@ -192,3 +192,3 @@ // A WeakMap to look up the event-timing-entries group of a given entry. | ||
// 1) intersect with entries in the newly cleaned up `pendingEntriesGroups` | ||
// 2) occur after the most recently-processed event entry. | ||
// 2) occur after the most recently-processed event entry (for up to MAX_PREVIOUS_FRAMES) | ||
const loafsToKeep: Set<PerformanceLongAnimationFrameTiming> = new Set(); | ||
@@ -203,15 +203,12 @@ for (let i = 0; i < pendingEntriesGroups.length; i++) { | ||
} | ||
for (let i = 0; i < MAX_PREVIOUS_FRAMES; i++) { | ||
// Look at pending LoAF in reverse order so the most recent are first. | ||
const loaf = pendingLoAFs[pendingLoAFs.length - 1 - i]; | ||
const prevFrameIndexCutoff = pendingLoAFs.length - 1 - MAX_PREVIOUS_FRAMES; | ||
// Filter `pendingLoAFs` to preserve LoAF order. | ||
pendingLoAFs = pendingLoAFs.filter((loaf, index) => { | ||
if (loaf.startTime > latestProcessingEnd && index > prevFrameIndexCutoff) { | ||
return true; | ||
} | ||
// If we reach LoAFs that overlap with event processing, | ||
// we can assume all previous ones have already been handled. | ||
if (!loaf || loaf.startTime < latestProcessingEnd) break; | ||
return loafsToKeep.has(loaf); | ||
}); | ||
loafsToKeep.add(loaf); | ||
} | ||
pendingLoAFs = Array.from(loafsToKeep); | ||
// Reset the idle callback handle so it can be queued again. | ||
@@ -218,0 +215,0 @@ idleHandle = -1; |
@@ -18,2 +18,3 @@ /* | ||
import {getNavigationEntry} from '../lib/getNavigationEntry.js'; | ||
import {getSoftNavigationEntry} from '../lib/softNavs.js'; | ||
import {getSelector} from '../lib/getSelector.js'; | ||
@@ -29,2 +30,3 @@ import {onLCP as unattributedOnLCP} from '../onLCP.js'; | ||
const attributeLCP = (metric: LCPMetric): LCPMetricWithAttribution => { | ||
const hardNavId = getNavigationEntry()?.navigationId || '1'; | ||
// Use a default object if no other attribution has been set. | ||
@@ -39,5 +41,25 @@ let attribution: LCPAttribution = { | ||
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]; | ||
@@ -50,3 +72,3 @@ const lcpResourceEntry = | ||
const ttfb = Math.max(0, navigationEntry.responseStart - activationStart); | ||
const ttfb = Math.max(0, responseStart - activationStart); | ||
@@ -62,8 +84,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, | ||
); | ||
@@ -70,0 +94,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 |
@@ -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 |
@@ -26,9 +26,15 @@ /* | ||
value?: number, | ||
navigation?: MetricType['navigationType'], | ||
navigationId?: string, | ||
) => { | ||
const navEntry = getNavigationEntry(); | ||
const hardNavId = getNavigationEntry()?.navigationId || '1'; | ||
const hardNavEntry = getNavigationEntry(); | ||
let navigationType: MetricType['navigationType'] = 'navigate'; | ||
if (getBFCacheRestoreTime() >= 0) { | ||
if (navigation) { | ||
// If it was passed in, then use that | ||
navigationType = navigation; | ||
} else if (getBFCacheRestoreTime() >= 0) { | ||
navigationType = 'back-forward-cache'; | ||
} else if (navEntry) { | ||
} else if (hardNavEntry) { | ||
if (document.prerendering || getActivationStart() > 0) { | ||
@@ -38,4 +44,4 @@ navigationType = 'prerender'; | ||
navigationType = 'restore'; | ||
} else if (navEntry.type) { | ||
navigationType = navEntry.type.replace( | ||
} else if (hardNavEntry.type) { | ||
navigationType = hardNavEntry.type.replace( | ||
/_/g, | ||
@@ -58,3 +64,4 @@ '-', | ||
navigationType, | ||
navigationId: navigationId || hardNavId, | ||
}; | ||
}; |
@@ -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 {getNavigationEntry} from '../getNavigationEntry.js'; | ||
import {observe} from '../observe.js'; | ||
@@ -29,6 +30,19 @@ | ||
let maxKnownInteractionId = 0; | ||
let currentNavId = ''; | ||
let softNavsEnabled = false; | ||
const updateEstimate = (entries: PerformanceEventTiming[]) => { | ||
if (!currentNavId) currentNavId = getNavigationEntry()?.navigationId || '1'; | ||
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); | ||
@@ -57,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, { | ||
@@ -65,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 {getNavigationEntry} 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,51 @@ /** Thresholds for FCP. See https://web.dev/articles/fcp#what_is_a_good_fcp_score */ | ||
opts = opts || {}; | ||
const softNavsEnabled = softNavs(opts); | ||
let metricNavStartTime = 0; | ||
const hardNavId = getNavigationEntry()?.navigationId || '1'; | ||
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 +104,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 +143,3 @@ } | ||
const po = observe('paint', handleEntries); | ||
const po = observe('paint', handleEntries, opts); | ||
@@ -84,3 +157,8 @@ if (po) { | ||
onBFCacheRestore((event) => { | ||
metric = initMetric('FCP'); | ||
metric = initMetric( | ||
'FCP', | ||
0, | ||
'back-forward-cache', | ||
metric.navigationId, | ||
); | ||
report = bindReporter( | ||
@@ -87,0 +165,0 @@ onReport, |
@@ -20,2 +20,3 @@ /* | ||
import {getVisibilityWatcher} from './lib/getVisibilityWatcher.js'; | ||
import {getNavigationEntry} 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,49 @@ ReportOpts, | ||
opts = opts || {}; | ||
const softNavsEnabled = softNavs(opts); | ||
const hardNavId = getNavigationEntry()?.navigationId || '1'; | ||
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 +115,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 +137,3 @@ onReport, | ||
resetFirstInputPolyfill(); | ||
firstInputPolyfill(handleEntry as FirstInputPolyfillCallback); | ||
firstInputPolyfill(handleEntries as FirstInputPolyfillCallback); | ||
}); | ||
@@ -109,0 +140,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); | ||
} | ||
} | ||
}); | ||
}; |
210
src/onLCP.ts
@@ -22,9 +22,15 @@ /* | ||
import {getVisibilityWatcher} from './lib/getVisibilityWatcher.js'; | ||
import {getNavigationEntry} 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,77 @@ * 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; | ||
const hardNavId = getNavigationEntry()?.navigationId || '1'; | ||
let finalizeNavId = ''; | ||
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; | ||
} | ||
addInputListeners(); | ||
}; | ||
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 +137,55 @@ }); | ||
const po = observe('largest-contentful-paint', handleEntries); | ||
const finalizeLCPs = () => { | ||
removeInputListeners(); | ||
if (!reportedMetric) { | ||
handleEntries(po!.takeRecords() as LCPMetric['entries']); | ||
if (!softNavsEnabled) po!.disconnect(); | ||
// As the clicks are handled when idle, check if the current metric was | ||
// for the reported NavId and only if so, then report. | ||
if (metric.navigationId === finalizeNavId) { | ||
reportedMetric = true; | ||
report(true); | ||
} | ||
} | ||
}; | ||
const addInputListeners = () => { | ||
['keydown', 'click'].forEach((type) => { | ||
// 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 | ||
addEventListener(type, () => handleInput(), true); | ||
}); | ||
}; | ||
const removeInputListeners = () => { | ||
['keydown', 'click'].forEach((type) => { | ||
// Remove event listeners as no longer required | ||
removeEventListener(type, () => handleInput(), true); | ||
}); | ||
}; | ||
const handleInput = () => { | ||
// Since we only finalize whenIdle, we only want to finalize the LCPs | ||
// for the current navigationId at the time of the input and not any | ||
// others that came after, and before it was idle. So note the current | ||
// metric.navigationId. | ||
finalizeNavId = metric.navigationId; | ||
// 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 | ||
whenIdle(finalizeLCPs); | ||
}; | ||
const handleHidden = () => { | ||
// Finalise the current navigationId metric. | ||
finalizeNavId = metric.navigationId; | ||
// 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 | ||
finalizeLCPs(); | ||
}; | ||
const po = observe('largest-contentful-paint', handleEntries, opts); | ||
if (po) { | ||
@@ -95,42 +199,50 @@ report = bindReporter( | ||
const stopListening = runOnce(() => { | ||
if (!reportedMetricIDs[metric.id]) { | ||
handleEntries(po!.takeRecords() as LCPMetric['entries']); | ||
po!.disconnect(); | ||
reportedMetricIDs[metric.id] = true; | ||
report(true); | ||
} | ||
}); | ||
addInputListeners(); | ||
// 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(handleHidden); | ||
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'; | ||
@@ -65,2 +67,3 @@ | ||
opts = opts || {}; | ||
const softNavsEnabled = softNavs(opts); | ||
@@ -76,5 +79,5 @@ let metric = initMetric('TTFB'); | ||
whenReady(() => { | ||
const navigationEntry = getNavigationEntry(); | ||
if (navigationEntry) { | ||
const hardNavEntry = getNavigationEntry(); | ||
if (hardNavEntry) { | ||
const responseStart = hardNavEntry.responseStart; | ||
// The activationStart reference is used because TTFB should be | ||
@@ -84,8 +87,5 @@ // relative to page activation rather than navigation start if the | ||
// after the first byte is received, this time should be clamped at 0. | ||
metric.value = Math.max( | ||
navigationEntry.responseStart - getActivationStart(), | ||
0, | ||
); | ||
metric.value = Math.max(responseStart - getActivationStart(), 0); | ||
metric.entries = [navigationEntry]; | ||
metric.entries = [hardNavEntry]; | ||
report(true); | ||
@@ -96,3 +96,8 @@ | ||
onBFCacheRestore(() => { | ||
metric = initMetric('TTFB', 0); | ||
metric = initMetric( | ||
'TTFB', | ||
0, | ||
'back-forward-cache', | ||
metric.navigationId, | ||
); | ||
report = bindReporter( | ||
@@ -107,4 +112,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
456239
146
7567
1227
2