Commit Graph

185 Commits

Author SHA1 Message Date
Yanzhen Yu
f511425d7e ignore removed move set 2026-04-01 12:00:00 +08:00
Justin Halsall
455c4d76f4 Add support for StylesheetRule in document fragment (#293)
* add failing test

* add stylesheet to dom to manipulate the rules

* cleanup
2026-04-01 12:00:00 +08:00
Eoghan Murray
70fe32b5d2 Suspend mutations during snapshot (#385)
* The `processMutations` function needed to be bound to the `mutationBuffer` object, as otherwise `this` referred to the `MutationObserver` object itself

* Enable external pausing of mutation buffer emissions

 - no automatic pausing based on e.g. pageVisibility yet, assuming such a thing is desirable
   https://developer.mozilla.org/en-US/docs/Web/API/Page_Visibility_API
 - user code has to call new API method `freezePage` e.g. when page is hidden or after a timeout
 - automatically unpauses when the next user initiated event occurs
   (am assuming everything that isn't a mutation event counts as 'user initiated'
   either way think this is the correct thing to do until I see a counterexample
   of an event that shouldn't cause the mutations to be unbufferred)

* Avoid a build up of duplicate `adds` by delaying pushing to adds until emission time

* Need to export freezePage in order to use it from rrweb.min.js

* Add a test to check if mutations can be turned off with the `freezePage` method

* I noticed out of order ids (in terms of a DOM walk) in a FullSnapshot.  A DOM mutation was executed against the mirror asynchronously before it could be fully processed. This would lead to a situation in replay where a mutation is executed against a DOM tree that already has the mutation applied. This changeset fixes that by freezing any mutations until the snapshot is completed.

* Remove attribute modifications from a mutation event that were incorrect in that they were repeating the attributes of those nodes present in the 'adds' array of the same mutation

* I've manually verified that this empty text node is actually removed when the dropdown is opened:

document.getElementById('select2-results-1').childNodes
NodeList(2) [li.select2-results-dept-0.select2-result.select2-result-selectable.select2-highlighted, li.select2-results-dept-0.select2-result.select2-result-selectable]

and also that it is not reinstated after the second `await page.click('.select2-container');`

* Rearrange when removal happens in order to satisfy tests. I'm also reverting a recent test change (2600fe7) so that tests pass after this rearrangement; I believe that test change to still be the correct way of doing it, but maybe it is not strictly important that there are extra mutations on attributes of just added nodes

* As mutations are now paused during FullSnapshots, we shouldn't be counting this as a 'user emission'. We automatically emit mutations after unpause anyway ('emit anything queued up now')

* Ensure that we clear arrays before emitting, as the mutation could have the side effect of triggering a FullSnapshot (checkoutEveryNth), which would otherwise re-trigger emission of same mutation (through the new pause/fullsnapshot/mutationemit/unpause process)

* Don't let the programattic pausing during TakeFullSnapshot accidentally unpause a manual call to the API method `freezePage`

* Rename paused -> frozen for consistency and change to use getter/setter access methods
2026-04-01 12:00:00 +08:00
Yanzhen Yu
4fc632b40e fix queue and use a unsafe but performant checker 2026-04-01 12:00:00 +08:00
Yanzhen Yu
80172126cf Release 0.9.6 2026-04-01 12:00:00 +08:00
jackycoder
9af39386d4 compatibility fixes (#358)
* fix polyfill NodeList forEach

* contentDocument.contains for IE

* polyfill DOMTokenList forEach
2026-04-01 12:00:00 +08:00
jackycoder
28d599f91b fix polyfill NodeList forEach (#357) 2026-04-01 12:00:00 +08:00
Yanzhen Yu
e6c0f787af close #356 improve loop checker 2026-04-01 12:00:00 +08:00
Yanzhen Yu
0ca0554613 make sure rrweb do not use browser API in static stage 2026-04-01 12:00:00 +08:00
Justin Halsall
95dd144227 child nodes without __sn now remove without error (#307) 2026-04-01 12:00:00 +08:00
Yanzhen Yu
58504e7d77 close #350 catch error may caused by checkout feature 2026-04-01 12:00:00 +08:00
Yanzhen Yu
8649ae2752 fix last played timestamp when it is a mousemove event 2026-04-01 12:00:00 +08:00
Yanzhen Yu
8b198b338e impl #309 observe font face set changes 2026-04-01 12:00:00 +08:00
Yanzhen Yu
0e30a819ce clean up __ln property when remove node 2026-04-01 12:00:00 +08:00
Yanzhen Yu
9b097ad030 close #322 hook select element selectedIndex property 2026-04-01 12:00:00 +08:00
Yanzhen Yu
2501993f07 close #336 add pointer-events: null to mouse tail wrapper 2026-04-01 12:00:00 +08:00
Yanzhen Yu
3c7e28b951 update #324, fix typo 2026-04-01 12:00:00 +08:00
Yanzhen Yu
11af001514 close #342 send SET_SPEED event when setConfig 2026-04-01 12:00:00 +08:00
Yanzhen Yu
8f3ea2b76a close #330 implement more accurate finish event 2026-04-01 12:00:00 +08:00
Yanzhen Yu
048aadaec5 close #51 add mouse tail feature 2026-04-01 12:00:00 +08:00
Yanzhen Yu
867ba85049 hide iframe before first meta event 2026-04-01 12:00:00 +08:00
Yanzhen Yu
f83f4e7b13 close #161 support "addEvent" in any state 2026-04-01 12:00:00 +08:00
Yanzhen Yu
51863e9ce8 close #215 catch delete rule errors 2026-04-01 12:00:00 +08:00
Yanzhen Yu
dc782096cb close #319 handle undefined nextId
Looks like some serializer will omit field with null value, so we
do some checks in the replayer to avoid of dead loop.
2026-04-01 12:00:00 +08:00
Yanzhen Yu
0dff4e13fa close #320 use emitter handler to catch before load state 2026-04-01 12:00:00 +08:00
Yanzhen Yu
4bf7bb9bb1 close #303 use a fork version of smooth scroll polyfill
The forked version support customize target window and document,
so we can polyfill it to the iframe.
2026-04-01 12:00:00 +08:00
Yanzhen Yu
04a5dfceef catch unexpected errors during replay media interactions 2026-04-01 12:00:00 +08:00
Justin Halsall
1cec65f565 Only trigger waitForStylesheetLoad if not seeking (#326) 2026-04-01 12:00:00 +08:00
Justin Halsall
d15f4ce8ff Update lastPlayedEvent in live mode (#327)
* Update lastPlayedEvent in live mode

* tricking travis-ci into running again
2026-04-01 12:00:00 +08:00
Justin Halsall
6ebfe69363 Fix live mode (#310)
* add failing test

* paused -> live now possible
2026-04-01 12:00:00 +08:00
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
yz-yu
6de74d7bca close #280. Improve the performance of the DOM mutation observer. (#284)
This issue was originally reported in #280 but may also relate
to #167 and other potential performance issues in the recording.

In #206 I implemented the new mutation observer which will defer
the serialization of DOM, which helps us to have a consistent DOM
order for the replay.

In this implementation, we use an array to represent the `addQueue`.
Whenever we need to consume the queue, we will iterate it to make
sure there is no dead loop, and then shift the first item to see
whether it can be serialized at the new timing.

But this implementation may be very slow when there are a lot of newly
added DOM since it will do an O(n^2) iteration.

For example, if we have three newly added DOM `n1`, `n2`, `n3`,
the iteration looks like this:
```
[n1, n2, n3]
	n1 -> n2 -> n3, consume n3
[n1, n2]
	n1 -> n2, consume n2
[n1]
	n1, consume n1
```
We should have a better performance if te iteration looks like this:
```
[n1, n2, n3]
	n3, consume n3
[n1, n2]
	n2, consume n2
[n1]
	n1, consume n1
```

Simply reverse the mutation payload does not work, because it does
not always as same as the DOM order.

So in this patch, we replace the `addQueue` with a double linked list,
which can:
1. represent the DOM order in its data structure
2. has an O(1) time complexity when looking up the sibling of a list item
3. has an O(1) time complexity when removing a list item
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
c17606630e remove global body style 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
0d6fe56b80 close #263 Since we improved the block class strategy,
we need to check parent node before pushAdd.
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
6c87b03e0d close #240 fix block class to handle text node changes 2026-04-01 12:00:00 +08:00
Yanzhen Yu
e056f1ae88 add the patch function to utils 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
8bb1c791f5 mask input options and sampling options (#252)
* part of #80, support mask input options

* close #188 enhance sampling options
Use a more general sampling strategy interface to describe the
configuration of sampling events collection.

Implemented mousmove, mouse interaction, scroll and input sampling
strategy.
2026-04-01 12:00:00 +08:00
Eoghan Murray
5b0539419c Restore functioning of #200 - this got broken after #242 (#246)
- What was broken was that it would just play activity from the first page view, but then would stop at the second page view (meta) as actions after that had been discarded
 - This restores the functionality given by the comment 'return the events from last meta to the end.' - we never want to discard events that are after the baseline time
 - I believe 'session' is the incorrect terminology for this function name, as a session in web analytics usually means a series of page views
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
ae71cf106a tweak the code of getting last session, without splice events array 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
c19aaa7ec0 Only execute events since most recent pageload when playing from an offset (#200)
On recordings with many full pageloads, dom state and mutations were being applied only to be discarded when a new pageload came in, resulting in very slow time to rebuild - and inability to interactively 'scrub' through these recordings
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