Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@highlight-run/rrweb",
"version": "1.1.6",
"version": "1.1.7",
"description": "record and replay the web",
"scripts": {
"test": "npm run bundle:browser && cross-env TS_NODE_CACHE=false TS_NODE_FILES=true mocha -r ts-node/register -r ignore-styles -r jsdom-global/register test/**.test.ts",
Expand Down
33 changes: 23 additions & 10 deletions src/record/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@ import {
listenerHandler,
mutationCallbackParam,
scrollCallback,
canvasMutationParam,
} from '../types';
import { IframeManager } from './iframe-manager';
import { ShadowDomManager } from './shadow-dom-manager';
import { CanvasManager } from './observers/canvas/canvas-manager';

function wrapEvent(e: event): eventWithTime {
return {
Expand Down Expand Up @@ -180,11 +182,29 @@ function record<T = eventWithTime>(
},
}),
);
const wrappedCanvasMutationEmit = (p: canvasMutationParam) =>
wrappedEmit(
wrapEvent({
type: EventType.IncrementalSnapshot,
data: {
source: IncrementalSource.CanvasMutation,
...p,
},
}),
);

const iframeManager = new IframeManager({
mutationCb: wrappedMutationEmit,
});

const canvasManager = new CanvasManager({
recordCanvas,
mutationCb: wrappedCanvasMutationEmit,
win: window,
blockClass,
mirror,
});

const shadowDomManager = new ShadowDomManager({
mutationCb: wrappedMutationEmit,
scrollCb: wrappedScrollEmit,
Expand All @@ -201,6 +221,7 @@ function record<T = eventWithTime>(
sampling,
slimDOMOptions,
iframeManager,
canvasManager,
enableStrictPrivacy,
},
mirror,
Expand Down Expand Up @@ -365,16 +386,7 @@ function record<T = eventWithTime>(
},
}),
),
canvasMutationCb: (p) =>
wrappedEmit(
wrapEvent({
type: EventType.IncrementalSnapshot,
data: {
source: IncrementalSource.CanvasMutation,
...p,
},
}),
),
canvasMutationCb: wrappedCanvasMutationEmit,
fontCb: (p) =>
wrappedEmit(
wrapEvent({
Expand Down Expand Up @@ -403,6 +415,7 @@ function record<T = eventWithTime>(
mirror,
iframeManager,
shadowDomManager,
canvasManager,
plugins:
plugins?.map((p) => ({
observer: p.observer,
Expand Down
16 changes: 14 additions & 2 deletions src/record/mutation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {
hasShadowRoot,
} from '../utils';
import { IframeManager } from './iframe-manager';
import { CanvasManager } from './observers/canvas/canvas-manager';
import { ShadowDomManager } from './shadow-dom-manager';

type DoubleLinkedListNode = {
Expand Down Expand Up @@ -179,6 +180,7 @@ export default class MutationBuffer {
private mirror: Mirror;
private iframeManager: IframeManager;
private shadowDomManager: ShadowDomManager;
private canvasManager: CanvasManager;

public init(
cb: mutationCallBack,
Expand All @@ -196,6 +198,7 @@ export default class MutationBuffer {
mirror: Mirror,
iframeManager: IframeManager,
shadowDomManager: ShadowDomManager,
canvasManager: CanvasManager,
enableStrictPrivacy: boolean,
) {
this.blockClass = blockClass;
Expand All @@ -214,14 +217,17 @@ export default class MutationBuffer {
this.mirror = mirror;
this.iframeManager = iframeManager;
this.shadowDomManager = shadowDomManager;
this.canvasManager = canvasManager;
}

public freeze() {
this.frozen = true;
this.canvasManager.freeze();
}

public unfreeze() {
this.frozen = false;
this.canvasManager.unfreeze();
this.emit();
}

Expand All @@ -231,16 +237,22 @@ export default class MutationBuffer {

public lock() {
this.locked = true;
this.canvasManager.lock();
}

public unlock() {
this.locked = false;
this.canvasManager.unlock();
this.emit();
}

public reset() {
this.canvasManager.reset();
}

public processMutations = (mutations: mutationRecord[]) => {
mutations.forEach(this.processMutation);
this.emit();
mutations.forEach(this.processMutation); // adds mutations to the buffer
this.emit(); // clears buffer if not locked/frozen
};

public emit = () => {
Expand Down
80 changes: 31 additions & 49 deletions src/record/observer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ import { ShadowDomManager } from './shadow-dom-manager';
import initCanvasContextObserver from './observers/canvas/canvas';
import initCanvas2DMutationObserver from './observers/canvas/2d';
import initCanvasWebGLMutationObserver from './observers/canvas/webgl';
import { CanvasManager } from './observers/canvas/canvas-manager';

type WindowWithStoredMutationObserver = IWindow & {
__rrMutationObserver?: MutationObserver;
Expand Down Expand Up @@ -105,6 +106,7 @@ export function initMutationObserver(
mirror: Mirror,
iframeManager: IframeManager,
shadowDomManager: ShadowDomManager,
canvasManager: CanvasManager,
rootEl: Node,
enableStrictPrivacy: boolean,
): MutationObserver {
Expand All @@ -127,6 +129,7 @@ export function initMutationObserver(
mirror,
iframeManager,
shadowDomManager,
canvasManager,
enableStrictPrivacy,
);
let mutationObserverCtor =
Expand Down Expand Up @@ -375,8 +378,14 @@ function initInputObserver(
userTriggeredOnInput: boolean,
): listenerHandler {
function eventHandler(event: Event) {
const target = getEventTarget(event);
let target = getEventTarget(event);
const userTriggered = event.isTrusted;
/**
* If a site changes the value 'selected' of an option element, the value of its parent element, usually a select element, will be changed as well.
* We can treat this change as a value change of the select element the current target belongs to.
*/
if (target && (target as Element).tagName === 'OPTION')
target = (target as Element).parentElement;
if (
!target ||
!(target as Element).tagName ||
Expand Down Expand Up @@ -467,6 +476,7 @@ function initInputObserver(
[HTMLTextAreaElement.prototype, 'value'],
// Some UI library use selectedIndex to set select value
[HTMLSelectElement.prototype, 'selectedIndex'],
[HTMLOptionElement.prototype, 'selected'],
];
if (propertyDescriptor && propertyDescriptor.set) {
handlers.push(
Expand Down Expand Up @@ -690,56 +700,34 @@ function initMediaInteractionObserver(
mediaInteractionCb: mediaInteractionCallback,
blockClass: blockClass,
mirror: Mirror,
sampling: SamplingStrategy,
): listenerHandler {
const handler = (type: MediaInteractions) => (event: Event) => {
const target = getEventTarget(event);
if (!target || isBlocked(target as Node, blockClass)) {
return;
}
mediaInteractionCb({
type,
id: mirror.getId(target as INode),
currentTime: (target as HTMLMediaElement).currentTime,
});
};
const handler = (type: MediaInteractions) =>
throttle((event: Event) => {
const target = getEventTarget(event);
if (!target || isBlocked(target as Node, blockClass)) {
return;
}
const { currentTime, volume, muted } = target as HTMLMediaElement;
mediaInteractionCb({
type,
id: mirror.getId(target as INode),
currentTime,
volume,
muted,
});
}, sampling.media || 500);
const handlers = [
on('play', handler(MediaInteractions.Play)),
on('pause', handler(MediaInteractions.Pause)),
on('seeked', handler(MediaInteractions.Seeked)),
on('volumechange', handler(MediaInteractions.VolumeChange)),
];
return () => {
handlers.forEach((h) => h());
};
}

function initCanvasMutationObserver(
cb: canvasMutationCallback,
win: IWindow,
blockClass: blockClass,
mirror: Mirror,
): listenerHandler {
const canvasContextReset = initCanvasContextObserver(win, blockClass);
const canvas2DReset = initCanvas2DMutationObserver(
cb,
win,
blockClass,
mirror,
);

const canvasWebGL1and2Reset = initCanvasWebGLMutationObserver(
cb,
win,
blockClass,
mirror,
);

return () => {
canvasContextReset();
canvas2DReset();
canvasWebGL1and2Reset();
};
}

function initFontObserver(cb: fontCallback, doc: Document): listenerHandler {
const win = doc.defaultView as IWindow;
if (!win) {
Expand Down Expand Up @@ -900,6 +888,7 @@ export function initObservers(
o.mirror,
o.iframeManager,
o.shadowDomManager,
o.canvasManager,
o.doc,
o.enableStrictPrivacy,
);
Expand Down Expand Up @@ -939,6 +928,7 @@ export function initObservers(
o.mediaInteractionCb,
o.blockClass,
o.mirror,
o.sampling,
);

const styleSheetObserver = initStyleSheetObserver(
Expand All @@ -951,14 +941,6 @@ export function initObservers(
currentWindow,
o.mirror,
);
const canvasMutationObserver = o.recordCanvas
? initCanvasMutationObserver(
o.canvasMutationCb,
currentWindow,
o.blockClass,
o.mirror,
)
: () => {};
const fontObserver = o.collectFonts
? initFontObserver(o.fontCb, o.doc)
: () => {};
Expand All @@ -971,6 +953,7 @@ export function initObservers(
}

return () => {
mutationBuffers.forEach((b) => b.reset());
mutationObserver.disconnect();
mousemoveHandler();
mouseInteractionHandler();
Expand All @@ -980,7 +963,6 @@ export function initObservers(
mediaInteractionHandler();
styleSheetObserver();
styleDeclarationObserver();
canvasMutationObserver();
fontObserver();
pluginHandlers.forEach((h) => h());
};
Expand Down
13 changes: 6 additions & 7 deletions src/record/observers/canvas/2d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,14 @@ import { INode } from '../../../snapshot';
import {
blockClass,
CanvasContext,
canvasMutationCallback,
canvasManagerMutationCallback,
IWindow,
listenerHandler,
Mirror,
} from '../../../types';
import { hookSetter, isBlocked, patch } from '../../../utils';

export default function initCanvas2DMutationObserver(
cb: canvasMutationCallback,
cb: canvasManagerMutationCallback,
win: IWindow,
blockClass: blockClass,
mirror: Mirror,
Expand All @@ -37,6 +36,8 @@ export default function initCanvas2DMutationObserver(
...args: Array<unknown>
) {
if (!isBlocked((this.canvas as unknown) as INode, blockClass)) {
// Using setTimeout as getImageData + JSON.stringify can be heavy
// and we'd rather not block the main thread
setTimeout(() => {
const recordArgs = [...args];
if (prop === 'drawImage') {
Expand All @@ -56,8 +57,7 @@ export default function initCanvas2DMutationObserver(
recordArgs[0] = JSON.stringify(pix);
}
}
cb({
id: mirror.getId((this.canvas as unknown) as INode),
cb(this.canvas, {
type: CanvasContext['2D'],
property: prop,
args: recordArgs,
Expand All @@ -75,8 +75,7 @@ export default function initCanvas2DMutationObserver(
prop,
{
set(v) {
cb({
id: mirror.getId((this.canvas as unknown) as INode),
cb(this.canvas, {
type: CanvasContext['2D'],
property: prop,
args: [v],
Expand Down
Loading