Skip to content

Commit

Permalink
feat: Ensure to use unwrapped versions of setTimeout / `clearTimeou…
Browse files Browse the repository at this point in the history
…t` (#176)

Let's see if that helps with Angular performance some more...!

Closes getsentry/sentry-javascript#11661
(hopefully...)
  • Loading branch information
mydea authored and billyvg committed Apr 26, 2024
1 parent dc20d46 commit 592a808
Show file tree
Hide file tree
Showing 5 changed files with 42 additions and 18 deletions.
1 change: 1 addition & 0 deletions packages/rrweb/src/record/observer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
patch,
StyleSheetMirror,
nowTimestamp,
setTimeout,
} from '../utils';
import type { observerParam, MutationBufferParam } from '../types';
import {
Expand Down
2 changes: 1 addition & 1 deletion packages/rrweb/src/record/observers/canvas/2d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
IWindow,
listenerHandler,
} from '@sentry-internal/rrweb-types';
import { hookSetter, isBlocked, patch } from '../../../utils';
import { hookSetter, isBlocked, patch, setTimeout } from '../../../utils';
import { serializeArgs } from './serialize-args';

export default function initCanvas2DMutationObserver(
Expand Down
2 changes: 1 addition & 1 deletion packages/rrweb/src/record/shadow-dom-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
initScrollObserver,
initAdoptedStyleSheetObserver,
} from './observer';
import { patch, inDom } from '../utils';
import { patch, inDom, setTimeout } from '../utils';
import type { Mirror } from '@sentry-internal/rrweb-snapshot';
import { isNativeShadowDom } from '@sentry-internal/rrweb-snapshot';

Expand Down
2 changes: 2 additions & 0 deletions packages/rrweb/src/replay/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ import {
getPositionsAndIndex,
uniqueTextMutations,
StyleSheetMirror,
clearTimeout,
setTimeout,
} from '../utils';
import getInjectStyleRules from './styles/inject-style';
import './styles/style.css';
Expand Down
53 changes: 37 additions & 16 deletions packages/rrweb/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -607,45 +607,66 @@ export function inDom(n: Node): boolean {
return doc.contains(n) || shadowHostInDom(n);
}

let cachedRequestAnimationFrameImplementation:
| undefined
| typeof requestAnimationFrame;

/**
* We generally want to use window.requestAnimationFrame.
* We generally want to use window.requestAnimationFrame / window.setTimeout / window.clearTimeout.
* However, in some cases this may be wrapped (e.g. by Zone.js for Angular),
* so we try to get an unpatched version of this from a sandboxed iframe.
*/
function getRequestAnimationFrameImplementation(): typeof requestAnimationFrame {
if (cachedRequestAnimationFrameImplementation) {
return cachedRequestAnimationFrameImplementation;

interface CacheableImplementations {
requestAnimationFrame: typeof requestAnimationFrame;
setTimeout: typeof setTimeout;
clearTimeout: typeof clearTimeout;
}

const cachedImplementations: Partial<CacheableImplementations> = {};

function getImplementation<T extends keyof CacheableImplementations>(
name: T,
): CacheableImplementations[T] {
const cached = cachedImplementations[name];
if (cached) {
return cached;
}

const document = window.document;
let requestAnimationFrameImplementation = window.requestAnimationFrame;
let impl = window[name] as CacheableImplementations[T];
if (document && typeof document.createElement === 'function') {
try {
const sandbox = document.createElement('iframe');
sandbox.hidden = true;
document.head.appendChild(sandbox);
const contentWindow = sandbox.contentWindow;
if (contentWindow && contentWindow.requestAnimationFrame) {
requestAnimationFrameImplementation =
if (contentWindow && contentWindow[name]) {
impl =
// eslint-disable-next-line @typescript-eslint/unbound-method
contentWindow.requestAnimationFrame;
contentWindow[name] as CacheableImplementations[T];
}
document.head.removeChild(sandbox);
} catch (e) {
// Could not create sandbox iframe, just use window.requestAnimationFrame
// Could not create sandbox iframe, just use window.xxx
}
}

return (cachedRequestAnimationFrameImplementation =
requestAnimationFrameImplementation.bind(window));
return (cachedImplementations[name] = impl.bind(
window,
) as CacheableImplementations[T]);
}

export function onRequestAnimationFrame(
...rest: Parameters<typeof requestAnimationFrame>
): ReturnType<typeof requestAnimationFrame> {
return getRequestAnimationFrameImplementation()(...rest);
return getImplementation('requestAnimationFrame')(...rest);
}

export function setTimeout(
...rest: Parameters<typeof window.setTimeout>
): ReturnType<typeof window.setTimeout> {
return getImplementation('setTimeout')(...rest);
}

export function clearTimeout(
...rest: Parameters<typeof window.clearTimeout>
): ReturnType<typeof window.clearTimeout> {
return getImplementation('clearTimeout')(...rest);
}

0 comments on commit 592a808

Please sign in to comment.