{"version":3,"file":"InteractionManager.js","sources":["../../../../../src/metrics/web-vitals/lib/InteractionManager.ts"],"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 */\n\nimport { getInteractionCount } from './polyfills/interactionCountPolyfill.js';\n\nexport interface Interaction {\n _latency: number;\n // While the `id` and `entries` properties are also internal and could be\n // mangled by prefixing with an underscore, since they correspond to public\n // symbols there is no need to mangle them as the library will compress\n // better if we reuse the existing names.\n id: number;\n entries: PerformanceEventTiming[];\n}\n\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\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/**\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};\n\n/**\n *\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 // oxlint-disable-next-line sdk/no-class-field-initializers\n _longestInteractionList: Interaction[] = [];\n\n /**\n * A mapping of longest interactions by their interaction ID.\n * This is used for faster lookup.\n */\n // oxlint-disable-next-line sdk/no-class-field-initializers\n _longestInteractionMap: Map = new Map();\n\n // eslint-disable-next-line @typescript-eslint/explicit-member-accessibility\n _onBeforeProcessingEntry?: (entry: PerformanceEventTiming) => void;\n\n // eslint-disable-next-line @typescript-eslint/explicit-member-accessibility\n _onAfterProcessingINPCandidate?: (interaction: Interaction) => void;\n\n // eslint-disable-next-line @typescript-eslint/explicit-member-accessibility, jsdoc/require-jsdoc\n _resetInteractions() {\n prevInteractionCount = getInteractionCount();\n this._longestInteractionList.length = 0;\n this._longestInteractionMap.clear();\n }\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 // eslint-disable-next-line @typescript-eslint/explicit-member-accessibility\n _estimateP98LongestInteraction() {\n const candidateInteractionIndex = Math.min(\n this._longestInteractionList.length - 1,\n Math.floor(getInteractionCountForNavigation() / 50),\n );\n\n return this._longestInteractionList[candidateInteractionIndex];\n }\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 // eslint-disable-next-line @typescript-eslint/explicit-member-accessibility\n _processEntry(entry: PerformanceEventTiming) {\n this._onBeforeProcessingEntry?.(entry);\n\n // Skip further processing for entries that cannot be INP candidates.\n if (!(entry.interactionId || entry.entryType === 'first-input')) return;\n\n // The least-long of the 10 longest interactions.\n const minLongestInteraction = this._longestInteractionList.at(-1);\n\n let interaction = this._longestInteractionMap.get(entry.interactionId);\n\n // Only process the entry if it's possibly one of the ten longest,\n // or if it's part of an existing interaction.\n if (\n interaction ||\n this._longestInteractionList.length < MAX_INTERACTIONS_TO_CONSIDER ||\n // If the above conditions are false, `minLongestInteraction` will be set.\n entry.duration > minLongestInteraction!._latency\n ) {\n // If the interaction already exists, update it. Otherwise create one.\n if (interaction) {\n // If the new entry has a longer duration, replace the old entries,\n // otherwise add to the array.\n if (entry.duration > interaction._latency) {\n interaction.entries = [entry];\n interaction._latency = entry.duration;\n } else if (entry.duration === interaction._latency && entry.startTime === interaction.entries[0]!.startTime) {\n interaction.entries.push(entry);\n }\n } else {\n interaction = {\n id: entry.interactionId,\n entries: [entry],\n _latency: entry.duration,\n };\n this._longestInteractionMap.set(interaction.id, interaction);\n this._longestInteractionList.push(interaction);\n }\n\n // Sort the entries by latency (descending) and keep only the top ten.\n this._longestInteractionList.sort((a, b) => b._latency - a._latency);\n if (this._longestInteractionList.length > MAX_INTERACTIONS_TO_CONSIDER) {\n const removedInteractions = this._longestInteractionList.splice(MAX_INTERACTIONS_TO_CONSIDER);\n\n for (const interaction of removedInteractions) {\n this._longestInteractionMap.delete(interaction.id);\n }\n }\n\n // Call any post-processing on the interaction\n this._onAfterProcessingINPCandidate?.(interaction);\n }\n }\n}\n"],"names":["interaction"],"mappings":"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"}