diff options
Diffstat (limited to 'frontend-old/node_modules/web-vitals/src/lib')
19 files changed, 1021 insertions, 0 deletions
diff --git a/frontend-old/node_modules/web-vitals/src/lib/bfcache.ts b/frontend-old/node_modules/web-vitals/src/lib/bfcache.ts new file mode 100644 index 0000000..647b731 --- /dev/null +++ b/frontend-old/node_modules/web-vitals/src/lib/bfcache.ts @@ -0,0 +1,36 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +interface onBFCacheRestoreCallback { + (event: PageTransitionEvent): void; +} + +let bfcacheRestoreTime = -1; + +export const getBFCacheRestoreTime = () => bfcacheRestoreTime; + +export const onBFCacheRestore = (cb: onBFCacheRestoreCallback) => { + addEventListener( + 'pageshow', + (event) => { + if (event.persisted) { + bfcacheRestoreTime = event.timeStamp; + cb(event); + } + }, + true, + ); +}; diff --git a/frontend-old/node_modules/web-vitals/src/lib/bindReporter.ts b/frontend-old/node_modules/web-vitals/src/lib/bindReporter.ts new file mode 100644 index 0000000..fa73db3 --- /dev/null +++ b/frontend-old/node_modules/web-vitals/src/lib/bindReporter.ts @@ -0,0 +1,58 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {MetricType, MetricRatingThresholds} from '../types.js'; + +const getRating = ( + value: number, + thresholds: MetricRatingThresholds, +): MetricType['rating'] => { + if (value > thresholds[1]) { + return 'poor'; + } + if (value > thresholds[0]) { + return 'needs-improvement'; + } + return 'good'; +}; + +export const bindReporter = <MetricName extends MetricType['name']>( + callback: (metric: Extract<MetricType, {name: MetricName}>) => void, + metric: Extract<MetricType, {name: MetricName}>, + thresholds: MetricRatingThresholds, + reportAllChanges?: boolean, +) => { + let prevValue: number; + let delta: number; + return (forceReport?: boolean) => { + if (metric.value >= 0) { + if (forceReport || reportAllChanges) { + delta = metric.value - (prevValue || 0); + + // Report the metric if there's a non-zero delta or if no previous + // value exists (which can happen in the case of the document becoming + // hidden when the metric value is 0). + // See: https://github.com/GoogleChrome/web-vitals/issues/14 + if (delta || prevValue === undefined) { + prevValue = metric.value; + metric.delta = delta; + metric.rating = getRating(metric.value, thresholds); + callback(metric); + } + } + } + }; +}; diff --git a/frontend-old/node_modules/web-vitals/src/lib/doubleRAF.ts b/frontend-old/node_modules/web-vitals/src/lib/doubleRAF.ts new file mode 100644 index 0000000..c76d142 --- /dev/null +++ b/frontend-old/node_modules/web-vitals/src/lib/doubleRAF.ts @@ -0,0 +1,19 @@ +/* + * Copyright 2022 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export const doubleRAF = (cb: () => unknown) => { + requestAnimationFrame(() => requestAnimationFrame(() => cb())); +}; diff --git a/frontend-old/node_modules/web-vitals/src/lib/generateUniqueID.ts b/frontend-old/node_modules/web-vitals/src/lib/generateUniqueID.ts new file mode 100644 index 0000000..637d013 --- /dev/null +++ b/frontend-old/node_modules/web-vitals/src/lib/generateUniqueID.ts @@ -0,0 +1,24 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Performantly generate a unique, 30-char string by combining a version + * number, the current timestamp with a 13-digit number integer. + * @return {string} + */ +export const generateUniqueID = () => { + return `v4-${Date.now()}-${Math.floor(Math.random() * (9e12 - 1)) + 1e12}`; +}; diff --git a/frontend-old/node_modules/web-vitals/src/lib/getActivationStart.ts b/frontend-old/node_modules/web-vitals/src/lib/getActivationStart.ts new file mode 100644 index 0000000..3991c0e --- /dev/null +++ b/frontend-old/node_modules/web-vitals/src/lib/getActivationStart.ts @@ -0,0 +1,22 @@ +/* + * Copyright 2022 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {getNavigationEntry} from './getNavigationEntry.js'; + +export const getActivationStart = (): number => { + const navEntry = getNavigationEntry(); + return (navEntry && navEntry.activationStart) || 0; +}; diff --git a/frontend-old/node_modules/web-vitals/src/lib/getLoadState.ts b/frontend-old/node_modules/web-vitals/src/lib/getLoadState.ts new file mode 100644 index 0000000..788db0f --- /dev/null +++ b/frontend-old/node_modules/web-vitals/src/lib/getLoadState.ts @@ -0,0 +1,51 @@ +/* + * Copyright 2022 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {getNavigationEntry} from './getNavigationEntry.js'; +import {LoadState} from '../types.js'; + +export const getLoadState = (timestamp: number): LoadState => { + if (document.readyState === 'loading') { + // If the `readyState` is 'loading' there's no need to look at timestamps + // since the timestamp has to be the current time or earlier. + return 'loading'; + } else { + const navigationEntry = getNavigationEntry(); + if (navigationEntry) { + if (timestamp < navigationEntry.domInteractive) { + return 'loading'; + } else if ( + navigationEntry.domContentLoadedEventStart === 0 || + timestamp < navigationEntry.domContentLoadedEventStart + ) { + // If the `domContentLoadedEventStart` timestamp has not yet been + // set, or if the given timestamp is less than that value. + return 'dom-interactive'; + } else if ( + navigationEntry.domComplete === 0 || + timestamp < navigationEntry.domComplete + ) { + // If the `domComplete` timestamp has not yet been + // set, or if the given timestamp is less than that value. + return 'dom-content-loaded'; + } + } + } + // If any of the above fail, default to loaded. This could really only + // happy if the browser doesn't support the performance timeline, which + // most likely means this code would never run anyway. + return 'complete'; +}; diff --git a/frontend-old/node_modules/web-vitals/src/lib/getNavigationEntry.ts b/frontend-old/node_modules/web-vitals/src/lib/getNavigationEntry.ts new file mode 100644 index 0000000..19d18cf --- /dev/null +++ b/frontend-old/node_modules/web-vitals/src/lib/getNavigationEntry.ts @@ -0,0 +1,37 @@ +/* + * Copyright 2022 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export const getNavigationEntry = (): PerformanceNavigationTiming | void => { + const navigationEntry = + self.performance && + performance.getEntriesByType && + performance.getEntriesByType('navigation')[0]; + + // Check to ensure the `responseStart` property is present and valid. + // In some cases no value is reported by the browser (for + // privacy/security reasons), and in other cases (bugs) the value is + // negative or is larger than the current page time. Ignore these cases: + // https://github.com/GoogleChrome/web-vitals/issues/137 + // https://github.com/GoogleChrome/web-vitals/issues/162 + // https://github.com/GoogleChrome/web-vitals/issues/275 + if ( + navigationEntry && + navigationEntry.responseStart > 0 && + navigationEntry.responseStart < performance.now() + ) { + return navigationEntry; + } +}; diff --git a/frontend-old/node_modules/web-vitals/src/lib/getSelector.ts b/frontend-old/node_modules/web-vitals/src/lib/getSelector.ts new file mode 100644 index 0000000..4ca0bb9 --- /dev/null +++ b/frontend-old/node_modules/web-vitals/src/lib/getSelector.ts @@ -0,0 +1,48 @@ +/* + * Copyright 2022 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const getName = (node: Node) => { + const name = node.nodeName; + return node.nodeType === 1 + ? name.toLowerCase() + : name.toUpperCase().replace(/^#/, ''); +}; + +export const getSelector = (node: Node | null | undefined, maxLen?: number) => { + let sel = ''; + + try { + while (node && node.nodeType !== 9) { + const el: Element = node as Element; + const part = el.id + ? '#' + el.id + : getName(el) + + (el.classList && + el.classList.value && + el.classList.value.trim() && + el.classList.value.trim().length + ? '.' + el.classList.value.trim().replace(/\s+/g, '.') + : ''); + if (sel.length + part.length > (maxLen || 100) - 1) return sel || part; + sel = sel ? part + '>' + sel : part; + if (el.id) break; + node = el.parentNode; + } + } catch (err) { + // Do nothing... + } + return sel; +}; diff --git a/frontend-old/node_modules/web-vitals/src/lib/getVisibilityWatcher.ts b/frontend-old/node_modules/web-vitals/src/lib/getVisibilityWatcher.ts new file mode 100644 index 0000000..ad7b1c3 --- /dev/null +++ b/frontend-old/node_modules/web-vitals/src/lib/getVisibilityWatcher.ts @@ -0,0 +1,89 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {onBFCacheRestore} from './bfcache.js'; + +let firstHiddenTime = -1; + +const initHiddenTime = () => { + // If the document is hidden when this code runs, assume it was always + // hidden and the page was loaded in the background, with the one exception + // that visibility state is always 'hidden' during prerendering, so we have + // to ignore that case until prerendering finishes (see: `prerenderingchange` + // event logic below). + return document.visibilityState === 'hidden' && !document.prerendering + ? 0 + : Infinity; +}; + +const onVisibilityUpdate = (event: Event) => { + // If the document is 'hidden' and no previous hidden timestamp has been + // set, update it based on the current event data. + if (document.visibilityState === 'hidden' && firstHiddenTime > -1) { + // If the event is a 'visibilitychange' event, it means the page was + // visible prior to this change, so the event timestamp is the first + // hidden time. + // However, if the event is not a 'visibilitychange' event, then it must + // be a 'prerenderingchange' event, and the fact that the document is + // still 'hidden' from the above check means the tab was activated + // in a background state and so has always been hidden. + firstHiddenTime = event.type === 'visibilitychange' ? event.timeStamp : 0; + + // Remove all listeners now that a `firstHiddenTime` value has been set. + removeChangeListeners(); + } +}; + +const addChangeListeners = () => { + addEventListener('visibilitychange', onVisibilityUpdate, true); + // IMPORTANT: when a page is prerendering, its `visibilityState` is + // 'hidden', so in order to account for cases where this module checks for + // visibility during prerendering, an additional check after prerendering + // completes is also required. + addEventListener('prerenderingchange', onVisibilityUpdate, true); +}; + +const removeChangeListeners = () => { + removeEventListener('visibilitychange', onVisibilityUpdate, true); + removeEventListener('prerenderingchange', onVisibilityUpdate, true); +}; + +export const getVisibilityWatcher = () => { + if (firstHiddenTime < 0) { + // If the document is hidden when this code runs, assume it was hidden + // since navigation start. This isn't a perfect heuristic, but it's the + // best we can do until an API is available to support querying past + // visibilityState. + firstHiddenTime = initHiddenTime(); + addChangeListeners(); + + // Reset the time on bfcache restores. + onBFCacheRestore(() => { + // Schedule a task in order to track the `visibilityState` once it's + // had an opportunity to change to visible in all browsers. + // https://bugs.chromium.org/p/chromium/issues/detail?id=1133363 + setTimeout(() => { + firstHiddenTime = initHiddenTime(); + addChangeListeners(); + }, 0); + }); + } + return { + get firstHiddenTime() { + return firstHiddenTime; + }, + }; +}; diff --git a/frontend-old/node_modules/web-vitals/src/lib/initMetric.ts b/frontend-old/node_modules/web-vitals/src/lib/initMetric.ts new file mode 100644 index 0000000..90618d8 --- /dev/null +++ b/frontend-old/node_modules/web-vitals/src/lib/initMetric.ts @@ -0,0 +1,57 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {getBFCacheRestoreTime} from './bfcache.js'; +import {generateUniqueID} from './generateUniqueID.js'; +import {getActivationStart} from './getActivationStart.js'; +import {getNavigationEntry} from './getNavigationEntry.js'; +import {MetricType} from '../types.js'; + +export const initMetric = <MetricName extends MetricType['name']>( + name: MetricName, + value?: number, +) => { + const navEntry = getNavigationEntry(); + let navigationType: MetricType['navigationType'] = 'navigate'; + + if (getBFCacheRestoreTime() >= 0) { + navigationType = 'back-forward-cache'; + } else if (navEntry) { + if (document.prerendering || getActivationStart() > 0) { + navigationType = 'prerender'; + } else if (document.wasDiscarded) { + navigationType = 'restore'; + } else if (navEntry.type) { + navigationType = navEntry.type.replace( + /_/g, + '-', + ) as MetricType['navigationType']; + } + } + + // Use `entries` type specific for the metric. + const entries: Extract<MetricType, {name: MetricName}>['entries'] = []; + + return { + name, + value: typeof value === 'undefined' ? -1 : value, + rating: 'good' as const, // If needed, will be updated when reported. `const` to keep the type from widening to `string`. + delta: 0, + entries, + id: generateUniqueID(), + navigationType, + }; +}; diff --git a/frontend-old/node_modules/web-vitals/src/lib/interactions.ts b/frontend-old/node_modules/web-vitals/src/lib/interactions.ts new file mode 100644 index 0000000..fbd0751 --- /dev/null +++ b/frontend-old/node_modules/web-vitals/src/lib/interactions.ts @@ -0,0 +1,139 @@ +/* + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {getInteractionCount} from './polyfills/interactionCountPolyfill.js'; + +interface Interaction { + id: number; + latency: number; + entries: PerformanceEventTiming[]; +} + +interface EntryPreProcessingHook { + (entry: PerformanceEventTiming): void; +} + +// A list of longest interactions on the page (by latency) sorted so the +// longest one is first. The list is at most MAX_INTERACTIONS_TO_CONSIDER long. +export const longestInteractionList: Interaction[] = []; + +// A mapping of longest interactions by their interaction ID. +// This is used for faster lookup. +export const longestInteractionMap: Map<number, Interaction> = new Map(); + +// The default `durationThreshold` used across this library for observing +// `event` entries via PerformanceObserver. +export const DEFAULT_DURATION_THRESHOLD = 40; + +// Used to store the interaction count after a bfcache restore, since p98 +// interaction latencies should only consider the current navigation. +let prevInteractionCount = 0; + +/** + * Returns the interaction count since the last bfcache restore (or for the + * full page lifecycle if there were no bfcache restores). + */ +const getInteractionCountForNavigation = () => { + return getInteractionCount() - prevInteractionCount; +}; + +export const resetInteractions = () => { + prevInteractionCount = getInteractionCount(); + longestInteractionList.length = 0; + longestInteractionMap.clear(); +}; + +/** + * Returns the estimated p98 longest interaction based on the stored + * interaction candidates and the interaction count for the current page. + */ +export const estimateP98LongestInteraction = () => { + const candidateInteractionIndex = Math.min( + longestInteractionList.length - 1, + Math.floor(getInteractionCountForNavigation() / 50), + ); + + return longestInteractionList[candidateInteractionIndex]; +}; + +// To prevent unnecessary memory usage on pages with lots of interactions, +// store at most 10 of the longest interactions to consider as INP candidates. +const MAX_INTERACTIONS_TO_CONSIDER = 10; + +/** + * A list of callback functions to run before each entry is processed. + * Exposing this list allows the attribution build to hook into the + * entry processing pipeline. + */ +export const entryPreProcessingCallbacks: EntryPreProcessingHook[] = []; + +/** + * Takes a performance entry and adds it to the list of worst interactions + * if its duration is long enough to make it among the worst. If the + * entry is part of an existing interaction, it is merged and the latency + * and entries list is updated as needed. + */ +export const processInteractionEntry = (entry: PerformanceEventTiming) => { + entryPreProcessingCallbacks.forEach((cb) => cb(entry)); + + // Skip further processing for entries that cannot be INP candidates. + if (!(entry.interactionId || entry.entryType === 'first-input')) return; + + // The least-long of the 10 longest interactions. + const minLongestInteraction = + longestInteractionList[longestInteractionList.length - 1]; + + const existingInteraction = longestInteractionMap.get(entry.interactionId!); + + // Only process the entry if it's possibly one of the ten longest, + // or if it's part of an existing interaction. + if ( + existingInteraction || + longestInteractionList.length < MAX_INTERACTIONS_TO_CONSIDER || + entry.duration > minLongestInteraction.latency + ) { + // If the interaction already exists, update it. Otherwise create one. + if (existingInteraction) { + // If the new entry has a longer duration, replace the old entries, + // otherwise add to the array. + if (entry.duration > existingInteraction.latency) { + existingInteraction.entries = [entry]; + existingInteraction.latency = entry.duration; + } else if ( + entry.duration === existingInteraction.latency && + entry.startTime === existingInteraction.entries[0].startTime + ) { + existingInteraction.entries.push(entry); + } + } else { + const interaction = { + id: entry.interactionId!, + latency: entry.duration, + entries: [entry], + }; + longestInteractionMap.set(interaction.id, interaction); + longestInteractionList.push(interaction); + } + + // Sort the entries by latency (descending) and keep only the top ten. + longestInteractionList.sort((a, b) => b.latency - a.latency); + if (longestInteractionList.length > MAX_INTERACTIONS_TO_CONSIDER) { + longestInteractionList + .splice(MAX_INTERACTIONS_TO_CONSIDER) + .forEach((i) => longestInteractionMap.delete(i.id)); + } + } +}; diff --git a/frontend-old/node_modules/web-vitals/src/lib/observe.ts b/frontend-old/node_modules/web-vitals/src/lib/observe.ts new file mode 100644 index 0000000..f3127a3 --- /dev/null +++ b/frontend-old/node_modules/web-vitals/src/lib/observe.ts @@ -0,0 +1,66 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +interface PerformanceEntryMap { + 'event': PerformanceEventTiming[]; + 'first-input': PerformanceEventTiming[]; + 'layout-shift': LayoutShift[]; + 'largest-contentful-paint': LargestContentfulPaint[]; + 'long-animation-frame': PerformanceLongAnimationFrameTiming[]; + 'paint': PerformancePaintTiming[]; + 'navigation': PerformanceNavigationTiming[]; + 'resource': PerformanceResourceTiming[]; +} + +/** + * Takes a performance entry type and a callback function, and creates a + * `PerformanceObserver` instance that will observe the specified entry type + * with buffering enabled and call the callback _for each entry_. + * + * This function also feature-detects entry support and wraps the logic in a + * try/catch to avoid errors in unsupporting browsers. + */ +export const observe = <K extends keyof PerformanceEntryMap>( + type: K, + callback: (entries: PerformanceEntryMap[K]) => void, + opts?: PerformanceObserverInit, +): PerformanceObserver | undefined => { + try { + if (PerformanceObserver.supportedEntryTypes.includes(type)) { + const po = new PerformanceObserver((list) => { + // Delay by a microtask to workaround a bug in Safari where the + // callback is invoked immediately, rather than in a separate task. + // See: https://github.com/GoogleChrome/web-vitals/issues/277 + Promise.resolve().then(() => { + callback(list.getEntries() as PerformanceEntryMap[K]); + }); + }); + po.observe( + Object.assign( + { + type, + buffered: true, + }, + opts || {}, + ) as PerformanceObserverInit, + ); + return po; + } + } catch (e) { + // Do nothing. + } + return; +}; diff --git a/frontend-old/node_modules/web-vitals/src/lib/onHidden.ts b/frontend-old/node_modules/web-vitals/src/lib/onHidden.ts new file mode 100644 index 0000000..f59d4c9 --- /dev/null +++ b/frontend-old/node_modules/web-vitals/src/lib/onHidden.ts @@ -0,0 +1,23 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export const onHidden = (cb: () => void) => { + document.addEventListener('visibilitychange', () => { + if (document.visibilityState === 'hidden') { + cb(); + } + }); +}; diff --git a/frontend-old/node_modules/web-vitals/src/lib/polyfills/firstInputPolyfill.ts b/frontend-old/node_modules/web-vitals/src/lib/polyfills/firstInputPolyfill.ts new file mode 100644 index 0000000..804c423 --- /dev/null +++ b/frontend-old/node_modules/web-vitals/src/lib/polyfills/firstInputPolyfill.ts @@ -0,0 +1,174 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + FirstInputPolyfillEntry, + FirstInputPolyfillCallback, +} from '../../types.js'; + +type addOrRemoveEventListener = + | typeof addEventListener + | typeof removeEventListener; + +let firstInputEvent: Event | null; +let firstInputDelay: number; +let firstInputTimeStamp: Date; +let callbacks: FirstInputPolyfillCallback[]; + +const listenerOpts: AddEventListenerOptions = {passive: true, capture: true}; +const startTimeStamp: Date = new Date(); + +/** + * Accepts a callback to be invoked once the first input delay and event + * are known. + */ +export const firstInputPolyfill = ( + onFirstInput: FirstInputPolyfillCallback, +) => { + callbacks.push(onFirstInput); + reportFirstInputDelayIfRecordedAndValid(); +}; + +export const resetFirstInputPolyfill = () => { + callbacks = []; + firstInputDelay = -1; + firstInputEvent = null; + eachEventType(addEventListener); +}; + +/** + * Records the first input delay and event, so subsequent events can be + * ignored. All added event listeners are then removed. + */ +const recordFirstInputDelay = (delay: number, event: Event) => { + if (!firstInputEvent) { + firstInputEvent = event; + firstInputDelay = delay; + firstInputTimeStamp = new Date(); + + eachEventType(removeEventListener); + reportFirstInputDelayIfRecordedAndValid(); + } +}; + +/** + * Reports the first input delay and event (if they're recorded and valid) + * by running the array of callback functions. + */ +const reportFirstInputDelayIfRecordedAndValid = () => { + // In some cases the recorded delay is clearly wrong, e.g. it's negative + // or it's larger than the delta between now and initialization. + // - https://github.com/GoogleChromeLabs/first-input-delay/issues/4 + // - https://github.com/GoogleChromeLabs/first-input-delay/issues/6 + // - https://github.com/GoogleChromeLabs/first-input-delay/issues/7 + if ( + firstInputDelay >= 0 && + // @ts-ignore (subtracting two dates always returns a number) + firstInputDelay < firstInputTimeStamp - startTimeStamp + ) { + const entry = { + entryType: 'first-input', + name: firstInputEvent!.type, + target: firstInputEvent!.target, + cancelable: firstInputEvent!.cancelable, + startTime: firstInputEvent!.timeStamp, + processingStart: firstInputEvent!.timeStamp + firstInputDelay, + } as FirstInputPolyfillEntry; + callbacks.forEach(function (callback) { + callback(entry); + }); + callbacks = []; + } +}; + +/** + * Handles pointer down events, which are a special case. + * Pointer events can trigger main or compositor thread behavior. + * We differentiate these cases based on whether or not we see a + * 'pointercancel' event, which are fired when we scroll. If we're scrolling + * we don't need to report input delay since FID excludes scrolling and + * pinch/zooming. + */ +const onPointerDown = (delay: number, event: Event) => { + /** + * Responds to 'pointerup' events and records a delay. If a pointer up event + * is the next event after a pointerdown event, then it's not a scroll or + * a pinch/zoom. + */ + const onPointerUp = () => { + recordFirstInputDelay(delay, event); + removePointerEventListeners(); + }; + + /** + * Responds to 'pointercancel' events and removes pointer listeners. + * If a 'pointercancel' is the next event to fire after a pointerdown event, + * it means this is a scroll or pinch/zoom interaction. + */ + const onPointerCancel = () => { + removePointerEventListeners(); + }; + + /** + * Removes added pointer event listeners. + */ + const removePointerEventListeners = () => { + removeEventListener('pointerup', onPointerUp, listenerOpts); + removeEventListener('pointercancel', onPointerCancel, listenerOpts); + }; + + addEventListener('pointerup', onPointerUp, listenerOpts); + addEventListener('pointercancel', onPointerCancel, listenerOpts); +}; + +/** + * Handles all input events and records the time between when the event + * was received by the operating system and when it's JavaScript listeners + * were able to run. + */ +const onInput = (event: Event) => { + // Only count cancelable events, which should trigger behavior + // important to the user. + if (event.cancelable) { + // In some browsers `event.timeStamp` returns a `DOMTimeStamp` value + // (epoch time) instead of the newer `DOMHighResTimeStamp` + // (document-origin time). To check for that we assume any timestamp + // greater than 1 trillion is a `DOMTimeStamp`, and compare it using + // the `Date` object rather than `performance.now()`. + // - https://github.com/GoogleChromeLabs/first-input-delay/issues/4 + const isEpochTime = event.timeStamp > 1e12; + const now = isEpochTime ? new Date() : performance.now(); + + // Input delay is the delta between when the system received the event + // (e.g. event.timeStamp) and when it could run the callback (e.g. `now`). + const delay = (now as number) - event.timeStamp; + + if (event.type == 'pointerdown') { + onPointerDown(delay, event); + } else { + recordFirstInputDelay(delay, event); + } + } +}; + +/** + * Invokes the passed callback const for = each event type with t =>he + * `onInput` const and = `listenerOpts =>`. + */ +const eachEventType = (callback: addOrRemoveEventListener) => { + const eventTypes = ['mousedown', 'keydown', 'touchstart', 'pointerdown']; + eventTypes.forEach((type) => callback(type, onInput, listenerOpts)); +}; diff --git a/frontend-old/node_modules/web-vitals/src/lib/polyfills/getFirstHiddenTimePolyfill.ts b/frontend-old/node_modules/web-vitals/src/lib/polyfills/getFirstHiddenTimePolyfill.ts new file mode 100644 index 0000000..9d7f26a --- /dev/null +++ b/frontend-old/node_modules/web-vitals/src/lib/polyfills/getFirstHiddenTimePolyfill.ts @@ -0,0 +1,29 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +let firstHiddenTime = document.visibilityState === 'hidden' ? 0 : Infinity; + +const onVisibilityChange = (event: Event) => { + if (document.visibilityState === 'hidden') { + firstHiddenTime = event.timeStamp; + removeEventListener('visibilitychange', onVisibilityChange, true); + } +}; + +// Note: do not add event listeners unconditionally (outside of polyfills). +addEventListener('visibilitychange', onVisibilityChange, true); + +export const getFirstHiddenTime = () => firstHiddenTime; diff --git a/frontend-old/node_modules/web-vitals/src/lib/polyfills/interactionCountPolyfill.ts b/frontend-old/node_modules/web-vitals/src/lib/polyfills/interactionCountPolyfill.ts new file mode 100644 index 0000000..e363376 --- /dev/null +++ b/frontend-old/node_modules/web-vitals/src/lib/polyfills/interactionCountPolyfill.ts @@ -0,0 +1,63 @@ +/* + * Copyright 2022 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {observe} from '../observe.js'; + +declare global { + interface Performance { + interactionCount: number; + } +} + +let interactionCountEstimate = 0; +let minKnownInteractionId = Infinity; +let maxKnownInteractionId = 0; + +const updateEstimate = (entries: PerformanceEventTiming[]) => { + entries.forEach((e) => { + if (e.interactionId) { + minKnownInteractionId = Math.min(minKnownInteractionId, e.interactionId); + maxKnownInteractionId = Math.max(maxKnownInteractionId, e.interactionId); + + interactionCountEstimate = maxKnownInteractionId + ? (maxKnownInteractionId - minKnownInteractionId) / 7 + 1 + : 0; + } + }); +}; + +let po: PerformanceObserver | undefined; + +/** + * Returns the `interactionCount` value using the native API (if available) + * or the polyfill estimate in this module. + */ +export const getInteractionCount = () => { + return po ? interactionCountEstimate : performance.interactionCount || 0; +}; + +/** + * Feature detects native support or initializes the polyfill if needed. + */ +export const initInteractionCountPolyfill = () => { + if ('interactionCount' in performance || po) return; + + po = observe('event', updateEstimate, { + type: 'event', + buffered: true, + durationThreshold: 0, + } as PerformanceObserverInit); +}; diff --git a/frontend-old/node_modules/web-vitals/src/lib/runOnce.ts b/frontend-old/node_modules/web-vitals/src/lib/runOnce.ts new file mode 100644 index 0000000..f2de2ea --- /dev/null +++ b/frontend-old/node_modules/web-vitals/src/lib/runOnce.ts @@ -0,0 +1,25 @@ +/* + * Copyright 2022 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export const runOnce = (cb: () => void) => { + let called = false; + return () => { + if (!called) { + cb(); + called = true; + } + }; +}; diff --git a/frontend-old/node_modules/web-vitals/src/lib/whenActivated.ts b/frontend-old/node_modules/web-vitals/src/lib/whenActivated.ts new file mode 100644 index 0000000..e4046c9 --- /dev/null +++ b/frontend-old/node_modules/web-vitals/src/lib/whenActivated.ts @@ -0,0 +1,23 @@ +/* + * Copyright 2022 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export const whenActivated = (callback: () => void) => { + if (document.prerendering) { + addEventListener('prerenderingchange', () => callback(), true); + } else { + callback(); + } +}; diff --git a/frontend-old/node_modules/web-vitals/src/lib/whenIdle.ts b/frontend-old/node_modules/web-vitals/src/lib/whenIdle.ts new file mode 100644 index 0000000..9abb30c --- /dev/null +++ b/frontend-old/node_modules/web-vitals/src/lib/whenIdle.ts @@ -0,0 +1,38 @@ +/* + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {onHidden} from './onHidden.js'; +import {runOnce} from './runOnce.js'; + +/** + * Runs the passed callback during the next idle period, or immediately + * if the browser's visibility state is (or becomes) hidden. + */ +export const whenIdle = (cb: () => void): number => { + const rIC = self.requestIdleCallback || self.setTimeout; + + let handle = -1; + cb = runOnce(cb); + // If the document is hidden, run the callback immediately, otherwise + // race an idle callback with the next `visibilitychange` event. + if (document.visibilityState === 'hidden') { + cb(); + } else { + handle = rIC(cb); + onHidden(cb); + } + return handle; +}; |
