From 0c3b2a41525101bb297cb391a085ae33c8bfafcd Mon Sep 17 00:00:00 2001 From: Charly Gomez Date: Fri, 10 Jan 2025 14:10:17 +0100 Subject: [PATCH] fix(v8/replay): Disable mousemove sampling in rrweb for iOS browsers (#14944) backports https://github.com/getsentry/sentry-javascript/pull/14937 ref https://github.com/getsentry/sentry-javascript/issues/14534 --- .size-limit.js | 2 +- packages/core/src/utils-hoist/worldwide.ts | 2 +- packages/replay-internal/src/replay.ts | 2 + .../src/util/getRecordingSamplingOptions.ts | 25 +++++++++ .../test/integration/rrweb.test.ts | 54 +++++++++++++++++++ 5 files changed, 83 insertions(+), 2 deletions(-) create mode 100644 packages/replay-internal/src/util/getRecordingSamplingOptions.ts diff --git a/.size-limit.js b/.size-limit.js index 6e73c9234c09..6fd3277c7580 100644 --- a/.size-limit.js +++ b/.size-limit.js @@ -47,7 +47,7 @@ module.exports = [ path: 'packages/browser/build/npm/esm/index.js', import: createImport('init', 'browserTracingIntegration', 'replayIntegration'), gzip: true, - limit: '75 KB', + limit: '76 KB', }, { name: '@sentry/browser (incl. Tracing, Replay) - with treeshaking flags', diff --git a/packages/core/src/utils-hoist/worldwide.ts b/packages/core/src/utils-hoist/worldwide.ts index 92018731ff4f..cb819bd92204 100644 --- a/packages/core/src/utils-hoist/worldwide.ts +++ b/packages/core/src/utils-hoist/worldwide.ts @@ -49,7 +49,7 @@ type BackwardsCompatibleSentryCarrier = SentryCarrier & { /** Internal global with common properties and Sentry extensions */ export type InternalGlobal = { - navigator?: { userAgent?: string }; + navigator?: { userAgent?: string; maxTouchPoints?: number }; console: Console; PerformanceObserver?: any; Sentry?: any; diff --git a/packages/replay-internal/src/replay.ts b/packages/replay-internal/src/replay.ts index f3169106d458..d8aaa247d917 100644 --- a/packages/replay-internal/src/replay.ts +++ b/packages/replay-internal/src/replay.ts @@ -47,6 +47,7 @@ import { createBreadcrumb } from './util/createBreadcrumb'; import { createPerformanceEntries } from './util/createPerformanceEntries'; import { createPerformanceSpans } from './util/createPerformanceSpans'; import { debounce } from './util/debounce'; +import { getRecordingSamplingOptions } from './util/getRecordingSamplingOptions'; import { getHandleRecordingEmit } from './util/handleRecordingEmit'; import { isExpired } from './util/isExpired'; import { isSessionExpired } from './util/isSessionExpired'; @@ -394,6 +395,7 @@ export class ReplayContainer implements ReplayContainerInterface { checkoutEveryNms: Math.max(360_000, this._options._experiments.continuousCheckout), }), emit: getHandleRecordingEmit(this), + ...getRecordingSamplingOptions(), onMutation: this._onMutationHandler, ...(canvasOptions ? { diff --git a/packages/replay-internal/src/util/getRecordingSamplingOptions.ts b/packages/replay-internal/src/util/getRecordingSamplingOptions.ts new file mode 100644 index 000000000000..4c7a78ed8ca3 --- /dev/null +++ b/packages/replay-internal/src/util/getRecordingSamplingOptions.ts @@ -0,0 +1,25 @@ +import { GLOBAL_OBJ } from '@sentry/core'; + +const NAVIGATOR = GLOBAL_OBJ.navigator; + +/** + * Disable sampling mousemove events on iOS browsers as this can cause blocking the main thread + * https://github.com/getsentry/sentry-javascript/issues/14534 + */ +export function getRecordingSamplingOptions(): Partial<{ sampling: { mousemove: boolean } }> { + if ( + /iPhone|iPad|iPod/i.test((NAVIGATOR && NAVIGATOR.userAgent) || '') || + (/Macintosh/i.test((NAVIGATOR && NAVIGATOR.userAgent) || '') && + NAVIGATOR && + NAVIGATOR.maxTouchPoints && + NAVIGATOR.maxTouchPoints > 1) + ) { + return { + sampling: { + mousemove: false, + }, + }; + } + + return {}; +} diff --git a/packages/replay-internal/test/integration/rrweb.test.ts b/packages/replay-internal/test/integration/rrweb.test.ts index cd3fbcd095be..7f156c542f08 100644 --- a/packages/replay-internal/test/integration/rrweb.test.ts +++ b/packages/replay-internal/test/integration/rrweb.test.ts @@ -86,4 +86,58 @@ describe('Integration | rrweb', () => { } `); }); + + it('calls rrweb.record with updated sampling options on iOS', async () => { + // Mock iOS user agent + const originalNavigator = global.navigator; + Object.defineProperty(global, 'navigator', { + value: { + userAgent: + 'Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Mobile/15E148 Safari/604.1', + }, + configurable: true, + }); + + const { mockRecord } = await resetSdkMock({ + replayOptions: {}, + sentryOptions: { + replaysOnErrorSampleRate: 1.0, + replaysSessionSampleRate: 1.0, + }, + }); + + // Restore original navigator + Object.defineProperty(global, 'navigator', { + value: originalNavigator, + configurable: true, + }); + + expect(mockRecord.mock.calls[0]?.[0]).toMatchInlineSnapshot(` + { + "blockSelector": ".sentry-block,[data-sentry-block],base,iframe[srcdoc]:not([src]),img,image,svg,video,object,picture,embed,map,audio,link[rel="icon"],link[rel="apple-touch-icon"]", + "collectFonts": true, + "emit": [Function], + "errorHandler": [Function], + "ignoreSelector": ".sentry-ignore,[data-sentry-ignore],input[type="file"]", + "inlineImages": false, + "inlineStylesheet": true, + "maskAllInputs": true, + "maskAllText": true, + "maskAttributeFn": [Function], + "maskInputFn": undefined, + "maskInputOptions": { + "password": true, + }, + "maskTextFn": undefined, + "maskTextSelector": ".sentry-mask,[data-sentry-mask]", + "onMutation": [Function], + "sampling": { + "mousemove": false, + }, + "slimDOMOptions": "all", + "unblockSelector": "", + "unmaskTextSelector": "", + } + `); + }); });