Skip to content

Commit 0c3b2a4

Browse files
authored
fix(v8/replay): Disable mousemove sampling in rrweb for iOS browsers (#14944)
backports #14937 ref #14534
1 parent fda1aee commit 0c3b2a4

File tree

5 files changed

+83
-2
lines changed

5 files changed

+83
-2
lines changed

.size-limit.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ module.exports = [
4747
path: 'packages/browser/build/npm/esm/index.js',
4848
import: createImport('init', 'browserTracingIntegration', 'replayIntegration'),
4949
gzip: true,
50-
limit: '75 KB',
50+
limit: '76 KB',
5151
},
5252
{
5353
name: '@sentry/browser (incl. Tracing, Replay) - with treeshaking flags',

packages/core/src/utils-hoist/worldwide.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ type BackwardsCompatibleSentryCarrier = SentryCarrier & {
4949

5050
/** Internal global with common properties and Sentry extensions */
5151
export type InternalGlobal = {
52-
navigator?: { userAgent?: string };
52+
navigator?: { userAgent?: string; maxTouchPoints?: number };
5353
console: Console;
5454
PerformanceObserver?: any;
5555
Sentry?: any;

packages/replay-internal/src/replay.ts

+2
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ import { createBreadcrumb } from './util/createBreadcrumb';
4747
import { createPerformanceEntries } from './util/createPerformanceEntries';
4848
import { createPerformanceSpans } from './util/createPerformanceSpans';
4949
import { debounce } from './util/debounce';
50+
import { getRecordingSamplingOptions } from './util/getRecordingSamplingOptions';
5051
import { getHandleRecordingEmit } from './util/handleRecordingEmit';
5152
import { isExpired } from './util/isExpired';
5253
import { isSessionExpired } from './util/isSessionExpired';
@@ -394,6 +395,7 @@ export class ReplayContainer implements ReplayContainerInterface {
394395
checkoutEveryNms: Math.max(360_000, this._options._experiments.continuousCheckout),
395396
}),
396397
emit: getHandleRecordingEmit(this),
398+
...getRecordingSamplingOptions(),
397399
onMutation: this._onMutationHandler,
398400
...(canvasOptions
399401
? {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { GLOBAL_OBJ } from '@sentry/core';
2+
3+
const NAVIGATOR = GLOBAL_OBJ.navigator;
4+
5+
/**
6+
* Disable sampling mousemove events on iOS browsers as this can cause blocking the main thread
7+
* https://github.com/getsentry/sentry-javascript/issues/14534
8+
*/
9+
export function getRecordingSamplingOptions(): Partial<{ sampling: { mousemove: boolean } }> {
10+
if (
11+
/iPhone|iPad|iPod/i.test((NAVIGATOR && NAVIGATOR.userAgent) || '') ||
12+
(/Macintosh/i.test((NAVIGATOR && NAVIGATOR.userAgent) || '') &&
13+
NAVIGATOR &&
14+
NAVIGATOR.maxTouchPoints &&
15+
NAVIGATOR.maxTouchPoints > 1)
16+
) {
17+
return {
18+
sampling: {
19+
mousemove: false,
20+
},
21+
};
22+
}
23+
24+
return {};
25+
}

packages/replay-internal/test/integration/rrweb.test.ts

+54
Original file line numberDiff line numberDiff line change
@@ -86,4 +86,58 @@ describe('Integration | rrweb', () => {
8686
}
8787
`);
8888
});
89+
90+
it('calls rrweb.record with updated sampling options on iOS', async () => {
91+
// Mock iOS user agent
92+
const originalNavigator = global.navigator;
93+
Object.defineProperty(global, 'navigator', {
94+
value: {
95+
userAgent:
96+
'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',
97+
},
98+
configurable: true,
99+
});
100+
101+
const { mockRecord } = await resetSdkMock({
102+
replayOptions: {},
103+
sentryOptions: {
104+
replaysOnErrorSampleRate: 1.0,
105+
replaysSessionSampleRate: 1.0,
106+
},
107+
});
108+
109+
// Restore original navigator
110+
Object.defineProperty(global, 'navigator', {
111+
value: originalNavigator,
112+
configurable: true,
113+
});
114+
115+
expect(mockRecord.mock.calls[0]?.[0]).toMatchInlineSnapshot(`
116+
{
117+
"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"]",
118+
"collectFonts": true,
119+
"emit": [Function],
120+
"errorHandler": [Function],
121+
"ignoreSelector": ".sentry-ignore,[data-sentry-ignore],input[type="file"]",
122+
"inlineImages": false,
123+
"inlineStylesheet": true,
124+
"maskAllInputs": true,
125+
"maskAllText": true,
126+
"maskAttributeFn": [Function],
127+
"maskInputFn": undefined,
128+
"maskInputOptions": {
129+
"password": true,
130+
},
131+
"maskTextFn": undefined,
132+
"maskTextSelector": ".sentry-mask,[data-sentry-mask]",
133+
"onMutation": [Function],
134+
"sampling": {
135+
"mousemove": false,
136+
},
137+
"slimDOMOptions": "all",
138+
"unblockSelector": "",
139+
"unmaskTextSelector": "",
140+
}
141+
`);
142+
});
89143
});

0 commit comments

Comments
 (0)