refactoring public API
This commit is contained in:
@@ -77,9 +77,12 @@ export function addHoverClass(cssText: string): string {
|
||||
|
||||
function buildNode(
|
||||
n: serializedNodeWithId,
|
||||
doc: Document,
|
||||
HACK_CSS: boolean,
|
||||
options: {
|
||||
doc: Document;
|
||||
hackCss: boolean;
|
||||
},
|
||||
): Node | null {
|
||||
const { doc, hackCss } = options;
|
||||
switch (n.type) {
|
||||
case NodeType.Document:
|
||||
return doc.implementation.createDocument(null, '', null);
|
||||
@@ -109,7 +112,7 @@ function buildNode(
|
||||
const isTextarea = tagName === 'textarea' && name === 'value';
|
||||
const isRemoteOrDynamicCss =
|
||||
tagName === 'style' && name === '_cssText';
|
||||
if (isRemoteOrDynamicCss && HACK_CSS) {
|
||||
if (isRemoteOrDynamicCss && hackCss) {
|
||||
value = addHoverClass(value);
|
||||
}
|
||||
if (isTextarea || isRemoteOrDynamicCss) {
|
||||
@@ -177,7 +180,7 @@ function buildNode(
|
||||
return node;
|
||||
case NodeType.Text:
|
||||
return doc.createTextNode(
|
||||
n.isStyle && HACK_CSS ? addHoverClass(n.textContent) : n.textContent,
|
||||
n.isStyle && hackCss ? addHoverClass(n.textContent) : n.textContent,
|
||||
);
|
||||
case NodeType.CDATA:
|
||||
return doc.createCDATASection(n.textContent);
|
||||
@@ -190,12 +193,15 @@ function buildNode(
|
||||
|
||||
export function buildNodeWithSN(
|
||||
n: serializedNodeWithId,
|
||||
doc: Document,
|
||||
map: idNodeMap,
|
||||
skipChild = false,
|
||||
HACK_CSS = true,
|
||||
options: {
|
||||
doc: Document;
|
||||
map: idNodeMap;
|
||||
skipChild?: boolean;
|
||||
hackCss: boolean;
|
||||
},
|
||||
): INode | null {
|
||||
let node = buildNode(n, doc, HACK_CSS);
|
||||
const { doc, map, skipChild = false, hackCss = true } = options;
|
||||
let node = buildNode(n, { doc, hackCss });
|
||||
if (!node) {
|
||||
return null;
|
||||
}
|
||||
@@ -214,7 +220,12 @@ export function buildNodeWithSN(
|
||||
!skipChild
|
||||
) {
|
||||
for (const childN of n.childNodes) {
|
||||
const childNode = buildNodeWithSN(childN, doc, map, false, HACK_CSS);
|
||||
const childNode = buildNodeWithSN(childN, {
|
||||
doc,
|
||||
map,
|
||||
skipChild: false,
|
||||
hackCss,
|
||||
});
|
||||
if (!childNode) {
|
||||
console.warn('Failed to rebuild', childN);
|
||||
} else {
|
||||
@@ -259,15 +270,20 @@ function handleScroll(node: INode) {
|
||||
|
||||
function rebuild(
|
||||
n: serializedNodeWithId,
|
||||
doc: Document,
|
||||
onVisit?: (node: INode) => unknown,
|
||||
/**
|
||||
* This is not a public API yet, just for POC
|
||||
*/
|
||||
HACK_CSS: boolean = true,
|
||||
options: {
|
||||
doc: Document;
|
||||
onVisit?: (node: INode) => unknown;
|
||||
hackCss?: boolean;
|
||||
},
|
||||
): [Node | null, idNodeMap] {
|
||||
const { doc, onVisit, hackCss = true } = options;
|
||||
const idNodeMap: idNodeMap = {};
|
||||
const node = buildNodeWithSN(n, doc, idNodeMap, false, HACK_CSS);
|
||||
const node = buildNodeWithSN(n, {
|
||||
doc,
|
||||
map: idNodeMap,
|
||||
skipChild: false,
|
||||
hackCss,
|
||||
});
|
||||
visit(idNodeMap, (visitedNode) => {
|
||||
if (onVisit) {
|
||||
onVisit(visitedNode);
|
||||
|
||||
115
src/snapshot.ts
115
src/snapshot.ts
@@ -187,13 +187,23 @@ export function _isBlockedElement(
|
||||
|
||||
function serializeNode(
|
||||
n: Node,
|
||||
doc: Document,
|
||||
blockClass: string | RegExp,
|
||||
blockSelector: string | null,
|
||||
inlineStylesheet: boolean,
|
||||
maskInputOptions: MaskInputOptions = {},
|
||||
recordCanvas: boolean,
|
||||
options: {
|
||||
doc: Document;
|
||||
blockClass: string | RegExp;
|
||||
blockSelector: string | null;
|
||||
inlineStylesheet: boolean;
|
||||
maskInputOptions: MaskInputOptions;
|
||||
recordCanvas: boolean;
|
||||
},
|
||||
): serializedNode | false {
|
||||
const {
|
||||
doc,
|
||||
blockClass,
|
||||
blockSelector,
|
||||
inlineStylesheet,
|
||||
maskInputOptions = {},
|
||||
recordCanvas,
|
||||
} = options;
|
||||
switch (n.nodeType) {
|
||||
case n.DOCUMENT_NODE:
|
||||
return {
|
||||
@@ -437,26 +447,39 @@ function slimDOMExcluded(
|
||||
|
||||
export function serializeNodeWithId(
|
||||
n: Node | INode,
|
||||
doc: Document,
|
||||
map: idNodeMap,
|
||||
blockClass: string | RegExp,
|
||||
blockSelector: string | null,
|
||||
skipChild = false,
|
||||
inlineStylesheet = true,
|
||||
maskInputOptions?: MaskInputOptions,
|
||||
slimDOMOptions: SlimDOMOptions = {},
|
||||
recordCanvas?: boolean,
|
||||
preserveWhiteSpace = true,
|
||||
options: {
|
||||
doc: Document;
|
||||
map: idNodeMap;
|
||||
blockClass: string | RegExp;
|
||||
blockSelector: string | null;
|
||||
skipChild: boolean;
|
||||
inlineStylesheet: boolean;
|
||||
maskInputOptions?: MaskInputOptions;
|
||||
slimDOMOptions: SlimDOMOptions;
|
||||
recordCanvas?: boolean;
|
||||
preserveWhiteSpace?: boolean;
|
||||
},
|
||||
): serializedNodeWithId | null {
|
||||
const _serializedNode = serializeNode(
|
||||
n,
|
||||
const {
|
||||
doc,
|
||||
map,
|
||||
blockClass,
|
||||
blockSelector,
|
||||
skipChild = false,
|
||||
inlineStylesheet = true,
|
||||
maskInputOptions = {},
|
||||
slimDOMOptions,
|
||||
recordCanvas = false,
|
||||
} = options;
|
||||
let { preserveWhiteSpace = true } = options;
|
||||
const _serializedNode = serializeNode(n, {
|
||||
doc,
|
||||
blockClass,
|
||||
blockSelector,
|
||||
inlineStylesheet,
|
||||
maskInputOptions,
|
||||
recordCanvas || false,
|
||||
);
|
||||
recordCanvas,
|
||||
});
|
||||
if (!_serializedNode) {
|
||||
// TODO: dev only
|
||||
console.warn(n, 'not serialized');
|
||||
@@ -504,8 +527,7 @@ export function serializeNodeWithId(
|
||||
preserveWhiteSpace = false;
|
||||
}
|
||||
for (const childN of Array.from(n.childNodes)) {
|
||||
const serializedChildNode = serializeNodeWithId(
|
||||
childN,
|
||||
const serializedChildNode = serializeNodeWithId(childN, {
|
||||
doc,
|
||||
map,
|
||||
blockClass,
|
||||
@@ -516,7 +538,7 @@ export function serializeNodeWithId(
|
||||
slimDOMOptions,
|
||||
recordCanvas,
|
||||
preserveWhiteSpace,
|
||||
);
|
||||
});
|
||||
if (serializedChildNode) {
|
||||
serializedNode.childNodes.push(serializedChildNode);
|
||||
}
|
||||
@@ -527,16 +549,26 @@ export function serializeNodeWithId(
|
||||
|
||||
function snapshot(
|
||||
n: Document,
|
||||
blockClass: string | RegExp = 'rr-block',
|
||||
inlineStylesheet = true,
|
||||
maskAllInputsOrOptions: boolean | MaskInputOptions,
|
||||
slimDOMSensibleOrOptions: boolean | SlimDOMOptions,
|
||||
recordCanvas?: boolean,
|
||||
blockSelector: string | null = null,
|
||||
options?: {
|
||||
blockClass?: string | RegExp;
|
||||
inlineStylesheet?: boolean;
|
||||
maskAllInputs?: boolean | MaskInputOptions;
|
||||
slimDOM?: boolean | SlimDOMOptions;
|
||||
recordCanvas?: boolean;
|
||||
blockSelector?: string | null;
|
||||
},
|
||||
): [serializedNodeWithId | null, idNodeMap] {
|
||||
const {
|
||||
blockClass = 'rr-block',
|
||||
inlineStylesheet = true,
|
||||
recordCanvas = false,
|
||||
blockSelector = null,
|
||||
maskAllInputs = false,
|
||||
slimDOM = false,
|
||||
} = options || {};
|
||||
const idNodeMap: idNodeMap = {};
|
||||
const maskInputOptions: MaskInputOptions =
|
||||
maskAllInputsOrOptions === true
|
||||
maskAllInputs === true
|
||||
? {
|
||||
color: true,
|
||||
date: true,
|
||||
@@ -554,40 +586,39 @@ function snapshot(
|
||||
textarea: true,
|
||||
select: true,
|
||||
}
|
||||
: maskAllInputsOrOptions === false
|
||||
: maskAllInputs === false
|
||||
? {}
|
||||
: maskAllInputsOrOptions;
|
||||
: maskAllInputs;
|
||||
const slimDOMOptions: SlimDOMOptions =
|
||||
slimDOMSensibleOrOptions === true || slimDOMSensibleOrOptions === 'all'
|
||||
slimDOM === true || slimDOM === 'all'
|
||||
? // if true: set of sensible options that should not throw away any information
|
||||
{
|
||||
script: true,
|
||||
comment: true,
|
||||
headFavicon: true,
|
||||
headWhitespace: true,
|
||||
headMetaDescKeywords: slimDOMSensibleOrOptions === 'all', // destructive
|
||||
headMetaDescKeywords: slimDOM === 'all', // destructive
|
||||
headMetaSocial: true,
|
||||
headMetaRobots: true,
|
||||
headMetaHttpEquiv: true,
|
||||
headMetaAuthorship: true,
|
||||
headMetaVerification: true,
|
||||
}
|
||||
: slimDOMSensibleOrOptions === false
|
||||
: slimDOM === false
|
||||
? {}
|
||||
: slimDOMSensibleOrOptions;
|
||||
: slimDOM;
|
||||
return [
|
||||
serializeNodeWithId(
|
||||
n,
|
||||
n,
|
||||
idNodeMap,
|
||||
serializeNodeWithId(n, {
|
||||
doc: n,
|
||||
map: idNodeMap,
|
||||
blockClass,
|
||||
blockSelector,
|
||||
false,
|
||||
skipChild: false,
|
||||
inlineStylesheet,
|
||||
maskInputOptions,
|
||||
slimDOMOptions,
|
||||
recordCanvas,
|
||||
),
|
||||
}),
|
||||
idNodeMap,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import { SnapshotState, toMatchSnapshot } from 'jest-snapshot';
|
||||
import { Suite } from 'mocha';
|
||||
|
||||
const htmlFolder = path.join(__dirname, 'html');
|
||||
const htmls = fs.readdirSync(htmlFolder).map(filePath => {
|
||||
const htmls = fs.readdirSync(htmlFolder).map((filePath) => {
|
||||
const raw = fs.readFileSync(path.resolve(htmlFolder, filePath), 'utf-8');
|
||||
return {
|
||||
filePath,
|
||||
@@ -24,7 +24,7 @@ interface IMimeType {
|
||||
}
|
||||
|
||||
const server = () =>
|
||||
new Promise<http.Server>(resolve => {
|
||||
new Promise<http.Server>((resolve) => {
|
||||
const mimeType: IMimeType = {
|
||||
'.html': 'text/html',
|
||||
'.js': 'text/javascript',
|
||||
@@ -73,7 +73,7 @@ interface ISuite extends Suite {
|
||||
code: string;
|
||||
}
|
||||
|
||||
describe('integration tests', function(this: ISuite) {
|
||||
describe('integration tests', function (this: ISuite) {
|
||||
before(async () => {
|
||||
this.server = await server();
|
||||
this.browser = await puppeteer.launch({
|
||||
@@ -102,16 +102,18 @@ describe('integration tests', function(this: ISuite) {
|
||||
const page: puppeteer.Page = await this.browser.newPage();
|
||||
// console for debug
|
||||
// tslint:disable-next-line: no-console
|
||||
page.on('console', msg => console.log(msg.text()));
|
||||
page.on('console', (msg) => console.log(msg.text()));
|
||||
await page.goto(`http://localhost:3030/html`);
|
||||
await page.setContent(html.src, {
|
||||
waitUntil: 'load',
|
||||
});
|
||||
const rebuildHtml = (await page.evaluate(`${this.code}
|
||||
const rebuildHtml = (
|
||||
await page.evaluate(`${this.code}
|
||||
const x = new XMLSerializer();
|
||||
const [snap] = rrweb.snapshot(document);
|
||||
x.serializeToString(rrweb.rebuild(snap, document)[0]);
|
||||
`)).replace(/\n\n/g, '');
|
||||
x.serializeToString(rrweb.rebuild(snap, { doc: document })[0]);
|
||||
`)
|
||||
).replace(/\n\n/g, '');
|
||||
const result = matchSnapshot(rebuildHtml, __filename, title);
|
||||
assert(result.pass, result.pass ? '' : result.report());
|
||||
}).timeout(5000);
|
||||
|
||||
13
typings/rebuild.d.ts
vendored
13
typings/rebuild.d.ts
vendored
@@ -1,5 +1,14 @@
|
||||
import { serializedNodeWithId, idNodeMap, INode } from './types';
|
||||
export declare function addHoverClass(cssText: string): string;
|
||||
export declare function buildNodeWithSN(n: serializedNodeWithId, doc: Document, map: idNodeMap, skipChild?: boolean, HACK_CSS?: boolean): INode | null;
|
||||
declare function rebuild(n: serializedNodeWithId, doc: Document, onVisit?: (node: INode) => unknown, HACK_CSS?: boolean): [Node | null, idNodeMap];
|
||||
export declare function buildNodeWithSN(n: serializedNodeWithId, options: {
|
||||
doc: Document;
|
||||
map: idNodeMap;
|
||||
skipChild?: boolean;
|
||||
hackCss: boolean;
|
||||
}): INode | null;
|
||||
declare function rebuild(n: serializedNodeWithId, options: {
|
||||
doc: Document;
|
||||
onVisit?: (node: INode) => unknown;
|
||||
hackCss?: boolean;
|
||||
}): [Node | null, idNodeMap];
|
||||
export default rebuild;
|
||||
|
||||
22
typings/snapshot.d.ts
vendored
22
typings/snapshot.d.ts
vendored
@@ -4,8 +4,26 @@ export declare function absoluteToStylesheet(cssText: string | null, href: strin
|
||||
export declare function absoluteToDoc(doc: Document, attributeValue: string): string;
|
||||
export declare function transformAttribute(doc: Document, name: string, value: string): string;
|
||||
export declare function _isBlockedElement(element: HTMLElement, blockClass: string | RegExp, blockSelector: string | null): boolean;
|
||||
export declare function serializeNodeWithId(n: Node | INode, doc: Document, map: idNodeMap, blockClass: string | RegExp, blockSelector: string | null, skipChild?: boolean, inlineStylesheet?: boolean, maskInputOptions?: MaskInputOptions, slimDOMOptions?: SlimDOMOptions, recordCanvas?: boolean, preserveWhiteSpace?: boolean): serializedNodeWithId | null;
|
||||
declare function snapshot(n: Document, blockClass: string | RegExp | undefined, inlineStylesheet: boolean | undefined, maskAllInputsOrOptions: boolean | MaskInputOptions, slimDOMSensibleOrOptions: boolean | SlimDOMOptions, recordCanvas?: boolean, blockSelector?: string | null): [serializedNodeWithId | null, idNodeMap];
|
||||
export declare function serializeNodeWithId(n: Node | INode, options: {
|
||||
doc: Document;
|
||||
map: idNodeMap;
|
||||
blockClass: string | RegExp;
|
||||
blockSelector: string | null;
|
||||
skipChild: boolean;
|
||||
inlineStylesheet: boolean;
|
||||
maskInputOptions?: MaskInputOptions;
|
||||
slimDOMOptions: SlimDOMOptions;
|
||||
recordCanvas?: boolean;
|
||||
preserveWhiteSpace?: boolean;
|
||||
}): serializedNodeWithId | null;
|
||||
declare function snapshot(n: Document, options?: {
|
||||
blockClass?: string | RegExp;
|
||||
inlineStylesheet?: boolean;
|
||||
maskAllInputs?: boolean | MaskInputOptions;
|
||||
slimDOM?: boolean | SlimDOMOptions;
|
||||
recordCanvas?: boolean;
|
||||
blockSelector?: string | null;
|
||||
}): [serializedNodeWithId | null, idNodeMap];
|
||||
export declare function visitSnapshot(node: serializedNodeWithId, onVisit: (node: serializedNodeWithId) => unknown): void;
|
||||
export declare function cleanupSnapshot(): void;
|
||||
export default snapshot;
|
||||
|
||||
Reference in New Issue
Block a user