Commit Graph

99 Commits

Author SHA1 Message Date
yz-yu
6fad7f642c record canvas mutations (#296)
* record canvas mutations

close #60, #261

This patch implements the canvas mutation observer.
It consists of both the record and the replay side changes.

In the record side, we add a `recordCanvas` flag to indicate
whether to record canvas elements and the flag defaults to false.
Different from our other observers, the canvas observer was
disabled by default. Because some applications with heavy canvas
usage may emit a lot of data as canvas changed, especially the
scenarios that use a lot of `drawImage` API.
So the behavior should be audited by users and only record canvas
when the flag was set to true.

In the replay side, we add a `UNSAFE_replayCanvas` flag to indicate
whether to replay canvas mutations.
Similar to the `recordCanvas` flag, `UNSAFE_replayCanvas` defaults
to false. But unlike the record canvas implementation is stable and
safe, the replay canvas implementation is UNSAFE.
It's unsafe because we need to add `allow-scripts` to the replay
sandbox, which may cause some unexpected script execution. Currently,
users should be aware of this implementation detail and enable this
feature carefully.

* update canvas integration test
2026-04-01 12:00:00 +08:00
Yanzhen Yu
3acaeadb93 close #268 subscribe latest player state before resume 2026-04-01 12:00:00 +08:00
Yanzhen Yu
9d686d4302 remove the internal use of resume API 2026-04-01 12:00:00 +08:00
Yanzhen Yu
a31efdc169 close #274 implement the new state management proposal 2026-04-01 12:00:00 +08:00
Eoghan Murray
9362baac3e Reset the lastPlayedEvent as it would otherwise be used to discard events in machine.ts as follows: (#250) 2026-04-01 12:00:00 +08:00
Yanzhen Yu
98f08f333c close #254 hotfix: only use virtual parent in sync mode 2026-04-01 12:00:00 +08:00
Yanzhen Yu
2a133f8214 close #228 safe access content document, which may be destroyed manually 2026-04-01 12:00:00 +08:00
Yanzhen Yu
520ddf3a4f close #244 pass fullsnapshot event to the callback 2026-04-01 12:00:00 +08:00
yz-yu
a8b493799b fix the skip event calculation (#242) 2026-04-01 12:00:00 +08:00
Yanzhen Yu
19acba745a fast-forward implementation v1
related to #6

Since the currently 'play at any time offset' implementation is pretty simple,
there are many things we can do to optimize its performance.

In this patch, we do the following optimizations:
1. Ignore some of the events during fast forward.
   For example, when we are going to fast forward to 10 minutes later,
   we do not need to perform mouse movement events during this period.
2. Use a fragment element as the 'virtual parent node'.
   So newly added DOM nodes will be appended to this fragment node,
   and finally being appended into the document as a batch operation.
These changes reduce a lot of time which was spent on reflow/repaint previously.
I've seen a 10 times performance improvement within these approaches.

And there are still some things we can do better but not in this patch.
1. We can build a virtual DOM tree to store the mutations of DOM.
   This will minimize the number of DOM operations.
2. Another thing that may help UX is to make the fast forward process async and cancellable.
   This may make the drag and drop interactions in the player's UI looks smooth.
2026-04-01 12:00:00 +08:00
Eoghan Murray
753ee25832 Expose startTime and endTime in the getMetaData() call. I was using player.events[0].timestamp but player.events has gone away (since a78da77 I believe) (#235) 2026-04-01 12:00:00 +08:00
Eoghan Murray
7d7def23d1 Ensure api method .play() can be called by external code without worrying about the internal state of the finite state machine (#236) 2026-04-01 12:00:00 +08:00
Yanzhen Yu
81b3407dd7 export utils as public API 2026-04-01 12:00:00 +08:00
Yanzhen Yu
6b10e1c4ee update typescript to 3.9.5 2026-04-01 12:00:00 +08:00
Yanzhen Yu
06024b245f close #216 rebuild first full snapshot when init the replayer 2026-04-01 12:00:00 +08:00
yz-yu
2a6e2e0ef9 Live mode 2 (#226)
* refactoring play, pause, resume, load style sheet to subscribe style code

* support live mode in state machine

* 1. upgrade @xstate/fsm
2. add toggle interact methods to the player
2026-04-01 12:00:00 +08:00
Eoghan Murray
da2ad8b456 Keep npm run typings happy (#222) 2026-04-01 12:00:00 +08:00
Eoghan Murray
610c2b3ec4 This dimension is in px but is unitless according to https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe (#214) 2026-04-01 12:00:00 +08:00
Rifaudeen
4bb0bdb180 added custom-event emitter to replayer (#219) 2026-04-01 12:00:00 +08:00
yz-yu
43be602816 mutation observer v2 (#206)
There are some long-term issues in rrweb's mutation observer.

A scenario cause problem:
A list of DOM node: n1, n2, n3, n4, n5
Steps of modifying the nodes:
  1. remove n1, n2, n3, n4 sequentially
  2. append n4, n3, n2, n1 after n5 sequentially
Then we got the added node data like this:
  (id: n4, prev: null, next: n3  )
  (id: n3, prev: n4,   next: n2  )
  (id: n2, prev: n3,   next: n1  )
  (id: n1, prev: n2,   next: null)
The problem comes when we try to replay the first add node datum.
Since its prev node is null, we rely on its next sibling n3. But
n3 was not present at this moment, and in previous code, we fallback
to append n4 to the last of its parent node.

The solution is to defer the append of elements that missing
siblings. But it is also hard to tell which node is the first one
that needs to be appended.

Take a step back and rethink the design of the mutation observer,
we've found there are two implementations make things complicated.
1. We set the id to -1 when we seeing some nodes are not serialized yet.
2. We record both previous sibling and next sibling to determine the
position of the node.
But we can do better!
First, we can put nodes with un-serialized siblings
to a queue, and try to add it again later. Then we can just record the next
sibling as 'the single truth' so we can be sure which node is the last
one of its parent.

This patch has implemented the new observer strategy. Data recorded with
the new observer should no longer have any node with id -1. But for
compatibility consideration, we still keep some replayer code that helps
solve legacy data.
2026-04-01 12:00:00 +08:00
yz-yu
bfc0c43aa7 impl basic player state machine (#198) 2026-04-01 12:00:00 +08:00
Yanzhen Yu
0a62fd730c upgrade TS 2026-04-01 12:00:00 +08:00
yz-yu
dcad6ff922 Packer (#172)
* introduce pako and add general packer interface

* add tests for packer

* use function API instead of class API for better tree shaking support

* refcatoring the rollup bundle config
2026-04-01 12:00:00 +08:00
Eoghan Murray
3d4280b23a Keep track of pause/play state so that player doesn't accidentally 'unpause' a user pause action (#189) 2026-04-01 12:00:00 +08:00
Yanzhen Yu
f57bb1041b remove useless console.log 2026-04-01 12:00:00 +08:00
Yanzhen Yu
67ef897454 tolerate insertRule error since browser may throw Error on wrong prefix 2026-04-01 12:00:00 +08:00
Yanzhen Yu
c68d3e1803 avoid style sheet rules index overflow 2026-04-01 12:00:00 +08:00
Filip Slatinac
a242a105d3 Checking node existence (#174)
* added our package

* reverted back to old rrweb snapshot

* Array.from does not capture all elements added in the set, we have to manually iterate through the iterator

* package lock

* checking if nodes are in the parent before we try inserting them
2026-04-01 12:00:00 +08:00
Eoghan Murray
23220f1771 Was experiencing case when a TouchEnd event occurred on a text element i.e. (nodeType: 3 / nodeName: #text) (#180)
This was a recording taken with rrweb 0.7.27 (3afff63970) and rrweb-snapshot 0.7.21 (a0dc9481b2) so issue may have been fixed in the intervening commits
2026-04-01 12:00:00 +08:00
Yanzhen Yu
329c790194 add trigger focus option to replayer, which may helps integration into other apps 2026-04-01 12:00:00 +08:00
David Cramer
704f6d4a4f Add support for replaying StyleSheetRule events (#178) 2026-04-01 12:00:00 +08:00
Yanzhen Yu
5bfc2c704a impl media interactions recording
close #159
close #72
listen to HTMLMediaElement's play/pause events, and replay them
by programmatically play and pause the target element.
2026-04-01 12:00:00 +08:00
Yanzhen Yu
d0c31bb4cf close #153 emit event when it being casted 2026-04-01 12:00:00 +08:00
dphuang2
d722554a07 Fix timeOffset on multiple stylesheet loads (#115)
In the case that the replayer triggers multiple FullSnapshot events, it
will call waitForStylesheetLoad multiple times. When the replayer
resumes, it could already have a timeoffset set from a previous
resume in a previous waitForStylesheetLoad call. In this case our new
timeoffset should be the value of our current time in the replay
(timer.timeOffset + getTimeOffset()). To solve this, I created a public
getCurrentTime function which correctly returns the time in the replay
and used that as our new timeoffset when resuming from a stylesheet
load.
2026-04-01 12:00:00 +08:00
Yanzhen Yu
be1d90ab1d fix#110 add NodeList forEach polyfill 2026-04-01 12:00:00 +08:00
dphuang2
469dbd5dee added 'pointer-events: none' for Replayer iframe (#96)
'pointer-events: none' prevents the user from interacting with the DOM
inside of the iframe. This prevents accidental tampering by a user when
watching replays such as link clicks or DOM manipulation.

pointer-events supported by 97.15% usage: https://caniuse.com/#feat=pointer-events
2026-04-01 12:00:00 +08:00
肉肉肉肉
338da11e35 new feature: add custom style in replayer (#87)
* new feature: add custom style in replayer

* concat style rules for loop
2026-04-01 12:00:00 +08:00
Yanzhen Yu
94ca07d45d fix#71 fix touch event listener and throttle touch move callback 2026-04-01 12:00:00 +08:00
Yanzhen Yu
57df28de2f remove mitt typings namesapce 2026-04-01 12:00:00 +08:00
Yanzhen Yu
eaf339ed79 Upgrade the DOM mutation observer
This is an important patch contains some crtical bug fixes for
the DOM mutation observer.
Previously the observer did not handle complex DOM movement very
well. So in this patch we optimized this by distinguishing moved
node better and added a resolving queue to avoid the error caused
by ordering.
2026-04-01 12:00:00 +08:00
Yanzhen Yu
7d4b2ad3e0 emit mouse interaction event during replay 2026-04-01 12:00:00 +08:00
yz-yu
689840d237 start to support live mode (#73)
1. add a liveMode flag to config, when liveMode is set, the timer
will keep running even though all the actions casted
2. add a public method addEvent, which will cast newly added event
in sync
3. move mouse in sync mode with the latest position info
2026-04-01 12:00:00 +08:00
Yanzhen Yu
4f58cad48d resume with time offset when after wait stylesheet load 2026-04-01 12:00:00 +08:00
Yanzhen Yu
32c33f3ce8 move browser related APIs into runtime 2026-04-01 12:00:00 +08:00
Yanzhen Yu
d1b32e6e9d impl #23 add custom privacy selectors 2026-04-01 12:00:00 +08:00
Yanzhen Yu
da54061b53 check attribute is null instead of any falsy value 2026-04-01 12:00:00 +08:00
Yanzhen Yu
12fc5de3a7 check whether the removed node's ancestors has been removed 2026-04-01 12:00:00 +08:00
Yanzhen Yu
153035f4b0 turn some warning message into debug message 2026-04-01 12:00:00 +08:00
yz-yu
4a2c733583 improve mouse record and replay (#49)
Increase the throttle value of collecting mouse move and add a
transition time to the replayer's mouse. This may decrease the
size of mouse move data and make the replay movement more smooth.
Another change is to move and hover when click event is replayed.
This is to improve the replay of some programmatic click, such as
E2E tests.
2026-04-01 12:00:00 +08:00
Yanzhen Yu
e2bc867b2e move all events to an enum and update guide 2026-04-01 12:00:00 +08:00