diff options
| author | altaf-creator <dev@altafcreator.com> | 2025-11-09 11:15:19 +0800 |
|---|---|---|
| committer | altaf-creator <dev@altafcreator.com> | 2025-11-09 11:15:19 +0800 |
| commit | 8eff962cab608341a6f2fedc640a0e32d96f26e2 (patch) | |
| tree | 05534d1a720ddc3691d346c69b4972555820a061 /frontend-old/node_modules/web-vitals/dist/modules/attribution | |
pain
Diffstat (limited to 'frontend-old/node_modules/web-vitals/dist/modules/attribution')
16 files changed, 762 insertions, 0 deletions
diff --git a/frontend-old/node_modules/web-vitals/dist/modules/attribution/deprecated.d.ts b/frontend-old/node_modules/web-vitals/dist/modules/attribution/deprecated.d.ts new file mode 100644 index 0000000..e29b6dc --- /dev/null +++ b/frontend-old/node_modules/web-vitals/dist/modules/attribution/deprecated.d.ts @@ -0,0 +1,7 @@ +export { +/** + * @deprecated Use `onINP()` instead. + */ +onFID, } from './onFID.js'; +export { FIDThresholds } from '../onFID.js'; +export * from '../types.js'; diff --git a/frontend-old/node_modules/web-vitals/dist/modules/attribution/deprecated.js b/frontend-old/node_modules/web-vitals/dist/modules/attribution/deprecated.js new file mode 100644 index 0000000..2132ab4 --- /dev/null +++ b/frontend-old/node_modules/web-vitals/dist/modules/attribution/deprecated.js @@ -0,0 +1,22 @@ +/* + * 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. + */ +export { +/** + * @deprecated Use `onINP()` instead. + */ +onFID, } from './onFID.js'; +export { FIDThresholds } from '../onFID.js'; +export * from '../types.js'; diff --git a/frontend-old/node_modules/web-vitals/dist/modules/attribution/index.d.ts b/frontend-old/node_modules/web-vitals/dist/modules/attribution/index.d.ts new file mode 100644 index 0000000..8513e3f --- /dev/null +++ b/frontend-old/node_modules/web-vitals/dist/modules/attribution/index.d.ts @@ -0,0 +1,12 @@ +export { onCLS } from './onCLS.js'; +export { onFCP } from './onFCP.js'; +export { onINP } from './onINP.js'; +export { onLCP } from './onLCP.js'; +export { onTTFB } from './onTTFB.js'; +export { CLSThresholds } from '../onCLS.js'; +export { FCPThresholds } from '../onFCP.js'; +export { INPThresholds } from '../onINP.js'; +export { LCPThresholds } from '../onLCP.js'; +export { TTFBThresholds } from '../onTTFB.js'; +export * from './deprecated.js'; +export * from '../types.js'; diff --git a/frontend-old/node_modules/web-vitals/dist/modules/attribution/index.js b/frontend-old/node_modules/web-vitals/dist/modules/attribution/index.js new file mode 100644 index 0000000..03702fc --- /dev/null +++ b/frontend-old/node_modules/web-vitals/dist/modules/attribution/index.js @@ -0,0 +1,27 @@ +/* + * 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 { onCLS } from './onCLS.js'; +export { onFCP } from './onFCP.js'; +export { onINP } from './onINP.js'; +export { onLCP } from './onLCP.js'; +export { onTTFB } from './onTTFB.js'; +export { CLSThresholds } from '../onCLS.js'; +export { FCPThresholds } from '../onFCP.js'; +export { INPThresholds } from '../onINP.js'; +export { LCPThresholds } from '../onLCP.js'; +export { TTFBThresholds } from '../onTTFB.js'; +export * from './deprecated.js'; +export * from '../types.js'; diff --git a/frontend-old/node_modules/web-vitals/dist/modules/attribution/onCLS.d.ts b/frontend-old/node_modules/web-vitals/dist/modules/attribution/onCLS.d.ts new file mode 100644 index 0000000..65e1ca9 --- /dev/null +++ b/frontend-old/node_modules/web-vitals/dist/modules/attribution/onCLS.d.ts @@ -0,0 +1,23 @@ +import { CLSMetricWithAttribution, ReportOpts } from '../types.js'; +/** + * Calculates the [CLS](https://web.dev/articles/cls) value for the current page and + * calls the `callback` function once the value is ready to be reported, along + * with all `layout-shift` performance entries that were used in the metric + * value calculation. The reported value is a `double` (corresponding to a + * [layout shift score](https://web.dev/articles/cls#layout_shift_score)). + * + * If the `reportAllChanges` configuration option is set to `true`, the + * `callback` function will be called as soon as the value is initially + * determined as well as any time the value changes throughout the page + * lifespan. + * + * _**Important:** CLS should be continually monitored for changes throughout + * the entire lifespan of a page—including if the user returns to the page after + * it's been hidden/backgrounded. However, since browsers often [will not fire + * additional callbacks once the user has backgrounded a + * page](https://developer.chrome.com/blog/page-lifecycle-api/#advice-hidden), + * `callback` is always called when the page's visibility state changes to + * hidden. As a result, the `callback` function might be called multiple times + * during the same page load._ + */ +export declare const onCLS: (onReport: (metric: CLSMetricWithAttribution) => void, opts?: ReportOpts) => void; diff --git a/frontend-old/node_modules/web-vitals/dist/modules/attribution/onCLS.js b/frontend-old/node_modules/web-vitals/dist/modules/attribution/onCLS.js new file mode 100644 index 0000000..78221fa --- /dev/null +++ b/frontend-old/node_modules/web-vitals/dist/modules/attribution/onCLS.js @@ -0,0 +1,74 @@ +/* + * 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 { getLoadState } from '../lib/getLoadState.js'; +import { getSelector } from '../lib/getSelector.js'; +import { onCLS as unattributedOnCLS } from '../onCLS.js'; +const getLargestLayoutShiftEntry = (entries) => { + return entries.reduce((a, b) => (a && a.value > b.value ? a : b)); +}; +const getLargestLayoutShiftSource = (sources) => { + return sources.find((s) => s.node && s.node.nodeType === 1) || sources[0]; +}; +const attributeCLS = (metric) => { + // Use an empty object if no other attribution has been set. + let attribution = {}; + if (metric.entries.length) { + const largestEntry = getLargestLayoutShiftEntry(metric.entries); + if (largestEntry && largestEntry.sources && largestEntry.sources.length) { + const largestSource = getLargestLayoutShiftSource(largestEntry.sources); + if (largestSource) { + attribution = { + largestShiftTarget: getSelector(largestSource.node), + largestShiftTime: largestEntry.startTime, + largestShiftValue: largestEntry.value, + largestShiftSource: largestSource, + largestShiftEntry: largestEntry, + loadState: getLoadState(largestEntry.startTime), + }; + } + } + } + // Use Object.assign to set property to keep tsc happy. + const metricWithAttribution = Object.assign(metric, { attribution }); + return metricWithAttribution; +}; +/** + * Calculates the [CLS](https://web.dev/articles/cls) value for the current page and + * calls the `callback` function once the value is ready to be reported, along + * with all `layout-shift` performance entries that were used in the metric + * value calculation. The reported value is a `double` (corresponding to a + * [layout shift score](https://web.dev/articles/cls#layout_shift_score)). + * + * If the `reportAllChanges` configuration option is set to `true`, the + * `callback` function will be called as soon as the value is initially + * determined as well as any time the value changes throughout the page + * lifespan. + * + * _**Important:** CLS should be continually monitored for changes throughout + * the entire lifespan of a page—including if the user returns to the page after + * it's been hidden/backgrounded. However, since browsers often [will not fire + * additional callbacks once the user has backgrounded a + * page](https://developer.chrome.com/blog/page-lifecycle-api/#advice-hidden), + * `callback` is always called when the page's visibility state changes to + * hidden. As a result, the `callback` function might be called multiple times + * during the same page load._ + */ +export const onCLS = (onReport, opts) => { + unattributedOnCLS((metric) => { + const metricWithAttribution = attributeCLS(metric); + onReport(metricWithAttribution); + }, opts); +}; diff --git a/frontend-old/node_modules/web-vitals/dist/modules/attribution/onFCP.d.ts b/frontend-old/node_modules/web-vitals/dist/modules/attribution/onFCP.d.ts new file mode 100644 index 0000000..b0b7f37 --- /dev/null +++ b/frontend-old/node_modules/web-vitals/dist/modules/attribution/onFCP.d.ts @@ -0,0 +1,8 @@ +import { FCPMetricWithAttribution, ReportOpts } from '../types.js'; +/** + * Calculates the [FCP](https://web.dev/articles/fcp) value for the current page and + * calls the `callback` function once the value is ready, along with the + * relevant `paint` performance entry used to determine the value. The reported + * value is a `DOMHighResTimeStamp`. + */ +export declare const onFCP: (onReport: (metric: FCPMetricWithAttribution) => void, opts?: ReportOpts) => void; diff --git a/frontend-old/node_modules/web-vitals/dist/modules/attribution/onFCP.js b/frontend-old/node_modules/web-vitals/dist/modules/attribution/onFCP.js new file mode 100644 index 0000000..06e87f6 --- /dev/null +++ b/frontend-old/node_modules/web-vitals/dist/modules/attribution/onFCP.js @@ -0,0 +1,57 @@ +/* + * 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 { getBFCacheRestoreTime } from '../lib/bfcache.js'; +import { getLoadState } from '../lib/getLoadState.js'; +import { getNavigationEntry } from '../lib/getNavigationEntry.js'; +import { onFCP as unattributedOnFCP } from '../onFCP.js'; +const attributeFCP = (metric) => { + // Use a default object if no other attribution has been set. + let attribution = { + timeToFirstByte: 0, + firstByteToFCP: metric.value, + loadState: getLoadState(getBFCacheRestoreTime()), + }; + if (metric.entries.length) { + const navigationEntry = getNavigationEntry(); + const fcpEntry = metric.entries[metric.entries.length - 1]; + if (navigationEntry) { + const activationStart = navigationEntry.activationStart || 0; + const ttfb = Math.max(0, navigationEntry.responseStart - activationStart); + attribution = { + timeToFirstByte: ttfb, + firstByteToFCP: metric.value - ttfb, + loadState: getLoadState(metric.entries[0].startTime), + navigationEntry, + fcpEntry, + }; + } + } + // Use Object.assign to set property to keep tsc happy. + const metricWithAttribution = Object.assign(metric, { attribution }); + return metricWithAttribution; +}; +/** + * Calculates the [FCP](https://web.dev/articles/fcp) value for the current page and + * calls the `callback` function once the value is ready, along with the + * relevant `paint` performance entry used to determine the value. The reported + * value is a `DOMHighResTimeStamp`. + */ +export const onFCP = (onReport, opts) => { + unattributedOnFCP((metric) => { + const metricWithAttribution = attributeFCP(metric); + onReport(metricWithAttribution); + }, opts); +}; diff --git a/frontend-old/node_modules/web-vitals/dist/modules/attribution/onFID.d.ts b/frontend-old/node_modules/web-vitals/dist/modules/attribution/onFID.d.ts new file mode 100644 index 0000000..b910f15 --- /dev/null +++ b/frontend-old/node_modules/web-vitals/dist/modules/attribution/onFID.d.ts @@ -0,0 +1,11 @@ +import { FIDMetricWithAttribution, ReportOpts } from '../types.js'; +/** + * Calculates the [FID](https://web.dev/articles/fid) value for the current page and + * calls the `callback` function once the value is ready, along with the + * relevant `first-input` performance entry used to determine the value. The + * reported value is a `DOMHighResTimeStamp`. + * + * _**Important:** since FID is only reported after the user interacts with the + * page, it's possible that it will not be reported for some page loads._ + */ +export declare const onFID: (onReport: (metric: FIDMetricWithAttribution) => void, opts?: ReportOpts) => void; diff --git a/frontend-old/node_modules/web-vitals/dist/modules/attribution/onFID.js b/frontend-old/node_modules/web-vitals/dist/modules/attribution/onFID.js new file mode 100644 index 0000000..52f1ddc --- /dev/null +++ b/frontend-old/node_modules/web-vitals/dist/modules/attribution/onFID.js @@ -0,0 +1,46 @@ +/* + * 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 { getLoadState } from '../lib/getLoadState.js'; +import { getSelector } from '../lib/getSelector.js'; +import { onFID as unattributedOnFID } from '../onFID.js'; +const attributeFID = (metric) => { + const fidEntry = metric.entries[0]; + const attribution = { + eventTarget: getSelector(fidEntry.target), + eventType: fidEntry.name, + eventTime: fidEntry.startTime, + eventEntry: fidEntry, + loadState: getLoadState(fidEntry.startTime), + }; + // Use Object.assign to set property to keep tsc happy. + const metricWithAttribution = Object.assign(metric, { attribution }); + return metricWithAttribution; +}; +/** + * Calculates the [FID](https://web.dev/articles/fid) value for the current page and + * calls the `callback` function once the value is ready, along with the + * relevant `first-input` performance entry used to determine the value. The + * reported value is a `DOMHighResTimeStamp`. + * + * _**Important:** since FID is only reported after the user interacts with the + * page, it's possible that it will not be reported for some page loads._ + */ +export const onFID = (onReport, opts) => { + unattributedOnFID((metric) => { + const metricWithAttribution = attributeFID(metric); + onReport(metricWithAttribution); + }, opts); +}; diff --git a/frontend-old/node_modules/web-vitals/dist/modules/attribution/onINP.d.ts b/frontend-old/node_modules/web-vitals/dist/modules/attribution/onINP.d.ts new file mode 100644 index 0000000..217faa0 --- /dev/null +++ b/frontend-old/node_modules/web-vitals/dist/modules/attribution/onINP.d.ts @@ -0,0 +1,30 @@ +import { INPMetricWithAttribution, ReportOpts } from '../types.js'; +export declare const interactionTargetMap: Map<number, Node>; +/** + * Calculates the [INP](https://web.dev/articles/inp) value for the current + * page and calls the `callback` function once the value is ready, along with + * the `event` performance entries reported for that interaction. The reported + * value is a `DOMHighResTimeStamp`. + * + * A custom `durationThreshold` configuration option can optionally be passed to + * control what `event-timing` entries are considered for INP reporting. The + * default threshold is `40`, which means INP scores of less than 40 are + * reported as 0. Note that this will not affect your 75th percentile INP value + * unless that value is also less than 40 (well below the recommended + * [good](https://web.dev/articles/inp#what_is_a_good_inp_score) threshold). + * + * If the `reportAllChanges` configuration option is set to `true`, the + * `callback` function will be called as soon as the value is initially + * determined as well as any time the value changes throughout the page + * lifespan. + * + * _**Important:** INP should be continually monitored for changes throughout + * the entire lifespan of a page—including if the user returns to the page after + * it's been hidden/backgrounded. However, since browsers often [will not fire + * additional callbacks once the user has backgrounded a + * page](https://developer.chrome.com/blog/page-lifecycle-api/#advice-hidden), + * `callback` is always called when the page's visibility state changes to + * hidden. As a result, the `callback` function might be called multiple times + * during the same page load._ + */ +export declare const onINP: (onReport: (metric: INPMetricWithAttribution) => void, opts?: ReportOpts) => void; diff --git a/frontend-old/node_modules/web-vitals/dist/modules/attribution/onINP.js b/frontend-old/node_modules/web-vitals/dist/modules/attribution/onINP.js new file mode 100644 index 0000000..51df8e3 --- /dev/null +++ b/frontend-old/node_modules/web-vitals/dist/modules/attribution/onINP.js @@ -0,0 +1,256 @@ +/* + * 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 { getLoadState } from '../lib/getLoadState.js'; +import { getSelector } from '../lib/getSelector.js'; +import { longestInteractionList, entryPreProcessingCallbacks, longestInteractionMap, } from '../lib/interactions.js'; +import { observe } from '../lib/observe.js'; +import { whenIdle } from '../lib/whenIdle.js'; +import { onINP as unattributedOnINP } from '../onINP.js'; +// The maximum number of previous frames for which data is kept. +// Storing data about previous frames is necessary to handle cases where event +// and LoAF entries are dispatched out of order, and so a buffer of previous +// frame data is needed to determine various bits of INP attribution once all +// the frame-related data has come in. +// In most cases this out-of-order data is only off by a frame or two, so +// keeping the most recent 50 should be more than sufficient. +const MAX_PREVIOUS_FRAMES = 50; +// A PerformanceObserver, observing new `long-animation-frame` entries. +// If this variable is defined it means the browser supports LoAF. +let loafObserver; +// A list of LoAF entries that have been dispatched and could potentially +// intersect with the INP candidate interaction. Note that periodically this +// list is cleaned up and entries that are known to not match INP are removed. +let pendingLoAFs = []; +// An array of groups of all the event timing entries that occurred within a +// particular frame. Note that periodically this array is cleaned up and entries +// that are known to not match INP are removed. +let pendingEntriesGroups = []; +// The `processingEnd` time of most recently-processed event, chronologically. +let latestProcessingEnd = 0; +// A WeakMap to look up the event-timing-entries group of a given entry. +// Note that this only maps from "important" entries: either the first input or +// those with an `interactionId`. +const entryToEntriesGroupMap = new WeakMap(); +// A mapping of interactionIds to the target Node. +export const interactionTargetMap = new Map(); +// A reference to the idle task used to clean up entries from the above +// variables. If the value is -1 it means no task is queue, and if it's +// greater than -1 the value corresponds to the idle callback handle. +let idleHandle = -1; +/** + * Adds new LoAF entries to the `pendingLoAFs` list. + */ +const handleLoAFEntries = (entries) => { + pendingLoAFs = pendingLoAFs.concat(entries); + queueCleanup(); +}; +// Get a reference to the interaction target element in case it's removed +// from the DOM later. +const saveInteractionTarget = (entry) => { + if (entry.interactionId && + entry.target && + !interactionTargetMap.has(entry.interactionId)) { + interactionTargetMap.set(entry.interactionId, entry.target); + } +}; +/** + * Groups entries that were presented within the same animation frame by + * a common `renderTime`. This function works by referencing + * `pendingEntriesGroups` and using an existing render time if one is found + * (otherwise creating a new one). This function also adds all interaction + * entries to an `entryToRenderTimeMap` WeakMap so that the "grouped" entries + * can be looked up later. + */ +const groupEntriesByRenderTime = (entry) => { + const renderTime = entry.startTime + entry.duration; + let group; + latestProcessingEnd = Math.max(latestProcessingEnd, entry.processingEnd); + // Iterate over all previous render times in reverse order to find a match. + // Go in reverse since the most likely match will be at the end. + for (let i = pendingEntriesGroups.length - 1; i >= 0; i--) { + const potentialGroup = pendingEntriesGroups[i]; + // If a group's render time is within 8ms of the entry's render time, + // assume they were part of the same frame and add it to the group. + if (Math.abs(renderTime - potentialGroup.renderTime) <= 8) { + group = potentialGroup; + group.startTime = Math.min(entry.startTime, group.startTime); + group.processingStart = Math.min(entry.processingStart, group.processingStart); + group.processingEnd = Math.max(entry.processingEnd, group.processingEnd); + group.entries.push(entry); + break; + } + } + // If there was no matching group, assume this is a new frame. + if (!group) { + group = { + startTime: entry.startTime, + processingStart: entry.processingStart, + processingEnd: entry.processingEnd, + renderTime, + entries: [entry], + }; + pendingEntriesGroups.push(group); + } + // Store the grouped render time for this entry for reference later. + if (entry.interactionId || entry.entryType === 'first-input') { + entryToEntriesGroupMap.set(entry, group); + } + queueCleanup(); +}; +const queueCleanup = () => { + // Queue cleanup of entries that are not part of any INP candidates. + if (idleHandle < 0) { + idleHandle = whenIdle(cleanupEntries); + } +}; +const cleanupEntries = () => { + // Delete any stored interaction target elements if they're not part of one + // of the 10 longest interactions. + if (interactionTargetMap.size > 10) { + interactionTargetMap.forEach((_, key) => { + if (!longestInteractionMap.has(key)) { + interactionTargetMap.delete(key); + } + }); + } + // Keep all render times that are part of a pending INP candidate or + // that occurred within the 50 most recently-dispatched groups of events. + const longestInteractionGroups = longestInteractionList.map((i) => { + return entryToEntriesGroupMap.get(i.entries[0]); + }); + const minIndex = pendingEntriesGroups.length - MAX_PREVIOUS_FRAMES; + pendingEntriesGroups = pendingEntriesGroups.filter((group, index) => { + if (index >= minIndex) + return true; + return longestInteractionGroups.includes(group); + }); + // Keep all pending LoAF entries that either: + // 1) intersect with entries in the newly cleaned up `pendingEntriesGroups` + // 2) occur after the most recently-processed event entry (for up to MAX_PREVIOUS_FRAMES) + const loafsToKeep = new Set(); + for (let i = 0; i < pendingEntriesGroups.length; i++) { + const group = pendingEntriesGroups[i]; + getIntersectingLoAFs(group.startTime, group.processingEnd).forEach((loaf) => { + loafsToKeep.add(loaf); + }); + } + const prevFrameIndexCutoff = pendingLoAFs.length - 1 - MAX_PREVIOUS_FRAMES; + // Filter `pendingLoAFs` to preserve LoAF order. + pendingLoAFs = pendingLoAFs.filter((loaf, index) => { + if (loaf.startTime > latestProcessingEnd && index > prevFrameIndexCutoff) { + return true; + } + return loafsToKeep.has(loaf); + }); + // Reset the idle callback handle so it can be queued again. + idleHandle = -1; +}; +entryPreProcessingCallbacks.push(saveInteractionTarget, groupEntriesByRenderTime); +const getIntersectingLoAFs = (start, end) => { + const intersectingLoAFs = []; + for (let i = 0, loaf; (loaf = pendingLoAFs[i]); i++) { + // If the LoAF ends before the given start time, ignore it. + if (loaf.startTime + loaf.duration < start) + continue; + // If the LoAF starts after the given end time, ignore it and all + // subsequent pending LoAFs (because they're in time order). + if (loaf.startTime > end) + break; + // Still here? If so this LoAF intersects with the interaction. + intersectingLoAFs.push(loaf); + } + return intersectingLoAFs; +}; +const attributeINP = (metric) => { + const firstEntry = metric.entries[0]; + const group = entryToEntriesGroupMap.get(firstEntry); + const processingStart = firstEntry.processingStart; + const processingEnd = group.processingEnd; + // Sort the entries in processing time order. + const processedEventEntries = group.entries.sort((a, b) => { + return a.processingStart - b.processingStart; + }); + const longAnimationFrameEntries = getIntersectingLoAFs(firstEntry.startTime, processingEnd); + // The first interaction entry may not have a target defined, so use the + // first one found in the entry list. + // TODO: when the following bug is fixed just use `firstInteractionEntry`. + // https://bugs.chromium.org/p/chromium/issues/detail?id=1367329 + // As a fallback, also check the interactionTargetMap (to account for + // cases where the element is removed from the DOM before reporting happens). + const firstEntryWithTarget = metric.entries.find((entry) => entry.target); + const interactionTargetElement = (firstEntryWithTarget && firstEntryWithTarget.target) || + interactionTargetMap.get(firstEntry.interactionId); + // Since entry durations are rounded to the nearest 8ms, we need to clamp + // the `nextPaintTime` value to be higher than the `processingEnd` or + // end time of any LoAF entry. + const nextPaintTimeCandidates = [ + firstEntry.startTime + firstEntry.duration, + processingEnd, + ].concat(longAnimationFrameEntries.map((loaf) => loaf.startTime + loaf.duration)); + const nextPaintTime = Math.max.apply(Math, nextPaintTimeCandidates); + const attribution = { + interactionTarget: getSelector(interactionTargetElement), + interactionTargetElement: interactionTargetElement, + interactionType: firstEntry.name.startsWith('key') ? 'keyboard' : 'pointer', + interactionTime: firstEntry.startTime, + nextPaintTime: nextPaintTime, + processedEventEntries: processedEventEntries, + longAnimationFrameEntries: longAnimationFrameEntries, + inputDelay: processingStart - firstEntry.startTime, + processingDuration: processingEnd - processingStart, + presentationDelay: Math.max(nextPaintTime - processingEnd, 0), + loadState: getLoadState(firstEntry.startTime), + }; + // Use Object.assign to set property to keep tsc happy. + const metricWithAttribution = Object.assign(metric, { attribution }); + return metricWithAttribution; +}; +/** + * Calculates the [INP](https://web.dev/articles/inp) value for the current + * page and calls the `callback` function once the value is ready, along with + * the `event` performance entries reported for that interaction. The reported + * value is a `DOMHighResTimeStamp`. + * + * A custom `durationThreshold` configuration option can optionally be passed to + * control what `event-timing` entries are considered for INP reporting. The + * default threshold is `40`, which means INP scores of less than 40 are + * reported as 0. Note that this will not affect your 75th percentile INP value + * unless that value is also less than 40 (well below the recommended + * [good](https://web.dev/articles/inp#what_is_a_good_inp_score) threshold). + * + * If the `reportAllChanges` configuration option is set to `true`, the + * `callback` function will be called as soon as the value is initially + * determined as well as any time the value changes throughout the page + * lifespan. + * + * _**Important:** INP should be continually monitored for changes throughout + * the entire lifespan of a page—including if the user returns to the page after + * it's been hidden/backgrounded. However, since browsers often [will not fire + * additional callbacks once the user has backgrounded a + * page](https://developer.chrome.com/blog/page-lifecycle-api/#advice-hidden), + * `callback` is always called when the page's visibility state changes to + * hidden. As a result, the `callback` function might be called multiple times + * during the same page load._ + */ +export const onINP = (onReport, opts) => { + if (!loafObserver) { + loafObserver = observe('long-animation-frame', handleLoAFEntries); + } + unattributedOnINP((metric) => { + const metricWithAttribution = attributeINP(metric); + onReport(metricWithAttribution); + }, opts); +}; diff --git a/frontend-old/node_modules/web-vitals/dist/modules/attribution/onLCP.d.ts b/frontend-old/node_modules/web-vitals/dist/modules/attribution/onLCP.d.ts new file mode 100644 index 0000000..caa6ea7 --- /dev/null +++ b/frontend-old/node_modules/web-vitals/dist/modules/attribution/onLCP.d.ts @@ -0,0 +1,13 @@ +import { LCPMetricWithAttribution, ReportOpts } from '../types.js'; +/** + * Calculates the [LCP](https://web.dev/articles/lcp) value for the current page and + * calls the `callback` function once the value is ready (along with the + * relevant `largest-contentful-paint` performance entry used to determine the + * value). The reported value is a `DOMHighResTimeStamp`. + * + * If the `reportAllChanges` configuration option is set to `true`, the + * `callback` function will be called any time a new `largest-contentful-paint` + * performance entry is dispatched, or once the final value of the metric has + * been determined. + */ +export declare const onLCP: (onReport: (metric: LCPMetricWithAttribution) => void, opts?: ReportOpts) => void; diff --git a/frontend-old/node_modules/web-vitals/dist/modules/attribution/onLCP.js b/frontend-old/node_modules/web-vitals/dist/modules/attribution/onLCP.js new file mode 100644 index 0000000..269323c --- /dev/null +++ b/frontend-old/node_modules/web-vitals/dist/modules/attribution/onLCP.js @@ -0,0 +1,83 @@ +/* + * 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 '../lib/getNavigationEntry.js'; +import { getSelector } from '../lib/getSelector.js'; +import { onLCP as unattributedOnLCP } from '../onLCP.js'; +const attributeLCP = (metric) => { + // Use a default object if no other attribution has been set. + let attribution = { + timeToFirstByte: 0, + resourceLoadDelay: 0, + resourceLoadDuration: 0, + elementRenderDelay: metric.value, + }; + if (metric.entries.length) { + const navigationEntry = getNavigationEntry(); + if (navigationEntry) { + const activationStart = navigationEntry.activationStart || 0; + const lcpEntry = metric.entries[metric.entries.length - 1]; + const lcpResourceEntry = lcpEntry.url && + performance + .getEntriesByType('resource') + .filter((e) => e.name === lcpEntry.url)[0]; + const ttfb = Math.max(0, navigationEntry.responseStart - activationStart); + const lcpRequestStart = Math.max(ttfb, + // Prefer `requestStart` (if TOA is set), otherwise use `startTime`. + lcpResourceEntry + ? (lcpResourceEntry.requestStart || lcpResourceEntry.startTime) - + activationStart + : 0); + const lcpResponseEnd = Math.max(lcpRequestStart, lcpResourceEntry ? lcpResourceEntry.responseEnd - activationStart : 0); + const lcpRenderTime = Math.max(lcpResponseEnd, lcpEntry.startTime - activationStart); + attribution = { + element: getSelector(lcpEntry.element), + timeToFirstByte: ttfb, + resourceLoadDelay: lcpRequestStart - ttfb, + resourceLoadDuration: lcpResponseEnd - lcpRequestStart, + elementRenderDelay: lcpRenderTime - lcpResponseEnd, + navigationEntry, + lcpEntry, + }; + // Only attribution the URL and resource entry if they exist. + if (lcpEntry.url) { + attribution.url = lcpEntry.url; + } + if (lcpResourceEntry) { + attribution.lcpResourceEntry = lcpResourceEntry; + } + } + } + // Use Object.assign to set property to keep tsc happy. + const metricWithAttribution = Object.assign(metric, { attribution }); + return metricWithAttribution; +}; +/** + * Calculates the [LCP](https://web.dev/articles/lcp) value for the current page and + * calls the `callback` function once the value is ready (along with the + * relevant `largest-contentful-paint` performance entry used to determine the + * value). The reported value is a `DOMHighResTimeStamp`. + * + * If the `reportAllChanges` configuration option is set to `true`, the + * `callback` function will be called any time a new `largest-contentful-paint` + * performance entry is dispatched, or once the final value of the metric has + * been determined. + */ +export const onLCP = (onReport, opts) => { + unattributedOnLCP((metric) => { + const metricWithAttribution = attributeLCP(metric); + onReport(metricWithAttribution); + }, opts); +}; diff --git a/frontend-old/node_modules/web-vitals/dist/modules/attribution/onTTFB.d.ts b/frontend-old/node_modules/web-vitals/dist/modules/attribution/onTTFB.d.ts new file mode 100644 index 0000000..a39d0ad --- /dev/null +++ b/frontend-old/node_modules/web-vitals/dist/modules/attribution/onTTFB.d.ts @@ -0,0 +1,17 @@ +import { TTFBMetricWithAttribution, ReportOpts } from '../types.js'; +/** + * Calculates the [TTFB](https://web.dev/articles/ttfb) value for the + * current page and calls the `callback` function once the page has loaded, + * along with the relevant `navigation` performance entry used to determine the + * value. The reported value is a `DOMHighResTimeStamp`. + * + * Note, this function waits until after the page is loaded to call `callback` + * in order to ensure all properties of the `navigation` entry are populated. + * This is useful if you want to report on other metrics exposed by the + * [Navigation Timing API](https://w3c.github.io/navigation-timing/). For + * example, the TTFB metric starts from the page's [time + * origin](https://www.w3.org/TR/hr-time-2/#sec-time-origin), which means it + * includes time spent on DNS lookup, connection negotiation, network latency, + * and server processing time. + */ +export declare const onTTFB: (onReport: (metric: TTFBMetricWithAttribution) => void, opts?: ReportOpts) => void; diff --git a/frontend-old/node_modules/web-vitals/dist/modules/attribution/onTTFB.js b/frontend-old/node_modules/web-vitals/dist/modules/attribution/onTTFB.js new file mode 100644 index 0000000..cd0d6bb --- /dev/null +++ b/frontend-old/node_modules/web-vitals/dist/modules/attribution/onTTFB.js @@ -0,0 +1,76 @@ +/* + * 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 { onTTFB as unattributedOnTTFB } from '../onTTFB.js'; +const attributeTTFB = (metric) => { + // Use a default object if no other attribution has been set. + let attribution = { + waitingDuration: 0, + cacheDuration: 0, + dnsDuration: 0, + connectionDuration: 0, + requestDuration: 0, + }; + if (metric.entries.length) { + const navigationEntry = metric.entries[0]; + const activationStart = navigationEntry.activationStart || 0; + // Measure from workerStart or fetchStart so any service worker startup + // time is included in cacheDuration (which also includes other sw time + // anyway, that cannot be accurately split out cross-browser). + const waitEnd = Math.max((navigationEntry.workerStart || navigationEntry.fetchStart) - + activationStart, 0); + const dnsStart = Math.max(navigationEntry.domainLookupStart - activationStart, 0); + const connectStart = Math.max(navigationEntry.connectStart - activationStart, 0); + const connectEnd = Math.max(navigationEntry.connectEnd - activationStart, 0); + attribution = { + waitingDuration: waitEnd, + cacheDuration: dnsStart - waitEnd, + // dnsEnd usually equals connectStart but use connectStart over dnsEnd + // for dnsDuration in case there ever is a gap. + dnsDuration: connectStart - dnsStart, + connectionDuration: connectEnd - connectStart, + // There is often a gap between connectEnd and requestStart. Attribute + // that to requestDuration so connectionDuration remains 0 for + // service worker controlled requests were connectStart and connectEnd + // are the same. + requestDuration: metric.value - connectEnd, + navigationEntry: navigationEntry, + }; + } + // Use Object.assign to set property to keep tsc happy. + const metricWithAttribution = Object.assign(metric, { attribution }); + return metricWithAttribution; +}; +/** + * Calculates the [TTFB](https://web.dev/articles/ttfb) value for the + * current page and calls the `callback` function once the page has loaded, + * along with the relevant `navigation` performance entry used to determine the + * value. The reported value is a `DOMHighResTimeStamp`. + * + * Note, this function waits until after the page is loaded to call `callback` + * in order to ensure all properties of the `navigation` entry are populated. + * This is useful if you want to report on other metrics exposed by the + * [Navigation Timing API](https://w3c.github.io/navigation-timing/). For + * example, the TTFB metric starts from the page's [time + * origin](https://www.w3.org/TR/hr-time-2/#sec-time-origin), which means it + * includes time spent on DNS lookup, connection negotiation, network latency, + * and server processing time. + */ +export const onTTFB = (onReport, opts) => { + unattributedOnTTFB((metric) => { + const metricWithAttribution = attributeTTFB(metric); + onReport(metricWithAttribution); + }, opts); +}; |
