Skip to content

Commit 67cd03c

Browse files
authored
Merge pull request #822 from getmaxun/clean-data
fix: extract clean data
2 parents 3b16098 + ce2e28a commit 67cd03c

File tree

2 files changed

+151
-41
lines changed

2 files changed

+151
-41
lines changed

src/components/recorder/DOMBrowserRenderer.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -908,6 +908,7 @@ export const DOMBrowserRenderer: React.FC<RRWebDOMBrowserRendererProps> = ({
908908
rebuild(snapshotData.snapshot, {
909909
doc: iframeDoc,
910910
mirror: mirror,
911+
hackCss: false,
911912
cache: { stylesWithHoverClass: new Map() },
912913
afterAppend: (node) => {
913914
if (node.nodeType === Node.TEXT_NODE && node.textContent) {

src/helpers/clientSelectorGenerator.ts

Lines changed: 150 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -555,36 +555,23 @@ class ClientSelectorGenerator {
555555
*/
556556
private isMeaningfulElement(element: HTMLElement): boolean {
557557
const tagName = element.tagName.toLowerCase();
558-
559-
// Fast path for common meaningful elements
560-
if (["a", "img", "input", "button", "select"].includes(tagName)) {
561-
return true;
558+
559+
if (tagName === "img") {
560+
return element.hasAttribute("src");
562561
}
563562

564-
const text = (element.textContent || "").trim();
565-
const hasHref = element.hasAttribute("href");
566-
const hasSrc = element.hasAttribute("src");
567-
568-
// Quick checks first
569-
if (text.length > 0 || hasHref || hasSrc) {
563+
if (tagName === "a" && element.hasAttribute("href")) {
570564
return true;
571565
}
572566

573-
const isCustomElement = tagName.includes("-");
567+
if (element.children.length > 0) {
568+
return false;
569+
}
574570

575-
// For custom elements, be more lenient about what's considered meaningful
576-
if (isCustomElement) {
577-
const hasChildren = element.children.length > 0;
578-
const hasSignificantAttributes = Array.from(element.attributes).some(
579-
(attr) => !["class", "style", "id"].includes(attr.name.toLowerCase())
580-
);
571+
const text = (element.textContent || "").trim();
581572

582-
return (
583-
hasChildren ||
584-
hasSignificantAttributes ||
585-
element.hasAttribute("role") ||
586-
element.hasAttribute("aria-label")
587-
);
573+
if (text.length > 0) {
574+
return true;
588575
}
589576

590577
return false;
@@ -2561,12 +2548,9 @@ class ClientSelectorGenerator {
25612548

25622549
const MAX_MEANINGFUL_ELEMENTS = 300;
25632550
const MAX_NODES_TO_CHECK = 1200;
2564-
const MAX_DEPTH = 12;
2551+
const MAX_DEPTH = 20;
25652552
let nodesChecked = 0;
25662553

2567-
let adjustedMaxDepth = MAX_DEPTH;
2568-
const elementDensityThreshold = 50;
2569-
25702554
const depths: number[] = [0];
25712555
let queueIndex = 0;
25722556

@@ -2576,14 +2560,10 @@ class ClientSelectorGenerator {
25762560
queueIndex++;
25772561
nodesChecked++;
25782562

2579-
if (currentDepth <= 3 && meaningfulDescendants.length > elementDensityThreshold) {
2580-
adjustedMaxDepth = Math.max(6, adjustedMaxDepth - 2);
2581-
}
2582-
25832563
if (
25842564
nodesChecked > MAX_NODES_TO_CHECK ||
25852565
meaningfulDescendants.length >= MAX_MEANINGFUL_ELEMENTS ||
2586-
currentDepth > adjustedMaxDepth
2566+
currentDepth > MAX_DEPTH
25872567
) {
25882568
break;
25892569
}
@@ -2592,7 +2572,7 @@ class ClientSelectorGenerator {
25922572
meaningfulDescendants.push(element);
25932573
}
25942574

2595-
if (currentDepth >= adjustedMaxDepth) {
2575+
if (currentDepth >= MAX_DEPTH) {
25962576
continue;
25972577
}
25982578

@@ -2607,7 +2587,7 @@ class ClientSelectorGenerator {
26072587
}
26082588
}
26092589

2610-
if (element.shadowRoot && currentDepth < adjustedMaxDepth - 1) {
2590+
if (element.shadowRoot && currentDepth < MAX_DEPTH - 1) {
26112591
const shadowChildren = element.shadowRoot.children;
26122592
const shadowLimit = Math.min(shadowChildren.length, 20);
26132593
for (let i = 0; i < shadowLimit; i++) {
@@ -2716,22 +2696,46 @@ class ClientSelectorGenerator {
27162696
}
27172697

27182698
if (!addPositionToAll) {
2719-
const meaningfulAttrs = ["role", "type", "name", "src", "aria-label"];
2699+
const meaningfulAttrs = ["role", "type"];
27202700
for (const attrName of meaningfulAttrs) {
27212701
if (element.hasAttribute(attrName)) {
27222702
const value = element.getAttribute(attrName)!.replace(/'/g, "\\'");
2723-
return `${tagName}[@${attrName}='${value}']`;
2703+
const isCommonAttribute = this.isAttributeCommonAcrossLists(
2704+
element,
2705+
attrName,
2706+
value,
2707+
otherListElements
2708+
);
2709+
if (isCommonAttribute) {
2710+
return `${tagName}[@${attrName}='${value}']`;
2711+
}
27242712
}
27252713
}
27262714
}
27272715

27282716
const testId = element.getAttribute("data-testid");
27292717
if (testId && !addPositionToAll) {
2730-
return `${tagName}[@data-testid='${testId}']`;
2718+
const isCommon = this.isAttributeCommonAcrossLists(
2719+
element,
2720+
"data-testid",
2721+
testId,
2722+
otherListElements
2723+
);
2724+
if (isCommon) {
2725+
return `${tagName}[@data-testid='${testId}']`;
2726+
}
27312727
}
27322728

27332729
if (element.id && !element.id.match(/^\d/) && !addPositionToAll) {
2734-
return `${tagName}[@id='${element.id}']`;
2730+
const isCommon = this.isAttributeCommonAcrossLists(
2731+
element,
2732+
"id",
2733+
element.id,
2734+
otherListElements
2735+
);
2736+
if (isCommon) {
2737+
return `${tagName}[@id='${element.id}']`;
2738+
}
27352739
}
27362740

27372741
if (!addPositionToAll) {
@@ -2742,7 +2746,15 @@ class ClientSelectorGenerator {
27422746
attr.name !== "data-mx-id" &&
27432747
attr.value
27442748
) {
2745-
return `${tagName}[@${attr.name}='${attr.value}']`;
2749+
const isCommon = this.isAttributeCommonAcrossLists(
2750+
element,
2751+
attr.name,
2752+
attr.value,
2753+
otherListElements
2754+
);
2755+
if (isCommon) {
2756+
return `${tagName}[@${attr.name}='${attr.value}']`;
2757+
}
27462758
}
27472759
}
27482760
}
@@ -2906,12 +2918,70 @@ class ClientSelectorGenerator {
29062918
const result = pathParts.length > 0 ? "/" + pathParts.join("/") : null;
29072919

29082920
this.pathCache.set(targetElement, result);
2909-
2921+
29102922
return result;
29112923
}
29122924

2925+
private isAttributeCommonAcrossLists(
2926+
targetElement: HTMLElement,
2927+
attrName: string,
2928+
attrValue: string,
2929+
otherListElements: HTMLElement[]
2930+
): boolean {
2931+
if (otherListElements.length === 0) {
2932+
return true;
2933+
}
2934+
2935+
const targetPath = this.getElementPath(targetElement);
2936+
2937+
for (const otherListElement of otherListElements) {
2938+
const correspondingElement = this.findCorrespondingElement(
2939+
otherListElement,
2940+
targetPath
2941+
);
2942+
if (correspondingElement) {
2943+
const otherValue = correspondingElement.getAttribute(attrName);
2944+
if (otherValue !== attrValue) {
2945+
return false;
2946+
}
2947+
}
2948+
}
2949+
2950+
return true;
2951+
}
2952+
2953+
private getElementPath(element: HTMLElement): number[] {
2954+
const path: number[] = [];
2955+
let current: HTMLElement | null = element;
2956+
2957+
while (current && current.parentElement) {
2958+
const siblings = Array.from(current.parentElement.children);
2959+
path.unshift(siblings.indexOf(current));
2960+
current = current.parentElement;
2961+
}
2962+
2963+
return path;
2964+
}
2965+
2966+
private findCorrespondingElement(
2967+
rootElement: HTMLElement,
2968+
path: number[]
2969+
): HTMLElement | null {
2970+
let current: HTMLElement = rootElement;
2971+
2972+
for (const index of path) {
2973+
const children = Array.from(current.children);
2974+
if (index >= children.length) {
2975+
return null;
2976+
}
2977+
current = children[index] as HTMLElement;
2978+
}
2979+
2980+
return current;
2981+
}
2982+
29132983
private getCommonClassesAcrossLists(
2914-
targetElement: HTMLElement,
2984+
targetElement: HTMLElement,
29152985
otherListElements: HTMLElement[]
29162986
): string[] {
29172987
if (otherListElements.length === 0) {
@@ -3919,9 +3989,48 @@ class ClientSelectorGenerator {
39193989
);
39203990
if (!deepestElement) return null;
39213991

3992+
if (!this.isMeaningfulElementCached(deepestElement)) {
3993+
const atomicChild = this.findAtomicChildAtPoint(deepestElement, x, y);
3994+
if (atomicChild) {
3995+
return atomicChild;
3996+
}
3997+
}
3998+
39223999
return deepestElement;
39234000
}
39244001

4002+
private findAtomicChildAtPoint(
4003+
parent: HTMLElement,
4004+
x: number,
4005+
y: number
4006+
): HTMLElement | null {
4007+
const stack: HTMLElement[] = [parent];
4008+
const visited = new Set<HTMLElement>();
4009+
4010+
while (stack.length > 0) {
4011+
const element = stack.pop()!;
4012+
if (visited.has(element)) continue;
4013+
visited.add(element);
4014+
4015+
if (element !== parent && this.isMeaningfulElementCached(element)) {
4016+
const rect = element.getBoundingClientRect();
4017+
if (x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom) {
4018+
return element;
4019+
}
4020+
}
4021+
4022+
for (let i = element.children.length - 1; i >= 0; i--) {
4023+
const child = element.children[i] as HTMLElement;
4024+
const rect = child.getBoundingClientRect();
4025+
if (x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom) {
4026+
stack.push(child);
4027+
}
4028+
}
4029+
}
4030+
4031+
return null;
4032+
}
4033+
39254034
/**
39264035
* Helper methods used by the unified getDeepestElementFromPoint
39274036
*/

0 commit comments

Comments
 (0)