Skip to content

Commit

Permalink
feat: Add onMutation option to record (#70)
Browse files Browse the repository at this point in the history
When this callback returns `false`, we skip handling the mutations
normally. It's up to the host app to do something with this then.
  • Loading branch information
mydea authored Feb 28, 2023
1 parent 9d4e72a commit 209abfe
Show file tree
Hide file tree
Showing 6 changed files with 196 additions and 2 deletions.
3 changes: 3 additions & 0 deletions packages/rrweb/src/record/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ function record<T = eventWithTime>(
inlineImages = false,
plugins,
keepIframeSrcFn = () => false,
onMutation,
} = options;
// runtime checks for user options
if (!emit) {
Expand Down Expand Up @@ -233,6 +234,7 @@ function record<T = eventWithTime>(
mutationCb: wrappedMutationEmit,
scrollCb: wrappedScrollEmit,
bypassOptions: {
onMutation,
blockClass,
blockSelector,
unblockSelector,
Expand Down Expand Up @@ -351,6 +353,7 @@ function record<T = eventWithTime>(
const observe = (doc: Document) => {
return callbackWrapper(initObservers)(
{
onMutation,
mutationCb: wrappedMutationEmit,
mousemoveCb: (positions, source) =>
wrappedEmit(
Expand Down
9 changes: 8 additions & 1 deletion packages/rrweb/src/record/observer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,14 @@ export function initMutationObserver(
}

const observer = new mutationObserverCtor(
callbackWrapper(mutationBuffer.processMutations.bind(mutationBuffer)),
callbackWrapper((mutations) => {
// If this callback returns `false`, we do not want to process the mutations
// This can be used to e.g. do a manual full snapshot when mutations become too large, or similar.
if (options.onMutation && options.onMutation(mutations) === false) {
return;
}
mutationBuffer.processMutations(mutations);
}),
);

observer.observe(rootEl, {
Expand Down
3 changes: 3 additions & 0 deletions packages/rrweb/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -244,9 +244,11 @@ export type recordOptions<T> = {
// departed, please use sampling options
mousemoveWait?: number;
keepIframeSrcFn?: KeepIframeSrcFn;
onMutation?: (mutations: MutationRecord[]) => boolean,
};

export type observerParam = {
onMutation?: (mutations: MutationRecord[]) => boolean,
mutationCb: mutationCallBack;
mousemoveCb: mousemoveCallBack;
mouseInteractionCb: mouseInteractionCallBack;
Expand Down Expand Up @@ -293,6 +295,7 @@ export type observerParam = {

export type MutationBufferParam = Pick<
observerParam,
| 'onMutation'
| 'mutationCb'
| 'blockClass'
| 'blockSelector'
Expand Down
149 changes: 149 additions & 0 deletions packages/rrweb/test/__snapshots__/integration.test.ts.snap
Original file line number Diff line number Diff line change
@@ -1,5 +1,154 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`record integration tests can configure onMutation 1`] = `
"[
{
"type": 0,
"data": {}
},
{
"type": 1,
"data": {}
},
{
"type": 4,
"data": {
"href": "about:blank",
"width": 1920,
"height": 1080
}
},
{
"type": 2,
"data": {
"node": {
"type": 0,
"childNodes": [
{
"type": 1,
"name": "html",
"publicId": "",
"systemId": "",
"id": 2
},
{
"type": 2,
"tagName": "html",
"attributes": {},
"childNodes": [
{
"type": 2,
"tagName": "head",
"attributes": {},
"childNodes": [],
"id": 4
},
{
"type": 2,
"tagName": "body",
"attributes": {},
"childNodes": [
{
"type": 3,
"textContent": "\\n ",
"id": 6
},
{
"type": 2,
"tagName": "p",
"attributes": {},
"childNodes": [
{
"type": 3,
"textContent": "mutation observer",
"id": 8
}
],
"id": 7
},
{
"type": 3,
"textContent": "\\n ",
"id": 9
},
{
"type": 2,
"tagName": "ul",
"attributes": {},
"childNodes": [
{
"type": 3,
"textContent": "\\n ",
"id": 11
},
{
"type": 2,
"tagName": "li",
"attributes": {},
"childNodes": [],
"id": 12
},
{
"type": 3,
"textContent": "\\n ",
"id": 13
}
],
"id": 10
},
{
"type": 3,
"textContent": "\\n ",
"id": 14
},
{
"type": 2,
"tagName": "canvas",
"attributes": {},
"childNodes": [],
"id": 15
},
{
"type": 3,
"textContent": "\\n\\n ",
"id": 16
},
{
"type": 2,
"tagName": "script",
"attributes": {},
"childNodes": [
{
"type": 3,
"textContent": "SCRIPT_PLACEHOLDER",
"id": 18
}
],
"id": 17
},
{
"type": 3,
"textContent": "\\n \\n \\n",
"id": 19
}
],
"id": 5
}
],
"id": 3
}
],
"id": 1
},
"initialOffset": {
"left": 0,
"top": 0
}
}
}
]"
`;

exports[`record integration tests can freeze mutations 1`] = `
"[
{
Expand Down
30 changes: 30 additions & 0 deletions packages/rrweb/test/integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ describe('record integration tests', function (this: ISuite) {
maskInputOptions: ${JSON.stringify(options.maskAllInputs)},
maskInputSelector: ${JSON.stringify(options.maskInputSelector)},
userTriggeredOnInput: ${options.userTriggeredOnInput},
onMutation: ${options.onMutation || undefined},
maskAllText: ${options.maskAllText},
maskTextFn: ${options.maskTextFn},
unmaskTextSelector: ${JSON.stringify(options.unmaskTextSelector)},
Expand Down Expand Up @@ -216,6 +217,35 @@ describe('record integration tests', function (this: ISuite) {
assertSnapshot(snapshots);
});

it('can configure onMutation', async () => {
const page: puppeteer.Page = await browser.newPage();
await page.goto('about:blank');

await page.setContent(
getHtml.call(this, 'mutation-observer.html', {
onMutation: `(mutations) => { window.lastMutationsLength = mutations.length; return mutations.length < 500 }`
}),
);

await page.evaluate(() => {
const ul = document.querySelector('ul') as HTMLUListElement;

for(let i = 0; i < 2000; i++) {
const li = document.createElement('li');
ul.appendChild(li);
const p = document.querySelector('p') as HTMLParagraphElement;
p.appendChild(document.createElement('span'));
}
});

const snapshots = await page.evaluate('window.snapshots');
assertSnapshot(snapshots);

const lastMutationsLength = await page.evaluate('window.lastMutationsLength');
expect(lastMutationsLength).toBe(4000);
});


it('can freeze mutations', async () => {
const page: puppeteer.Page = await browser.newPage();
await page.goto('about:blank');
Expand Down
4 changes: 3 additions & 1 deletion packages/rrweb/typings/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,8 +163,10 @@ export type recordOptions<T> = {
plugins?: RecordPlugin[];
mousemoveWait?: number;
keepIframeSrcFn?: KeepIframeSrcFn;
onMutation?: (mutations: MutationRecord[]) => boolean;
};
export type observerParam = {
onMutation?: (mutations: MutationRecord[]) => boolean;
mutationCb: mutationCallBack;
mousemoveCb: mousemoveCallBack;
mouseInteractionCb: mouseInteractionCallBack;
Expand Down Expand Up @@ -208,7 +210,7 @@ export type observerParam = {
options: unknown;
}>;
};
export type MutationBufferParam = Pick<observerParam, 'mutationCb' | 'blockClass' | 'blockSelector' | 'unblockSelector' | 'maskTextClass' | 'maskTextSelector' | 'unmaskTextSelector' | 'inlineStylesheet' | 'maskInputSelector' | 'unmaskInputSelector' | 'maskAllText' | 'maskInputOptions' | 'maskTextFn' | 'maskInputFn' | 'recordCanvas' | 'inlineImages' | 'slimDOMOptions' | 'doc' | 'mirror' | 'iframeManager' | 'shadowDomManager' | 'canvasManager'>;
export type MutationBufferParam = Pick<observerParam, 'onMutation' | 'mutationCb' | 'blockClass' | 'blockSelector' | 'unblockSelector' | 'maskTextClass' | 'maskTextSelector' | 'unmaskTextSelector' | 'inlineStylesheet' | 'maskInputSelector' | 'unmaskInputSelector' | 'maskAllText' | 'maskInputOptions' | 'maskTextFn' | 'maskInputFn' | 'recordCanvas' | 'inlineImages' | 'slimDOMOptions' | 'doc' | 'mirror' | 'iframeManager' | 'shadowDomManager' | 'canvasManager'>;
export type hooksParam = {
mutation?: mutationCallBack;
mousemove?: mousemoveCallBack;
Expand Down

0 comments on commit 209abfe

Please sign in to comment.