diff options
Diffstat (limited to 'frontend-old/node_modules/web-vitals/src/lib/polyfills')
3 files changed, 266 insertions, 0 deletions
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); +}; |
