Merge pull request #822 from getmaxun/clean-data
fix: extract clean data
This commit is contained in:
@@ -908,6 +908,7 @@ export const DOMBrowserRenderer: React.FC<RRWebDOMBrowserRendererProps> = ({
|
|||||||
rebuild(snapshotData.snapshot, {
|
rebuild(snapshotData.snapshot, {
|
||||||
doc: iframeDoc,
|
doc: iframeDoc,
|
||||||
mirror: mirror,
|
mirror: mirror,
|
||||||
|
hackCss: false,
|
||||||
cache: { stylesWithHoverClass: new Map() },
|
cache: { stylesWithHoverClass: new Map() },
|
||||||
afterAppend: (node) => {
|
afterAppend: (node) => {
|
||||||
if (node.nodeType === Node.TEXT_NODE && node.textContent) {
|
if (node.nodeType === Node.TEXT_NODE && node.textContent) {
|
||||||
|
|||||||
@@ -556,37 +556,24 @@ class ClientSelectorGenerator {
|
|||||||
private isMeaningfulElement(element: HTMLElement): boolean {
|
private isMeaningfulElement(element: HTMLElement): boolean {
|
||||||
const tagName = element.tagName.toLowerCase();
|
const tagName = element.tagName.toLowerCase();
|
||||||
|
|
||||||
// Fast path for common meaningful elements
|
if (tagName === "img") {
|
||||||
if (["a", "img", "input", "button", "select"].includes(tagName)) {
|
return element.hasAttribute("src");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tagName === "a" && element.hasAttribute("href")) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (element.children.length > 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
const text = (element.textContent || "").trim();
|
const text = (element.textContent || "").trim();
|
||||||
const hasHref = element.hasAttribute("href");
|
|
||||||
const hasSrc = element.hasAttribute("src");
|
|
||||||
|
|
||||||
// Quick checks first
|
if (text.length > 0) {
|
||||||
if (text.length > 0 || hasHref || hasSrc) {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isCustomElement = tagName.includes("-");
|
|
||||||
|
|
||||||
// For custom elements, be more lenient about what's considered meaningful
|
|
||||||
if (isCustomElement) {
|
|
||||||
const hasChildren = element.children.length > 0;
|
|
||||||
const hasSignificantAttributes = Array.from(element.attributes).some(
|
|
||||||
(attr) => !["class", "style", "id"].includes(attr.name.toLowerCase())
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
hasChildren ||
|
|
||||||
hasSignificantAttributes ||
|
|
||||||
element.hasAttribute("role") ||
|
|
||||||
element.hasAttribute("aria-label")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2561,12 +2548,9 @@ class ClientSelectorGenerator {
|
|||||||
|
|
||||||
const MAX_MEANINGFUL_ELEMENTS = 300;
|
const MAX_MEANINGFUL_ELEMENTS = 300;
|
||||||
const MAX_NODES_TO_CHECK = 1200;
|
const MAX_NODES_TO_CHECK = 1200;
|
||||||
const MAX_DEPTH = 12;
|
const MAX_DEPTH = 20;
|
||||||
let nodesChecked = 0;
|
let nodesChecked = 0;
|
||||||
|
|
||||||
let adjustedMaxDepth = MAX_DEPTH;
|
|
||||||
const elementDensityThreshold = 50;
|
|
||||||
|
|
||||||
const depths: number[] = [0];
|
const depths: number[] = [0];
|
||||||
let queueIndex = 0;
|
let queueIndex = 0;
|
||||||
|
|
||||||
@@ -2576,14 +2560,10 @@ class ClientSelectorGenerator {
|
|||||||
queueIndex++;
|
queueIndex++;
|
||||||
nodesChecked++;
|
nodesChecked++;
|
||||||
|
|
||||||
if (currentDepth <= 3 && meaningfulDescendants.length > elementDensityThreshold) {
|
|
||||||
adjustedMaxDepth = Math.max(6, adjustedMaxDepth - 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
nodesChecked > MAX_NODES_TO_CHECK ||
|
nodesChecked > MAX_NODES_TO_CHECK ||
|
||||||
meaningfulDescendants.length >= MAX_MEANINGFUL_ELEMENTS ||
|
meaningfulDescendants.length >= MAX_MEANINGFUL_ELEMENTS ||
|
||||||
currentDepth > adjustedMaxDepth
|
currentDepth > MAX_DEPTH
|
||||||
) {
|
) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -2592,7 +2572,7 @@ class ClientSelectorGenerator {
|
|||||||
meaningfulDescendants.push(element);
|
meaningfulDescendants.push(element);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentDepth >= adjustedMaxDepth) {
|
if (currentDepth >= MAX_DEPTH) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2607,7 +2587,7 @@ class ClientSelectorGenerator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (element.shadowRoot && currentDepth < adjustedMaxDepth - 1) {
|
if (element.shadowRoot && currentDepth < MAX_DEPTH - 1) {
|
||||||
const shadowChildren = element.shadowRoot.children;
|
const shadowChildren = element.shadowRoot.children;
|
||||||
const shadowLimit = Math.min(shadowChildren.length, 20);
|
const shadowLimit = Math.min(shadowChildren.length, 20);
|
||||||
for (let i = 0; i < shadowLimit; i++) {
|
for (let i = 0; i < shadowLimit; i++) {
|
||||||
@@ -2716,22 +2696,46 @@ class ClientSelectorGenerator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!addPositionToAll) {
|
if (!addPositionToAll) {
|
||||||
const meaningfulAttrs = ["role", "type", "name", "src", "aria-label"];
|
const meaningfulAttrs = ["role", "type"];
|
||||||
for (const attrName of meaningfulAttrs) {
|
for (const attrName of meaningfulAttrs) {
|
||||||
if (element.hasAttribute(attrName)) {
|
if (element.hasAttribute(attrName)) {
|
||||||
const value = element.getAttribute(attrName)!.replace(/'/g, "\\'");
|
const value = element.getAttribute(attrName)!.replace(/'/g, "\\'");
|
||||||
return `${tagName}[@${attrName}='${value}']`;
|
const isCommonAttribute = this.isAttributeCommonAcrossLists(
|
||||||
|
element,
|
||||||
|
attrName,
|
||||||
|
value,
|
||||||
|
otherListElements
|
||||||
|
);
|
||||||
|
if (isCommonAttribute) {
|
||||||
|
return `${tagName}[@${attrName}='${value}']`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const testId = element.getAttribute("data-testid");
|
const testId = element.getAttribute("data-testid");
|
||||||
if (testId && !addPositionToAll) {
|
if (testId && !addPositionToAll) {
|
||||||
return `${tagName}[@data-testid='${testId}']`;
|
const isCommon = this.isAttributeCommonAcrossLists(
|
||||||
|
element,
|
||||||
|
"data-testid",
|
||||||
|
testId,
|
||||||
|
otherListElements
|
||||||
|
);
|
||||||
|
if (isCommon) {
|
||||||
|
return `${tagName}[@data-testid='${testId}']`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (element.id && !element.id.match(/^\d/) && !addPositionToAll) {
|
if (element.id && !element.id.match(/^\d/) && !addPositionToAll) {
|
||||||
return `${tagName}[@id='${element.id}']`;
|
const isCommon = this.isAttributeCommonAcrossLists(
|
||||||
|
element,
|
||||||
|
"id",
|
||||||
|
element.id,
|
||||||
|
otherListElements
|
||||||
|
);
|
||||||
|
if (isCommon) {
|
||||||
|
return `${tagName}[@id='${element.id}']`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!addPositionToAll) {
|
if (!addPositionToAll) {
|
||||||
@@ -2742,7 +2746,15 @@ class ClientSelectorGenerator {
|
|||||||
attr.name !== "data-mx-id" &&
|
attr.name !== "data-mx-id" &&
|
||||||
attr.value
|
attr.value
|
||||||
) {
|
) {
|
||||||
return `${tagName}[@${attr.name}='${attr.value}']`;
|
const isCommon = this.isAttributeCommonAcrossLists(
|
||||||
|
element,
|
||||||
|
attr.name,
|
||||||
|
attr.value,
|
||||||
|
otherListElements
|
||||||
|
);
|
||||||
|
if (isCommon) {
|
||||||
|
return `${tagName}[@${attr.name}='${attr.value}']`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2910,6 +2922,64 @@ class ClientSelectorGenerator {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private isAttributeCommonAcrossLists(
|
||||||
|
targetElement: HTMLElement,
|
||||||
|
attrName: string,
|
||||||
|
attrValue: string,
|
||||||
|
otherListElements: HTMLElement[]
|
||||||
|
): boolean {
|
||||||
|
if (otherListElements.length === 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const targetPath = this.getElementPath(targetElement);
|
||||||
|
|
||||||
|
for (const otherListElement of otherListElements) {
|
||||||
|
const correspondingElement = this.findCorrespondingElement(
|
||||||
|
otherListElement,
|
||||||
|
targetPath
|
||||||
|
);
|
||||||
|
if (correspondingElement) {
|
||||||
|
const otherValue = correspondingElement.getAttribute(attrName);
|
||||||
|
if (otherValue !== attrValue) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getElementPath(element: HTMLElement): number[] {
|
||||||
|
const path: number[] = [];
|
||||||
|
let current: HTMLElement | null = element;
|
||||||
|
|
||||||
|
while (current && current.parentElement) {
|
||||||
|
const siblings = Array.from(current.parentElement.children);
|
||||||
|
path.unshift(siblings.indexOf(current));
|
||||||
|
current = current.parentElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
private findCorrespondingElement(
|
||||||
|
rootElement: HTMLElement,
|
||||||
|
path: number[]
|
||||||
|
): HTMLElement | null {
|
||||||
|
let current: HTMLElement = rootElement;
|
||||||
|
|
||||||
|
for (const index of path) {
|
||||||
|
const children = Array.from(current.children);
|
||||||
|
if (index >= children.length) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
current = children[index] as HTMLElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
|
||||||
private getCommonClassesAcrossLists(
|
private getCommonClassesAcrossLists(
|
||||||
targetElement: HTMLElement,
|
targetElement: HTMLElement,
|
||||||
otherListElements: HTMLElement[]
|
otherListElements: HTMLElement[]
|
||||||
@@ -3919,9 +3989,48 @@ class ClientSelectorGenerator {
|
|||||||
);
|
);
|
||||||
if (!deepestElement) return null;
|
if (!deepestElement) return null;
|
||||||
|
|
||||||
|
if (!this.isMeaningfulElementCached(deepestElement)) {
|
||||||
|
const atomicChild = this.findAtomicChildAtPoint(deepestElement, x, y);
|
||||||
|
if (atomicChild) {
|
||||||
|
return atomicChild;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return deepestElement;
|
return deepestElement;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private findAtomicChildAtPoint(
|
||||||
|
parent: HTMLElement,
|
||||||
|
x: number,
|
||||||
|
y: number
|
||||||
|
): HTMLElement | null {
|
||||||
|
const stack: HTMLElement[] = [parent];
|
||||||
|
const visited = new Set<HTMLElement>();
|
||||||
|
|
||||||
|
while (stack.length > 0) {
|
||||||
|
const element = stack.pop()!;
|
||||||
|
if (visited.has(element)) continue;
|
||||||
|
visited.add(element);
|
||||||
|
|
||||||
|
if (element !== parent && this.isMeaningfulElementCached(element)) {
|
||||||
|
const rect = element.getBoundingClientRect();
|
||||||
|
if (x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom) {
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = element.children.length - 1; i >= 0; i--) {
|
||||||
|
const child = element.children[i] as HTMLElement;
|
||||||
|
const rect = child.getBoundingClientRect();
|
||||||
|
if (x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom) {
|
||||||
|
stack.push(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper methods used by the unified getDeepestElementFromPoint
|
* Helper methods used by the unified getDeepestElementFromPoint
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user