* Don't store the full style attribute change, as small mutations to single style properties result in storage of a rewrite for the full style attribute, which may be very large.
Had an example of a website using http://schillmania.com/projects/snowstorm/ where many direct style changes were happening every second across many 'snowflake' elements, with each attribute change looking like:
"style":"color: rgb(255, 255, 255); position: absolute; width: 8px; height: 8px; font-family: arial, verdana; overflow: hidden; font-weight: normal; z-index: 0; display: block; bottom: auto; opacity: 1; padding: 0px; margin: 0px; font-size: 10px; line-height: 10px; text-align: center; vertical-align: baseline; left: 242.807px; top: 85.7332px;"
even though maybe just the left/top position had been changed
* More compact storage for the much more common attribute value without an `!important` flag - saves 6 chars per style attr in the json :)
* Fix bug: attributes weren't getting removed after changes to treatment of 'style' attributes
* Fix sheet insertion
Restore skip duration
Use virtualStyleRulesMap to re-populate stylesheet on Flush event
Clear virtualStyleRulesMap after flush applied
* Support rule deletion in virtual processing
* Simply restoreNodeSheet with early aborts
* Encountered a bug where firstFullSnapshot was played twice because timer was immediately started and reached the snapshot before the setTimeout returned
* Ignoring a FullSnapshot needs to be a one-time only thing, as otherwise we'll ignore it after scrubbing (restarting play head at a particular time). This is a problem if mutations have altered the player state, and we try to replay those mutations, so we e.g. try to remove an element that has already been removed because we haven't reset the FullSnapshot state
* Some `npm run typings` related fixups
* add basic html snapshot functionality
* move restoreNodeSheet to it's own module
* Refactor virtual style rules to buffer changes.
Only applies changes on flush.
`virtualStyleRulesMap` now works with strings instead of CSSRules.
CSSRules can only be via made `.insertRule` on CSSStyleSheet in most browsers.
And `new CSSStyleSheet()` only works in Chrome currently.
* remove unused code
* move VirtualStyleRules from CSSRule to string in tests
* correct paths for tests
* naming
* create and restore style snapshots for virtual nodes
* update replayer snapshot
* move storeCSSRules to virtual-styles.ts
* try/catch access to .sheet in case of access errors
* clean up tests
Co-authored-by: Vladimir Milenko <vladimir.milenko@uber.com>
Co-authored-by: Eoghan Murray <eoghan@getthere.ie>
* add `userTriggered`
* update snapshots to add userTriggered
* add `userTriggered`
* update snapshots to add userTriggered
* update snapshot to include userTrigger
* only set userTriggered on `userTriggeredOnInput: true`
* What is user triggered?
* correct snapshot
* add second radio to demonstrate userTriggered
* mask value attribute changes for elements in maskInputOptions
* refactor initInputObserver to use maskInputValue
* add todo
* Fix typo
* upgrade rrweb-snapshot to 1.1.6
* move maskInputValue to rrweb-snapshot
* temp: plugin API
* fix a bug in the replay handler and rename some type names.
* update integration test
* improve plugin types and handle legacy log data
* use different naming in record and replay bundles
* delete unreferenced types
Co-authored-by: Lucky Feng <294889365@qq.com>
* add failing test
* assert order of events
* defer attaching of iframe till FullSnapshot is done
Fixes: https://github.com/rrweb-io/rrweb/issues/567
* correct event order in iframe integration test snapshot
* trigger build
* trigger build
* Move settimeout responsibility to snapshot
https://github.com/rrweb-io/rrweb-snapshot/pull/78
* upgrade rrweb-snapshot to 1.1.4
* DRY record tests
* cleanup
* Upgrade puppeteer to 9.1.1
for (hopefully) more consistent behaviour between CI and development
* make input bigger to prevent triggering scroll events
* page.waitFor is deprecated
more info: https://github.com/puppeteer/puppeteer/issues/6214
* Set os and distro for Travis ci
Co-authored-by: yz-yu <yanzhen@smartx.com>
* feat: add options to mask texts
* feat: add the default mask function
* refactor: rename options to identify the difference between mask text and mask input
* test: add tests about masking
* doc: add options about masking
* chore: bump up rrweb-snapshot version
* Impl record iframe
* iframe observe
* temp: add bundle file to git
* update bundle
* update with pick
* update bundle
* fix fragment map remove
* feat: add an option to determine whether to pause CSS animation when playback is paused (#428)
set pauseAnimation to true by default
* fix: elements would lose some states like scroll position because of "virtual parent" optimization (#427)
* fix: elements would lose some state like scroll position because of "virtual parent" optimization
* refactor: the bugfix code
bug: elements would lose some state like scroll position because of "virtual parent" optimization
* fix: an error occured at applyMutation(remove nodes part)
error message:
Uncaught (in promise) DOMException: Failed to execute 'removeChild' on 'Node': The node to be removed is not a child of this node
* pick fixes
* revert ignore file
* re-impl iframe record
* re-impl iframe replay
* code housekeeping
* move multi layer dimension calculation to replay side
* update test cases
* teardown test server
* upgrade rrweb-snapshot with iframe load timeout
Co-authored-by: Lucky Feng <yun.feng@smartx.com>
These were removed in 8ed1c999cf in order to smooth over differences in test environments
so have maintained that by converting pixel values to 'Npx' (could also try rounding, but didn't attempt that)
* Tweaks to timings to get tests passing on my dev laptop - hopefully this makes tests more deterministic
* Okay understand what's going on now that the test has run in the travis environment
* wip: working on rrweb logger
* wip: can record and replay some simple log
* wip: can record and replay log's stack
* wip: try to serialize object
* wip: record and replay console logger
hijack all of the console functions.
add listener to thrown errors
* wip: record and replay console logger
add limit to the max number of log records
* feat: enable rrweb to record and replay log messages in console
this is the implementation of new feature request(issue #234)
here are a few points of description.
1. users need to set recordLog option in rrweb.record's parameter to record log messages. The log recorder is off by default.
2. support recording and replaying all kinds of console functions. But the reliability of them should be tested more
3. the stringify function in stringify.ts needs improvement. e.g. robustness, handler for cyclical structures and better support for more kinds of object
4. we can replay the log messages in a simulated html console like LogRocket by implementing the interface "ReplayLogger" in the future
* improve: the stringify function
1. handle cyclical structures
2. add stringify option to limit the length of result
3. handle function type
* refactor: simplify the type definition of ReplayLogger
Loop the append queue has been proved to be very inefficient, and
some times lead to N^2 time complexity.
Especially when some abnormal data could not be appended into the
real DOM, will make a dead loop.
Previously we use a 5000ms time out to handle this, which is not
user-friendly and not explicitly.
In this patch, we transform the queue into a tree data structure,
which reflects the layout of real DOM. With the tree data structure,
we can find whether there are dangling nodes that need to be dropped.
Also, the iteration will be much more efficient.
There is still a 500ms time out to avoid a dead loop, but should not
be called in expected scenarios.
* 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
* 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
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
* 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.
- 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
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
* 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>
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.