diff --git a/.size-limit.js b/.size-limit.js
index 0030dc3afe7f..8b506b8f683b 100644
--- a/.size-limit.js
+++ b/.size-limit.js
@@ -40,7 +40,7 @@ module.exports = [
path: 'packages/browser/build/npm/esm/index.js',
import: createImport('init', 'browserTracingIntegration'),
gzip: true,
- limit: '36 KB',
+ limit: '36.5 KB',
},
{
name: '@sentry/browser (incl. Tracing, Replay)',
@@ -124,7 +124,7 @@ module.exports = [
import: createImport('init', 'ErrorBoundary', 'reactRouterV6BrowserTracingIntegration'),
ignore: ['react/jsx-runtime'],
gzip: true,
- limit: '39.05 KB',
+ limit: '39.5 KB',
},
// Vue SDK (ESM)
{
diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-before-navigation/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-before-navigation/init.js
new file mode 100644
index 000000000000..1f396416d855
--- /dev/null
+++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-before-navigation/init.js
@@ -0,0 +1,19 @@
+import * as Sentry from '@sentry/browser';
+
+window.Sentry = Sentry;
+
+Sentry.init({
+ dsn: 'https://public@dsn.ingest.sentry.io/1337',
+ integrations: [
+ Sentry.browserTracingIntegration({
+ idleTimeout: 9000,
+ enableLongAnimationFrame: false,
+ instrumentPageLoad: false,
+ instrumentNavigation: true,
+ enableInp: false,
+ enableLongTask: true,
+ }),
+ ],
+ tracesSampleRate: 1,
+ debug: true,
+});
diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-before-navigation/subject.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-before-navigation/subject.js
new file mode 100644
index 000000000000..2c477161b9f4
--- /dev/null
+++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-before-navigation/subject.js
@@ -0,0 +1,17 @@
+const longTaskButton = document.getElementById('myButton');
+
+longTaskButton?.addEventListener('click', () => {
+ const startTime = Date.now();
+
+ function getElapsed() {
+ const time = Date.now();
+ return time - startTime;
+ }
+
+ while (getElapsed() < 500) {
+ //
+ }
+
+ // trigger a navigation in the same event loop tick
+ window.history.pushState({}, '', '/#myHeading');
+});
diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-before-navigation/template.html b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-before-navigation/template.html
new file mode 100644
index 000000000000..1c6430a388b2
--- /dev/null
+++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-before-navigation/template.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+ Rendered Before Long Task
+
+
+
+ Heading
+
+
diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-before-navigation/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-before-navigation/test.ts
new file mode 100644
index 000000000000..fe2efb6b3565
--- /dev/null
+++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-before-navigation/test.ts
@@ -0,0 +1,27 @@
+import { expect } from '@playwright/test';
+import type { Event } from '@sentry/types';
+
+import { sentryTest } from '../../../../utils/fixtures';
+import { getFirstSentryEnvelopeRequest, shouldSkipTracingTest } from '../../../../utils/helpers';
+
+sentryTest(
+ "doesn't capture long task spans starting before a navigation in the navigation transaction",
+ async ({ browserName, getLocalTestPath, page }) => {
+ // Long tasks only work on chrome
+ if (shouldSkipTracingTest() || browserName !== 'chromium') {
+ sentryTest.skip();
+ }
+ const url = await getLocalTestPath({ testDir: __dirname });
+
+ await page.goto(url);
+
+ await page.locator('#myButton').click();
+
+ const navigationTransactionEvent = await getFirstSentryEnvelopeRequest(page, url);
+
+ expect(navigationTransactionEvent.contexts?.trace?.op).toBe('navigation');
+
+ const longTaskSpans = navigationTransactionEvent?.spans?.filter(span => span.op === 'ui.long-task');
+ expect(longTaskSpans).toHaveLength(0);
+ },
+);
diff --git a/packages/browser-utils/src/metrics/browserMetrics.ts b/packages/browser-utils/src/metrics/browserMetrics.ts
index 59a58290c502..670b1ce4ae25 100644
--- a/packages/browser-utils/src/metrics/browserMetrics.ts
+++ b/packages/browser-utils/src/metrics/browserMetrics.ts
@@ -105,24 +105,32 @@ export function startTrackingWebVitals({ recordClsStandaloneSpans }: StartTracki
*/
export function startTrackingLongTasks(): void {
addPerformanceInstrumentationHandler('longtask', ({ entries }) => {
- if (!getActiveSpan()) {
+ const parent = getActiveSpan();
+ if (!parent) {
return;
}
+
+ const { op: parentOp, start_timestamp: parentStartTimestamp } = spanToJSON(parent);
+
for (const entry of entries) {
const startTime = msToSec((browserPerformanceTimeOrigin as number) + entry.startTime);
const duration = msToSec(entry.duration);
- const span = startInactiveSpan({
+ if (parentOp === 'navigation' && parentStartTimestamp && startTime < parentStartTimestamp) {
+ // Skip adding a span if the long task started before the navigation started.
+ // `startAndEndSpan` will otherwise adjust the parent's start time to the span's start
+ // time, potentially skewing the duration of the actual navigation as reported via our
+ // routing instrumentations
+ continue;
+ }
+
+ startAndEndSpan(parent, startTime, startTime + duration, {
name: 'Main UI thread blocked',
op: 'ui.long-task',
- startTime,
attributes: {
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.browser.metrics',
},
});
- if (span) {
- span.end(startTime + duration);
- }
}
});
}
diff --git a/packages/browser/package.json b/packages/browser/package.json
index 532ebe06c4ea..f72074b4c5ba 100644
--- a/packages/browser/package.json
+++ b/packages/browser/package.json
@@ -9,9 +9,7 @@
"engines": {
"node": ">=14.18"
},
- "files": [
- "/build/npm"
- ],
+ "files": ["/build/npm"],
"main": "build/npm/cjs/index.js",
"module": "build/npm/esm/index.js",
"types": "build/npm/types/index.d.ts",
@@ -30,9 +28,7 @@
},
"typesVersions": {
"<4.9": {
- "build/npm/types/index.d.ts": [
- "build/npm/types-ts3.8/index.d.ts"
- ]
+ "build/npm/types/index.d.ts": ["build/npm/types-ts3.8/index.d.ts"]
}
},
"publishConfig": {
@@ -53,7 +49,7 @@
},
"scripts": {
"build": "run-p build:transpile build:bundle build:types",
- "build:dev": "yarn build",
+ "build:dev": "run-p build:transpile build:types",
"build:bundle": "rollup -c rollup.bundle.config.mjs",
"build:transpile": "rollup -c rollup.npm.config.mjs",
"build:types": "run-s build:types:core build:types:downlevel",