Commit Graph

62 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
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
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
6c87b03e0d close #240 fix block class to handle text node changes 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
Yanzhen Yu
81b3407dd7 export utils as public API 2026-04-01 12:00:00 +08:00
Eoghan Murray
d57cae3d51 Move mutation processing into it's own class (#223)
* Move mutation processing into it's own object.

This should stand on it's own as a refactor, but is intended as a basis
for exposing the new MutationBuffer object to further outside control e.g.
to 'mute' or batch up mutation emission when the page becomes inactive
from a https://developer.mozilla.org/en-US/docs/Web/API/Page_Visibility_API
point of view

* The `processMutations` function needed to be bound to the `mutationBuffer` object, as otherwise `this` referred to the `MutationObserver` object itself

* Neglected to add this output of `npm run typings`

* Get around the binding problem by using Arrow function expressions

* Prettier formatting
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
Yanzhen Yu
0a62fd730c upgrade TS 2026-04-01 12:00:00 +08:00
Eoghan Murray
cd78aaa0fd Fix for certain websites which don't scroll on their document.documentElement (#193)
- document.documentElement.scrollTop may be zero, but document.body.scrollTop may have the actual scrolling amount
 - main fallback idea taken from https://developer.mozilla.org/en-US/docs/Web/API/Window/scrollX
 - modified as `(document.documentElement || document.body).scrollTop` will incorrectly report zero.
 - version here supported by https://github.com/mochi/mochikit/blob/master/MochiKit/Position.js#L23
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
Yanzhen Yu
bd5aa59589 ignore style sheet changes before the target DOM was serialized
The serialized DOM will contains all the styles, so this looks safe.
2026-04-01 12:00:00 +08:00
David Cramer
3a0e829884 Add observers for stylesheet mutations (#177)
* hack together stylesheet observer

* Add test coverage for insertRule/deleteRule on stylesheets

* Add new observers

* update patch based on changes to master

* Functioning event recording

* Remove print statements

* Fix ID usage and mark add vs remove

* Correct type

Co-authored-by: Jon Perl <perl.jonathan@gmail.com>
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
33e69c5af9 close #143 tweak the code of iterating iterators
Originally we use Array.from to transform iterator into array.
But we found some framework may overwrite the Array.from with a
pollyfill which was not implement correctly.
2026-04-01 12:00:00 +08:00
Filip Slatinac
39eacdece9 Changing Array.from to iterators. (#145)
* 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
2026-04-01 12:00:00 +08:00
mpstv
84de337040 Try add configurable threshold for events throttling (#147) 2026-04-01 12:00:00 +08:00
Yanzhen Yu
cd12816638 close #140 transform mutated attributes 2026-04-01 12:00:00 +08:00
yz-yu
1afc0b7527 add hooks API (#132) 2026-04-01 12:00:00 +08:00
Yanzhen Yu
2ac8634607 re-add addCustomEvent function via function property 2026-04-01 12:00:00 +08:00
Yanzhen Yu
f872ed143b Revert "temp revert #81"
This reverts commit 835161c737.
2026-04-01 12:00:00 +08:00
Yanzhen Yu
2be090cc0d temp revert #81
This patch introduce a breaking change in rrweb-record's API, so
we will revert it in 0.7.x and release it in 0.8.
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
Yanzhen Yu
3a5e9271d5 close #84 set mousemoveData's source by event source 2026-04-01 12:00:00 +08:00
Yanzhen Yu
d92a946926 impl #81 custom event
This is the record side impl of custom event, according to the
issue, we may also add first-class support for the custom event
tag like display color labels in the replayer-ui.
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
891b678557 pass mask input option to snapshot 2026-04-01 12:00:00 +08:00
Yanzhen Yu
5c3619b795 tweak some code for mask input option 2026-04-01 12:00:00 +08:00
Sebastian Jakob
2502913883 Option to mask inputs (#80)
* Option to mask inputs

Added option 'maskAllInputs' to replace all user inputs with an Asterisk.

* Update types.d.ts
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
f312ecbadc add the inline stylesheet option when recording 2026-04-01 12:00:00 +08:00
Yanzhen Yu
503987c583 fix #62 accept RegExp type block class config 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
931f71e738 update mutation observer handler
1. deep delete from adds set when node was dropped
2. remove node from dropped set when node was added again
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
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
yz-yu
ca61d6b19e fix remove node observer and check on the result of getNode (#43)
* check removed node and its parent before collect

* add more more checks on the result of getNode
2026-04-01 12:00:00 +08:00
yz-yu
081781d1ca add checkout config to recorder (#36)
* add checkout config to recorder

* add test cases for checkout feature and extract assertSnapshot method
2026-04-01 12:00:00 +08:00
Yanzhen Yu
e4cb91e1e6 fix block strategy
If an element was blocked, its child nodes should also be blocked.
The interactions and mutations on the element and its child nodes
also need to be blocked.
2026-04-01 12:00:00 +08:00
Yanzhen Yu
b093fa6015 fix some safari's scroll related issue 2026-04-01 12:00:00 +08:00
Yanzhen Yu
56aca3c2b7 check is dropped and is removed by ancestors instead of only check parent 2026-04-01 12:00:00 +08:00
Yanzhen Yu
86beabd2bb resolve #1 add ignore and block strategy for privacy purpose 2026-04-01 12:00:00 +08:00
Yanzhen Yu
09f30d2a81 return stopper function as the result of record 2026-04-01 12:00:00 +08:00
Yanzhen Yu
20f27a3822 improve prevent default event when replay 2026-04-01 12:00:00 +08:00
Yanzhen Yu
fdcf1d0bb2 update dependencies
In this commit, we updated rrweb-snapshot to 0.6.0 which including
a breaking change in data structure.
2026-04-01 12:00:00 +08:00
Yanzhen Yu
7a57784daf Implement #2: simulate hover event 2026-04-01 12:00:00 +08:00
Yanzhen Yu
c18626bf5a fix mutation filter in recorder and change the order of apply mutations in replayer
This change include two critical fix for both recorder and replayer.

In the recorder, previously we filter text and attribute mutations by check its target id,
but this was a wrong approach because removed node also has id at the callback moment.
We corrected this by checking whether the mirror map still has the target id in its keys.

In the replayer side, the issue was we got exceptions when calling insertBefore which says
the ref node was not the child node of the caller node. This will happen when the previous
or next sibling has been removed in the same callback but the previousId or nextId was
recorded.
After apply remove node mutations before add node mutations, we can make sure the removed
siblings will not exist in the mirror map when apply add node mutations. When we get node
from mirror map with an removed id, we will get null and pass it to insertBefore which
is valid.
As a side note, this apply order is safe because we ensured all the remove node mutations
do not include removing newly added nodes in the same callback.
2026-04-01 12:00:00 +08:00
Yanzhen Yu
6ce32f7994 filter text and attributes mutations which target tot a removed node 2026-04-01 12:00:00 +08:00