From 9d83a16644460918e72fbc8a758b550ae9d2d0e1 Mon Sep 17 00:00:00 2001 From: Rohit Date: Sun, 6 Jul 2025 09:32:38 +0530 Subject: [PATCH 1/2] feat: revamp rrweb snapshot rendering --- .../recorder/DOMBrowserRenderer.tsx | 207 +++++++----------- 1 file changed, 78 insertions(+), 129 deletions(-) diff --git a/src/components/recorder/DOMBrowserRenderer.tsx b/src/components/recorder/DOMBrowserRenderer.tsx index a7cc2d26..529bbff3 100644 --- a/src/components/recorder/DOMBrowserRenderer.tsx +++ b/src/components/recorder/DOMBrowserRenderer.tsx @@ -296,23 +296,23 @@ export const DOMBrowserRenderer: React.FC = ({ if (element) { setCurrentHighlight({ element, - rect: rect, + rect: rect, selector, elementInfo: { ...elementInfo, tagName: elementInfo?.tagName ?? "", isDOMMode: true, }, - childSelectors, + childSelectors, }); if (onHighlight) { onHighlight({ - rect: rect, + rect: rect, elementInfo: { ...elementInfo, tagName: elementInfo?.tagName ?? "", - isDOMMode: true, + isDOMMode: true, }, selector, childSelectors, @@ -670,8 +670,8 @@ export const DOMBrowserRenderer: React.FC = ({ if (socket) { socket.emit("dom:scroll", { deltaX, - deltaY - }) + deltaY, + }); } notifyLastAction("scroll"); } @@ -749,20 +749,86 @@ export const DOMBrowserRenderer: React.FC = ({ return; } - const iframe = iframeRef.current; + if (isInCaptureMode) { + return; // Skip rendering in capture mode + } try { setRenderError(null); setIsRendered(false); - const tempDoc = - document.implementation.createHTMLDocument("RRWeb Snapshot"); + const iframe = iframeRef.current!; + const iframeDoc = iframe.contentDocument!; + + const styleTags = Array.from( + document.querySelectorAll('link[rel="stylesheet"], style') + ) + .map((tag) => tag.outerHTML) + .join("\n"); + + const enhancedCSS = ` + /* rrweb rebuilt content styles */ + html, body { + margin: 0 !important; + padding: 8px !important; + overflow-x: hidden !important; + } + + html::-webkit-scrollbar, + body::-webkit-scrollbar { + display: none !important; + width: 0 !important; + height: 0 !important; + background: transparent !important; + } + + /* Hide scrollbars for all elements */ + *::-webkit-scrollbar { + display: none !important; + width: 0 !important; + height: 0 !important; + background: transparent !important; + } + + * { + scrollbar-width: none !important; /* Firefox */ + -ms-overflow-style: none !important; /* Internet Explorer 10+ */ + } + + /* Make everything interactive */ + * { + cursor: "pointer" !important; + } + `; + + const skeleton = ` + + + + + + + ${styleTags} + + + + + `; + + if (!iframeDoc) { + throw new Error("Cannot access iframe document"); + } + + // Write the skeleton into the iframe + iframeDoc.open(); + iframeDoc.write(skeleton); + iframeDoc.close(); const mirror = createMirror(); try { rebuild(snapshotData.snapshot, { - doc: tempDoc, + doc: iframeDoc, mirror: mirror, cache: { stylesWithHoverClass: new Map() }, afterAppend: (node) => { @@ -786,125 +852,8 @@ export const DOMBrowserRenderer: React.FC = ({ throw new Error(`rrweb rebuild failed: ${rebuildError}`); } - let rebuiltHTML = tempDoc.documentElement.outerHTML; - - rebuiltHTML = "\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; - font-family: system-ui, -apple-system, BlinkMacSystemFont, sans-serif !important; - background: white !important; - overflow-x: hidden !important; - } - - html::-webkit-scrollbar, - body::-webkit-scrollbar { - display: none !important; - width: 0 !important; - height: 0 !important; - background: transparent !important; - } - - /* Hide scrollbars for all elements */ - *::-webkit-scrollbar { - display: none !important; - width: 0 !important; - height: 0 !important; - background: transparent !important; - } - - * { - 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 = /]*>/i; - const cssInjection = ` - - - - - `; - - if (headTagRegex.test(rebuiltHTML)) { - rebuiltHTML = rebuiltHTML.replace( - headTagRegex, - `${cssInjection}` - ); - } else { - rebuiltHTML = rebuiltHTML.replace( - /]*>/i, - `${cssInjection}` - ); - } - - rebuiltHTML = rebuiltHTML - .replace(/)<[^<]*)*<\/script>/gi, "") - .replace(/\s*on\w+\s*=\s*"[^"]*"/gi, "") - .replace(/\s*on\w+\s*=\s*'[^']*'/gi, "") - .replace(/javascript:/gi, "void:") - .replace(/ { - setIsRendered(true); - setupIframeInteractions(iframeDoc); - }; + setIsRendered(true); + setupIframeInteractions(iframeDoc); } catch (error) { console.error("Error rendering rrweb snapshot:", error); setRenderError(error instanceof Error ? error.message : String(error)); From 2ede49b72a687c5721230f56f7ec135c509e4d00 Mon Sep 17 00:00:00 2001 From: Rohit Date: Sun, 6 Jul 2025 09:33:34 +0530 Subject: [PATCH 2/2] fix: window open syntax --- src/components/integration/IntegrationSettings.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/integration/IntegrationSettings.tsx b/src/components/integration/IntegrationSettings.tsx index 6a7d6ce0..cf550443 100644 --- a/src/components/integration/IntegrationSettings.tsx +++ b/src/components/integration/IntegrationSettings.tsx @@ -734,7 +734,7 @@ export const IntegrationSettingsModal = ({