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.
* 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
* 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
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
* 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>
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.
* 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
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.
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.
'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
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.
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