@sentry/browser-utils 11.0.0-beta.1 → 11.0.0-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/cjs/index.js +5 -0
- package/build/cjs/index.js.map +1 -1
- package/build/cjs/instrumentation/performanceObserver.js +29 -16
- package/build/cjs/instrumentation/performanceObserver.js.map +1 -1
- package/build/cjs/instrumentation/xhr.js +2 -1
- package/build/cjs/instrumentation/xhr.js.map +1 -1
- package/build/cjs/performance/entries.js.map +1 -1
- package/build/cjs/web-vitals/emitSpan.js +89 -0
- package/build/cjs/web-vitals/emitSpan.js.map +1 -0
- package/build/cjs/web-vitals/inp.js +8 -1
- package/build/cjs/web-vitals/inp.js.map +1 -1
- package/build/cjs/web-vitals/softNavs.js +84 -0
- package/build/cjs/web-vitals/softNavs.js.map +1 -0
- package/build/cjs/web-vitals/spans.js +118 -104
- package/build/cjs/web-vitals/spans.js.map +1 -1
- package/build/esm/index.js +2 -1
- package/build/esm/index.js.map +1 -1
- package/build/esm/instrumentation/performanceObserver.js +28 -17
- package/build/esm/instrumentation/performanceObserver.js.map +1 -1
- package/build/esm/instrumentation/xhr.js +2 -1
- package/build/esm/instrumentation/xhr.js.map +1 -1
- package/build/esm/package.json +1 -1
- package/build/esm/performance/entries.js.map +1 -1
- package/build/esm/web-vitals/emitSpan.js +87 -0
- package/build/esm/web-vitals/emitSpan.js.map +1 -0
- package/build/esm/web-vitals/inp.js +8 -1
- package/build/esm/web-vitals/inp.js.map +1 -1
- package/build/esm/web-vitals/softNavs.js +80 -0
- package/build/esm/web-vitals/softNavs.js.map +1 -0
- package/build/esm/web-vitals/spans.js +118 -103
- package/build/esm/web-vitals/spans.js.map +1 -1
- package/build/types/index.d.ts +2 -1
- package/build/types/index.d.ts.map +1 -1
- package/build/types/instrumentation/performanceObserver.d.ts +65 -2
- package/build/types/instrumentation/performanceObserver.d.ts.map +1 -1
- package/build/types/instrumentation/xhr.d.ts.map +1 -1
- package/build/types/web-vitals/emitSpan.d.ts +49 -0
- package/build/types/web-vitals/emitSpan.d.ts.map +1 -0
- package/build/types/web-vitals/inp.d.ts.map +1 -1
- package/build/types/web-vitals/softNavs.d.ts +39 -0
- package/build/types/web-vitals/softNavs.d.ts.map +1 -0
- package/build/types/web-vitals/spans.d.ts +10 -47
- package/build/types/web-vitals/spans.d.ts.map +1 -1
- package/package.json +4 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"inp.js","sources":["../../../src/web-vitals/inp.ts"],"sourcesContent":["import type { Span } from '@sentry/core';\nimport { getActiveSpan, getRootSpan, isBrowser } from '@sentry/core';\nimport { htmlTreeAsString } from '../htmlTreeAsString';\nimport { WINDOW } from '../types';\nimport { addPerformanceInstrumentationHandler, isPerformanceEventTiming } from '../instrumentation/performanceObserver';\n\ninterface InteractionContext {\n span: Span | undefined;\n elementName: string;\n}\n\nconst LAST_INTERACTIONS: number[] = [];\nconst INTERACTIONS_SPAN_MAP = new Map<number, InteractionContext>();\n\n// Map to store element names by timestamp, since we get the DOM event before the PerformanceObserver entry\nconst ELEMENT_NAME_TIMESTAMP_MAP = new Map<number, string>();\n\n/**\n * 60 seconds is the maximum for a plausible INP value\n * (source: Me)\n */\nexport const MAX_PLAUSIBLE_INP_DURATION = 60;\n\nexport type InteractionType = 'click' | 'hover' | 'drag' | 'press';\n\nexport const INP_ENTRY_MAP: Record<string, InteractionType> = {\n click: 'click',\n pointerdown: 'click',\n pointerup: 'click',\n mousedown: 'click',\n mouseup: 'click',\n touchstart: 'click',\n touchend: 'click',\n mouseover: 'hover',\n mouseout: 'hover',\n mouseenter: 'hover',\n mouseleave: 'hover',\n pointerover: 'hover',\n pointerout: 'hover',\n pointerenter: 'hover',\n pointerleave: 'hover',\n dragstart: 'drag',\n dragend: 'drag',\n drag: 'drag',\n dragenter: 'drag',\n dragleave: 'drag',\n dragover: 'drag',\n drop: 'drag',\n keydown: 'press',\n keyup: 'press',\n keypress: 'press',\n input: 'press',\n};\n\n/**\n * Look up a cached interaction context (element name + root span) by interactionId.\n * Returns undefined if no context was cached for this interaction.\n */\nexport function getCachedInteractionContext(interactionId: number | undefined): InteractionContext | undefined {\n return interactionId != null ? INTERACTIONS_SPAN_MAP.get(interactionId) : undefined;\n}\n\n/**\n * Register a listener to cache route information for INP interactions.\n */\nexport function registerInpInteractionListener(): void {\n // Listen for all interaction events that could contribute to INP\n const interactionEvents = Object.keys(INP_ENTRY_MAP);\n if (isBrowser()) {\n interactionEvents.forEach(eventType => {\n WINDOW.addEventListener(eventType, captureElementFromEvent, { capture: true, passive: true });\n });\n }\n\n /**\n * Captures the element name from a DOM event and stores it in the ELEMENT_NAME_TIMESTAMP_MAP.\n */\n function captureElementFromEvent(event: Event): void {\n const target = event.target as HTMLElement | null;\n if (!target) {\n return;\n }\n\n const elementName = htmlTreeAsString(target);\n const timestamp = Math.round(event.timeStamp);\n\n // Store the element name by timestamp so we can match it with the PerformanceEntry\n ELEMENT_NAME_TIMESTAMP_MAP.set(timestamp, elementName);\n\n // Clean up old\n if (ELEMENT_NAME_TIMESTAMP_MAP.size > 50) {\n const firstKey = ELEMENT_NAME_TIMESTAMP_MAP.keys().next().value;\n if (firstKey !== undefined) {\n ELEMENT_NAME_TIMESTAMP_MAP.delete(firstKey);\n }\n }\n }\n\n /**\n * Tries to get the element name from the timestamp map.\n */\n function resolveElementNameFromEntry(entry: PerformanceEntry): string {\n const timestamp = Math.round(entry.startTime);\n let elementName = ELEMENT_NAME_TIMESTAMP_MAP.get(timestamp);\n\n // try nearby timestamps (±5ms)\n if (!elementName) {\n for (let offset = -5; offset <= 5; offset++) {\n const nearbyName = ELEMENT_NAME_TIMESTAMP_MAP.get(timestamp + offset);\n if (nearbyName) {\n elementName = nearbyName;\n break;\n }\n }\n }\n\n return elementName ||
|
|
1
|
+
{"version":3,"file":"inp.js","sources":["../../../src/web-vitals/inp.ts"],"sourcesContent":["import type { Span } from '@sentry/core';\nimport { getActiveSpan, getRootSpan, isBrowser } from '@sentry/core';\nimport { htmlTreeAsString } from '../htmlTreeAsString';\nimport { WINDOW } from '../types';\nimport { addPerformanceInstrumentationHandler, isPerformanceEventTiming } from '../instrumentation/performanceObserver';\n\ninterface InteractionContext {\n span: Span | undefined;\n elementName: string;\n}\n\nconst LAST_INTERACTIONS: number[] = [];\nconst INTERACTIONS_SPAN_MAP = new Map<number, InteractionContext>();\n\n// Map to store element names by timestamp, since we get the DOM event before the PerformanceObserver entry\n/** What `htmlTreeAsString` returns when it cannot describe the target. */\nconst UNKNOWN_ELEMENT_NAME = '<unknown>';\n\nconst ELEMENT_NAME_TIMESTAMP_MAP = new Map<number, string>();\n\n/**\n * 60 seconds is the maximum for a plausible INP value\n * (source: Me)\n */\nexport const MAX_PLAUSIBLE_INP_DURATION = 60;\n\nexport type InteractionType = 'click' | 'hover' | 'drag' | 'press';\n\nexport const INP_ENTRY_MAP: Record<string, InteractionType> = {\n click: 'click',\n pointerdown: 'click',\n pointerup: 'click',\n mousedown: 'click',\n mouseup: 'click',\n touchstart: 'click',\n touchend: 'click',\n mouseover: 'hover',\n mouseout: 'hover',\n mouseenter: 'hover',\n mouseleave: 'hover',\n pointerover: 'hover',\n pointerout: 'hover',\n pointerenter: 'hover',\n pointerleave: 'hover',\n dragstart: 'drag',\n dragend: 'drag',\n drag: 'drag',\n dragenter: 'drag',\n dragleave: 'drag',\n dragover: 'drag',\n drop: 'drag',\n keydown: 'press',\n keyup: 'press',\n keypress: 'press',\n input: 'press',\n};\n\n/**\n * Look up a cached interaction context (element name + root span) by interactionId.\n * Returns undefined if no context was cached for this interaction.\n */\nexport function getCachedInteractionContext(interactionId: number | undefined): InteractionContext | undefined {\n return interactionId != null ? INTERACTIONS_SPAN_MAP.get(interactionId) : undefined;\n}\n\n/**\n * Register a listener to cache route information for INP interactions.\n */\nexport function registerInpInteractionListener(): void {\n // Listen for all interaction events that could contribute to INP\n const interactionEvents = Object.keys(INP_ENTRY_MAP);\n if (isBrowser()) {\n interactionEvents.forEach(eventType => {\n WINDOW.addEventListener(eventType, captureElementFromEvent, { capture: true, passive: true });\n });\n }\n\n /**\n * Captures the element name from a DOM event and stores it in the ELEMENT_NAME_TIMESTAMP_MAP.\n */\n function captureElementFromEvent(event: Event): void {\n const target = event.target as HTMLElement | null;\n if (!target) {\n return;\n }\n\n const elementName = htmlTreeAsString(target);\n const timestamp = Math.round(event.timeStamp);\n\n // Not every event of an interaction has a describable target, and one that doesn't would\n // otherwise claim the timestamp and leave the span unnamed.\n if (!elementName || elementName === UNKNOWN_ELEMENT_NAME) {\n return;\n }\n\n // Every event of one interaction shares a timestamp, and so do the `pointerover`/`mouseover`\n // the browser fires afterwards when a handler swaps out the element under the cursor. Those\n // arrive last and describe the new DOM, so keeping the first usable name is what pins the entry\n // to the element that was actually interacted with.\n if (ELEMENT_NAME_TIMESTAMP_MAP.has(timestamp)) {\n return;\n }\n\n // Store the element name by timestamp so we can match it with the PerformanceEntry\n ELEMENT_NAME_TIMESTAMP_MAP.set(timestamp, elementName);\n\n // Clean up old\n if (ELEMENT_NAME_TIMESTAMP_MAP.size > 50) {\n const firstKey = ELEMENT_NAME_TIMESTAMP_MAP.keys().next().value;\n if (firstKey !== undefined) {\n ELEMENT_NAME_TIMESTAMP_MAP.delete(firstKey);\n }\n }\n }\n\n /**\n * Tries to get the element name from the timestamp map.\n */\n function resolveElementNameFromEntry(entry: PerformanceEntry): string {\n const timestamp = Math.round(entry.startTime);\n let elementName = ELEMENT_NAME_TIMESTAMP_MAP.get(timestamp);\n\n // try nearby timestamps (±5ms)\n if (!elementName) {\n for (let offset = -5; offset <= 5; offset++) {\n const nearbyName = ELEMENT_NAME_TIMESTAMP_MAP.get(timestamp + offset);\n if (nearbyName) {\n elementName = nearbyName;\n break;\n }\n }\n }\n\n return elementName || UNKNOWN_ELEMENT_NAME;\n }\n\n const handleEntries = ({ entries }: { entries: PerformanceEntry[] }): void => {\n const activeSpan = getActiveSpan();\n const activeRootSpan = activeSpan && getRootSpan(activeSpan);\n\n entries.forEach(entry => {\n if (!isPerformanceEventTiming(entry)) {\n return;\n }\n\n const interactionId = entry.interactionId;\n if (interactionId == null) {\n return;\n }\n\n // If the interaction was already recorded before, nothing more to do\n if (INTERACTIONS_SPAN_MAP.has(interactionId)) {\n return;\n }\n\n const elementName = entry.target ? htmlTreeAsString(entry.target) : resolveElementNameFromEntry(entry);\n\n // We keep max. 10 interactions in the list, then remove the oldest one & clean up\n if (LAST_INTERACTIONS.length > 10) {\n const last = LAST_INTERACTIONS.shift() as number;\n INTERACTIONS_SPAN_MAP.delete(last);\n }\n\n // We add the interaction to the list of recorded interactions\n // and store both the span and element name for this interaction\n LAST_INTERACTIONS.push(interactionId);\n INTERACTIONS_SPAN_MAP.set(interactionId, {\n span: activeRootSpan,\n elementName,\n });\n });\n };\n\n addPerformanceInstrumentationHandler('event', handleEntries);\n addPerformanceInstrumentationHandler('first-input', handleEntries);\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { LRUMap, spanToJSON, SEMANTIC_ATTRIBUTE_SENTRY_OP, debug } from '@sentry/core';
|
|
2
|
+
import { BROWSER_NAVIGATION_ID } from '@sentry/conventions/attributes';
|
|
3
|
+
import { DEBUG_BUILD } from '../debug-build.js';
|
|
4
|
+
import { addPerformanceInstrumentationHandler, isPerformanceEventTiming } from '../instrumentation/performanceObserver.js';
|
|
5
|
+
import { WINDOW } from '../types.js';
|
|
6
|
+
|
|
7
|
+
const MAX_TRACKED_NAVIGATIONS = 5;
|
|
8
|
+
const INTERACTION_MATCH_TOLERANCE_MS = 5;
|
|
9
|
+
let _pendingNavigation;
|
|
10
|
+
let _lastInteractionTimestamp;
|
|
11
|
+
const _interactionIdToNavigationSpan = new LRUMap(MAX_TRACKED_NAVIGATIONS);
|
|
12
|
+
const _navigationIdToNavigationSpan = new LRUMap(MAX_TRACKED_NAVIGATIONS);
|
|
13
|
+
let _correlationStarted = false;
|
|
14
|
+
function supportsSoftNavigations() {
|
|
15
|
+
try {
|
|
16
|
+
return PerformanceObserver.supportedEntryTypes.includes("soft-navigation") && // Older implementations exposed this as an attribute rather than a method. Only the method
|
|
17
|
+
// form shipped unflagged, so it's what web-vitals gates on.
|
|
18
|
+
typeof WINDOW.PerformanceSoftNavigation?.prototype?.getLargestInteractionContentfulPaint === "function";
|
|
19
|
+
} catch {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function startSoftNavigationCorrelation(client) {
|
|
24
|
+
if (_correlationStarted || !supportsSoftNavigations()) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
_correlationStarted = true;
|
|
28
|
+
const onInteraction = (event) => {
|
|
29
|
+
if (event.isTrusted) {
|
|
30
|
+
_lastInteractionTimestamp = event.timeStamp;
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
WINDOW.addEventListener("click", onInteraction, { capture: true, passive: true });
|
|
34
|
+
WINDOW.addEventListener("keydown", onInteraction, { capture: true, passive: true });
|
|
35
|
+
client.on("spanStart", (span) => {
|
|
36
|
+
if (spanToJSON(span).attributes?.[SEMANTIC_ATTRIBUTE_SENTRY_OP] !== "navigation") {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
_pendingNavigation = _lastInteractionTimestamp != null ? { span, interactionTimestamp: _lastInteractionTimestamp } : void 0;
|
|
40
|
+
});
|
|
41
|
+
const bindInteractionToNavigationSpan = ({ entries }) => {
|
|
42
|
+
for (const entry of entries) {
|
|
43
|
+
const pending = _pendingNavigation;
|
|
44
|
+
if (!pending || !isPerformanceEventTiming(entry) || !entry.interactionId) {
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
if (Math.abs(entry.startTime - pending.interactionTimestamp) > INTERACTION_MATCH_TOLERANCE_MS) {
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
_interactionIdToNavigationSpan.set(entry.interactionId, pending.span);
|
|
51
|
+
_pendingNavigation = void 0;
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
addPerformanceInstrumentationHandler("event", bindInteractionToNavigationSpan);
|
|
55
|
+
addPerformanceInstrumentationHandler("first-input", bindInteractionToNavigationSpan);
|
|
56
|
+
addPerformanceInstrumentationHandler("soft-navigation", ({ entries }) => {
|
|
57
|
+
for (const entry of entries) {
|
|
58
|
+
const span = _interactionIdToNavigationSpan.get(entry.interactionId);
|
|
59
|
+
if (!span) {
|
|
60
|
+
DEBUG_BUILD && debug.log(`[SoftNav] No navigation span found for soft navigation ${entry.navigationId}`, entry);
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
_navigationIdToNavigationSpan.set(entry.navigationId, span);
|
|
64
|
+
span.setAttribute(BROWSER_NAVIGATION_ID, entry.navigationId);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
function getNavigationSpanForMetric(metric) {
|
|
69
|
+
if (metric.navigationType !== "soft-navigation") {
|
|
70
|
+
return void 0;
|
|
71
|
+
}
|
|
72
|
+
const span = _navigationIdToNavigationSpan.get(metric.navigationId);
|
|
73
|
+
if (span) {
|
|
74
|
+
return span;
|
|
75
|
+
}
|
|
76
|
+
return metric.navigationInteractionId != null ? _interactionIdToNavigationSpan.get(metric.navigationInteractionId) : void 0;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export { getNavigationSpanForMetric, startSoftNavigationCorrelation, supportsSoftNavigations };
|
|
80
|
+
//# sourceMappingURL=softNavs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"softNavs.js","sources":["../../../src/web-vitals/softNavs.ts"],"sourcesContent":["import type { Client, Span } from '@sentry/core';\nimport { debug, LRUMap, SEMANTIC_ATTRIBUTE_SENTRY_OP, spanToJSON } from '@sentry/core';\nimport { BROWSER_NAVIGATION_ID } from '@sentry/conventions/attributes';\nimport { DEBUG_BUILD } from '../debug-build';\nimport type { PerformanceSoftNavigation } from '../instrumentation/performanceObserver';\nimport { addPerformanceInstrumentationHandler, isPerformanceEventTiming } from '../instrumentation/performanceObserver';\nimport { WINDOW } from '../types';\n\n/**\n * A page only ever needs its most recent navigations to still be joinable: web vitals for a soft\n * navigation are finalized at the next soft navigation or on pagehide, never later than that.\n */\nconst MAX_TRACKED_NAVIGATIONS = 5;\n\n/**\n * Tolerance when matching a DOM event's `timeStamp` against the `startTime` of its Event Timing\n * entry. Both are `DOMHighResTimeStamp`s from the same clock, so this only absorbs rounding.\n */\nconst INTERACTION_MATCH_TOLERANCE_MS = 5;\n\ninterface SoftNavMetric {\n navigationType: string;\n navigationId: number;\n navigationInteractionId?: number;\n}\n\ninterface PendingNavigation {\n span: Span;\n interactionTimestamp: number;\n}\n\n// The navigation span whose triggering interaction we haven't identified yet.\nlet _pendingNavigation: PendingNavigation | undefined;\n// The timestamp of the most recent trusted click/keydown, i.e. our best guess at the interaction\n// that a history change happening right now was driven by.\nlet _lastInteractionTimestamp: number | undefined;\n\nconst _interactionIdToNavigationSpan = new LRUMap<number, Span>(MAX_TRACKED_NAVIGATIONS);\nconst _navigationIdToNavigationSpan = new LRUMap<number, Span>(MAX_TRACKED_NAVIGATIONS);\n\nlet _correlationStarted = false;\n\n/**\n * Whether the browser can report web vitals for soft navigations.\n *\n * This mirrors web-vitals' own feature detection: passing `reportSoftNavs` on a browser that fails\n * this check is a no-op there, so it has to be a no-op here too.\n */\nexport function supportsSoftNavigations(): boolean {\n try {\n return (\n PerformanceObserver.supportedEntryTypes.includes('soft-navigation') &&\n // Older implementations exposed this as an attribute rather than a method. Only the method\n // form shipped unflagged, so it's what web-vitals gates on.\n typeof (\n WINDOW as {\n PerformanceSoftNavigation?: { prototype?: { getLargestInteractionContentfulPaint?: unknown } };\n }\n ).PerformanceSoftNavigation?.prototype?.getLargestInteractionContentfulPaint === 'function'\n );\n } catch {\n return false;\n }\n}\n\n/**\n * Start correlating the browser's soft navigations with the SDK's navigation spans.\n *\n * A navigation span is created synchronously on the history change, but the browser only mints the\n * `soft-navigation` entry (and with it the `navigationId` that web vitals are reported against)\n * once the navigation has been confirmed by a paint. So the `navigationId` cannot be known at span\n * creation time and the two have to be joined after the fact.\n *\n * The join key is the `interactionId` of the interaction that drove the navigation: per the Soft\n * Navigations spec the `soft-navigation` entry carries the `interactionId` of the interaction that\n * triggered it, which is the same id the interaction's own `PerformanceEventTiming` entry carries.\n * So we bind a navigation span to the interaction it happened during, and the soft navigation\n * joins back to that span through the shared id.\n *\n * This is inherently partial. Navigations that don't meet the browser's soft navigation heuristic\n * (programmatic navigations, navigations that never paint, back/forward from the browser chrome)\n * produce no entry at all, so those navigation spans simply have no web vitals.\n */\nexport function startSoftNavigationCorrelation(client: Client): void {\n if (_correlationStarted || !supportsSoftNavigations()) {\n return;\n }\n _correlationStarted = true;\n\n const onInteraction = (event: Event): void => {\n if (event.isTrusted) {\n _lastInteractionTimestamp = event.timeStamp;\n }\n };\n // Only click and keydown can start a soft navigation, which is also what the SDK's redirect\n // detection listens for.\n WINDOW.addEventListener('click', onInteraction, { capture: true, passive: true });\n WINDOW.addEventListener('keydown', onInteraction, { capture: true, passive: true });\n\n client.on('spanStart', span => {\n if (spanToJSON(span).attributes?.[SEMANTIC_ATTRIBUTE_SENTRY_OP] !== 'navigation') {\n return;\n }\n\n // A navigation with no preceding interaction can't produce a soft navigation, so there is\n // nothing to wait for. Dropping the pending span here also keeps us from binding a stale one.\n _pendingNavigation =\n _lastInteractionTimestamp != null ? { span, interactionTimestamp: _lastInteractionTimestamp } : undefined;\n });\n\n const bindInteractionToNavigationSpan = ({ entries }: { entries: PerformanceEntry[] }): void => {\n for (const entry of entries) {\n const pending = _pendingNavigation;\n if (!pending || !isPerformanceEventTiming(entry) || !entry.interactionId) {\n continue;\n }\n\n if (Math.abs(entry.startTime - pending.interactionTimestamp) > INTERACTION_MATCH_TOLERANCE_MS) {\n continue;\n }\n\n _interactionIdToNavigationSpan.set(entry.interactionId, pending.span);\n _pendingNavigation = undefined;\n }\n };\n\n // `durationThreshold: 0` is applied for `event` by the shared observer, which matters here:\n // interactions below the 104ms default would otherwise never surface an `interactionId`.\n addPerformanceInstrumentationHandler('event', bindInteractionToNavigationSpan);\n addPerformanceInstrumentationHandler('first-input', bindInteractionToNavigationSpan);\n\n addPerformanceInstrumentationHandler('soft-navigation', ({ entries }) => {\n for (const entry of entries as PerformanceSoftNavigation[]) {\n const span = _interactionIdToNavigationSpan.get(entry.interactionId);\n if (!span) {\n DEBUG_BUILD && debug.log(`[SoftNav] No navigation span found for soft navigation ${entry.navigationId}`, entry);\n continue;\n }\n\n _navigationIdToNavigationSpan.set(entry.navigationId, span);\n // Best effort: the soft navigation entry usually lands well within the navigation span's idle\n // window, but if the span has already been sent this attribute is dropped.\n span.setAttribute(BROWSER_NAVIGATION_ID, entry.navigationId);\n }\n });\n}\n\n/**\n * The navigation span a soft navigation web vital belongs to, or `undefined` if the metric isn't\n * for a soft navigation or we failed to correlate it.\n */\nexport function getNavigationSpanForMetric(metric: SoftNavMetric): Span | undefined {\n if (metric.navigationType !== 'soft-navigation') {\n return undefined;\n }\n\n const span = _navigationIdToNavigationSpan.get(metric.navigationId);\n if (span) {\n return span;\n }\n\n // The `soft-navigation` observer may not have run for this navigation yet - entries from\n // different observers aren't delivered in a guaranteed order - so fall back to the join key the\n // metric carries itself.\n return metric.navigationInteractionId != null\n ? _interactionIdToNavigationSpan.get(metric.navigationInteractionId)\n : undefined;\n}\n"],"names":[],"mappings":";;;;;;AAYA,MAAM,uBAAA,GAA0B,CAAA;AAMhC,MAAM,8BAAA,GAAiC,CAAA;AAcvC,IAAI,kBAAA;AAGJ,IAAI,yBAAA;AAEJ,MAAM,8BAAA,GAAiC,IAAI,MAAA,CAAqB,uBAAuB,CAAA;AACvF,MAAM,6BAAA,GAAgC,IAAI,MAAA,CAAqB,uBAAuB,CAAA;AAEtF,IAAI,mBAAA,GAAsB,KAAA;AAQnB,SAAS,uBAAA,GAAmC;AACjD,EAAA,IAAI;AACF,IAAA,OACE,mBAAA,CAAoB,mBAAA,CAAoB,QAAA,CAAS,iBAAiB,CAAA;AAAA;AAAA,IAGlE,OACE,MAAA,CAGA,yBAAA,EAA2B,SAAA,EAAW,oCAAA,KAAyC,UAAA;AAAA,EAErF,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,KAAA;AAAA,EACT;AACF;AAoBO,SAAS,+BAA+B,MAAA,EAAsB;AACnE,EAAA,IAAI,mBAAA,IAAuB,CAAC,uBAAA,EAAwB,EAAG;AACrD,IAAA;AAAA,EACF;AACA,EAAA,mBAAA,GAAsB,IAAA;AAEtB,EAAA,MAAM,aAAA,GAAgB,CAAC,KAAA,KAAuB;AAC5C,IAAA,IAAI,MAAM,SAAA,EAAW;AACnB,MAAA,yBAAA,GAA4B,KAAA,CAAM,SAAA;AAAA,IACpC;AAAA,EACF,CAAA;AAGA,EAAA,MAAA,CAAO,gBAAA,CAAiB,SAAS,aAAA,EAAe,EAAE,SAAS,IAAA,EAAM,OAAA,EAAS,MAAM,CAAA;AAChF,EAAA,MAAA,CAAO,gBAAA,CAAiB,WAAW,aAAA,EAAe,EAAE,SAAS,IAAA,EAAM,OAAA,EAAS,MAAM,CAAA;AAElF,EAAA,MAAA,CAAO,EAAA,CAAG,aAAa,CAAA,IAAA,KAAQ;AAC7B,IAAA,IAAI,WAAW,IAAI,CAAA,CAAE,UAAA,GAAa,4BAA4B,MAAM,YAAA,EAAc;AAChF,MAAA;AAAA,IACF;AAIA,IAAA,kBAAA,GACE,6BAA6B,IAAA,GAAO,EAAE,IAAA,EAAM,oBAAA,EAAsB,2BAA0B,GAAI,MAAA;AAAA,EACpG,CAAC,CAAA;AAED,EAAA,MAAM,+BAAA,GAAkC,CAAC,EAAE,OAAA,EAAQ,KAA6C;AAC9F,IAAA,KAAA,MAAW,SAAS,OAAA,EAAS;AAC3B,MAAA,MAAM,OAAA,GAAU,kBAAA;AAChB,MAAA,IAAI,CAAC,WAAW,CAAC,wBAAA,CAAyB,KAAK,CAAA,IAAK,CAAC,MAAM,aAAA,EAAe;AACxE,QAAA;AAAA,MACF;AAEA,MAAA,IAAI,KAAK,GAAA,CAAI,KAAA,CAAM,YAAY,OAAA,CAAQ,oBAAoB,IAAI,8BAAA,EAAgC;AAC7F,QAAA;AAAA,MACF;AAEA,MAAA,8BAAA,CAA+B,GAAA,CAAI,KAAA,CAAM,aAAA,EAAe,OAAA,CAAQ,IAAI,CAAA;AACpE,MAAA,kBAAA,GAAqB,MAAA;AAAA,IACvB;AAAA,EACF,CAAA;AAIA,EAAA,oCAAA,CAAqC,SAAS,+BAA+B,CAAA;AAC7E,EAAA,oCAAA,CAAqC,eAAe,+BAA+B,CAAA;AAEnF,EAAA,oCAAA,CAAqC,iBAAA,EAAmB,CAAC,EAAE,OAAA,EAAQ,KAAM;AACvE,IAAA,KAAA,MAAW,SAAS,OAAA,EAAwC;AAC1D,MAAA,MAAM,IAAA,GAAO,8BAAA,CAA+B,GAAA,CAAI,KAAA,CAAM,aAAa,CAAA;AACnE,MAAA,IAAI,CAAC,IAAA,EAAM;AACT,QAAA,WAAA,IAAe,MAAM,GAAA,CAAI,CAAA,uDAAA,EAA0D,KAAA,CAAM,YAAY,IAAI,KAAK,CAAA;AAC9G,QAAA;AAAA,MACF;AAEA,MAAA,6BAAA,CAA8B,GAAA,CAAI,KAAA,CAAM,YAAA,EAAc,IAAI,CAAA;AAG1D,MAAA,IAAA,CAAK,YAAA,CAAa,qBAAA,EAAuB,KAAA,CAAM,YAAY,CAAA;AAAA,IAC7D;AAAA,EACF,CAAC,CAAA;AACH;AAMO,SAAS,2BAA2B,MAAA,EAAyC;AAClF,EAAA,IAAI,MAAA,CAAO,mBAAmB,iBAAA,EAAmB;AAC/C,IAAA,OAAO,MAAA;AAAA,EACT;AAEA,EAAA,MAAM,IAAA,GAAO,6BAAA,CAA8B,GAAA,CAAI,MAAA,CAAO,YAAY,CAAA;AAClE,EAAA,IAAI,IAAA,EAAM;AACR,IAAA,OAAO,IAAA;AAAA,EACT;AAKA,EAAA,OAAO,OAAO,uBAAA,IAA2B,IAAA,GACrC,+BAA+B,GAAA,CAAI,MAAA,CAAO,uBAAuB,CAAA,GACjE,MAAA;AACN;;;;"}
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import { browserPerformanceTimeOrigin, hasSpanStreamingEnabled,
|
|
2
|
-
import { startInactiveSpan } from '@sentry/core/browser';
|
|
1
|
+
import { browserPerformanceTimeOrigin, hasSpanStreamingEnabled, spanToJSON, SEMANTIC_ATTRIBUTE_SENTRY_OP, debug, timestampInSeconds, getActiveSpan, getRootSpan, SEMANTIC_ATTRIBUTE_EXCLUSIVE_TIME } from '@sentry/core';
|
|
3
2
|
import { DEBUG_BUILD } from '../debug-build.js';
|
|
4
3
|
import { htmlTreeAsString } from '../htmlTreeAsString.js';
|
|
5
|
-
import { WINDOW } from '../types.js';
|
|
6
4
|
import { MAX_PLAUSIBLE_INP_DURATION, INP_ENTRY_MAP, getCachedInteractionContext } from './inp.js';
|
|
7
5
|
import { addClsInstrumentationHandler, addInpInstrumentationHandler, addLcpInstrumentationHandler } from '../instrumentation/performanceObserver.js';
|
|
6
|
+
import { BROWSER_NAVIGATION_TYPE } from '@sentry/conventions/attributes';
|
|
7
|
+
import { _emitWebVitalSpan } from './emitSpan.js';
|
|
8
8
|
import { isValidLcpMetric } from './lcp.js';
|
|
9
9
|
import { listenForWebVitalReportEvents } from './reportEvents.js';
|
|
10
|
+
import { getNavigationSpanForMetric } from './softNavs.js';
|
|
10
11
|
import { supportsWebVital, getBrowserPerformanceAPI, msToSec } from '../performance/utils.js';
|
|
11
|
-
import {
|
|
12
|
-
import { UI_WEBVITAL_CLS, UI_INTERACTION_PRESS, UI_INTERACTION_DRAG, UI_INTERACTION_HOVER, UI_INTERACTION_CLICK, UI_WEBVITAL_LCP } from '@sentry/conventions/op';
|
|
12
|
+
import { NAVIGATION, UI_WEBVITAL_CLS, UI_INTERACTION_PRESS, UI_INTERACTION_DRAG, UI_INTERACTION_HOVER, UI_INTERACTION_CLICK, UI_WEBVITAL_LCP } from '@sentry/conventions/op';
|
|
13
13
|
|
|
14
14
|
const INTERACTION_TYPE_TO_SPAN_OP = {
|
|
15
15
|
click: UI_INTERACTION_CLICK,
|
|
@@ -17,73 +17,59 @@ const INTERACTION_TYPE_TO_SPAN_OP = {
|
|
|
17
17
|
drag: UI_INTERACTION_DRAG,
|
|
18
18
|
press: UI_INTERACTION_PRESS
|
|
19
19
|
};
|
|
20
|
-
function
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
attributes
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
};
|
|
48
|
-
if (parentSpan && spanToJSON(parentSpan).attributes[SEMANTIC_ATTRIBUTE_SENTRY_OP] === "pageload") {
|
|
49
|
-
attributes["sentry.pageload.span_id"] = parentSpan.spanContext().spanId;
|
|
50
|
-
}
|
|
51
|
-
if (reportEvent) {
|
|
52
|
-
attributes[`browser.web_vital.${metricName}.report_event`] = reportEvent;
|
|
53
|
-
}
|
|
54
|
-
if (standalone) {
|
|
55
|
-
Object.assign(attributes, getReplayAttributes());
|
|
56
|
-
}
|
|
57
|
-
const span = startInactiveSpan({
|
|
58
|
-
name,
|
|
59
|
-
attributes,
|
|
60
|
-
startTime,
|
|
61
|
-
parentSpan,
|
|
62
|
-
// oxlint-disable-next-line typescript/no-deprecated -- intentional during the v1/v2 transition; see the TODO(standalone) above
|
|
63
|
-
experimental: standalone ? { standalone: true } : void 0
|
|
20
|
+
function trackWebVitalPerNavigation(client, addInstrumentationHandler, send) {
|
|
21
|
+
let pageloadSpan;
|
|
22
|
+
client.on("afterStartPageLoadSpan", (span) => {
|
|
23
|
+
pageloadSpan = span;
|
|
24
|
+
});
|
|
25
|
+
let bfcacheNavigationSpan;
|
|
26
|
+
client.on("spanStart", (span) => {
|
|
27
|
+
const attributes = spanToJSON(span).attributes;
|
|
28
|
+
if (attributes?.[SEMANTIC_ATTRIBUTE_SENTRY_OP] === NAVIGATION && attributes[BROWSER_NAVIGATION_TYPE] === "bfcache") {
|
|
29
|
+
bfcacheNavigationSpan = span;
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
addInstrumentationHandler(({ metric }) => {
|
|
33
|
+
const navigationSpan = getNavigationSpanForMetric(metric);
|
|
34
|
+
if (metric.navigationType === "soft-navigation") {
|
|
35
|
+
if (navigationSpan) {
|
|
36
|
+
send(metric, navigationSpan, metric.navigationId);
|
|
37
|
+
} else {
|
|
38
|
+
DEBUG_BUILD && debug.log(`[SoftNav] Dropping ${metric.name} for uncorrelated soft navigation ${metric.navigationId}`);
|
|
39
|
+
}
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
if (metric.navigationType === "back-forward-cache") {
|
|
43
|
+
send(metric, bfcacheNavigationSpan, void 0);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
send(metric, pageloadSpan, void 0);
|
|
64
47
|
});
|
|
65
|
-
if (span) {
|
|
66
|
-
span.end(endTime ?? startTime);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
function getReplayAttributes() {
|
|
70
|
-
const replay = getClient()?.getIntegrationByName("Replay");
|
|
71
|
-
const replayId = replay?.getReplayId(true);
|
|
72
|
-
if (!replayId) {
|
|
73
|
-
return {};
|
|
74
|
-
}
|
|
75
|
-
return {
|
|
76
|
-
"sentry.replay_id": replayId,
|
|
77
|
-
"sentry._internal.replay_is_buffering": replay.getRecordingMode() === "buffer" ? true : void 0
|
|
78
|
-
};
|
|
79
48
|
}
|
|
80
|
-
function trackLcpAsSpan(client) {
|
|
81
|
-
let lcpValue = 0;
|
|
82
|
-
let lcpEntry;
|
|
49
|
+
function trackLcpAsSpan(client, perNavigation = false) {
|
|
83
50
|
if (!supportsWebVital("largest-contentful-paint")) {
|
|
84
51
|
return;
|
|
85
52
|
}
|
|
53
|
+
if (perNavigation) {
|
|
54
|
+
trackWebVitalPerNavigation(client, addLcpInstrumentationHandler, (metric, parentSpan, softNavigationId) => {
|
|
55
|
+
const entry = metric.entries[metric.entries.length - 1];
|
|
56
|
+
_sendLcpSpan(
|
|
57
|
+
metric.value,
|
|
58
|
+
entry,
|
|
59
|
+
parentSpan,
|
|
60
|
+
void 0,
|
|
61
|
+
softNavigationId,
|
|
62
|
+
metric.navigationType,
|
|
63
|
+
metric.navigationStartTime
|
|
64
|
+
);
|
|
65
|
+
});
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
let lcpValue = 0;
|
|
69
|
+
let lcpEntry;
|
|
70
|
+
let lcpNavigationType;
|
|
86
71
|
const cleanupLcpHandler = addLcpInstrumentationHandler(({ metric }) => {
|
|
72
|
+
lcpNavigationType = metric.navigationType;
|
|
87
73
|
const entry = metric.entries[metric.entries.length - 1];
|
|
88
74
|
if (!entry || !isValidLcpMetric(metric.value)) {
|
|
89
75
|
return;
|
|
@@ -92,18 +78,18 @@ function trackLcpAsSpan(client) {
|
|
|
92
78
|
lcpEntry = entry;
|
|
93
79
|
}, true);
|
|
94
80
|
listenForWebVitalReportEvents(client, (reportEvent, _, pageloadSpan) => {
|
|
95
|
-
_sendLcpSpan(lcpValue, lcpEntry, pageloadSpan, reportEvent);
|
|
81
|
+
_sendLcpSpan(lcpValue, lcpEntry, pageloadSpan, reportEvent, void 0, lcpNavigationType);
|
|
96
82
|
cleanupLcpHandler();
|
|
97
83
|
});
|
|
98
84
|
}
|
|
99
|
-
function _sendLcpSpan(lcpValue, entry, pageloadSpan, reportEvent) {
|
|
85
|
+
function _sendLcpSpan(lcpValue, entry, pageloadSpan, reportEvent, softNavigationId, navigationType, navigationStartTime) {
|
|
100
86
|
if (!isValidLcpMetric(lcpValue)) {
|
|
101
87
|
return;
|
|
102
88
|
}
|
|
103
89
|
DEBUG_BUILD && debug.log(`Sending LCP span (${lcpValue})`);
|
|
104
90
|
const performanceTimeOrigin = browserPerformanceTimeOrigin() || 0;
|
|
105
|
-
const
|
|
106
|
-
const endTime = msToSec(performanceTimeOrigin +
|
|
91
|
+
const startTime = msToSec(performanceTimeOrigin + (navigationStartTime || 0));
|
|
92
|
+
const endTime = entry ? msToSec(performanceTimeOrigin + entry.startTime) : startTime + msToSec(lcpValue);
|
|
107
93
|
const name = entry ? htmlTreeAsString(entry.element) : "Largest contentful paint";
|
|
108
94
|
const attributes = {};
|
|
109
95
|
entry?.element && (attributes["browser.web_vital.lcp.element"] = htmlTreeAsString(entry.element));
|
|
@@ -121,17 +107,36 @@ function _sendLcpSpan(lcpValue, entry, pageloadSpan, reportEvent) {
|
|
|
121
107
|
attributes,
|
|
122
108
|
parentSpan: pageloadSpan,
|
|
123
109
|
reportEvent,
|
|
124
|
-
startTime
|
|
125
|
-
endTime
|
|
110
|
+
startTime,
|
|
111
|
+
endTime,
|
|
112
|
+
softNavigationId,
|
|
113
|
+
navigationType
|
|
126
114
|
});
|
|
127
115
|
}
|
|
128
|
-
function trackClsAsSpan(client) {
|
|
129
|
-
let clsValue = 0;
|
|
130
|
-
let clsEntry;
|
|
116
|
+
function trackClsAsSpan(client, perNavigation = false) {
|
|
131
117
|
if (!supportsWebVital("layout-shift")) {
|
|
132
118
|
return;
|
|
133
119
|
}
|
|
120
|
+
if (perNavigation) {
|
|
121
|
+
trackWebVitalPerNavigation(client, addClsInstrumentationHandler, (metric, parentSpan, softNavigationId) => {
|
|
122
|
+
const entry = metric.entries[metric.entries.length - 1];
|
|
123
|
+
_sendClsSpan(
|
|
124
|
+
metric.value,
|
|
125
|
+
entry,
|
|
126
|
+
parentSpan,
|
|
127
|
+
void 0,
|
|
128
|
+
softNavigationId,
|
|
129
|
+
metric.navigationType,
|
|
130
|
+
metric.navigationStartTime
|
|
131
|
+
);
|
|
132
|
+
});
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
let clsValue = 0;
|
|
136
|
+
let clsEntry;
|
|
137
|
+
let clsNavigationType;
|
|
134
138
|
const cleanupClsHandler = addClsInstrumentationHandler(({ metric }) => {
|
|
139
|
+
clsNavigationType = metric.navigationType;
|
|
135
140
|
const entry = metric.entries[metric.entries.length - 1];
|
|
136
141
|
if (!entry) {
|
|
137
142
|
return;
|
|
@@ -140,13 +145,15 @@ function trackClsAsSpan(client) {
|
|
|
140
145
|
clsEntry = entry;
|
|
141
146
|
}, true);
|
|
142
147
|
listenForWebVitalReportEvents(client, (reportEvent, _, pageloadSpan) => {
|
|
143
|
-
_sendClsSpan(clsValue, clsEntry, pageloadSpan, reportEvent);
|
|
148
|
+
_sendClsSpan(clsValue, clsEntry, pageloadSpan, reportEvent, void 0, clsNavigationType);
|
|
144
149
|
cleanupClsHandler();
|
|
145
150
|
});
|
|
146
151
|
}
|
|
147
|
-
function _sendClsSpan(clsValue, entry, pageloadSpan, reportEvent) {
|
|
152
|
+
function _sendClsSpan(clsValue, entry, pageloadSpan, reportEvent, softNavigationId, navigationType, navigationStartTime) {
|
|
148
153
|
DEBUG_BUILD && debug.log(`Sending CLS span (${clsValue})`);
|
|
149
|
-
const
|
|
154
|
+
const performanceTimeOrigin = browserPerformanceTimeOrigin();
|
|
155
|
+
const offset = entry?.startTime ?? navigationStartTime ?? 0;
|
|
156
|
+
const startTime = performanceTimeOrigin ? msToSec(performanceTimeOrigin + offset) : timestampInSeconds();
|
|
150
157
|
const name = entry ? htmlTreeAsString(entry.sources[0]?.node) : "Layout shift";
|
|
151
158
|
const attributes = {};
|
|
152
159
|
if (entry?.sources) {
|
|
@@ -163,44 +170,50 @@ function _sendClsSpan(clsValue, entry, pageloadSpan, reportEvent) {
|
|
|
163
170
|
attributes,
|
|
164
171
|
parentSpan: pageloadSpan,
|
|
165
172
|
reportEvent,
|
|
166
|
-
startTime
|
|
173
|
+
startTime,
|
|
174
|
+
softNavigationId,
|
|
175
|
+
navigationType
|
|
167
176
|
});
|
|
168
177
|
}
|
|
169
|
-
function trackInpAsSpan(client) {
|
|
178
|
+
function trackInpAsSpan(client, perNavigation = false) {
|
|
170
179
|
const performance = getBrowserPerformanceAPI();
|
|
171
180
|
if (!performance || !browserPerformanceTimeOrigin()) {
|
|
172
181
|
return;
|
|
173
182
|
}
|
|
174
183
|
const standalone = !hasSpanStreamingEnabled(client);
|
|
184
|
+
if (perNavigation) {
|
|
185
|
+
trackWebVitalPerNavigation(client, addInpInstrumentationHandler, (metric, parentSpan, softNavigationId) => {
|
|
186
|
+
if (isPlausibleInp(metric)) {
|
|
187
|
+
_sendInpSpan(metric.value, findInpEntry(metric), standalone, parentSpan, softNavigationId, metric);
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
175
192
|
const onInp = ({ metric }) => {
|
|
176
|
-
if (metric
|
|
177
|
-
|
|
193
|
+
if (isPlausibleInp(metric)) {
|
|
194
|
+
_sendInpSpan(metric.value, findInpEntry(metric), standalone, void 0, void 0, metric);
|
|
178
195
|
}
|
|
179
|
-
const duration = msToSec(metric.value);
|
|
180
|
-
if (duration > MAX_PLAUSIBLE_INP_DURATION) {
|
|
181
|
-
return;
|
|
182
|
-
}
|
|
183
|
-
const entry = metric.entries.find((e) => e.duration === metric.value && INP_ENTRY_MAP[e.name]);
|
|
184
|
-
if (!entry) {
|
|
185
|
-
return;
|
|
186
|
-
}
|
|
187
|
-
_sendInpSpan(metric.value, entry, standalone);
|
|
188
196
|
};
|
|
189
197
|
addInpInstrumentationHandler(onInp);
|
|
190
198
|
}
|
|
191
|
-
function
|
|
199
|
+
function isPlausibleInp(metric) {
|
|
200
|
+
return metric.value != null && msToSec(metric.value) <= MAX_PLAUSIBLE_INP_DURATION;
|
|
201
|
+
}
|
|
202
|
+
function findInpEntry(metric) {
|
|
203
|
+
return metric.entries.find((e) => e.duration === metric.value && INP_ENTRY_MAP[e.name]);
|
|
204
|
+
}
|
|
205
|
+
function _sendInpSpan(inpValue, entry, standalone = false, attributedSpan, softNavigationId, metric) {
|
|
192
206
|
DEBUG_BUILD && debug.log(`Sending INP span (${inpValue})`);
|
|
193
|
-
const startTime = msToSec(
|
|
207
|
+
const startTime = msToSec(
|
|
208
|
+
browserPerformanceTimeOrigin() + (entry?.startTime ?? metric?.navigationStartTime ?? 0)
|
|
209
|
+
);
|
|
194
210
|
const duration = msToSec(inpValue);
|
|
195
|
-
const interactionType = INP_ENTRY_MAP[entry.name];
|
|
196
|
-
|
|
197
|
-
return;
|
|
198
|
-
}
|
|
199
|
-
const cachedContext = getCachedInteractionContext(entry.interactionId);
|
|
211
|
+
const interactionType = entry && INP_ENTRY_MAP[entry.name] || "click";
|
|
212
|
+
const cachedContext = entry && getCachedInteractionContext(entry.interactionId);
|
|
200
213
|
const activeSpan = getActiveSpan();
|
|
201
214
|
const rootSpan = activeSpan ? getRootSpan(activeSpan) : void 0;
|
|
202
|
-
const spanToUse = cachedContext?.span || rootSpan;
|
|
203
|
-
const name = cachedContext?.elementName || htmlTreeAsString(entry.target);
|
|
215
|
+
const spanToUse = attributedSpan || cachedContext?.span || rootSpan;
|
|
216
|
+
const name = cachedContext?.elementName || (entry ? htmlTreeAsString(entry.target) : "Interaction to next paint");
|
|
204
217
|
_emitWebVitalSpan({
|
|
205
218
|
name,
|
|
206
219
|
op: INTERACTION_TYPE_TO_SPAN_OP[interactionType],
|
|
@@ -208,14 +221,16 @@ function _sendInpSpan(inpValue, entry, standalone = false) {
|
|
|
208
221
|
metricName: "inp",
|
|
209
222
|
value: inpValue,
|
|
210
223
|
attributes: {
|
|
211
|
-
[SEMANTIC_ATTRIBUTE_EXCLUSIVE_TIME]: entry
|
|
224
|
+
[SEMANTIC_ATTRIBUTE_EXCLUSIVE_TIME]: entry?.duration ?? inpValue
|
|
212
225
|
},
|
|
213
226
|
startTime,
|
|
214
227
|
endTime: startTime + duration,
|
|
228
|
+
navigationType: metric?.navigationType,
|
|
215
229
|
parentSpan: spanToUse,
|
|
216
|
-
standalone
|
|
230
|
+
standalone,
|
|
231
|
+
softNavigationId
|
|
217
232
|
});
|
|
218
233
|
}
|
|
219
234
|
|
|
220
|
-
export {
|
|
235
|
+
export { _sendClsSpan, _sendInpSpan, _sendLcpSpan, trackClsAsSpan, trackInpAsSpan, trackLcpAsSpan };
|
|
221
236
|
//# sourceMappingURL=spans.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"spans.js","sources":["../../../src/web-vitals/spans.ts"],"sourcesContent":["import type { Client, Integration, Span, SpanAttributes } from '@sentry/core';\nimport {\n browserPerformanceTimeOrigin,\n debug,\n getActiveSpan,\n getClient,\n getCurrentScope,\n getRootSpan,\n hasSpanStreamingEnabled,\n SEMANTIC_ATTRIBUTE_EXCLUSIVE_TIME,\n SEMANTIC_ATTRIBUTE_SENTRY_OP,\n SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,\n spanToJSON,\n timestampInSeconds,\n} from '@sentry/core';\nimport { startInactiveSpan } from '@sentry/core/browser';\nimport { DEBUG_BUILD } from '../debug-build';\nimport { htmlTreeAsString } from '../htmlTreeAsString';\nimport { WINDOW } from '../types';\nimport type { InteractionType } from './inp';\nimport { getCachedInteractionContext, INP_ENTRY_MAP, MAX_PLAUSIBLE_INP_DURATION } from './inp';\nimport type { InstrumentationHandlerCallback } from '../instrumentation/performanceObserver';\nimport {\n addClsInstrumentationHandler,\n addInpInstrumentationHandler,\n addLcpInstrumentationHandler,\n} from '../instrumentation/performanceObserver';\nimport { isValidLcpMetric } from './lcp';\nimport type { WebVitalReportEvent } from './reportEvents';\nimport { listenForWebVitalReportEvents } from './reportEvents';\nimport { getBrowserPerformanceAPI, msToSec, supportsWebVital } from '../performance/utils';\nimport type { PerformanceEventTiming } from '../instrumentation/performanceObserver';\nimport { SENTRY_SEGMENT_NAME, SENTRY_TRANSACTION } from '@sentry/conventions/attributes';\nimport {\n UI_INTERACTION_CLICK,\n UI_INTERACTION_DRAG,\n UI_INTERACTION_HOVER,\n UI_INTERACTION_PRESS,\n UI_WEBVITAL_CLS,\n UI_WEBVITAL_LCP,\n} from '@sentry/conventions/op';\n\nconst INTERACTION_TYPE_TO_SPAN_OP: Record<InteractionType, string> = {\n click: UI_INTERACTION_CLICK,\n hover: UI_INTERACTION_HOVER,\n drag: UI_INTERACTION_DRAG,\n press: UI_INTERACTION_PRESS,\n};\n\n// Locally-defined interfaces to avoid leaking bare global type references into the\n// generated .d.ts. The `declare global` augmentations in web-vitals/types.ts make these\n// available during this package's compilation but are NOT carried to consumers.\n// This mirrors the pattern used for PerformanceEventTiming in instrument.ts.\nexport interface LayoutShift extends PerformanceEntry {\n value: number;\n sources: Array<{ node: Node | null }>;\n hadRecentInput: boolean;\n}\n\nexport interface LargestContentfulPaint extends PerformanceEntry {\n readonly renderTime: DOMHighResTimeStamp;\n readonly loadTime: DOMHighResTimeStamp;\n readonly size: number;\n readonly id: string;\n readonly url: string;\n readonly element: Element | null;\n}\n\ninterface WebVitalSpanOptions {\n name: string;\n op: string;\n origin: string;\n metricName: 'lcp' | 'cls' | 'inp';\n value: number;\n attributes?: SpanAttributes;\n parentSpan?: Span;\n reportEvent?: WebVitalReportEvent;\n startTime: number;\n endTime?: number;\n /**\n * When `true`, the span is sent on its own as a v2 streamed span instead of being folded into a\n * transaction. Used for INP when span streaming is disabled (it reports late, so it can't ride\n * the pageload transaction).\n *\n * TODO(standalone): remove once the static (transaction) trace lifecycle is dropped and INP always streams.\n */\n standalone?: boolean;\n}\n\n/**\n * Emits a web vital span. When `standalone` is set it is sent on its own as a v2 streamed span;\n * otherwise it flows through the span streaming pipeline as a child of `parentSpan`.\n */\nexport function _emitWebVitalSpan(options: WebVitalSpanOptions): void {\n const {\n name,\n op,\n origin,\n metricName,\n value,\n attributes: passedAttributes,\n parentSpan,\n reportEvent,\n startTime,\n endTime,\n standalone,\n } = options;\n\n // Taken off the segment span itself, so it can't diverge from it: a routing instrumentation may\n // rename that span (a pageload span is named `Pageload` until its route resolves), and the scope's\n // transaction name is deliberately not kept in sync with it. Only a standalone span, which is sent\n // without its segment span, has to fall back to the scope.\n const segmentSpan = parentSpan && getRootSpan(parentSpan);\n const segmentName = segmentSpan ? spanToJSON(segmentSpan).name : getCurrentScope().getScopeData().transactionName;\n\n const attributes: SpanAttributes = {\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: origin,\n [SEMANTIC_ATTRIBUTE_SENTRY_OP]: op,\n [SEMANTIC_ATTRIBUTE_EXCLUSIVE_TIME]: 0,\n [`browser.web_vital.${metricName}.value`]: value,\n // oxlint-disable-next-line typescript-eslint/no-deprecated\n [SENTRY_TRANSACTION]: segmentName,\n [SENTRY_SEGMENT_NAME]: segmentName,\n // Web vital score calculation relies on the user agent\n 'user_agent.original': WINDOW.navigator?.userAgent,\n ...passedAttributes,\n };\n\n if (parentSpan && spanToJSON(parentSpan).attributes[SEMANTIC_ATTRIBUTE_SENTRY_OP] === 'pageload') {\n // for LCP and CLS, we collect the pageload span id as an attribute\n attributes['sentry.pageload.span_id'] = parentSpan.spanContext().spanId;\n }\n\n if (reportEvent) {\n attributes[`browser.web_vital.${metricName}.report_event`] = reportEvent;\n }\n\n // A standalone span is sent as a plain v2 span without running the `processSpan` hooks (see\n // `captureStandaloneSpanWithStaticCallback`), so Replay can't attach the replay id itself. Set it\n // here, mirroring Replay's `processSpan`, so INP keeps its replay association like it did on v1.\n // TODO(standalone): remove once the static (transaction) trace lifecycle is dropped and INP always\n // streams, at which point Replay's `processSpan` runs and attaches the replay id.\n if (standalone) {\n Object.assign(attributes, getReplayAttributes());\n }\n\n const span = startInactiveSpan({\n name,\n attributes,\n startTime,\n parentSpan,\n // oxlint-disable-next-line typescript/no-deprecated -- intentional during the v1/v2 transition; see the TODO(standalone) above\n experimental: standalone ? { standalone: true } : undefined,\n });\n\n if (span) {\n span.end(endTime ?? startTime);\n }\n}\n\ninterface ReplayIntegration extends Integration {\n getReplayId: (onlyIfSampled?: boolean) => string | undefined;\n getRecordingMode: () => 'session' | 'buffer' | undefined;\n}\n\n// TODO(standalone): remove once the static (transaction) trace lifecycle is dropped; Replay's\n// `processSpan` then attaches the replay id to the streamed INP span instead.\nfunction getReplayAttributes(): SpanAttributes {\n const replay = getClient()?.getIntegrationByName<ReplayIntegration>('Replay');\n const replayId = replay?.getReplayId(true);\n if (!replayId) {\n return {};\n }\n\n return {\n 'sentry.replay_id': replayId,\n 'sentry._internal.replay_is_buffering': replay!.getRecordingMode() === 'buffer' ? true : undefined,\n };\n}\n\n/**\n * Tracks LCP as a streamed span.\n */\nexport function trackLcpAsSpan(client: Client): void {\n let lcpValue = 0;\n let lcpEntry: LargestContentfulPaint | undefined;\n\n if (!supportsWebVital('largest-contentful-paint')) {\n return;\n }\n\n const cleanupLcpHandler = addLcpInstrumentationHandler(({ metric }) => {\n const entry = metric.entries[metric.entries.length - 1] as LargestContentfulPaint | undefined;\n if (!entry || !isValidLcpMetric(metric.value)) {\n return;\n }\n lcpValue = metric.value;\n lcpEntry = entry;\n }, true);\n\n listenForWebVitalReportEvents(client, (reportEvent, _, pageloadSpan) => {\n _sendLcpSpan(lcpValue, lcpEntry, pageloadSpan, reportEvent);\n cleanupLcpHandler();\n });\n}\n\n/**\n * Exported only for testing.\n */\nexport function _sendLcpSpan(\n lcpValue: number,\n entry: LargestContentfulPaint | undefined,\n pageloadSpan?: Span,\n reportEvent?: WebVitalReportEvent,\n): void {\n if (!isValidLcpMetric(lcpValue)) {\n return;\n }\n\n DEBUG_BUILD && debug.log(`Sending LCP span (${lcpValue})`);\n\n const performanceTimeOrigin = browserPerformanceTimeOrigin() || 0;\n const timeOrigin = msToSec(performanceTimeOrigin);\n const endTime = msToSec(performanceTimeOrigin + (entry?.startTime || 0));\n const name = entry ? htmlTreeAsString(entry.element) : 'Largest contentful paint';\n\n const attributes: SpanAttributes = {};\n\n entry?.element && (attributes['browser.web_vital.lcp.element'] = htmlTreeAsString(entry.element));\n entry?.id && (attributes['browser.web_vital.lcp.id'] = entry.id);\n entry?.url && (attributes['browser.web_vital.lcp.url'] = entry.url);\n entry?.loadTime != null && (attributes['browser.web_vital.lcp.load_time'] = entry.loadTime);\n entry?.renderTime != null && (attributes['browser.web_vital.lcp.render_time'] = entry.renderTime);\n entry?.size != null && (attributes['browser.web_vital.lcp.size'] = entry.size);\n\n _emitWebVitalSpan({\n name,\n op: UI_WEBVITAL_LCP,\n origin: 'auto.http.browser.lcp',\n metricName: 'lcp',\n value: lcpValue,\n attributes,\n parentSpan: pageloadSpan,\n reportEvent,\n startTime: timeOrigin,\n endTime,\n });\n}\n\n/**\n * Tracks CLS as a streamed span.\n */\nexport function trackClsAsSpan(client: Client): void {\n let clsValue = 0;\n let clsEntry: LayoutShift | undefined;\n\n if (!supportsWebVital('layout-shift')) {\n return;\n }\n\n const cleanupClsHandler = addClsInstrumentationHandler(({ metric }) => {\n const entry = metric.entries[metric.entries.length - 1] as LayoutShift | undefined;\n if (!entry) {\n return;\n }\n clsValue = metric.value;\n clsEntry = entry;\n }, true);\n\n listenForWebVitalReportEvents(client, (reportEvent, _, pageloadSpan) => {\n _sendClsSpan(clsValue, clsEntry, pageloadSpan, reportEvent);\n cleanupClsHandler();\n });\n}\n\n/**\n * Exported only for testing.\n */\nexport function _sendClsSpan(\n clsValue: number,\n entry: LayoutShift | undefined,\n pageloadSpan?: Span,\n reportEvent?: WebVitalReportEvent,\n): void {\n DEBUG_BUILD && debug.log(`Sending CLS span (${clsValue})`);\n\n const startTime = entry ? msToSec((browserPerformanceTimeOrigin() || 0) + entry.startTime) : timestampInSeconds();\n const name = entry ? htmlTreeAsString(entry.sources[0]?.node) : 'Layout shift';\n\n const attributes: SpanAttributes = {};\n\n if (entry?.sources) {\n entry.sources.forEach((source, index) => {\n attributes[`browser.web_vital.cls.source.${index + 1}`] = htmlTreeAsString(source.node);\n });\n }\n\n _emitWebVitalSpan({\n name,\n op: UI_WEBVITAL_CLS,\n origin: 'auto.http.browser.cls',\n metricName: 'cls',\n value: clsValue,\n attributes,\n parentSpan: pageloadSpan,\n reportEvent,\n startTime,\n });\n}\n\n/**\n * Tracks INP and emits it as a web vital span, as a child of the interaction's root span.\n * Requires `registerInpInteractionListener()` to be called separately for cached element names and\n * root spans per interaction.\n */\nexport function trackInpAsSpan(client: Client): void {\n const performance = getBrowserPerformanceAPI();\n if (!performance || !browserPerformanceTimeOrigin()) {\n return;\n }\n\n // INP reports late (on pagehide, after the pageload span has ended). With span streaming enabled\n // it rides the streaming pipeline. With streaming disabled it would be dropped as a late span, so\n // it is emitted as its own standalone v2 span instead (see `_emitWebVitalSpan`), overriding the\n // static trace lifecycle for INP only.\n // TODO(standalone): once the static trace lifecycle is dropped, INP always streams; drop this flag.\n const standalone = !hasSpanStreamingEnabled(client);\n\n const onInp: InstrumentationHandlerCallback = ({ metric }) => {\n if (metric.value == null) {\n return;\n }\n\n const duration = msToSec(metric.value);\n\n if (duration > MAX_PLAUSIBLE_INP_DURATION) {\n return;\n }\n\n const entry = metric.entries.find(e => e.duration === metric.value && INP_ENTRY_MAP[e.name]);\n\n if (!entry) {\n return;\n }\n\n _sendInpSpan(metric.value, entry, standalone);\n };\n\n addInpInstrumentationHandler(onInp);\n}\n\n/**\n * Exported only for testing.\n */\nexport function _sendInpSpan(inpValue: number, entry: PerformanceEventTiming, standalone = false): void {\n DEBUG_BUILD && debug.log(`Sending INP span (${inpValue})`);\n\n const startTime = msToSec((browserPerformanceTimeOrigin() as number) + entry.startTime);\n const duration = msToSec(inpValue);\n const interactionType = INP_ENTRY_MAP[entry.name];\n\n if (!interactionType) {\n return;\n }\n\n const cachedContext = getCachedInteractionContext(entry.interactionId);\n const activeSpan = getActiveSpan();\n const rootSpan = activeSpan ? getRootSpan(activeSpan) : undefined;\n\n const spanToUse = cachedContext?.span || rootSpan;\n const name = cachedContext?.elementName || htmlTreeAsString(entry.target);\n\n _emitWebVitalSpan({\n name,\n op: INTERACTION_TYPE_TO_SPAN_OP[interactionType],\n origin: 'auto.http.browser.inp',\n metricName: 'inp',\n value: inpValue,\n attributes: {\n [SEMANTIC_ATTRIBUTE_EXCLUSIVE_TIME]: entry.duration,\n },\n startTime,\n endTime: startTime + duration,\n parentSpan: spanToUse,\n standalone,\n });\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"spans.js","sources":["../../../src/web-vitals/spans.ts"],"sourcesContent":["import type { Client, Span, SpanAttributes } from '@sentry/core';\nimport {\n browserPerformanceTimeOrigin,\n debug,\n getActiveSpan,\n getRootSpan,\n hasSpanStreamingEnabled,\n SEMANTIC_ATTRIBUTE_EXCLUSIVE_TIME,\n SEMANTIC_ATTRIBUTE_SENTRY_OP,\n spanToJSON,\n timestampInSeconds,\n} from '@sentry/core';\nimport { DEBUG_BUILD } from '../debug-build';\nimport { htmlTreeAsString } from '../htmlTreeAsString';\nimport type { InteractionType } from './inp';\nimport { getCachedInteractionContext, INP_ENTRY_MAP, MAX_PLAUSIBLE_INP_DURATION } from './inp';\nimport type { InstrumentationHandlerCallback, MetricNavigationType } from '../instrumentation/performanceObserver';\nimport {\n addClsInstrumentationHandler,\n addInpInstrumentationHandler,\n addLcpInstrumentationHandler,\n} from '../instrumentation/performanceObserver';\nimport type { LargestContentfulPaint, LayoutShift } from './emitSpan';\nimport { BROWSER_NAVIGATION_TYPE } from '@sentry/conventions/attributes';\nimport { _emitWebVitalSpan } from './emitSpan';\nimport { isValidLcpMetric } from './lcp';\nimport type { WebVitalReportEvent } from './reportEvents';\nimport { listenForWebVitalReportEvents } from './reportEvents';\nimport { getNavigationSpanForMetric } from './softNavs';\nimport { getBrowserPerformanceAPI, msToSec, supportsWebVital } from '../performance/utils';\nimport type { PerformanceEventTiming } from '../instrumentation/performanceObserver';\nimport {\n NAVIGATION,\n UI_INTERACTION_CLICK,\n UI_INTERACTION_DRAG,\n UI_INTERACTION_HOVER,\n UI_INTERACTION_PRESS,\n UI_WEBVITAL_CLS,\n UI_WEBVITAL_LCP,\n} from '@sentry/conventions/op';\n\nconst INTERACTION_TYPE_TO_SPAN_OP: Record<InteractionType, string> = {\n click: UI_INTERACTION_CLICK,\n hover: UI_INTERACTION_HOVER,\n drag: UI_INTERACTION_DRAG,\n press: UI_INTERACTION_PRESS,\n};\n\ntype WebVitalMetric = Parameters<Parameters<typeof addLcpInstrumentationHandler>[0]>[0]['metric'];\ntype InpMetric = Parameters<InstrumentationHandlerCallback>[0]['metric'];\n\n/**\n * Reports a web vital once per navigation, rather than once per page load.\n *\n * web-vitals restarts the metric on every soft navigation and force-reports the previous one just\n * before it does (and again on pagehide), and re-reports every metric after a bfcache restore.\n * Since `reportAllChanges` is off in this mode, every value we're handed is already the final one\n * for its navigation, so there is nothing to accumulate: each report is a span.\n */\nfunction trackWebVitalPerNavigation<M extends WebVitalMetric>(\n client: Client,\n addInstrumentationHandler: (callback: (data: { metric: M }) => void) => unknown,\n send: (metric: M, parentSpan: Span | undefined, softNavigationId: number | undefined) => void,\n): void {\n let pageloadSpan: Span | undefined;\n client.on('afterStartPageLoadSpan', span => {\n pageloadSpan = span;\n });\n\n // Remembered when the restore happens rather than read back at report time: the restore\n // navigation span is an idle span, and CLS and INP are only finalized on pagehide, by which point\n // it has long ended and is no longer what is active.\n let bfcacheNavigationSpan: Span | undefined;\n client.on('spanStart', span => {\n // The op has to be checked too: the web vital spans emitted for a restore carry the same\n // `bfcache` navigation type, so matching on that alone lets the first of them replace the\n // navigation span, and every later vital then hangs off a sibling vital instead.\n const attributes = spanToJSON(span).attributes;\n if (\n attributes?.[SEMANTIC_ATTRIBUTE_SENTRY_OP] === NAVIGATION &&\n attributes[BROWSER_NAVIGATION_TYPE] === 'bfcache'\n ) {\n bfcacheNavigationSpan = span;\n }\n });\n\n addInstrumentationHandler(({ metric }) => {\n const navigationSpan = getNavigationSpanForMetric(metric);\n if (metric.navigationType === 'soft-navigation') {\n // Reporting an uncorrelated soft navigation vital would attribute it to the wrong route, so\n // it's dropped instead.\n if (navigationSpan) {\n send(metric, navigationSpan, metric.navigationId);\n } else {\n DEBUG_BUILD &&\n debug.log(`[SoftNav] Dropping ${metric.name} for uncorrelated soft navigation ${metric.navigationId}`);\n }\n return;\n }\n\n if (metric.navigationType === 'back-forward-cache') {\n // A restore reuses the frozen document, so the pageload span above belongs to the page view\n // from before the freeze. The navigation span started for the restore is the page view these\n // values were actually measured on.\n send(metric, bfcacheNavigationSpan, undefined);\n return;\n }\n\n send(metric, pageloadSpan, undefined);\n });\n}\n\n/**\n * Tracks LCP as a streamed span.\n */\nexport function trackLcpAsSpan(client: Client, perNavigation = false): void {\n if (!supportsWebVital('largest-contentful-paint')) {\n return;\n }\n\n if (perNavigation) {\n trackWebVitalPerNavigation(client, addLcpInstrumentationHandler, (metric, parentSpan, softNavigationId) => {\n const entry = metric.entries[metric.entries.length - 1] as LargestContentfulPaint | undefined;\n _sendLcpSpan(\n metric.value,\n entry,\n parentSpan,\n undefined,\n softNavigationId,\n metric.navigationType,\n metric.navigationStartTime,\n );\n });\n return;\n }\n\n let lcpValue = 0;\n let lcpEntry: LargestContentfulPaint | undefined;\n let lcpNavigationType: MetricNavigationType | undefined;\n\n const cleanupLcpHandler = addLcpInstrumentationHandler(({ metric }) => {\n // The navigation type describes the page, not the entry, so it is worth keeping even for a\n // report we otherwise discard.\n lcpNavigationType = metric.navigationType;\n\n const entry = metric.entries[metric.entries.length - 1] as LargestContentfulPaint | undefined;\n if (!entry || !isValidLcpMetric(metric.value)) {\n return;\n }\n lcpValue = metric.value;\n lcpEntry = entry;\n }, true);\n\n listenForWebVitalReportEvents(client, (reportEvent, _, pageloadSpan) => {\n _sendLcpSpan(lcpValue, lcpEntry, pageloadSpan, reportEvent, undefined, lcpNavigationType);\n cleanupLcpHandler();\n });\n}\n\n/**\n * Exported only for testing.\n */\nexport function _sendLcpSpan(\n lcpValue: number,\n entry: LargestContentfulPaint | undefined,\n pageloadSpan?: Span,\n reportEvent?: WebVitalReportEvent,\n softNavigationId?: number,\n navigationType?: MetricNavigationType,\n navigationStartTime?: number,\n): void {\n if (!isValidLcpMetric(lcpValue)) {\n return;\n }\n\n DEBUG_BUILD && debug.log(`Sending LCP span (${lcpValue})`);\n\n const performanceTimeOrigin = browserPerformanceTimeOrigin() || 0;\n // A soft navigation's LCP is measured from the triggering interaction, not the document time\n // origin. Starting the span there too keeps it inside the navigation span it is parented to and\n // keeps its duration equal to the value it reports.\n const startTime = msToSec(performanceTimeOrigin + (navigationStartTime || 0));\n // Without an entry there is no render time to end at, so the span lasts the value it reports,\n // like an entry-less INP does. Ending at the time origin instead would invert the span.\n const endTime = entry ? msToSec(performanceTimeOrigin + entry.startTime) : startTime + msToSec(lcpValue);\n const name = entry ? htmlTreeAsString(entry.element) : 'Largest contentful paint';\n\n const attributes: SpanAttributes = {};\n\n entry?.element && (attributes['browser.web_vital.lcp.element'] = htmlTreeAsString(entry.element));\n entry?.id && (attributes['browser.web_vital.lcp.id'] = entry.id);\n entry?.url && (attributes['browser.web_vital.lcp.url'] = entry.url);\n entry?.loadTime != null && (attributes['browser.web_vital.lcp.load_time'] = entry.loadTime);\n entry?.renderTime != null && (attributes['browser.web_vital.lcp.render_time'] = entry.renderTime);\n entry?.size != null && (attributes['browser.web_vital.lcp.size'] = entry.size);\n\n _emitWebVitalSpan({\n name,\n op: UI_WEBVITAL_LCP,\n origin: 'auto.http.browser.lcp',\n metricName: 'lcp',\n value: lcpValue,\n attributes,\n parentSpan: pageloadSpan,\n reportEvent,\n startTime,\n endTime,\n softNavigationId,\n navigationType,\n });\n}\n\n/**\n * Tracks CLS as a streamed span.\n */\nexport function trackClsAsSpan(client: Client, perNavigation = false): void {\n if (!supportsWebVital('layout-shift')) {\n return;\n }\n\n if (perNavigation) {\n trackWebVitalPerNavigation(client, addClsInstrumentationHandler, (metric, parentSpan, softNavigationId) => {\n const entry = metric.entries[metric.entries.length - 1] as LayoutShift | undefined;\n _sendClsSpan(\n metric.value,\n entry,\n parentSpan,\n undefined,\n softNavigationId,\n metric.navigationType,\n metric.navigationStartTime,\n );\n });\n return;\n }\n\n let clsValue = 0;\n let clsEntry: LayoutShift | undefined;\n let clsNavigationType: MetricNavigationType | undefined;\n\n const cleanupClsHandler = addClsInstrumentationHandler(({ metric }) => {\n // A CLS of 0 is reported with no entries and still emits a span, so the navigation type has to\n // be captured before the entry check rather than alongside the value.\n clsNavigationType = metric.navigationType;\n\n const entry = metric.entries[metric.entries.length - 1] as LayoutShift | undefined;\n if (!entry) {\n return;\n }\n clsValue = metric.value;\n clsEntry = entry;\n }, true);\n\n listenForWebVitalReportEvents(client, (reportEvent, _, pageloadSpan) => {\n _sendClsSpan(clsValue, clsEntry, pageloadSpan, reportEvent, undefined, clsNavigationType);\n cleanupClsHandler();\n });\n}\n\n/**\n * Exported only for testing.\n */\nexport function _sendClsSpan(\n clsValue: number,\n entry: LayoutShift | undefined,\n pageloadSpan?: Span,\n reportEvent?: WebVitalReportEvent,\n softNavigationId?: number,\n navigationType?: MetricNavigationType,\n navigationStartTime?: number,\n): void {\n DEBUG_BUILD && debug.log(`Sending CLS span (${clsValue})`);\n\n const performanceTimeOrigin = browserPerformanceTimeOrigin();\n // A CLS of 0 has no shift to place the span at. It is reported when the navigation it was\n // measured on is already over - the next soft navigation, or pagehide - so the current time would\n // land it outside that navigation, on the route that follows it.\n const offset = entry?.startTime ?? navigationStartTime ?? 0;\n const startTime = performanceTimeOrigin ? msToSec(performanceTimeOrigin + offset) : timestampInSeconds();\n const name = entry ? htmlTreeAsString(entry.sources[0]?.node) : 'Layout shift';\n\n const attributes: SpanAttributes = {};\n\n if (entry?.sources) {\n entry.sources.forEach((source, index) => {\n attributes[`browser.web_vital.cls.source.${index + 1}`] = htmlTreeAsString(source.node);\n });\n }\n\n _emitWebVitalSpan({\n name,\n op: UI_WEBVITAL_CLS,\n origin: 'auto.http.browser.cls',\n metricName: 'cls',\n value: clsValue,\n attributes,\n parentSpan: pageloadSpan,\n reportEvent,\n startTime,\n softNavigationId,\n navigationType,\n });\n}\n\n/**\n * Tracks INP and emits it as a web vital span, as a child of the interaction's root span.\n * Requires `registerInpInteractionListener()` to be called separately for cached element names and\n * root spans per interaction.\n */\nexport function trackInpAsSpan(client: Client, perNavigation = false): void {\n const performance = getBrowserPerformanceAPI();\n if (!performance || !browserPerformanceTimeOrigin()) {\n return;\n }\n\n // INP reports late (on pagehide, after the pageload span has ended). With span streaming enabled\n // it rides the streaming pipeline. With streaming disabled it would be dropped as a late span, so\n // it is emitted as its own standalone v2 span instead (see `_emitWebVitalSpan`), overriding the\n // static trace lifecycle for INP only.\n // TODO(standalone): once the static trace lifecycle is dropped, INP always streams; drop this flag.\n const standalone = !hasSpanStreamingEnabled(client);\n\n if (perNavigation) {\n // INP restarts per navigation and reports once that navigation is over, by which point the\n // navigation span has ended and the interaction cache no longer knows about it. The metric\n // says which navigation it belongs to, so INP is attributed exactly like LCP and CLS.\n trackWebVitalPerNavigation(client, addInpInstrumentationHandler, (metric, parentSpan, softNavigationId) => {\n if (isPlausibleInp(metric)) {\n _sendInpSpan(metric.value, findInpEntry(metric), standalone, parentSpan, softNavigationId, metric);\n }\n });\n return;\n }\n\n const onInp: InstrumentationHandlerCallback = ({ metric }) => {\n if (isPlausibleInp(metric)) {\n _sendInpSpan(metric.value, findInpEntry(metric), standalone, undefined, undefined, metric);\n }\n };\n\n addInpInstrumentationHandler(onInp);\n}\n\nfunction isPlausibleInp(metric: InpMetric): boolean {\n return metric.value != null && msToSec(metric.value) <= MAX_PLAUSIBLE_INP_DURATION;\n}\n\n/**\n * The entry an INP span is built from: the one whose duration the reported value came from.\n *\n * There isn't always one. When every interaction of a soft navigation stayed below the Event Timing\n * threshold, web-vitals reports a synthetic value with no entries at all - see\n * `_estimateP98LongestInteraction`. The span still gets reported in that case, just without the\n * element and interaction type an entry would have supplied.\n */\nfunction findInpEntry(metric: InpMetric): PerformanceEventTiming | undefined {\n return metric.entries.find(e => e.duration === metric.value && INP_ENTRY_MAP[e.name]);\n}\n\n/**\n * Exported only for testing.\n */\nexport function _sendInpSpan(\n inpValue: number,\n entry: PerformanceEventTiming | undefined,\n standalone = false,\n attributedSpan?: Span,\n softNavigationId?: number,\n metric?: InpMetric,\n): void {\n DEBUG_BUILD && debug.log(`Sending INP span (${inpValue})`);\n\n // A web vital span carries the metric, not a real interaction timing, so an INP without an entry\n // is still worth reporting. It just has no element or interaction type to describe, and is placed\n // at the start of the navigation it belongs to rather than at the interaction.\n const startTime = msToSec(\n (browserPerformanceTimeOrigin() as number) + (entry?.startTime ?? metric?.navigationStartTime ?? 0),\n );\n const duration = msToSec(inpValue);\n // An INP without an entry has no interaction type to report. It still has to land inside the\n // `ui.interaction.*` family, because falling outside it would hide exactly the fast navigations\n // that web-vitals synthesizes these values for (GoogleChrome/web-vitals#724), reintroducing the\n // reporting bias they were added to remove.\n const interactionType = (entry && INP_ENTRY_MAP[entry.name]) || 'click';\n\n const cachedContext = entry && getCachedInteractionContext(entry.interactionId);\n const activeSpan = getActiveSpan();\n const rootSpan = activeSpan ? getRootSpan(activeSpan) : undefined;\n\n // With soft navigations the caller knows exactly which navigation the metric belongs to. Without\n // them we fall back to the span that was active when the interaction was observed.\n const spanToUse = attributedSpan || cachedContext?.span || rootSpan;\n const name = cachedContext?.elementName || (entry ? htmlTreeAsString(entry.target) : 'Interaction to next paint');\n\n _emitWebVitalSpan({\n name,\n op: INTERACTION_TYPE_TO_SPAN_OP[interactionType],\n origin: 'auto.http.browser.inp',\n metricName: 'inp',\n value: inpValue,\n attributes: {\n [SEMANTIC_ATTRIBUTE_EXCLUSIVE_TIME]: entry?.duration ?? inpValue,\n },\n startTime,\n endTime: startTime + duration,\n navigationType: metric?.navigationType,\n parentSpan: spanToUse,\n standalone,\n softNavigationId,\n });\n}\n"],"names":[],"mappings":"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"}
|
package/build/types/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
export { addPerformanceInstrumentationHandler, addClsInstrumentationHandler, addTtfbInstrumentationHandler, addLcpInstrumentationHandler, addInpInstrumentationHandler, addFcpInstrumentationHandler, } from './instrumentation/performanceObserver';
|
|
1
|
+
export { addPerformanceInstrumentationHandler, addClsInstrumentationHandler, addTtfbInstrumentationHandler, addLcpInstrumentationHandler, addInpInstrumentationHandler, addFcpInstrumentationHandler, enableBfcacheReporting, enableSoftNavigationReporting, } from './instrumentation/performanceObserver';
|
|
2
|
+
export { startSoftNavigationCorrelation, supportsSoftNavigations } from './web-vitals/softNavs';
|
|
2
3
|
export { addPerformanceEntries, startTrackingLongTasks, startTrackingLongAnimationFrames } from './performance/entries';
|
|
3
4
|
export { addWebVitalsToSpan, startTrackingWebVitals, registerInpInteractionListener, } from './web-vitals/tracking';
|
|
4
5
|
export { elementTimingIntegration, startTrackingElementTiming } from './performance/elementTiming';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,oCAAoC,EACpC,4BAA4B,EAC5B,6BAA6B,EAC7B,4BAA4B,EAC5B,4BAA4B,EAC5B,4BAA4B,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,oCAAoC,EACpC,4BAA4B,EAC5B,6BAA6B,EAC7B,4BAA4B,EAC5B,4BAA4B,EAC5B,4BAA4B,EAC5B,sBAAsB,EACtB,6BAA6B,GAC9B,MAAM,uCAAuC,CAAC;AAE/C,OAAO,EAAE,8BAA8B,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAEhG,OAAO,EAAE,qBAAqB,EAAE,sBAAsB,EAAE,gCAAgC,EAAE,MAAM,uBAAuB,CAAC;AAExH,OAAO,EACL,kBAAkB,EAElB,sBAAsB,EACtB,8BAA8B,GAC/B,MAAM,uBAAuB,CAAC;AAG/B,OAAO,EAAE,wBAAwB,EAAE,0BAA0B,EAAE,MAAM,6BAA6B,CAAC;AAEnG,OAAO,EAAE,uBAAuB,EAAE,MAAM,4BAA4B,CAAC;AAErE,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAElD,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEpD,OAAO,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AAEjE,OAAO,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAE7D,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAEpF,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAEtD,OAAO,EAAE,sCAAsC,EAAE,MAAM,uBAAuB,CAAC;AAE/E,OAAO,EAAE,gCAAgC,EAAE,MAAM,2BAA2B,CAAC;AAE7E,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,yBAAyB,EAAE,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AAElH,OAAO,EAAE,4BAA4B,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAE1F,OAAO,EAAE,aAAa,EAAE,sBAAsB,EAAE,iBAAiB,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAC;AAEnH,OAAO,EAAE,8BAA8B,EAAE,MAAM,8BAA8B,CAAC;AAE9E,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAEtD,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAEpD,OAAO,EAAE,SAAS,EAAE,MAAM,MAAM,CAAC;AAEjC,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAE5D,YAAY,EACV,SAAS,EACT,cAAc,EACd,kBAAkB,EAClB,cAAc,EACd,kBAAkB,EAClB,2BAA2B,EAC3B,aAAa,EACb,OAAO,GACR,MAAM,SAAS,CAAC"}
|