Skip to content

Commit

Permalink
use native setTimeout,add/removeEventListener when zone.js is present
Browse files Browse the repository at this point in the history
  • Loading branch information
mdellanoce authored and jaj1014 committed Apr 22, 2024
1 parent 220e600 commit e4ad6c0
Show file tree
Hide file tree
Showing 9 changed files with 77 additions and 34 deletions.
6 changes: 6 additions & 0 deletions .changeset/fair-seahorses-care.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'rrweb': patch
---

Use native setTimeout when zone.js is present
Use native add/removeEventListener when zone.js is present
7 changes: 4 additions & 3 deletions packages/rrweb-snapshot/src/snapshot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
getInputType,
toLowerCase,
extractFileExtension,
nativeSetTimeout
} from './utils';

let _id = 1;
Expand Down Expand Up @@ -363,7 +364,7 @@ function onceIframeLoaded(
return;
}
if (readyState !== 'complete') {
const timer = setTimeout(() => {
const timer = nativeSetTimeout(() => {
if (!fired) {
listener();
fired = true;
Expand All @@ -385,7 +386,7 @@ function onceIframeLoaded(
) {
// iframe was already loaded, make sure we wait to trigger the listener
// till _after_ the mutation that found this iframe has had time to process
setTimeout(listener, 0);
nativeSetTimeout(listener, 0);

return iframeEl.addEventListener('load', listener); // keep listing for future loads
}
Expand Down Expand Up @@ -413,7 +414,7 @@ function onceStylesheetLoaded(

if (styleSheetLoaded) return;

const timer = setTimeout(() => {
const timer = nativeSetTimeout(() => {
if (!fired) {
listener();
fired = true;
Expand Down
2 changes: 2 additions & 0 deletions packages/rrweb-snapshot/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,3 +184,5 @@ export type KeepIframeSrcFn = (src: string) => boolean;
export type BuildCache = {
stylesWithHoverClass: Map<string, string>;
};

export type IWindow = Window & typeof globalThis;
29 changes: 29 additions & 0 deletions packages/rrweb-snapshot/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
documentTypeNode,
textNode,
elementNode,
IWindow,
} from './types';

export function isElement(n: Node): n is Element {
Expand All @@ -30,6 +31,34 @@ export function isNativeShadowDom(shadowRoot: ShadowRoot) {
return Object.prototype.toString.call(shadowRoot) === '[object ShadowRoot]';
}

type WindowWithAngularZone = IWindow & {
Zone?: {
__symbol__?: (key: keyof IWindow) => string;
};
[key: string]: any;
};

export function getNative<T>(
symbolName: keyof IWindow,
windowObj: IWindow = window,
): T {
const windowWithZone = windowObj as WindowWithAngularZone;
const angularZoneSymbol = windowWithZone?.Zone?.__symbol__?.(symbolName);
if (angularZoneSymbol) {
const zonelessImpl = windowWithZone[angularZoneSymbol] as T;
if (zonelessImpl) {
return zonelessImpl;
}
}

return windowWithZone[symbolName] as T;
}

export const nativeSetTimeout =
typeof window !== 'undefined'
? getNative<typeof window.setTimeout>('setTimeout')
: global.setTimeout;

/**
* Browsers sometimes destructively modify the css rules they receive.
* This function tries to rectify the modifications the browser made to make it more cross platform compatible.
Expand Down
24 changes: 4 additions & 20 deletions packages/rrweb/src/record/observer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import {
Mirror,
getInputType,
toLowerCase,
getNative,
nativeSetTimeout,
} from 'rrweb-snapshot';
import type { FontFaceSet } from 'css-font-loading-module';
import {
Expand Down Expand Up @@ -54,11 +56,6 @@ import { callbackWrapper } from './error-handler';
type WindowWithStoredMutationObserver = IWindow & {
__rrMutationObserver?: MutationObserver;
};
type WindowWithAngularZone = IWindow & {
Zone?: {
__symbol__?: (key: string) => string;
};
};

export const mutationBuffers: MutationBuffer[] = [];

Expand Down Expand Up @@ -93,7 +90,7 @@ export function initMutationObserver(
// see mutation.ts for details
mutationBuffer.init(options);
let mutationObserverCtor =
window.MutationObserver ||
getNative<typeof MutationObserver>('MutationObserver') ||
/**
* Some websites may disable MutationObserver by removing it from the window object.
* If someone is using rrweb to build a browser extention or things like it, they
Expand All @@ -103,19 +100,6 @@ export function initMutationObserver(
* window.__rrMutationObserver = MutationObserver
*/
(window as WindowWithStoredMutationObserver).__rrMutationObserver;
const angularZoneSymbol = (
window as WindowWithAngularZone
)?.Zone?.__symbol__?.('MutationObserver');
if (
angularZoneSymbol &&
(window as unknown as Record<string, typeof MutationObserver>)[
angularZoneSymbol
]
) {
mutationObserverCtor = (
window as unknown as Record<string, typeof MutationObserver>
)[angularZoneSymbol];
}
const observer = new (mutationObserverCtor as new (
callback: MutationCallback,
) => MutationObserver)(
Expand Down Expand Up @@ -1105,7 +1089,7 @@ function initFontObserver({ fontCb, doc }: observerParam): listenerHandler {
'add',
function (original: (font: FontFace) => void) {
return function (this: FontFaceSet, fontFace: FontFace) {
setTimeout(
nativeSetTimeout(
callbackWrapper(() => {
const p = fontMap.get(fontFace);
if (p) {
Expand Down
3 changes: 2 additions & 1 deletion packages/rrweb/src/record/observers/canvas/2d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { Mirror } from 'rrweb-snapshot';
import { nativeSetTimeout } from 'rrweb-snapshot';
import {
blockClass,
CanvasContext,
Expand Down Expand Up @@ -44,7 +45,7 @@ export default function initCanvas2DMutationObserver(
if (!isBlocked(this.canvas, blockClass, blockSelector, true)) {
// Using setTimeout as toDataURL can be heavy
// and we'd rather not block the main thread
setTimeout(() => {
nativeSetTimeout(() => {
const recordArgs = serializeArgs(args, win, this);
cb(this.canvas, {
type: CanvasContext['2D'],
Expand Down
4 changes: 2 additions & 2 deletions packages/rrweb/src/record/shadow-dom-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
} from './observer';
import { patch, inDom } from '../utils';
import type { Mirror } from 'rrweb-snapshot';
import { isNativeShadowDom } from 'rrweb-snapshot';
import { isNativeShadowDom, nativeSetTimeout } from 'rrweb-snapshot';

type BypassOptions = Omit<
MutationBufferParam,
Expand Down Expand Up @@ -74,7 +74,7 @@ export class ShadowDomManager {
}),
);
// Defer this to avoid adoptedStyleSheet events being created before the full snapshot is created or attachShadow action is recorded.
setTimeout(() => {
nativeSetTimeout(() => {
if (
shadowRoot.adoptedStyleSheets &&
shadowRoot.adoptedStyleSheets.length > 0
Expand Down
31 changes: 25 additions & 6 deletions packages/rrweb/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,36 @@ import type {
textMutation,
} from '@rrweb/types';
import type { IMirror, Mirror } from 'rrweb-snapshot';
import { isShadowRoot, IGNORED_NODE, classMatchesRegex } from 'rrweb-snapshot';
import {
isShadowRoot,
IGNORED_NODE,
classMatchesRegex,
getNative,
nativeSetTimeout,
} from 'rrweb-snapshot';
import type { RRNode, RRIFrameElement } from 'rrdom';

function getWindow(documentOrWindow: Document | IWindow): IWindow {
const defaultView = (documentOrWindow as Document).defaultView;
return (defaultView ? defaultView : documentOrWindow) as IWindow;
}

export function on(
type: string,
fn: EventListenerOrEventListenerObject,
target: Document | IWindow = document,
): listenerHandler {
const windowObj = getWindow(target);
const nativeAddEventListener = getNative<typeof window.addEventListener>(
'addEventListener',
windowObj,
);
const nativeRemoveEventListener = getNative<
typeof window.removeEventListener
>('removeEventListener', windowObj);
const options = { capture: true, passive: true };
target.addEventListener(type, fn, options);
return () => target.removeEventListener(type, fn, options);
nativeAddEventListener.call(target, type, fn, options);
return () => nativeRemoveEventListener.call(target, type, fn, options);
}

// https://github.com/rrweb-io/rrweb/pull/407
Expand Down Expand Up @@ -70,7 +89,7 @@ export function throttle<T>(
wait: number,
options: throttleOptions = {},
) {
let timeout: ReturnType<typeof setTimeout> | null = null;
let timeout: ReturnType<typeof setTimeout> | number | null = null;
let previous = 0;
return function (...args: T[]) {
const now = Date.now();
Expand All @@ -88,7 +107,7 @@ export function throttle<T>(
previous = now;
func.apply(context, args);
} else if (!timeout && options.trailing !== false) {
timeout = setTimeout(() => {
timeout = nativeSetTimeout(() => {
previous = options.leading === false ? 0 : Date.now();
timeout = null;
func.apply(context, args);
Expand All @@ -113,7 +132,7 @@ export function hookSetter<T>(
: {
set(value) {
// put hooked setter into event loop to avoid of set latency
setTimeout(() => {
nativeSetTimeout(() => {
d.set!.call(this, value);
}, 0);
if (original && original.set) {
Expand Down
5 changes: 3 additions & 2 deletions packages/types/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type {
Mirror,
INode,
DataURLOptions,
IWindow,
} from 'rrweb-snapshot';

export enum EventType {
Expand Down Expand Up @@ -688,8 +689,6 @@ declare global {
}
}

export type IWindow = Window & typeof globalThis;

export type Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>;

export type GetTypedKeys<Obj extends object, ValueType> = TakeTypeHelper<
Expand All @@ -704,3 +703,5 @@ export type TakeTypedKeyValues<Obj extends object, Type> = Pick<
Obj,
TakeTypeHelper<Obj, Type>[keyof TakeTypeHelper<Obj, Type>]
>;

export { IWindow };

0 comments on commit e4ad6c0

Please sign in to comment.