-
Notifications
You must be signed in to change notification settings - Fork 1.1k
feat: better, faster, smarter capture list #675
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
8bf929f
315cf94
33d9522
369b56f
5f1084c
fec6e81
afe52a1
82152c5
a677cb9
be42c1d
9a065a3
330997f
f21ad1e
bd00405
1517f26
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -98,6 +98,7 @@ interface RRWebDOMBrowserRendererProps { | |
| getList?: boolean; | ||
| getText?: boolean; | ||
| listSelector?: string | null; | ||
| cachedChildSelectors?: string[]; | ||
| paginationMode?: boolean; | ||
| paginationType?: string; | ||
| limitMode?: boolean; | ||
|
|
@@ -106,12 +107,14 @@ interface RRWebDOMBrowserRendererProps { | |
| selector: string; | ||
| elementInfo: ElementInfo | null; | ||
| childSelectors?: string[]; | ||
| groupInfo?: any; | ||
| }) => void; | ||
| onElementSelect?: (data: { | ||
| rect: DOMRect; | ||
| selector: string; | ||
| elementInfo: ElementInfo | null; | ||
| childSelectors?: string[]; | ||
| groupInfo?: any; | ||
| }) => void; | ||
| onShowDatePicker?: (info: { | ||
| coordinates: { x: number; y: number }; | ||
|
|
@@ -144,6 +147,7 @@ export const DOMBrowserRenderer: React.FC<RRWebDOMBrowserRendererProps> = ({ | |
| getList = false, | ||
| getText = false, | ||
| listSelector = null, | ||
| cachedChildSelectors = [], | ||
| paginationMode = false, | ||
| paginationType = "", | ||
| limitMode = false, | ||
|
|
@@ -205,11 +209,24 @@ export const DOMBrowserRenderer: React.FC<RRWebDOMBrowserRendererProps> = ({ | |
| const handleDOMHighlighting = useCallback( | ||
| (x: number, y: number, iframeDoc: Document) => { | ||
| try { | ||
| if (!getText && !getList) { | ||
| setCurrentHighlight(null); | ||
| if (onHighlight) { | ||
| onHighlight({ | ||
| rect: new DOMRect(0, 0, 0, 0), | ||
| selector: "", | ||
| elementInfo: null, | ||
| }); | ||
| } | ||
| return; | ||
| } | ||
|
|
||
| const highlighterData = | ||
| clientSelectorGenerator.generateDataForHighlighter( | ||
| { x, y }, | ||
| iframeDoc, | ||
| true | ||
| true, | ||
| cachedChildSelectors | ||
| ); | ||
|
|
||
| if (!highlighterData) { | ||
|
|
@@ -224,70 +241,40 @@ export const DOMBrowserRenderer: React.FC<RRWebDOMBrowserRendererProps> = ({ | |
| return; | ||
| } | ||
|
|
||
| const { rect, selector, elementInfo, childSelectors } = highlighterData; | ||
| const { rect, selector, elementInfo, childSelectors, groupInfo } = | ||
| highlighterData; | ||
|
|
||
| let shouldHighlight = false; | ||
|
|
||
| if (getList) { | ||
| if (listSelector) { | ||
| const hasValidChildSelectors = | ||
| Array.isArray(childSelectors) && childSelectors.length > 0; | ||
|
|
||
| // First phase: Allow any group to be highlighted for selection | ||
| if (!listSelector && groupInfo?.isGroupElement) { | ||
| shouldHighlight = true; | ||
| } | ||
| // Second phase: Show valid children within selected group | ||
| else if (listSelector) { | ||
| if (limitMode) { | ||
| shouldHighlight = false; | ||
| } else if (paginationMode) { | ||
| if ( | ||
| paginationType !== "" && | ||
| !["none", "scrollDown", "scrollUp"].includes(paginationType) | ||
| ) { | ||
| shouldHighlight = true; | ||
| } else { | ||
| shouldHighlight = false; | ||
| } | ||
| } else if (childSelectors && childSelectors.includes(selector)) { | ||
| } else if ( | ||
| paginationMode && | ||
| paginationType !== "" && | ||
| !["none", "scrollDown", "scrollUp"].includes(paginationType) | ||
| ) { | ||
| shouldHighlight = true; | ||
| } else if (childSelectors && childSelectors.length > 0) { | ||
| console.log("✅ Child selectors present, highlighting enabled"); | ||
| shouldHighlight = true; | ||
| } else if (elementInfo?.isIframeContent && childSelectors) { | ||
| const isIframeChild = childSelectors.some( | ||
| (childSelector: string) => | ||
| selector.includes(":>>") && | ||
| childSelector | ||
| .split(":>>") | ||
| .some((part) => selector.includes(part.trim())) | ||
| ); | ||
| shouldHighlight = isIframeChild; | ||
| } else if (selector.includes(":>>") && hasValidChildSelectors) { | ||
| const selectorParts = selector | ||
| .split(":>>") | ||
| .map((part: string) => part.trim()); | ||
| const isValidMixedSelector = selectorParts.some((part: any) => | ||
| childSelectors!.some((childSelector) => | ||
| childSelector.includes(part) | ||
| ) | ||
| ); | ||
| } else if (elementInfo?.isShadowRoot && childSelectors) { | ||
| const isShadowChild = childSelectors.some( | ||
| (childSelector: string) => | ||
| selector.includes(">>") && | ||
| childSelector | ||
| .split(">>") | ||
| .some((part) => selector.includes(part.trim())) | ||
| ); | ||
| } else if (selector.includes(">>") && hasValidChildSelectors) { | ||
| const selectorParts = selector | ||
| .split(">>") | ||
| .map((part: string) => part.trim()); | ||
| const isValidMixedSelector = selectorParts.some((part: any) => | ||
| childSelectors!.some((childSelector) => | ||
| childSelector.includes(part) | ||
| ) | ||
| ); | ||
| } else { | ||
| console.log("❌ No child selectors available"); | ||
| shouldHighlight = false; | ||
| } | ||
| } else { | ||
| } | ||
| // No list selector - show regular highlighting | ||
| else { | ||
| shouldHighlight = true; | ||
| } | ||
| } else { | ||
| // getText mode - always highlight | ||
| shouldHighlight = true; | ||
| } | ||
|
|
||
|
|
@@ -316,6 +303,7 @@ export const DOMBrowserRenderer: React.FC<RRWebDOMBrowserRendererProps> = ({ | |
| }, | ||
| selector, | ||
| childSelectors, | ||
| groupInfo, | ||
| }); | ||
| } | ||
| } | ||
|
|
@@ -335,9 +323,11 @@ export const DOMBrowserRenderer: React.FC<RRWebDOMBrowserRendererProps> = ({ | |
| } | ||
| }, | ||
| [ | ||
| getText, | ||
| getList, | ||
| listSelector, | ||
| paginationMode, | ||
| cachedChildSelectors, | ||
| paginationType, | ||
| limitMode, | ||
| onHighlight, | ||
|
|
@@ -363,6 +353,10 @@ export const DOMBrowserRenderer: React.FC<RRWebDOMBrowserRendererProps> = ({ | |
| return; | ||
| } | ||
|
|
||
| if (!isInCaptureMode) { | ||
| return; | ||
| } | ||
|
|
||
| const now = performance.now(); | ||
| if (now - lastMouseMoveTime.current < MOUSE_MOVE_THROTTLE) { | ||
| return; | ||
|
|
@@ -401,11 +395,24 @@ export const DOMBrowserRenderer: React.FC<RRWebDOMBrowserRendererProps> = ({ | |
| e.stopPropagation(); | ||
|
|
||
| if (currentHighlight && onElementSelect) { | ||
| // Get the group info for the current highlight | ||
| const highlighterData = | ||
| clientSelectorGenerator.generateDataForHighlighter( | ||
| { x: iframeX, y: iframeY }, | ||
| iframeDoc, | ||
| true, | ||
| cachedChildSelectors | ||
| ); | ||
|
|
||
| onElementSelect({ | ||
| rect: currentHighlight.rect, | ||
| selector: currentHighlight.selector, | ||
| elementInfo: currentHighlight.elementInfo, | ||
| childSelectors: currentHighlight.childSelectors || [], | ||
| childSelectors: | ||
| cachedChildSelectors.length > 0 | ||
| ? cachedChildSelectors | ||
| : highlighterData?.childSelectors || [], | ||
| groupInfo: highlighterData?.groupInfo, | ||
| }); | ||
|
Comment on lines
+398
to
416
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Remove redundant selector generation call. The code retrieves highlighter data again to get groupInfo, but this information should already be available from the currentHighlight state that was set during the last mouse move. Use the existing highlight data: if (currentHighlight && onElementSelect) {
- // Get the group info for the current highlight
- const highlighterData =
- clientSelectorGenerator.generateDataForHighlighter(
- { x: iframeX, y: iframeY },
- iframeDoc,
- true,
- cachedChildSelectors
- );
-
onElementSelect({
rect: currentHighlight.rect,
selector: currentHighlight.selector,
elementInfo: currentHighlight.elementInfo,
childSelectors:
cachedChildSelectors.length > 0
? cachedChildSelectors
- : highlighterData?.childSelectors || [],
- groupInfo: highlighterData?.groupInfo,
+ : currentHighlight.childSelectors || [],
+ groupInfo: currentHighlight.groupInfo,
});
}To support this, update the currentHighlight state interface to include groupInfo. 🤖 Prompt for AI Agents |
||
| } | ||
| notifyLastAction("select element"); | ||
|
|
@@ -790,12 +797,41 @@ export const DOMBrowserRenderer: React.FC<RRWebDOMBrowserRendererProps> = ({ | |
|
|
||
| rebuiltHTML = "<!DOCTYPE html>\n" + rebuiltHTML; | ||
|
|
||
| const additionalCSS = []; | ||
|
|
||
| if (snapshotData.resources.fonts?.length > 0) { | ||
| const fontCSS = snapshotData.resources.fonts | ||
| .map((font) => { | ||
| const format = font.format || "woff2"; | ||
| return ` | ||
| @font-face { | ||
| font-family: 'ProxiedFont-${ | ||
| font.url.split("/").pop()?.split(".")[0] || "unknown" | ||
| }'; | ||
| src: url("${font.dataUrl}") format("${format}"); | ||
| font-display: swap; | ||
| } | ||
| `; | ||
| }) | ||
| .join("\n"); | ||
| additionalCSS.push(fontCSS); | ||
| } | ||
|
|
||
| if (snapshotData.resources.stylesheets?.length > 0) { | ||
| const externalCSS = snapshotData.resources.stylesheets | ||
| .map((stylesheet) => stylesheet.content) | ||
| .join("\n\n"); | ||
| additionalCSS.push(externalCSS); | ||
| } | ||
|
|
||
| const enhancedCSS = ` | ||
| /* rrweb rebuilt content styles */ | ||
| html, body { | ||
| margin: 0 !important; | ||
| padding: 8px !important; | ||
| overflow-x: hidden !important; | ||
| margin: 0 !important; | ||
| padding: 8px !important; | ||
| font-family: system-ui, -apple-system, BlinkMacSystemFont, sans-serif !important; | ||
| background: white !important; | ||
| overflow-x: hidden !important; | ||
| } | ||
|
|
||
| html::-webkit-scrollbar, | ||
|
|
@@ -818,12 +854,22 @@ export const DOMBrowserRenderer: React.FC<RRWebDOMBrowserRendererProps> = ({ | |
| scrollbar-width: none !important; /* Firefox */ | ||
| -ms-overflow-style: none !important; /* Internet Explorer 10+ */ | ||
| } | ||
|
|
||
| img { | ||
| max-width: 100% !important; | ||
| height: auto !important; | ||
| } | ||
|
|
||
|
|
||
| /* Make everything interactive */ | ||
| * { | ||
| cursor: "pointer" !important; | ||
| } | ||
| `; | ||
|
|
||
| /* Additional CSS from resources */ | ||
| ${additionalCSS.join("\n\n")} | ||
| `; | ||
|
|
||
|
|
||
| const headTagRegex = /<head[^>]*>/i; | ||
| const cssInjection = ` | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Improve type safety by defining proper types for groupInfo.
The
groupInfoparameter usesanytype, which reduces type safety and makes the code harder to maintain.Define a proper interface for groupInfo:
Also applies to: 117-117
🤖 Prompt for AI Agents