web-vitals
Version:
Easily measure performance metrics in JavaScript
1 lines • 137 kB
Source Map (JSON)
{"version":3,"file":"web-vitals.attribution.umd.cjs","sources":["modules/lib/LayoutShiftManager.js","modules/lib/getNavigationEntry.js","modules/lib/getLoadState.js","modules/lib/getSelector.js","modules/lib/initUnique.js","modules/lib/bfcache.js","modules/lib/bindReporter.js","modules/lib/doubleRAF.js","modules/lib/getActivationStart.js","modules/lib/getVisibilityWatcher.js","modules/lib/initMetric.js","modules/lib/generateUniqueID.js","modules/lib/observe.js","modules/lib/softNavs.js","modules/lib/runOnce.js","modules/lib/FCPEntryManager.js","modules/lib/whenActivated.js","modules/onFCP.js","modules/onCLS.js","modules/attribution/onCLS.js","modules/lib/polyfills/interactionCountPolyfill.js","modules/lib/InteractionManager.js","modules/lib/whenIdleOrHidden.js","modules/onINP.js","modules/lib/LCPEntryManager.js","modules/onLCP.js","modules/onTTFB.js","modules/attribution/onFCP.js","modules/attribution/onINP.js","modules/attribution/onLCP.js","modules/attribution/onTTFB.js"],"sourcesContent":["/*\n * Copyright 2024 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nexport class LayoutShiftManager {\n _onAfterProcessingUnexpectedShift;\n _sessionValue = 0;\n _sessionEntries = [];\n _processEntry(entry) {\n // Only count layout shifts without recent user input.\n if (entry.hadRecentInput)\n return;\n const firstSessionEntry = this._sessionEntries[0];\n const lastSessionEntry = this._sessionEntries.at(-1);\n // If the entry occurred less than 1 second after the previous entry\n // and less than 5 seconds after the first entry in the session,\n // include the entry in the current session. Otherwise, start a new\n // session.\n if (this._sessionValue &&\n firstSessionEntry &&\n lastSessionEntry &&\n entry.startTime - lastSessionEntry.startTime < 1000 &&\n entry.startTime - firstSessionEntry.startTime < 5000) {\n this._sessionValue += entry.value;\n this._sessionEntries.push(entry);\n }\n else {\n this._sessionValue = entry.value;\n this._sessionEntries = [entry];\n }\n this._onAfterProcessingUnexpectedShift?.(entry);\n }\n}\n//# sourceMappingURL=LayoutShiftManager.js.map","/*\n * Copyright 2022 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nexport const getNavigationEntry = () => {\n const navigationEntry = performance.getEntriesByType('navigation')[0];\n // Check to ensure the `responseStart` property is present and valid.\n // In some cases a zero value is reported by the browser (for\n // privacy/security reasons), and in other cases (bugs) the value is\n // negative or is larger than the current page time. Ignore these cases:\n // - https://github.com/GoogleChrome/web-vitals/issues/137\n // - https://github.com/GoogleChrome/web-vitals/issues/162\n // - https://github.com/GoogleChrome/web-vitals/issues/275\n if (navigationEntry &&\n navigationEntry.responseStart > 0 &&\n navigationEntry.responseStart < performance.now()) {\n return navigationEntry;\n }\n};\n//# sourceMappingURL=getNavigationEntry.js.map","/*\n * Copyright 2022 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { getNavigationEntry } from './getNavigationEntry.js';\nexport const getLoadState = (timestamp) => {\n if (document.readyState === 'loading') {\n // If the `readyState` is 'loading' there's no need to look at timestamps\n // since the timestamp has to be the current time or earlier.\n return 'loading';\n }\n const hardNavEntry = getNavigationEntry();\n if (hardNavEntry) {\n if (timestamp < hardNavEntry.domInteractive) {\n return 'loading';\n }\n else if (hardNavEntry.domContentLoadedEventStart === 0 ||\n timestamp < hardNavEntry.domContentLoadedEventStart) {\n // If the `domContentLoadedEventStart` timestamp has not yet been\n // set, or if the given timestamp is less than that value.\n return 'dom-interactive';\n }\n else if (hardNavEntry.domComplete === 0 ||\n timestamp < hardNavEntry.domComplete) {\n // If the `domComplete` timestamp has not yet been\n // set, or if the given timestamp is less than that value.\n return 'dom-content-loaded';\n }\n }\n // If any of the above fail, default to loaded. This could really only\n // happy if the browser doesn't support the performance timeline, which\n // most likely means this code would never run anyway.\n return 'complete';\n};\n//# sourceMappingURL=getLoadState.js.map","/*\n * Copyright 2022 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nconst getName = (node) => {\n const name = node.nodeName;\n return node.nodeType === 1\n ? name.toLowerCase()\n : name.toUpperCase().replace(/^#/, '');\n};\nconst MAX_LEN = 100;\nexport const getSelector = (node) => {\n let sel = '';\n try {\n while (node?.nodeType !== 9) {\n const el = node;\n const part = el.id\n ? '#' + el.id\n : [getName(el), ...Array.from(el.classList ?? []).sort()].join('.');\n if (sel.length + part.length > MAX_LEN - 1) {\n return sel || part;\n }\n sel = sel ? part + '>' + sel : part;\n if (el.id) {\n break;\n }\n node = el.parentNode;\n }\n }\n catch {\n // Do nothing...\n }\n return sel;\n};\n//# sourceMappingURL=getSelector.js.map","/*\n * Copyright 2024 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nconst instanceMap = new WeakMap();\n/**\n * A function that accepts and identity object and a class object and returns\n * either a new instance of that class or an existing instance, if the\n * identity object was previously used.\n */\nexport function initUnique(identityObj, ClassObj) {\n let classInstances = instanceMap.get(ClassObj);\n if (!classInstances) {\n classInstances = new WeakMap();\n instanceMap.set(ClassObj, classInstances);\n }\n if (!classInstances.get(identityObj)) {\n classInstances.set(identityObj, new ClassObj());\n }\n return classInstances.get(identityObj);\n}\n//# sourceMappingURL=initUnique.js.map","/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nlet bfcacheRestoreTime = -1;\nexport const getBFCacheRestoreTime = () => bfcacheRestoreTime;\nexport const onBFCacheRestore = (cb) => {\n addEventListener('pageshow', (event) => {\n if (event.persisted) {\n bfcacheRestoreTime = event.timeStamp;\n cb(event);\n }\n }, true);\n};\n//# sourceMappingURL=bfcache.js.map","/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nconst getRating = (value, thresholds) => {\n if (value > thresholds[1]) {\n return 'poor';\n }\n if (value > thresholds[0]) {\n return 'needs-improvement';\n }\n return 'good';\n};\nexport const bindReporter = (callback, metric, thresholds, reportAllChanges) => {\n let prevValue;\n let delta;\n return (forceReport) => {\n if (metric.value >= 0) {\n if (forceReport || reportAllChanges) {\n delta = metric.value - (prevValue ?? 0);\n // Report the metric if there's a non-zero delta or if no previous\n // value exists (which can happen in the case of the document becoming\n // hidden when the metric value is 0).\n // See: https://github.com/GoogleChrome/web-vitals/issues/14\n if (delta || prevValue === undefined) {\n prevValue = metric.value;\n metric.delta = delta;\n metric.rating = getRating(metric.value, thresholds);\n callback(metric);\n }\n }\n }\n };\n};\n//# sourceMappingURL=bindReporter.js.map","/*\n * Copyright 2022 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nexport const doubleRAF = (cb) => {\n requestAnimationFrame(() => requestAnimationFrame(cb));\n};\n//# sourceMappingURL=doubleRAF.js.map","/*\n * Copyright 2022 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { getNavigationEntry } from './getNavigationEntry.js';\nexport const getActivationStart = () => {\n return getNavigationEntry()?.activationStart ?? 0;\n};\n//# sourceMappingURL=getActivationStart.js.map","/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { onBFCacheRestore } from './bfcache.js';\nimport { getActivationStart } from './getActivationStart.js';\nlet firstHiddenTime = -1;\nconst onHiddenFunctions = new Set();\nconst initHiddenTime = () => {\n // If the document is hidden when this code runs, assume it was always\n // hidden and the page was loaded in the background, with the one exception\n // that visibility state is always 'hidden' during prerendering, so we have\n // to ignore that case until prerendering finishes (see: `prerenderingchange`\n // event logic below).\n return document.visibilityState === 'hidden' && !document.prerendering\n ? 0\n : Infinity;\n};\nconst onVisibilityUpdate = (event) => {\n // Handle changes to hidden state\n if (document.visibilityState === 'hidden') {\n if (event.type === 'visibilitychange') {\n for (const onHiddenFunction of onHiddenFunctions) {\n onHiddenFunction();\n }\n }\n // If the document is 'hidden' and no previous hidden timestamp has been\n // set (so is infinity), update it based on the current event data.\n if (!isFinite(firstHiddenTime)) {\n // If the event is a 'visibilitychange' event, it means the page was\n // visible prior to this change, so the event timestamp is the first\n // hidden time.\n // However, if the event is not a 'visibilitychange' event, then it must\n // be a 'prerenderingchange' event, and the fact that the document is\n // still 'hidden' from the above check means the tab was activated\n // in a background state and so has always been hidden.\n firstHiddenTime = event.type === 'visibilitychange' ? event.timeStamp : 0;\n // We no longer need the `prerenderingchange` event listener now we've\n // set an initial init time so remove that\n // (we'll keep the visibilitychange one for onHiddenFunction above)\n removeEventListener('prerenderingchange', onVisibilityUpdate, true);\n }\n }\n};\nexport const getVisibilityWatcher = (reset = false) => {\n if (reset) {\n firstHiddenTime = Infinity;\n }\n if (firstHiddenTime < 0) {\n // Check if we have a previous hidden `visibility-state` performance entry.\n const activationStart = getActivationStart();\n /* eslint-disable indent */\n const firstVisibilityStateHiddenTime = !document.prerendering\n ? globalThis.performance\n .getEntriesByType('visibility-state')\n .find((e) => e.name === 'hidden' && e.startTime >= activationStart)\n ?.startTime\n : undefined;\n /* eslint-enable indent */\n // Prefer that, but if it's not available and the document is hidden when\n // this code runs, assume it was hidden since navigation start. This isn't\n // a perfect heuristic, but it's the best we can do until the\n // `visibility-state` performance entry becomes available in all browsers.\n firstHiddenTime = firstVisibilityStateHiddenTime ?? initHiddenTime();\n // Listen for visibility changes so we can handle things like bfcache\n // restores and/or prerender without having to examine individual\n // timestamps in detail and also for onHidden function calls.\n addEventListener('visibilitychange', onVisibilityUpdate, true);\n // IMPORTANT: when a page is prerendering, its `visibilityState` is\n // 'hidden', so in order to account for cases where this module checks for\n // visibility during prerendering, an additional check after prerendering\n // completes is also required.\n addEventListener('prerenderingchange', onVisibilityUpdate, true);\n // Reset the time on bfcache restores.\n onBFCacheRestore(() => {\n // Schedule a task in order to track the `visibilityState` once it's\n // had an opportunity to change to visible in all browsers.\n // https://bugs.chromium.org/p/chromium/issues/detail?id=1133363\n setTimeout(() => {\n firstHiddenTime = initHiddenTime();\n });\n });\n }\n return {\n get firstHiddenTime() {\n return firstHiddenTime;\n },\n onHidden(cb) {\n onHiddenFunctions.add(cb);\n },\n };\n};\n//# sourceMappingURL=getVisibilityWatcher.js.map","/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { getBFCacheRestoreTime } from './bfcache.js';\nimport { generateUniqueID } from './generateUniqueID.js';\nimport { getActivationStart } from './getActivationStart.js';\nimport { getNavigationEntry } from './getNavigationEntry.js';\nexport const initMetric = (name, value = -1, navigationType, navigationId = 0, navigationInteractionId, navigationURL, navigationStartTime) => {\n const hardNavEntry = getNavigationEntry();\n const hardNavId = hardNavEntry?.navigationId || 0;\n let _navigationType = 'navigate';\n if (navigationType) {\n // If it was passed in, then use that\n _navigationType = navigationType;\n }\n else if (getBFCacheRestoreTime() >= 0) {\n _navigationType = 'back-forward-cache';\n }\n else if (hardNavEntry) {\n if (document.prerendering || getActivationStart() > 0) {\n _navigationType = 'prerender';\n }\n else if (document.wasDiscarded) {\n _navigationType = 'restore';\n }\n else if (hardNavEntry.type) {\n _navigationType = hardNavEntry.type.replace(/_/g, '-');\n }\n }\n // Use `entries` type specific for the metric.\n const entries = [];\n return {\n name,\n value,\n rating: 'good', // If needed, will be updated when reported. `const` to keep the type from widening to `string`.\n delta: 0,\n entries,\n id: generateUniqueID(),\n navigationType: _navigationType,\n navigationId: navigationId || hardNavId,\n navigationInteractionId: navigationInteractionId,\n navigationURL: navigationURL || hardNavEntry?.name,\n navigationStartTime: navigationStartTime || 0,\n };\n};\n//# sourceMappingURL=initMetric.js.map","/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n/**\n * Performantly generate a unique, 30-char string by combining a version\n * number, the current timestamp with a 13-digit number integer.\n * @return {string}\n */\nexport const generateUniqueID = () => {\n return `v6-${Date.now()}-${Math.floor(Math.random() * (9e12 - 1)) + 1e12}`;\n};\n//# sourceMappingURL=generateUniqueID.js.map","/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n/**\n * Takes a performance entry type and a callback function, and creates a\n * `PerformanceObserver` instance that will observe the specified entry type\n * with buffering enabled and call the callback _for each entry_.\n *\n * This function also feature-detects entry support and wraps the logic in a\n * try/catch to avoid errors in unsupporting browsers.\n */\nexport const observe = (types, callback, opts = {}) => {\n try {\n const supportedTypes = types.filter((t) => PerformanceObserver.supportedEntryTypes.includes(t));\n if (supportedTypes.length > 0) {\n const po = new PerformanceObserver((list) => {\n // Delay by a microtask to workaround a bug in Safari where the\n // callback is invoked immediately, rather than in a separate task.\n // See: https://github.com/GoogleChrome/web-vitals/issues/277\n queueMicrotask(() => {\n const entries = list.getEntries();\n // When observing more than one entry type, entries from different\n // types can be delivered out of order, so sort by end time\n // (startTime + duration) to ensure they're in the right order.\n // See: https://github.com/w3c/performance-timeline/issues/224\n if (supportedTypes.length > 1) {\n entries.sort((a, b) => {\n const scoreA = a.startTime + a.duration;\n const scoreB = b.startTime + b.duration;\n return scoreA - scoreB;\n });\n }\n callback(entries);\n });\n });\n for (const t of supportedTypes) {\n po.observe({ type: t, buffered: true, ...opts });\n }\n return po;\n }\n }\n catch {\n // Do nothing.\n }\n return;\n};\n//# sourceMappingURL=observe.js.map","/*\n * Copyright 2023 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nexport const checkSoftNavsEnabled = (opts) => {\n return (PerformanceObserver.supportedEntryTypes.includes('soft-navigation') &&\n // Older implementations expose the value as an attribute rather than the\n // method. We only support the newer method as that was what was launched\n // to stable unflagged.\n typeof globalThis.PerformanceSoftNavigation?.prototype\n ?.getLargestInteractionContentfulPaint === 'function' &&\n opts &&\n opts.reportSoftNavs);\n};\n// Stores a soft navigation entry keyed by its navigationId, keeping only\n// the 2 most recent entries so the map cannot grow unbounded.\nexport const storeSoftNavEntry = (map, entry) => {\n map.set(entry.navigationId, entry);\n // Clean up older entries to prevent memory leaks, keeping only\n // the 2 most recent entries.\n if (map.size > 2) {\n const firstKey = map.keys().next().value;\n if (firstKey !== undefined) {\n map.delete(firstKey);\n }\n }\n};\n//# sourceMappingURL=softNavs.js.map","/*\n * Copyright 2022 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nexport const runOnce = (cb) => {\n let called = false;\n return () => {\n if (!called) {\n cb();\n called = true;\n }\n };\n};\n//# sourceMappingURL=runOnce.js.map","/*\n * Copyright 2026 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nexport class FCPEntryManager {\n _softNavigationEntryMap;\n}\n//# sourceMappingURL=FCPEntryManager.js.map","/*\n * Copyright 2022 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nexport const whenActivated = (callback) => {\n if (document.prerendering) {\n addEventListener('prerenderingchange', callback, true);\n }\n else {\n callback();\n }\n};\n//# sourceMappingURL=whenActivated.js.map","/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { bindReporter } from './lib/bindReporter.js';\nimport { checkSoftNavsEnabled, storeSoftNavEntry } from './lib/softNavs.js';\nimport { doubleRAF } from './lib/doubleRAF.js';\nimport { getActivationStart } from './lib/getActivationStart.js';\nimport { getVisibilityWatcher } from './lib/getVisibilityWatcher.js';\nimport { initMetric } from './lib/initMetric.js';\nimport { initUnique } from './lib/initUnique.js';\nimport { FCPEntryManager } from './lib/FCPEntryManager.js';\nimport { observe } from './lib/observe.js';\nimport { getBFCacheRestoreTime, onBFCacheRestore } from './lib/bfcache.js';\nimport { whenActivated } from './lib/whenActivated.js';\n/** Thresholds for FCP. See https://web.dev/articles/fcp#what_is_a_good_fcp_score */\nexport const FCPThresholds = [1800, 3000];\n/**\n * Calculates the [FCP](https://web.dev/articles/fcp) value for the current page and\n * calls the `callback` function once the value is ready, along with the\n * relevant `paint` performance entry used to determine the value. The reported\n * value is a `DOMHighResTimeStamp`.\n */\nexport const onFCP = (onReport, opts = {}) => {\n const softNavsEnabled = checkSoftNavsEnabled(opts);\n whenActivated(() => {\n // Create a new FCP entry manager for each page activation\n // This allows us to track soft navigations separately\n // needed when attribution is enabled.\n const fcpEntryManager = initUnique(opts, FCPEntryManager);\n const visibilityWatcher = getVisibilityWatcher();\n let metric = initMetric('FCP');\n let report;\n const handleEntries = (entries) => {\n for (const entry of entries) {\n if (entry.name === 'first-contentful-paint') {\n po.disconnect();\n // Only report if the page wasn't hidden prior to FCP.\n if (entry.startTime < visibilityWatcher.firstHiddenTime) {\n // The activationStart reference is used because FCP should be\n // relative to page activation rather than navigation start if the\n // page was prerendered. But in cases where `activationStart` occurs\n // after the FCP, this time should be clamped at 0.\n metric.value = Math.max(entry.startTime - getActivationStart(), 0);\n metric.entries.push(entry);\n metric.navigationId = entry.navigationId || metric.navigationId;\n // FCP should only be reported once so can report right away\n report(true);\n }\n }\n }\n };\n const po = observe(['paint'], handleEntries);\n if (po) {\n report = bindReporter(onReport, metric, FCPThresholds, opts.reportAllChanges);\n // Only report after a bfcache restore if the `PerformanceObserver`\n // successfully registered or the `paint` entry exists.\n onBFCacheRestore((event) => {\n metric = initMetric('FCP', -1, 'back-forward-cache', metric.navigationId, metric.navigationInteractionId, metric.navigationURL, getBFCacheRestoreTime());\n report = bindReporter(onReport, metric, FCPThresholds, opts.reportAllChanges);\n doubleRAF(() => {\n metric.value = performance.now() - event.timeStamp;\n report(true);\n });\n });\n }\n if (softNavsEnabled) {\n // As first-contentful-paint is only reported once, we can handle soft\n // navigations afterwards on their own for simplicity, as no need to\n // observe both and sort the entries like for the other metrics\n const handleSoftNavEntries = (entries) => {\n entries.forEach((entry) => {\n // Store the soft navigation entries in the entry manager so that\n // they can be retrieved for attribution if necessary. This code\n // is only used when attribution is enabled which sets the\n // _softNavigationEntryMap.\n if (fcpEntryManager._softNavigationEntryMap && entry.navigationId) {\n storeSoftNavEntry(fcpEntryManager._softNavigationEntryMap, entry);\n }\n // Clamp FCP at 0. It should never be less, but better safe than sorry.\n const FCPTime = Math.max((entry.presentationTime || entry.paintTime || 0) - entry.startTime, 0);\n metric = initMetric('FCP', FCPTime, 'soft-navigation', entry.navigationId, entry.interactionId, entry.name, entry.startTime);\n report = bindReporter(onReport, metric, FCPThresholds, opts.reportAllChanges);\n report(true);\n });\n };\n observe(['soft-navigation'], handleSoftNavEntries, opts);\n }\n });\n};\n//# sourceMappingURL=onFCP.js.map","/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { getBFCacheRestoreTime, onBFCacheRestore } from './lib/bfcache.js';\nimport { bindReporter } from './lib/bindReporter.js';\nimport { doubleRAF } from './lib/doubleRAF.js';\nimport { getVisibilityWatcher } from './lib/getVisibilityWatcher.js';\nimport { initMetric } from './lib/initMetric.js';\nimport { initUnique } from './lib/initUnique.js';\nimport { LayoutShiftManager } from './lib/LayoutShiftManager.js';\nimport { observe } from './lib/observe.js';\nimport { checkSoftNavsEnabled } from './lib/softNavs.js';\nimport { runOnce } from './lib/runOnce.js';\nimport { onFCP } from './onFCP.js';\n/** Thresholds for CLS. See https://web.dev/articles/cls#what_is_a_good_cls_score */\nexport const CLSThresholds = [0.1, 0.25];\n/**\n * Calculates the [CLS](https://web.dev/articles/cls) value for the current page and\n * calls the `callback` function once the value is ready to be reported, along\n * with all `layout-shift` performance entries that were used in the metric\n * value calculation. The reported value is a `double` (corresponding to a\n * [layout shift score](https://web.dev/articles/cls#layout_shift_score)).\n *\n * If the `reportAllChanges` configuration option is set to `true`, the\n * `callback` function will be called as soon as the value is initially\n * determined as well as any time the value changes throughout the page\n * lifespan.\n *\n * _**Important:** CLS should be continually monitored for changes throughout\n * the entire lifespan of a page—including if the user returns to the page after\n * it's been hidden/backgrounded. However, since browsers often [will not fire\n * additional callbacks once the user has backgrounded a\n * page](https://developer.chrome.com/blog/page-lifecycle-api/#advice-hidden),\n * `callback` is always called when the page's visibility state changes to\n * hidden. As a result, the `callback` function might be called multiple times\n * during the same page load._\n */\nexport const onCLS = (onReport, opts = {}) => {\n const visibilityWatcher = getVisibilityWatcher();\n // Start monitoring FCP so we can only report CLS if FCP is also reported.\n // Note: this is done to match the current behavior of CrUX.\n onFCP(runOnce(() => {\n let metric = initMetric('CLS', 0);\n let report;\n const layoutShiftManager = initUnique(opts, LayoutShiftManager);\n const initNewCLSMetric = (navigationType, navigationId, navigationInteractionId, navigationURL, navigationStartTime) => {\n metric = initMetric('CLS', 0, navigationType, navigationId, navigationInteractionId, navigationURL, navigationStartTime);\n layoutShiftManager._sessionValue = 0;\n report = bindReporter(onReport, metric, CLSThresholds, opts.reportAllChanges);\n };\n const updateAndReportMetric = (forceReport = false) => {\n // If the current session value is larger than the current CLS value,\n // update CLS and the entries contributing to it.\n if (layoutShiftManager._sessionValue > metric.value) {\n metric.value = layoutShiftManager._sessionValue;\n metric.entries = layoutShiftManager._sessionEntries;\n }\n report(forceReport);\n };\n const handleSoftNavEntry = (entry) => {\n updateAndReportMetric(true);\n initNewCLSMetric('soft-navigation', entry.navigationId, entry.interactionId, entry.name, entry.startTime);\n };\n const handleEntries = (entries) => {\n for (const entry of entries) {\n if (entry.entryType === 'soft-navigation') {\n handleSoftNavEntry(entry);\n continue;\n }\n layoutShiftManager._processEntry(entry);\n }\n updateAndReportMetric();\n };\n const types = ['layout-shift'];\n if (checkSoftNavsEnabled(opts)) {\n types.push('soft-navigation');\n }\n const po = observe(types, handleEntries);\n if (po) {\n report = bindReporter(onReport, metric, CLSThresholds, opts.reportAllChanges);\n visibilityWatcher.onHidden(() => {\n handleEntries(po.takeRecords());\n report(true);\n });\n // Only report after a bfcache restore if the `PerformanceObserver`\n // successfully registered.\n onBFCacheRestore(() => {\n initNewCLSMetric('back-forward-cache', metric.navigationId, metric.navigationInteractionId, metric.navigationURL, getBFCacheRestoreTime());\n doubleRAF(report);\n });\n // Queue a task to report (if nothing else triggers a report first).\n // This allows CLS to be reported as soon as FCP fires when\n // `reportAllChanges` is true.\n setTimeout(report);\n }\n }));\n};\n//# sourceMappingURL=onCLS.js.map","/*\n * Copyright 2022 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { LayoutShiftManager } from '../lib/LayoutShiftManager.js';\nimport { getLoadState } from '../lib/getLoadState.js';\nimport { getSelector } from '../lib/getSelector.js';\nimport { initUnique } from '../lib/initUnique.js';\nimport { onCLS as unattributedOnCLS } from '../onCLS.js';\nconst getLargestLayoutShiftEntry = (entries) => {\n return entries.reduce((a, b) => (a.value > b.value ? a : b));\n};\nconst getLargestLayoutShiftSource = (sources) => {\n return sources.find((s) => s.node?.nodeType === 1) || sources[0];\n};\n/**\n * Calculates the [CLS](https://web.dev/articles/cls) value for the current page and\n * calls the `callback` function once the value is ready to be reported, along\n * with all `layout-shift` performance entries that were used in the metric\n * value calculation. The reported value is a `double` (corresponding to a\n * [layout shift score](https://web.dev/articles/cls#layout_shift_score)).\n *\n * If the `reportAllChanges` configuration option is set to `true`, the\n * `callback` function will be called as soon as the value is initially\n * determined as well as any time the value changes throughout the page\n * lifespan.\n *\n * _**Important:** CLS should be continually monitored for changes throughout\n * the entire lifespan of a page—including if the user returns to the page after\n * it's been hidden/backgrounded. However, since browsers often [will not fire\n * additional callbacks once the user has backgrounded a\n * page](https://developer.chrome.com/blog/page-lifecycle-api/#advice-hidden),\n * `callback` is always called when the page's visibility state changes to\n * hidden. As a result, the `callback` function might be called multiple times\n * during the same page load._\n */\nexport const onCLS = (onReport, opts = {}) => {\n // Clone the opts object to ensure it's unique, so we can initialize a\n // single instance of the `LayoutShiftManager` class that's shared only with\n // this function invocation and the `unattributedOnCLS()` invocation below\n // (which is passed the same `opts` object).\n opts = Object.assign({}, opts);\n const layoutShiftManager = initUnique(opts, LayoutShiftManager);\n const layoutShiftTargetMap = new WeakMap();\n layoutShiftManager._onAfterProcessingUnexpectedShift = (entry) => {\n if (entry?.sources?.length) {\n const largestSource = getLargestLayoutShiftSource(entry.sources);\n const node = largestSource?.node;\n if (node) {\n const customTarget = opts.generateTarget?.(node) ?? getSelector(node);\n layoutShiftTargetMap.set(largestSource, customTarget);\n }\n }\n };\n const attributeCLS = (metric) => {\n // Use an empty object if no other attribution has been set.\n let attribution = {};\n if (metric.entries.length) {\n const largestEntry = getLargestLayoutShiftEntry(metric.entries);\n if (largestEntry?.sources?.length) {\n const largestSource = getLargestLayoutShiftSource(largestEntry.sources);\n if (largestSource) {\n attribution = {\n largestShiftTarget: layoutShiftTargetMap.get(largestSource),\n largestShiftTime: largestEntry.startTime,\n largestShiftValue: largestEntry.value,\n largestShiftSource: largestSource,\n largestShiftEntry: largestEntry,\n loadState: getLoadState(largestEntry.startTime),\n };\n }\n }\n }\n // Use `Object.assign()` to ensure the original metric object is returned.\n return Object.assign(metric, { attribution });\n };\n unattributedOnCLS((metric) => {\n onReport(attributeCLS(metric));\n }, opts);\n};\n//# sourceMappingURL=onCLS.js.map","/*\n * Copyright 2022 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { observe } from '../observe.js';\nlet interactionCountEstimate = 0;\nlet minKnownInteractionId = Infinity;\nlet maxKnownInteractionId = 0;\nconst updateEstimate = (entries) => {\n for (const entry of entries) {\n if (entry.interactionId) {\n minKnownInteractionId = Math.min(minKnownInteractionId, entry.interactionId);\n maxKnownInteractionId = Math.max(maxKnownInteractionId, entry.interactionId);\n interactionCountEstimate = maxKnownInteractionId\n ? (maxKnownInteractionId - minKnownInteractionId) / 7 + 1\n : 0;\n }\n }\n};\nlet po;\n/**\n * Returns the `interactionCount` value using the native API (if available)\n * or the polyfill estimate in this module.\n */\nexport const getInteractionCount = () => {\n return po ? interactionCountEstimate : (performance.interactionCount ?? 0);\n};\n/**\n * Feature detects native support or initializes the polyfill if needed.\n */\nexport const initInteractionCountPolyfill = () => {\n if ('interactionCount' in performance || po)\n return;\n po = observe(['event'], updateEstimate, {\n durationThreshold: 0,\n });\n};\n//# sourceMappingURL=interactionCountPolyfill.js.map","/*\n * Copyright 2024 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { getInteractionCount } from './polyfills/interactionCountPolyfill.js';\n// To prevent unnecessary memory usage on pages with lots of interactions,\n// store at most 10 of the longest interactions to consider as INP candidates.\nconst MAX_INTERACTIONS_TO_CONSIDER = 10;\n// Used to store the interaction count after a bfcache restore, since p98\n// interaction latencies should only consider the current navigation.\nlet prevInteractionCount = 0;\n/**\n * Returns the interaction count since the last bfcache restore (or for the\n * full page lifecycle if there were no bfcache restores).\n */\nconst getInteractionCountForNavigation = () => {\n return getInteractionCount() - prevInteractionCount;\n};\nexport class InteractionManager {\n /**\n * A list of longest interactions on the page (by latency) sorted so the\n * longest one is first. The list is at most MAX_INTERACTIONS_TO_CONSIDER\n * long.\n */\n _longestInteractionList = [];\n /**\n * A mapping of longest interactions by their interaction ID.\n * This is used for faster lookup.\n */\n _longestInteractionMap = new Map();\n _onBeforeProcessingEntry;\n _onAfterProcessingINPCandidate;\n _resetInteractions() {\n prevInteractionCount = getInteractionCount();\n this._longestInteractionList.length = 0;\n this._longestInteractionMap.clear();\n }\n /**\n * Returns the estimated p98 longest interaction based on the stored\n * interaction candidates and the interaction count for the current page.\n */\n _estimateP98LongestInteraction(navigationType) {\n const interactionCountForNavigation = getInteractionCountForNavigation();\n const candidateInteractionIndex = Math.min(this._longestInteractionList.length - 1, Math.floor(interactionCountForNavigation / 50));\n // If we have a non-zero interactionCountForNavigation but no\n // candidateInteractionIndex, then it's below the 16ms limit\n // so report a dummy 8ms interaction. This is only needed for\n // soft-navs and bfcache restores as `first-input` handles the\n // rest.\n if (interactionCountForNavigation &&\n candidateInteractionIndex === -1 &&\n (navigationType === 'soft-navigation' ||\n navigationType === 'back-forward-cache')) {\n return {\n _latency: 8,\n id: -1,\n entries: [],\n };\n }\n return this._longestInteractionList[candidateInteractionIndex];\n }\n /**\n * Takes a performance entry and adds it to the list of worst interactions\n * if its duration is long enough to make it among the worst. If the\n * entry is part of an existing interaction, it is merged and the latency\n * and entries list is updated as needed.\n */\n _processEntry(entry) {\n this._onBeforeProcessingEntry?.(entry);\n // Skip further processing for entries that cannot be INP candidates.\n if (!(entry.interactionId || entry.entryType