Skip to content

Commit

Permalink
John/hig-1919-update-rrweb (#60)
Browse files Browse the repository at this point in the history
  • Loading branch information
John Pham authored Feb 3, 2022
1 parent 394b865 commit 8c1b928
Show file tree
Hide file tree
Showing 13 changed files with 349 additions and 196 deletions.
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

0 comments on commit 8c1b928

Please sign in to comment.