diff --git a/src/record/index.ts b/src/record/index.ts index 585a5fc6..1331d02a 100644 --- a/src/record/index.ts +++ b/src/record/index.ts @@ -18,6 +18,7 @@ import { listenerHandler, LogRecordOptions, mutationCallbackParam, + scrollCallback, } from '../types'; import { IframeManager } from './iframe-manager'; import { ShadowDomManager } from './shadow-dom-manager'; @@ -197,6 +198,16 @@ function record( }), ); }; + const wrappedScrollEmit: scrollCallback = (p) => + wrappedEmit( + wrapEvent({ + type: EventType.IncrementalSnapshot, + data: { + source: IncrementalSource.Scroll, + ...p, + }, + }), + ); const iframeManager = new IframeManager({ mutationCb: wrappedMutationEmit, @@ -204,6 +215,7 @@ function record( const shadowDomManager = new ShadowDomManager({ mutationCb: wrappedMutationEmit, + scrollCb: wrappedScrollEmit, bypassOptions: { blockClass, blockSelector, @@ -213,6 +225,7 @@ function record( maskInputOptions, maskTextFn, recordCanvas, + sampling, slimDOMOptions, iframeManager, }, @@ -325,16 +338,7 @@ function record( }, }), ), - scrollCb: (p) => - wrappedEmit( - wrapEvent({ - type: EventType.IncrementalSnapshot, - data: { - source: IncrementalSource.Scroll, - ...p, - }, - }), - ), + scrollCb: wrappedScrollEmit, viewportResizeCb: (d) => wrappedEmit( wrapEvent({ diff --git a/src/record/observer.ts b/src/record/observer.ts index dd2a1934..24a459e5 100644 --- a/src/record/observer.ts +++ b/src/record/observer.ts @@ -60,6 +60,25 @@ type WindowWithAngularZone = Window & { export const mutationBuffers: MutationBuffer[] = []; +function getEventTarget(event: Event): EventTarget | null { + try { + if ('composedPath' in event) { + const path = event.composedPath(); + if (path.length) { + return path[0]; + } + } else if ( + 'path' in event && + (event as { path: EventTarget[] }).path.length + ) { + return (event as { path: EventTarget[] }).path[0]; + } + return event.target; + } catch { + return event.target; + } +} + export function initMutationObserver( cb: mutationCallBack, doc: Document, @@ -176,7 +195,7 @@ function initMoveObserver( ); const updatePosition = throttle( (evt) => { - const { target } = evt; + const target = getEventTarget(evt); const { clientX, clientY } = isTouchEvent(evt) ? evt.changedTouches[0] : evt; @@ -231,14 +250,15 @@ function initMouseInteractionObserver( const handlers: listenerHandler[] = []; const getHandler = (eventKey: keyof typeof MouseInteractions) => { return (event: MouseEvent | TouchEvent) => { - if (isBlocked(event.target as Node, blockClass)) { + const target = getEventTarget(event) as Node; + if (isBlocked(target as Node, blockClass)) { return; } const e = isTouchEvent(event) ? event.changedTouches[0] : event; if (!e) { return; } - const id = mirror.getId(event.target as INode); + const id = mirror.getId(target as INode); const { clientX, clientY } = e; cb({ type: MouseInteractions[eventKey], @@ -265,7 +285,7 @@ function initMouseInteractionObserver( }; } -function initScrollObserver( +export function initScrollObserver( cb: scrollCallback, doc: Document, mirror: Mirror, @@ -273,11 +293,12 @@ function initScrollObserver( sampling: SamplingStrategy, ): listenerHandler { const updatePosition = throttle((evt) => { - if (!evt.target || isBlocked(evt.target as Node, blockClass)) { + const target = getEventTarget(evt); + if (!target || isBlocked(target as Node, blockClass)) { return; } - const id = mirror.getId(evt.target as INode); - if (evt.target === doc) { + const id = mirror.getId(target as INode); + if (target === doc) { const scrollEl = (doc.scrollingElement || doc.documentElement)!; cb({ id, @@ -287,12 +308,12 @@ function initScrollObserver( } else { cb({ id, - x: (evt.target as HTMLElement).scrollLeft, - y: (evt.target as HTMLElement).scrollTop, + x: (target as HTMLElement).scrollLeft, + y: (target as HTMLElement).scrollTop, }); } }, sampling.scroll || 100); - return on('scroll', updatePosition); + return on('scroll', updatePosition, doc); } function initViewportResizeObserver( @@ -328,7 +349,7 @@ function initInputObserver( sampling: SamplingStrategy, ): listenerHandler { function eventHandler(event: Event) { - const { target } = event; + const target = getEventTarget(event); if ( !target || !(target as Element).tagName || @@ -465,7 +486,7 @@ function initMediaInteractionObserver( mirror: Mirror, ): listenerHandler { const handler = (type: 'play' | 'pause') => (event: Event) => { - const { target } = event; + const target = getEventTarget(event); if (!target || isBlocked(target as Node, blockClass)) { return; } diff --git a/src/record/shadow-dom-manager.ts b/src/record/shadow-dom-manager.ts index 92774f4e..119ca992 100644 --- a/src/record/shadow-dom-manager.ts +++ b/src/record/shadow-dom-manager.ts @@ -4,10 +4,12 @@ import { maskTextClass, MaskTextFn, Mirror, + scrollCallback, + SamplingStrategy, } from '../types'; import { MaskInputOptions, SlimDOMOptions } from 'rrweb-snapshot'; import { IframeManager } from './iframe-manager'; -import { initMutationObserver } from './observer'; +import { initMutationObserver, initScrollObserver } from './observer'; type BypassOptions = { blockClass: blockClass; @@ -18,21 +20,25 @@ type BypassOptions = { maskInputOptions: MaskInputOptions; maskTextFn: MaskTextFn | undefined; recordCanvas: boolean; + sampling: SamplingStrategy; slimDOMOptions: SlimDOMOptions; iframeManager: IframeManager; }; export class ShadowDomManager { private mutationCb: mutationCallBack; + private scrollCb: scrollCallback; private bypassOptions: BypassOptions; private mirror: Mirror; constructor(options: { mutationCb: mutationCallBack; + scrollCb: scrollCallback; bypassOptions: BypassOptions; mirror: Mirror; }) { this.mutationCb = options.mutationCb; + this.scrollCb = options.scrollCb; this.bypassOptions = options.bypassOptions; this.mirror = options.mirror; } @@ -55,5 +61,14 @@ export class ShadowDomManager { this, shadowRoot, ); + initScrollObserver( + this.scrollCb, + // https://gist.github.com/praveenpuglia/0832da687ed5a5d7a0907046c9ef1813 + // scroll is not allowed to pass the boundary, so we need to listen the shadow document + (shadowRoot as unknown) as Document, + this.mirror, + this.bypassOptions.blockClass, + this.bypassOptions.sampling, + ); } }