diff --git a/package.json b/package.json index 22985f3e..bc265d92 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@highlight-run/rrweb", - "version": "0.12.3", + "version": "0.12.4", "description": "record and replay the web", "scripts": { "test": "npm run bundle:browser && cross-env TS_NODE_CACHE=false TS_NODE_FILES=true mocha -r ts-node/register -r ignore-styles -r jsdom-global/register test/**.test.ts", diff --git a/src/record/mutation.ts b/src/record/mutation.ts index ade413ba..2e62b2ee 100644 --- a/src/record/mutation.ts +++ b/src/record/mutation.ts @@ -273,7 +273,10 @@ export default class MutationBuffer { const shadowHost: Element | null = n.getRootNode ? (n.getRootNode() as ShadowRoot)?.host : null; - const notInDoc = !this.doc.contains(n) && !this.doc.contains(shadowHost); + // ensure shadowHost is a Node, or doc.contains will throw an error + const notInDoc = + !this.doc.contains(n) && + (!(shadowHost instanceof Node) || !this.doc.contains(shadowHost)); if (!n.parentNode || notInDoc) { return; } @@ -298,7 +301,7 @@ export default class MutationBuffer { maskInputFn: this.maskInputFn, slimDOMOptions: this.slimDOMOptions, recordCanvas: this.recordCanvas, - enableStrictPrivacy: this.enableStrictPrivacy, + enableStrictPrivacy: this.enableStrictPrivacy, onSerialize: (currentN) => { if (isIframeINode(currentN)) { this.iframeManager.addIframe(currentN); @@ -362,13 +365,16 @@ export default class MutationBuffer { if (!node) { for (let index = addList.length - 1; index >= 0; index--) { const _node = addList.get(index)!; - const parentId = this.mirror.getId( - (_node.value.parentNode as Node) as INode, - ); - const nextId = getNextId(_node.value); - if (parentId !== -1 && nextId !== -1) { - node = _node; - break; + // ensure _node is defined before attempting to find value + if (_node) { + const parentId = this.mirror.getId( + (_node.value.parentNode as Node) as INode, + ); + const nextId = getNextId(_node.value); + if (parentId !== -1 && nextId !== -1) { + node = _node; + break; + } } } } diff --git a/src/record/observer.ts b/src/record/observer.ts index cf81b64e..6261b73e 100644 --- a/src/record/observer.ts +++ b/src/record/observer.ts @@ -196,21 +196,42 @@ function initMoveObserver( }, callbackThreshold, ); - const updatePosition = throttle( + // update position for mouse, touch, and drag events (drag event extends mouse event) + function handleUpdatePositionEvent(evt: MouseEvent | TouchEvent) { + const target = getEventTarget(evt); + const { clientX, clientY } = isTouchEvent(evt) + ? evt.changedTouches[0] + : evt; + if (!timeBaseline) { + timeBaseline = Date.now(); + } + positions.push({ + x: clientX, + y: clientY, + id: mirror.getId(target as INode), + timeOffset: Date.now() - timeBaseline, + }); + } + + // separate call for non-drag events, in case DragEvent is not defined + const updatePosition = throttle( (evt) => { - const target = getEventTarget(evt); - const { clientX, clientY } = isTouchEvent(evt) - ? evt.changedTouches[0] - : evt; - if (!timeBaseline) { - timeBaseline = Date.now(); - } - positions.push({ - x: clientX, - y: clientY, - id: mirror.getId(target as INode), - timeOffset: Date.now() - timeBaseline, - }); + handleUpdatePositionEvent(evt); + wrappedCb( + evt instanceof MouseEvent + ? IncrementalSource.MouseMove + : IncrementalSource.TouchMove, + ); + }, + threshold, + { + trailing: false, + }, + ); + // call for drag events, when DragEvent is defined + const updateDragPosition = throttle( + (evt) => { + handleUpdatePositionEvent(evt); wrappedCb( evt instanceof DragEvent ? IncrementalSource.Drag @@ -224,10 +245,13 @@ function initMoveObserver( trailing: false, }, ); + // it is possible DragEvent is undefined even on devices + // that support event 'drag' + const dragEventDefined = typeof DragEvent !== 'undefined'; const handlers = [ on('mousemove', updatePosition, doc), on('touchmove', updatePosition, doc), - on('drag', updatePosition, doc), + on('drag', dragEventDefined ? updateDragPosition : updatePosition, doc), ]; return () => { handlers.forEach((h) => h());