* Chore: Add move most types from rrweb to @rrweb/types package * Split off type imports * Split off type import to its own line * Get vite to generate type definitions * Apply formatting changes * noEmit not allowed in tsconfig, moved it to build step * Migrate rrdom-nodejs build to vite * Apply formatting changes * Migrate rrweb-snapshot to vite * Unify configs * Chore: Migrate rrdom to vite Turns out what we where doing by overwriting `public textContent: string | undefined` as a getter in a subclass is something that isn't allowed in typescript. Because we where using `// @ts-ignore` to hide this error our bundler chose to allow the overwrite. Vite choses to disallow the overwrite making all subclasses' `textContent` undefined. To mitigate this we're using an abstract class, which does allow sub classes to decide if they wan't to use getters or not. * Chore: Migrate rrweb to vite WIP * build:browser was removed (for now) * BREAKING: moved rrweb-plugin-console to its own npm module This removes console from rrweb-all.js * Support cjs files in startServer * Move canvas-webrtc plugin to its own package * Chore: move sequential-id plugin to its own package * Chore: Configure rrweb's vite bundling * `Id` had lowercase `d` before, making it lowercase again * Test: Move console tests to their own package * remove unused utils from rrdom * pull in latest version of master something when wrong earlier when resolving merge conflicts, this should be correct * Fix type casting issue in diff.ts * Fix typo * Fix duplicate entries in package.json and tsconfig.json * Apply formatting changes * Update dependencies in package.json files * Update dependencies to use Vite 5.2.8 in package.json files * Get tests passing for rrdom `apply virtual style rules to node` tests need to be moved to rrweb to avoid circular dependencies * Fix image loading issue in integration tests * Move pack/unpack to its own @rrweb/packer module * Get tests to work in rrdom-nodejs * Port tests in rrweb-snapshot to vitest and fix them * Fix tests for rrweb-plugin-console-record * Add @rrweb/all package * Fix publint and attw errors for rrdom and @rrweb/types * Use shared vitest.config.ts in rrweb-snapshot package * Fix publint and attw issues for rrweb-snapshot * Export `ReplayPlugin` type directly from rrweb * Fix publint and attw issues for packages * Fix publint & attw issue. I was bumping into this issue:3729bc2a3c/docs/problems/NoResolution.mdAnd had to choose one of these three methods described here: https://github.com/andrewbranch/example-subpath-exports-ts-compat?tab=readme-ov-file#typescript-friendly-strategies-for-packagejson-subpath-exports-compatibility And I ended up going for the method described here:1ffe3425b0/examples/node_modules/package-json-redirects (package-json-redirects)The redirect method seemed the least invasive and most effective. * Fix publint & attw issue. I was bumping into this issue:3729bc2a3c/docs/problems/NoResolution.mdAnd had to choose one of these three methods described here: https://github.com/andrewbranch/example-subpath-exports-ts-compat?tab=readme-ov-file#typescript-friendly-strategies-for-packagejson-subpath-exports-compatibility And I ended up going for the method described here:1ffe3425b0/examples/node_modules/package-json-redirects (package-json-redirects)The redirect method seemed the least invasive and most effective. * move some rrdom tests that require rrweb to rrweb package * Use pre-jest 29 syntax for snapshotting * get rrweb passing publint and attw * const enum does not work with isolated modules flag * Fix script tag type in webgl.test.ts.snap and update rrweb.umd.cjs path in webgl.test.ts * Fix paths * Move tests for console record plugin and fix bundle path * Fix tests for rrweb * pack integration tests were moved to @rrweb/all * Update rrweb bundle path in test files * Fix flaky scroll emit from test * Migrate rrweb's tests over to vitest and make them pass * Make sure benchmarks & updating tests work * Remove jest from rrweb * Fix paths * always use rrweb's own cssom * Update tsconfig.json for rrweb-plugin-sequential-id-record Fixes this error: Error: @rrweb/rrweb-plugin-sequential-id-record:prepublish: tsconfig.json(9,5): error TS6377: Cannot write file '/home/runner/work/rrweb/rrweb/tsconfig.tsbuildinfo' because it will overwrite '.tsbuildinfo' file generated by referenced project '/home/runner/work/rrweb/rrweb/packages/rrweb' * Add tsbuildinfo config to extended tsconfig files * Move rrdom over to vitest * Apply formatting changes * Update rrweb imports to use the new package structure * extend rrweb-snapshot's tsconfig from monorepo base config * extend @rrweb/types's tsconfig from monorepo base config * extend rrdom's tsconfig from monorepo base config * extend rrdom-nodejs's tsconfig from monorepo base config * extend web-extension's tsconfig from monorepo base config * unify tsconfigs * Continue when tests fail * Add stricter type checking * Add check-types global command * remove jest * Remove unused code * Add check-types command to build script * Fix linting issues * Add setup Chrome action for CI/CD workflow * Update puppeteer version in package.json for rrweb * Update Chrome setup in CI/CD workflow * Update Chrome setup in CI/CD workflow * Add Chrome setup and test cache location * Update CI/CD workflow to test chrome cache location * Add chrome installation step to CI/CD workflow * Update Puppeteer configuration for headless testing * Update dependencies and workflow configuration * Use same version of chrome on CI as is run locally * Use version of chrome that seems to work with rrdom tests * Try using puppeteerrc to define chrome version * Add .cache directory to .gitignore * Move global flag to vitest config * Update puppeteer version to 20.9.0 * Update console log messages in rrweb-plugin-console-record for new puppeteer version * Remove redundant Chrome setup from CI/CD workflow * Add minification and umd for all built files * Update import paths for rrweb dist files * Add @rrweb/replay and @rrweb/record * Add script to lint packages * Apply formatting changes * exclude styles export from typescript package type checking * WIP Move rrweb-player over to vite * Apply formatting changes * chore: Update rrweb plugin import paths * Remove rollup from rrweb-player * Fix typing issues * Fix typing issues * chore: Update rrweb-player to use vite for build process * Apply formatting changes * chore: Export Player class in rrweb-player/src/main.ts Makes attw happy * Apply formatting changes * Gets wiped by yarn workspaces-to-typescript-project-references * Add .eslintignore and .eslintrc.cjs files for rrweb-player package * Apply formatting changes * Update dependencies in rrweb-player/package.json * Apply formatting changes * chore: Update eslint configuration for rrweb-player package * Apply formatting changes * chore: Remove unused files from rrweb-player package * Apply formatting changes * chore: Update rrweb-player import path to use rrweb-player.cjs * chore: Update addEventListener signature in rrweb-player * Apply formatting changes * Add .eslintignore and update .gitignore files for to root * Apply formatting changes * Update documentation * Update @rrweb/types package description * Apply formatting changes * Update build and run commands in CONTRIBUTING.md * Apply formatting changes * Update package versions to 2.0.0-alpha.13 * Apply formatting changes * Apply formatting changes * Fix import statement in media/index.ts * Apply formatting changes * chore: Update .gitignore to exclude build and dist directories * Apply formatting changes * Apply formatting changes * Migrate setTimeout to vitest * Apply formatting changes * Apply formatting changes * Fix isNativeShadowDom function signature in utils.ts * try out jsr * Apply formatting changes * Update package versions to 2.0.0-alpha.14 * Apply formatting changes * Fix name of rrwebSnapshot object * Apply formatting changes * Remove unused lock files * Apply formatting changes * Update rrweb bundle path to use umd.cjs format * Apply formatting changes * Trigger tests to run again * Rename snapshots for vitest * Apply formatting changes * Ping CI * Apply formatting changes * Ping CI * Apply formatting changes * Ignore files generated by svelte-kit for prettier * Correct Player object
488 lines
13 KiB
TypeScript
488 lines
13 KiB
TypeScript
import {
|
|
NodeType as RRNodeType,
|
|
createMirror as createNodeMirror,
|
|
} from 'rrweb-snapshot';
|
|
import type {
|
|
Mirror as NodeMirror,
|
|
IMirror,
|
|
serializedNodeWithId,
|
|
} from 'rrweb-snapshot';
|
|
import type {
|
|
canvasMutationData,
|
|
canvasEventWithTime,
|
|
inputData,
|
|
scrollData,
|
|
styleSheetRuleData,
|
|
styleDeclarationData,
|
|
} from '@rrweb/types';
|
|
import {
|
|
BaseRRNode as RRNode,
|
|
BaseRRCDATASection,
|
|
BaseRRComment,
|
|
BaseRRDocument,
|
|
BaseRRDocumentType,
|
|
BaseRRElement,
|
|
BaseRRMediaElement,
|
|
BaseRRText,
|
|
type IRRDocument,
|
|
type IRRElement,
|
|
type IRRNode,
|
|
NodeType,
|
|
type IRRDocumentType,
|
|
type IRRText,
|
|
type IRRComment,
|
|
} from './document';
|
|
|
|
export class RRDocument extends BaseRRDocument {
|
|
private UNSERIALIZED_STARTING_ID = -2;
|
|
// In the rrweb replayer, there are some unserialized nodes like the element that stores the injected style rules.
|
|
// These unserialized nodes may interfere the execution of the diff algorithm.
|
|
// The id of serialized node is larger than 0. So this value less than 0 is used as id for these unserialized nodes.
|
|
private _unserializedId = this.UNSERIALIZED_STARTING_ID;
|
|
|
|
/**
|
|
* Every time the id is used, it will minus 1 automatically to avoid collisions.
|
|
*/
|
|
public get unserializedId(): number {
|
|
return this._unserializedId--;
|
|
}
|
|
|
|
public mirror: Mirror = createMirror();
|
|
|
|
public scrollData: scrollData | null = null;
|
|
|
|
constructor(mirror?: Mirror) {
|
|
super();
|
|
if (mirror) {
|
|
this.mirror = mirror;
|
|
}
|
|
}
|
|
|
|
createDocument(
|
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
_namespace: string | null,
|
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
_qualifiedName: string | null,
|
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
_doctype?: DocumentType | null,
|
|
) {
|
|
return new RRDocument();
|
|
}
|
|
|
|
createDocumentType(
|
|
qualifiedName: string,
|
|
publicId: string,
|
|
systemId: string,
|
|
) {
|
|
const documentTypeNode = new RRDocumentType(
|
|
qualifiedName,
|
|
publicId,
|
|
systemId,
|
|
);
|
|
documentTypeNode.ownerDocument = this;
|
|
return documentTypeNode;
|
|
}
|
|
|
|
createElement<K extends keyof HTMLElementTagNameMap>(
|
|
tagName: K,
|
|
): RRElementType<K>;
|
|
createElement(tagName: string): RRElement;
|
|
createElement(tagName: string) {
|
|
const upperTagName = tagName.toUpperCase();
|
|
let element;
|
|
switch (upperTagName) {
|
|
case 'AUDIO':
|
|
case 'VIDEO':
|
|
element = new RRMediaElement(upperTagName);
|
|
break;
|
|
case 'IFRAME':
|
|
element = new RRIFrameElement(upperTagName, this.mirror);
|
|
break;
|
|
case 'CANVAS':
|
|
element = new RRCanvasElement(upperTagName);
|
|
break;
|
|
case 'STYLE':
|
|
element = new RRStyleElement(upperTagName);
|
|
break;
|
|
default:
|
|
element = new RRElement(upperTagName);
|
|
break;
|
|
}
|
|
element.ownerDocument = this;
|
|
return element;
|
|
}
|
|
|
|
createComment(data: string) {
|
|
const commentNode = new RRComment(data);
|
|
commentNode.ownerDocument = this;
|
|
return commentNode;
|
|
}
|
|
|
|
createCDATASection(data: string) {
|
|
const sectionNode = new RRCDATASection(data);
|
|
sectionNode.ownerDocument = this;
|
|
return sectionNode;
|
|
}
|
|
|
|
createTextNode(data: string) {
|
|
const textNode = new RRText(data);
|
|
textNode.ownerDocument = this;
|
|
return textNode;
|
|
}
|
|
|
|
destroyTree() {
|
|
this.firstChild = null;
|
|
this.lastChild = null;
|
|
this.mirror.reset();
|
|
}
|
|
|
|
open() {
|
|
super.open();
|
|
this._unserializedId = this.UNSERIALIZED_STARTING_ID;
|
|
}
|
|
}
|
|
|
|
export const RRDocumentType = BaseRRDocumentType;
|
|
|
|
export class RRElement extends BaseRRElement {
|
|
inputData: inputData | null = null;
|
|
scrollData: scrollData | null = null;
|
|
}
|
|
|
|
export class RRMediaElement extends BaseRRMediaElement {}
|
|
|
|
export class RRCanvasElement extends RRElement implements IRRElement {
|
|
public rr_dataURL: string | null = null;
|
|
public canvasMutations: {
|
|
event: canvasEventWithTime;
|
|
mutation: canvasMutationData;
|
|
}[] = [];
|
|
/**
|
|
* This is a dummy implementation to distinguish RRCanvasElement from real HTMLCanvasElement.
|
|
*/
|
|
getContext(): RenderingContext | null {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export class RRStyleElement extends RRElement {
|
|
public rules: (styleSheetRuleData | styleDeclarationData)[] = [];
|
|
}
|
|
|
|
export class RRIFrameElement extends RRElement {
|
|
contentDocument: RRDocument = new RRDocument();
|
|
constructor(upperTagName: string, mirror: Mirror) {
|
|
super(upperTagName);
|
|
this.contentDocument.mirror = mirror;
|
|
}
|
|
}
|
|
|
|
export const RRText = BaseRRText;
|
|
export type RRText = typeof RRText;
|
|
|
|
export const RRComment = BaseRRComment;
|
|
export type RRComment = typeof RRComment;
|
|
|
|
export const RRCDATASection = BaseRRCDATASection;
|
|
export type RRCDATASection = typeof RRCDATASection;
|
|
|
|
interface RRElementTagNameMap {
|
|
audio: RRMediaElement;
|
|
canvas: RRCanvasElement;
|
|
iframe: RRIFrameElement;
|
|
style: RRStyleElement;
|
|
video: RRMediaElement;
|
|
}
|
|
|
|
type RRElementType<K extends keyof HTMLElementTagNameMap> =
|
|
K extends keyof RRElementTagNameMap ? RRElementTagNameMap[K] : RRElement;
|
|
|
|
function getValidTagName(element: HTMLElement): string {
|
|
// https://github.com/rrweb-io/rrweb-snapshot/issues/56
|
|
if (element instanceof HTMLFormElement) {
|
|
return 'FORM';
|
|
}
|
|
return element.tagName.toUpperCase();
|
|
}
|
|
|
|
/**
|
|
* Build a RRNode from a real Node.
|
|
* @param node - the real Node
|
|
* @param rrdom - the RRDocument
|
|
* @param domMirror - the NodeMirror that records the real document tree
|
|
* @returns the built RRNode
|
|
*/
|
|
export function buildFromNode(
|
|
node: Node,
|
|
rrdom: IRRDocument,
|
|
domMirror: NodeMirror,
|
|
parentRRNode?: IRRNode | null,
|
|
): IRRNode | null {
|
|
let rrNode: IRRNode;
|
|
|
|
switch (node.nodeType) {
|
|
case NodeType.DOCUMENT_NODE:
|
|
if (parentRRNode && parentRRNode.nodeName === 'IFRAME')
|
|
rrNode = (parentRRNode as RRIFrameElement).contentDocument;
|
|
else {
|
|
rrNode = rrdom;
|
|
(rrNode as IRRDocument).compatMode = (node as Document).compatMode as
|
|
| 'BackCompat'
|
|
| 'CSS1Compat';
|
|
}
|
|
break;
|
|
case NodeType.DOCUMENT_TYPE_NODE: {
|
|
const documentType = node as DocumentType;
|
|
rrNode = rrdom.createDocumentType(
|
|
documentType.name,
|
|
documentType.publicId,
|
|
documentType.systemId,
|
|
);
|
|
break;
|
|
}
|
|
case NodeType.ELEMENT_NODE: {
|
|
const elementNode = node as HTMLElement;
|
|
const tagName = getValidTagName(elementNode);
|
|
rrNode = rrdom.createElement(tagName);
|
|
const rrElement = rrNode as IRRElement;
|
|
for (const { name, value } of Array.from(elementNode.attributes)) {
|
|
rrElement.attributes[name] = value;
|
|
}
|
|
elementNode.scrollLeft && (rrElement.scrollLeft = elementNode.scrollLeft);
|
|
elementNode.scrollTop && (rrElement.scrollTop = elementNode.scrollTop);
|
|
/**
|
|
* We don't have to record special values of input elements at the beginning.
|
|
* Because if these values are changed later, the mutation will be applied through the batched input events on its RRElement after the diff algorithm is executed.
|
|
*/
|
|
break;
|
|
}
|
|
case NodeType.TEXT_NODE:
|
|
rrNode = rrdom.createTextNode((node as Text).textContent || '');
|
|
break;
|
|
case NodeType.CDATA_SECTION_NODE:
|
|
rrNode = rrdom.createCDATASection((node as CDATASection).data);
|
|
break;
|
|
case NodeType.COMMENT_NODE:
|
|
rrNode = rrdom.createComment((node as Comment).textContent || '');
|
|
break;
|
|
// if node is a shadow root
|
|
case NodeType.DOCUMENT_FRAGMENT_NODE:
|
|
rrNode = (parentRRNode as IRRElement).attachShadow({ mode: 'open' });
|
|
break;
|
|
default:
|
|
return null;
|
|
}
|
|
|
|
let sn: serializedNodeWithId | null = domMirror.getMeta(node);
|
|
|
|
if (rrdom instanceof RRDocument) {
|
|
if (!sn) {
|
|
sn = getDefaultSN(rrNode, rrdom.unserializedId);
|
|
domMirror.add(node, sn);
|
|
}
|
|
rrdom.mirror.add(rrNode, { ...sn });
|
|
}
|
|
|
|
return rrNode;
|
|
}
|
|
|
|
/**
|
|
* Build a RRDocument from a real document tree.
|
|
* @param dom - the real document tree
|
|
* @param domMirror - the NodeMirror that records the real document tree
|
|
* @param rrdom - the rrdom object to be constructed
|
|
* @returns the build rrdom
|
|
*/
|
|
export function buildFromDom(
|
|
dom: Document,
|
|
domMirror: NodeMirror = createNodeMirror(),
|
|
rrdom: IRRDocument = new RRDocument(),
|
|
) {
|
|
function walk(node: Node, parentRRNode: IRRNode | null) {
|
|
const rrNode = buildFromNode(node, rrdom, domMirror, parentRRNode);
|
|
if (rrNode === null) return;
|
|
if (
|
|
// if the parentRRNode isn't a RRIFrameElement
|
|
parentRRNode?.nodeName !== 'IFRAME' &&
|
|
// if node isn't a shadow root
|
|
node.nodeType !== NodeType.DOCUMENT_FRAGMENT_NODE
|
|
) {
|
|
parentRRNode?.appendChild(rrNode);
|
|
rrNode.parentNode = parentRRNode;
|
|
rrNode.parentElement = parentRRNode as RRElement;
|
|
}
|
|
|
|
if (node.nodeName === 'IFRAME') {
|
|
const iframeDoc = (node as HTMLIFrameElement).contentDocument;
|
|
iframeDoc && walk(iframeDoc, rrNode);
|
|
} else if (
|
|
node.nodeType === NodeType.DOCUMENT_NODE ||
|
|
node.nodeType === NodeType.ELEMENT_NODE ||
|
|
node.nodeType === NodeType.DOCUMENT_FRAGMENT_NODE
|
|
) {
|
|
// if the node is a shadow dom
|
|
if (
|
|
node.nodeType === NodeType.ELEMENT_NODE &&
|
|
(node as HTMLElement).shadowRoot
|
|
)
|
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
walk((node as HTMLElement).shadowRoot!, rrNode);
|
|
node.childNodes.forEach((childNode) => walk(childNode, rrNode));
|
|
}
|
|
}
|
|
walk(dom, null);
|
|
return rrdom;
|
|
}
|
|
|
|
export function createMirror(): Mirror {
|
|
return new Mirror();
|
|
}
|
|
|
|
// based on Mirror from rrweb-snapshots
|
|
export class Mirror implements IMirror<RRNode> {
|
|
private idNodeMap: Map<number, RRNode> = new Map();
|
|
private nodeMetaMap: WeakMap<RRNode, serializedNodeWithId> = new WeakMap();
|
|
|
|
getId(n: RRNode | undefined | null): number {
|
|
if (!n) return -1;
|
|
|
|
const id = this.getMeta(n)?.id;
|
|
|
|
// if n is not a serialized Node, use -1 as its id.
|
|
return id ?? -1;
|
|
}
|
|
|
|
getNode(id: number): RRNode | null {
|
|
return this.idNodeMap.get(id) || null;
|
|
}
|
|
|
|
getIds(): number[] {
|
|
return Array.from(this.idNodeMap.keys());
|
|
}
|
|
|
|
getMeta(n: RRNode): serializedNodeWithId | null {
|
|
return this.nodeMetaMap.get(n) || null;
|
|
}
|
|
|
|
// removes the node from idNodeMap
|
|
// doesn't remove the node from nodeMetaMap
|
|
removeNodeFromMap(n: RRNode) {
|
|
const id = this.getId(n);
|
|
this.idNodeMap.delete(id);
|
|
|
|
if (n.childNodes) {
|
|
n.childNodes.forEach((childNode) => this.removeNodeFromMap(childNode));
|
|
}
|
|
}
|
|
has(id: number): boolean {
|
|
return this.idNodeMap.has(id);
|
|
}
|
|
|
|
hasNode(node: RRNode): boolean {
|
|
return this.nodeMetaMap.has(node);
|
|
}
|
|
|
|
add(n: RRNode, meta: serializedNodeWithId) {
|
|
const id = meta.id;
|
|
this.idNodeMap.set(id, n);
|
|
this.nodeMetaMap.set(n, meta);
|
|
}
|
|
|
|
replace(id: number, n: RRNode) {
|
|
const oldNode = this.getNode(id);
|
|
if (oldNode) {
|
|
const meta = this.nodeMetaMap.get(oldNode);
|
|
if (meta) this.nodeMetaMap.set(n, meta);
|
|
}
|
|
this.idNodeMap.set(id, n);
|
|
}
|
|
|
|
reset() {
|
|
this.idNodeMap = new Map();
|
|
this.nodeMetaMap = new WeakMap();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get a default serializedNodeWithId value for a RRNode.
|
|
* @param id - the serialized id to assign
|
|
*/
|
|
export function getDefaultSN(node: IRRNode, id: number): serializedNodeWithId {
|
|
switch (node.RRNodeType) {
|
|
case RRNodeType.Document:
|
|
return {
|
|
id,
|
|
type: node.RRNodeType,
|
|
childNodes: [],
|
|
};
|
|
case RRNodeType.DocumentType: {
|
|
const doctype = node as IRRDocumentType;
|
|
return {
|
|
id,
|
|
type: node.RRNodeType,
|
|
name: doctype.name,
|
|
publicId: doctype.publicId,
|
|
systemId: doctype.systemId,
|
|
};
|
|
}
|
|
case RRNodeType.Element:
|
|
return {
|
|
id,
|
|
type: node.RRNodeType,
|
|
tagName: (node as IRRElement).tagName.toLowerCase(), // In rrweb data, all tagNames are lowercase.
|
|
attributes: {},
|
|
childNodes: [],
|
|
};
|
|
case RRNodeType.Text:
|
|
return {
|
|
id,
|
|
type: node.RRNodeType,
|
|
textContent: (node as IRRText).textContent || '',
|
|
};
|
|
case RRNodeType.Comment:
|
|
return {
|
|
id,
|
|
type: node.RRNodeType,
|
|
textContent: (node as IRRComment).textContent || '',
|
|
};
|
|
case RRNodeType.CDATA:
|
|
return {
|
|
id,
|
|
type: node.RRNodeType,
|
|
textContent: '',
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Print the RRDom as a string.
|
|
* @param rootNode - the root node of the RRDom tree
|
|
* @param mirror - a rrweb or rrdom Mirror
|
|
* @returns printed string
|
|
*/
|
|
export function printRRDom(rootNode: IRRNode, mirror: IMirror<IRRNode>) {
|
|
return walk(rootNode, mirror, '');
|
|
}
|
|
function walk(node: IRRNode, mirror: IMirror<IRRNode>, blankSpace: string) {
|
|
let printText = `${blankSpace}${mirror.getId(node)} ${node.toString()}\n`;
|
|
if (node.RRNodeType === RRNodeType.Element) {
|
|
const element = node as IRRElement;
|
|
if (element.shadowRoot)
|
|
printText += walk(element.shadowRoot, mirror, blankSpace + ' ');
|
|
}
|
|
for (const child of node.childNodes)
|
|
printText += walk(child, mirror, blankSpace + ' ');
|
|
if (node.nodeName === 'IFRAME')
|
|
printText += walk(
|
|
(node as RRIFrameElement).contentDocument,
|
|
mirror,
|
|
blankSpace + ' ',
|
|
);
|
|
return printText;
|
|
}
|
|
|
|
export { RRNode };
|
|
|
|
export { diff, createOrGetNode, type ReplayerHandler } from './diff';
|
|
export * from './document';
|