diff --git a/.changeset/rotten-squids-run.md b/.changeset/rotten-squids-run.md new file mode 100644 index 0000000000..98b4cfa0ae --- /dev/null +++ b/.changeset/rotten-squids-run.md @@ -0,0 +1,5 @@ +--- +'rrweb': patch +--- + +rrweb sends message to cross-origin iframe to force snapshot diff --git a/packages/rrweb/src/record/iframe-manager.ts b/packages/rrweb/src/record/iframe-manager.ts index 2d9205c0cb..8ad46940f9 100644 --- a/packages/rrweb/src/record/iframe-manager.ts +++ b/packages/rrweb/src/record/iframe-manager.ts @@ -21,6 +21,7 @@ export class IframeManager { private mirror: Mirror; private mutationCb: mutationCallBack; private wrappedEmit: (e: eventWithoutTime, isCheckout?: boolean) => void; + private takeFullSnapshot: (isCheckout?: boolean) => void; private loadListener?: (iframeEl: HTMLIFrameElement) => unknown; private stylesheetManager: StylesheetManager; private recordCrossOriginIframes: boolean; @@ -31,9 +32,11 @@ export class IframeManager { stylesheetManager: StylesheetManager; recordCrossOriginIframes: boolean; wrappedEmit: (e: eventWithoutTime, isCheckout?: boolean) => void; + takeFullSnapshot: (isCheckout?: boolean) => void; }) { this.mutationCb = options.mutationCb; this.wrappedEmit = options.wrappedEmit; + this.takeFullSnapshot = options.takeFullSnapshot; this.stylesheetManager = options.stylesheetManager; this.recordCrossOriginIframes = options.recordCrossOriginIframes; this.crossOriginIframeStyleMirror = new CrossOriginIframeMirror( @@ -47,10 +50,24 @@ export class IframeManager { } } + public setTakeFullSnapshot(takeFullSnapshot: (isCheckout?: boolean) => void) { + this.takeFullSnapshot = takeFullSnapshot; + } + public addIframe(iframeEl: HTMLIFrameElement) { this.iframes.set(iframeEl, true); if (iframeEl.contentWindow) this.crossOriginIframeMap.set(iframeEl.contentWindow, iframeEl); + + if (!iframeEl.contentDocument && iframeEl.contentWindow) + iframeEl.contentWindow.postMessage( + { + type: 'rrweb', + origin: window.location.origin, + snapshot: true, + }, + '*', + ); } public addLoadListener(cb: (iframeEl: HTMLIFrameElement) => unknown) { @@ -95,10 +112,21 @@ export class IframeManager { ) return; - const iframeSourceWindow = message.source; + const iframeSourceWindow = crossOriginMessageEvent.source; if (!iframeSourceWindow) return; - const iframeEl = this.crossOriginIframeMap.get(message.source); + if ( + iframeSourceWindow == window.parent && + window != window.parent && + crossOriginMessageEvent.data.snapshot + ) { + this.takeFullSnapshot(); + return; + } + + const iframeEl = this.crossOriginIframeMap.get( + crossOriginMessageEvent.source, + ); if (!iframeEl) return; const transformedEvent = this.transformCrossOriginEvent( diff --git a/packages/rrweb/src/record/index.ts b/packages/rrweb/src/record/index.ts index 7be978199d..a95f7893c9 100644 --- a/packages/rrweb/src/record/index.ts +++ b/packages/rrweb/src/record/index.ts @@ -41,8 +41,9 @@ import { } from './error-handler'; let wrappedEmit!: (e: eventWithoutTime, isCheckout?: boolean) => void; - -let takeFullSnapshot!: (isCheckout?: boolean) => void; +let takeFullSnapshot: (isCheckout?: boolean) => void = () => { + /* no-op */ +}; let canvasManager!: CanvasManager; let recording = false; @@ -278,6 +279,7 @@ function record( stylesheetManager: stylesheetManager, recordCrossOriginIframes, wrappedEmit, + takeFullSnapshot, }); /** @@ -411,6 +413,7 @@ function record( mirror.getId(document), ); }; + iframeManager.setTakeFullSnapshot(takeFullSnapshot); try { const handlers: listenerHandler[] = []; diff --git a/packages/rrweb/src/types.ts b/packages/rrweb/src/types.ts index 10ffb62b43..d4da925c23 100644 --- a/packages/rrweb/src/types.ts +++ b/packages/rrweb/src/types.ts @@ -216,6 +216,7 @@ export type CrossOriginIframeMessageEventContent = { // The origin of the iframe which originally emits this message. It is used to check the integrity of message and to filter out the rrweb messages which are forwarded by some sites. origin: string; isCheckout?: boolean; + snapshot?: boolean; }; export type CrossOriginIframeMessageEvent = MessageEvent;