Files
rrweb/packages/rrweb-snapshot/test/snapshot.test.ts
Justin Halsall 058c4579b4 Speed up snapshotting of many new dom nodes (#903)
* Speed up snapshotting of many new dom nodes

By avoiding reflow we shave about 15-25% off our snapshotting time

* Improve newlyAddedElement docs

* Optimize needMaskingText by using el.closest and less recursion

* Serve all rrweb dist files

* Split serializeNode into smaller functions

Makes it easier to profile

* Slow down cpu enhance tracing on fast machines

* Increase timeout

* Perf: only loop through ancestors when they have something to compare to

* Perf: `hasNode` is cheaper than `getMeta`

* Perf: If parents where already checked, no need to do it again

* Perf: reverse for loops are faster

Because they only do the .lenght check once. In this case I don't think we'll see much performance gains if any

* Clean up code

* Perf: check ancestors once with isBlocked

* guessing this might fixes canvas test

* Update packages/rrweb/src/record/observers/canvas/webgl.ts

Co-authored-by: yz-yu <yanzhen@smartx.com>

* Fix #904 (#906)

Properly remove crossorigin attribute

* Bump minimist from 1.2.5 to 1.2.6 (#902)

Bumps [minimist](https://github.com/substack/minimist) from 1.2.5 to 1.2.6.
- [Release notes](https://github.com/substack/minimist/releases)
- [Commits](https://github.com/substack/minimist/compare/1.2.5...1.2.6)

---
updated-dependencies:
- dependency-name: minimist
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

Co-authored-by: yz-yu <yanzhen@smartx.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-06-06 22:55:15 +08:00

221 lines
7.2 KiB
TypeScript

/**
* @jest-environment jsdom
*/
import { JSDOM } from 'jsdom';
import {
absoluteToStylesheet,
serializeNodeWithId,
_isBlockedElement,
} from '../src/snapshot';
import { serializedNodeWithId } from '../src/types';
import { Mirror } from '../src/utils';
describe('absolute url to stylesheet', () => {
const href = 'http://localhost/css/style.css';
it('can handle relative path', () => {
expect(absoluteToStylesheet('url(a.jpg)', href)).toEqual(
`url(http://localhost/css/a.jpg)`,
);
});
it('can handle same level path', () => {
expect(absoluteToStylesheet('url("./a.jpg")', href)).toEqual(
`url("http://localhost/css/a.jpg")`,
);
});
it('can handle parent level path', () => {
expect(absoluteToStylesheet('url("../a.jpg")', href)).toEqual(
`url("http://localhost/a.jpg")`,
);
});
it('can handle absolute path', () => {
expect(absoluteToStylesheet('url("/a.jpg")', href)).toEqual(
`url("http://localhost/a.jpg")`,
);
});
it('can handle external path', () => {
expect(absoluteToStylesheet('url("http://localhost/a.jpg")', href)).toEqual(
`url("http://localhost/a.jpg")`,
);
});
it('can handle single quote path', () => {
expect(absoluteToStylesheet(`url('./a.jpg')`, href)).toEqual(
`url('http://localhost/css/a.jpg')`,
);
});
it('can handle no quote path', () => {
expect(absoluteToStylesheet('url(./a.jpg)', href)).toEqual(
`url(http://localhost/css/a.jpg)`,
);
});
it('can handle multiple no quote paths', () => {
expect(
absoluteToStylesheet(
'background-image: url(images/b.jpg);background: #aabbcc url(images/a.jpg) 50% 50% repeat;',
href,
),
).toEqual(
`background-image: url(http://localhost/css/images/b.jpg);` +
`background: #aabbcc url(http://localhost/css/images/a.jpg) 50% 50% repeat;`,
);
});
it('can handle data url image', () => {
expect(
absoluteToStylesheet('url(data:image/gif;base64,ABC)', href),
).toEqual('url(data:image/gif;base64,ABC)');
expect(
absoluteToStylesheet(
'url(data:application/font-woff;base64,d09GMgABAAAAAAm)',
href,
),
).toEqual('url(data:application/font-woff;base64,d09GMgABAAAAAAm)');
});
it('preserves quotes around inline svgs with spaces', () => {
expect(
absoluteToStylesheet(
"url(\"data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3E%3Cpath fill='%2328a745' d='M3'/%3E%3C/svg%3E\")",
href,
),
).toEqual(
"url(\"data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3E%3Cpath fill='%2328a745' d='M3'/%3E%3C/svg%3E\")",
);
expect(
absoluteToStylesheet(
'url(\'data:image/svg+xml;utf8,<svg width="28" height="32" viewBox="0 0 28 32" xmlns="http://www.w3.org/2000/svg"><path d="M27 14C28" fill="white"/></svg>\')',
href,
),
).toEqual(
'url(\'data:image/svg+xml;utf8,<svg width="28" height="32" viewBox="0 0 28 32" xmlns="http://www.w3.org/2000/svg"><path d="M27 14C28" fill="white"/></svg>\')',
);
expect(
absoluteToStylesheet(
'url("data:image/svg+xml;utf8,<svg width="28" height="32" viewBox="0 0 28 32" xmlns="http://www.w3.org/2000/svg"><path d="M27 14C28" fill="white"/></svg>")',
href,
),
).toEqual(
'url("data:image/svg+xml;utf8,<svg width="28" height="32" viewBox="0 0 28 32" xmlns="http://www.w3.org/2000/svg"><path d="M27 14C28" fill="white"/></svg>")',
);
});
it('can handle empty path', () => {
expect(absoluteToStylesheet(`url('')`, href)).toEqual(`url('')`);
});
});
describe('isBlockedElement()', () => {
const subject = (html: string, opt: any = {}) =>
_isBlockedElement(render(html), 'rr-block', opt.blockSelector);
const render = (html: string): HTMLElement =>
JSDOM.fragment(html).querySelector('div')!;
it('can handle empty elements', () => {
expect(subject('<div />')).toEqual(false);
});
it('blocks prohibited className', () => {
expect(subject('<div class="foo rr-block bar" />')).toEqual(true);
});
it('does not block random data selector', () => {
expect(subject('<div data-rr-block />')).toEqual(false);
});
it('blocks blocked selector', () => {
expect(
subject('<div data-rr-block />', { blockSelector: '[data-rr-block]' }),
).toEqual(true);
});
});
describe('style elements', () => {
const serializeNode = (node: Node): serializedNodeWithId | null => {
return serializeNodeWithId(node, {
doc: document,
mirror: new Mirror(),
blockClass: 'blockblock',
blockSelector: null,
maskTextClass: 'maskmask',
maskTextSelector: null,
skipChild: false,
inlineStylesheet: true,
maskTextFn: undefined,
maskInputFn: undefined,
slimDOMOptions: {},
});
};
const render = (html: string): HTMLStyleElement => {
document.write(html);
return document.querySelector('style')!;
};
it('should serialize all rules of stylesheet when the sheet has a single child node', () => {
const styleEl = render(`<style>body { color: red; }</style>`);
styleEl.sheet?.insertRule('section { color: blue; }');
expect(serializeNode(styleEl.childNodes[0])).toMatchObject({
isStyle: true,
rootId: undefined,
textContent: 'section {color: blue;}body {color: red;}',
type: 3,
});
});
it('should serialize individual text nodes on stylesheets with multiple child nodes', () => {
const styleEl = render(`<style>body { color: red; }</style>`);
styleEl.append(document.createTextNode('section { color: blue; }'));
expect(serializeNode(styleEl.childNodes[1])).toMatchObject({
isStyle: true,
rootId: undefined,
textContent: 'section { color: blue; }',
type: 3,
});
});
});
describe('scrollTop/scrollLeft', () => {
const serializeNode = (node: Node): serializedNodeWithId | null => {
return serializeNodeWithId(node, {
doc: document,
mirror: new Mirror(),
blockClass: 'blockblock',
blockSelector: null,
maskTextClass: 'maskmask',
maskTextSelector: null,
skipChild: false,
inlineStylesheet: true,
maskTextFn: undefined,
maskInputFn: undefined,
slimDOMOptions: {},
newlyAddedElement: false,
});
};
const render = (html: string): HTMLDivElement => {
document.write(html);
return document.querySelector('div')!;
};
it('should serialize scroll positions', () => {
const el = render(`<div stylel='overflow: auto; width: 1px; height: 1px;'>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
</div>`);
el.scrollTop = 10;
el.scrollLeft = 20;
expect(serializeNode(el)).toMatchObject({
attributes: {
rr_scrollTop: 10,
rr_scrollLeft: 20,
},
});
});
});