Skip to content

Commit

Permalink
Reverse monkey patch built in methods to support LWC #1509
Browse files Browse the repository at this point in the history
  • Loading branch information
Vadman97 committed Jun 25, 2024
1 parent 6cf9dd1 commit 00cdbce
Show file tree
Hide file tree
Showing 7 changed files with 331 additions and 45 deletions.
1 change: 1 addition & 0 deletions packages/rrweb-snapshot/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
},
"homepage": "https://github.com/rrweb-io/rrweb/tree/master/packages/rrweb-snapshot#readme",
"devDependencies": {
"@rrweb/utils": "workspace:*",
"@types/jsdom": "^20.0.0",
"@types/node": "^18.15.11",
"@types/puppeteer": "^5.4.4",
Expand Down
71 changes: 38 additions & 33 deletions packages/rrweb-snapshot/src/snapshot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,13 @@ import {
extractFileExtension,
isElementSrcBlocked,
} from './utils';
import {
childNodes,
parentNode,
parentElement,
textContent,
shadowRoot,
} from '@rrweb/utils';

let _id = 1;
const tagNameRegex = new RegExp('[^a-z0-9-_:]');
Expand Down Expand Up @@ -311,7 +318,7 @@ export function classMatchesRegex(
if (!node) return false;
if (node.nodeType !== node.ELEMENT_NODE) {
if (!checkAncestors) return false;
return classMatchesRegex(node.parentNode, regex, checkAncestors);
return classMatchesRegex(parentNode(node), regex, checkAncestors);
}

for (let eIndex = (node as HTMLElement).classList.length; eIndex--; ) {
Expand All @@ -321,7 +328,7 @@ export function classMatchesRegex(
}
}
if (!checkAncestors) return false;
return classMatchesRegex(node.parentNode, regex, checkAncestors);
return classMatchesRegex(parentNode(node), regex, checkAncestors);
}

export function needMaskingText(
Expand All @@ -333,16 +340,16 @@ export function needMaskingText(
let el: Element;
if (isElement(node)) {
el = node;
if (!el.childNodes.length) {
if (!childNodes(el).length) {
// optimisation: we can avoid any of the below checks on leaf elements
// as masking is applied to child text nodes only
return false;
}
} else if (node.parentElement === null) {
} else if (parentElement(node) === null) {
// should warn? maybe a text node isn't attached to a parent node yet?
return false;
} else {
el = node.parentElement;
el = parentElement(node)!;
}
try {
if (typeof maskTextClass === 'string') {
Expand Down Expand Up @@ -548,7 +555,7 @@ function serializeNode(
case n.COMMENT_NODE:
return {
type: NodeType.Comment,
textContent: (n as Comment).textContent || '',
textContent: textContent(n as Comment) || '',
rootId,
};
default:
Expand Down Expand Up @@ -580,45 +587,44 @@ function serializeTextNode(
} = options;
// The parent node may not be a html element which has a tagName attribute.
// So just let it be undefined which is ok in this use case.
const parentTagName = n.parentNode && (n.parentNode as HTMLElement).tagName;
let textContent = n.textContent;
const parent = parentNode(n);
const parentTagName = parent && (parent as HTMLElement).tagName;
let text = textContent(n);
const isStyle = parentTagName === 'STYLE' ? true : undefined;
const isScript = parentTagName === 'SCRIPT' ? true : undefined;
/** Determines if this node has been handled already. */
let textContentHandled = false;
if (isStyle && textContent) {
if (isStyle && text) {
try {
// try to read style sheet
if (n.nextSibling || n.previousSibling) {
// This is not the only child of the stylesheet.
// We can't read all of the sheet's .cssRules and expect them
// to _only_ include the current rule(s) added by the text node.
// So we'll be conservative and keep textContent as-is.
} else if ((n.parentNode as HTMLStyleElement).sheet?.cssRules) {
textContent = stringifyStylesheet(
(n.parentNode as HTMLStyleElement).sheet!,
);
} else if ((parent as HTMLStyleElement).sheet?.cssRules) {
text = stringifyStylesheet((parent as HTMLStyleElement).sheet!);
}
} catch (err) {
console.warn(
`Cannot get CSS styles from text's parentNode. Error: ${err as string}`,
n,
);
}
textContent = absoluteToStylesheet(textContent, getHref(options.doc));
text = absoluteToStylesheet(text, getHref(options.doc));
textContentHandled = true;
}
if (isScript) {
textContent = 'SCRIPT_PLACEHOLDER';
text = 'SCRIPT_PLACEHOLDER';
textContentHandled = true;
} else if (parentTagName === 'NOSCRIPT') {
textContent = '';
text = '';
textContentHandled = true;
}
if (!isStyle && !isScript && textContent && needsMask) {
textContent = maskTextFn
? maskTextFn(textContent, n.parentElement)
: textContent.replace(/[\S]/g, '*');
if (!isStyle && !isScript && text && needsMask) {
text = maskTextFn
? maskTextFn(text, parentElement(n))
: text.replace(/[\S]/g, '*');
}

/* Start of Highlight */
Expand Down Expand Up @@ -651,7 +657,7 @@ function serializeTextNode(

return {
type: NodeType.Text,
textContent: textContent || '',
textContent: text || '',
isStyle,
rootId,
};
Expand Down Expand Up @@ -714,6 +720,7 @@ function serializeElementNode(
}
// remote css
if (tagName === 'link' && inlineStylesheet) {
//TODO: maybe replace this `.styleSheets` with original one
const stylesheet = Array.from(doc.styleSheets).find((s) => {
return s.href === (n as HTMLLinkElement).href;
});
Expand All @@ -732,7 +739,7 @@ function serializeElementNode(
tagName === 'style' &&
(n as HTMLStyleElement).sheet &&
// TODO: Currently we only try to get dynamic stylesheet when it is an empty style element
!(n.innerText || n.textContent || '').trim().length
!(n.innerText || textContent(n) || '').trim().length
) {
const cssText = stringifyStylesheet(
(n as HTMLStyleElement).sheet as CSSStyleSheet,
Expand Down Expand Up @@ -1191,8 +1198,8 @@ export function serializeNodeWithId(
// these properties was not needed in replay side
delete serializedNode.needBlock;
delete serializedNode.needMask;
const shadowRoot = (n as HTMLElement).shadowRoot;
if (shadowRoot && isNativeShadowDom(shadowRoot))
const shadowRootEl = shadowRoot(n);
if (shadowRootEl && isNativeShadowDom(shadowRootEl))
serializedNode.isShadowHost = true;
}
if (
Expand Down Expand Up @@ -1242,31 +1249,29 @@ export function serializeNodeWithId(
) {
// value parameter in DOM reflects the correct value, so ignore childNode
} else {
for (const childN of Array.from(n.childNodes)) {
for (const childN of Array.from(childNodes(n))) {
const serializedChildNode = serializeNodeWithId(childN, bypassOptions);
if (serializedChildNode) {
serializedNode.childNodes.push(serializedChildNode);
}
}
}

if (isElement(n) && n.shadowRoot) {
for (const childN of Array.from(n.shadowRoot.childNodes)) {
let shadowRootEl: ShadowRoot | null = null;
if (isElement(n) && (shadowRootEl = shadowRoot(n))) {
for (const childN of Array.from(childNodes(shadowRootEl))) {
const serializedChildNode = serializeNodeWithId(childN, bypassOptions);
if (serializedChildNode) {
isNativeShadowDom(n.shadowRoot) &&
isNativeShadowDom(shadowRootEl) &&
(serializedChildNode.isShadow = true);
serializedNode.childNodes.push(serializedChildNode);
}
}
}
}

if (
n.parentNode &&
isShadowRoot(n.parentNode) &&
isNativeShadowDom(n.parentNode)
) {
const parent = parentNode(n);
if (parent && isShadowRoot(parent) && isNativeShadowDom(parent)) {
serializedNode.isShadow = true;
}

Expand Down
8 changes: 6 additions & 2 deletions packages/rrweb-snapshot/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,19 @@ import type {
textNode,
elementNode,
} from './types';
import { shadowRoot, host } from '@rrweb/utils';
import { NodeType } from './types';

export function isElement(n: Node): n is Element {
return n.nodeType === n.ELEMENT_NODE;
}

export function isShadowRoot(n: Node): n is ShadowRoot {
const host: Element | null = (n as ShadowRoot)?.host;
return Boolean(host?.shadowRoot === n);
const hostEl: Element | null =
// anchor and textarea elements also have a `host` property
// but only shadow roots have a `mode` property
(n && 'host' in n && 'mode' in n && host(n as ShadowRoot)) || null;
return Boolean(hostEl && 'shadowRoot' in hostEl && shadowRoot(hostEl) === n);
}

/**
Expand Down
Loading

0 comments on commit 00cdbce

Please sign in to comment.