diff --git a/.gitignore b/.gitignore index de1069e348..895e1115aa 100644 --- a/.gitignore +++ b/.gitignore @@ -1,13 +1,17 @@ -.vscode +.vscode/* +!/.vscode/rrweb-monorepo.code-workspace .idea node_modules package-lock.json # yarn.lock -build -dist -es -lib temp *.log + +.env + +.DS_Store + +build +dist diff --git a/.release-it.json b/.release-it.json index 72e1199133..41e16cf33f 100644 --- a/.release-it.json +++ b/.release-it.json @@ -1,9 +1,15 @@ { - "non-interactive": true, - "hooks": { - "before:init": ["npm run bundle", "npm run typings"] - }, + "hooks": {}, "git": { - "requireCleanWorkingDir": false + "commit": false, + "tag": false, + "push": false + }, + "npm": { + "publish": false + }, + "github": { + "release": true, + "releaseName": "Release ${version}" } } \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index 52d6bad905..9d5753868e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,9 @@ language: node_js +os: linux + +dist: focal + node_js: - 12 @@ -7,4 +11,6 @@ install: - yarn script: - - xvfb-run --server-args="-screen 0 1920x1080x24" npm test + - yarn lerna run prepublish + - yarn lerna run check-types + - xvfb-run --server-args="-screen 0 1920x1080x24" yarn lerna run test diff --git a/.vscode/rrweb-monorepo.code-workspace b/.vscode/rrweb-monorepo.code-workspace new file mode 100644 index 0000000000..79849c8fe9 --- /dev/null +++ b/.vscode/rrweb-monorepo.code-workspace @@ -0,0 +1,31 @@ +{ + "folders": [ + { + "name": " rrweb monorepo", // added a space to bump it to the top + "path": ".." + }, + { + "name": "rrdom (package)", + "path": "../packages/rrdom" + }, + { + "name": "rrweb (package)", + "path": "../packages/rrweb" + }, + { + "name": "rrweb-player (package)", + "path": "../packages/rrweb-player" + }, + { + "name": "rrweb-snapshot (package)", + "path": "../packages/rrweb-snapshot" + } + ], + "settings": { + "jest.disabledWorkspaceFolders": [ + " rrweb monorepo", + "rrweb-player (package)", + "rrdom (package)" + ] + } +} diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000000..8521fa79c2 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,26 @@ +# Changelog + +## v1.0.0 + +### Featrues & Improvements + +- Support record same-origin non-sandboxed iframe. +- Support record open-mode shadow DOM. +- Implement the plugin API. +- Export `record.takeFullSnapshot` as a public API +- Record and replay drag events. +- Add options to mask texts (#540). + +### Fixes + +- Get the original MutationObserver when Angular patched it. +- Fix RangeError: Maximum call stack size exceeded (#479). +- Fix the linked-list implementation in the recorder. +- Don't perform newly added actions if the player is paused (#539). +- Fix inaccurate mouse position (#522) + +### Breaking Changes + +- Deprecated the usage of `rrweb.mirror`. Please use `record.mirror` and `replayer.getMirror()` instead. +- Deprecated the record option `recordLog `. See the new plugin API [here](./docs/recipes/console.md). +- Deprecated the replay option ` `. See the new plugin API [here](./docs/recipes/console.md). diff --git a/README.md b/README.md index c62942ed13..3e6490f17b 100644 --- a/README.md +++ b/README.md @@ -7,14 +7,13 @@ # rrweb -**[🚀 Try storyteller, a no-code interactive tutorial builder build with rrweb](https://storyteller.webzard.io/?utm_source=rrweb&utm_medium=github)** +**[The rrweb documentary (in Chinese, with English subtitles)](https://www.bilibili.com/video/BV1wL4y1B7wN?share_source=copy_web)** -**[The new adventure of the rrweb community](http://www.myriptide.com/rrweb-community/)** - -[![Build Status](https://travis-ci.org/rrweb-io/rrweb.svg?branch=master)](https://travis-ci.org/rrweb-io/rrweb) -[![Join the chat at https://gitter.im/rrweb-io/rrweb](https://badges.gitter.im/rrweb-io/rrweb.svg)](https://gitter.im/rrweb-io/rrweb?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +[![Join the chat at slack](https://img.shields.io/badge/slack-@rrweb-teal.svg?logo=slack)](https://join.slack.com/t/rrweb/shared_invite/zt-siwoc6hx-uWay3s2wyG8t5GpZVb8rWg) +[![Twitter Follow](https://img.shields.io/badge/twitter-@rrweb__io-teal.svg?logo=twitter)](https://twitter.com/rrweb_io) ![total gzip size](https://img.badgesize.io/https://cdn.jsdelivr.net/npm/rrweb@latest/dist/rrweb.min.js?compression=gzip&label=total%20gzip%20size) ![recorder gzip size](https://img.badgesize.io/https://cdn.jsdelivr.net/npm/rrweb@latest/dist/record/rrweb-record.min.js?compression=gzip&label=recorder%20gzip%20size) +[![](https://data.jsdelivr.com/v1/package/npm/rrweb/badge)](https://www.jsdelivr.com/package/npm/rrweb) [中文文档](./README.zh_CN.md) @@ -22,36 +21,31 @@ rrweb refers to 'record and replay the web', which is a tool for recording and replaying users' interactions on the web. -**Currently, rrweb has already solved many difficult problems in recording and replaying, but the data structure may still be changed before the release of Version 1.0. So please be cautious to use rrweb in the production environment.** - ## Guide [**📚 Read the rrweb guide here. 📚**](./guide.md) -[**Recipes**](./docs/recipes/index.md) +[**🍳 Recipes 🍳**](./docs/recipes/index.md) ## Project Structure rrweb is mainly composed of 3 parts: -- **[rrweb-snapshot](https://github.com/rrweb-io/rrweb-snapshot)**, including both snapshot and rebuilding features. The snapshot is used to convert the DOM and its state into a serializable data structure with a unique identifier; the rebuilding feature is to rebuild the snapshot into corresponding DOM. +- **[rrweb-snapshot](https://github.com/rrweb-io/rrweb/tree/master/packages/rrweb-snapshot/)**, including both snapshot and rebuilding features. The snapshot is used to convert the DOM and its state into a serializable data structure with a unique identifier; the rebuilding feature is to rebuild the snapshot into corresponding DOM. - **[rrweb](https://github.com/rrweb-io/rrweb)**, including two functions, record and replay. The record function is used to record all the mutations in the DOM; the replay is to replay the recorded mutations one by one according to the corresponding timestamp. -- **[rrweb-player](https://github.com/rrweb-io/rrweb-player)**, is a player UI for rrweb, providing GUI-based functions like pause, fast-forward, drag and drop to play at any time. +- **[rrweb-player](https://github.com/rrweb-io/rrweb/tree/master/packages/rrweb-player/)**, is a player UI for rrweb, providing GUI-based functions like pause, fast-forward, drag and drop to play at any time. ## Roadmap -- rrweb - - handle cross-domain request errors - - record in web worker - - implement transmission data compression - - verify recording in mobile browser -- rrweb-player - - implement efficient progress bar drag and drop control - - add full screen mode -- extensions - - hijack the console API and record corresponding events - - hijack Ajax/fetch API and record request events - - use TraceKit to log exception events +- rrdom: an ad-hoc DOM for rrweb session data [#419](https://github.com/rrweb-io/rrweb/issues/419) +- storage engine: do deduplication on a large number of rrweb sessions +- more end-to-end tests +- compact mutation data in common patterns +- provide plugins via the new plugin API, including: + - XHR plugin + - fetch plugin + - GraphQL plugin + - ... ## Internal Design @@ -66,19 +60,101 @@ Since we want the record and replay sides to share a strongly typed data structu [Typescript handbook](https://www.typescriptlang.org/docs/handbook/declaration-files/introduction.html) -1. Fork the rrweb component repository you want to patch. -2. Run `npm install` to install required dependencies. -3. Patch the code and pass all the tests. -4. Push the code and create a pull request. +1. Fork this repository. +2. Run `yarn install` in the root to install required dependencies for all sub-packages (note: `npm install` is _not_ recommended). +3. Run `yarn dev` in the root to get auto-building for all the sub-packages whenever you modify anything. +4. Navigate to one of the sub-packages (in the `packages` folder) where you'd like to make a change. +5. Patch the code and run `yarn test` to run the tests, make sure they pass before you commit anything. +6. Push the code and create a pull request. + +Protip: You can run `yarn test` in the root folder to run all the tests. In addition to adding integration tests and unit tests, rrweb also provides a REPL testing tool. [Using the REPL tool](./guide.md#REPL-tool) +## Core Team Members + + + + + + + + +
+ + +
Yuyz0112 +
+
+ + +
Mark-Fenng +
+
+ + +
eoghanmurray +
+
+ + +
Juice10 +
+
+ ## Who's using rrweb -

- - - -

+ + + + + + + + + + + +
+ + + + + + + + + + + + + + The first ever UX automation tool + +
+ + + + + + Remote Access & Co-Browsing + +
diff --git a/README.zh_CN.md b/README.zh_CN.md index 0aa7d54d40..6d37bbb19b 100644 --- a/README.zh_CN.md +++ b/README.zh_CN.md @@ -7,52 +7,42 @@ # rrweb -**[🚀 Storyteller, 基于 rrweb 开发的无代码交互式教程编辑器](https://storyteller.webzard.io/?utm_source=rrweb&utm_medium=github)** +**[rrweb 纪录片(中文)](https://www.bilibili.com/video/BV1wL4y1B7wN?share_source=copy_web)** -**[rrweb 社区新的征程](http://www.myriptide.com/rrweb-community-cn/)** - -[![Build Status](https://travis-ci.org/rrweb-io/rrweb.svg?branch=master)](https://travis-ci.org/rrweb-io/rrweb) -[![Join the chat at https://gitter.im/rrweb-io/rrweb](https://badges.gitter.im/rrweb-io/rrweb.svg)](https://gitter.im/rrweb-io/rrweb?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +[![Join the chat at slack](https://img.shields.io/badge/slack-@rrweb-teal.svg?logo=slack)](https://join.slack.com/t/rrweb/shared_invite/zt-siwoc6hx-uWay3s2wyG8t5GpZVb8rWg) ![total gzip size](https://img.badgesize.io/https://cdn.jsdelivr.net/npm/rrweb@latest/dist/rrweb.min.js?compression=gzip&label=total%20gzip%20size) ![recorder gzip size](https://img.badgesize.io/https://cdn.jsdelivr.net/npm/rrweb@latest/dist/record/rrweb-record.min.js?compression=gzip&label=recorder%20gzip%20size) +[![](https://data.jsdelivr.com/v1/package/npm/rrweb/badge)](https://www.jsdelivr.com/package/npm/rrweb) > 我已开通 Github Sponsor, 您可以通过赞助的形式帮助 rrweb 的开发。 rrweb 是 'record and replay the web' 的简写,旨在利用现代浏览器所提供的强大 API 录制并回放任意 web 界面中的用户操作。 -**目前 rrweb 已经解决了许多录制与回放中的难点问题,但在 1.0 版本 release 之前数据结构仍有可能发生变化,请谨慎用于生产环境中。** - ## 指南 [**📚 rrweb 使用指南 📚**](./guide.zh_CN.md) -[**场景示例**](./docs/recipes/index.zh_CN.md) +[**🍳 场景示例 🍳**](./docs/recipes/index.zh_CN.md) ## 项目结构 rrweb 主要由 3 部分组成: -- **[rrweb-snapshot](https://github.com/rrweb-io/rrweb-snapshot)**,包含 snapshot 和 rebuild 两个功能。snapshot 用于将 DOM 及其状态转化为可序列化的数据结构并添加唯一标识;rebuild 则是将 snapshot 记录的数据结构重建为对应的 DOM。 +- **[rrweb-snapshot](https://github.com/rrweb-io/rrweb/tree/master/packages/rrweb-snapshot/)**,包含 snapshot 和 rebuild 两个功能。snapshot 用于将 DOM 及其状态转化为可序列化的数据结构并添加唯一标识;rebuild 则是将 snapshot 记录的数据结构重建为对应的 DOM。 - **[rrweb](https://github.com/rrweb-io/rrweb)**,包含 record 和 replay 两个功能。record 用于记录 DOM 中的所有变更(mutation);replay 则是将记录的变更按照对应的时间一一重放。 -- **[rrweb-player](https://github.com/rrweb-io/rrweb-player)**,为 rrweb 提供一套 UI 控件,提供基于 GUI 的暂停、快进、拖拽至任意时间点播放等功能。 +- **[rrweb-player](https://github.com/rrweb-io/rrweb/tree/master/packages/rrweb-player/)**,为 rrweb 提供一套 UI 控件,提供基于 GUI 的暂停、快进、拖拽至任意时间点播放等功能。 ## Roadmap -- rrweb - - 处理跨域请求错误 - - 转移至 web worker 中执行 - - 实现传输数据压缩 - - 验证移动端录制效果 -- rrweb-player - - 实现高效的进度条拖拽功能 - - 增加全屏模式 -- extensions - - 劫持 console API,记录对应的事件 - - 劫持 Ajax/fetch API,记录请求事件 - - 封装 TraceKit,记录异常事件 -- 测试 - - 补充更多单元测试 - - 随机在更多网站上运行集成测试 +- rrdom: rrweb 数据专用的 DOM 实现 [#419](https://github.com/rrweb-io/rrweb/issues/419) +- storage engine: 对大规模 rrweb 数据进行去重 +- 更多的 E2E 测试 +- 在常见场景下对 mutation 数据进行压缩 +- 基于新的插件 API 提供更多插件,包括: + - XHR 插件 + - fetch 插件 + - GraphQL 插件 + - ... ## Internal Design @@ -68,7 +58,7 @@ rrweb 主要由 3 部分组成: [Typescript 手册](https://www.typescriptlang.org/docs/handbook/declaration-files/introduction.html) 1. Fork 需要修改的 rrweb 组件仓库 -2. `npm install` 安装所需依赖 +2. `yarn install` 安装所需依赖 3. 修改代码并通过测试 4. 提交代码,创建 pull request @@ -76,10 +66,71 @@ rrweb 主要由 3 部分组成: [使用 REPL 工具](./guide.zh_CN.md#REPL-工具) +## Core Team Members + + + + + + + + +
+ + +
Yuyz0112 +
+
+ + +
Mark-Fenng +
+
+ + +
eoghanmurray +
+
+ + +
Juice10 +
+
+ ## Who's using rrweb -

- - - -

+ + + + + + +
+ + + + + + + + + + + +
diff --git a/docs/observer.md b/docs/observer.md index 20417b42c6..aed6cf4ee2 100644 --- a/docs/observer.md +++ b/docs/observer.md @@ -1,4 +1,5 @@ # Incremental snapshots + After completing a full snapshot, we need to record events that change the state. Right now, rrweb records the following events (we will expand upon this): @@ -18,9 +19,11 @@ Right now, rrweb records the following events (we will expand upon this): - Input ## Mutation Observer + Since we don't execute any JavaScript during replay, we instead need to record all changes scripts make to the document. Consider this example: + > User clicks a button. A dropdown menu appears. User selects the first item. The dropdown menu disappears. During replay, the dropdown menu does not automatically appear after the "click button" is executed, because the original JavaScript is not part of the recording. Thus, we need to record the creation of the dropdown menu DOM nodes, the selection of the first item, and subsequent deletion of the dropdown menu DOM nodes. This is the most difficult part. @@ -33,9 +36,10 @@ The first thing to understand is that MutationObserver uses a **Bulk Asynchronou This mechanism is not problematic for normal use, because we do not only have the mutation record, but we can also directly access the DOM object of the mutated node as well as any parent, child and sibling nodes. -However in rrweb, since we have a serialization process, we need more sophisticated soluation to be able to deal with various scenarios. +However in rrweb, since we have a serialization process, we need more sophisticated solution to be able to deal with various scenarios. ### Add node + For example, the following two operations generate the same DOM structure, but produce a different set of mutation records: ``` @@ -63,32 +67,40 @@ We already introduced in the [serialization design document](./serialization.md) As you can see, since we have delayed serialization of the newly added nodes, all mutation records also need to be processed first, and only then the new nodes can be de-duplicated without causing trouble. ### Remove node + When processing mutation records, we may encounter a removed node that has not yet been serialized. That indicates that it is a newly added node, and the "add node" mutation record is also somewhere in the mutation records we received. We label these nodes as "dropped nodes". There are two cases we need to handle here: + 1. Since the node was removed already, there is no need to replay it, and thus we remove it from the newly added node pool. 2. This also applies to descendants of the dropped node, thus when processing newly added nodes we need to check if it has a dropped node as an ancestor. ### Attribute change + Although MutationObserver is an asynchronous batch callback, we can still assume that the time interval between mutations occurring in a callback is extremely short, so we can optimize the size of the incremental snapshot by overwriting some data when recording the DOM property changes. For example, resizing a ` + + + + + + " +`; + +exports[`integration tests [html file]: hover.html 1`] = ` +" + + + + hover selector + + +
hover me
+" +`; + +exports[`integration tests [html file]: iframe.html 1`] = ` +" + + + + iframe + + + +" +`; + +exports[`integration tests [html file]: iframe-inner.html 1`] = ` +" +" +`; + +exports[`integration tests [html file]: invalid-attribute.html 1`] = ` +" +" +`; + +exports[`integration tests [html file]: invalid-doctype.html 1`] = ` +" + + + Invalid Doctype + + " +`; + +exports[`integration tests [html file]: invalid-tagname.html 1`] = ` +" + + + + Document + + +
Hello
+
Hello
+
+" +`; + +exports[`integration tests [html file]: mask-text.html 1`] = ` +" + + + + Document + +

**** *

+
+ **** * +
+
**** *
+ " +`; + +exports[`integration tests [html file]: picture.html 1`] = ` +" + + + + + \\"This + " +`; + +exports[`integration tests [html file]: preload.html 1`] = ` +" + + + Document + + + + " +`; + +exports[`integration tests [html file]: shadow-dom.html 1`] = ` +" + + + shadow DOM + + + + + + +
content panel 1
+
content panel 2
+
content panel 3
+
+ + " +`; + +exports[`integration tests [html file]: svg.html 1`] = ` +" + IcoMoon - SVG Icons + + + + +
+

Grid Size: 0

+
+
+ + Icon-behance +
+
+
+
+ + Icon-linkedin +
+
+
+ + + + + + + + + " +`; + +exports[`integration tests [html file]: video.html 1`] = ` +" + + + + video + + + + " +`; + +exports[`integration tests [html file]: with-relative-res.html 1`] = ` +" + + + + Document + + + +
Hello
+ Hello +
Hello
+
+ \\"\\" + \\"\\" + \\"\\" + \\"\\" + \\"\\"" +`; + +exports[`integration tests [html file]: with-script.html 1`] = ` +" + + + + with script + + + " +`; + +exports[`integration tests [html file]: with-style-sheet.html 1`] = ` +" + + + + with style sheet + + +" +`; + +exports[`integration tests [html file]: with-style-sheet-with-import.html 1`] = ` +" + + + + with style sheet with import + + +" +`; + +exports[`shadow DOM integration tests snapshot shadow DOM 1`] = ` +"{ + \\"type\\": 0, + \\"childNodes\\": [ + { + \\"type\\": 1, + \\"name\\": \\"html\\", + \\"publicId\\": \\"\\", + \\"systemId\\": \\"\\", + \\"id\\": 2 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"html\\", + \\"attributes\\": { + \\"lang\\": \\"en\\" + }, + \\"childNodes\\": [ + { + \\"type\\": 2, + \\"tagName\\": \\"head\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 5 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"meta\\", + \\"attributes\\": { + \\"charset\\": \\"UTF-8\\" + }, + \\"childNodes\\": [], + \\"id\\": 6 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 7 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"meta\\", + \\"attributes\\": { + \\"name\\": \\"viewport\\", + \\"content\\": \\"width=device-width, initial-scale=1.0\\" + }, + \\"childNodes\\": [], + \\"id\\": 8 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 9 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"title\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"shadow DOM\\", + \\"id\\": 11 + } + ], + \\"id\\": 10 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 12 + } + ], + \\"id\\": 4 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 13 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"body\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 15 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"fancy-tabs\\", + \\"attributes\\": { + \\"background\\": \\"\\", + \\"role\\": \\"tablist\\", + \\"selected\\": \\"1\\" + }, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 17 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"button\\", + \\"attributes\\": { + \\"slot\\": \\"title\\", + \\"role\\": \\"tab\\", + \\"tabindex\\": \\"-1\\", + \\"aria-selected\\": \\"false\\" + }, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"Tab 1\\", + \\"id\\": 19 + } + ], + \\"id\\": 18 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 20 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"button\\", + \\"attributes\\": { + \\"slot\\": \\"title\\", + \\"selected\\": \\"\\", + \\"role\\": \\"tab\\", + \\"tabindex\\": \\"0\\", + \\"aria-selected\\": \\"true\\" + }, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"Tab 2\\", + \\"id\\": 22 + } + ], + \\"id\\": 21 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 23 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"button\\", + \\"attributes\\": { + \\"slot\\": \\"title\\", + \\"role\\": \\"tab\\", + \\"tabindex\\": \\"-1\\", + \\"aria-selected\\": \\"false\\" + }, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"Tab 3\\", + \\"id\\": 25 + } + ], + \\"id\\": 24 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 26 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"section\\", + \\"attributes\\": { + \\"role\\": \\"tabpanel\\", + \\"tabindex\\": \\"0\\", + \\"aria-hidden\\": \\"true\\" + }, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"content panel 1\\", + \\"id\\": 28 + } + ], + \\"id\\": 27 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 29 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"section\\", + \\"attributes\\": { + \\"role\\": \\"tabpanel\\", + \\"tabindex\\": \\"0\\", + \\"aria-hidden\\": \\"false\\" + }, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"content panel 2\\", + \\"id\\": 31 + } + ], + \\"id\\": 30 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 32 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"section\\", + \\"attributes\\": { + \\"role\\": \\"tabpanel\\", + \\"tabindex\\": \\"0\\", + \\"aria-hidden\\": \\"true\\" + }, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"content panel 3\\", + \\"id\\": 34 + } + ], + \\"id\\": 33 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 35 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 36, + \\"isShadow\\": true + }, + { + \\"type\\": 2, + \\"tagName\\": \\"style\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\":host { display: inline-block; width: 650px; font-family: \\\\\\"Roboto Slab\\\\\\"; contain: content; }:host([background]) { background: var(--background-color, #9E9E9E); border-radius: 10px; padding: 10px; }#panels { box-shadow: rgba(0, 0, 0, 0.3) 0px 2px 2px; background: white; border-radius: 3px; padding: 16px; height: 250px; overflow: auto; }#tabs { display: inline-flex; user-select: none; }#tabs slot { display: inline-flex; }#tabs ::slotted(*) { font: 400 16px/22px Roboto; padding: 16px 8px; margin: 0px; text-align: center; width: 100px; text-overflow: ellipsis; white-space: nowrap; overflow: hidden; cursor: pointer; border-top-left-radius: 3px; border-top-right-radius: 3px; background: linear-gradient(rgb(250, 250, 250), rgb(238, 238, 238)); border: none; }#tabs ::slotted([aria-selected=\\\\\\"true\\\\\\"]) { font-weight: 600; background: white; box-shadow: none; }#tabs ::slotted(:focus) { z-index: 1; }#panels ::slotted([aria-hidden=\\\\\\"true\\\\\\"]) { display: none; }\\", + \\"isStyle\\": true, + \\"id\\": 38 + } + ], + \\"id\\": 37, + \\"isShadow\\": true + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 39, + \\"isShadow\\": true + }, + { + \\"type\\": 2, + \\"tagName\\": \\"div\\", + \\"attributes\\": { + \\"id\\": \\"tabs\\" + }, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 41 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"slot\\", + \\"attributes\\": { + \\"id\\": \\"tabsSlot\\", + \\"name\\": \\"title\\" + }, + \\"childNodes\\": [], + \\"id\\": 42 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 43 + } + ], + \\"id\\": 40, + \\"isShadow\\": true + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 44, + \\"isShadow\\": true + }, + { + \\"type\\": 2, + \\"tagName\\": \\"div\\", + \\"attributes\\": { + \\"id\\": \\"panels\\" + }, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 46 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"slot\\", + \\"attributes\\": { + \\"id\\": \\"panelsSlot\\" + }, + \\"childNodes\\": [], + \\"id\\": 47 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 48 + } + ], + \\"id\\": 45, + \\"isShadow\\": true + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 49, + \\"isShadow\\": true + } + ], + \\"id\\": 16, + \\"isShadowHost\\": true + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 50 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"script\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", + \\"id\\": 52 + } + ], + \\"id\\": 51 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\\\n\\\\n\\", + \\"id\\": 53 + } + ], + \\"id\\": 14 + } + ], + \\"id\\": 3 + } + ], + \\"id\\": 1 +}" +`; diff --git a/packages/rrweb-snapshot/test/css.test.ts b/packages/rrweb-snapshot/test/css.test.ts new file mode 100644 index 0000000000..4461022beb --- /dev/null +++ b/packages/rrweb-snapshot/test/css.test.ts @@ -0,0 +1,109 @@ +import { parse, Rule, Media } from '../src/css'; + +describe('css parser', () => { + it('should save the filename and source', () => { + const css = 'booty {\n size: large;\n}\n'; + const ast = parse(css, { + source: 'booty.css', + }); + + expect(ast.stylesheet!.source).toEqual('booty.css'); + + const position = ast.stylesheet!.rules[0].position!; + expect(position.start).toBeTruthy(); + expect(position.end).toBeTruthy(); + expect(position.source).toEqual('booty.css'); + expect(position.content).toEqual(css); + }); + + it('should throw when a selector is missing', () => { + expect(() => { + parse('{size: large}'); + }).toThrow(); + + expect(() => { + parse('b { color: red; }\n{ color: green; }\na { color: blue; }'); + }).toThrow(); + }); + + it('should throw when a broken comment is found', () => { + expect(() => { + parse('thing { color: red; } /* b { color: blue; }'); + }).toThrow(); + + expect(() => { + parse('/*'); + }).toThrow(); + + /* Nested comments should be fine */ + expect(() => { + parse('/* /* */'); + }).not.toThrow(); + }); + + it('should allow empty property value', () => { + expect(() => { + parse('p { color:; }'); + }).not.toThrow(); + }); + + it('should not throw with silent option', () => { + expect(() => { + parse('thing { color: red; } /* b { color: blue; }', { silent: true }); + }).not.toThrow(); + }); + + it('should list the parsing errors and continue parsing', () => { + const result = parse( + 'foo { color= red; } bar { color: blue; } baz {}} boo { display: none}', + { + silent: true, + source: 'foo.css', + }, + ); + + const rules = result.stylesheet!.rules; + expect(rules.length).toBeGreaterThan(2); + + const errors = result.stylesheet!.parsingErrors!; + expect(errors.length).toEqual(2); + + expect(errors[0]).toHaveProperty('message'); + expect(errors[0]).toHaveProperty('reason'); + expect(errors[0]).toHaveProperty('filename'); + expect(errors[0]).toHaveProperty('line'); + expect(errors[0]).toHaveProperty('column'); + expect(errors[0]).toHaveProperty('source'); + expect(errors[0].filename).toEqual('foo.css'); + }); + + it('should set parent property', () => { + const result = parse( + 'thing { test: value; }\n' + + '@media (min-width: 100px) { thing { test: value; } }', + ); + + expect(result.parent).toEqual(null); + + const rules = result.stylesheet!.rules; + expect(rules.length).toEqual(2); + + let rule = rules[0] as Rule; + expect(rule.parent).toEqual(result); + expect(rule.declarations!.length).toEqual(1); + + let decl = rule.declarations![0]; + expect(decl.parent).toEqual(rule); + + const media = rules[1] as Media; + expect(media.parent).toEqual(result); + expect(media.rules!.length).toEqual(1); + + rule = media.rules![0] as Rule; + expect(rule.parent).toEqual(media); + + expect(rule.declarations!.length).toEqual(1); + decl = rule.declarations![0]; + expect(decl.parent).toEqual(rule); + }); +}); diff --git a/packages/rrweb-snapshot/test/css/benchmark.css b/packages/rrweb-snapshot/test/css/benchmark.css new file mode 100644 index 0000000000..ea7e0585c3 --- /dev/null +++ b/packages/rrweb-snapshot/test/css/benchmark.css @@ -0,0 +1,6 @@ +/*!----------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Version: 0.17.0(63d87164d0bc8c6206d9339c195289c93665028e) + * Released under the MIT license + * https://github.com/Microsoft/vscode/blob/master/LICENSE.txt + *-----------------------------------------------------------*/.monaco-action-bar{text-align:right;overflow:hidden;white-space:nowrap}.monaco-action-bar .actions-container{display:flex;margin:0 auto;padding:0;width:100%;justify-content:flex-end}.monaco-action-bar.vertical .actions-container{display:inline-block}.monaco-action-bar.reverse .actions-container{flex-direction:row-reverse}.monaco-action-bar .action-item{cursor:pointer;display:inline-block;transition:transform 50ms ease;position:relative}.monaco-action-bar .action-item.disabled{cursor:default}.monaco-action-bar.animated .action-item.active{transform:scale(1.272019649)}.monaco-action-bar .action-item .icon{display:inline-block}.monaco-action-bar .action-label{font-size:11px;margin-right:4px}.monaco-action-bar .action-label.octicon{font-size:15px;line-height:35px;text-align:center}.monaco-action-bar .action-item.disabled .action-label,.monaco-action-bar .action-item.disabled .action-label:hover{opacity:.4}.monaco-action-bar.vertical{text-align:left}.monaco-action-bar.vertical .action-item{display:block}.monaco-action-bar.vertical .action-label.separator{display:block;border-bottom:1px solid #bbb;padding-top:1px;margin-left:.8em;margin-right:.8em}.monaco-action-bar.animated.vertical .action-item.active{transform:translate(5px)}.secondary-actions .monaco-action-bar .action-label{margin-left:6px}.monaco-action-bar .action-item.select-container{overflow:hidden;flex:1;max-width:170px;min-width:60px;display:flex;align-items:center;justify-content:center}.monaco-aria-container{position:absolute;left:-999em}.monaco-custom-checkbox{margin-left:2px;float:left;cursor:pointer;overflow:hidden;opacity:.7;width:20px;height:20px;border:1px solid transparent;padding:1px;-o-box-sizing:border-box;-ms-box-sizing:border-box;box-sizing:border-box;-webkit-user-select:none;-moz-user-select:none;-o-user-select:none;-ms-user-select:none;user-select:none}.monaco-custom-checkbox.checked,.monaco-custom-checkbox:hover{opacity:1}.hc-black .monaco-custom-checkbox,.hc-black .monaco-custom-checkbox:hover{background:none}.context-view{position:absolute;z-index:2000}.monaco-count-badge{padding:.3em .5em;border-radius:1em;font-size:85%;min-width:1.6em;line-height:1em;font-weight:400;text-align:center;display:inline-block;box-sizing:border-box}.monaco-findInput{position:relative}.monaco-findInput .monaco-inputbox{font-size:13px;width:100%}.monaco-findInput>.controls{position:absolute;top:3px;right:2px}.vs .monaco-findInput.disabled{background-color:#e1e1e1}.vs-dark .monaco-findInput.disabled{background-color:#333}.monaco-findInput.highlight-0 .controls{animation:monaco-findInput-highlight-0 .1s linear 0s}.monaco-findInput.highlight-1 .controls{animation:monaco-findInput-highlight-1 .1s linear 0s}.hc-black .monaco-findInput.highlight-0 .controls,.vs-dark .monaco-findInput.highlight-0 .controls{animation:monaco-findInput-highlight-dark-0 .1s linear 0s}.hc-black .monaco-findInput.highlight-1 .controls,.vs-dark .monaco-findInput.highlight-1 .controls{animation:monaco-findInput-highlight-dark-1 .1s linear 0s}@keyframes monaco-findInput-highlight-0{0%{background:rgba(253,255,0,.8)}to{background:transparent}}@keyframes monaco-findInput-highlight-1{0%{background:rgba(253,255,0,.8)}99%{background:transparent}}@keyframes monaco-findInput-highlight-dark-0{0%{background:hsla(0,0%,100%,.44)}to{background:transparent}}@keyframes monaco-findInput-highlight-dark-1{0%{background:hsla(0,0%,100%,.44)}99%{background:transparent}}.vs .monaco-custom-checkbox.monaco-case-sensitive{background:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiI+PHN0eWxlIHR5cGU9InRleHQvY3NzIj4uc3Qwe29wYWNpdHk6MDtmaWxsOiNGNkY2RjY7fSAuc3Qxe2ZpbGw6I0Y2RjZGNjt9IC5zdDJ7ZmlsbDojNDI0MjQyO308L3N0eWxlPjxnIGlkPSJvdXRsaW5lIj48cmVjdCBjbGFzcz0ic3QwIiB3aWR0aD0iMTYiIGhlaWdodD0iMTYiLz48cGF0aCBjbGFzcz0ic3QxIiBkPSJNMTQuMTc2IDUuNTkyYy0uNTU1LS42LTEuMzM2LS45MDQtMi4zMjItLjkwNC0uMjU4IDAtLjUyMS4wMjQtLjc4NC4wNzItLjI0Ni4wNDQtLjQ3OS4xMDEtLjcuMTY5LS4yMjguMDctLjQzMi4xNDctLjYxMy4yMjktLjIyLjA5OS0uMzg5LjE5Ni0uNTEyLjI4NGwtLjQxOS4yOTl2Mi43MDFjLS4wODYuMTA4LS4xNjIuMjIzLS4yMjkuMzQ0bC0yLjQ1LTYuMzU0aC0yLjM5NGwtMy43NTMgOS44MDR2LjU5OGgzLjAyNWwuODM4LTIuMzVoMi4xNjdsLjg5MSAyLjM1aDMuMjM3bC0uMDAxLS4wMDNjLjMwNS4wOTIuNjMzLjE1Ljk5My4xNS4zNDQgMCAuNjcxLS4wNDkuOTc4LS4xNDZoMi44NTN2LTQuOTAzYy0uMDAxLS45NzUtLjI3MS0xLjc2My0uODA1LTIuMzR6Ii8+PC9nPjxnIGlkPSJpY29uX3g1Rl9iZyI+PHBhdGggY2xhc3M9InN0MiIgZD0iTTcuNjExIDExLjgzNGwtLjg5MS0yLjM1aC0zLjU2MmwtLjgzOCAyLjM1aC0xLjA5NWwzLjIxNy04LjQwMmgxLjAybDMuMjQgOC40MDJoLTEuMDkxem0tMi41MzEtNi44MTRsLS4wNDQtLjEzNS0uMDM4LS4xNTYtLjAyOS0uMTUyLS4wMjQtLjEyNmgtLjAyM2wtLjAyMS4xMjYtLjAzMi4xNTItLjAzOC4xNTYtLjA0NC4xMzUtMS4zMDcgMy41NzRoMi45MThsLTEuMzE4LTMuNTc0eiIvPjxwYXRoIGNsYXNzPSJzdDIiIGQ9Ik0xMy4wMiAxMS44MzR2LS45MzhoLS4wMjNjLS4xOTkuMzUyLS40NTYuNjItLjc3MS44MDZzLS42NzMuMjc4LTEuMDc1LjI3OGMtLjMxMyAwLS41ODgtLjA0NS0uODI2LS4xMzVzLS40MzgtLjIxMi0uNTk4LS4zNjYtLjI4MS0uMzM4LS4zNjMtLjU1MS0uMTI0LS40NDItLjEyNC0uNjg4YzAtLjI2Mi4wMzktLjUwMi4xMTctLjcyMXMuMTk4LS40MTIuMzYtLjU4LjM2Ny0uMzA4LjYxNS0uNDE5LjU0NC0uMTkuODg4LS4yMzdsMS44MTEtLjI1MmMwLS4yNzMtLjAyOS0uNTA3LS4wODgtLjdzLS4xNDMtLjM1MS0uMjUyLS40NzItLjI0MS0uMjEtLjM5Ni0uMjY3LS4zMjUtLjA4NS0uNTEzLS4wODVjLS4zNjMgMC0uNzE0LjA2NC0xLjA1Mi4xOTNzLS42MzguMzEtLjkwNC41NHYtLjk4NGMuMDgyLS4wNTkuMTk2LS4xMjEuMzQzLS4xODhzLjMxMi0uMTI4LjQ5NS0uMTg1LjM3OC0uMTA0LjU4My0uMTQxLjQwNy0uMDU2LjYwNi0uMDU2Yy42OTkgMCAxLjIyOS4xOTQgMS41ODguNTgzcy41MzkuOTQyLjUzOSAxLjY2MXYzLjkwMmgtLjk2em0tMS40NTQtMi44M2MtLjI3My4wMzUtLjQ5OC4wODUtLjY3NC4xNDlzLS4zMTMuMTQ0LS40MS4yMzctLjE2NS4yMDUtLjIwMi4zMzQtLjA1NS4yNzYtLjA1NS40NGMwIC4xNDEuMDI1LjI3MS4wNzYuMzkzcy4xMjQuMjI3LjIyLjMxNi4yMTUuMTYuMzU3LjIxMS4zMDguMDc2LjQ5NS4wNzZjLjI0MiAwIC40NjUtLjA0NS42NjgtLjEzNXMuMzc4LS4yMTQuNTI0LS4zNzIuMjYxLS4zNDQuMzQzLS41NTcuMTIzLS40NDIuMTIzLS42ODh2LS42MDlsLTEuNDY1LjIwNXoiLz48L2c+PC9zdmc+) 50% no-repeat}.hc-black .monaco-custom-checkbox.monaco-case-sensitive,.hc-black .monaco-custom-checkbox.monaco-case-sensitive:hover,.vs-dark .monaco-custom-checkbox.monaco-case-sensitive{background:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiI+PHN0eWxlIHR5cGU9InRleHQvY3NzIj4uc3Qwe29wYWNpdHk6MDtmaWxsOiMyNjI2MjY7fSAuc3Qxe2ZpbGw6IzI2MjYyNjt9IC5zdDJ7ZmlsbDojQzVDNUM1O308L3N0eWxlPjxnIGlkPSJvdXRsaW5lIj48cmVjdCBjbGFzcz0ic3QwIiB3aWR0aD0iMTYiIGhlaWdodD0iMTYiLz48cGF0aCBjbGFzcz0ic3QxIiBkPSJNMTQuMTc2IDUuNTkyYy0uNTU1LS42LTEuMzM2LS45MDQtMi4zMjItLjkwNC0uMjU4IDAtLjUyMS4wMjQtLjc4NC4wNzItLjI0Ni4wNDQtLjQ3OS4xMDEtLjcuMTY5LS4yMjguMDctLjQzMi4xNDctLjYxMy4yMjktLjIyLjA5OS0uMzg5LjE5Ni0uNTEyLjI4NGwtLjQxOS4yOTl2Mi43MDFjLS4wODYuMTA4LS4xNjIuMjIzLS4yMjkuMzQ0bC0yLjQ1LTYuMzU0aC0yLjM5NGwtMy43NTMgOS44MDR2LjU5OGgzLjAyNWwuODM4LTIuMzVoMi4xNjdsLjg5MSAyLjM1aDMuMjM3bC0uMDAxLS4wMDNjLjMwNS4wOTIuNjMzLjE1Ljk5My4xNS4zNDQgMCAuNjcxLS4wNDkuOTc4LS4xNDZoMi44NTN2LTQuOTAzYy0uMDAxLS45NzUtLjI3MS0xLjc2My0uODA1LTIuMzR6Ii8+PC9nPjxnIGlkPSJpY29uX3g1Rl9iZyI+PHBhdGggY2xhc3M9InN0MiIgZD0iTTcuNjExIDExLjgzNGwtLjg5MS0yLjM1aC0zLjU2MmwtLjgzOCAyLjM1aC0xLjA5NWwzLjIxNy04LjQwMmgxLjAybDMuMjQgOC40MDJoLTEuMDkxem0tMi41MzEtNi44MTRsLS4wNDQtLjEzNS0uMDM4LS4xNTYtLjAyOS0uMTUyLS4wMjQtLjEyNmgtLjAyM2wtLjAyMS4xMjYtLjAzMi4xNTItLjAzOC4xNTYtLjA0NC4xMzUtMS4zMDcgMy41NzRoMi45MThsLTEuMzE4LTMuNTc0eiIvPjxwYXRoIGNsYXNzPSJzdDIiIGQ9Ik0xMy4wMiAxMS44MzR2LS45MzhoLS4wMjNjLS4xOTkuMzUyLS40NTYuNjItLjc3MS44MDZzLS42NzMuMjc4LTEuMDc1LjI3OGMtLjMxMyAwLS41ODgtLjA0NS0uODI2LS4xMzVzLS40MzgtLjIxMi0uNTk4LS4zNjYtLjI4MS0uMzM4LS4zNjMtLjU1MS0uMTI0LS40NDItLjEyNC0uNjg4YzAtLjI2Mi4wMzktLjUwMi4xMTctLjcyMXMuMTk4LS40MTIuMzYtLjU4LjM2Ny0uMzA4LjYxNS0uNDE5LjU0NC0uMTkuODg4LS4yMzdsMS44MTEtLjI1MmMwLS4yNzMtLjAyOS0uNTA3LS4wODgtLjdzLS4xNDMtLjM1MS0uMjUyLS40NzItLjI0MS0uMjEtLjM5Ni0uMjY3LS4zMjUtLjA4NS0uNTEzLS4wODVjLS4zNjMgMC0uNzE0LjA2NC0xLjA1Mi4xOTNzLS42MzguMzEtLjkwNC41NHYtLjk4NGMuMDgyLS4wNTkuMTk2LS4xMjEuMzQzLS4xODhzLjMxMi0uMTI4LjQ5NS0uMTg1LjM3OC0uMTA0LjU4My0uMTQxLjQwNy0uMDU2LjYwNi0uMDU2Yy42OTkgMCAxLjIyOS4xOTQgMS41ODguNTgzcy41MzkuOTQyLjUzOSAxLjY2MXYzLjkwMmgtLjk2em0tMS40NTQtMi44M2MtLjI3My4wMzUtLjQ5OC4wODUtLjY3NC4xNDlzLS4zMTMuMTQ0LS40MS4yMzctLjE2NS4yMDUtLjIwMi4zMzQtLjA1NS4yNzYtLjA1NS40NGMwIC4xNDEuMDI1LjI3MS4wNzYuMzkzcy4xMjQuMjI3LjIyLjMxNi4yMTUuMTYuMzU3LjIxMS4zMDguMDc2LjQ5NS4wNzZjLjI0MiAwIC40NjUtLjA0NS42NjgtLjEzNXMuMzc4LS4yMTQuNTI0LS4zNzIuMjYxLS4zNDQuMzQzLS41NTcuMTIzLS40NDIuMTIzLS42ODh2LS42MDlsLTEuNDY1LjIwNXoiLz48L2c+PC9zdmc+) 50% no-repeat}.vs .monaco-custom-checkbox.monaco-whole-word{background:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiI+PHN0eWxlIHR5cGU9InRleHQvY3NzIj4uc3Qwe29wYWNpdHk6MDtmaWxsOiNGNkY2RjY7fSAuc3Qxe2ZpbGw6I0Y2RjZGNjt9IC5zdDJ7ZmlsbDojNDI0MjQyO308L3N0eWxlPjxnIGlkPSJvdXRsaW5lIj48cmVjdCBjbGFzcz0ic3QwIiB3aWR0aD0iMTYiIGhlaWdodD0iMTYiLz48cGF0aCBjbGFzcz0ic3QxIiBkPSJNMTYgNC4wMjJ2LTMuMDIyaC0xNi4wMTR2My4wMjJoMy4wNDZsLTMuMDQzIDcuOTQ1aC0uMDA0di4wMWwuMDE1IDEuMDIzaC0uMDE0djEuOTkxaDE2LjAxNHYtMy4wMjNoLTF2LTcuOTQ2aDF6bS01LjkxNCA1LjMwMWMwIC4yMzMtLjAyMy40NDEtLjA2Ni41OTUtLjA0Ny4xNjQtLjA5OS4yNDctLjEyNy4yODRsLS4wNzguMDY5LS4xNTEuMDI2LS4xMTUtLjAxNy0uMTM5LS4xMzdjLS4wMzEtLjA3OC0uMTEyLS4zMzItLjExMi0uNTY2IDAtLjI1NC4wOTEtLjU2MS4xMjYtLjY1NmwuMDY5LS4xNDEuMTA5LS4wODIuMTc4LS4wMjdjLjA3NyAwIC4xMTcuMDE0LjE3Ny4wNTZsLjA4Ny4xNzkuMDUxLjIzNy0uMDA5LjE4em0tMy42OTUtNS4zMDF2Mi44OTNsLTEuMTE2LTIuODkzaDEuMTE2em0tMy4wMjYgNy4wMmgxLjU3M2wuMzUxLjkyNmgtMi4yNTRsLjMzLS45MjZ6bTguNjM1LTQuMzU0Yy0uMjA2LS4yLS40MzEtLjM4LS42OTUtLjUxMi0uMzk2LS4xOTgtLjg1My0uMjk4LTEuMzU1LS4yOTgtLjIxNSAwLS40MjMuMDItLjYyMS4wNTh2LTEuOTE0aDIuNjcxdjIuNjY2eiIvPjwvZz48ZyBpZD0iaWNvbl94NUZfYmciPjxyZWN0IHg9IjEzIiB5PSI0IiBjbGFzcz0ic3QyIiB3aWR0aD0iMSIgaGVpZ2h0PSI4Ii8+PHBhdGggY2xhc3M9InN0MiIgZD0iTTExLjIyNSA4LjM4N2MtLjA3OC0uMjk5LS4xOTktLjU2Mi0uMzYtLjc4NnMtLjM2NS0uNDAxLS42MDktLjUzLS41MzQtLjE5My0uODY2LS4xOTNjLS4xOTggMC0uMzguMDI0LS41NDcuMDczLS4xNjUuMDQ5LS4zMTYuMTE3LS40NTMuMjA1LS4xMzYuMDg4LS4yNTcuMTk0LS4zNjUuMzE4bC0uMTc5LjI1OHYtMy4xNTRoLS44OTN2Ny40MjJoLjg5M3YtLjU3NWwuMTI2LjE3NWMuMDg3LjEwMi4xODkuMTkuMzA0LjI2OS4xMTcuMDc4LjI0OS4xNC4zOTguMTg2LjE0OS4wNDYuMzE0LjA2OC40OTguMDY4LjM1MyAwIC42NjYtLjA3MS45MzctLjIxMi4yNzItLjE0My40OTktLjMzOC42ODItLjU4Ni4xODMtLjI1LjMyMS0uNTQzLjQxNC0uODc5LjA5My0uMzM4LjE0LS43MDMuMTQtMS4wOTctLjAwMS0uMzQyLS4wNC0uNjYzLS4xMi0uOTYyem0tMS40NzktLjYwN2MuMTUxLjA3MS4yODIuMTc2LjM5LjMxNC4xMDkuMTQuMTk0LjMxMy4yNTUuNTE3LjA1MS4xNzQuMDgyLjM3MS4wODkuNTg3bC0uMDA3LjEyNWMwIC4zMjctLjAzMy42Mi0uMS44NjktLjA2Ny4yNDYtLjE2MS40NTMtLjI3OC42MTQtLjExNy4xNjItLjI2LjI4NS0uNDIxLjM2Ni0uMzIyLjE2Mi0uNzYuMTY2LTEuMDY5LjAxNS0uMTUzLS4wNzUtLjI4Ni0uMTc1LS4zOTMtLjI5Ni0uMDg1LS4wOTYtLjE1Ni0uMjE2LS4yMTgtLjM2NyAwIDAtLjE3OS0uNDQ3LS4xNzktLjk0NyAwLS41LjE3OS0xLjAwMi4xNzktMS4wMDIuMDYyLS4xNzcuMTM2LS4zMTguMjI0LS40My4xMTQtLjE0My4yNTYtLjI1OS40MjQtLjM0NS4xNjgtLjA4Ni4zNjUtLjEyOS41ODctLjEyOS4xOSAwIC4zNjQuMDM3LjUxNy4xMDl6Ii8+PHJlY3QgeD0iLjk4NyIgeT0iMiIgY2xhc3M9InN0MiIgd2lkdGg9IjE0LjAxMyIgaGVpZ2h0PSIxLjAyMyIvPjxyZWN0IHg9Ii45ODciIHk9IjEyLjk2OCIgY2xhc3M9InN0MiIgd2lkdGg9IjE0LjAxMyIgaGVpZ2h0PSIxLjAyMyIvPjxwYXRoIGNsYXNzPSJzdDIiIGQ9Ik0xLjk5MSAxMi4wMzFsLjcyOC0yLjAzMWgyLjIxOWwuNzc4IDIuMDMxaDEuMDgybC0yLjQ4NS03LjE1OGgtLjk0MWwtMi40NDEgNy4wODYtLjAyNS4wNzJoMS4wODV6bTEuODI3LTUuNjA5aC4wMjJsLjkxNCAyLjc1M2gtMS44NDFsLjkwNS0yLjc1M3oiLz48L2c+PC9zdmc+) 50% no-repeat}.hc-black .monaco-custom-checkbox.monaco-whole-word,.hc-black .monaco-custom-checkbox.monaco-whole-word:hover,.vs-dark .monaco-custom-checkbox.monaco-whole-word{background:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiI+PHN0eWxlIHR5cGU9InRleHQvY3NzIj4uc3Qwe29wYWNpdHk6MDtmaWxsOiMyNjI2MjY7fSAuc3Qxe2ZpbGw6IzI2MjYyNjt9IC5zdDJ7ZmlsbDojQzVDNUM1O308L3N0eWxlPjxnIGlkPSJvdXRsaW5lIj48cmVjdCBjbGFzcz0ic3QwIiB3aWR0aD0iMTYiIGhlaWdodD0iMTYiLz48cGF0aCBjbGFzcz0ic3QxIiBkPSJNMTYgNC4wMjJ2LTMuMDIyaC0xNi4wMTR2My4wMjJoMy4wNDZsLTMuMDQzIDcuOTQ1aC0uMDA0di4wMWwuMDE1IDEuMDIzaC0uMDE0djEuOTkxaDE2LjAxNHYtMy4wMjNoLTF2LTcuOTQ2aDF6bS01LjkxNCA1LjMwMWMwIC4yMzMtLjAyMy40NDEtLjA2Ni41OTUtLjA0Ny4xNjQtLjA5OS4yNDctLjEyNy4yODRsLS4wNzguMDY5LS4xNTEuMDI2LS4xMTUtLjAxNy0uMTM5LS4xMzdjLS4wMzEtLjA3OC0uMTEyLS4zMzItLjExMi0uNTY2IDAtLjI1NC4wOTEtLjU2MS4xMjYtLjY1NmwuMDY5LS4xNDEuMTA5LS4wODIuMTc4LS4wMjdjLjA3NyAwIC4xMTcuMDE0LjE3Ny4wNTZsLjA4Ny4xNzkuMDUxLjIzNy0uMDA5LjE4em0tMy42OTUtNS4zMDF2Mi44OTNsLTEuMTE2LTIuODkzaDEuMTE2em0tMy4wMjYgNy4wMmgxLjU3M2wuMzUxLjkyNmgtMi4yNTRsLjMzLS45MjZ6bTguNjM1LTQuMzU0Yy0uMjA2LS4yLS40MzEtLjM4LS42OTUtLjUxMi0uMzk2LS4xOTgtLjg1My0uMjk4LTEuMzU1LS4yOTgtLjIxNSAwLS40MjMuMDItLjYyMS4wNTh2LTEuOTE0aDIuNjcxdjIuNjY2eiIvPjwvZz48ZyBpZD0iaWNvbl94NUZfYmciPjxyZWN0IHg9IjEzIiB5PSI0IiBjbGFzcz0ic3QyIiB3aWR0aD0iMSIgaGVpZ2h0PSI4Ii8+PHBhdGggY2xhc3M9InN0MiIgZD0iTTExLjIyNSA4LjM4N2MtLjA3OC0uMjk5LS4xOTktLjU2Mi0uMzYtLjc4NnMtLjM2NS0uNDAxLS42MDktLjUzLS41MzQtLjE5My0uODY2LS4xOTNjLS4xOTggMC0uMzguMDI0LS41NDcuMDczLS4xNjUuMDQ5LS4zMTYuMTE3LS40NTMuMjA1LS4xMzYuMDg4LS4yNTcuMTk0LS4zNjUuMzE4bC0uMTc5LjI1OHYtMy4xNTRoLS44OTN2Ny40MjJoLjg5M3YtLjU3NWwuMTI2LjE3NWMuMDg3LjEwMi4xODkuMTkuMzA0LjI2OS4xMTcuMDc4LjI0OS4xNC4zOTguMTg2LjE0OS4wNDYuMzE0LjA2OC40OTguMDY4LjM1MyAwIC42NjYtLjA3MS45MzctLjIxMi4yNzItLjE0My40OTktLjMzOC42ODItLjU4Ni4xODMtLjI1LjMyMS0uNTQzLjQxNC0uODc5LjA5My0uMzM4LjE0LS43MDMuMTQtMS4wOTctLjAwMS0uMzQyLS4wNC0uNjYzLS4xMi0uOTYyem0tMS40NzktLjYwN2MuMTUxLjA3MS4yODIuMTc2LjM5LjMxNC4xMDkuMTQuMTk0LjMxMy4yNTUuNTE3LjA1MS4xNzQuMDgyLjM3MS4wODkuNTg3bC0uMDA3LjEyNWMwIC4zMjctLjAzMy42Mi0uMS44NjktLjA2Ny4yNDYtLjE2MS40NTMtLjI3OC42MTQtLjExNy4xNjItLjI2LjI4NS0uNDIxLjM2Ni0uMzIyLjE2Mi0uNzYuMTY2LTEuMDY5LjAxNS0uMTUzLS4wNzUtLjI4Ni0uMTc1LS4zOTMtLjI5Ni0uMDg1LS4wOTYtLjE1Ni0uMjE2LS4yMTgtLjM2NyAwIDAtLjE3OS0uNDQ3LS4xNzktLjk0NyAwLS41LjE3OS0xLjAwMi4xNzktMS4wMDIuMDYyLS4xNzcuMTM2LS4zMTguMjI0LS40My4xMTQtLjE0My4yNTYtLjI1OS40MjQtLjM0NS4xNjgtLjA4Ni4zNjUtLjEyOS41ODctLjEyOS4xOSAwIC4zNjQuMDM3LjUxNy4xMDl6Ii8+PHJlY3QgeD0iLjk4NyIgeT0iMiIgY2xhc3M9InN0MiIgd2lkdGg9IjE0LjAxMyIgaGVpZ2h0PSIxLjAyMyIvPjxyZWN0IHg9Ii45ODciIHk9IjEyLjk2OCIgY2xhc3M9InN0MiIgd2lkdGg9IjE0LjAxMyIgaGVpZ2h0PSIxLjAyMyIvPjxwYXRoIGNsYXNzPSJzdDIiIGQ9Ik0xLjk5MSAxMi4wMzFsLjcyOC0yLjAzMWgyLjIxOWwuNzc4IDIuMDMxaDEuMDgybC0yLjQ4NS03LjE1OGgtLjk0MWwtMi40NDEgNy4wODYtLjAyNS4wNzJoMS4wODV6bTEuODI3LTUuNjA5aC4wMjJsLjkxNCAyLjc1M2gtMS44NDFsLjkwNS0yLjc1M3oiLz48L2c+PC9zdmc+) 50% no-repeat}.vs .monaco-custom-checkbox.monaco-regex{background:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiI+PHBvbHlnb24gZmlsbD0iI0Y2RjZGNiIgcG9pbnRzPSIxMy42NCw3LjM5NiAxMi4xNjksMi44OTggMTAuNzA2LDMuNzYxIDExLjA4NywyIDYuNTU3LDIgNi45MzYsMy43NjIgNS40NzMsMi44OTggNCw3LjM5NiA1LjY4Miw3LjU1NCA0LjUxMyw4LjU2MSA1LjAxMyw5IDIsOSAyLDE0IDcsMTQgNywxMC43NDcgNy45NzgsMTEuNjA2IDguODIsOS43MjUgOS42NjEsMTEuNjAyIDEzLjE0NCw4LjU2MiAxMS45NjgsNy41NTQiLz48ZyBmaWxsPSIjNDI0MjQyIj48cGF0aCBkPSJNMTIuMzAxIDYuNTE4bC0yLjc3Mi4yNjIgMi4wODYgMS43ODgtMS41OTQgMS4zOTItMS4yMDEtMi42ODItMS4yMDEgMi42ODItMS41ODMtMS4zOTIgMi4wNzUtMS43ODgtMi43NzEtLjI2Mi42OTYtMi4xMjYgMi4zNTggMS4zOTItLjU5OS0yLjc4NGgyLjA1M2wtLjYwMiAyLjc4MyAyLjM1OS0xLjM5Mi42OTYgMi4xMjd6Ii8+PHJlY3QgeD0iMyIgeT0iMTAiIHdpZHRoPSIzIiBoZWlnaHQ9IjMiLz48L2c+PC9zdmc+) 50% no-repeat}.hc-black .monaco-custom-checkbox.monaco-regex,.hc-black .monaco-custom-checkbox.monaco-regex:hover,.vs-dark .monaco-custom-checkbox.monaco-regex{background:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiI+PHBvbHlnb24gZmlsbD0iIzJkMmQzMCIgcG9pbnRzPSIxMy42NCw3LjM5NiAxMi4xNjksMi44OTggMTAuNzA2LDMuNzYxIDExLjA4NywyIDYuNTU3LDIgNi45MzYsMy43NjIgNS40NzMsMi44OTggNCw3LjM5NiA1LjY4Miw3LjU1NCA0LjUxMyw4LjU2MSA1LjAxMyw5IDIsOSAyLDE0IDcsMTQgNywxMC43NDcgNy45NzgsMTEuNjA2IDguODIsOS43MjUgOS42NjEsMTEuNjAyIDEzLjE0NCw4LjU2MiAxMS45NjgsNy41NTQiLz48ZyBmaWxsPSIjQzVDNUM1Ij48cGF0aCBkPSJNMTIuMzAxIDYuNTE4bC0yLjc3Mi4yNjIgMi4wODYgMS43ODgtMS41OTQgMS4zOTItMS4yMDEtMi42ODItMS4yMDEgMi42ODItMS41ODMtMS4zOTIgMi4wNzUtMS43ODgtMi43NzEtLjI2Mi42OTYtMi4xMjYgMi4zNTggMS4zOTItLjU5OS0yLjc4NGgyLjA1M2wtLjYwMiAyLjc4MyAyLjM1OS0xLjM5Mi42OTYgMi4xMjd6Ii8+PHJlY3QgeD0iMyIgeT0iMTAiIHdpZHRoPSIzIiBoZWlnaHQ9IjMiLz48L2c+PC9zdmc+) 50% no-repeat}.monaco-icon-label{display:flex;overflow:hidden;text-overflow:ellipsis}.monaco-icon-label:before{background-size:16px;background-position:0;background-repeat:no-repeat;padding-right:6px;width:16px;height:22px;display:inline-block;-webkit-font-smoothing:antialiased;vertical-align:top;flex-shrink:0}.monaco-icon-label>.monaco-icon-label-description-container{overflow:hidden;text-overflow:ellipsis}.monaco-icon-label>.monaco-icon-label-description-container>.label-name{color:inherit;white-space:pre}.monaco-icon-label>.monaco-icon-label-description-container>.label-description{opacity:.7;margin-left:.5em;font-size:.9em;white-space:pre}.monaco-icon-label.italic>.monaco-icon-label-description-container>.label-description,.monaco-icon-label.italic>.monaco-icon-label-description-container>.label-name{font-style:italic}.monaco-icon-label:after{opacity:.75;font-size:90%;font-weight:600;padding:0 12px 0 5px;margin-left:auto;text-align:center}.monaco-list:focus .selected .monaco-icon-label,.monaco-list:focus .selected .monaco-icon-label:after,.monaco-tree.focused .selected .monaco-icon-label,.monaco-tree.focused .selected .monaco-icon-label:after{color:inherit!important}.monaco-list-row.focused.selected .label-description,.monaco-list-row.selected .label-description,.monaco-tree-row.focused.selected .label-description,.monaco-tree-row.selected .label-description{opacity:.8}.monaco-inputbox{position:relative;display:block;padding:0;-o-box-sizing:border-box;-ms-box-sizing:border-box;box-sizing:border-box;line-height:auto!important;font-size:inherit}.monaco-inputbox.idle{border:1px solid transparent}.monaco-inputbox>.wrapper>.input,.monaco-inputbox>.wrapper>.mirror{padding:4px}.monaco-inputbox>.wrapper{position:relative;width:100%;height:100%}.monaco-inputbox>.wrapper>.input{display:inline-block;-o-box-sizing:border-box;-ms-box-sizing:border-box;box-sizing:border-box;width:100%;height:100%;line-height:inherit;border:none;font-family:inherit;font-size:inherit;resize:none;color:inherit}.monaco-inputbox>.wrapper>input{text-overflow:ellipsis}.monaco-inputbox>.wrapper>textarea.input{display:block;overflow:hidden}.monaco-inputbox>.wrapper>.mirror{position:absolute;display:inline-block;width:100%;top:0;left:0;-o-box-sizing:border-box;-ms-box-sizing:border-box;box-sizing:border-box;white-space:pre-wrap;visibility:hidden;word-wrap:break-word}.monaco-inputbox-container{text-align:right}.monaco-inputbox-container .monaco-inputbox-message{display:inline-block;overflow:hidden;text-align:left;width:100%;-o-box-sizing:border-box;-ms-box-sizing:border-box;box-sizing:border-box;padding:.4em;font-size:12px;line-height:17px;min-height:34px;margin-top:-1px;word-wrap:break-word}.monaco-inputbox .monaco-action-bar{position:absolute;right:2px;top:4px}.monaco-inputbox .monaco-action-bar .action-item{margin-left:2px}.monaco-inputbox .monaco-action-bar .action-item .icon{background-repeat:no-repeat;width:16px;height:16px}.monaco-keybinding{display:flex;align-items:center;line-height:10px}.monaco-keybinding>.monaco-keybinding-key{display:inline-block;border:1px solid hsla(0,0%,80%,.4);border-bottom-color:hsla(0,0%,73%,.4);border-radius:3px;box-shadow:inset 0 -1px 0 hsla(0,0%,73%,.4);background-color:hsla(0,0%,87%,.4);vertical-align:middle;color:#555;font-size:11px;padding:3px 5px;margin:0 2px}.monaco-keybinding>.monaco-keybinding-key:first-child{margin-left:0}.monaco-keybinding>.monaco-keybinding-key:last-child{margin-right:0}.hc-black .monaco-keybinding>.monaco-keybinding-key,.vs-dark .monaco-keybinding>.monaco-keybinding-key{background-color:hsla(0,0%,50%,.17);color:#ccc;border:1px solid rgba(51,51,51,.6);border-bottom-color:rgba(68,68,68,.6);box-shadow:inset 0 -1px 0 rgba(68,68,68,.6)}.monaco-keybinding>.monaco-keybinding-key-separator{display:inline-block}.monaco-keybinding>.monaco-keybinding-key-chord-separator{width:6px}.monaco-list{position:relative;height:100%;width:100%;white-space:nowrap}.monaco-list.mouse-support{-webkit-user-select:none;-moz-user-select:-moz-none;-ms-user-select:none;-o-user-select:none;user-select:none}.monaco-list>.monaco-scrollable-element{height:100%}.monaco-list-rows{position:relative;width:100%;height:100%}.monaco-list.horizontal-scrolling .monaco-list-rows{width:auto;min-width:100%}.monaco-list-row{position:absolute;-o-box-sizing:border-box;-ms-box-sizing:border-box;box-sizing:border-box;overflow:hidden;width:100%}.monaco-list.mouse-support .monaco-list-row{cursor:pointer;touch-action:none}.monaco-list-row.scrolling{display:none!important}.monaco-list.element-focused,.monaco-list.selection-multiple,.monaco-list.selection-single{outline:0!important}.monaco-drag-image{display:inline-block;padding:1px 7px;border-radius:10px;font-size:12px;position:absolute}.monaco-list-type-filter{display:flex;align-items:center;position:absolute;border-radius:2px;padding:0 3px;max-width:calc(100% - 10px);text-overflow:ellipsis;overflow:hidden;text-align:right;box-sizing:border-box;cursor:all-scroll;font-size:13px;line-height:18px;height:20px;z-index:1;top:4px}.monaco-list-type-filter.dragging{transition:top .2s,left .2s}.monaco-list-type-filter.ne{right:4px}.monaco-list-type-filter.nw{left:4px}.monaco-list-type-filter>.controls{display:flex;align-items:center;box-sizing:border-box;transition:width .2s;width:0}.monaco-list-type-filter.dragging>.controls,.monaco-list-type-filter:hover>.controls{width:36px}.monaco-list-type-filter>.controls>*{box-sizing:border-box;width:16px;height:16px;margin:0 0 0 2px;flex-shrink:0}.monaco-list-type-filter>.controls>.filter{-webkit-appearance:none;width:16px;height:16px;background:url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTYgOS45OTk1MUgyVjExLjk5OTVINlY5Ljk5OTUxWiIgZmlsbD0iIzRCNEI0QiIvPgo8cGF0aCBkPSJNMTAgN0gyVjlIMTBWN1oiIGZpbGw9IiM0QjRCNEIiLz4KPHBhdGggZD0iTTE0IDRIMlY2SDE0VjRaIiBmaWxsPSIjNEI0QjRCIi8+Cjwvc3ZnPgo=);background-position:50% 50%;cursor:pointer}.monaco-list-type-filter>.controls>.filter:checked{background-image:url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTEwLjAwMDIgMTBINi4wMDAyNFYxMkgxMC4wMDAyVjEwWiIgZmlsbD0iIzQyNDI0MiIvPgo8cGF0aCBkPSJNMTEuOTk5OCA3SDMuOTk5NzZWOUgxMS45OTk4VjdaIiBmaWxsPSIjNDI0MjQyIi8+CjxwYXRoIGQ9Ik0xNCA0SDJWNkgxNFY0WiIgZmlsbD0iIzQyNDI0MiIvPgo8L3N2Zz4K)}.vs-dark .monaco-list-type-filter>.controls>.filter{background-image:url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTYgOS45OTk1MUgyVjExLjk5OTVINlY5Ljk5OTUxWiIgZmlsbD0iI0U4RThFOCIvPgo8cGF0aCBkPSJNMTAgN0gyVjlIMTBWN1oiIGZpbGw9IiNFOEU4RTgiLz4KPHBhdGggZD0iTTE0IDRIMlY2SDE0VjRaIiBmaWxsPSIjRThFOEU4Ii8+Cjwvc3ZnPgo=)}.vs-dark .monaco-list-type-filter>.controls>.filter:checked{background-image:url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTEwLjAwMDIgMTBINi4wMDAyNFYxMkgxMC4wMDAyVjEwWiIgZmlsbD0iI0U4RThFOCIvPgo8cGF0aCBkPSJNMTEuOTk5OCA3SDMuOTk5NzZWOUgxMS45OTk4VjdaIiBmaWxsPSIjRThFOEU4Ii8+CjxwYXRoIGQ9Ik0xNCA0SDJWNkgxNFY0WiIgZmlsbD0iI0U4RThFOCIvPgo8L3N2Zz4K)}.hc-black .monaco-list-type-filter>.controls>.filter{background-image:url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTYgOS45OTk1MUgyVjExLjk5OTVINlY5Ljk5OTUxWiIgZmlsbD0id2hpdGUiLz4KPHBhdGggZD0iTTEwIDdIMlY5SDEwVjdaIiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBkPSJNMTQgNEgyVjZIMTRWNFoiIGZpbGw9IndoaXRlIi8+Cjwvc3ZnPgo=)}.hc-black .monaco-list-type-filter>.controls>.filter:checked{background-image:url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTEwLjAwMDIgMTBINi4wMDAyNFYxMkgxMC4wMDAyVjEwWiIgZmlsbD0id2hpdGUiLz4KPHBhdGggZD0iTTExLjk5OTggN0gzLjk5OTc2VjlIMTEuOTk5OFY3WiIgZmlsbD0id2hpdGUiLz4KPHBhdGggZD0iTTE0IDRIMlY2SDE0VjRaIiBmaWxsPSJ3aGl0ZSIvPgo8L3N2Zz4K)}.monaco-list-type-filter>.controls>.clear{border:none;background:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgdmlld0JveD0iMyAzIDE2IDE2IiBlbmFibGUtYmFja2dyb3VuZD0ibmV3IDMgMyAxNiAxNiI+PHBvbHlnb24gZmlsbD0iIzQyNDI0MiIgcG9pbnRzPSIxMi41OTcsMTEuMDQyIDE1LjQsMTMuODQ1IDEzLjg0NCwxNS40IDExLjA0MiwxMi41OTggOC4yMzksMTUuNCA2LjY4MywxMy44NDUgOS40ODUsMTEuMDQyIDYuNjgzLDguMjM5IDguMjM4LDYuNjgzIDExLjA0Miw5LjQ4NiAxMy44NDUsNi42ODMgMTUuNCw4LjIzOSIvPjwvc3ZnPg==);cursor:pointer}.vs-dark .monaco-list-type-filter>.controls>.clear{background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgdmlld0JveD0iMyAzIDE2IDE2IiBlbmFibGUtYmFja2dyb3VuZD0ibmV3IDMgMyAxNiAxNiI+PHBvbHlnb24gZmlsbD0iI2U4ZThlOCIgcG9pbnRzPSIxMi41OTcsMTEuMDQyIDE1LjQsMTMuODQ1IDEzLjg0NCwxNS40IDExLjA0MiwxMi41OTggOC4yMzksMTUuNCA2LjY4MywxMy44NDUgOS40ODUsMTEuMDQyIDYuNjgzLDguMjM5IDguMjM4LDYuNjgzIDExLjA0Miw5LjQ4NiAxMy44NDUsNi42ODMgMTUuNCw4LjIzOSIvPjwvc3ZnPg==)}.hc-black .monaco-list-type-filter>.controls>.clear{background-image:url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcKICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIgogICB4bWxuczpjYz0iaHR0cDovL2NyZWF0aXZlY29tbW9ucy5vcmcvbnMjIgogICB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiCiAgIHhtbG5zOnN2Zz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICAgaWQ9InN2ZzczMjAiCiAgIHZlcnNpb249IjEuMSIKICAgZW5hYmxlLWJhY2tncm91bmQ9Im5ldyAzIDMgMTYgMTYiCiAgIHZpZXdCb3g9IjMgMyAxNiAxNiIKICAgaGVpZ2h0PSIxNiIKICAgd2lkdGg9IjE2Ij4KICA8bWV0YWRhdGEKICAgICBpZD0ibWV0YWRhdGE3MzI2Ij4KICAgIDxyZGY6UkRGPgogICAgICA8Y2M6V29yawogICAgICAgICByZGY6YWJvdXQ9IiI+CiAgICAgICAgPGRjOmZvcm1hdD5pbWFnZS9zdmcreG1sPC9kYzpmb3JtYXQ+CiAgICAgICAgPGRjOnR5cGUKICAgICAgICAgICByZGY6cmVzb3VyY2U9Imh0dHA6Ly9wdXJsLm9yZy9kYy9kY21pdHlwZS9TdGlsbEltYWdlIiAvPgogICAgICAgIDxkYzp0aXRsZT48L2RjOnRpdGxlPgogICAgICA8L2NjOldvcms+CiAgICA8L3JkZjpSREY+CiAgPC9tZXRhZGF0YT4KICA8ZGVmcwogICAgIGlkPSJkZWZzNzMyNCIgLz4KICA8cG9seWdvbgogICAgIHN0eWxlPSJmaWxsOiNmZmZmZmY7ZmlsbC1vcGFjaXR5OjEiCiAgICAgaWQ9InBvbHlnb243MzE4IgogICAgIHBvaW50cz0iMTIuNTk3LDExLjA0MiAxNS40LDEzLjg0NSAxMy44NDQsMTUuNCAxMS4wNDIsMTIuNTk4IDguMjM5LDE1LjQgNi42ODMsMTMuODQ1IDkuNDg1LDExLjA0MiA2LjY4Myw4LjIzOSA4LjIzOCw2LjY4MyAxMS4wNDIsOS40ODYgMTMuODQ1LDYuNjgzIDE1LjQsOC4yMzkiCiAgICAgZmlsbD0iIzQyNDI0MiIgLz4KPC9zdmc+Cg==)}.monaco-list-type-filter-message{position:absolute;box-sizing:border-box;width:100%;height:100%;top:0;left:0;padding:40px 1em 1em;text-align:center;white-space:normal;opacity:.7;pointer-events:none}.monaco-list-type-filter-message:empty{display:none}.monaco-list-type-filter{cursor:-webkit-grab}.monaco-list-type-filter.dragging{cursor:-webkit-grabbing}.monaco-menu .monaco-action-bar.vertical{margin-left:0;overflow:visible}.monaco-menu .monaco-action-bar.vertical .actions-container{display:block}.monaco-menu .monaco-action-bar.vertical .action-item{padding:0;transform:none;display:-ms-flexbox;display:flex}.monaco-menu .monaco-action-bar.vertical .action-item.active{transform:none}.monaco-menu .monaco-action-bar.vertical .action-menu-item{-ms-flex:1 1 auto;flex:1 1 auto;display:-ms-flexbox;display:flex;height:2em;align-items:center;position:relative}.monaco-menu .monaco-action-bar.vertical .action-label{-ms-flex:1 1 auto;flex:1 1 auto;text-decoration:none;padding:0 1em;background:none;font-size:12px;line-height:1}.monaco-menu .monaco-action-bar.vertical .keybinding,.monaco-menu .monaco-action-bar.vertical .submenu-indicator{display:inline-block;-ms-flex:2 1 auto;flex:2 1 auto;padding:0 1em;text-align:right;font-size:12px;line-height:1}.monaco-menu .monaco-action-bar.vertical .submenu-indicator{height:100%;-webkit-mask:url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTQiIGhlaWdodD0iMTQiIHZpZXdCb3g9IjAgMCAxNCAxNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTQuNTIwNTEgMTIuMzY0M0w5Ljg3NzkzIDdMNC41MjA1MSAxLjYzNTc0Mkw1LjEzNTc0IDEuMDIwNTA3OEwxMS4xMjIxIDdMNS4xMzU3NCAxMi45Nzk1TDQuNTIwNTEgMTIuMzY0M1oiIGZpbGw9ImJsYWNrIi8+Cjwvc3ZnPgo=) no-repeat 90% 50%/13px 13px;mask:url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTQiIGhlaWdodD0iMTQiIHZpZXdCb3g9IjAgMCAxNCAxNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTQuNTIwNTEgMTIuMzY0M0w5Ljg3NzkzIDdMNC41MjA1MSAxLjYzNTc0Mkw1LjEzNTc0IDEuMDIwNTA3OEwxMS4xMjIxIDdMNS4xMzU3NCAxMi45Nzk1TDQuNTIwNTEgMTIuMzY0M1oiIGZpbGw9ImJsYWNrIi8+Cjwvc3ZnPgo=) no-repeat 90% 50%/13px 13px}.monaco-menu .monaco-action-bar.vertical .action-item.disabled .keybinding,.monaco-menu .monaco-action-bar.vertical .action-item.disabled .submenu-indicator{opacity:.4}.monaco-menu .monaco-action-bar.vertical .action-label:not(.separator){display:inline-block;-o-box-sizing:border-box;-ms-box-sizing:border-box;box-sizing:border-box;margin:0}.monaco-menu .monaco-action-bar.vertical .action-item{position:static;overflow:visible}.monaco-menu .monaco-action-bar.vertical .action-item .monaco-submenu{position:absolute}.monaco-menu .monaco-action-bar.vertical .action-label.separator{padding:.5em 0 0;margin-bottom:.5em;width:100%}.monaco-menu .monaco-action-bar.vertical .action-label.separator.text{padding:.7em 1em .1em;font-weight:700;opacity:1}.monaco-menu .monaco-action-bar.vertical .action-label:hover{color:inherit}.monaco-menu .monaco-action-bar.vertical .menu-item-check{position:absolute;visibility:hidden;-webkit-mask:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgdmlld0JveD0iLTIgLTIgMTYgMTYiIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgLTIgLTIgMTYgMTYiPjxwb2x5Z29uIGZpbGw9IiM0MjQyNDIiIHBvaW50cz0iOSwwIDQuNSw5IDMsNiAwLDYgMywxMiA2LDEyIDEyLDAiLz48L3N2Zz4=) no-repeat 50% 56%/15px 15px;mask:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgdmlld0JveD0iLTIgLTIgMTYgMTYiIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgLTIgLTIgMTYgMTYiPjxwb2x5Z29uIGZpbGw9IiM0MjQyNDIiIHBvaW50cz0iOSwwIDQuNSw5IDMsNiAwLDYgMywxMiA2LDEyIDEyLDAiLz48L3N2Zz4=) no-repeat 50% 56%/15px 15px;width:1em;height:100%}.monaco-menu .monaco-action-bar.vertical .action-menu-item.checked .menu-item-check{visibility:visible}.context-view.monaco-menu-container{outline:0;border:none;-webkit-animation:fadeIn 83ms linear;animation:fadeIn 83ms linear}.context-view.monaco-menu-container .monaco-action-bar.vertical:focus,.context-view.monaco-menu-container .monaco-action-bar.vertical :focus,.context-view.monaco-menu-container :focus{outline:0}.monaco-menu .monaco-action-bar.vertical .action-item{border:1px solid transparent}.hc-black .context-view.monaco-menu-container{box-shadow:none}.hc-black .monaco-menu .monaco-action-bar.vertical .action-item.focused{background:none}.menubar{display:flex;flex-shrink:1;box-sizing:border-box;height:30px;overflow:hidden;flex-wrap:wrap}.fullscreen .menubar{margin:0;padding:0 5px}.menubar>.menubar-menu-button{align-items:center;box-sizing:border-box;padding:0 8px;cursor:default;-webkit-app-region:no-drag;zoom:1;white-space:nowrap;outline:0}.menubar .menubar-menu-items-holder{position:absolute;left:0;opacity:1;z-index:2000}.menubar .menubar-menu-items-holder.monaco-menu-container{outline:0;border:none}.menubar .menubar-menu-items-holder.monaco-menu-container :focus{outline:0}.menubar .toolbar-toggle-more{background-position:50%;background-repeat:no-repeat;background-size:14px;width:20px;height:100%;display:inline-block;padding:0;-webkit-mask:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNiAxNiI+PGRlZnM+PHN0eWxlPi5pY29uLWNhbnZhcy10cmFuc3BhcmVudCwuaWNvbi12cy1vdXR7ZmlsbDojZjZmNmY2O30uaWNvbi1jYW52YXMtdHJhbnNwYXJlbnR7b3BhY2l0eTowO30uaWNvbi12cy1iZ3tmaWxsOiM0MjQyNDI7fTwvc3R5bGU+PC9kZWZzPjx0aXRsZT5FbGxpcHNpc19ib2xkXzE2eDwvdGl0bGU+PGcgaWQ9ImNhbnZhcyI+PHBhdGggY2xhc3M9Imljb24tY2FudmFzLXRyYW5zcGFyZW50IiBkPSJNMTYsMFYxNkgwVjBaIi8+PC9nPjxnIGlkPSJvdXRsaW5lIiBzdHlsZT0iZGlzcGxheTogbm9uZTsiPjxwYXRoIGNsYXNzPSJpY29uLXZzLW91dCIgZD0iTTYsNy41QTIuNSwyLjUsMCwxLDEsMy41LDUsMi41LDIuNSwwLDAsMSw2LDcuNVpNOC41LDVBMi41LDIuNSwwLDEsMCwxMSw3LjUsMi41LDIuNSwwLDAsMCw4LjUsNVptNSwwQTIuNSwyLjUsMCwxLDAsMTYsNy41LDIuNSwyLjUsMCwwLDAsMTMuNSw1WiIgc3R5bGU9ImRpc3BsYXk6IG5vbmU7Ii8+PC9nPjxnIGlkPSJpY29uQmciPjxwYXRoIGNsYXNzPSJpY29uLXZzLWJnIiBkPSJNNSw3LjVBMS41LDEuNSwwLDEsMSwzLjUsNiwxLjUsMS41LDAsMCwxLDUsNy41Wk04LjUsNkExLjUsMS41LDAsMSwwLDEwLDcuNSwxLjUsMS41LDAsMCwwLDguNSw2Wm01LDBBMS41LDEuNSwwLDEsMCwxNSw3LjUsMS41LDEuNSwwLDAsMCwxMy41LDZaIi8+PC9nPjwvc3ZnPg==) no-repeat 50% 55%/14px 14px;mask:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNiAxNiI+PGRlZnM+PHN0eWxlPi5pY29uLWNhbnZhcy10cmFuc3BhcmVudCwuaWNvbi12cy1vdXR7ZmlsbDojZjZmNmY2O30uaWNvbi1jYW52YXMtdHJhbnNwYXJlbnR7b3BhY2l0eTowO30uaWNvbi12cy1iZ3tmaWxsOiM0MjQyNDI7fTwvc3R5bGU+PC9kZWZzPjx0aXRsZT5FbGxpcHNpc19ib2xkXzE2eDwvdGl0bGU+PGcgaWQ9ImNhbnZhcyI+PHBhdGggY2xhc3M9Imljb24tY2FudmFzLXRyYW5zcGFyZW50IiBkPSJNMTYsMFYxNkgwVjBaIi8+PC9nPjxnIGlkPSJvdXRsaW5lIiBzdHlsZT0iZGlzcGxheTogbm9uZTsiPjxwYXRoIGNsYXNzPSJpY29uLXZzLW91dCIgZD0iTTYsNy41QTIuNSwyLjUsMCwxLDEsMy41LDUsMi41LDIuNSwwLDAsMSw2LDcuNVpNOC41LDVBMi41LDIuNSwwLDEsMCwxMSw3LjUsMi41LDIuNSwwLDAsMCw4LjUsNVptNSwwQTIuNSwyLjUsMCwxLDAsMTYsNy41LDIuNSwyLjUsMCwwLDAsMTMuNSw1WiIgc3R5bGU9ImRpc3BsYXk6IG5vbmU7Ii8+PC9nPjxnIGlkPSJpY29uQmciPjxwYXRoIGNsYXNzPSJpY29uLXZzLWJnIiBkPSJNNSw3LjVBMS41LDEuNSwwLDEsMSwzLjUsNiwxLjUsMS41LDAsMCwxLDUsNy41Wk04LjUsNkExLjUsMS41LDAsMSwwLDEwLDcuNSwxLjUsMS41LDAsMCwwLDguNSw2Wm01LDBBMS41LDEuNSwwLDEsMCwxNSw3LjUsMS41LDEuNSwwLDAsMCwxMy41LDZaIi8+PC9nPjwvc3ZnPg==) no-repeat 50% 55%/14px 14px}.monaco-progress-container{width:100%;height:5px;overflow:hidden}.monaco-progress-container .progress-bit{width:2%;height:5px;position:absolute;left:0;display:none}.monaco-progress-container.active .progress-bit{display:inherit}.monaco-progress-container.discrete .progress-bit{left:0;transition:width .1s linear}.monaco-progress-container.discrete.done .progress-bit{width:100%}.monaco-progress-container.infinite .progress-bit{animation-name:progress;animation-duration:4s;animation-iteration-count:infinite;animation-timing-function:linear;-ms-animation-name:progress;-ms-animation-duration:4s;-ms-animation-iteration-count:infinite;-ms-animation-timing-function:linear;-webkit-animation-name:progress;-webkit-animation-duration:4s;-webkit-animation-iteration-count:infinite;-webkit-animation-timing-function:linear;-moz-animation-name:progress;-moz-animation-duration:4s;-moz-animation-iteration-count:infinite;-moz-animation-timing-function:linear;will-change:transform}@keyframes progress{0%{transform:translateX(0) scaleX(1)}50%{transform:translateX(2500%) scaleX(3)}to{transform:translateX(4950%) scaleX(1)}}@-webkit-keyframes progress{0%{transform:translateX(0) scaleX(1)}50%{transform:translateX(2500%) scaleX(3)}to{transform:translateX(4950%) scaleX(1)}}.monaco-sash{position:absolute;z-index:35;touch-action:none}.monaco-sash.disabled{pointer-events:none}.monaco-sash.vertical{cursor:ew-resize;top:0;width:4px;height:100%}.monaco-sash.mac.vertical{cursor:col-resize}.monaco-sash.vertical.minimum{cursor:e-resize}.monaco-sash.vertical.maximum{cursor:w-resize}.monaco-sash.horizontal{cursor:ns-resize;left:0;width:100%;height:4px}.monaco-sash.mac.horizontal{cursor:row-resize}.monaco-sash.horizontal.minimum{cursor:s-resize}.monaco-sash.horizontal.maximum{cursor:n-resize}.monaco-sash:not(.disabled).orthogonal-end:after,.monaco-sash:not(.disabled).orthogonal-start:before{content:" ";height:8px;width:8px;z-index:100;display:block;cursor:all-scroll;position:absolute}.monaco-sash.orthogonal-start.vertical:before{left:-2px;top:-4px}.monaco-sash.orthogonal-end.vertical:after{left:-2px;bottom:-4px}.monaco-sash.orthogonal-start.horizontal:before{top:-2px;left:-4px}.monaco-sash.orthogonal-end.horizontal:after{top:-2px;right:-4px}.monaco-sash.disabled{cursor:default!important;pointer-events:none!important}.monaco-sash.touch.vertical{width:20px}.monaco-sash.touch.horizontal{height:20px}.monaco-sash.debug{background:cyan}.monaco-sash.debug.disabled{background:rgba(0,255,255,.2)}.monaco-sash.debug:not(.disabled).orthogonal-end:after,.monaco-sash.debug:not(.disabled).orthogonal-start:before{background:red}.monaco-scrollable-element>.scrollbar>.up-arrow{background:url(data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMTEgMTEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0ibTkuNDgwNDYsOC45NjE1bDEuMjYsLTEuMjZsLTUuMDQsLTUuMDRsLTUuNDYsNS4wNGwxLjI2LDEuMjZsNC4yLC0zLjc4bDMuNzgsMy43OHoiIGZpbGw9IiM0MjQyNDIiLz48L3N2Zz4=);cursor:pointer}.monaco-scrollable-element>.scrollbar>.down-arrow{background:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMSAxMSI+PHBhdGggdHJhbnNmb3JtPSJyb3RhdGUoLTE4MCA1LjQ5MDQ1OTkxODk3NTgzLDUuODExNTAwMDcyNDc5MjQ4KSIgZmlsbD0iIzQyNDI0MiIgZD0ibTkuNDgwNDYsOC45NjE1bDEuMjYsLTEuMjZsLTUuMDQsLTUuMDRsLTUuNDYsNS4wNGwxLjI2LDEuMjZsNC4yLC0zLjc4bDMuNzgsMy43OHoiLz48L3N2Zz4=);cursor:pointer}.monaco-scrollable-element>.scrollbar>.left-arrow{background:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMSAxMSI+PHBhdGggdHJhbnNmb3JtPSJyb3RhdGUoLTkwIDUuNDkwNDU5OTE4OTc1ODMxLDUuNDMxMzgyMTc5MjYwMjU0KSIgZmlsbD0iIzQyNDI0MiIgZD0ibTkuNDgwNDYsOC41ODEzOGwxLjI2LC0xLjI2bC01LjA0LC01LjA0bC01LjQ2LDUuMDRsMS4yNiwxLjI2bDQuMiwtMy43OGwzLjc4LDMuNzh6Ii8+PC9zdmc+);cursor:pointer}.monaco-scrollable-element>.scrollbar>.right-arrow{background:url(data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMTEgMTEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggdHJhbnNmb3JtPSJyb3RhdGUoOTAgNS42MTcxNjUwODg2NTM1NjQ1LDUuNTU4MDg5NzMzMTIzNzgpICIgZmlsbD0iIzQyNDI0MiIgZD0ibTkuNjA3MTcsOC43MDgwOWwxLjI2LC0xLjI2bC01LjA0LC01LjA0bC01LjQ2LDUuMDRsMS4yNiwxLjI2bDQuMiwtMy43OGwzLjc4LDMuNzh6Ii8+PC9zdmc+);cursor:pointer}.hc-black .monaco-scrollable-element>.scrollbar>.up-arrow,.vs-dark .monaco-scrollable-element>.scrollbar>.up-arrow{background:url(data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMTEgMTEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0ibTkuNDgwNDYsOC45NjE1bDEuMjYsLTEuMjZsLTUuMDQsLTUuMDRsLTUuNDYsNS4wNGwxLjI2LDEuMjZsNC4yLC0zLjc4bDMuNzgsMy43OHoiIGZpbGw9IiNFOEU4RTgiLz48L3N2Zz4=)}.hc-black .monaco-scrollable-element>.scrollbar>.down-arrow,.vs-dark .monaco-scrollable-element>.scrollbar>.down-arrow{background:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMSAxMSI+PHBhdGggdHJhbnNmb3JtPSJyb3RhdGUoLTE4MCA1LjQ5MDQ1OTkxODk3NTgzLDUuODExNTAwMDcyNDc5MjQ4KSIgZmlsbD0iI0U4RThFOCIgZD0ibTkuNDgwNDYsOC45NjE1bDEuMjYsLTEuMjZsLTUuMDQsLTUuMDRsLTUuNDYsNS4wNGwxLjI2LDEuMjZsNC4yLC0zLjc4bDMuNzgsMy43OHoiLz48L3N2Zz4=)}.hc-black .monaco-scrollable-element>.scrollbar>.left-arrow,.vs-dark .monaco-scrollable-element>.scrollbar>.left-arrow{background:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMSAxMSI+PHBhdGggdHJhbnNmb3JtPSJyb3RhdGUoLTkwIDUuNDkwNDU5OTE4OTc1ODMxLDUuNDMxMzgyMTc5MjYwMjU0KSIgZmlsbD0iI0U4RThFOCIgZD0ibTkuNDgwNDYsOC41ODEzOGwxLjI2LC0xLjI2bC01LjA0LC01LjA0bC01LjQ2LDUuMDRsMS4yNiwxLjI2bDQuMiwtMy43OGwzLjc4LDMuNzh6Ii8+PC9zdmc+)}.hc-black .monaco-scrollable-element>.scrollbar>.right-arrow,.vs-dark .monaco-scrollable-element>.scrollbar>.right-arrow{background:url(data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMTEgMTEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggdHJhbnNmb3JtPSJyb3RhdGUoOTAgNS42MTcxNjUwODg2NTM1NjQ1LDUuNTU4MDg5NzMzMTIzNzgpICIgZmlsbD0iI0U4RThFOCIgZD0ibTkuNjA3MTcsOC43MDgwOWwxLjI2LC0xLjI2bC01LjA0LC01LjA0bC01LjQ2LDUuMDRsMS4yNiwxLjI2bDQuMiwtMy43OGwzLjc4LDMuNzh6Ii8+PC9zdmc+)}.monaco-scrollable-element>.visible{opacity:1;background:transparent;transition:opacity .1s linear}.monaco-scrollable-element>.invisible{opacity:0;pointer-events:none}.monaco-scrollable-element>.invisible.fade{transition:opacity .8s linear}.monaco-scrollable-element>.shadow{position:absolute;display:none}.monaco-scrollable-element>.shadow.top{display:block;top:0;left:3px;height:3px;width:100%;box-shadow:inset 0 6px 6px -6px #ddd}.monaco-scrollable-element>.shadow.left{display:block;top:3px;left:0;height:100%;width:3px;box-shadow:inset 6px 0 6px -6px #ddd}.monaco-scrollable-element>.shadow.top-left-corner{display:block;top:0;left:0;height:3px;width:3px}.monaco-scrollable-element>.shadow.top.left{box-shadow:inset 6px 6px 6px -6px #ddd}.vs .monaco-scrollable-element>.scrollbar>.slider{background:hsla(0,0%,39%,.4)}.vs-dark .monaco-scrollable-element>.scrollbar>.slider{background:hsla(0,0%,47%,.4)}.hc-black .monaco-scrollable-element>.scrollbar>.slider{background:rgba(111,195,223,.6)}.monaco-scrollable-element>.scrollbar>.slider:hover{background:hsla(0,0%,39%,.7)}.hc-black .monaco-scrollable-element>.scrollbar>.slider:hover{background:rgba(111,195,223,.8)}.monaco-scrollable-element>.scrollbar>.slider.active{background:rgba(0,0,0,.6)}.vs-dark .monaco-scrollable-element>.scrollbar>.slider.active{background:hsla(0,0%,75%,.4)}.hc-black .monaco-scrollable-element>.scrollbar>.slider.active{background:#6fc3df}.vs-dark .monaco-scrollable-element .shadow.top{box-shadow:none}.vs-dark .monaco-scrollable-element .shadow.left{box-shadow:inset 6px 0 6px -6px #000}.vs-dark .monaco-scrollable-element .shadow.top.left{box-shadow:inset 6px 6px 6px -6px #000}.hc-black .monaco-scrollable-element .shadow.left,.hc-black .monaco-scrollable-element .shadow.top,.hc-black .monaco-scrollable-element .shadow.top.left{box-shadow:none}.monaco-split-view2{position:relative;width:100%;height:100%}.monaco-split-view2>.sash-container{position:absolute;width:100%;height:100%;pointer-events:none}.monaco-split-view2>.sash-container>.monaco-sash{pointer-events:auto}.monaco-split-view2>.split-view-container{display:flex;width:100%;height:100%;white-space:nowrap}.monaco-split-view2.vertical>.split-view-container{flex-direction:column}.monaco-split-view2.horizontal>.split-view-container{flex-direction:row}.monaco-split-view2>.split-view-container>.split-view-view{white-space:normal;flex:none;position:relative}.monaco-split-view2.vertical>.split-view-container>.split-view-view{width:100%}.monaco-split-view2.horizontal>.split-view-container>.split-view-view{height:100%;display:inline-block}.monaco-split-view2.separator-border>.split-view-container>.split-view-view:not(:first-child):before{content:" ";position:absolute;top:0;left:0;z-index:5;pointer-events:none;background-color:var(--separator-border)}.monaco-split-view2.separator-border.horizontal>.split-view-container>.split-view-view:not(:first-child):before{height:100%;width:1px}.monaco-split-view2.separator-border.vertical>.split-view-container>.split-view-view:not(:first-child):before{height:1px;width:100%}.monaco-tl-row{display:flex;height:100%;align-items:center}.monaco-tl-contents,.monaco-tl-twistie{height:100%}.monaco-tl-twistie{font-size:10px;text-align:right;margin-right:6px;flex-shrink:0;width:16px}.monaco-tl-contents{flex:1;overflow:hidden}.monaco-tl-twistie.collapsible{background-size:16px;background-position:3px 50%;background-repeat:no-repeat;background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNiAxNiI+PHBhdGggZmlsbD0iIzY0NjQ2NSIgZD0iTTExIDEwSDUuMzQ0TDExIDQuNDE0VjEweiIvPjwvc3ZnPg==)}.monaco-tl-twistie.collapsible.collapsed:not(.loading){display:inline-block;background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNiAxNiI+PHBhdGggZmlsbD0iIzY0NjQ2NSIgZD0iTTYgNHY4bDQtNC00LTR6bTEgMi40MTRMOC41ODYgOCA3IDkuNTg2VjYuNDE0eiIvPjwvc3ZnPg==)}.vs-dark .monaco-tl-twistie.collapsible:not(.loading){background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNiAxNiI+PHBhdGggZmlsbD0iI0U4RThFOCIgZD0iTTExIDEwSDUuMzQ0TDExIDQuNDE0VjEweiIvPjwvc3ZnPg==)}.vs-dark .monaco-tl-twistie.collapsible.collapsed:not(.loading){background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNiAxNiI+PHBhdGggZmlsbD0iI0U4RThFOCIgZD0iTTYgNHY4bDQtNC00LTR6bTEgMi40MTRMOC41ODYgOCA3IDkuNTg2VjYuNDE0eiIvPjwvc3ZnPg==)}.hc-black .monaco-tl-twistie.collapsible:not(.loading){background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiI+PHBhdGggZmlsbD0iI2ZmZiIgZD0iTTExIDEwLjA3aC01LjY1Nmw1LjY1Ni01LjY1NnY1LjY1NnoiLz48L3N2Zz4=)}.hc-black .monaco-tl-twistie.collapsible.collapsed:not(.loading){background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiI+PHBhdGggZmlsbD0iI2ZmZiIgZD0iTTYgNHY4bDQtNC00LTR6bTEgMi40MTRsMS41ODYgMS41ODYtMS41ODYgMS41ODZ2LTMuMTcyeiIvPjwvc3ZnPg==)}.monaco-tl-twistie.loading{background-image:url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0nMS4wJyBzdGFuZGFsb25lPSdubycgPz4KPHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZlcnNpb249JzEuMScgd2lkdGg9JzEwcHgnIGhlaWdodD0nMTBweCc+Cgk8c3R5bGU+CiAgICBjaXJjbGUgewogICAgICBhbmltYXRpb246IGJhbGwgMC42cyBsaW5lYXIgaW5maW5pdGU7CiAgICB9CgogICAgY2lyY2xlOm50aC1jaGlsZCgyKSB7IGFuaW1hdGlvbi1kZWxheTogMC4wNzVzOyB9CiAgICBjaXJjbGU6bnRoLWNoaWxkKDMpIHsgYW5pbWF0aW9uLWRlbGF5OiAwLjE1czsgfQogICAgY2lyY2xlOm50aC1jaGlsZCg0KSB7IGFuaW1hdGlvbi1kZWxheTogMC4yMjVzOyB9CiAgICBjaXJjbGU6bnRoLWNoaWxkKDUpIHsgYW5pbWF0aW9uLWRlbGF5OiAwLjNzOyB9CiAgICBjaXJjbGU6bnRoLWNoaWxkKDYpIHsgYW5pbWF0aW9uLWRlbGF5OiAwLjM3NXM7IH0KICAgIGNpcmNsZTpudGgtY2hpbGQoNykgeyBhbmltYXRpb24tZGVsYXk6IDAuNDVzOyB9CiAgICBjaXJjbGU6bnRoLWNoaWxkKDgpIHsgYW5pbWF0aW9uLWRlbGF5OiAwLjUyNXM7IH0KCiAgICBAa2V5ZnJhbWVzIGJhbGwgewogICAgICBmcm9tIHsgb3BhY2l0eTogMTsgfQogICAgICB0byB7IG9wYWNpdHk6IDAuMzsgfQogICAgfQoJPC9zdHlsZT4KCTxnPgoJCTxjaXJjbGUgY3g9JzUnIGN5PScxJyByPScxJyBzdHlsZT0nb3BhY2l0eTowLjM7JyAvPgoJCTxjaXJjbGUgY3g9JzcuODI4NCcgY3k9JzIuMTcxNicgcj0nMScgc3R5bGU9J29wYWNpdHk6MC4zOycgLz4KCQk8Y2lyY2xlIGN4PSc5JyBjeT0nNScgcj0nMScgc3R5bGU9J29wYWNpdHk6MC4zOycgLz4KCQk8Y2lyY2xlIGN4PSc3LjgyODQnIGN5PSc3LjgyODQnIHI9JzEnIHN0eWxlPSdvcGFjaXR5OjAuMzsnIC8+CgkJPGNpcmNsZSBjeD0nNScgY3k9JzknIHI9JzEnIHN0eWxlPSdvcGFjaXR5OjAuMzsnIC8+CgkJPGNpcmNsZSBjeD0nMi4xNzE2JyBjeT0nNy44Mjg0JyByPScxJyBzdHlsZT0nb3BhY2l0eTowLjM7JyAvPgoJCTxjaXJjbGUgY3g9JzEnIGN5PSc1JyByPScxJyBzdHlsZT0nb3BhY2l0eTowLjM7JyAvPgoJCTxjaXJjbGUgY3g9JzIuMTcxNicgY3k9JzIuMTcxNicgcj0nMScgc3R5bGU9J29wYWNpdHk6MC4zOycgLz4KCTwvZz4KPC9zdmc+Cg==);background-position:0}.vs-dark .monaco-tl-twistie.loading{background-image:url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0nMS4wJyBzdGFuZGFsb25lPSdubycgPz4KPHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZlcnNpb249JzEuMScgd2lkdGg9JzEwcHgnIGhlaWdodD0nMTBweCc+Cgk8c3R5bGU+CiAgICBjaXJjbGUgewogICAgICBhbmltYXRpb246IGJhbGwgMC42cyBsaW5lYXIgaW5maW5pdGU7CiAgICB9CgogICAgY2lyY2xlOm50aC1jaGlsZCgyKSB7IGFuaW1hdGlvbi1kZWxheTogMC4wNzVzOyB9CiAgICBjaXJjbGU6bnRoLWNoaWxkKDMpIHsgYW5pbWF0aW9uLWRlbGF5OiAwLjE1czsgfQogICAgY2lyY2xlOm50aC1jaGlsZCg0KSB7IGFuaW1hdGlvbi1kZWxheTogMC4yMjVzOyB9CiAgICBjaXJjbGU6bnRoLWNoaWxkKDUpIHsgYW5pbWF0aW9uLWRlbGF5OiAwLjNzOyB9CiAgICBjaXJjbGU6bnRoLWNoaWxkKDYpIHsgYW5pbWF0aW9uLWRlbGF5OiAwLjM3NXM7IH0KICAgIGNpcmNsZTpudGgtY2hpbGQoNykgeyBhbmltYXRpb24tZGVsYXk6IDAuNDVzOyB9CiAgICBjaXJjbGU6bnRoLWNoaWxkKDgpIHsgYW5pbWF0aW9uLWRlbGF5OiAwLjUyNXM7IH0KCiAgICBAa2V5ZnJhbWVzIGJhbGwgewogICAgICBmcm9tIHsgb3BhY2l0eTogMTsgfQogICAgICB0byB7IG9wYWNpdHk6IDAuMzsgfQogICAgfQoJPC9zdHlsZT4KCTxnIHN0eWxlPSJmaWxsOmdyZXk7Ij4KCQk8Y2lyY2xlIGN4PSc1JyBjeT0nMScgcj0nMScgc3R5bGU9J29wYWNpdHk6MC4zOycgLz4KCQk8Y2lyY2xlIGN4PSc3LjgyODQnIGN5PScyLjE3MTYnIHI9JzEnIHN0eWxlPSdvcGFjaXR5OjAuMzsnIC8+CgkJPGNpcmNsZSBjeD0nOScgY3k9JzUnIHI9JzEnIHN0eWxlPSdvcGFjaXR5OjAuMzsnIC8+CgkJPGNpcmNsZSBjeD0nNy44Mjg0JyBjeT0nNy44Mjg0JyByPScxJyBzdHlsZT0nb3BhY2l0eTowLjM7JyAvPgoJCTxjaXJjbGUgY3g9JzUnIGN5PSc5JyByPScxJyBzdHlsZT0nb3BhY2l0eTowLjM7JyAvPgoJCTxjaXJjbGUgY3g9JzIuMTcxNicgY3k9JzcuODI4NCcgcj0nMScgc3R5bGU9J29wYWNpdHk6MC4zOycgLz4KCQk8Y2lyY2xlIGN4PScxJyBjeT0nNScgcj0nMScgc3R5bGU9J29wYWNpdHk6MC4zOycgLz4KCQk8Y2lyY2xlIGN4PScyLjE3MTYnIGN5PScyLjE3MTYnIHI9JzEnIHN0eWxlPSdvcGFjaXR5OjAuMzsnIC8+Cgk8L2c+Cjwvc3ZnPgo=)}.hc-black .monaco-tl-twistie.loading{background-image:url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0nMS4wJyBzdGFuZGFsb25lPSdubycgPz4KPHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZlcnNpb249JzEuMScgd2lkdGg9JzEwcHgnIGhlaWdodD0nMTBweCc+Cgk8c3R5bGU+CiAgICBjaXJjbGUgewogICAgICBhbmltYXRpb246IGJhbGwgMC42cyBsaW5lYXIgaW5maW5pdGU7CiAgICB9CgogICAgY2lyY2xlOm50aC1jaGlsZCgyKSB7IGFuaW1hdGlvbi1kZWxheTogMC4wNzVzOyB9CiAgICBjaXJjbGU6bnRoLWNoaWxkKDMpIHsgYW5pbWF0aW9uLWRlbGF5OiAwLjE1czsgfQogICAgY2lyY2xlOm50aC1jaGlsZCg0KSB7IGFuaW1hdGlvbi1kZWxheTogMC4yMjVzOyB9CiAgICBjaXJjbGU6bnRoLWNoaWxkKDUpIHsgYW5pbWF0aW9uLWRlbGF5OiAwLjNzOyB9CiAgICBjaXJjbGU6bnRoLWNoaWxkKDYpIHsgYW5pbWF0aW9uLWRlbGF5OiAwLjM3NXM7IH0KICAgIGNpcmNsZTpudGgtY2hpbGQoNykgeyBhbmltYXRpb24tZGVsYXk6IDAuNDVzOyB9CiAgICBjaXJjbGU6bnRoLWNoaWxkKDgpIHsgYW5pbWF0aW9uLWRlbGF5OiAwLjUyNXM7IH0KCiAgICBAa2V5ZnJhbWVzIGJhbGwgewogICAgICBmcm9tIHsgb3BhY2l0eTogMTsgfQogICAgICB0byB7IG9wYWNpdHk6IDAuMzsgfQogICAgfQoJPC9zdHlsZT4KCTxnIHN0eWxlPSJmaWxsOndoaXRlOyI+CgkJPGNpcmNsZSBjeD0nNScgY3k9JzEnIHI9JzEnIHN0eWxlPSdvcGFjaXR5OjAuMzsnIC8+CgkJPGNpcmNsZSBjeD0nNy44Mjg0JyBjeT0nMi4xNzE2JyByPScxJyBzdHlsZT0nb3BhY2l0eTowLjM7JyAvPgoJCTxjaXJjbGUgY3g9JzknIGN5PSc1JyByPScxJyBzdHlsZT0nb3BhY2l0eTowLjM7JyAvPgoJCTxjaXJjbGUgY3g9JzcuODI4NCcgY3k9JzcuODI4NCcgcj0nMScgc3R5bGU9J29wYWNpdHk6MC4zOycgLz4KCQk8Y2lyY2xlIGN4PSc1JyBjeT0nOScgcj0nMScgc3R5bGU9J29wYWNpdHk6MC4zOycgLz4KCQk8Y2lyY2xlIGN4PScyLjE3MTYnIGN5PSc3LjgyODQnIHI9JzEnIHN0eWxlPSdvcGFjaXR5OjAuMzsnIC8+CgkJPGNpcmNsZSBjeD0nMScgY3k9JzUnIHI9JzEnIHN0eWxlPSdvcGFjaXR5OjAuMzsnIC8+CgkJPGNpcmNsZSBjeD0nMi4xNzE2JyBjeT0nMi4xNzE2JyByPScxJyBzdHlsZT0nb3BhY2l0eTowLjM7JyAvPgoJPC9nPgo8L3N2Zz4K)}.monaco-quick-open-widget{position:absolute;width:600px;z-index:2000;padding-bottom:6px;left:50%;margin-left:-300px}.monaco-quick-open-widget .monaco-progress-container{position:absolute;left:0;top:38px;z-index:1;height:2px}.monaco-quick-open-widget .monaco-progress-container .progress-bit{height:2px}.monaco-quick-open-widget .quick-open-input{width:588px;border:none;margin:6px}.monaco-quick-open-widget .quick-open-input .monaco-inputbox{width:100%;height:25px}.monaco-quick-open-widget .quick-open-result-count{position:absolute;left:-10000px}.monaco-quick-open-widget .quick-open-tree{line-height:22px}.monaco-quick-open-widget .quick-open-tree .monaco-tree-row>.content>.sub-content{overflow:hidden}.monaco-quick-open-widget.content-changing .quick-open-tree .monaco-scrollable-element .slider{display:none}.monaco-quick-open-widget .quick-open-tree .quick-open-entry{overflow:hidden;text-overflow:ellipsis;display:flex;flex-direction:column;height:100%}.monaco-quick-open-widget .quick-open-tree .quick-open-entry>.quick-open-row{display:flex;align-items:center}.monaco-quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon{overflow:hidden;width:16px;height:16px;margin-right:4px;display:inline-block;vertical-align:middle;flex-shrink:0}.monaco-quick-open-widget .quick-open-tree .monaco-icon-label,.monaco-quick-open-widget .quick-open-tree .monaco-icon-label .monaco-icon-label-description-container{flex:1}.monaco-quick-open-widget .quick-open-tree .quick-open-entry .monaco-highlighted-label span{opacity:1}.monaco-quick-open-widget .quick-open-tree .quick-open-entry-meta{opacity:.7;line-height:normal}.monaco-quick-open-widget .quick-open-tree .content.has-group-label .quick-open-entry-keybinding{margin-right:8px}.monaco-quick-open-widget .quick-open-tree .quick-open-entry-keybinding .monaco-keybinding-key{vertical-align:text-bottom}.monaco-quick-open-widget .quick-open-tree .results-group{margin-right:18px}.monaco-quick-open-widget .quick-open-tree .focused .monaco-tree-row.focused>.content.has-actions>.results-group,.monaco-quick-open-widget .quick-open-tree .monaco-tree-row.focused>.content.has-actions>.results-group,.monaco-quick-open-widget .quick-open-tree .monaco-tree-row:hover:not(.highlighted)>.content.has-actions>.results-group{margin-right:0}.monaco-quick-open-widget .quick-open-tree .results-group-separator{border-top-width:1px;border-top-style:solid;box-sizing:border-box;margin-left:-11px;padding-left:11px}.monaco-tree .monaco-tree-row>.content.actions{position:relative;display:flex}.monaco-tree .monaco-tree-row>.content.actions>.sub-content{flex:1}.monaco-tree .monaco-tree-row>.content.actions .action-item{margin:0}.monaco-tree .monaco-tree-row>.content.actions>.primary-action-bar{line-height:22px;display:none;padding:0 .8em 0 .4em}.monaco-tree .monaco-tree-row.focused>.content.has-actions>.primary-action-bar{width:0;display:block}.monaco-tree.focused .monaco-tree-row.focused>.content.has-actions>.primary-action-bar,.monaco-tree .monaco-tree-row:hover:not(.highlighted)>.content.has-actions>.primary-action-bar,.monaco-tree .monaco-tree-row>.content.has-actions.more>.primary-action-bar{width:inherit;display:block}.monaco-tree .monaco-tree-row>.content.actions>.primary-action-bar .action-label{margin-right:.4em;margin-top:4px;background-repeat:no-repeat;width:16px;height:16px}.monaco-quick-open-widget .quick-open-tree .monaco-highlighted-label .highlight{font-weight:700}.monaco-tree{height:100%;width:100%;white-space:nowrap;-webkit-user-select:none;-moz-user-select:-moz-none;-ms-user-select:none;-o-user-select:none;user-select:none;position:relative}.monaco-tree>.monaco-scrollable-element{height:100%}.monaco-tree>.monaco-scrollable-element>.monaco-tree-wrapper{height:100%;width:100%;position:relative}.monaco-tree .monaco-tree-rows{position:absolute;width:100%;height:100%}.monaco-tree .monaco-tree-rows>.monaco-tree-row{-o-box-sizing:border-box;-ms-box-sizing:border-box;box-sizing:border-box;cursor:pointer;overflow:hidden;width:100%;touch-action:none}.monaco-tree .monaco-tree-rows>.monaco-tree-row>.content{position:relative;height:100%}.monaco-tree-drag-image{display:inline-block;padding:1px 7px;border-radius:10px;font-size:12px;position:absolute}.monaco-tree .monaco-tree-rows>.monaco-tree-row.scrolling{display:none}.monaco-tree .monaco-tree-rows.show-twisties>.monaco-tree-row.has-children>.content:before{content:" ";position:absolute;display:block;background:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNiAxNiI+PHBhdGggZmlsbD0iIzY0NjQ2NSIgZD0iTTYgNHY4bDQtNC00LTR6bTEgMi40MTRMOC41ODYgOCA3IDkuNTg2VjYuNDE0eiIvPjwvc3ZnPg==) 50% 50% no-repeat;width:16px;height:100%;top:0;left:-16px}.monaco-tree .monaco-tree-rows.show-twisties>.monaco-tree-row.expanded>.content:before{background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNiAxNiI+PHBhdGggZmlsbD0iIzY0NjQ2NSIgZD0iTTExIDEwSDUuMzQ0TDExIDQuNDE0VjEweiIvPjwvc3ZnPg==)}.monaco-tree .monaco-tree-rows>.monaco-tree-row.has-children.loading>.content:before{background-image:url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0nMS4wJyBzdGFuZGFsb25lPSdubycgPz4KPHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZlcnNpb249JzEuMScgd2lkdGg9JzEwcHgnIGhlaWdodD0nMTBweCc+Cgk8c3R5bGU+CiAgICBjaXJjbGUgewogICAgICBhbmltYXRpb246IGJhbGwgMC42cyBsaW5lYXIgaW5maW5pdGU7CiAgICB9CgogICAgY2lyY2xlOm50aC1jaGlsZCgyKSB7IGFuaW1hdGlvbi1kZWxheTogMC4wNzVzOyB9CiAgICBjaXJjbGU6bnRoLWNoaWxkKDMpIHsgYW5pbWF0aW9uLWRlbGF5OiAwLjE1czsgfQogICAgY2lyY2xlOm50aC1jaGlsZCg0KSB7IGFuaW1hdGlvbi1kZWxheTogMC4yMjVzOyB9CiAgICBjaXJjbGU6bnRoLWNoaWxkKDUpIHsgYW5pbWF0aW9uLWRlbGF5OiAwLjNzOyB9CiAgICBjaXJjbGU6bnRoLWNoaWxkKDYpIHsgYW5pbWF0aW9uLWRlbGF5OiAwLjM3NXM7IH0KICAgIGNpcmNsZTpudGgtY2hpbGQoNykgeyBhbmltYXRpb24tZGVsYXk6IDAuNDVzOyB9CiAgICBjaXJjbGU6bnRoLWNoaWxkKDgpIHsgYW5pbWF0aW9uLWRlbGF5OiAwLjUyNXM7IH0KCiAgICBAa2V5ZnJhbWVzIGJhbGwgewogICAgICBmcm9tIHsgb3BhY2l0eTogMTsgfQogICAgICB0byB7IG9wYWNpdHk6IDAuMzsgfQogICAgfQoJPC9zdHlsZT4KCTxnPgoJCTxjaXJjbGUgY3g9JzUnIGN5PScxJyByPScxJyBzdHlsZT0nb3BhY2l0eTowLjM7JyAvPgoJCTxjaXJjbGUgY3g9JzcuODI4NCcgY3k9JzIuMTcxNicgcj0nMScgc3R5bGU9J29wYWNpdHk6MC4zOycgLz4KCQk8Y2lyY2xlIGN4PSc5JyBjeT0nNScgcj0nMScgc3R5bGU9J29wYWNpdHk6MC4zOycgLz4KCQk8Y2lyY2xlIGN4PSc3LjgyODQnIGN5PSc3LjgyODQnIHI9JzEnIHN0eWxlPSdvcGFjaXR5OjAuMzsnIC8+CgkJPGNpcmNsZSBjeD0nNScgY3k9JzknIHI9JzEnIHN0eWxlPSdvcGFjaXR5OjAuMzsnIC8+CgkJPGNpcmNsZSBjeD0nMi4xNzE2JyBjeT0nNy44Mjg0JyByPScxJyBzdHlsZT0nb3BhY2l0eTowLjM7JyAvPgoJCTxjaXJjbGUgY3g9JzEnIGN5PSc1JyByPScxJyBzdHlsZT0nb3BhY2l0eTowLjM7JyAvPgoJCTxjaXJjbGUgY3g9JzIuMTcxNicgY3k9JzIuMTcxNicgcj0nMScgc3R5bGU9J29wYWNpdHk6MC4zOycgLz4KCTwvZz4KPC9zdmc+Cg==)}.monaco-tree.highlighted .monaco-tree-rows>.monaco-tree-row:not(.highlighted){opacity:.3}.vs-dark .monaco-tree .monaco-tree-rows.show-twisties>.monaco-tree-row.has-children>.content:before{background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNiAxNiI+PHBhdGggZmlsbD0iI0U4RThFOCIgZD0iTTYgNHY4bDQtNC00LTR6bTEgMi40MTRMOC41ODYgOCA3IDkuNTg2VjYuNDE0eiIvPjwvc3ZnPg==)}.vs-dark .monaco-tree .monaco-tree-rows.show-twisties>.monaco-tree-row.expanded>.content:before{background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNiAxNiI+PHBhdGggZmlsbD0iI0U4RThFOCIgZD0iTTExIDEwSDUuMzQ0TDExIDQuNDE0VjEweiIvPjwvc3ZnPg==)}.vs-dark .monaco-tree .monaco-tree-rows>.monaco-tree-row.has-children.loading>.content:before{background-image:url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0nMS4wJyBzdGFuZGFsb25lPSdubycgPz4KPHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZlcnNpb249JzEuMScgd2lkdGg9JzEwcHgnIGhlaWdodD0nMTBweCc+Cgk8c3R5bGU+CiAgICBjaXJjbGUgewogICAgICBhbmltYXRpb246IGJhbGwgMC42cyBsaW5lYXIgaW5maW5pdGU7CiAgICB9CgogICAgY2lyY2xlOm50aC1jaGlsZCgyKSB7IGFuaW1hdGlvbi1kZWxheTogMC4wNzVzOyB9CiAgICBjaXJjbGU6bnRoLWNoaWxkKDMpIHsgYW5pbWF0aW9uLWRlbGF5OiAwLjE1czsgfQogICAgY2lyY2xlOm50aC1jaGlsZCg0KSB7IGFuaW1hdGlvbi1kZWxheTogMC4yMjVzOyB9CiAgICBjaXJjbGU6bnRoLWNoaWxkKDUpIHsgYW5pbWF0aW9uLWRlbGF5OiAwLjNzOyB9CiAgICBjaXJjbGU6bnRoLWNoaWxkKDYpIHsgYW5pbWF0aW9uLWRlbGF5OiAwLjM3NXM7IH0KICAgIGNpcmNsZTpudGgtY2hpbGQoNykgeyBhbmltYXRpb24tZGVsYXk6IDAuNDVzOyB9CiAgICBjaXJjbGU6bnRoLWNoaWxkKDgpIHsgYW5pbWF0aW9uLWRlbGF5OiAwLjUyNXM7IH0KCiAgICBAa2V5ZnJhbWVzIGJhbGwgewogICAgICBmcm9tIHsgb3BhY2l0eTogMTsgfQogICAgICB0byB7IG9wYWNpdHk6IDAuMzsgfQogICAgfQoJPC9zdHlsZT4KCTxnIHN0eWxlPSJmaWxsOmdyZXk7Ij4KCQk8Y2lyY2xlIGN4PSc1JyBjeT0nMScgcj0nMScgc3R5bGU9J29wYWNpdHk6MC4zOycgLz4KCQk8Y2lyY2xlIGN4PSc3LjgyODQnIGN5PScyLjE3MTYnIHI9JzEnIHN0eWxlPSdvcGFjaXR5OjAuMzsnIC8+CgkJPGNpcmNsZSBjeD0nOScgY3k9JzUnIHI9JzEnIHN0eWxlPSdvcGFjaXR5OjAuMzsnIC8+CgkJPGNpcmNsZSBjeD0nNy44Mjg0JyBjeT0nNy44Mjg0JyByPScxJyBzdHlsZT0nb3BhY2l0eTowLjM7JyAvPgoJCTxjaXJjbGUgY3g9JzUnIGN5PSc5JyByPScxJyBzdHlsZT0nb3BhY2l0eTowLjM7JyAvPgoJCTxjaXJjbGUgY3g9JzIuMTcxNicgY3k9JzcuODI4NCcgcj0nMScgc3R5bGU9J29wYWNpdHk6MC4zOycgLz4KCQk8Y2lyY2xlIGN4PScxJyBjeT0nNScgcj0nMScgc3R5bGU9J29wYWNpdHk6MC4zOycgLz4KCQk8Y2lyY2xlIGN4PScyLjE3MTYnIGN5PScyLjE3MTYnIHI9JzEnIHN0eWxlPSdvcGFjaXR5OjAuMzsnIC8+Cgk8L2c+Cjwvc3ZnPgo=)}.hc-black .monaco-tree .monaco-tree-rows.show-twisties>.monaco-tree-row.has-children>.content:before{background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiI+PHBhdGggZmlsbD0iI2ZmZiIgZD0iTTYgNHY4bDQtNC00LTR6bTEgMi40MTRsMS41ODYgMS41ODYtMS41ODYgMS41ODZ2LTMuMTcyeiIvPjwvc3ZnPg==)}.hc-black .monaco-tree .monaco-tree-rows.show-twisties>.monaco-tree-row.expanded>.content:before{background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiI+PHBhdGggZmlsbD0iI2ZmZiIgZD0iTTExIDEwLjA3aC01LjY1Nmw1LjY1Ni01LjY1NnY1LjY1NnoiLz48L3N2Zz4=)}.hc-black .monaco-tree .monaco-tree-rows>.monaco-tree-row.has-children.loading>.content:before{background-image:url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0nMS4wJyBzdGFuZGFsb25lPSdubycgPz4KPHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZlcnNpb249JzEuMScgd2lkdGg9JzEwcHgnIGhlaWdodD0nMTBweCc+Cgk8c3R5bGU+CiAgICBjaXJjbGUgewogICAgICBhbmltYXRpb246IGJhbGwgMC42cyBsaW5lYXIgaW5maW5pdGU7CiAgICB9CgogICAgY2lyY2xlOm50aC1jaGlsZCgyKSB7IGFuaW1hdGlvbi1kZWxheTogMC4wNzVzOyB9CiAgICBjaXJjbGU6bnRoLWNoaWxkKDMpIHsgYW5pbWF0aW9uLWRlbGF5OiAwLjE1czsgfQogICAgY2lyY2xlOm50aC1jaGlsZCg0KSB7IGFuaW1hdGlvbi1kZWxheTogMC4yMjVzOyB9CiAgICBjaXJjbGU6bnRoLWNoaWxkKDUpIHsgYW5pbWF0aW9uLWRlbGF5OiAwLjNzOyB9CiAgICBjaXJjbGU6bnRoLWNoaWxkKDYpIHsgYW5pbWF0aW9uLWRlbGF5OiAwLjM3NXM7IH0KICAgIGNpcmNsZTpudGgtY2hpbGQoNykgeyBhbmltYXRpb24tZGVsYXk6IDAuNDVzOyB9CiAgICBjaXJjbGU6bnRoLWNoaWxkKDgpIHsgYW5pbWF0aW9uLWRlbGF5OiAwLjUyNXM7IH0KCiAgICBAa2V5ZnJhbWVzIGJhbGwgewogICAgICBmcm9tIHsgb3BhY2l0eTogMTsgfQogICAgICB0byB7IG9wYWNpdHk6IDAuMzsgfQogICAgfQoJPC9zdHlsZT4KCTxnIHN0eWxlPSJmaWxsOndoaXRlOyI+CgkJPGNpcmNsZSBjeD0nNScgY3k9JzEnIHI9JzEnIHN0eWxlPSdvcGFjaXR5OjAuMzsnIC8+CgkJPGNpcmNsZSBjeD0nNy44Mjg0JyBjeT0nMi4xNzE2JyByPScxJyBzdHlsZT0nb3BhY2l0eTowLjM7JyAvPgoJCTxjaXJjbGUgY3g9JzknIGN5PSc1JyByPScxJyBzdHlsZT0nb3BhY2l0eTowLjM7JyAvPgoJCTxjaXJjbGUgY3g9JzcuODI4NCcgY3k9JzcuODI4NCcgcj0nMScgc3R5bGU9J29wYWNpdHk6MC4zOycgLz4KCQk8Y2lyY2xlIGN4PSc1JyBjeT0nOScgcj0nMScgc3R5bGU9J29wYWNpdHk6MC4zOycgLz4KCQk8Y2lyY2xlIGN4PScyLjE3MTYnIGN5PSc3LjgyODQnIHI9JzEnIHN0eWxlPSdvcGFjaXR5OjAuMzsnIC8+CgkJPGNpcmNsZSBjeD0nMScgY3k9JzUnIHI9JzEnIHN0eWxlPSdvcGFjaXR5OjAuMzsnIC8+CgkJPGNpcmNsZSBjeD0nMi4xNzE2JyBjeT0nMi4xNzE2JyByPScxJyBzdHlsZT0nb3BhY2l0eTowLjM7JyAvPgoJPC9nPgo8L3N2Zz4K)}.monaco-tree-action.collapse-all{background:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgdmlld0JveD0iLTEgMCAxNiAxNiIgZW5hYmxlLWJhY2tncm91bmQ9Im5ldyAtMSAwIDE2IDE2Ij48cGF0aCBmaWxsPSIjNDI0MjQyIiBkPSJNMTQgMXY5aC0xdi04aC04di0xaDl6bS0xMSAydjFoOHY4aDF2LTloLTl6bTcgMnY5aC05di05aDl6bS0yIDJoLTV2NWg1di01eiIvPjxyZWN0IHg9IjQiIHk9IjkiIGZpbGw9IiMwMDUzOUMiIHdpZHRoPSIzIiBoZWlnaHQ9IjEiLz48L3N2Zz4=) 50% no-repeat}.hc-black .monaco-tree-action.collapse-all,.vs-dark .monaco-tree-action.collapse-all{background:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgdmlld0JveD0iLTEgMCAxNiAxNiIgZW5hYmxlLWJhY2tncm91bmQ9Im5ldyAtMSAwIDE2IDE2Ij48cGF0aCBmaWxsPSIjQzVDNUM1IiBkPSJNMTQgMXY5aC0xdi04aC04di0xaDl6bS0xMSAydjFoOHY4aDF2LTloLTl6bTcgMnY5aC05di05aDl6bS0yIDJoLTV2NWg1di01eiIvPjxyZWN0IHg9IjQiIHk9IjkiIGZpbGw9IiM3NUJFRkYiIHdpZHRoPSIzIiBoZWlnaHQ9IjEiLz48L3N2Zz4=) 50% no-repeat}.monaco-editor .inputarea{min-width:0;min-height:0;margin:0;padding:0;position:absolute;outline:none!important;resize:none;border:none;overflow:hidden;color:transparent;background-color:transparent}.monaco-editor .inputarea.ime-input{z-index:10}.monaco-editor .margin-view-overlays .current-line,.monaco-editor .view-overlays .current-line{display:block;position:absolute;left:0;top:0;box-sizing:border-box}.monaco-editor .margin-view-overlays .current-line.current-line-margin.current-line-margin-both{border-right:0}.monaco-editor .lines-content .cdr{position:absolute}.monaco-editor .glyph-margin{position:absolute;top:0}.monaco-editor .lines-content .cigr,.monaco-editor .lines-content .cigra,.monaco-editor .margin-view-overlays .cgmr{position:absolute}.monaco-editor .margin-view-overlays .line-numbers{position:absolute;text-align:right;display:inline-block;vertical-align:middle;box-sizing:border-box;cursor:default;height:100%}.monaco-editor .relative-current-line-number{text-align:left;display:inline-block;width:100%}.monaco-editor .margin-view-overlays .line-numbers{cursor:-webkit-image-set(url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNSIgaGVpZ2h0PSIyNSIgeD0iMHB4IiB5PSIwcHgiIHZpZXdCb3g9IjAgMCAxNSAyNSIgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgMTUgMjU7Ij48cG9seWdvbiBzdHlsZT0iZmlsbDojRkZGRkZGO3N0cm9rZTojMDAwMDAwIiBwb2ludHM9IjE0LjUsMS4yIDEuOSwxMy44IDcsMTMuOCAzLjIsMjEuNSA2LjMsMjIuNSAxMC4xLDE0LjkgMTQuNSwxOCIvPjwvc3ZnPg==) 1x,url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHg9IjBweCIgeT0iMHB4IiB3aWR0aD0iMzAiIGhlaWdodD0iNTAiIHZpZXdCb3g9IjAgMCAzMCA1MCIgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgMzAgNTA7Ij48cG9seWdvbiBzdHlsZT0iZmlsbDojRkZGRkZGO3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDoyOyIgcG9pbnRzPSIyOSwyLjQgMy44LDI3LjYgMTQsMjcuNiA2LjQsNDMgMTIuNiw0NSAyMC4yLDI5LjggMjksMzYiLz48L3N2Zz4K) 2x) 30 0,default}.monaco-editor.mac .margin-view-overlays .line-numbers{cursor:-webkit-image-set(url(data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIGlkPSJMYXllcl8xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSIwIDAgMTMgMTkiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDEzIDE5OyIgd2lkdGg9IjEzIiBoZWlnaHQ9IjE5Ij48c3R5bGUgdHlwZT0idGV4dC9jc3MiPi5zdDB7c3Ryb2tlOiNGRkZGRkY7c3Ryb2tlLW1pdGVybGltaXQ6MTA7fTwvc3R5bGU+PHRpdGxlPmZsaXBwZWQtY3Vyc29yLW1hYzwvdGl0bGU+PHBhdGggY2xhc3M9InN0MCIgZD0iTTUuMywxNi42bDEuNi00LjdIMi4xTDEyLjUsMS4ydjE0LjRMOS43LDEzbC0xLjYsNC42Yy0wLjIsMC41LTAuOCwwLjgtMS4zLDAuNkw2LDE3LjkgQzUuNCwxNy43LDUuMSwxNy4yLDUuMywxNi42eiIvPjwvc3ZnPgo=) 1x,url(data:image/svg+xml;base64,CjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0iTGF5ZXJfMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmlld0JveD0iMCAwIDI2IDM4IiBzdHlsZT0iZW5hYmxlLWJhY2tncm91bmQ6bmV3IDAgMCAyNiAzODsiIHdpZHRoPSIyNiIgaGVpZ2h0PSIzOCI+PHN0eWxlIHR5cGU9InRleHQvY3NzIj4uc3Qwe3N0cm9rZTojRkZGRkZGO3N0cm9rZS1taXRlcmxpbWl0OjEwO308L3N0eWxlPgk8dGl0bGU+ZmxpcHBlZC1jdXJzb3ItbWFjPC90aXRsZT48cGF0aCBjbGFzcz0ic3QwIiBkPSJNMTAuNiwzMy4ybDMuMi05LjRINC4yTDI1LDIuNHYyOC44TDE5LjQsMjZsLTMuMiw5LjJjLTAuNCwxLTEuNiwxLjYtMi42LDEuMkwxMiwzNS44IEMxMC44LDM1LjQsMTAuMiwzNC40LDEwLjYsMzMuMnoiLz48L3N2Zz4K) 2x) 24 3,default}.monaco-editor .margin-view-overlays .line-numbers.lh-odd{margin-top:1px}.monaco-editor.no-user-select .lines-content,.monaco-editor.no-user-select .view-line,.monaco-editor.no-user-select .view-lines{-webkit-user-select:none;-ms-user-select:none;-moz-user-select:none;-o-user-select:none;user-select:none}.monaco-editor .view-lines{cursor:text;white-space:nowrap}.monaco-editor.hc-black.mac .view-lines,.monaco-editor.vs-dark.mac .view-lines{cursor:-webkit-image-set(url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAAL0lEQVQoz2NgCD3x//9/BhBYBWdhgFVAiVW4JBFKGIa4AqD0//9D3pt4I4tAdAMAHTQ/j5Zom30AAAAASUVORK5CYII=) 1x,url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J/AAAAz0lEQVRIx2NgYGBY/R8I/vx5eelX3n82IJ9FxGf6tksvf/8FiTMQAcAGQMDvSwu09abffY8QYSAScNk45G198eX//yev73/4///701eh//kZSARckrNBRvz//+8+6ZohwCzjGNjdgQxkAg7B9WADeBjIBqtJCbhRA0YNoIkBSNmaPEMoNmA0FkYNoFKhapJ6FGyAH3nauaSmPfwI0v/3OukVi0CIZ+F25KrtYcx/CTIy0e+rC7R1Z4KMICVTQQ14feVXIbR695u14+Ir4gwAAD49E54wc1kWAAAAAElFTkSuQmCC) 2x) 5 8,text}.monaco-editor .view-line{position:absolute;width:100%}.monaco-editor .lines-decorations{position:absolute;top:0;background:#fff}.monaco-editor .margin-view-overlays .cldr{position:absolute;height:100%}.monaco-editor .margin-view-overlays .cmdr{position:absolute;left:0;width:100%;height:100%}.monaco-editor .minimap.slider-mouseover .minimap-slider{opacity:0;transition:opacity .1s linear}.monaco-editor .minimap.slider-mouseover .minimap-slider.active,.monaco-editor .minimap.slider-mouseover:hover .minimap-slider{opacity:1}.monaco-editor .minimap-shadow-hidden{position:absolute;width:0}.monaco-editor .minimap-shadow-visible{position:absolute;left:-6px;width:6px}.monaco-editor .overlayWidgets{position:absolute;top:0;left:0}.monaco-editor .view-ruler{position:absolute;top:0}.monaco-editor .scroll-decoration{position:absolute;top:0;left:0;height:6px}.monaco-editor .lines-content .cslr{position:absolute}.monaco-editor .top-left-radius{border-top-left-radius:3px}.monaco-editor .bottom-left-radius{border-bottom-left-radius:3px}.monaco-editor .top-right-radius{border-top-right-radius:3px}.monaco-editor .bottom-right-radius{border-bottom-right-radius:3px}.monaco-editor.hc-black .top-left-radius{border-top-left-radius:0}.monaco-editor.hc-black .bottom-left-radius{border-bottom-left-radius:0}.monaco-editor.hc-black .top-right-radius{border-top-right-radius:0}.monaco-editor.hc-black .bottom-right-radius{border-bottom-right-radius:0}.monaco-editor .cursors-layer{position:absolute;top:0}.monaco-editor .cursors-layer>.cursor{position:absolute;cursor:text;overflow:hidden}.monaco-editor .cursors-layer.cursor-smooth-caret-animation>.cursor{transition:80ms}.monaco-editor .cursors-layer.cursor-block-outline-style>.cursor{box-sizing:border-box;background:transparent!important;border-style:solid;border-width:1px}.monaco-editor .cursors-layer.cursor-underline-style>.cursor{border-bottom-width:2px;border-bottom-style:solid;background:transparent!important;box-sizing:border-box}.monaco-editor .cursors-layer.cursor-underline-thin-style>.cursor{border-bottom-width:1px;border-bottom-style:solid;background:transparent!important;box-sizing:border-box}@keyframes monaco-cursor-smooth{0%,20%{opacity:1}60%,to{opacity:0}}@keyframes monaco-cursor-phase{0%,20%{opacity:1}90%,to{opacity:0}}@keyframes monaco-cursor-expand{0%,20%{transform:scaleY(1)}80%,to{transform:scaleY(0)}}.cursor-smooth{animation:monaco-cursor-smooth .5s ease-in-out 0s 20 alternate}.cursor-phase{animation:monaco-cursor-phase .5s ease-in-out 0s 20 alternate}.cursor-expand>.cursor{animation:monaco-cursor-expand .5s ease-in-out 0s 20 alternate}.monaco-diff-editor .diffOverview{z-index:9}.monaco-diff-editor.vs .diffOverview{background:rgba(0,0,0,.03)}.monaco-diff-editor.vs-dark .diffOverview{background:hsla(0,0%,100%,.01)}.monaco-diff-editor .diffViewport{box-shadow:inset 0 0 1px 0 #b9b9b9;background:rgba(0,0,0,.1)}.monaco-diff-editor.hc-black .diffViewport,.monaco-diff-editor.vs-dark .diffViewport{background:hsla(0,0%,100%,.1)}.monaco-scrollable-element.modified-in-monaco-diff-editor.vs-dark .scrollbar,.monaco-scrollable-element.modified-in-monaco-diff-editor.vs .scrollbar{background:transparent}.monaco-scrollable-element.modified-in-monaco-diff-editor.hc-black .scrollbar{background:none}.monaco-scrollable-element.modified-in-monaco-diff-editor .slider{z-index:10}.modified-in-monaco-diff-editor .slider.active{background:hsla(0,0%,67%,.4)}.modified-in-monaco-diff-editor.hc-black .slider.active{background:none}.monaco-diff-editor .delete-sign,.monaco-diff-editor .insert-sign,.monaco-editor .delete-sign,.monaco-editor .insert-sign{background-size:60%;opacity:.7;background-repeat:no-repeat;background-position:50% 50%;background-position:50%;background-size:11px 11px}.monaco-diff-editor.hc-black .delete-sign,.monaco-diff-editor.hc-black .insert-sign,.monaco-editor.hc-black .delete-sign,.monaco-editor.hc-black .insert-sign{opacity:1}.monaco-diff-editor .insert-sign,.monaco-editor .insert-sign{background-image:url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHRpdGxlPkxheWVyIDE8L3RpdGxlPjxyZWN0IGhlaWdodD0iMTEiIHdpZHRoPSIzIiB5PSIzIiB4PSI3IiBmaWxsPSIjNDI0MjQyIi8+PHJlY3QgaGVpZ2h0PSIzIiB3aWR0aD0iMTEiIHk9IjciIHg9IjMiIGZpbGw9IiM0MjQyNDIiLz48L3N2Zz4=)}.monaco-diff-editor .delete-sign,.monaco-editor .delete-sign{background-image:url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHRpdGxlPkxheWVyIDE8L3RpdGxlPjxyZWN0IGhlaWdodD0iMyIgd2lkdGg9IjExIiB5PSI3IiB4PSIzIiBmaWxsPSIjNDI0MjQyIi8+PC9zdmc+)}.monaco-diff-editor.hc-black .insert-sign,.monaco-diff-editor.vs-dark .insert-sign,.monaco-editor.hc-black .insert-sign,.monaco-editor.vs-dark .insert-sign{background-image:url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHRpdGxlPkxheWVyIDE8L3RpdGxlPjxyZWN0IGhlaWdodD0iMTEiIHdpZHRoPSIzIiB5PSIzIiB4PSI3IiBmaWxsPSIjQzVDNUM1Ii8+PHJlY3QgaGVpZ2h0PSIzIiB3aWR0aD0iMTEiIHk9IjciIHg9IjMiIGZpbGw9IiNDNUM1QzUiLz48L3N2Zz4=)}.monaco-diff-editor.hc-black .delete-sign,.monaco-diff-editor.vs-dark .delete-sign,.monaco-editor.hc-black .delete-sign,.monaco-editor.vs-dark .delete-sign{background-image:url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHRpdGxlPkxheWVyIDE8L3RpdGxlPjxyZWN0IGhlaWdodD0iMyIgd2lkdGg9IjExIiB5PSI3IiB4PSIzIiBmaWxsPSIjQzVDNUM1Ii8+PC9zdmc+)}.monaco-editor .inline-added-margin-view-zone,.monaco-editor .inline-deleted-margin-view-zone{text-align:right}.monaco-editor .diagonal-fill{background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAJCAYAAADgkQYQAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAadEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41LjEwMPRyoQAAAChJREFUKFNjOH/+fAMDDgCSu3Dhwn9c8gwwBTgNGR4KQP4HhQOhsAIAZCBTkhtqePcAAAAASUVORK5CYII=)}.monaco-editor.vs-dark .diagonal-fill{opacity:.2}.monaco-editor.hc-black .diagonal-fill{background:none}.monaco-editor .view-zones .view-lines .view-line span{display:inline-block}.monaco-diff-editor .diff-review-line-number{text-align:right;display:inline-block}.monaco-diff-editor .diff-review{position:absolute;-webkit-user-select:none;-ms-user-select:none;-moz-user-select:none;-o-user-select:none;user-select:none}.monaco-diff-editor .diff-review-summary{padding-left:10px}.monaco-diff-editor .diff-review-shadow{position:absolute}.monaco-diff-editor .diff-review-row{white-space:pre}.monaco-diff-editor .diff-review-table{display:table;min-width:100%}.monaco-diff-editor .diff-review-row{display:table-row;width:100%}.monaco-diff-editor .diff-review-cell{display:table-cell}.monaco-diff-editor .diff-review-spacer{display:inline-block;width:10px}.monaco-diff-editor .diff-review-actions{display:inline-block;position:absolute;right:10px;top:2px}.monaco-diff-editor .diff-review-actions .action-label{width:16px;height:16px;margin:2px 0}.monaco-diff-editor .action-label.icon.close-diff-review{background:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgdmlld0JveD0iMyAzIDE2IDE2IiBlbmFibGUtYmFja2dyb3VuZD0ibmV3IDMgMyAxNiAxNiI+PHBvbHlnb24gZmlsbD0iIzQyNDI0MiIgcG9pbnRzPSIxMi41OTcsMTEuMDQyIDE1LjQsMTMuODQ1IDEzLjg0NCwxNS40IDExLjA0MiwxMi41OTggOC4yMzksMTUuNCA2LjY4MywxMy44NDUgOS40ODUsMTEuMDQyIDYuNjgzLDguMjM5IDguMjM4LDYuNjgzIDExLjA0Miw5LjQ4NiAxMy44NDUsNi42ODMgMTUuNCw4LjIzOSIvPjwvc3ZnPg==) 50% no-repeat}.monaco-diff-editor.hc-black .action-label.icon.close-diff-review,.monaco-diff-editor.vs-dark .action-label.icon.close-diff-review{background:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgdmlld0JveD0iMyAzIDE2IDE2IiBlbmFibGUtYmFja2dyb3VuZD0ibmV3IDMgMyAxNiAxNiI+PHBvbHlnb24gZmlsbD0iI2U4ZThlOCIgcG9pbnRzPSIxMi41OTcsMTEuMDQyIDE1LjQsMTMuODQ1IDEzLjg0NCwxNS40IDExLjA0MiwxMi41OTggOC4yMzksMTUuNCA2LjY4MywxMy44NDUgOS40ODUsMTEuMDQyIDYuNjgzLDguMjM5IDguMjM4LDYuNjgzIDExLjA0Miw5LjQ4NiAxMy44NDUsNi42ODMgMTUuNCw4LjIzOSIvPjwvc3ZnPg==) 50% no-repeat}::-ms-clear{display:none}.monaco-editor .editor-widget input{color:inherit}.monaco-editor{position:relative;overflow:visible;-webkit-text-size-adjust:100%;-webkit-font-feature-settings:"liga" off,"calt" off;font-feature-settings:"liga" off,"calt" off}.monaco-editor.enable-ligatures{-webkit-font-feature-settings:"liga" on,"calt" on;font-feature-settings:"liga" on,"calt" on}.monaco-editor .overflow-guard{position:relative;overflow:hidden}.monaco-editor .view-overlays{position:absolute;top:0}.monaco-editor .vs-whitespace{display:inline-block}.monaco-editor .bracket-match{box-sizing:border-box}.monaco-menu .monaco-action-bar.vertical .action-label.hover{background-color:#eee}.monaco-editor .lightbulb-glyph{display:flex;align-items:center;justify-content:center;height:16px;width:20px;padding-left:2px}.monaco-editor .lightbulb-glyph:hover{cursor:pointer}.monaco-editor.vs .lightbulb-glyph{background:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNiAxNiIgZW5hYmxlLWJhY2tncm91bmQ9Im5ldyAwIDAgMTYgMTYiIGhlaWdodD0iMTYiIHdpZHRoPSIxNiI+PHBhdGggZmlsbD0iI0Y2RjZGNiIgZD0iTTEzLjUgNC4yQzEzLjEgMi4xIDEwLjggMCA5LjMgMEg2LjdjLS40IDAtLjYuMi0uNi4yQzQgLjggMi41IDIuNyAyLjUgNC45YzAgLjUtLjEgMi4zIDEuNyAzLjguNS41IDEuMiAyIDEuMyAyLjR2My4zTDcuMSAxNmgybDEuNS0xLjZWMTFjLjEtLjQuOC0xLjkgMS4zLTIuMyAxLjEtLjkgMS41LTEuOSAxLjYtMi43VjQuMnoiLz48Zz48ZyBmaWxsPSIjODQ4NDg0Ij48cGF0aCBkPSJNNi41IDEyaDN2MWgtM3pNNy41IDE1aDEuMWwuOS0xaC0zeiIvPjwvZz48cGF0aCBmaWxsPSIjZmMwIiBkPSJNMTIuNiA1YzAtMi4zLTEuOC00LjEtNC4xLTQuMS0uMSAwLTEuNC4xLTEuNC4xLTIuMS4zLTMuNyAyLTMuNyA0IDAgLjEtLjIgMS42IDEuNCAzIC43LjcgMS41IDIuNCAxLjYgMi45bC4xLjFoM2wuMS0uMmMuMS0uNS45LTIuMiAxLjYtMi45IDEuNi0xLjMgMS40LTIuOCAxLjQtMi45em0tMyAxbC0uNSAzaC0uNlY2YzEuMSAwIC45LTEgLjktMUg2LjV2LjFjMCAuMi4xLjkgMSAuOXYzSDdsLS4yLS43TDYuNSA2Yy0uNyAwLS45LS40LTEtLjd2LS40YzAtLjguOS0uOS45LS45aDMuMXMxIC4xIDEgMWMwIDAgLjEgMS0uOSAxeiIvPjwvZz48cGF0aCBmaWxsPSIjRjBFRkYxIiBkPSJNMTAuNSA1YzAtLjktMS0xLTEtMUg2LjRzLS45LjEtLjkuOXYuNGMwIC4zLjMuNy45LjdsLjQgMi4zLjIuN2guNVY2Yy0xIDAtMS0uNy0xLS45VjVoM3MuMSAxLS45IDF2M2guNmwuNS0zYy45IDAgLjgtMSAuOC0xeiIvPjwvc3ZnPg==) 50% no-repeat}.monaco-editor.vs .lightbulb-glyph.autofixable{background:url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTEzLjAwMSA0LjJDMTIuNjAxIDIuMSAxMC4zMDEgMCA4LjgwMSAwSDYuMjAxQzUuODAxIDAgNS42MDEgMC4yIDUuNjAxIDAuMkMzLjUwMSAwLjggMi4wMDEgMi43IDIuMDAxIDQuOUMyLjAwMSA1LjQgMS45MDEgNy4yIDMuNzAxIDguN0M0LjIwMSA5LjIgNC45MDEgMTAuNyA1LjAwMSAxMS4xVjE0LjRMNi42MDEgMTZIOC42MDFMMTAuMTAxIDE0LjRWMTFDMTAuMjAxIDEwLjYgMTAuOTAxIDkuMSAxMS40MDEgOC43QzEyLjUwMSA3LjggMTIuOTAxIDYuOCAxMy4wMDEgNlY0LjJaIiBmaWxsPSIjRjZGNkY2Ii8+CjxwYXRoIGQ9Ik02LjAwMDk4IDEySDkuMDAwOThWMTNINi4wMDA5OFYxMlpNNy4wMDA5OCAxNUg4LjEwMDk4TDkuMDAwOTggMTRINi4wMDA5OEw3LjAwMDk4IDE1WiIgZmlsbD0iIzg0ODQ4NCIvPgo8cGF0aCBkPSJNMTIuMTAxMSA0Ljk5OTlDMTIuMTAxMSAyLjY5OTkgMTAuMzAxMSAwLjg5OTkwMiA4LjAwMTA3IDAuODk5OTAyQzcuOTAxMDcgMC44OTk5MDIgNi42MDEwNyAwLjk5OTkwMiA2LjYwMTA3IDAuOTk5OTAyQzQuNTAxMDcgMS4yOTk5IDIuOTAxMDcgMi45OTk5IDIuOTAxMDcgNC45OTk5QzIuOTAxMDcgNS4wOTk5IDIuNzAxMDcgNi41OTk5IDQuMzAxMDcgNy45OTk5QzUuMDAxMDcgOC42OTk5IDUuODAxMDcgMTAuMzk5OSA1LjkwMTA3IDEwLjg5OTlMNi4wMDEwNyAxMC45OTk5SDkuMDAxMDdMOS4xMDEwNyAxMC43OTk5QzkuMjAxMDcgMTAuMjk5OSAxMC4wMDExIDguNTk5OSAxMC43MDExIDcuODk5OUMxMi4zMDExIDYuNTk5OSAxMi4xMDExIDUuMDk5OSAxMi4xMDExIDQuOTk5OVY0Ljk5OTlaTTkuMTAxMDcgNS45OTk5TDguNjAxMDcgOC45OTk5SDguMDAxMDdWNS45OTk5QzkuMTAxMDcgNS45OTk5IDguOTAxMDcgNC45OTk5IDguOTAxMDcgNC45OTk5SDYuMDAxMDdWNS4wOTk5QzYuMDAxMDcgNS4yOTk5IDYuMTAxMDcgNS45OTk5IDcuMDAxMDcgNS45OTk5VjguOTk5OUg2LjUwMTA3TDYuMzAxMDcgOC4yOTk5TDYuMDAxMDcgNS45OTk5QzUuMzAxMDcgNS45OTk5IDUuMTAxMDcgNS41OTk5IDUuMDAxMDcgNS4yOTk5VjQuODk5OUM1LjAwMTA3IDQuMDk5OSA1LjkwMTA3IDMuOTk5OSA1LjkwMTA3IDMuOTk5OUg5LjAwMTA3QzkuMDAxMDcgMy45OTk5IDEwLjAwMTEgNC4wOTk5IDEwLjAwMTEgNC45OTk5QzEwLjAwMTEgNC45OTk5IDEwLjEwMTEgNS45OTk5IDkuMTAxMDcgNS45OTk5WiIgZmlsbD0iI0ZGQ0MwMCIvPgo8cGF0aCBkPSJNMTAuMDAxIDVDMTAuMDAxIDQuMSA5LjAwMDk4IDQgOS4wMDA5OCA0SDUuOTAwOThDNS45MDA5OCA0IDUuMDAwOTggNC4xIDUuMDAwOTggNC45VjUuM0M1LjAwMDk4IDUuNiA1LjMwMDk4IDYgNS45MDA5OCA2TDYuMzAwOTggOC4zTDYuNTAwOTggOUg3LjAwMDk4VjZDNi4wMDA5OCA2IDYuMDAwOTggNS4zIDYuMDAwOTggNS4xVjVIOS4wMDA5OEM5LjAwMDk4IDUgOS4xMDA5OCA2IDguMTAwOTggNlY5SDguNzAwOThMOS4yMDA5OCA2QzEwLjEwMSA2IDEwLjAwMSA1IDEwLjAwMSA1WiIgZmlsbD0iI0YwRUZGMSIvPgo8cGF0aCBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGNsaXAtcnVsZT0iZXZlbm9kZCIgZD0iTTggMTJDOCA5Ljc3Mzg2IDkuNzczODYgOCAxMiA4QzE0LjIyNjEgOCAxNiA5Ljc3Mzg2IDE2IDEyQzE2IDE0LjIyNjEgMTQuMjI2MSAxNiAxMiAxNkM5Ljc3Mzg2IDE2IDggMTQuMjI2MSA4IDEyWiIgZmlsbD0iI0Y2RjZGNiIvPgo8cGF0aCBkPSJNMTIuMzE5MiAxMi4zMDMxTDEzLjM0OTUgMTMuMzMzNEwxMy4zMzM0IDEzLjM0OTVMMTIuMzAzMSAxMi4zMTkyTDEyIDEyLjAxNjJMMTEuNjk3IDEyLjMxOTJMMTAuNjY2NyAxMy4zNDk1TDEwLjY1MDYgMTMuMzMzNEwxMS42ODA5IDEyLjMwMzFMMTEuOTgzOSAxMkwxMS42ODA5IDExLjY5N0wxMC42NTA2IDEwLjY2NjdMMTAuNjY2NyAxMC42NTA2TDExLjY5NyAxMS42ODA5TDEyIDExLjk4MzlMMTIuMzAzMSAxMS42ODA5TDEzLjMzMzQgMTAuNjUwNkwxMy4zNDk1IDEwLjY2NjdMMTIuMzE5MiAxMS42OTdMMTIuMDE2MiAxMkwxMi4zMTkyIDEyLjMwMzFaTTEyIDguNDYwMzRDMTAuMDMgOC40NjAzNCA4LjQ2MDM0IDEwLjAzIDguNDYwMzQgMTJDOC40NjAzNCAxMy45NzAxIDEwLjAzIDE1LjUzOTcgMTIgMTUuNTM5N0MxMy45NzAxIDE1LjUzOTcgMTUuNTM5NyAxMy45NzAxIDE1LjUzOTcgMTJDMTUuNTM5NyAxMC4wMyAxMy45NzAxIDguNDYwMzQgMTIgOC40NjAzNFoiIGZpbGw9IiMwMDdBQ0MiIHN0cm9rZT0iI0Y2RjZGNiIgc3Ryb2tlLXdpZHRoPSIwLjg1NzE0MyIvPgo8cGF0aCBkPSJNMTIuNjIyNSAxMi4wMDAyTDEzLjk1NTggMTMuMzMzNkwxMy4zMzM2IDEzLjk1NThMMTIuMDAwMiAxMi42MjI1TDEwLjY2NjkgMTMuOTU1OEwxMC4wNDQ3IDEzLjMzMzZMMTEuMzc4IDEyLjAwMDJMMTAuMDQ0NyAxMC42NjY5TDEwLjY2NjkgMTAuMDQ0N0wxMi4wMDAyIDExLjM3OEwxMy4zMzM2IDEwLjA0NDdMMTMuOTU1OCAxMC42NjY5TDEyLjYyMjUgMTIuMDAwMloiIGZpbGw9IiMwMDdBQ0MiLz4KPHBhdGggZD0iTTEwLjcwNCAxNEwxMS4yMDI4IDEyLjQ3MTJMMTAgMTEuNjM5NEgxMS40NzMyTDEyIDEwTDEyLjUzNjEgMTEuNjM5NEgxNEwxMi43OTcyIDEyLjQ3MTJMMTMuMzA1NCAxNEwxMiAxMy4wMjRMMTAuNzA0IDE0WiIgZmlsbD0id2hpdGUiLz4KPC9zdmc+Cg==) 50% no-repeat}.monaco-editor.hc-black .lightbulb-glyph,.monaco-editor.vs-dark .lightbulb-glyph{background:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNiAxNiIgZW5hYmxlLWJhY2tncm91bmQ9Im5ldyAwIDAgMTYgMTYiIGhlaWdodD0iMTYiIHdpZHRoPSIxNiI+PHBhdGggZmlsbD0iIzFFMUUxRSIgZD0iTTEzLjUgNC4yQzEzLjEgMi4xIDEwLjggMCA5LjMgMEg2LjdjLS40IDAtLjYuMi0uNi4yQzQgLjggMi41IDIuNyAyLjUgNC45YzAgLjUtLjEgMi4zIDEuNyAzLjguNS41IDEuMiAyIDEuMyAyLjR2My4zTDcuMSAxNmgybDEuNS0xLjZWMTFjLjEtLjQuOC0xLjkgMS4zLTIuMyAxLjEtLjkgMS41LTEuOSAxLjYtMi43VjQuMnoiLz48Zz48ZyBmaWxsPSIjQzVDNUM1Ij48cGF0aCBkPSJNNi41IDEyaDN2MWgtM3pNNy41IDE1aDEuMWwuOS0xaC0zeiIvPjwvZz48cGF0aCBmaWxsPSIjRERCMjA0IiBkPSJNMTIuNiA1YzAtMi4zLTEuOC00LjEtNC4xLTQuMS0uMSAwLTEuNC4xLTEuNC4xLTIuMS4zLTMuNyAyLTMuNyA0IDAgLjEtLjIgMS42IDEuNCAzIC43LjcgMS41IDIuNCAxLjYgMi45bC4xLjFoM2wuMS0uMmMuMS0uNS45LTIuMiAxLjYtMi45IDEuNi0xLjMgMS40LTIuOCAxLjQtMi45em0tMyAxbC0uNSAzaC0uNlY2YzEuMSAwIC45LTEgLjktMUg2LjV2LjFjMCAuMi4xLjkgMSAuOXYzSDdsLS4yLS43TDYuNSA2Yy0uNyAwLS45LS40LTEtLjd2LS40YzAtLjguOS0uOS45LS45aDMuMXMxIC4xIDEgMWMwIDAgLjEgMS0uOSAxeiIvPjwvZz48cGF0aCBmaWxsPSIjMjUyNTI2IiBkPSJNMTAuNSA1YzAtLjktMS0xLTEtMUg2LjRzLS45LjEtLjkuOXYuNGMwIC4zLjMuNy45LjdsLjQgMi4zLjIuN2guNVY2Yy0xIDAtMS0uNy0xLS45VjVoM3MuMSAxLS45IDF2M2guNmwuNS0zYy45IDAgLjgtMSAuOC0xeiIvPjwvc3ZnPg==) 50% no-repeat}.monaco-editor.hc-black .lightbulb-glyph.autofixable,.monaco-editor.vs-dark .lightbulb-glyph.autofixable{background:url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTEzLjAwMSA0LjJDMTIuNjAxIDIuMSAxMC4zMDEgMCA4LjgwMSAwSDYuMjAxQzUuODAxIDAgNS42MDEgMC4yIDUuNjAxIDAuMkMzLjUwMSAwLjggMi4wMDEgMi43IDIuMDAxIDQuOUMyLjAwMSA1LjQgMS45MDEgNy4yIDMuNzAxIDguN0M0LjIwMSA5LjIgNC45MDEgMTAuNyA1LjAwMSAxMS4xVjE0LjRMNi42MDEgMTZIOC42MDFMMTAuMTAxIDE0LjRWMTFDMTAuMjAxIDEwLjYgMTAuOTAxIDkuMSAxMS40MDEgOC43QzEyLjUwMSA3LjggMTIuOTAxIDYuOCAxMy4wMDEgNlY0LjJaIiBmaWxsPSIjMUUxRTFFIi8+CjxwYXRoIGQ9Ik02LjAwMDk4IDEySDkuMDAwOThWMTNINi4wMDA5OFYxMlpNNy4wMDA5OCAxNUg4LjEwMDk4TDkuMDAwOTggMTRINi4wMDA5OEw3LjAwMDk4IDE1WiIgZmlsbD0iI0M1QzVDNSIvPgo8cGF0aCBkPSJNMTIuMTAxMSA0Ljk5OTlDMTIuMTAxMSAyLjY5OTkgMTAuMzAxMSAwLjg5OTkwMiA4LjAwMTA3IDAuODk5OTAyQzcuOTAxMDcgMC44OTk5MDIgNi42MDEwNyAwLjk5OTkwMiA2LjYwMTA3IDAuOTk5OTAyQzQuNTAxMDcgMS4yOTk5IDIuOTAxMDcgMi45OTk5IDIuOTAxMDcgNC45OTk5QzIuOTAxMDcgNS4wOTk5IDIuNzAxMDcgNi41OTk5IDQuMzAxMDcgNy45OTk5QzUuMDAxMDcgOC42OTk5IDUuODAxMDcgMTAuMzk5OSA1LjkwMTA3IDEwLjg5OTlMNi4wMDEwNyAxMC45OTk5SDkuMDAxMDdMOS4xMDEwNyAxMC43OTk5QzkuMjAxMDcgMTAuMjk5OSAxMC4wMDExIDguNTk5OSAxMC43MDExIDcuODk5OUMxMi4zMDExIDYuNTk5OSAxMi4xMDExIDUuMDk5OSAxMi4xMDExIDQuOTk5OVY0Ljk5OTlaTTkuMTAxMDcgNS45OTk5TDguNjAxMDcgOC45OTk5SDguMDAxMDdWNS45OTk5QzkuMTAxMDcgNS45OTk5IDguOTAxMDcgNC45OTk5IDguOTAxMDcgNC45OTk5SDYuMDAxMDdWNS4wOTk5QzYuMDAxMDcgNS4yOTk5IDYuMTAxMDcgNS45OTk5IDcuMDAxMDcgNS45OTk5VjguOTk5OUg2LjUwMTA3TDYuMzAxMDcgOC4yOTk5TDYuMDAxMDcgNS45OTk5QzUuMzAxMDcgNS45OTk5IDUuMTAxMDcgNS41OTk5IDUuMDAxMDcgNS4yOTk5VjQuODk5OUM1LjAwMTA3IDQuMDk5OSA1LjkwMTA3IDMuOTk5OSA1LjkwMTA3IDMuOTk5OUg5LjAwMTA3QzkuMDAxMDcgMy45OTk5IDEwLjAwMTEgNC4wOTk5IDEwLjAwMTEgNC45OTk5QzEwLjAwMTEgNC45OTk5IDEwLjEwMTEgNS45OTk5IDkuMTAxMDcgNS45OTk5WiIgZmlsbD0iI0REQjIwNCIvPgo8cGF0aCBkPSJNMTAuMDAxIDVDMTAuMDAxIDQuMSA5LjAwMDk4IDQgOS4wMDA5OCA0SDUuOTAwOThDNS45MDA5OCA0IDUuMDAwOTggNC4xIDUuMDAwOTggNC45VjUuM0M1LjAwMDk4IDUuNiA1LjMwMDk4IDYgNS45MDA5OCA2TDYuMzAwOTggOC4zTDYuNTAwOTggOUg3LjAwMDk4VjZDNi4wMDA5OCA2IDYuMDAwOTggNS4zIDYuMDAwOTggNS4xVjVIOS4wMDA5OEM5LjAwMDk4IDUgOS4xMDA5OCA2IDguMTAwOTggNlY5SDguNzAwOThMOS4yMDA5OCA2QzEwLjEwMSA2IDEwLjAwMSA1IDEwLjAwMSA1WiIgZmlsbD0iIzI1MjUyNiIvPgo8cGF0aCBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGNsaXAtcnVsZT0iZXZlbm9kZCIgZD0iTTggMTJDOCA5Ljc3Mzg2IDkuNzczODYgOCAxMiA4QzE0LjIyNjEgOCAxNiA5Ljc3Mzg2IDE2IDEyQzE2IDE0LjIyNjEgMTQuMjI2MSAxNiAxMiAxNkM5Ljc3Mzg2IDE2IDggMTQuMjI2MSA4IDEyWiIgZmlsbD0iIzFFMUUxRSIvPgo8cGF0aCBkPSJNMTIuMzE5MiAxMi4zMDMxTDEzLjM0OTUgMTMuMzMzNEwxMy4zMzM0IDEzLjM0OTVMMTIuMzAzMSAxMi4zMTkyTDEyIDEyLjAxNjJMMTEuNjk3IDEyLjMxOTJMMTAuNjY2NyAxMy4zNDk1TDEwLjY1MDYgMTMuMzMzNEwxMS42ODA5IDEyLjMwMzFMMTEuOTgzOSAxMkwxMS42ODA5IDExLjY5N0wxMC42NTA2IDEwLjY2NjdMMTAuNjY2NyAxMC42NTA2TDExLjY5NyAxMS42ODA5TDEyIDExLjk4MzlMMTIuMzAzMSAxMS42ODA5TDEzLjMzMzQgMTAuNjUwNkwxMy4zNDk1IDEwLjY2NjdMMTIuMzE5MiAxMS42OTdMMTIuMDE2MiAxMkwxMi4zMTkyIDEyLjMwMzFaTTEyIDguNDYwMzRDMTAuMDMgOC40NjAzNCA4LjQ2MDM0IDEwLjAzIDguNDYwMzQgMTJDOC40NjAzNCAxMy45NzAxIDEwLjAzIDE1LjUzOTcgMTIgMTUuNTM5N0MxMy45NzAxIDE1LjUzOTcgMTUuNTM5NyAxMy45NzAxIDE1LjUzOTcgMTJDMTUuNTM5NyAxMC4wMyAxMy45NzAxIDguNDYwMzQgMTIgOC40NjAzNFoiIGZpbGw9IiMwMDdBQ0MiIHN0cm9rZT0iIzFFMUUxRSIgc3Ryb2tlLXdpZHRoPSIwLjg1NzE0MyIvPgo8cGF0aCBkPSJNMTIuNjIyNSAxMi4wMDAyTDEzLjk1NTggMTMuMzMzNkwxMy4zMzM2IDEzLjk1NThMMTIuMDAwMiAxMi42MjI1TDEwLjY2NjkgMTMuOTU1OEwxMC4wNDQ3IDEzLjMzMzZMMTEuMzc4IDEyLjAwMDJMMTAuMDQ0NyAxMC42NjY5TDEwLjY2NjkgMTAuMDQ0N0wxMi4wMDAyIDExLjM3OEwxMy4zMzM2IDEwLjA0NDdMMTMuOTU1OCAxMC42NjY5TDEyLjYyMjUgMTIuMDAwMloiIGZpbGw9IiMwMDdBQ0MiLz4KPHBhdGggZD0iTTEwLjcwNCAxNEwxMS4yMDI4IDEyLjQ3MTJMMTAgMTEuNjM5NEgxMS40NzMyTDEyIDEwTDEyLjUzNjEgMTEuNjM5NEgxNEwxMi43OTcyIDEyLjQ3MTJMMTMuMzA1NCAxNEwxMiAxMy4wMjRMMTAuNzA0IDE0WiIgZmlsbD0id2hpdGUiLz4KPC9zdmc+Cg==) 50% no-repeat}.monaco-editor .codelens-decoration{overflow:hidden;display:inline-block;text-overflow:ellipsis}.monaco-editor .codelens-decoration>a,.monaco-editor .codelens-decoration>span{-moz-user-select:none;-webkit-user-select:none;-ms-user-select:none;user-select:none;white-space:nowrap;vertical-align:sub}.monaco-editor .codelens-decoration>a{text-decoration:none}.monaco-editor .codelens-decoration>a:hover{text-decoration:underline;cursor:pointer}.monaco-editor .codelens-decoration.invisible-cl{opacity:0}@keyframes fadein{0%{opacity:0;visibility:visible}to{opacity:1}}.monaco-editor .codelens-decoration.fadein{animation:fadein .1s linear}.colorpicker-widget{height:190px;user-select:none}.monaco-editor .colorpicker-hover:focus{outline:none}.colorpicker-header{display:flex;height:24px;position:relative;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAECAYAAACp8Z5+AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAZdEVYdFNvZnR3YXJlAHBhaW50Lm5ldCA0LjAuMTZEaa/1AAAAHUlEQVQYV2PYvXu3JAi7uLiAMaYAjAGTQBPYLQkAa/0Zef3qRswAAAAASUVORK5CYII=);background-size:9px 9px;image-rendering:pixelated}.colorpicker-header .picked-color{width:216px;line-height:24px;cursor:pointer;color:#fff;flex:1;text-align:center}.colorpicker-header .picked-color.light{color:#000}.colorpicker-header .original-color{width:74px;z-index:inherit;cursor:pointer}.colorpicker-body{display:flex;padding:8px;position:relative}.colorpicker-body .saturation-wrap{overflow:hidden;height:150px;position:relative;min-width:220px;flex:1}.colorpicker-body .saturation-box{height:150px;position:absolute}.colorpicker-body .saturation-selection{width:9px;height:9px;margin:-5px 0 0 -5px;border:1px solid #fff;border-radius:100%;box-shadow:0 0 2px rgba(0,0,0,.8);position:absolute}.colorpicker-body .strip{width:25px;height:150px}.colorpicker-body .hue-strip{position:relative;margin-left:8px;cursor:-webkit-grab;background:linear-gradient(180deg,red 0,#ff0 17%,#0f0 33%,#0ff 50%,#00f 67%,#f0f 83%,red)}.colorpicker-body .opacity-strip{position:relative;margin-left:8px;cursor:-webkit-grab;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAECAYAAACp8Z5+AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAZdEVYdFNvZnR3YXJlAHBhaW50Lm5ldCA0LjAuMTZEaa/1AAAAHUlEQVQYV2PYvXu3JAi7uLiAMaYAjAGTQBPYLQkAa/0Zef3qRswAAAAASUVORK5CYII=);background-size:9px 9px;image-rendering:pixelated}.colorpicker-body .strip.grabbing{cursor:-webkit-grabbing}.colorpicker-body .slider{position:absolute;top:0;left:-2px;width:calc(100% + 4px);height:4px;box-sizing:border-box;border:1px solid hsla(0,0%,100%,.71);box-shadow:0 0 1px rgba(0,0,0,.85)}.colorpicker-body .strip .overlay{height:150px;pointer-events:none}.monaco-editor.vs .dnd-target{border-right:2px dotted #000;color:#fff}.monaco-editor.vs-dark .dnd-target{border-right:2px dotted #aeafad;color:#51504f}.monaco-editor.hc-black .dnd-target{border-right:2px dotted #fff;color:#000}.monaco-editor.hc-black.mac.mouse-default .view-lines,.monaco-editor.mouse-default .view-lines,.monaco-editor.vs-dark.mac.mouse-default .view-lines{cursor:default}.monaco-editor.hc-black.mac.mouse-copy .view-lines,.monaco-editor.mouse-copy .view-lines,.monaco-editor.vs-dark.mac.mouse-copy .view-lines{cursor:copy}.monaco-checkbox .label{width:12px;height:12px;border:1px solid #000;background-color:transparent;display:inline-block}.monaco-checkbox .checkbox{position:absolute;overflow:hidden;clip:rect(0 0 0 0);height:1px;width:1px;margin:-1px;padding:0;border:0}.monaco-checkbox .checkbox:checked+.label{background-color:#000}.monaco-editor .find-widget{position:absolute;z-index:10;top:-44px;height:34px;overflow:hidden;line-height:19px;transition:top .2s linear;padding:0 4px}.monaco-editor .find-widget.replaceToggled{top:-74px;height:64px}.monaco-editor .find-widget.replaceToggled>.replace-part{display:flex;display:-webkit-flex;align-items:center}.monaco-editor .find-widget.replaceToggled.visible,.monaco-editor .find-widget.visible{top:0}.monaco-editor .find-widget .monaco-inputbox .input{background-color:transparent;min-height:0}.monaco-editor .find-widget .replace-input .input{font-size:13px}.monaco-editor .find-widget>.find-part,.monaco-editor .find-widget>.replace-part{margin:4px 0 0 17px;font-size:12px;display:flex;display:-webkit-flex;align-items:center}.monaco-editor .find-widget>.find-part .monaco-inputbox,.monaco-editor .find-widget>.replace-part .monaco-inputbox{height:25px}.monaco-editor .find-widget>.find-part .monaco-inputbox>.wrapper>.input,.monaco-editor .find-widget>.replace-part .monaco-inputbox>.wrapper>.input{padding-top:2px;padding-bottom:2px}.monaco-editor .find-widget .monaco-findInput{vertical-align:middle;display:flex;display:-webkit-flex;flex:1}.monaco-editor .find-widget .matchesCount{display:flex;display:-webkit-flex;flex:initial;margin:0 1px 0 3px;padding:2px 2px 0;height:25px;vertical-align:middle;box-sizing:border-box;text-align:center;line-height:23px}.monaco-editor .find-widget .button{min-width:20px;width:20px;height:20px;display:flex;display:-webkit-flex;flex:initial;margin-left:3px;background-position:50%;background-repeat:no-repeat;cursor:pointer}.monaco-editor .find-widget .button:not(.disabled):hover{background-color:rgba(0,0,0,.1)}.monaco-editor .find-widget .button.left{margin-left:0;margin-right:3px}.monaco-editor .find-widget .button.wide{width:auto;padding:1px 6px;top:-1px}.monaco-editor .find-widget .button.toggle{position:absolute;top:0;left:0;width:18px;height:100%;-o-box-sizing:border-box;-ms-box-sizing:border-box;box-sizing:border-box}.monaco-editor .find-widget .button.toggle.disabled{display:none}.monaco-editor .find-widget .previous{background-image:url(data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiCgkgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIKCSB4PSIwcHgiIHk9IjBweCIgd2lkdGg9IjE2cHgiIGhlaWdodD0iMTZweCIgdmlld0JveD0iLTEgLTMgMTYgMTYiIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgLTEgLTMgMTYgMTYiIHhtbDpzcGFjZT0icHJlc2VydmUiPgo8cG9seWdvbiBmaWxsPSIjNDI0MjQyIiBwb2ludHM9IjEzLDQgNiw0IDksMSA2LDEgMiw1IDYsOSA5LDkgNiw2IDEzLDYgIi8+Cjwvc3ZnPgo=)}.monaco-editor .find-widget .next{background-image:url(data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiCgkgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIKCSB4PSIwcHgiIHk9IjBweCIgd2lkdGg9IjE2cHgiIGhlaWdodD0iMTZweCIgdmlld0JveD0iLTEgLTMgMTYgMTYiIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgLTEgLTMgMTYgMTYiIHhtbDpzcGFjZT0icHJlc2VydmUiPgo8cGF0aCBmaWxsPSIjNDI0MjQyIiBkPSJNMSw0aDdMNSwxaDNsNCw0TDgsOUg1bDMtM0gxVjR6Ii8+Cjwvc3ZnPgo=)}.monaco-editor .find-widget .disabled{opacity:.3;cursor:default}.monaco-editor .find-widget .monaco-checkbox{width:20px;height:20px;display:inline-block;vertical-align:middle;margin-left:3px}.monaco-editor .find-widget .monaco-checkbox .label{content:"";display:inline-block;background-repeat:no-repeat;background-position:0 0;background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCI+CjxnIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAsLTEwMzIuMzYyMikiPgogIDxyZWN0IHdpZHRoPSI5IiBoZWlnaHQ9IjIiIHg9IjIiIHk9IjEwNDYuMzYyMiIgc3R5bGU9ImZpbGw6IzQyNDI0MjtmaWxsLW9wYWNpdHk6MTtzdHJva2U6bm9uZSIgLz4KICA8cmVjdCB3aWR0aD0iMTMiIGhlaWdodD0iMiIgeD0iMiIgeT0iMTA0My4zNjIyIiBzdHlsZT0iZmlsbDojNDI0MjQyO2ZpbGwtb3BhY2l0eToxO3N0cm9rZTpub25lIiAvPgogIDxyZWN0IHdpZHRoPSI2IiBoZWlnaHQ9IjIiIHg9IjIiIHk9IjEwNDAuMzYyMiIgc3R5bGU9ImZpbGw6IzQyNDI0MjtmaWxsLW9wYWNpdHk6MTtzdHJva2U6bm9uZSIgLz4KICA8cmVjdCB3aWR0aD0iMTIiIGhlaWdodD0iMiIgeD0iMiIgeT0iMTAzNy4zNjIyIiBzdHlsZT0iZmlsbDojNDI0MjQyO2ZpbGwtb3BhY2l0eToxO3N0cm9rZTpub25lIiAvPgo8L2c+Cjwvc3ZnPg==);width:20px;height:20px;border:none}.monaco-editor .find-widget .monaco-checkbox .checkbox:disabled+.label{opacity:.3;cursor:default}.monaco-editor .find-widget .monaco-checkbox .checkbox:not(:disabled)+.label{cursor:pointer}.monaco-editor .find-widget .monaco-checkbox .checkbox:not(:disabled):hover:before+.label{background-color:#ddd}.monaco-editor .find-widget .monaco-checkbox .checkbox:checked+.label{background-color:hsla(0,0%,39%,.2)}.monaco-editor .find-widget .close-fw{background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgdmlld0JveD0iMyAzIDE2IDE2IiBlbmFibGUtYmFja2dyb3VuZD0ibmV3IDMgMyAxNiAxNiI+PHBvbHlnb24gZmlsbD0iIzQyNDI0MiIgcG9pbnRzPSIxMi41OTcsMTEuMDQyIDE1LjQsMTMuODQ1IDEzLjg0NCwxNS40IDExLjA0MiwxMi41OTggOC4yMzksMTUuNCA2LjY4MywxMy44NDUgOS40ODUsMTEuMDQyIDYuNjgzLDguMjM5IDguMjM4LDYuNjgzIDExLjA0Miw5LjQ4NiAxMy44NDUsNi42ODMgMTUuNCw4LjIzOSIvPjwvc3ZnPg==)}.monaco-editor .find-widget .expand{background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiI+PHBhdGggZmlsbD0iIzY0NjQ2NSIgZD0iTTExIDEwLjA3aC01LjY1Nmw1LjY1Ni01LjY1NnY1LjY1NnoiLz48L3N2Zz4=)}.monaco-editor .find-widget .collapse{background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiI+PHBhdGggZmlsbD0iIzY0NjQ2NSIgZD0iTTYgNHY4bDQtNC00LTR6bTEgMi40MTRsMS41ODYgMS41ODYtMS41ODYgMS41ODZ2LTMuMTcyeiIvPjwvc3ZnPg==)}.monaco-editor .find-widget .replace{background-image:url(data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IiB3aWR0aD0iMTZweCIKCSBoZWlnaHQ9IjE2cHgiIHZpZXdCb3g9IjAgMCAxNiAxNiIgZW5hYmxlLWJhY2tncm91bmQ9Im5ldyAwIDAgMTYgMTYiIHhtbDpzcGFjZT0icHJlc2VydmUiPgo8ZyBpZD0iaWNvbl94NUZfYmciPgoJPGc+CgkJPHBhdGggZmlsbD0iIzQyNDI0MiIgZD0iTTExLDNWMWgtMXY1djFoMWgyaDFWNFYzSDExeiBNMTMsNmgtMlY0aDJWNnoiLz4KCQk8cGF0aCBmaWxsPSIjNDI0MjQyIiBkPSJNMiwxNWg3VjlIMlYxNXogTTQsMTBoM3YxSDV2MmgydjFINFYxMHoiLz4KCTwvZz4KPC9nPgo8ZyBpZD0iY29sb3JfeDVGX2ltcG9ydGFuY2UiPgoJPHBhdGggZmlsbD0iIzAwNTM5QyIgZD0iTTMuOTc5LDMuNUw0LDZMMyw1djEuNUw0LjUsOEw2LDYuNVY1TDUsNkw0Ljk3OSwzLjVjMC0wLjI3NSwwLjIyNS0wLjUsMC41LTAuNUg5VjJINS40NzkKCQlDNC42NTEsMiwzLjk3OSwyLjY3MywzLjk3OSwzLjV6Ii8+CjwvZz4KPC9zdmc+Cg==)}.monaco-editor .find-widget .replace-all{background-image:url(data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IiB3aWR0aD0iMTZweCIKCSBoZWlnaHQ9IjE2cHgiIHZpZXdCb3g9IjAgMCAxNiAxNiIgZW5hYmxlLWJhY2tncm91bmQ9Im5ldyAwIDAgMTYgMTYiIHhtbDpzcGFjZT0icHJlc2VydmUiPgo8ZyBpZD0iaWNvbl94NUZfYmciPgoJPHBhdGggZmlsbD0iIzQyNDI0MiIgZD0iTTExLDE1VjlIMXY2SDExeiBNMiwxNHYtMmgxdi0xSDJ2LTFoM3Y0SDJ6IE0xMCwxMUg4djJoMnYxSDd2LTRoM1YxMXogTTMsMTN2LTFoMXYxSDN6IE0xMyw3djZoLTFWOEg1VjcKCQlIMTN6IE0xMywyVjFoLTF2NWgzVjJIMTN6IE0xNCw1aC0xVjNoMVY1eiBNMTEsMnY0SDhWNGgxdjFoMVY0SDlWM0g4VjJIMTF6Ii8+CjwvZz4KPGcgaWQ9ImNvbG9yX3g1Rl9hY3Rpb24iPgoJPHBhdGggZmlsbD0iIzAwNTM5QyIgZD0iTTEuOTc5LDMuNUwyLDZMMSw1djEuNUwyLjUsOEw0LDYuNVY1TDMsNkwyLjk3OSwzLjVjMC0wLjI3NSwwLjIyNS0wLjUsMC41LTAuNUg3VjJIMy40NzkKCQlDMi42NTEsMiwxLjk3OSwyLjY3MywxLjk3OSwzLjV6Ii8+CjwvZz4KPC9zdmc+Cg==)}.monaco-editor .find-widget>.replace-part{display:none}.monaco-editor .find-widget>.replace-part>.replace-input{display:flex;display:-webkit-flex;vertical-align:middle;width:auto!important}.monaco-editor .find-widget.reduced-find-widget .matchesCount,.monaco-editor .find-widget.reduced-find-widget .monaco-checkbox{display:none}.monaco-editor .find-widget.narrow-find-widget{max-width:257px!important}.monaco-editor .find-widget.collapsed-find-widget{max-width:170px!important}.monaco-editor .find-widget.collapsed-find-widget .button.next,.monaco-editor .find-widget.collapsed-find-widget .button.previous,.monaco-editor .find-widget.collapsed-find-widget .button.replace,.monaco-editor .find-widget.collapsed-find-widget .button.replace-all,.monaco-editor .find-widget.collapsed-find-widget>.find-part .monaco-findInput .controls{display:none}.monaco-editor .findMatch{-webkit-animation-duration:0;-webkit-animation-name:inherit!important;-moz-animation-duration:0;-moz-animation-name:inherit!important;-ms-animation-duration:0;-ms-animation-name:inherit!important;animation-duration:0;animation-name:inherit!important}.monaco-editor .find-widget .monaco-sash{width:2px!important;margin-left:-4px}.monaco-editor.hc-black .find-widget .previous,.monaco-editor.vs-dark .find-widget .previous{background-image:url(data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiCgkgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIKCSB4PSIwcHgiIHk9IjBweCIgd2lkdGg9IjE2cHgiIGhlaWdodD0iMTZweCIgdmlld0JveD0iLTEgLTMgMTYgMTYiIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgLTEgLTMgMTYgMTYiIHhtbDpzcGFjZT0icHJlc2VydmUiPgo8cG9seWdvbiBmaWxsPSIjQzVDNUM1IiBwb2ludHM9IjEzLDQgNiw0IDksMSA2LDEgMiw1IDYsOSA5LDkgNiw2IDEzLDYgIi8+Cjwvc3ZnPgo=)}.monaco-editor.hc-black .find-widget .next,.monaco-editor.vs-dark .find-widget .next{background-image:url(data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiCgkgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIKCSB4PSIwcHgiIHk9IjBweCIgd2lkdGg9IjE2cHgiIGhlaWdodD0iMTZweCIgdmlld0JveD0iLTEgLTMgMTYgMTYiIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgLTEgLTMgMTYgMTYiIHhtbDpzcGFjZT0icHJlc2VydmUiPgo8cGF0aCBmaWxsPSIjQzVDNUM1IiBkPSJNMSw0aDdMNSwxaDNsNCw0TDgsOUg1bDMtM0gxVjR6Ii8+Cjwvc3ZnPgo=)}.monaco-editor.hc-black .find-widget .monaco-checkbox .label,.monaco-editor.vs-dark .find-widget .monaco-checkbox .label{background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCI+CjxnIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAsLTEwMzIuMzYyMikiPgogIDxyZWN0IHdpZHRoPSI5IiBoZWlnaHQ9IjIiIHg9IjIiIHk9IjEwNDYuMzYyMiIgc3R5bGU9ImZpbGw6I0M1QzVDNTtmaWxsLW9wYWNpdHk6MTtzdHJva2U6bm9uZSIgLz4KICA8cmVjdCB3aWR0aD0iMTMiIGhlaWdodD0iMiIgeD0iMiIgeT0iMTA0My4zNjIyIiBzdHlsZT0iZmlsbDojQzVDNUM1O2ZpbGwtb3BhY2l0eToxO3N0cm9rZTpub25lIiAvPgogIDxyZWN0IHdpZHRoPSI2IiBoZWlnaHQ9IjIiIHg9IjIiIHk9IjEwNDAuMzYyMiIgc3R5bGU9ImZpbGw6I0M1QzVDNTtmaWxsLW9wYWNpdHk6MTtzdHJva2U6bm9uZSIgLz4KICA8cmVjdCB3aWR0aD0iMTIiIGhlaWdodD0iMiIgeD0iMiIgeT0iMTAzNy4zNjIyIiBzdHlsZT0iZmlsbDojQzVDNUM1O2ZpbGwtb3BhY2l0eToxO3N0cm9rZTpub25lIiAvPgo8L2c+Cjwvc3ZnPg==)}.monaco-editor.vs-dark .find-widget .monaco-checkbox .checkbox:checked+.label,.monaco-editor.vs-dark .find-widget .monaco-checkbox .checkbox:not(:disabled):hover:before+.label{background-color:hsla(0,0%,100%,.1)}.monaco-editor.hc-black .find-widget .close-fw,.monaco-editor.vs-dark .find-widget .close-fw{background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgdmlld0JveD0iMyAzIDE2IDE2IiBlbmFibGUtYmFja2dyb3VuZD0ibmV3IDMgMyAxNiAxNiI+PHBvbHlnb24gZmlsbD0iI2U4ZThlOCIgcG9pbnRzPSIxMi41OTcsMTEuMDQyIDE1LjQsMTMuODQ1IDEzLjg0NCwxNS40IDExLjA0MiwxMi41OTggOC4yMzksMTUuNCA2LjY4MywxMy44NDUgOS40ODUsMTEuMDQyIDYuNjgzLDguMjM5IDguMjM4LDYuNjgzIDExLjA0Miw5LjQ4NiAxMy44NDUsNi42ODMgMTUuNCw4LjIzOSIvPjwvc3ZnPg==)}.monaco-editor.hc-black .find-widget .replace,.monaco-editor.vs-dark .find-widget .replace{background-image:url(data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IiB3aWR0aD0iMTZweCIKCSBoZWlnaHQ9IjE2cHgiIHZpZXdCb3g9IjAgMCAxNiAxNiIgZW5hYmxlLWJhY2tncm91bmQ9Im5ldyAwIDAgMTYgMTYiIHhtbDpzcGFjZT0icHJlc2VydmUiPgo8ZyBpZD0iaWNvbl94NUZfYmciPgoJPGc+CgkJPHBhdGggZmlsbD0iI0M1QzVDNSIgZD0iTTExLDNWMWgtMXY1djFoMWgyaDFWNFYzSDExeiBNMTMsNmgtMlY0aDJWNnoiLz4KCQk8cGF0aCBmaWxsPSIjQzVDNUM1IiBkPSJNMiwxNWg3VjlIMlYxNXogTTQsMTBoM3YxSDV2MmgydjFINFYxMHoiLz4KCTwvZz4KPC9nPgo8ZyBpZD0iY29sb3JfeDVGX2ltcG9ydGFuY2UiPgoJPHBhdGggZmlsbD0iIzc1QkVGRiIgZD0iTTMuOTc5LDMuNUw0LDZMMyw1djEuNUw0LjUsOEw2LDYuNVY1TDUsNkw0Ljk3OSwzLjVjMC0wLjI3NSwwLjIyNS0wLjUsMC41LTAuNUg5VjJINS40NzkKCQlDNC42NTEsMiwzLjk3OSwyLjY3MywzLjk3OSwzLjV6Ii8+CjwvZz4KPC9zdmc+Cg==)}.monaco-editor.hc-black .find-widget .replace-all,.monaco-editor.vs-dark .find-widget .replace-all{background-image:url(data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IiB3aWR0aD0iMTZweCIKCSBoZWlnaHQ9IjE2cHgiIHZpZXdCb3g9IjAgMCAxNiAxNiIgZW5hYmxlLWJhY2tncm91bmQ9Im5ldyAwIDAgMTYgMTYiIHhtbDpzcGFjZT0icHJlc2VydmUiPgo8ZyBpZD0iaWNvbl94NUZfYmciPgoJPHBhdGggZmlsbD0iI0M1QzVDNSIgZD0iTTExLDE1VjlIMXY2SDExeiBNMiwxNHYtMmgxdi0xSDJ2LTFoM3Y0SDJ6IE0xMCwxMUg4djJoMnYxSDd2LTRoM1YxMXogTTMsMTN2LTFoMXYxSDN6IE0xMyw3djZoLTFWOEg1VjcKCQlIMTN6IE0xMywyVjFoLTF2NWgzVjJIMTN6IE0xNCw1aC0xVjNoMVY1eiBNMTEsMnY0SDhWNGgxdjFoMVY0SDlWM0g4VjJIMTF6Ii8+CjwvZz4KPGcgaWQ9ImNvbG9yX3g1Rl9hY3Rpb24iPgoJPHBhdGggZmlsbD0iIzc1QkVGRiIgZD0iTTEuOTc5LDMuNUwyLDZMMSw1djEuNUwyLjUsOEw0LDYuNVY1TDMsNkwyLjk3OSwzLjVjMC0wLjI3NSwwLjIyNS0wLjUsMC41LTAuNUg3VjJIMy40NzkKCQlDMi42NTEsMiwxLjk3OSwyLjY3MywxLjk3OSwzLjV6Ii8+CjwvZz4KPC9zdmc+Cg==)}.monaco-editor.hc-black .find-widget .expand,.monaco-editor.vs-dark .find-widget .expand{background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiI+PHBhdGggZmlsbD0iI2U4ZThlOCIgZD0iTTExIDEwLjA3aC01LjY1Nmw1LjY1Ni01LjY1NnY1LjY1NnoiLz48L3N2Zz4=)}.monaco-editor.hc-black .find-widget .collapse,.monaco-editor.vs-dark .find-widget .collapse{background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiI+PHBhdGggZmlsbD0iI2U4ZThlOCIgZD0iTTYgNHY4bDQtNC00LTR6bTEgMi40MTRsMS41ODYgMS41ODYtMS41ODYgMS41ODZ2LTMuMTcyeiIvPjwvc3ZnPg==)}.monaco-editor.hc-black .find-widget .button:not(.disabled):hover,.monaco-editor.vs-dark .find-widget .button:not(.disabled):hover{background-color:hsla(0,0%,100%,.1)}.monaco-editor.hc-black .find-widget .button:before{position:relative;top:1px;left:2px}.monaco-editor.hc-black .find-widget .monaco-checkbox .checkbox:checked+.label{background-color:hsla(0,0%,100%,.1)}.monaco-editor .margin-view-overlays .folding{cursor:pointer;background-repeat:no-repeat;background-origin:border-box;background-position:calc(50% + 2px) 50%;background-size:auto calc(100% - 3px);opacity:0;transition:opacity .5s;background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm94PSIwIDAgMTUgMTUiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDE1IDE1OyI+CjxwYXRoIHN0eWxlPSJmaWxsOiNCNkI2QjYiIGQ9Ik0xMSw0djdINFY0SDExIE0xMiwzSDN2OWg5VjNMMTIsM3oiLz4KPGxpbmUgc3R5bGU9ImZpbGw6bm9uZTtzdHJva2U6IzZCNkI2QjtzdHJva2UtbWl0ZXJsaW1pdDoxMCIgeDE9IjEwIiB5MT0iNy41IiB4Mj0iNSIgeTI9IjcuNSIvPgo8L3N2Zz4=)}.monaco-editor.hc-black .margin-view-overlays .folding,.monaco-editor.vs-dark .margin-view-overlays .folding{background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm94PSIwIDAgMTUgMTUiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDE1IDE1OyI+CjxwYXRoIHN0eWxlPSJmaWxsOiM1QTVBNUEiIGQ9Ik0xMSw0djdINFY0SDExIE0xMiwzSDN2OWg5VjNMMTIsM3oiLz4KPGxpbmUgc3R5bGU9ImZpbGw6bm9uZTtzdHJva2U6I0M1QzVDNTtzdHJva2UtbWl0ZXJsaW1pdDoxMCIgeDE9IjEwIiB5MT0iNy41IiB4Mj0iNSIgeTI9IjcuNSIvPgo8L3N2Zz4=)}.monaco-editor .margin-view-overlays .folding.alwaysShowFoldIcons,.monaco-editor .margin-view-overlays:hover .folding{opacity:1}.monaco-editor .margin-view-overlays .folding.collapsed{background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm94PSIwIDAgMTUgMTUiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDE1IDE1OyI+CjxyZWN0IHg9IjMiIHk9IjMiIHN0eWxlPSJmaWxsOiNFOEU4RTgiIHdpZHRoPSI5IiBoZWlnaHQ9IjkiLz4KPHBhdGggc3R5bGU9ImZpbGw6I0I2QjZCNiIgZD0iTTExLDR2N0g0VjRIMTEgTTEyLDNIM3Y5aDlWM0wxMiwzeiIvPgo8bGluZSBzdHlsZT0iZmlsbDpub25lO3N0cm9rZTojNkI2QjZCO3N0cm9rZS1taXRlcmxpbWl0OjEwIiB4MT0iMTAiIHkxPSI3LjUiIHgyPSI1IiB5Mj0iNy41Ii8+CjxsaW5lIHN0eWxlPSJmaWxsOm5vbmU7c3Ryb2tlOiM2QjZCNkI7c3Ryb2tlLW1pdGVybGltaXQ6MTAiIHgxPSI3LjUiIHkxPSI1IiB4Mj0iNy41IiB5Mj0iMTAiLz4KPC9zdmc+);opacity:1}.monaco-editor.hc-black .margin-view-overlays .folding.collapsed,.monaco-editor.vs-dark .margin-view-overlays .folding.collapsed{background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm94PSIwIDAgMTUgMTUiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDE1IDE1OyI+CjxyZWN0IHg9IjMiIHk9IjMiIHN0eWxlPSJvcGFjaXR5OjAuMTtmaWxsOiNGRkZGRkYiIHdpZHRoPSI5IiBoZWlnaHQ9IjkiLz4KPHBhdGggc3R5bGU9ImZpbGw6IzVBNUE1QSIgZD0iTTExLDR2N0g0VjRIMTEgTTEyLDNIM3Y5aDlWM0wxMiwzeiIvPgo8bGluZSBzdHlsZT0iZmlsbDpub25lO3N0cm9rZTojQzVDNUM1O3N0cm9rZS1taXRlcmxpbWl0OjEwIiB4MT0iMTAiIHkxPSI3LjUiIHgyPSI1IiB5Mj0iNy41Ii8+CjxsaW5lIHN0eWxlPSJmaWxsOm5vbmU7c3Ryb2tlOiNDNUM1QzU7c3Ryb2tlLW1pdGVybGltaXQ6MTAiIHgxPSI3LjUiIHkxPSI1IiB4Mj0iNy41IiB5Mj0iMTAiLz4KPC9zdmc+)}.monaco-editor .inline-folded:after{color:grey;margin:.1em .2em 0;content:"⋯";display:inline;line-height:1em;cursor:pointer}.monaco-editor .goto-definition-link{text-decoration:underline;cursor:pointer}.monaco-editor .peekview-widget .head .peekview-title .icon.warning{background:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNiAxNiIgZW5hYmxlLWJhY2tncm91bmQ9Im5ldyAwIDAgMTYgMTYiIGhlaWdodD0iMTYiIHdpZHRoPSIxNiI+PHBhdGggZmlsbD0iI0Y2RjZGNiIgZD0iTTcuNSAyTDIgMTJsMiAyaDlsMi0yTDkuNSAyeiIvPjxwYXRoIGQ9Ik05IDNIOGwtNC41IDkgMSAxaDhsMS0xTDkgM3ptMCA5SDh2LTFoMXYxem0wLTJIOFY2aDF2NHoiIGZpbGw9IiNmYzAiLz48cGF0aCBkPSJNOSAxMEg4VjZoMXY0em0wIDFIOHYxaDF2LTF6Ii8+PC9zdmc+) 50% no-repeat}.monaco-editor .peekview-widget .head .peekview-title .icon.error{background:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNiAxNiIgZW5hYmxlLWJhY2tncm91bmQ9Im5ldyAwIDAgMTYgMTYiIGhlaWdodD0iMTYiIHdpZHRoPSIxNiI+PGNpcmNsZSBjeD0iOCIgY3k9IjgiIHI9IjYiIGZpbGw9IiNGNkY2RjYiLz48cGF0aCBkPSJNOCAzQzUuMjM4IDMgMyA1LjIzOCAzIDhzMi4yMzggNSA1IDUgNS0yLjIzOCA1LTUtMi4yMzgtNS01LTV6bTMgN2wtMSAxLTItMi0yIDItMS0xIDItMi4wMjdMNSA2bDEtMSAyIDIgMi0yIDEgMS0yIDEuOTczTDExIDEweiIgZmlsbD0iI0U1MTQwMCIvPjxwYXRoIGZpbGw9IiNmZmYiIGQ9Ik0xMSA2bC0xLTEtMiAyLTItMi0xIDEgMiAxLjk3M0w1IDEwbDEgMSAyLTIgMiAyIDEtMS0yLTIuMDI3eiIvPjwvc3ZnPg==) 50% no-repeat}.monaco-editor .peekview-widget .head .peekview-title .icon.info{background:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNiAxNiIgZW5hYmxlLWJhY2tncm91bmQ9Im5ldyAwIDAgMTYgMTYiIGhlaWdodD0iMTYiIHdpZHRoPSIxNiI+PGNpcmNsZSBjeD0iOC41IiBjeT0iNy41IiByPSI1LjUiIGZpbGw9IiNGNkY2RjYiLz48cGF0aCBkPSJNOC41IDNDNi4wMTUgMyA0IDUuMDE1IDQgNy41UzYuMDE1IDEyIDguNSAxMiAxMyA5Ljk4NSAxMyA3LjUgMTAuOTg1IDMgOC41IDN6bS41IDhIOFY2aDF2NXptMC02SDhWNGgxdjF6IiBmaWxsPSIjMUJBMUUyIi8+PHBhdGggZD0iTTggNmgxdjVIOFY2em0wLTJ2MWgxVjRIOHoiIGZpbGw9IiNmZmYiLz48L3N2Zz4=) 50% no-repeat}.vs-dark .monaco-editor .peekview-widget .head .peekview-title .icon.warning{background:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNiAxNiIgaGVpZ2h0PSIxNiIgd2lkdGg9IjE2Ij48cGF0aCBmaWxsPSIjMUUxRTFFIiBkPSJNNy41IDJMMiAxMmwyIDJoOWwyLTJMOS41IDJ6Ii8+PHBhdGggZD0iTTkgM0g4bC00LjUgOSAxIDFoOGwxLTFMOSAzem0wIDlIOHYtMWgxdjF6bTAtMkg4VjZoMXY0eiIgZmlsbD0iI2ZjMCIvPjxwYXRoIGQ9Ik05IDEwSDhWNmgxdjR6bTAgMUg4djFoMXYtMXoiLz48L3N2Zz4=) 50% no-repeat}.vs-dark .monaco-editor .peekview-widget .head .peekview-title .icon.error{background:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNiAxNiIgaGVpZ2h0PSIxNiIgd2lkdGg9IjE2Ij48Y2lyY2xlIGN4PSI4IiBjeT0iOCIgcj0iNiIgZmlsbD0iIzFFMUUxRSIvPjxwYXRoIGQ9Ik04IDNDNS4yMzggMyAzIDUuMjM4IDMgOHMyLjIzOCA1IDUgNSA1LTIuMjM4IDUtNS0yLjIzOC01LTUtNXptMyA3bC0xIDEtMi0yLTIgMi0xLTEgMi0yLjAyN0w1IDZsMS0xIDIgMiAyLTIgMSAxLTIgMS45NzNMMTEgMTB6IiBmaWxsPSIjRjQ4NzcxIi8+PHBhdGggZmlsbD0iIzI1MjUyNiIgZD0iTTExIDZsLTEtMS0yIDItMi0yLTEgMSAyIDEuOTczTDUgMTBsMSAxIDItMiAyIDIgMS0xLTItMi4wMjd6Ii8+PC9zdmc+) 50% no-repeat}.vs-dark .monaco-editor .peekview-widget .head .peekview-title .icon.info{background:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNiAxNiIgZW5hYmxlLWJhY2tncm91bmQ9Im5ldyAwIDAgMTYgMTYiIGhlaWdodD0iMTYiIHdpZHRoPSIxNiI+PGNpcmNsZSBjeD0iOC41IiBjeT0iNy41IiByPSI1LjUiIGZpbGw9IiMxRTFFMUUiLz48cGF0aCBkPSJNOC41IDNDNi4wMTUgMyA0IDUuMDE1IDQgNy41UzYuMDE1IDEyIDguNSAxMiAxMyA5Ljk4NSAxMyA3LjUgMTAuOTg1IDMgOC41IDN6bS41IDhIOFY2aDF2NXptMC02SDhWNGgxdjF6IiBmaWxsPSIjMUJBMUUyIi8+PHBhdGggZD0iTTggNmgxdjVIOFY2em0wLTJ2MWgxVjRIOHoiIGZpbGw9IiMyNTI1MjYiLz48L3N2Zz4=) 50% no-repeat}.monaco-editor .marker-widget{text-overflow:ellipsis;white-space:nowrap}.monaco-editor .marker-widget>.stale{opacity:.6;font-style:italic}.monaco-editor .marker-widget .title{display:inline-block;padding-right:5px}.monaco-editor .marker-widget .descriptioncontainer{position:absolute;white-space:pre;-webkit-user-select:text;user-select:text;padding:8px 12px 0 20px}.monaco-editor .marker-widget .descriptioncontainer .message{display:flex;flex-direction:column}.monaco-editor .marker-widget .descriptioncontainer .message .details{padding-left:6px}.monaco-editor .marker-widget .descriptioncontainer .message .code,.monaco-editor .marker-widget .descriptioncontainer .message .source{opacity:.6}.monaco-editor .marker-widget .descriptioncontainer .filename{cursor:pointer}.monaco-editor-hover{cursor:default;position:absolute;overflow:hidden;z-index:50;-webkit-user-select:text;-ms-user-select:text;-moz-user-select:text;-o-user-select:text;user-select:text;box-sizing:initial;animation:fadein .1s linear;line-height:1.5em}.monaco-editor-hover.hidden{display:none}.monaco-editor-hover .hover-contents{padding:4px 8px}.monaco-editor-hover .markdown-hover>.hover-contents:not(.code-hover-contents){max-width:500px}.monaco-editor-hover p,.monaco-editor-hover ul{margin:8px 0}.monaco-editor-hover hr{margin:4px -10px -6px;height:1px}.monaco-editor-hover p:first-child,.monaco-editor-hover ul:first-child{margin-top:0}.monaco-editor-hover p:last-child,.monaco-editor-hover ul:last-child{margin-bottom:0}.monaco-editor-hover ul{padding-left:20px}.monaco-editor-hover li>p{margin-bottom:0}.monaco-editor-hover li>ul{margin-top:0}.monaco-editor-hover code{border-radius:3px;padding:0 .4em}.monaco-editor-hover .monaco-tokenized-source{white-space:pre-wrap;word-break:break-all}.monaco-editor-hover .hover-row.status-bar{font-size:12px;line-height:22px}.monaco-editor-hover .hover-row.status-bar .actions{display:flex}.monaco-editor-hover .hover-row.status-bar .actions .action-container{margin:0 8px;cursor:pointer}.monaco-editor-hover .hover-row.status-bar .actions .action-container .action .icon{padding-right:4px}.monaco-editor .detected-link,.monaco-editor .detected-link-active{text-decoration:underline;text-underline-position:under}.monaco-editor .detected-link-active{cursor:pointer}.monaco-editor .monaco-editor-overlaymessage{padding-bottom:8px}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}.monaco-editor .monaco-editor-overlaymessage.fadeIn{animation:fadeIn .15s ease-out}@keyframes fadeOut{0%{opacity:1}to{opacity:0}}.monaco-editor .monaco-editor-overlaymessage.fadeOut{animation:fadeOut .1s ease-out}.monaco-editor .monaco-editor-overlaymessage .message{padding:1px 4px}.monaco-editor .monaco-editor-overlaymessage .anchor{width:0!important;height:0!important;border:8px solid transparent;z-index:1000;position:absolute}.monaco-editor .parameter-hints-widget{z-index:10;display:flex;flex-direction:column;line-height:1.5em}.monaco-editor .parameter-hints-widget>.wrapper{max-width:440px;display:flex;flex-direction:column}.monaco-editor .parameter-hints-widget.multiple{min-height:3.3em;padding:0 0 0 1.9em}.monaco-editor .parameter-hints-widget.visible{transition:left .05s ease-in-out}.monaco-editor .parameter-hints-widget p,.monaco-editor .parameter-hints-widget ul{margin:8px 0}.monaco-editor .parameter-hints-widget .body,.monaco-editor .parameter-hints-widget .monaco-scrollable-element{display:flex;flex-direction:column}.monaco-editor .parameter-hints-widget .signature{padding:4px 5px}.monaco-editor .parameter-hints-widget .docs{padding:0 10px 0 5px;white-space:pre-wrap}.monaco-editor .parameter-hints-widget .docs .markdown-docs{white-space:normal}.monaco-editor .parameter-hints-widget .docs .code{white-space:pre-wrap}.monaco-editor .parameter-hints-widget .docs code{border-radius:3px;padding:0 .4em}.monaco-editor .parameter-hints-widget .buttons{position:absolute;display:none;bottom:0;left:0}.monaco-editor .parameter-hints-widget.multiple .buttons{display:block}.monaco-editor .parameter-hints-widget.multiple .button{position:absolute;left:2px;width:16px;height:16px;background-repeat:no-repeat;cursor:pointer}.monaco-editor .parameter-hints-widget .button.previous{bottom:24px;background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNiAxNiI+PHBhdGggZmlsbD0iIzQyNDI0MiIgZD0iTTEwLjggOS41bC45LS45TDguMSA1IDQuMiA4LjZsLjkuOSAzLTIuNyAyLjcgMi43eiIvPjwvc3ZnPg==)}.monaco-editor .parameter-hints-widget .button.next{bottom:0;background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNiAxNiI+PHBhdGggZmlsbD0iIzQyNDI0MiIgZD0iTTUuMSA1bC0uOS45IDMuNiAzLjYgMy45LTMuNi0xLS45LTMgMi43TDUuMSA1eiIvPjwvc3ZnPg==)}.monaco-editor .parameter-hints-widget .overloads{position:absolute;display:none;text-align:center;bottom:14px;left:0;width:22px;height:12px;line-height:12px;opacity:.5}.monaco-editor .parameter-hints-widget.multiple .overloads{display:block}.monaco-editor .parameter-hints-widget .signature .parameter.active{font-weight:700;text-decoration:underline}.monaco-editor .parameter-hints-widget .documentation-parameter>.parameter{font-weight:700;margin-right:.5em}.monaco-editor.hc-black .parameter-hints-widget .button.previous,.monaco-editor.vs-dark .parameter-hints-widget .button.previous{background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNiAxNiI+PHBhdGggZmlsbD0iI0U4RThFOCIgZD0iTTEwLjggOS41bC45LS45TDguMSA1IDQuMiA4LjZsLjkuOSAzLTIuNyAyLjcgMi43eiIvPjwvc3ZnPg==)}.monaco-editor.hc-black .parameter-hints-widget .button.next,.monaco-editor.vs-dark .parameter-hints-widget .button.next{background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNiAxNiI+PHBhdGggZmlsbD0iI0U4RThFOCIgZD0iTTUuMSA1bC0uOS45IDMuNiAzLjYgMy45LTMuNi0xLS45LTMgMi43TDUuMSA1eiIvPjwvc3ZnPg==)}.monaco-editor .peekview-widget .head{-o-box-sizing:border-box;-ms-box-sizing:border-box;box-sizing:border-box;display:flex}.monaco-editor .peekview-widget .head .peekview-title{display:inline-block;font-size:13px;margin-left:20px;cursor:pointer}.monaco-editor .peekview-widget .head .peekview-title .icon{display:inline-block;height:16px;width:16px;vertical-align:text-bottom;margin-right:4px}.monaco-editor .peekview-widget .head .peekview-title .dirname:not(:empty){font-size:.9em;margin-left:.5em}.monaco-editor .peekview-widget .head .peekview-actions{flex:1;text-align:right;padding-right:2px}.monaco-editor .peekview-widget .head .peekview-actions>.monaco-action-bar{display:inline-block}.monaco-editor .peekview-widget .head .peekview-actions>.monaco-action-bar,.monaco-editor .peekview-widget .head .peekview-actions>.monaco-action-bar>.actions-container{height:100%}.monaco-editor .peekview-widget .head .peekview-actions>.monaco-action-bar .action-item{margin-left:4px}.monaco-editor .peekview-widget .head .peekview-actions>.monaco-action-bar .action-label{width:16px;height:100%;margin:0;line-height:inherit;background-repeat:no-repeat;background-position:50%}.monaco-editor .peekview-widget .head .peekview-actions>.monaco-action-bar .action-label.octicon{margin:0}.monaco-editor .peekview-widget .head .peekview-actions .action-label.icon.close-peekview-action{background:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgdmlld0JveD0iMyAzIDE2IDE2IiBlbmFibGUtYmFja2dyb3VuZD0ibmV3IDMgMyAxNiAxNiI+PHBvbHlnb24gZmlsbD0iIzQyNDI0MiIgcG9pbnRzPSIxMi41OTcsMTEuMDQyIDE1LjQsMTMuODQ1IDEzLjg0NCwxNS40IDExLjA0MiwxMi41OTggOC4yMzksMTUuNCA2LjY4MywxMy44NDUgOS40ODUsMTEuMDQyIDYuNjgzLDguMjM5IDguMjM4LDYuNjgzIDExLjA0Miw5LjQ4NiAxMy44NDUsNi42ODMgMTUuNCw4LjIzOSIvPjwvc3ZnPg==) 50% no-repeat}.monaco-editor .peekview-widget>.body{border-top:1px solid;position:relative}.monaco-editor.hc-black .peekview-widget .head .peekview-actions .action-label.icon.close-peekview-action,.monaco-editor.vs-dark .peekview-widget .head .peekview-actions .action-label.icon.close-peekview-action{background:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgdmlld0JveD0iMyAzIDE2IDE2IiBlbmFibGUtYmFja2dyb3VuZD0ibmV3IDMgMyAxNiAxNiI+PHBvbHlnb24gZmlsbD0iI2U4ZThlOCIgcG9pbnRzPSIxMi41OTcsMTEuMDQyIDE1LjQsMTMuODQ1IDEzLjg0NCwxNS40IDExLjA0MiwxMi41OTggOC4yMzksMTUuNCA2LjY4MywxMy44NDUgOS40ODUsMTEuMDQyIDYuNjgzLDguMjM5IDguMjM4LDYuNjgzIDExLjA0Miw5LjQ4NiAxMy44NDUsNi42ODMgMTUuNCw4LjIzOSIvPjwvc3ZnPg==) 50% no-repeat}.monaco-editor .peekview-widget .peekview-actions .icon.chevron-up{background:url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPGcgY2xpcC1wYXRoPSJ1cmwoI2NsaXAwKSI+CjxwYXRoIGQ9Ik0xNCAxMC41TDEyLjk0NTMgMTEuNTU1N0w4IDYuNjEwNDVMMy4wNTQ3MyAxMS41NTU3TDIgMTAuNUw4IDQuNUwxNCAxMC41WiIgZmlsbD0iIzRCNEI0QiIvPgo8L2c+CjxkZWZzPgo8Y2xpcFBhdGggaWQ9ImNsaXAwIj4KPHJlY3Qgd2lkdGg9IjEyIiBoZWlnaHQ9IjEyIiBmaWxsPSJ3aGl0ZSIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMiAyKSIvPgo8L2NsaXBQYXRoPgo8L2RlZnM+Cjwvc3ZnPgo=) 50% no-repeat}.vs-dark .monaco-editor .peekview-widget .peekview-actions .icon.chevron-up{background:url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPGcgY2xpcC1wYXRoPSJ1cmwoI2NsaXAwKSI+CjxwYXRoIGQ9Ik0xNCAxMC41TDEyLjk0NTMgMTEuNTU1N0w4IDYuNjEwNDVMMy4wNTQ3MyAxMS41NTU3TDIgMTAuNUw4IDQuNUwxNCAxMC41WiIgZmlsbD0iI0M4QzhDOCIvPgo8L2c+CjxkZWZzPgo8Y2xpcFBhdGggaWQ9ImNsaXAwIj4KPHJlY3Qgd2lkdGg9IjEyIiBoZWlnaHQ9IjEyIiBmaWxsPSJ3aGl0ZSIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMiAyKSIvPgo8L2NsaXBQYXRoPgo8L2RlZnM+Cjwvc3ZnPgo=) 50% no-repeat}.hc-black .monaco-editor .peekview-widget .peekview-actions .icon.chevron-up{background:url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPGcgY2xpcC1wYXRoPSJ1cmwoI2NsaXAwKSI+CjxwYXRoIGQ9Ik0xNCAxMC41TDEyLjk0NTMgMTEuNTU1N0w4IDYuNjEwNDVMMy4wNTQ3MyAxMS41NTU3TDIgMTAuNUw4IDQuNUwxNCAxMC41WiIgZmlsbD0id2hpdGUiLz4KPC9nPgo8ZGVmcz4KPGNsaXBQYXRoIGlkPSJjbGlwMCI+CjxyZWN0IHdpZHRoPSIxMiIgaGVpZ2h0PSIxMiIgZmlsbD0id2hpdGUiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDIgMikiLz4KPC9jbGlwUGF0aD4KPC9kZWZzPgo8L3N2Zz4K) 50% no-repeat}.monaco-editor .peekview-widget .peekview-actions .icon.chevron-down{background:url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPGcgY2xpcC1wYXRoPSJ1cmwoI2NsaXAwKSI+CjxwYXRoIGQ9Ik0yIDUuNUwzLjA1NDczIDQuNDQ0MjhMOCA5LjM4OTU1TDEyLjk0NTMgNC40NDQyOEwxNCA1LjVMOCAxMS41TDIgNS41WiIgZmlsbD0iIzRCNEI0QiIvPgo8L2c+CjxkZWZzPgo8Y2xpcFBhdGggaWQ9ImNsaXAwIj4KPHJlY3Qgd2lkdGg9IjEyIiBoZWlnaHQ9IjEyIiBmaWxsPSJ3aGl0ZSIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMiAyKSIvPgo8L2NsaXBQYXRoPgo8L2RlZnM+Cjwvc3ZnPgo=) 50% no-repeat}.vs-dark .monaco-editor .peekview-widget .peekview-actions .icon.chevron-down{background:url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPGcgY2xpcC1wYXRoPSJ1cmwoI2NsaXAwKSI+CjxwYXRoIGQ9Ik0yIDUuNUwzLjA1NDczIDQuNDQ0MjhMOCA5LjM4OTU1TDEyLjk0NTMgNC40NDQyOEwxNCA1LjVMOCAxMS41TDIgNS41WiIgZmlsbD0iI0M4QzhDOCIvPgo8L2c+CjxkZWZzPgo8Y2xpcFBhdGggaWQ9ImNsaXAwIj4KPHJlY3Qgd2lkdGg9IjEyIiBoZWlnaHQ9IjEyIiBmaWxsPSJ3aGl0ZSIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMiAyKSIvPgo8L2NsaXBQYXRoPgo8L2RlZnM+Cjwvc3ZnPgo=) 50% no-repeat}.hc-black .monaco-editor .peekview-widget .peekview-actions .icon.chevron-down{background:url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPGcgY2xpcC1wYXRoPSJ1cmwoI2NsaXAwKSI+CjxwYXRoIGQ9Ik0yIDUuNUwzLjA1NDczIDQuNDQ0MjhMOCA5LjM4OTU1TDEyLjk0NTMgNC40NDQyOEwxNCA1LjVMOCAxMS41TDIgNS41WiIgZmlsbD0id2hpdGUiLz4KPC9nPgo8ZGVmcz4KPGNsaXBQYXRoIGlkPSJjbGlwMCI+CjxyZWN0IHdpZHRoPSIxMiIgaGVpZ2h0PSIxMiIgZmlsbD0id2hpdGUiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDIgMikiLz4KPC9jbGlwUGF0aD4KPC9kZWZzPgo8L3N2Zz4K) 50% no-repeat}.monaco-editor .zone-widget .zone-widget-container.reference-zone-widget{border-top-width:1px;border-bottom-width:1px}.monaco-editor .reference-zone-widget .inline{display:inline-block;vertical-align:top}.monaco-editor .reference-zone-widget .messages{height:100%;width:100%;text-align:center;padding:3em 0}.monaco-editor .reference-zone-widget .ref-tree{line-height:23px}.monaco-editor .reference-zone-widget .ref-tree .reference{text-overflow:ellipsis;overflow:hidden}.monaco-editor .reference-zone-widget .ref-tree .reference-file{display:inline-flex;width:100%;height:100%}.monaco-editor .reference-zone-widget .ref-tree .monaco-list:focus .selected .reference-file{color:inherit!important}.monaco-editor .reference-zone-widget .ref-tree .reference-file .count{margin-right:12px;margin-left:auto}.monaco-editor.hc-black .reference-zone-widget .ref-tree .reference-file{font-weight:700}.monaco-editor .rename-box{z-index:100;color:inherit}.monaco-editor .rename-box .rename-input{padding:4px}.monaco-editor .snippet-placeholder{min-width:2px}.monaco-editor .finish-snippet-placeholder,.monaco-editor .snippet-placeholder{outline-style:solid;outline-width:1px}.monaco-editor .suggest-widget{z-index:40;width:430px}.monaco-editor .suggest-widget>.details,.monaco-editor .suggest-widget>.message,.monaco-editor .suggest-widget>.tree{width:100%;border-style:solid;border-width:1px;box-sizing:border-box}.monaco-editor.hc-black .suggest-widget>.details,.monaco-editor.hc-black .suggest-widget>.message,.monaco-editor.hc-black .suggest-widget>.tree{border-width:2px}.monaco-editor .suggest-widget.docs-side{width:660px}.monaco-editor .suggest-widget.docs-side>.details,.monaco-editor .suggest-widget.docs-side>.tree{width:50%;float:left}.monaco-editor .suggest-widget.docs-side.list-right>.details,.monaco-editor .suggest-widget.docs-side.list-right>.tree{float:right}.monaco-editor .suggest-widget>.message{padding-left:22px}.monaco-editor .suggest-widget>.tree{height:100%}.monaco-editor .suggest-widget .monaco-list{-webkit-user-select:none;-moz-user-select:-moz-none;-ms-user-select:none;-o-user-select:none;user-select:none}.monaco-editor .suggest-widget .monaco-list .monaco-list-row{display:flex;-mox-box-sizing:border-box;box-sizing:border-box;padding-right:10px;background-repeat:no-repeat;background-position:2px 2px;white-space:nowrap;cursor:pointer;touch-action:none}.monaco-editor .suggest-widget .monaco-list .monaco-list-row>.contents{flex:1;height:100%;overflow:hidden;padding-left:2px}.monaco-editor .suggest-widget .monaco-list .monaco-list-row>.contents>.main{display:flex;overflow:hidden;text-overflow:ellipsis;white-space:pre}.monaco-editor .suggest-widget:not(.frozen) .monaco-highlighted-label .highlight{font-weight:700}.monaco-editor .suggest-widget .details>.monaco-scrollable-element>.body>.header>.close,.monaco-editor .suggest-widget .monaco-list .monaco-list-row>.contents>.main>.readMore{opacity:.6;background-position:50%;background-repeat:no-repeat;background-size:70%;cursor:pointer}.monaco-editor .suggest-widget .details>.monaco-scrollable-element>.body>.header>.close{background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgdmlld0JveD0iMyAzIDE2IDE2IiBlbmFibGUtYmFja2dyb3VuZD0ibmV3IDMgMyAxNiAxNiI+PHBvbHlnb24gZmlsbD0iIzQyNDI0MiIgcG9pbnRzPSIxMi41OTcsMTEuMDQyIDE1LjQsMTMuODQ1IDEzLjg0NCwxNS40IDExLjA0MiwxMi41OTggOC4yMzksMTUuNCA2LjY4MywxMy44NDUgOS40ODUsMTEuMDQyIDYuNjgzLDguMjM5IDguMjM4LDYuNjgzIDExLjA0Miw5LjQ4NiAxMy44NDUsNi42ODMgMTUuNCw4LjIzOSIvPjwvc3ZnPg==);float:right;margin-right:5px}.monaco-editor .suggest-widget .monaco-list .monaco-list-row>.contents>.main>.readMore{background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiI+PHBhdGggZD0iTTggMWMtMy44NjUgMC03IDMuMTM1LTcgN3MzLjEzNSA3IDcgNyA3LTMuMTM1IDctNy0zLjEzNS03LTctN3ptMSAxMmgtMnYtN2gydjd6bTAtOGgtMnYtMmgydjJ6IiBmaWxsPSIjMUJBMUUyIi8+PHBhdGggZD0iTTcgNmgydjdoLTJ2LTd6bTAtMWgydi0yaC0ydjJ6IiBmaWxsPSIjZmZmIi8+PC9zdmc+)}.monaco-editor .suggest-widget .details>.monaco-scrollable-element>.body>.header>.close:hover,.monaco-editor .suggest-widget .monaco-list .monaco-list-row>.contents>.main>.readMore:hover{opacity:1}.monaco-editor .suggest-widget .monaco-list .monaco-list-row>.contents>.main>.type-label{margin-left:.8em;flex:1;text-align:right;overflow:hidden;text-overflow:ellipsis;opacity:.7;white-space:nowrap}.monaco-editor .suggest-widget .monaco-list .monaco-list-row>.contents>.main>.type-label>.monaco-tokenized-source{display:inline}.monaco-editor .suggest-widget.docs-below .monaco-list .monaco-list-row.focused>.contents>.main>.readMore,.monaco-editor .suggest-widget.docs-side .monaco-list .monaco-list-row.focused>.contents>.main>.readMore,.monaco-editor .suggest-widget.docs-side .monaco-list .monaco-list-row.focused>.contents>.main>.type-label,.monaco-editor .suggest-widget .monaco-list .monaco-list-row>.contents>.main>.readMore,.monaco-editor .suggest-widget .monaco-list .monaco-list-row>.contents>.main>.type-label{display:none}.monaco-editor .suggest-widget .monaco-list .monaco-list-row.focused>.contents>.main>.readMore,.monaco-editor .suggest-widget .monaco-list .monaco-list-row.focused>.contents>.main>.type-label{display:inline}.monaco-editor .suggest-widget .monaco-list .monaco-list-row .monaco-icon-label:before{height:100%}.monaco-editor .suggest-widget .monaco-list .monaco-list-row .icon{display:block;height:16px;width:16px;margin-left:2px;background-repeat:no-repeat;background-size:80%;background-position:50%}.monaco-editor .suggest-widget .monaco-list .monaco-list-row .icon.hide,.monaco-editor .suggest-widget.no-icons .monaco-list .monaco-list-row .icon,.monaco-editor .suggest-widget.no-icons .monaco-list .monaco-list-row .monaco-icon-label.suggest-icon:before{display:none}.monaco-editor .suggest-widget .monaco-list .monaco-list-row .monaco-icon-label.suggest-icon:before{content:" ";background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNiAxNiI+PHN0eWxlPi5pY29uLWNhbnZhcy10cmFuc3BhcmVudHtvcGFjaXR5OjA7ZmlsbDojZjZmNmY2fS5pY29uLXZzLW91dHtmaWxsOiNmNmY2ZjZ9Lmljb24tdnMtYmd7ZmlsbDojNDI0MjQyfTwvc3R5bGU+PHBhdGggY2xhc3M9Imljb24tY2FudmFzLXRyYW5zcGFyZW50IiBkPSJNMTYgMTZIMFYwaDE2djE2eiIgaWQ9ImNhbnZhcyIvPjxwYXRoIGNsYXNzPSJpY29uLXZzLW91dCIgZD0iTTE2IDEwYzAgMi4yMDUtMS43OTQgNC00IDQtMS44NTggMC0zLjQxMS0xLjI3OS0zLjg1OC0zaC0uOTc4bDIuMzE4IDRIMHYtMS43MDNsMi0zLjQwOFYwaDExdjYuMTQyYzEuNzIxLjQ0NyAzIDIgMyAzLjg1OHoiIGlkPSJvdXRsaW5lIi8+PHBhdGggY2xhc3M9Imljb24tdnMtYmciIGQ9Ik0xMiAxdjQuNzVBNC4yNTUgNC4yNTUgMCAwIDAgNy43NSAxMGgtLjczMkw0LjI3NSA1LjI2OSAzIDcuNDQyVjFoOXpNNy43NDcgMTRMNC4yNjkgOCAuNzQ4IDE0aDYuOTk5ek0xNSAxMGEzIDMgMCAxIDEtNiAwIDMgMyAwIDAgMSA2IDB6IiBpZD0iaWNvbkJnIi8+PC9zdmc+);background-repeat:no-repeat;background-position:50%;background-size:75%}.monaco-editor .suggest-widget .monaco-list .monaco-list-row .suggest-icon.constructor:before,.monaco-editor .suggest-widget .monaco-list .monaco-list-row .suggest-icon.function:before,.monaco-editor .suggest-widget .monaco-list .monaco-list-row .suggest-icon.method:before{background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNiAxNiI+PHN0eWxlPi5pY29uLWNhbnZhcy10cmFuc3BhcmVudHtvcGFjaXR5OjA7ZmlsbDojZjZmNmY2fS5pY29uLXZzLW91dHtmaWxsOiNmNmY2ZjZ9Lmljb24tdnMtZmd7ZmlsbDojZjBlZmYxfS5pY29uLXZzLWFjdGlvbi1wdXJwbGV7ZmlsbDojNjUyZDkwfTwvc3R5bGU+PHBhdGggY2xhc3M9Imljb24tY2FudmFzLXRyYW5zcGFyZW50IiBkPSJNMTYgMTZIMFYwaDE2djE2eiIgaWQ9ImNhbnZhcyIvPjxwYXRoIGNsYXNzPSJpY29uLXZzLW91dCIgZD0iTTE1IDMuMzQ5djguNDAzTDguOTc1IDE2SDguMDdMMSAxMS41ODJWMy4zMjdMNy41OTUgMGgxLjExOEwxNSAzLjM0OXoiIGlkPSJvdXRsaW5lIi8+PHBhdGggY2xhc3M9Imljb24tdnMtZmciIGQ9Ik0xMi43MTUgNC4zOThMOC40ODcgNy4wMiAzLjU2NSA0LjI3Mmw0LjU3OC0yLjMwOSA0LjU3MiAyLjQzNXpNMyA1LjEwMmw1IDIuNzkydjUuNzA1bC01LTMuMTI1VjUuMTAyem02IDguNDM0VjcuODc4bDQtMi40OHY1LjMxN2wtNCAyLjgyMXoiIGlkPSJpY29uRmciLz48cGF0aCBjbGFzcz0iaWNvbi12cy1hY3Rpb24tcHVycGxlIiBkPSJNOC4xNTYuODM3TDIgMy45NDJ2Ny4wODVMOC41MTcgMTUuMSAxNCAxMS4yMzNWMy45NUw4LjE1Ni44Mzd6bTQuNTU5IDMuNTYxTDguNDg3IDcuMDIgMy41NjUgNC4yNzJsNC41NzgtMi4zMDkgNC41NzIgMi40MzV6TTMgNS4xMDJsNSAyLjc5MnY1LjcwNWwtNS0zLjEyNVY1LjEwMnptNiA4LjQzNFY3Ljg3OGw0LTIuNDh2NS4zMTdsLTQgMi44MjF6IiBpZD0iaWNvbkJnIi8+PC9zdmc+)}.monaco-editor .suggest-widget .monaco-list .monaco-list-row .suggest-icon.field:before{background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNiAxNiI+PHN0eWxlPi5pY29uLWNhbnZhcy10cmFuc3BhcmVudHtvcGFjaXR5OjA7ZmlsbDojZjZmNmY2fS5pY29uLXZzLW91dHtmaWxsOiNmNmY2ZjZ9Lmljb24tdnMtZmd7ZmlsbDojZjBlZmYxfS5pY29uLXZzLWFjdGlvbi1ibHVle2ZpbGw6IzAwNTM5Y308L3N0eWxlPjxwYXRoIGNsYXNzPSJpY29uLWNhbnZhcy10cmFuc3BhcmVudCIgZD0iTTE2IDE2SDBWMGgxNnYxNnoiIGlkPSJjYW52YXMiLz48cGF0aCBjbGFzcz0iaWNvbi12cy1vdXQiIGQ9Ik0wIDEwLjczNlY0LjVMOSAwbDcgMy41djYuMjM2bC05IDQuNS03LTMuNXoiIGlkPSJvdXRsaW5lIi8+PHBhdGggY2xhc3M9Imljb24tdnMtYWN0aW9uLWJsdWUiIGQ9Ik05IDFMMSA1djVsNiAzIDgtNFY0TDkgMXpNNyA2Ljg4MkwzLjIzNiA1IDkgMi4xMTggMTIuNzY0IDQgNyA2Ljg4MnoiIGlkPSJpY29uQmciLz48cGF0aCBjbGFzcz0iaWNvbi12cy1mZyIgZD0iTTkgMi4xMThMMTIuNzY0IDQgNyA2Ljg4MiAzLjIzNiA1IDkgMi4xMTh6IiBpZD0iaWNvbkZnIi8+PC9zdmc+)}.monaco-editor .suggest-widget .monaco-list .monaco-list-row .suggest-icon.event:before{background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNiAxNiI+PHN0eWxlPi5pY29uLWNhbnZhcy10cmFuc3BhcmVudHtvcGFjaXR5OjA7ZmlsbDojZjZmNmY2fS5pY29uLXZzLW91dHtmaWxsOiNmNmY2ZjZ9Lmljb24tdnMtYWN0aW9uLW9yYW5nZXtmaWxsOiNjMjdkMWF9PC9zdHlsZT48cGF0aCBjbGFzcz0iaWNvbi1jYW52YXMtdHJhbnNwYXJlbnQiIGQ9Ik0xNiAxNkgwVjBoMTZ2MTZ6IiBpZD0iY2FudmFzIi8+PHBhdGggY2xhc3M9Imljb24tdnMtb3V0IiBkPSJNMTQgMS40MTRMOS40MTQgNkgxNHYxLjQxNEw1LjQxNCAxNkgzdi0xLjIzNEw1LjM3MSAxMEgyVjguNzY0TDYuMzgyIDBIMTR2MS40MTR6IiBpZD0ib3V0bGluZSIgc3R5bGU9ImRpc3BsYXk6IG5vbmU7Ii8+PHBhdGggY2xhc3M9Imljb24tdnMtYWN0aW9uLW9yYW5nZSIgZD0iTTcgN2g2bC04IDhINGwyLjk4NS02SDNsNC04aDZMNyA3eiIgaWQ9Imljb25CZyIvPjwvc3ZnPg==)}.monaco-editor .suggest-widget .monaco-list .monaco-list-row .suggest-icon.operator:before{background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNiAxNiI+PHN0eWxlPi5pY29uLWNhbnZhcy10cmFuc3BhcmVudHtvcGFjaXR5OjA7ZmlsbDojZjZmNmY2fS5pY29uLXZzLW91dHtmaWxsOiNmNmY2ZjZ9Lmljb24tdnMtZmd7ZmlsbDojZjBlZmYxfS5pY29uLXZzLWFjdGlvbi1ibHVle2ZpbGw6IzAwNTM5Y308L3N0eWxlPjxwYXRoIGNsYXNzPSJpY29uLWNhbnZhcy10cmFuc3BhcmVudCIgZD0iTTE2IDE2SDBWMGgxNnYxNnoiIGlkPSJjYW52YXMiLz48cGF0aCBjbGFzcz0iaWNvbi12cy1vdXQiIGQ9Ik0xNiAxNkgwVjBoMTZ2MTZ6IiBpZD0ib3V0bGluZSIgc3R5bGU9ImRpc3BsYXk6IG5vbmU7Ii8+PHBhdGggY2xhc3M9Imljb24tdnMtYWN0aW9uLWJsdWUiIGQ9Ik0xIDF2MTRoMTRWMUgxem02IDEySDN2LTFoNHYxem0wLTNIM1Y5aDR2MXptMC01SDV2Mkg0VjVIMlY0aDJWMmgxdjJoMnYxem0zLjI4MSA4SDguNzE5bDMtNGgxLjU2M2wtMy4wMDEgNHpNMTQgNUg5VjRoNXYxeiIgaWQ9Imljb25CZyIvPjxwYXRoIGNsYXNzPSJpY29uLXZzLWZnIiBkPSJNNyA1SDV2Mkg0VjVIMlY0aDJWMmgxdjJoMnYxem03LTFIOXYxaDVWNHpNNyA5SDN2MWg0Vjl6bTAgM0gzdjFoNHYtMXptMy4yODEgMWwzLTRoLTEuNTYzbC0zIDRoMS41NjN6IiBpZD0iaWNvbkZnIiBzdHlsZT0iZGlzcGxheTogbm9uZTsiLz48L3N2Zz4=)}.monaco-editor .suggest-widget .monaco-list .monaco-list-row .suggest-icon.variable:before{background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNiAxNiI+PHN0eWxlPi5pY29uLWNhbnZhcy10cmFuc3BhcmVudHtvcGFjaXR5OjA7ZmlsbDojZjZmNmY2fS5pY29uLXZzLW91dHtmaWxsOiNmNmY2ZjZ9Lmljb24tdnMtYmd7ZmlsbDojNDI0MjQyfS5pY29uLXZzLWZne2ZpbGw6I2YwZWZmMX0uaWNvbi12cy1hY3Rpb24tYmx1ZXtmaWxsOiMwMDUzOWN9PC9zdHlsZT48cGF0aCBjbGFzcz0iaWNvbi1jYW52YXMtdHJhbnNwYXJlbnQiIGQ9Ik0xNiAxNkgwVjBoMTZ2MTZ6IiBpZD0iY2FudmFzIi8+PHBhdGggY2xhc3M9Imljb24tdnMtb3V0IiBkPSJNMTEgM3YxLjAxNUw4LjczMyAyLjg4MiA1IDQuNzQ5VjNIMHYxMGg1di0xLjg1OWwyLjE1NiAxLjA3N0wxMSAxMC4yOTVWMTNoNVYzaC01eiIgaWQ9Im91dGxpbmUiIHN0eWxlPSJkaXNwbGF5OiBub25lOyIvPjxwYXRoIGNsYXNzPSJpY29uLXZzLWJnIiBkPSJNMiA1djZoMnYxSDFWNGgzdjFIMnptMTAgNnYxaDNWNGgtM3YxaDJ2NmgtMnoiIGlkPSJpY29uQmciLz48cGF0aCBjbGFzcz0iaWNvbi12cy1mZyIgZD0iTTcuMTU2IDcuMTU2bC0xLjU3OC0uNzg5IDMuMTU2LTEuNTc4IDEuNTc4Ljc4OS0zLjE1NiAxLjU3OHoiIGlkPSJpY29uRmciIHN0eWxlPSJkaXNwbGF5OiBub25lOyIvPjxwYXRoIGNsYXNzPSJpY29uLXZzLWFjdGlvbi1ibHVlIiBkPSJNOC43MzMgNEw0IDYuMzY3djMuMTU2TDcuMTU2IDExLjFsNC43MzMtMi4zNjdWNS41NzhMOC43MzMgNHpNNy4xNTYgNy4xNTZsLTEuNTc4LS43ODkgMy4xNTYtMS41NzggMS41NzguNzg5LTMuMTU2IDEuNTc4eiIgaWQ9ImNvbG9ySW1wb3J0YW5jZSIvPjwvc3ZnPg==)}.monaco-editor .suggest-widget .monaco-list .monaco-list-row .suggest-icon.class:before{background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNiAxNiI+PHN0eWxlPi5pY29uLWNhbnZhcy10cmFuc3BhcmVudHtvcGFjaXR5OjA7ZmlsbDojZjZmNmY2fS5pY29uLXZzLW91dHtmaWxsOiNmNmY2ZjZ9Lmljb24tdnMtYWN0aW9uLW9yYW5nZXtmaWxsOiNjMjdkMWF9PC9zdHlsZT48cGF0aCBjbGFzcz0iaWNvbi1jYW52YXMtdHJhbnNwYXJlbnQiIGQ9Ik0xNiAxNkgwVjBoMTZ2MTZ6IiBpZD0iY2FudmFzIi8+PHBhdGggY2xhc3M9Imljb24tdnMtb3V0IiBkPSJNMTYgNi41ODZsLTMtM0wxMS41ODYgNUg5LjQxNGwxLTEtNC00aC0uODI4TDAgNS41ODZ2LjgyOGw0IDRMNi40MTQgOEg3djVoMS41ODZsMyAzaC44MjhMMTYgMTIuNDE0di0uODI4TDEzLjkxNCA5LjUgMTYgNy40MTR2LS44Mjh6IiBpZD0ib3V0bGluZSIvPjxwYXRoIGNsYXNzPSJpY29uLXZzLWFjdGlvbi1vcmFuZ2UiIGQ9Ik0xMyAxMGwyIDItMyAzLTItMiAxLTFIOFY3SDZMNCA5IDEgNmw1LTUgMyAzLTIgMmg1bDEtMSAyIDItMyAzLTItMiAxLTFIOXY0bDIuOTk5LjAwMkwxMyAxMHoiIGlkPSJpY29uQmciLz48L3N2Zz4=)}.monaco-editor .suggest-widget .monaco-list .monaco-list-row .suggest-icon.interface:before{background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNiAxNiI+PHN0eWxlPi5pY29uLWNhbnZhcy10cmFuc3BhcmVudHtvcGFjaXR5OjA7ZmlsbDojZjZmNmY2fS5pY29uLXZzLW91dHtmaWxsOiNmNmY2ZjZ9Lmljb24tdnMtZmd7ZmlsbDojZjBlZmYxfS5pY29uLXZzLWFjdGlvbi1ibHVle2ZpbGw6IzAwNTM5Y308L3N0eWxlPjxwYXRoIGNsYXNzPSJpY29uLWNhbnZhcy10cmFuc3BhcmVudCIgZD0iTTE2IDE2SDBWMGgxNnYxNnoiIGlkPSJjYW52YXMiLz48cGF0aCBjbGFzcz0iaWNvbi12cy1vdXQiIGQ9Ik0xMS41IDEyYy0xLjkxNSAwLTMuNjAyLTEuMjQxLTQuMjI4LTNoLTEuNDFhMy4xMSAzLjExIDAgMCAxLTIuNzM3IDEuNjI1QzEuNDAyIDEwLjYyNSAwIDkuMjIzIDAgNy41czEuNDAyLTMuMTI1IDMuMTI1LTMuMTI1YzEuMTY1IDAgMi4yMDEuNjM5IDIuNzM3IDEuNjI1aDEuNDFjLjYyNi0xLjc1OSAyLjMxMy0zIDQuMjI4LTNDMTMuOTgxIDMgMTYgNS4wMTkgMTYgNy41UzEzLjk4MSAxMiAxMS41IDEyeiIgaWQ9Im91dGxpbmUiLz48cGF0aCBjbGFzcz0iaWNvbi12cy1mZyIgZD0iTTExLjUgOUExLjUwMSAxLjUwMSAwIDEgMSAxMyA3LjVjMCAuODI2LS42NzMgMS41LTEuNSAxLjV6IiBpZD0iaWNvbkZnIi8+PHBhdGggY2xhc3M9Imljb24tdnMtYWN0aW9uLWJsdWUiIGQ9Ik0xMS41IDRhMy40OSAzLjQ5IDAgMCAwLTMuNDUgM0g1LjE4NUEyLjEyMiAyLjEyMiAwIDAgMCAxIDcuNWEyLjEyMyAyLjEyMyAwIDEgMCA0LjE4NS41SDguMDVhMy40OSAzLjQ5IDAgMCAwIDMuNDUgMyAzLjUgMy41IDAgMSAwIDAtN3ptMCA1Yy0uODI3IDAtMS41LS42NzMtMS41LTEuNVMxMC42NzMgNiAxMS41IDZzMS41LjY3MyAxLjUgMS41UzEyLjMyNyA5IDExLjUgOXoiIGlkPSJpY29uQmciLz48L3N2Zz4=)}.monaco-editor .suggest-widget .monaco-list .monaco-list-row .suggest-icon.struct:before{background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNiAxNiI+PHN0eWxlPi5pY29uLWNhbnZhcy10cmFuc3BhcmVudHtvcGFjaXR5OjA7ZmlsbDojZjZmNmY2fS5pY29uLXZzLW91dHtmaWxsOiNmNmY2ZjZ9Lmljb24tdnMtYWN0aW9uLWJsdWV7ZmlsbDojMDA1MzljfTwvc3R5bGU+PHBhdGggY2xhc3M9Imljb24tY2FudmFzLXRyYW5zcGFyZW50IiBkPSJNMTYgMTZIMFYwaDE2djE2eiIgaWQ9ImNhbnZhcyIvPjxwYXRoIGNsYXNzPSJpY29uLXZzLW91dCIgZD0iTTkgMTRWOEg3djZIMVYyaDE0djEySDl6IiBpZD0ib3V0bGluZSIgc3R5bGU9ImRpc3BsYXk6IG5vbmU7Ii8+PHBhdGggY2xhc3M9Imljb24tdnMtYWN0aW9uLWJsdWUiIGQ9Ik0xMCA5aDR2NGgtNFY5em0tOCA0aDRWOUgydjR6TTIgM3Y0aDEyVjNIMnoiIGlkPSJpY29uQmciLz48L3N2Zz4=)}.monaco-editor .suggest-widget .monaco-list .monaco-list-row .suggest-icon.type-parameter:before{background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNiAxNiI+PHN0eWxlPi5pY29uLWNhbnZhcy10cmFuc3BhcmVudHtvcGFjaXR5OjA7ZmlsbDojZjZmNmY2fS5pY29uLXZzLW91dHtmaWxsOiNmNmY2ZjZ9Lmljb24tdnMtYmd7ZmlsbDojNDI0MjQyfTwvc3R5bGU+PHBhdGggY2xhc3M9Imljb24tY2FudmFzLXRyYW5zcGFyZW50IiBkPSJNMTYgMTZIMFYwaDE2djE2eiIgaWQ9ImNhbnZhcyIvPjxwYXRoIGNsYXNzPSJpY29uLXZzLW91dCIgZD0iTTEwLjcwMiAxMC41bDItMi0yLTIgLjUtLjVIMTB2NWgxdjNINXYtM2gxVjZINC43OThsLjUuNS0yIDIgMiAyTDMgMTIuNzk3bC0zLTNWNy4yMDFsMy0zVjJoMTB2Mi4yMDFsMyAzdjIuNTk2bC0zIDMtMi4yOTgtMi4yOTd6IiBpZD0ib3V0bGluZSIgc3R5bGU9ImRpc3BsYXk6IG5vbmU7Ii8+PHBhdGggY2xhc3M9Imljb24tdnMtYmciIGQ9Ik00IDNoOHYyaC0xdi0uNWMwLS4yNzctLjIyNC0uNS0uNS0uNUg5djcuNWMwIC4yNzUuMjI0LjUuNS41aC41djFINnYtMWguNWEuNS41IDAgMCAwIC41LS41VjRINS41YS41LjUgMCAwIDAtLjUuNVY1SDRWM3pNMyA1LjYxNUwuMTE2IDguNSAzIDExLjM4M2wuODg0LS44ODMtMi0yIDItMkwzIDUuNjE1em0xMCAwbC0uODg0Ljg4NSAyIDItMiAyIC44ODQuODgzTDE1Ljg4NCA4LjUgMTMgNS42MTV6IiBpZD0iaWNvbkJnIi8+PC9zdmc+)}.monaco-editor .suggest-widget .monaco-list .monaco-list-row .suggest-icon.module:before{background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNiAxNiI+PHN0eWxlPi5pY29uLWNhbnZhcy10cmFuc3BhcmVudHtvcGFjaXR5OjA7ZmlsbDojZjZmNmY2fS5pY29uLXZzLW91dHtmaWxsOiNmNmY2ZjZ9Lmljb24tdnMtYmd7ZmlsbDojNDI0MjQyfTwvc3R5bGU+PHBhdGggY2xhc3M9Imljb24tY2FudmFzLXRyYW5zcGFyZW50IiBkPSJNMTYgMTZIMFYwaDE2djE2eiIgaWQ9ImNhbnZhcyIvPjxwYXRoIGNsYXNzPSJpY29uLXZzLW91dCIgZD0iTTkuMjYgMTEuOTg0bC45NzgtLjAyMWEuOTYyLjk2MiAwIDAgMCAuMDktLjAwNmMuMDExLS4wNjMuMDI2LS4xNzkuMDI2LS4zNjFWOS42ODhjMC0uNjc5LjE4NS0xLjI1Ny41My0xLjcwNy0uMzQ2LS40NTItLjUzLTEuMDMtLjUzLTEuNzA1VjQuMzVjMC0uMTY3LS4wMjEtLjI1OS0uMDM0LS4zMDJMOS4yNiA0LjAyVi45NzNsMS4wMTEuMDExYzIuMTY3LjAyNCAzLjQwOSAxLjE1NiAzLjQwOSAzLjEwNXYxLjk2MmMwIC4zNTEuMDcxLjQ2MS4wNzIuNDYybC45MzYuMDYuMDUzLjkyN3YxLjkzNmwtLjkzNi4wNjFjLS4wNzYuMDE2LS4xMjUuMTQ2LS4xMjUuNDI0djIuMDE3YzAgLjkxNC0uMzMyIDMuMDQzLTMuNDA4IDMuMDc4bC0xLjAxMi4wMTF2LTMuMDQzem0tMy41MjEgMy4wMzJjLTMuMDg5LS4wMzUtMy40MjItMi4xNjQtMy40MjItMy4wNzhWOS45MjFjMC0uMzI3LS4wNjYtLjQzMi0uMDY3LS40MzNsLS45MzctLjA2LS4wNjMtLjkyOVY2LjU2M2wuOTQyLS4wNmMuMDU4IDAgLjEyNS0uMTE0LjEyNS0uNDUyVjQuMDljMC0xLjk0OSAxLjI0OC0zLjA4MSAzLjQyMi0zLjEwNUw2Ljc1Ljk3M1Y0LjAybC0uOTc1LjAyM2EuNTcyLjU3MiAwIDAgMC0uMDkzLjAxYy4wMDYuMDIxLS4wMTkuMTE1LS4wMTkuMjk3djEuOTI4YzAgLjY3NS0uMTg2IDEuMjUzLS41MzQgMS43MDUuMzQ4LjQ1LjUzNCAxLjAyOC41MzQgMS43MDd2MS45MDdjMCAuMTc1LjAxNC4yOTEuMDI3LjM2My4wMjMuMDAyIDEuMDYuMDI1IDEuMDYuMDI1djMuMDQzbC0xLjAxMS0uMDEyeiIgaWQ9Im91dGxpbmUiLz48cGF0aCBjbGFzcz0iaWNvbi12cy1iZyIgZD0iTTUuNzUgMTQuMDE2Yy0xLjYyMy0uMDE5LTIuNDM0LS43MTEtMi40MzQtMi4wNzhWOS45MjFjMC0uOTAyLS4zNTUtMS4zNzYtMS4wNjYtMS40MjJ2LS45OThjLjcxMS0uMDQ1IDEuMDY2LS41MjkgMS4wNjYtMS40NDlWNC4wOWMwLTEuMzg1LjgxMS0yLjA4NyAyLjQzNC0yLjEwNXYxLjA2Yy0uNzI1LjAxNy0xLjA4Ny40NTMtMS4wODcgMS4zMDV2MS45MjhjMCAuOTItLjQ1NCAxLjQ4OC0xLjM2IDEuNzAyVjhjLjkwNy4yMDEgMS4zNi43NjMgMS4zNiAxLjY4OHYxLjkwN2MwIC40ODguMDgxLjgzNS4yNDMgMS4wNDIuMTYyLjIwOC40NDMuMzE2Ljg0NC4zMjV2MS4wNTR6bTcuOTktNS41MTdjLS43MDYuMDQ1LTEuMDYuNTItMS4wNiAxLjQyMnYyLjAxN2MwIDEuMzY3LS44MDcgMi4wNi0yLjQyIDIuMDc4di0xLjA1M2MuMzk2LS4wMDkuNjc4LS4xMTguODQ0LS4zMjguMTY3LS4yMS4yNS0uNTU2LjI1LTEuMDM5VjkuNjg4YzAtLjkyNS40NDktMS40ODggMS4zNDctMS42ODh2LS4wMjFjLS44OTgtLjIxNC0xLjM0Ny0uNzgyLTEuMzQ3LTEuNzAyVjQuMzVjMC0uODUyLS4zNjQtMS4yODgtMS4wOTQtMS4zMDZ2LTEuMDZjMS42MTMuMDE4IDIuNDIuNzIgMi40MiAyLjEwNXYxLjk2MmMwIC45Mi4zNTQgMS40MDQgMS4wNiAxLjQ0OXYuOTk5eiIgaWQ9Imljb25CZyIvPjwvc3ZnPg==)}.monaco-editor .suggest-widget .monaco-list .monaco-list-row .suggest-icon.property:before{background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNiAxNiI+PHN0eWxlPi5pY29uLWNhbnZhcy10cmFuc3BhcmVudHtvcGFjaXR5OjA7ZmlsbDojZjZmNmY2fS5pY29uLXZzLW91dHtmaWxsOiNmNmY2ZjZ9Lmljb24tdnMtYmd7ZmlsbDojNDI0MjQyfTwvc3R5bGU+PHBhdGggY2xhc3M9Imljb24tY2FudmFzLXRyYW5zcGFyZW50IiBkPSJNMTYgMTZIMFYwaDE2djE2eiIgaWQ9ImNhbnZhcyIvPjxwYXRoIGNsYXNzPSJpY29uLXZzLW91dCIgZD0iTTE2IDUuNWE1LjUgNS41IDAgMCAxLTUuNSA1LjVjLS4yNzUgMC0uNTQzLS4wMjctLjgwNy0uMDY2bC0uMDc5LS4wMTJhNS40MjkgNS40MjkgMCAwIDEtLjgxLS4xOTJsLTQuNTM3IDQuNTM3Yy0uNDcyLjQ3My0xLjEuNzMzLTEuNzY3LjczM3MtMS4yOTUtLjI2LTEuNzY4LS43MzJhMi41MDIgMi41MDIgMCAwIDEgMC0zLjUzNWw0LjUzNy00LjUzN2E1LjQ1MiA1LjQ1MiAwIDAgMS0uMTkxLS44MTJjLS4wMDUtLjAyNS0uMDA4LS4wNTEtLjAxMi0uMDc3QTUuNTAzIDUuNTAzIDAgMCAxIDUgNS41YTUuNSA1LjUgMCAxIDEgMTEgMHoiIGlkPSJvdXRsaW5lIi8+PHBhdGggY2xhc3M9Imljb24tdnMtYmciIGQ9Ik0xNSA1LjVhNC41IDQuNSAwIDAgMS00LjUgNC41Yy0uNjkzIDAtMS4zNDItLjE3LTEuOTI5LS40NWwtNS4wMSA1LjAxYy0uMjkzLjI5NC0uNjc3LjQ0LTEuMDYxLjQ0cy0uNzY4LS4xNDYtMS4wNjEtLjQzOWExLjUgMS41IDAgMCAxIDAtMi4xMjFsNS4wMS01LjAxQTQuNDgzIDQuNDgzIDAgMCAxIDYgNS41IDQuNSA0LjUgMCAwIDEgMTAuNSAxYy42OTMgMCAxLjM0Mi4xNyAxLjkyOS40NUw5LjYzNiA0LjI0M2wyLjEyMSAyLjEyMSAyLjc5My0yLjc5M2MuMjguNTg3LjQ1IDEuMjM2LjQ1IDEuOTI5eiIgaWQ9Imljb25CZyIvPjwvc3ZnPg==)}.monaco-editor .suggest-widget .monaco-list .monaco-list-row .suggest-icon.unit:before{background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNiAxNiI+PHN0eWxlPi5pY29uLWNhbnZhcy10cmFuc3BhcmVudHtvcGFjaXR5OjA7ZmlsbDojZjZmNmY2fS5pY29uLXZzLW91dHtmaWxsOiNmNmY2ZjZ9Lmljb24tdnMtYmd7ZmlsbDojNDI0MjQyfS5pY29uLXZzLWZne2ZpbGw6I2YwZWZmMX08L3N0eWxlPjxwYXRoIGNsYXNzPSJpY29uLWNhbnZhcy10cmFuc3BhcmVudCIgZD0iTTE2IDE2SDBWMGgxNnYxNnoiIGlkPSJjYW52YXMiLz48cGF0aCBjbGFzcz0iaWNvbi12cy1vdXQiIGQ9Ik0xNiAxMS4wMTNIMVY0aDE1djcuMDEzeiIgaWQ9Im91dGxpbmUiLz48cGF0aCBjbGFzcz0iaWNvbi12cy1mZyIgZD0iTTggOUg3VjZoM3YzSDlWN0g4djJ6TTQgN2gxdjJoMVY2SDN2M2gxVjd6bTggMGgxdjJoMVY2aC0zdjNoMVY3eiIgaWQ9Imljb25GZyIvPjxwYXRoIGNsYXNzPSJpY29uLXZzLWJnIiBkPSJNMiA1djVoMTNWNUgyem00IDRINVY3SDR2MkgzVjZoM3Yzem00IDBIOVY3SDh2Mkg3VjZoM3Yzem00IDBoLTFWN2gtMXYyaC0xVjZoM3YzeiIgaWQ9Imljb25CZyIvPjwvc3ZnPg==)}.monaco-editor .suggest-widget .monaco-list .monaco-list-row .suggest-icon.constant:before{background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNiAxNiI+PHN0eWxlPi5pY29uLWNhbnZhcy10cmFuc3BhcmVudHtvcGFjaXR5OjA7ZmlsbDojZjZmNmY2fS5pY29uLXZzLW91dHtmaWxsOiNmNmY2ZjZ9Lmljb24tdnMtYmd7ZmlsbDojNDI0MjQyfS5pY29uLXZzLWZne2ZpbGw6I2YwZWZmMX0uaWNvbi12cy1hY3Rpb24tYmx1ZXtmaWxsOiMwMDUzOWN9PC9zdHlsZT48cGF0aCBjbGFzcz0iaWNvbi1jYW52YXMtdHJhbnNwYXJlbnQiIGQ9Ik0xNiAxNkgwVjBoMTZ2MTZ6IiBpZD0iY2FudmFzIi8+PHBhdGggY2xhc3M9Imljb24tdnMtb3V0IiBkPSJNMi44NzkgMTRMMSAxMi4xMjFWMy44NzlMMi44NzkgMmgxMC4yNDJMMTUgMy44Nzl2OC4yNDJMMTMuMTIxIDE0SDIuODc5eiIgaWQ9Im91dGxpbmUiLz48cGF0aCBjbGFzcz0iaWNvbi12cy1mZyIgZD0iTTEyLjI5MyA0SDMuNzA3TDMgNC43MDd2Ni41ODZsLjcwNy43MDdoOC41ODZsLjcwNy0uNzA3VjQuNzA3TDEyLjI5MyA0ek0xMSAxMEg1VjloNnYxem0wLTNINVY2aDZ2MXoiIGlkPSJpY29uRmciLz48ZyBpZD0iaWNvbkJnIj48cGF0aCBjbGFzcz0iaWNvbi12cy1iZyIgZD0iTTEyLjcwNyAxM0gzLjI5M0wyIDExLjcwN1Y0LjI5M0wzLjI5MyAzaDkuNDE0TDE0IDQuMjkzdjcuNDE0TDEyLjcwNyAxM3ptLTktMWg4LjU4NmwuNzA3LS43MDdWNC43MDdMMTIuMjkzIDRIMy43MDdMMyA0LjcwN3Y2LjU4NmwuNzA3LjcwN3oiLz48cGF0aCBjbGFzcz0iaWNvbi12cy1hY3Rpb24tYmx1ZSIgZD0iTTExIDdINVY2aDZ2MXptMCAySDV2MWg2Vjl6Ii8+PC9nPjwvc3ZnPg==)}.monaco-editor .suggest-widget .monaco-list .monaco-list-row .suggest-icon.enum:before,.monaco-editor .suggest-widget .monaco-list .monaco-list-row .suggest-icon.value:before{background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNiAxNiI+PHN0eWxlPi5pY29uLWNhbnZhcy10cmFuc3BhcmVudHtvcGFjaXR5OjA7ZmlsbDojZjZmNmY2fS5pY29uLXZzLW91dHtmaWxsOiNmNmY2ZjZ9Lmljb24tdnMtZmd7ZmlsbDojZjBlZmYxfS5pY29uLXZzLWFjdGlvbi1vcmFuZ2V7ZmlsbDojYzI3ZDFhfTwvc3R5bGU+PHBhdGggY2xhc3M9Imljb24tY2FudmFzLXRyYW5zcGFyZW50IiBkPSJNMTYgMTZIMFYwaDE2djE2eiIgaWQ9ImNhbnZhcyIvPjxwYXRoIGNsYXNzPSJpY29uLXZzLW91dCIgZD0iTTE0LjQxNCAxTDE2IDIuNTg2djUuODI4TDE0LjQxNCAxMEgxMHYzLjQxNkw4LjQxNCAxNUgxLjU4NkwwIDEzLjQxNnYtNS44M0wxLjU4NiA2SDZWMi41ODZMNy41ODYgMWg2LjgyOHoiIGlkPSJvdXRsaW5lIi8+PHBhdGggY2xhc3M9Imljb24tdnMtZmciIGQ9Ik0yIDEzaDZWOEgydjV6bTEtNGg0djFIM1Y5em0wIDJoNHYxSDN2LTF6bTExLTVWM0g4djNoLjQxNEw5IDYuNTg2VjZoNHYxSDkuNDE0bC41ODYuNTg2VjhoNFY2em0tMS0xSDlWNGg0djF6IiBpZD0iaWNvbkZnIi8+PHBhdGggY2xhc3M9Imljb24tdnMtYWN0aW9uLW9yYW5nZSIgZD0iTTMgMTFoNC4wMDF2MUgzdi0xem0wLTFoNC4wMDFWOUgzdjF6bTYtMnY1bC0xIDFIMmwtMS0xVjhsMS0xaDZsMSAxek04IDhIMnY1aDZWOHptMS0ybDEgMWgzVjZIOXptMC0xaDRWNEg5djF6bTUtM0g4TDcgM3YzaDFWM2g2djVoLTR2MWg0bDEtMVYzbC0xLTF6IiBpZD0iaWNvbkJnIi8+PC9zdmc+)}.monaco-editor .suggest-widget .monaco-list .monaco-list-row .suggest-icon.enum-member:before{background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNiAxNiI+PHN0eWxlPi5pY29uLWNhbnZhcy10cmFuc3BhcmVudHtvcGFjaXR5OjA7ZmlsbDojZjZmNmY2fS5pY29uLXZzLW91dHtmaWxsOiNmNmY2ZjZ9Lmljb24tdnMtZmd7ZmlsbDojZjBlZmYxfS5pY29uLXZzLWFjdGlvbi1ibHVle2ZpbGw6IzAwNTM5Y308L3N0eWxlPjxwYXRoIGNsYXNzPSJpY29uLWNhbnZhcy10cmFuc3BhcmVudCIgZD0iTTE2IDE2SDBWMGgxNnYxNnoiIGlkPSJjYW52YXMiLz48cGF0aCBjbGFzcz0iaWNvbi12cy1vdXQiIGQ9Ik0wIDE1VjZoNlYyLjU4Nkw3LjU4NSAxaDYuODI5TDE2IDIuNTg2djUuODI5TDE0LjQxNCAxMEgxMHY1SDB6bTMtNnoiIGlkPSJvdXRsaW5lIi8+PHBhdGggY2xhc3M9Imljb24tdnMtZmciIGQ9Ik04IDN2M2g1djFoLTN2MWg0VjNIOHptNSAySDlWNGg0djF6TTIgOHY1aDZWOEgyem01IDNIM3YtMWg0djF6IiBpZD0iaWNvbkZnIi8+PHBhdGggY2xhc3M9Imljb24tdnMtYWN0aW9uLWJsdWUiIGQ9Ik0xMCA2aDN2MWgtM1Y2ek05IDR2MWg0VjRIOXptNS0ySDhMNyAzdjNoMVYzaDZ2NWgtNHYxaDRsMS0xVjNsLTEtMXptLTcgOEgzdjFoNHYtMXptMi0zdjdIMVY3aDh6TTggOEgydjVoNlY4eiIgaWQ9Imljb25CZyIvPjwvc3ZnPg==)}.monaco-editor .suggest-widget .monaco-list .monaco-list-row .suggest-icon.keyword:before{background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNiAxNiI+PHN0eWxlPi5pY29uLWNhbnZhcy10cmFuc3BhcmVudHtvcGFjaXR5OjA7ZmlsbDojZjZmNmY2fS5pY29uLXZzLW91dHtmaWxsOiNmNmY2ZjZ9Lmljb24tdnMtYmd7ZmlsbDojNDI0MjQyfS5pY29uLXZzLWZne2ZpbGw6I2YwZWZmMX08L3N0eWxlPjxwYXRoIGNsYXNzPSJpY29uLWNhbnZhcy10cmFuc3BhcmVudCIgZD0iTTE2IDE2SDBWMGgxNnYxNnoiIGlkPSJjYW52YXMiLz48cGF0aCBjbGFzcz0iaWNvbi12cy1vdXQiIGQ9Ik0xNiA1VjJIOVYxSDB2MTRoMTN2LTNoM1Y5aC0xVjZIOVY1aDd6bS04IDdWOWgxdjNIOHoiIGlkPSJvdXRsaW5lIi8+PHBhdGggY2xhc3M9Imljb24tdnMtZmciIGQ9Ik0yIDNoNXYxSDJWM3oiIGlkPSJpY29uRmciLz48cGF0aCBjbGFzcz0iaWNvbi12cy1iZyIgZD0iTTE1IDRoLTVWM2g1djF6bS0xIDNoLTJ2MWgyVjd6bS00IDBIMXYxaDlWN3ptMiA2SDF2MWgxMXYtMXptLTUtM0gxdjFoNnYtMXptOCAwaC01djFoNXYtMXpNOCAydjNIMVYyaDd6TTcgM0gydjFoNVYzeiIgaWQ9Imljb25CZyIvPjwvc3ZnPg==)}.monaco-editor .suggest-widget .monaco-list .monaco-list-row .suggest-icon.text:before{background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNiAxNiI+PHN0eWxlPi5pY29uLWNhbnZhcy10cmFuc3BhcmVudHtvcGFjaXR5OjA7ZmlsbDojZjZmNmY2fS5pY29uLXZzLW91dHtmaWxsOiNmNmY2ZjZ9Lmljb24tdnMtYmd7ZmlsbDojNDI0MjQyfS5pY29uLXZzLWZne2ZpbGw6I2YwZWZmMX08L3N0eWxlPjxwYXRoIGNsYXNzPSJpY29uLWNhbnZhcy10cmFuc3BhcmVudCIgZD0iTTE2IDE2SDBWMGgxNnYxNnoiIGlkPSJjYW52YXMiLz48cGF0aCBjbGFzcz0iaWNvbi12cy1vdXQiIGQ9Ik0xNiAxNUgwVjFoMTZ2MTR6IiBpZD0ib3V0bGluZSIvPjxwYXRoIGNsYXNzPSJpY29uLXZzLWZnIiBkPSJNOS4yMjkgNy4zNTRjLjAzNS4xNDYuMDUyLjMxLjA1Mi40OTQgMCAuMjM0LS4wMi40NDEtLjA2LjYyMS0uMDM5LjE4LS4wOTUuMzI4LS4xNjguNDQ1YS42ODcuNjg3IDAgMCAxLS45MTQuMjgxLjc2Ljc2IDAgMCAxLS4yMzctLjIwNy45ODguOTg4IDAgMCAxLS4xNTQtLjMwNiAxLjI2MiAxLjI2MiAwIDAgMS0uMDU3LS4zODF2LS41MDZjMC0uMTcuMDItLjMyNi4wNjEtLjQ2NXMuMDk2LS4yNTguMTY4LS4zNTlhLjc1Ni43NTYgMCAwIDEgLjI1Ny0uMjMyYy4xLS4wNTUuMjEtLjA4Mi4zMzEtLjA4MmEuNjQ2LjY0NiAwIDAgMSAuNTcxLjMyYy4wNjcuMTA1LjExNi4yMy4xNS4zNzd6bS01LjEyNi44NjlhLjU1Ny41NTcgMCAwIDAtLjE5Ni4xMzJjLS4wNDcuMDUzLS4wOC4xMTItLjA5Ny4xOHMtLjAyOC4xNDctLjAyOC4yMzNhLjUxMy41MTMgMCAwIDAgLjE1Ny4zOS41MjguNTI4IDAgMCAwIC4xODYuMTEzLjY4Mi42ODIgMCAwIDAgLjI0Mi4wNDEuNzYuNzYgMCAwIDAgLjU5My0uMjcxLjg5Ny44OTcgMCAwIDAgLjE2NS0uMjk1Yy4wMzgtLjExMy4wNTktLjIzNC4wNTktLjM2NXYtLjM0NmwtLjc2MS4xMWExLjI5IDEuMjkgMCAwIDAtLjMyLjA3OHpNMTQgM3YxMEgyVjNoMTJ6TTUuOTYyIDcuNDY5YzAtLjIzOC0uMDI3LS40NTEtLjA4My0uNjM3YTEuMjg2IDEuMjg2IDAgMCAwLS4yNDktLjQ3MSAxLjA4IDEuMDggMCAwIDAtLjQyNC0uMjk1IDEuNjQ0IDEuNjQ0IDAgMCAwLS42MDgtLjEwMWMtLjExOSAwLS4yNDEuMDEyLS4zNjguMDMzYTMuMjEzIDMuMjEzIDAgMCAwLS42NzMuMTk1IDEuMzEzIDEuMzEzIDAgMCAwLS4yMTIuMTE0di43NjhjLjE1OC0uMTMyLjM0MS0uMjM1LjU0NC0uMzEzLjIwNC0uMDc4LjQxMy0uMTE3LjYyNy0uMTE3LjIxMyAwIC4zNzcuMDYzLjQ5NC4xODYuMTE2LjEyNS4xNzQuMzI0LjE3NC42bC0xLjAzLjE1NGMtLjIwNS4wMjYtLjM4LjA3Ny0uNTI2LjE1MWExLjA4MyAxLjA4MyAwIDAgMC0uNTYzLjY2QTEuNTYyIDEuNTYyIDAgMCAwIDMgOC44NTdjMCAuMTcuMDI1LjMyMy4wNzQuNDYzYS45NDUuOTQ1IDAgMCAwIC41NjguNTk2Yy4xMzkuMDU3LjI5Ny4wODQuNDc4LjA4NC4yMjkgMCAuNDMxLS4wNTMuNjA0LS4xNmExLjMgMS4zIDAgMCAwIC40MzktLjQ2M2guMDE0di41MjloLjc4NVY3LjQ2OXpNMTAgNy44NjFhMy41NCAzLjU0IDAgMCAwLS4wNzQtLjczNCAyLjA0NyAyLjA0NyAwIDAgMC0uMjI4LS42MTEgMS4yMDMgMS4yMDMgMCAwIDAtLjM5NC0uNDE2IDEuMDMgMS4wMyAwIDAgMC0uNTc0LS4xNTNjLS4xMjMgMC0uMjM0LjAxOC0uMzM2LjA1MWExIDEgMCAwIDAtLjI3OC4xNDcgMS4xNTMgMS4xNTMgMCAwIDAtLjIyNS4yMjIgMi4wMjIgMi4wMjIgMCAwIDAtLjE4MS4yODloLS4wMTNWNUg3djQuODg3aC42OTd2LS40ODVoLjAxM2MuMDQ0LjA4Mi4wOTUuMTU4LjE1MS4yMjkuMDU3LjA3LjExOS4xMzMuMTkxLjE4NmEuODM1LjgzNSAwIDAgMCAuMjM4LjEyMS45NDMuOTQzIDAgMCAwIC4yOTMuMDQyYy4yMyAwIC40MzQtLjA1My42MDktLjE2YTEuMzQgMS4zNCAwIDAgMCAuNDQzLS40NDNjLjEyLS4xODguMjExLS40MTIuMjcyLS42NzJBMy42MiAzLjYyIDAgMCAwIDEwIDcuODYxem0zLTEuNjU4YS43LjcgMCAwIDAtLjEwNi0uMDY2IDEuMTgzIDEuMTgzIDAgMCAwLS4xNDItLjA2MyAxLjIzMyAxLjIzMyAwIDAgMC0uMzYzLS4wNjVjLS4yMDkgMC0uMzk5LjA1MS0uNTY5LjE1YTEuMzU1IDEuMzU1IDAgMCAwLS40MzMuNDI0Yy0uMTE4LjE4Mi0uMjEuNDAyLS4yNzMuNjZhMy42MyAzLjYzIDAgMCAwLS4wMDggMS42MTVjLjA2LjIzLjE0My40My4yNTIuNjAyLjEwOS4xNjguMjQxLjMwMy4zOTYuMzk2YS45NzIuOTcyIDAgMCAwIC41MjQuMTQ0Yy4xNTggMCAuMjk2LS4wMjEuNDEzLS4wNjguMTE3LS4wNDUuMjE5LS4xMDguMzA5LS4xODR2LS43N2ExLjA5NCAxLjA5NCAwIDAgMS0uMjg4LjIyNS44MTkuODE5IDAgMCAxLS4xNTguMDY4LjQ4LjQ4IDAgMCAxLS4xNTMuMDI3LjYyLjYyIDAgMCAxLS4yNzQtLjA3NGMtLjI0MS0uMTM2LS40MjMtLjQ3OS0uNDIzLTEuMTQ2IDAtLjcxNS4yMDYtMS4xMi40NjktMS4zMDEuMDc3LS4wMzIuMTUzLS4wNjQuMjM4LS4wNjQuMTEzIDAgLjIyLjAyNy4zMTcuMDgyLjA5Ni4wNTcuMTg4LjEzMS4yNzIuMjIzdi0uODE1eiIgaWQ9Imljb25GZyIvPjxwYXRoIGNsYXNzPSJpY29uLXZzLWJnIiBkPSJNMSAydjEyaDE0VjJIMXptMTMgMTFIMlYzaDEydjEwek01LjYzIDYuMzYxYTEuMDggMS4wOCAwIDAgMC0uNDI0LS4yOTUgMS42NDQgMS42NDQgMCAwIDAtLjYwOC0uMTAxYy0uMTE5IDAtLjI0MS4wMTItLjM2OC4wMzNhMy4yMTMgMy4yMTMgMCAwIDAtLjY3My4xOTUgMS4zMTMgMS4zMTMgMCAwIDAtLjIxMi4xMTR2Ljc2OGMuMTU4LS4xMzIuMzQxLS4yMzUuNTQ0LS4zMTMuMjA0LS4wNzguNDEzLS4xMTcuNjI3LS4xMTcuMjEzIDAgLjM3Ny4wNjMuNDk0LjE4Ni4xMTYuMTI1LjE3NC4zMjQuMTc0LjZsLTEuMDMuMTU0Yy0uMjA1LjAyNi0uMzguMDc3LS41MjYuMTUxYTEuMDgzIDEuMDgzIDAgMCAwLS41NjMuNjZBMS41NjIgMS41NjIgMCAwIDAgMyA4Ljg1N2MwIC4xNy4wMjUuMzIzLjA3NC40NjNhLjk0NS45NDUgMCAwIDAgLjU2OC41OTZjLjEzOS4wNTcuMjk3LjA4NC40NzguMDg0LjIyOSAwIC40MzEtLjA1My42MDQtLjE2YTEuMyAxLjMgMCAwIDAgLjQzOS0uNDYzaC4wMTR2LjUyOWguNzg1VjcuNDY5YzAtLjIzOC0uMDI3LS40NTEtLjA4My0uNjM3YTEuMjg2IDEuMjg2IDAgMCAwLS4yNDktLjQ3MXptLS40NDYgMi4wMmMwIC4xMzEtLjAyLjI1Mi0uMDU5LjM2NWEuODk3Ljg5NyAwIDAgMS0uMTY1LjI5NS43NTguNzU4IDAgMCAxLS41OTMuMjcyLjY4Mi42ODIgMCAwIDEtLjI0Mi0uMDQxLjUwNy41MDcgMCAwIDEtLjMwMi0uMjg2LjU4My41ODMgMCAwIDEtLjA0MS0uMjE4YzAtLjA4Ni4wMS0uMTY0LjAyNy0uMjMycy4wNTEtLjEyNy4wOTgtLjE4YS41NDYuNTQ2IDAgMCAxIC4xOTYtLjEzM2MuMDgzLS4wMzMuMTg5LS4wNjEuMzItLjA3OGwuNzYxLS4xMDl2LjM0NXptNC41MTQtMS44NjVhMS4yMDMgMS4yMDMgMCAwIDAtLjM5NC0uNDE2IDEuMDMgMS4wMyAwIDAgMC0uNTc0LS4xNTNjLS4xMjMgMC0uMjM0LjAxOC0uMzM2LjA1MWExIDEgMCAwIDAtLjI3OC4xNDcgMS4xNTMgMS4xNTMgMCAwIDAtLjIyNS4yMjIgMi4wMjIgMi4wMjIgMCAwIDAtLjE4MS4yODloLS4wMTNWNUg3djQuODg3aC42OTd2LS40ODVoLjAxM2MuMDQ0LjA4Mi4wOTUuMTU4LjE1MS4yMjkuMDU3LjA3LjExOS4xMzMuMTkxLjE4NmEuODM1LjgzNSAwIDAgMCAuMjM4LjEyMS45NDMuOTQzIDAgMCAwIC4yOTMuMDQyYy4yMyAwIC40MzQtLjA1My42MDktLjE2YTEuMzQgMS4zNCAwIDAgMCAuNDQzLS40NDNjLjEyLS4xODguMjExLS40MTIuMjcyLS42NzJBMy42MiAzLjYyIDAgMCAwIDEwIDcuODYxYTMuNTQgMy41NCAwIDAgMC0uMDc0LS43MzQgMi4wNDcgMi4wNDcgMCAwIDAtLjIyOC0uNjExem0tLjQ3NiAxLjk1M2MtLjAzOS4xOC0uMDk1LjMyOC0uMTY4LjQ0NWEuNzU1Ljc1NSAwIDAgMS0uMjY0LjI2Ni42ODcuNjg3IDAgMCAxLS42NTEuMDE1Ljc2Ljc2IDAgMCAxLS4yMzctLjIwNy45ODguOTg4IDAgMCAxLS4xNTQtLjMwNiAxLjI2MiAxLjI2MiAwIDAgMS0uMDU3LS4zODF2LS41MDZjMC0uMTcuMDItLjMyNi4wNjEtLjQ2NXMuMDk2LS4yNTguMTY4LS4zNTlhLjc1Ni43NTYgMCAwIDEgLjI1Ny0uMjMyYy4xLS4wNTUuMjEtLjA4Mi4zMzEtLjA4MmEuNjQ2LjY0NiAwIDAgMSAuNTcxLjMyYy4wNjYuMTA1LjExNi4yMy4xNS4zNzcuMDM1LjE0Ni4wNTIuMzEuMDUyLjQ5NCAwIC4yMzQtLjAxOS40NDEtLjA1OS42MjF6bTMuNjcyLTIuMzMyYS43LjcgMCAwIDEgLjEwNi4wNjZ2LjgxNGExLjE3OCAxLjE3OCAwIDAgMC0uMjczLS4yMjMuNjQ1LjY0NSAwIDAgMC0uMzE3LS4wODFjLS4wODUgMC0uMTYxLjAzMi0uMjM4LjA2NC0uMjYzLjE4MS0uNDY5LjU4Ni0uNDY5IDEuMzAxIDAgLjY2OC4xODIgMS4wMTEuNDIzIDEuMTQ2LjA4NC4wNC4xNzEuMDc0LjI3NC4wNzQuMDQ5IDAgLjEwMS0uMDEuMTUzLS4wMjdhLjg1Ni44NTYgMCAwIDAgLjE1OC0uMDY4IDEuMTYgMS4xNiAwIDAgMCAuMjg4LS4yMjV2Ljc3Yy0uMDkuMDc2LS4xOTIuMTM5LS4zMDkuMTg0YTEuMDk4IDEuMDk4IDAgMCAxLS40MTIuMDY4Ljk3NC45NzQgMCAwIDEtLjUyMy0uMTQzIDEuMjU3IDEuMjU3IDAgMCAxLS4zOTYtLjM5NiAyLjA5OCAyLjA5OCAwIDAgMS0uMjUyLS42MDIgMy4xMTggMy4xMTggMCAwIDEtLjA4OC0uNzU0YzAtLjMxNi4wMzItLjYwNC4wOTYtLjg2MS4wNjMtLjI1OC4xNTUtLjQ3OS4yNzMtLjY2LjExOS0uMTgyLjI2NS0uMzIyLjQzMy0uNDI0YTEuMTAyIDEuMTAyIDAgMCAxIDEuMDczLS4wMjN6IiBpZD0iaWNvbkJnIi8+PC9zdmc+)}.monaco-editor .suggest-widget .monaco-list .monaco-list-row .suggest-icon.color:before{background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNiAxNiI+PHN0eWxlPi5pY29uLWNhbnZhcy10cmFuc3BhcmVudHtvcGFjaXR5OjA7ZmlsbDojZjZmNmY2fS5pY29uLXZzLW91dHtmaWxsOiNmNmY2ZjZ9Lmljb24tdnMtYmd7ZmlsbDojNDI0MjQyfS5pY29uLXZzLXJlZHtmaWxsOiNlNTE0MDB9Lmljb24tdnMteWVsbG93e2ZpbGw6I2ZmY2MwMH0uaWNvbi12cy1ncmVlbntmaWxsOiMzMzk5MzN9Lmljb24tdnMtYmx1ZXtmaWxsOiMxYmExZTJ9Lmljb24tdnMtYWN0aW9uLXB1cnBsZXtmaWxsOiM2NTJkOTB9Lmljb24td2hpdGV7ZmlsbDojZmZmZmZmfTwvc3R5bGU+PHBhdGggY2xhc3M9Imljb24tY2FudmFzLXRyYW5zcGFyZW50IiBkPSJNMTYgMTZIMFYwaDE2djE2eiIgaWQ9ImNhbnZhcyIvPjxwYXRoIGNsYXNzPSJpY29uLXZzLW91dCIgZD0iTTE2IDhjMCA0LjQxMS0zLjU4OSA4LTggOGEyLjgwMyAyLjgwMyAwIDAgMS0yLjgtMi44YzAtLjgzMy4yNzItMS42MjkuNzY2LTIuMjQxYS41OTYuNTk2IDAgMCAwIC4xMDEtLjM1OS42NjcuNjY3IDAgMCAwLS42NjctLjY2Ni41OC41OCAwIDAgMC0uMzU4LjEwMkEzLjU4NCAzLjU4NCAwIDAgMSAyLjggMTAuOCAyLjgwMyAyLjgwMyAwIDAgMSAwIDhjMC00LjQxMSAzLjU4OS04IDgtOHM4IDMuNTg5IDggOHoiIGlkPSJvdXRsaW5lIi8+PHBhdGggY2xhc3M9Imljb24td2hpdGUiIGQ9Ik01LjQgNy45MzNhMi42NyAyLjY3IDAgMCAxIDIuNjY3IDIuNjY2YzAgLjYwNi0uMTkzIDEuMTc5LS41NDQgMS42MTRhMS41OTkgMS41OTkgMCAwIDAtLjMyMy45ODcuOC44IDAgMCAwIC44LjhjMy4zMDkgMCA2LTIuNjkxIDYtNnMtMi42OTEtNi02LTYtNiAyLjY5MS02IDZjMCAuNDQxLjM1OS44LjguOC4zNzggMCAuNzI5LS4xMTQuOTg2LS4zMjJBMi41NjggMi41NjggMCAwIDEgNS40IDcuOTMzeiIgaWQ9Imljb25GZyIvPjxnIGlkPSJpY29uQmciPjxwYXRoIGNsYXNzPSJpY29uLXZzLWJnIiBkPSJNOCAxNWMtLjk5MiAwLTEuOC0uODA4LTEuOC0xLjggMC0uNjA2LjE5My0xLjE3OS41NDQtMS42MTMuMjA4LS4yNTkuMzIzLS42MDkuMzIzLS45ODcgMC0uOTE5LS43NDgtMS42NjYtMS42NjctMS42NjYtLjM3NyAwLS43MjguMTE1LS45ODYuMzIzQTIuNTggMi41OCAwIDAgMSAyLjggOS44QzEuODA4IDkuOCAxIDguOTkyIDEgOGMwLTMuODYgMy4xNC03IDctNyAzLjg1OSAwIDcgMy4xNCA3IDcgMCAzLjg1OS0zLjE0MSA3LTcgN3pNNS40IDcuOTMzYTIuNjcgMi42NyAwIDAgMSAyLjY2NyAyLjY2NmMwIC42MDYtLjE5MyAxLjE3OS0uNTQ0IDEuNjE0YTEuNTk5IDEuNTk5IDAgMCAwLS4zMjMuOTg3LjguOCAwIDAgMCAuOC44YzMuMzA5IDAgNi0yLjY5MSA2LTZzLTIuNjkxLTYtNi02LTYgMi42OTEtNiA2YzAgLjQ0MS4zNTkuOC44LjguMzc4IDAgLjcyOS0uMTE0Ljk4Ni0uMzIyQTIuNTY4IDIuNTY4IDAgMCAxIDUuNCA3LjkzM3oiLz48cGF0aCBjbGFzcz0iaWNvbi12cy1hY3Rpb24tcHVycGxlIiBkPSJNNC41IDUuMzc1YS44NzUuODc1IDAgMSAwIDAgMS43NS44NzUuODc1IDAgMCAwIDAtMS43NXoiLz48cGF0aCBjbGFzcz0iaWNvbi12cy1ibHVlIiBkPSJNNy4xMjUgMy42MjVhLjg3NS44NzUgMCAxIDAgMCAxLjc1Ljg3NS44NzUgMCAwIDAgMC0xLjc1eiIvPjxwYXRoIGNsYXNzPSJpY29uLXZzLWdyZWVuIiBkPSJNMTAuNjI1IDQuNWEuODc1Ljg3NSAwIDEgMCAwIDEuNzUuODc1Ljg3NSAwIDAgMCAwLTEuNzV6Ii8+PHBhdGggY2xhc3M9Imljb24tdnMteWVsbG93IiBkPSJNMTEuNSA4YS44NzUuODc1IDAgMSAwIDAgMS43NS44NzUuODc1IDAgMCAwIDAtMS43NXoiLz48cGF0aCBjbGFzcz0iaWNvbi12cy1yZWQiIGQ9Ik05Ljc1IDEwLjYyNWEuODc1Ljg3NSAwIDEgMCAwIDEuNzUuODc1Ljg3NSAwIDAgMCAwLTEuNzV6Ii8+PC9nPjwvc3ZnPg==)}.monaco-editor .suggest-widget .monaco-list .monaco-list-row .suggest-icon.file:before{background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNiAxNiI+PHN0eWxlPi5pY29uLWNhbnZhcy10cmFuc3BhcmVudHtvcGFjaXR5OjA7ZmlsbDojZjZmNmY2fS5pY29uLXZzLW91dHtmaWxsOiNmNmY2ZjZ9Lmljb24tdnMtYmd7ZmlsbDojNDI0MjQyfS5pY29uLXZzLWZne2ZpbGw6I2YwZWZmMX08L3N0eWxlPjxwYXRoIGNsYXNzPSJpY29uLWNhbnZhcy10cmFuc3BhcmVudCIgZD0iTTE2IDE2SDBWMGgxNnYxNnoiIGlkPSJjYW52YXMiLz48cGF0aCBjbGFzcz0iaWNvbi12cy1vdXQiIGQ9Ik0xNSAxNkgyVjBoOC42MjFMMTUgNC4zNzlWMTZ6IiBpZD0ib3V0bGluZSIvPjxwYXRoIGNsYXNzPSJpY29uLXZzLWZnIiBkPSJNMTMgMTRINFYyaDV2NGg0djh6bS0zLTlWMi4yMDdMMTIuNzkzIDVIMTB6IiBpZD0iaWNvbkZnIi8+PHBhdGggY2xhc3M9Imljb24tdnMtYmciIGQ9Ik0zIDF2MTRoMTFWNC43OTNMMTAuMjA3IDFIM3ptMTAgMTNINFYyaDV2NGg0djh6bS0zLTlWMi4yMDdMMTIuNzkzIDVIMTB6IiBpZD0iaWNvbkJnIi8+PC9zdmc+)}.monaco-editor .suggest-widget .monaco-list .monaco-list-row .suggest-icon.reference:before{background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNiAxNiI+PHN0eWxlPi5pY29uLWNhbnZhcy10cmFuc3BhcmVudHtvcGFjaXR5OjA7ZmlsbDojZjZmNmY2fS5pY29uLXZzLW91dHtmaWxsOiNmNmY2ZjZ9Lmljb24tdnMtYmd7ZmlsbDojNDI0MjQyfS5pY29uLXZzLWZne2ZpbGw6I2YwZWZmMX0uaWNvbi12cy1hY3Rpb24tYmx1ZXtmaWxsOiMwMDUzOWN9PC9zdHlsZT48cGF0aCBjbGFzcz0iaWNvbi1jYW52YXMtdHJhbnNwYXJlbnQiIGQ9Ik0xNiAxNkgwVjBoMTZ2MTZ6IiBpZD0iY2FudmFzIi8+PHBhdGggY2xhc3M9Imljb24tdnMtb3V0IiBkPSJNMTQgNC41NTZWMTNjMCAuOTctLjcwMSAyLTIgMkg0Yy0uOTcgMC0yLS43MDEtMi0yVjYuNjQ5QTMuNDk1IDMuNDk1IDAgMCAxIDAgMy41QzAgMS41NyAxLjU3IDAgMy41IDBINXYxaDUuMDYxTDE0IDQuNTU2eiIgaWQ9Im91dGxpbmUiIHN0eWxlPSJkaXNwbGF5OiBub25lOyIvPjxwYXRoIGNsYXNzPSJpY29uLXZzLWJnIiBkPSJNMTMgNXY4cy0uMDM1IDEtMS4wMzUgMWgtOFMzIDE0IDMgMTNWOWgxdjRoOFY2SDkuMzk3bC41MTctLjUyTDkgNC41NzJWM0g3LjQxOUw2LjQxMyAyaDMuMjI4TDEzIDV6IiBpZD0iaWNvbkJnIi8+PHBhdGggY2xhc3M9Imljb24tdnMtZmciIGQ9Ik03LjQxOSAzSDl2MS41NzJMNy40MTkgM3ptMS45NzggM0w2LjQxNiA5SDR2NGg4VjZIOS4zOTd6IiBpZD0iaWNvbkZnIiBzdHlsZT0iZGlzcGxheTogbm9uZTsiLz48cGF0aCBjbGFzcz0iaWNvbi12cy1hY3Rpb24tYmx1ZSIgZD0iTTUuOTg4IDZIMy41YTIuNSAyLjUgMCAxIDEgMC01SDR2MWgtLjVDMi42NzMgMiAyIDIuNjczIDIgMy41UzIuNjczIDUgMy41IDVoMi41MTNMNCAzaDJsMi41IDIuNDg0TDYgOEg0bDEuOTg4LTJ6IiBpZD0iY29sb3JBY3Rpb24iLz48L3N2Zz4=)}.monaco-editor .suggest-widget .monaco-list .monaco-list-row .suggest-icon.snippet:before{background-image:url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcKICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIgogICB4bWxuczpjYz0iaHR0cDovL2NyZWF0aXZlY29tbW9ucy5vcmcvbnMjIgogICB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiCiAgIHhtbG5zOnN2Zz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICAgdmVyc2lvbj0iMS4xIgogICBpZD0ic3ZnNDY5NCIKICAgdmlld0JveD0iMCAwIDE2IDE2Ij4KICA8bWV0YWRhdGEKICAgICBpZD0ibWV0YWRhdGE0NzA1Ij4KICAgIDxyZGY6UkRGPgogICAgICA8Y2M6V29yawogICAgICAgICByZGY6YWJvdXQ9IiI+CiAgICAgICAgPGRjOmZvcm1hdD5pbWFnZS9zdmcreG1sPC9kYzpmb3JtYXQ+CiAgICAgICAgPGRjOnR5cGUKICAgICAgICAgICByZGY6cmVzb3VyY2U9Imh0dHA6Ly9wdXJsLm9yZy9kYy9kY21pdHlwZS9TdGlsbEltYWdlIiAvPgogICAgICAgIDxkYzp0aXRsZT48L2RjOnRpdGxlPgogICAgICA8L2NjOldvcms+CiAgICA8L3JkZjpSREY+CiAgPC9tZXRhZGF0YT4KICA8ZGVmcwogICAgIGlkPSJkZWZzNDcwMyIgLz4KICA8c3R5bGUKICAgICBpZD0ic3R5bGU0Njk2Ij4uaWNvbi1jYW52YXMtdHJhbnNwYXJlbnR7b3BhY2l0eTowO2ZpbGw6I2Y2ZjZmNn0uaWNvbi12cy1vdXR7ZmlsbDojZjZmNmY2fS5pY29uLXZzLWFjdGlvbi1vcmFuZ2V7ZmlsbDojYzI3ZDFhfTwvc3R5bGU+CiAgPGcKICAgICBpZD0iZzQ3MDciCiAgICAgdHJhbnNmb3JtPSJtYXRyaXgoMS4zMzMzMzMzLDAsMCwxLjMzMzMzMzMsLTI0NS45OTk5OSwtNS4zMzMzMzMpIj4KICAgIDxwYXRoCiAgICAgICBkPSJtIDE4NSw0IDExLDAgMCwxMiAtMTEsMCB6IgogICAgICAgaWQ9InBhdGg0NTM0IgogICAgICAgc3R5bGU9ImZpbGw6I2Y2ZjZmNiIgLz4KICAgIDxwYXRoCiAgICAgICBkPSJtIDE5NCwxMyAwLC03IC03LDAgMCw3IC0xLDAgMCwtOCA5LDAgMCw4IC0xLDAgeiBtIC03LDIgLTEsMCAwLC0xIDEsMCAwLDEgeiBtIDIsLTEgLTEsMCAwLDEgMSwwIDAsLTEgeiBtIDIsMCAtMSwwIDAsMSAxLDAgMCwtMSB6IG0gMiwxIC0xLDAgMCwtMSAxLDAgMCwxIHogbSAyLC0xIC0xLDAgMCwxIDEsMCAwLC0xIHoiCiAgICAgICBpZD0icGF0aDQ1MzYiCiAgICAgICBzdHlsZT0iZmlsbDojNDI0MjQyIiAvPgogICAgPHBhdGgKICAgICAgIGQ9Im0gMTg3LDEzIDAsLTcgNywwIDAsNyAtNywwIHoiCiAgICAgICBpZD0icGF0aDQ1MzgiCiAgICAgICBzdHlsZT0iZmlsbDojZjBlZmYxIiAvPgogIDwvZz4KICA8cGF0aAogICAgIGlkPSJjYW52YXMiCiAgICAgZD0iTTE2IDE2SDBWMGgxNnYxNnoiCiAgICAgY2xhc3M9Imljb24tY2FudmFzLXRyYW5zcGFyZW50IiAvPgo8L3N2Zz4K)}.monaco-editor .suggest-widget .monaco-list .monaco-list-row .suggest-icon.customcolor:before{background-image:none}.monaco-editor .suggest-widget .monaco-list .monaco-list-row .suggest-icon.folder:before{background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiI+PHN0eWxlIHR5cGU9InRleHQvY3NzIj4uaWNvbi1jYW52YXMtdHJhbnNwYXJlbnR7b3BhY2l0eTowO2ZpbGw6I0Y2RjZGNjt9IC5pY29uLXZzLW91dHtvcGFjaXR5OjA7ZmlsbDojRjZGNkY2O30gLmljb24tdnMtZmd7ZmlsbDojRjBFRkYxO30gLmljb24tZm9sZGVye2ZpbGw6IzY1NjU2NTt9PC9zdHlsZT48cGF0aCBjbGFzcz0iaWNvbi1jYW52YXMtdHJhbnNwYXJlbnQiIGQ9Ik0xNiAxNmgtMTZ2LTE2aDE2djE2eiIgaWQ9ImNhbnZhcyIvPjxwYXRoIGNsYXNzPSJpY29uLXZzLW91dCIgZD0iTTE2IDIuNXYxMGMwIC44MjctLjY3MyAxLjUtMS41IDEuNWgtMTEuOTk2Yy0uODI3IDAtMS41LS42NzMtMS41LTEuNXYtOGMwLS44MjcuNjczLTEuNSAxLjUtMS41aDIuODg2bDEtMmg4LjExYy44MjcgMCAxLjUuNjczIDEuNSAxLjV6IiBpZD0ib3V0bGluZSIvPjxwYXRoIGNsYXNzPSJpY29uLWZvbGRlciIgZD0iTTE0LjUgMmgtNy40OTJsLTEgMmgtMy41MDRjLS4yNzcgMC0uNS4yMjQtLjUuNXY4YzAgLjI3Ni4yMjMuNS41LjVoMTEuOTk2Yy4yNzUgMCAuNS0uMjI0LjUtLjV2LTEwYzAtLjI3Ni0uMjI1LS41LS41LS41em0tLjQ5NiAyaC02LjQ5NmwuNS0xaDUuOTk2djF6IiBpZD0iaWNvbkJnIi8+PHBhdGggY2xhc3M9Imljb24tdnMtZmciIGQ9Ik0xNCAzdjFoLTYuNWwuNS0xaDZ6IiBpZD0iaWNvbkZnIi8+PC9zdmc+)}.monaco-editor .suggest-widget .monaco-list .monaco-list-row .icon.customcolor .colorspan{margin:0 0 0 .3em;border:.1em solid #000;width:.7em;height:.7em;display:inline-block}.monaco-editor .suggest-widget .details{display:flex;flex-direction:column;cursor:default}.monaco-editor .suggest-widget .details.no-docs{display:none}.monaco-editor .suggest-widget.docs-below .details{border-top-width:0}.monaco-editor .suggest-widget .details>.monaco-scrollable-element{flex:1}.monaco-editor .suggest-widget .details>.monaco-scrollable-element>.body{position:absolute;box-sizing:border-box;height:100%;width:100%}.monaco-editor .suggest-widget .details>.monaco-scrollable-element>.body>.header>.type{flex:2;overflow:hidden;text-overflow:ellipsis;opacity:.7;word-break:break-all;margin:0;padding:4px 0 12px 5px}.monaco-editor .suggest-widget .details>.monaco-scrollable-element>.body>.docs{margin:0;padding:4px 5px;white-space:pre-wrap}.monaco-editor .suggest-widget .details>.monaco-scrollable-element>.body>.docs.markdown-docs{padding:0;white-space:normal}.monaco-editor .suggest-widget .details>.monaco-scrollable-element>.body>.docs.markdown-docs>div,.monaco-editor .suggest-widget .details>.monaco-scrollable-element>.body>.docs.markdown-docs>span:not(:empty){padding:4px 5px}.monaco-editor .suggest-widget .details>.monaco-scrollable-element>.body>.docs.markdown-docs>div>p:first-child{margin-top:0}.monaco-editor .suggest-widget .details>.monaco-scrollable-element>.body>.docs.markdown-docs>div>p:last-child{margin-bottom:0}.monaco-editor .suggest-widget .details>.monaco-scrollable-element>.body>.docs .code{white-space:pre-wrap;word-wrap:break-word}.monaco-editor .suggest-widget .details>.monaco-scrollable-element>.body>p:empty{display:none}.monaco-editor .suggest-widget .details code{border-radius:3px;padding:0 .4em}.monaco-editor.hc-black .suggest-widget .details>.monaco-scrollable-element>.body>.header>.close,.monaco-editor.vs-dark .suggest-widget .details>.monaco-scrollable-element>.body>.header>.close{background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgdmlld0JveD0iMyAzIDE2IDE2IiBlbmFibGUtYmFja2dyb3VuZD0ibmV3IDMgMyAxNiAxNiI+PHBvbHlnb24gZmlsbD0iI2U4ZThlOCIgcG9pbnRzPSIxMi41OTcsMTEuMDQyIDE1LjQsMTMuODQ1IDEzLjg0NCwxNS40IDExLjA0MiwxMi41OTggOC4yMzksMTUuNCA2LjY4MywxMy44NDUgOS40ODUsMTEuMDQyIDYuNjgzLDguMjM5IDguMjM4LDYuNjgzIDExLjA0Miw5LjQ4NiAxMy44NDUsNi42ODMgMTUuNCw4LjIzOSIvPjwvc3ZnPg==)}.monaco-editor.hc-black .suggest-widget .monaco-list .monaco-list-row .suggest-icon:before,.monaco-editor.vs-dark .suggest-widget .monaco-list .monaco-list-row .suggest-icon:before{background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNiAxNiI+PHN0eWxlPi5pY29uLWNhbnZhcy10cmFuc3BhcmVudHtvcGFjaXR5OjA7ZmlsbDojMmQyZDMwfS5pY29uLXZzLW91dHtmaWxsOiMyZDJkMzB9Lmljb24tdnMtYmd7ZmlsbDojYzVjNWM1fTwvc3R5bGU+PHBhdGggY2xhc3M9Imljb24tY2FudmFzLXRyYW5zcGFyZW50IiBkPSJNMTYgMTZIMFYwaDE2djE2eiIgaWQ9ImNhbnZhcyIvPjxwYXRoIGNsYXNzPSJpY29uLXZzLW91dCIgZD0iTTE2IDEwYzAgMi4yMDUtMS43OTQgNC00IDQtMS44NTggMC0zLjQxMS0xLjI3OS0zLjg1OC0zaC0uOTc4bDIuMzE4IDRIMHYtMS43MDNsMi0zLjQwOFYwaDExdjYuMTQyYzEuNzIxLjQ0NyAzIDIgMyAzLjg1OHoiIGlkPSJvdXRsaW5lIi8+PHBhdGggY2xhc3M9Imljb24tdnMtYmciIGQ9Ik0xMiAxdjQuNzVBNC4yNTUgNC4yNTUgMCAwIDAgNy43NSAxMGgtLjczMkw0LjI3NSA1LjI2OSAzIDcuNDQyVjFoOXpNNy43NDcgMTRMNC4yNjkgOCAuNzQ4IDE0aDYuOTk5ek0xNSAxMGEzIDMgMCAxIDEtNiAwIDMgMyAwIDAgMSA2IDB6IiBpZD0iaWNvbkJnIi8+PC9zdmc+)}.monaco-editor.hc-black .suggest-widget .monaco-list .monaco-list-row .suggest-icon.constructor:before,.monaco-editor.hc-black .suggest-widget .monaco-list .monaco-list-row .suggest-icon.function:before,.monaco-editor.hc-black .suggest-widget .monaco-list .monaco-list-row .suggest-icon.method:before,.monaco-editor.vs-dark .suggest-widget .monaco-list .monaco-list-row .suggest-icon.constructor:before,.monaco-editor.vs-dark .suggest-widget .monaco-list .monaco-list-row .suggest-icon.function:before,.monaco-editor.vs-dark .suggest-widget .monaco-list .monaco-list-row .suggest-icon.method:before{background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNiAxNiI+PHN0eWxlPi5pY29uLWNhbnZhcy10cmFuc3BhcmVudHtvcGFjaXR5OjA7ZmlsbDojMmQyZDMwfS5pY29uLXZzLW91dHtmaWxsOiMyZDJkMzB9Lmljb24tdnMtZmd7ZmlsbDojMmIyODJlfS5pY29uLXZzLWFjdGlvbi1wdXJwbGV7ZmlsbDojYjE4MGQ3fTwvc3R5bGU+PHBhdGggY2xhc3M9Imljb24tY2FudmFzLXRyYW5zcGFyZW50IiBkPSJNMTYgMTZIMFYwaDE2djE2eiIgaWQ9ImNhbnZhcyIvPjxwYXRoIGNsYXNzPSJpY29uLXZzLW91dCIgZD0iTTE1IDMuMzQ5djguNDAzTDguOTc1IDE2SDguMDdMMSAxMS41ODJWMy4zMjdMNy41OTUgMGgxLjExOEwxNSAzLjM0OXoiIGlkPSJvdXRsaW5lIi8+PHBhdGggY2xhc3M9Imljb24tdnMtZmciIGQ9Ik0xMi43MTUgNC4zOThMOC40ODcgNy4wMiAzLjU2NSA0LjI3Mmw0LjU3OC0yLjMwOSA0LjU3MiAyLjQzNXpNMyA1LjEwMmw1IDIuNzkydjUuNzA1bC01LTMuMTI1VjUuMTAyem02IDguNDM0VjcuODc4bDQtMi40OHY1LjMxN2wtNCAyLjgyMXoiIGlkPSJpY29uRmciLz48cGF0aCBjbGFzcz0iaWNvbi12cy1hY3Rpb24tcHVycGxlIiBkPSJNOC4xNTYuODM3TDIgMy45NDJ2Ny4wODVMOC41MTcgMTUuMSAxNCAxMS4yMzNWMy45NUw4LjE1Ni44Mzd6bTQuNTU5IDMuNTYxTDguNDg3IDcuMDIgMy41NjUgNC4yNzJsNC41NzgtMi4zMDkgNC41NzIgMi40MzV6TTMgNS4xMDJsNSAyLjc5MnY1LjcwNWwtNS0zLjEyNVY1LjEwMnptNiA4LjQzNFY3Ljg3OGw0LTIuNDh2NS4zMTdsLTQgMi44MjF6IiBpZD0iaWNvbkJnIi8+PC9zdmc+)}.monaco-editor.hc-black .suggest-widget .monaco-list .monaco-list-row .suggest-icon.field:before,.monaco-editor.vs-dark .suggest-widget .monaco-list .monaco-list-row .suggest-icon.field:before{background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNiAxNiI+PHN0eWxlPi5pY29uLWNhbnZhcy10cmFuc3BhcmVudHtvcGFjaXR5OjA7ZmlsbDojMmQyZDMwfS5pY29uLXZzLW91dHtmaWxsOiMyZDJkMzB9Lmljb24tdnMtZmd7ZmlsbDojMmIyODJlfS5pY29uLXZzLWFjdGlvbi1ibHVle2ZpbGw6Izc1YmVmZn08L3N0eWxlPjxwYXRoIGNsYXNzPSJpY29uLWNhbnZhcy10cmFuc3BhcmVudCIgZD0iTTE2IDE2SDBWMGgxNnYxNnoiIGlkPSJjYW52YXMiLz48cGF0aCBjbGFzcz0iaWNvbi12cy1vdXQiIGQ9Ik0wIDEwLjczNlY0LjVMOSAwbDcgMy41djYuMjM2bC05IDQuNS03LTMuNXoiIGlkPSJvdXRsaW5lIi8+PHBhdGggY2xhc3M9Imljb24tdnMtYWN0aW9uLWJsdWUiIGQ9Ik05IDFMMSA1djVsNiAzIDgtNFY0TDkgMXpNNyA2Ljg4MkwzLjIzNiA1IDkgMi4xMTggMTIuNzY0IDQgNyA2Ljg4MnoiIGlkPSJpY29uQmciLz48cGF0aCBjbGFzcz0iaWNvbi12cy1mZyIgZD0iTTkgMi4xMThMMTIuNzY0IDQgNyA2Ljg4MiAzLjIzNiA1IDkgMi4xMTh6IiBpZD0iaWNvbkZnIi8+PC9zdmc+)}.monaco-editor.hc-black .suggest-widget .monaco-list .monaco-list-row .suggest-icon.event:before,.monaco-editor.vs-dark .suggest-widget .monaco-list .monaco-list-row .suggest-icon.event:before{background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNiAxNiI+PHN0eWxlPi5pY29uLWNhbnZhcy10cmFuc3BhcmVudHtvcGFjaXR5OjA7ZmlsbDojMmQyZDMwfS5pY29uLXZzLW91dHtmaWxsOiMyZDJkMzB9Lmljb24tdnMtYWN0aW9uLW9yYW5nZXtmaWxsOiNlOGFiNTN9PC9zdHlsZT48cGF0aCBjbGFzcz0iaWNvbi1jYW52YXMtdHJhbnNwYXJlbnQiIGQ9Ik0xNiAxNkgwVjBoMTZ2MTZ6IiBpZD0iY2FudmFzIi8+PHBhdGggY2xhc3M9Imljb24tdnMtb3V0IiBkPSJNMTQgMS40MTRMOS40MTQgNkgxNHYxLjQxNEw1LjQxNCAxNkgzdi0xLjIzNEw1LjM3MSAxMEgyVjguNzY0TDYuMzgyIDBIMTR2MS40MTR6IiBpZD0ib3V0bGluZSIgc3R5bGU9ImRpc3BsYXk6IG5vbmU7Ii8+PHBhdGggY2xhc3M9Imljb24tdnMtYWN0aW9uLW9yYW5nZSIgZD0iTTcgN2g2bC04IDhINGwyLjk4NS02SDNsNC04aDZMNyA3eiIgaWQ9Imljb25CZyIvPjwvc3ZnPg==)}.monaco-editor.hc-black .suggest-widget .monaco-list .monaco-list-row .suggest-icon.operator:before,.monaco-editor.vs-dark .suggest-widget .monaco-list .monaco-list-row .suggest-icon.operator:before{background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNiAxNiI+PHN0eWxlPi5pY29uLWNhbnZhcy10cmFuc3BhcmVudHtvcGFjaXR5OjA7ZmlsbDojMmQyZDMwfS5pY29uLXZzLW91dHtmaWxsOiMyZDJkMzB9Lmljb24tdnMtZmd7ZmlsbDojMmIyODJlfS5pY29uLXZzLWFjdGlvbi1ibHVle2ZpbGw6Izc1YmVmZn08L3N0eWxlPjxwYXRoIGNsYXNzPSJpY29uLWNhbnZhcy10cmFuc3BhcmVudCIgZD0iTTE2IDE2SDBWMGgxNnYxNnoiIGlkPSJjYW52YXMiLz48cGF0aCBjbGFzcz0iaWNvbi12cy1vdXQiIGQ9Ik0xNiAxNkgwVjBoMTZ2MTZ6IiBpZD0ib3V0bGluZSIgc3R5bGU9ImRpc3BsYXk6IG5vbmU7Ii8+PHBhdGggY2xhc3M9Imljb24tdnMtYWN0aW9uLWJsdWUiIGQ9Ik0xIDF2MTRoMTRWMUgxem02IDEySDN2LTFoNHYxem0wLTNIM1Y5aDR2MXptMC01SDV2Mkg0VjVIMlY0aDJWMmgxdjJoMnYxem0zLjI4MSA4SDguNzE5bDMtNGgxLjU2M2wtMy4wMDEgNHpNMTQgNUg5VjRoNXYxeiIgaWQ9Imljb25CZyIvPjxwYXRoIGNsYXNzPSJpY29uLXZzLWZnIiBkPSJNNyA1SDV2Mkg0VjVIMlY0aDJWMmgxdjJoMnYxem03LTFIOXYxaDVWNHpNNyA5SDN2MWg0Vjl6bTAgM0gzdjFoNHYtMXptMy4yODEgMWwzLTRoLTEuNTYzbC0zIDRoMS41NjN6IiBpZD0iaWNvbkZnIiBzdHlsZT0iZGlzcGxheTogbm9uZTsiLz48L3N2Zz4=)}.monaco-editor.hc-black .suggest-widget .monaco-list .monaco-list-row .suggest-icon.variable:before,.monaco-editor.vs-dark .suggest-widget .monaco-list .monaco-list-row .suggest-icon.variable:before{background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNiAxNiI+PHN0eWxlPi5pY29uLWNhbnZhcy10cmFuc3BhcmVudHtvcGFjaXR5OjA7ZmlsbDojMmQyZDMwfS5pY29uLXZzLW91dHtmaWxsOiMyZDJkMzB9Lmljb24tdnMtYmd7ZmlsbDojYzVjNWM1fS5pY29uLXZzLWZne2ZpbGw6IzJiMjgyZX0uaWNvbi12cy1hY3Rpb24tYmx1ZXtmaWxsOiM3NWJlZmZ9PC9zdHlsZT48cGF0aCBjbGFzcz0iaWNvbi1jYW52YXMtdHJhbnNwYXJlbnQiIGQ9Ik0xNiAxNkgwVjBoMTZ2MTZ6IiBpZD0iY2FudmFzIi8+PHBhdGggY2xhc3M9Imljb24tdnMtb3V0IiBkPSJNMTEgM3YxLjAxNUw4LjczMyAyLjg4MiA1IDQuNzQ5VjNIMHYxMGg1di0xLjg1OWwyLjE1NiAxLjA3N0wxMSAxMC4yOTVWMTNoNVYzaC01eiIgaWQ9Im91dGxpbmUiIHN0eWxlPSJkaXNwbGF5OiBub25lOyIvPjxwYXRoIGNsYXNzPSJpY29uLXZzLWJnIiBkPSJNMiA1djZoMnYxSDFWNGgzdjFIMnptMTAgNnYxaDNWNGgtM3YxaDJ2NmgtMnoiIGlkPSJpY29uQmciLz48cGF0aCBjbGFzcz0iaWNvbi12cy1mZyIgZD0iTTcuMTU2IDcuMTU2bC0xLjU3OC0uNzg5IDMuMTU2LTEuNTc4IDEuNTc4Ljc4OS0zLjE1NiAxLjU3OHoiIGlkPSJpY29uRmciIHN0eWxlPSJkaXNwbGF5OiBub25lOyIvPjxwYXRoIGNsYXNzPSJpY29uLXZzLWFjdGlvbi1ibHVlIiBkPSJNOC43MzMgNEw0IDYuMzY3djMuMTU2TDcuMTU2IDExLjFsNC43MzMtMi4zNjdWNS41NzhMOC43MzMgNHpNNy4xNTYgNy4xNTZsLTEuNTc4LS43ODkgMy4xNTYtMS41NzggMS41NzguNzg5LTMuMTU2IDEuNTc4eiIgaWQ9ImNvbG9ySW1wb3J0YW5jZSIvPjwvc3ZnPg==)}.monaco-editor.hc-black .suggest-widget .monaco-list .monaco-list-row .suggest-icon.class:before,.monaco-editor.vs-dark .suggest-widget .monaco-list .monaco-list-row .suggest-icon.class:before{background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNiAxNiI+PHN0eWxlPi5pY29uLWNhbnZhcy10cmFuc3BhcmVudHtvcGFjaXR5OjA7ZmlsbDojMmQyZDMwfS5pY29uLXZzLW91dHtmaWxsOiMyZDJkMzB9Lmljb24tdnMtYWN0aW9uLW9yYW5nZXtmaWxsOiNlOGFiNTN9PC9zdHlsZT48cGF0aCBjbGFzcz0iaWNvbi1jYW52YXMtdHJhbnNwYXJlbnQiIGQ9Ik0xNiAxNkgwVjBoMTZ2MTZ6IiBpZD0iY2FudmFzIi8+PHBhdGggY2xhc3M9Imljb24tdnMtb3V0IiBkPSJNMTYgNi41ODZsLTMtM0wxMS41ODYgNUg5LjQxNGwxLTEtNC00aC0uODI4TDAgNS41ODZ2LjgyOGw0IDRMNi40MTQgOEg3djVoMS41ODZsMyAzaC44MjhMMTYgMTIuNDE0di0uODI4TDEzLjkxNCA5LjUgMTYgNy40MTR2LS44Mjh6IiBpZD0ib3V0bGluZSIvPjxwYXRoIGNsYXNzPSJpY29uLXZzLWFjdGlvbi1vcmFuZ2UiIGQ9Ik0xMyAxMGwyIDItMyAzLTItMiAxLTFIOFY3SDZMNCA5IDEgNmw1LTUgMyAzLTIgMmg1bDEtMSAyIDItMyAzLTItMiAxLTFIOXY0bDIuOTk5LjAwMkwxMyAxMHoiIGlkPSJpY29uQmciLz48L3N2Zz4=)}.monaco-editor.hc-black .suggest-widget .monaco-list .monaco-list-row .suggest-icon.interface:before,.monaco-editor.vs-dark .suggest-widget .monaco-list .monaco-list-row .suggest-icon.interface:before{background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNiAxNiI+PHN0eWxlPi5pY29uLWNhbnZhcy10cmFuc3BhcmVudHtvcGFjaXR5OjA7ZmlsbDojMmQyZDMwfS5pY29uLXZzLW91dHtmaWxsOiMyZDJkMzB9Lmljb24tdnMtZmd7ZmlsbDojMmIyODJlfS5pY29uLXZzLWFjdGlvbi1ibHVle2ZpbGw6Izc1YmVmZn08L3N0eWxlPjxwYXRoIGNsYXNzPSJpY29uLWNhbnZhcy10cmFuc3BhcmVudCIgZD0iTTE2IDE2SDBWMGgxNnYxNnoiIGlkPSJjYW52YXMiLz48cGF0aCBjbGFzcz0iaWNvbi12cy1vdXQiIGQ9Ik0xMS41IDEyYy0xLjkxNSAwLTMuNjAyLTEuMjQxLTQuMjI4LTNoLTEuNDFhMy4xMSAzLjExIDAgMCAxLTIuNzM3IDEuNjI1QzEuNDAyIDEwLjYyNSAwIDkuMjIzIDAgNy41czEuNDAyLTMuMTI1IDMuMTI1LTMuMTI1YzEuMTY1IDAgMi4yMDEuNjM5IDIuNzM3IDEuNjI1aDEuNDFjLjYyNi0xLjc1OSAyLjMxMy0zIDQuMjI4LTNDMTMuOTgxIDMgMTYgNS4wMTkgMTYgNy41UzEzLjk4MSAxMiAxMS41IDEyeiIgaWQ9Im91dGxpbmUiLz48cGF0aCBjbGFzcz0iaWNvbi12cy1mZyIgZD0iTTExLjUgOUExLjUwMSAxLjUwMSAwIDEgMSAxMyA3LjVjMCAuODI2LS42NzMgMS41LTEuNSAxLjV6IiBpZD0iaWNvbkZnIi8+PHBhdGggY2xhc3M9Imljb24tdnMtYWN0aW9uLWJsdWUiIGQ9Ik0xMS41IDRhMy40OSAzLjQ5IDAgMCAwLTMuNDUgM0g1LjE4NUEyLjEyMiAyLjEyMiAwIDAgMCAxIDcuNWEyLjEyMyAyLjEyMyAwIDEgMCA0LjE4NS41SDguMDVhMy40OSAzLjQ5IDAgMCAwIDMuNDUgMyAzLjUgMy41IDAgMSAwIDAtN3ptMCA1Yy0uODI3IDAtMS41LS42NzMtMS41LTEuNVMxMC42NzMgNiAxMS41IDZzMS41LjY3MyAxLjUgMS41UzEyLjMyNyA5IDExLjUgOXoiIGlkPSJpY29uQmciLz48L3N2Zz4=)}.monaco-editor.hc-black .suggest-widget .monaco-list .monaco-list-row .suggest-icon.struct:before,.monaco-editor.vs-dark .suggest-widget .monaco-list .monaco-list-row .suggest-icon.struct:before{background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNiAxNiI+PHN0eWxlPi5pY29uLWNhbnZhcy10cmFuc3BhcmVudHtvcGFjaXR5OjA7ZmlsbDojMmQyZDMwfS5pY29uLXZzLW91dHtmaWxsOiMyZDJkMzB9Lmljb24tdnMtYWN0aW9uLWJsdWV7ZmlsbDojNzViZWZmfTwvc3R5bGU+PHBhdGggY2xhc3M9Imljb24tY2FudmFzLXRyYW5zcGFyZW50IiBkPSJNMTYgMTZIMFYwaDE2djE2eiIgaWQ9ImNhbnZhcyIvPjxwYXRoIGNsYXNzPSJpY29uLXZzLW91dCIgZD0iTTkgMTRWOEg3djZIMVYyaDE0djEySDl6IiBpZD0ib3V0bGluZSIgc3R5bGU9ImRpc3BsYXk6IG5vbmU7Ii8+PHBhdGggY2xhc3M9Imljb24tdnMtYWN0aW9uLWJsdWUiIGQ9Ik0xMCA5aDR2NGgtNFY5em0tOCA0aDRWOUgydjR6TTIgM3Y0aDEyVjNIMnoiIGlkPSJpY29uQmciLz48L3N2Zz4=)}.monaco-editor.hc-black .suggest-widget .monaco-list .monaco-list-row .suggest-icon.type-parameter:before,.monaco-editor.vs-dark .suggest-widget .monaco-list .monaco-list-row .suggest-icon.type-parameter:before{background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNiAxNiI+PHN0eWxlPi5pY29uLWNhbnZhcy10cmFuc3BhcmVudHtvcGFjaXR5OjA7ZmlsbDojMmQyZDMwfS5pY29uLXZzLW91dHtmaWxsOiMyZDJkMzB9Lmljb24tdnMtYmd7ZmlsbDojYzVjNWM1fTwvc3R5bGU+PHBhdGggY2xhc3M9Imljb24tY2FudmFzLXRyYW5zcGFyZW50IiBkPSJNMTYgMTZIMFYwaDE2djE2eiIgaWQ9ImNhbnZhcyIvPjxwYXRoIGNsYXNzPSJpY29uLXZzLW91dCIgZD0iTTEwLjcwMiAxMC41bDItMi0yLTIgLjUtLjVIMTB2NWgxdjNINXYtM2gxVjZINC43OThsLjUuNS0yIDIgMiAyTDMgMTIuNzk3bC0zLTNWNy4yMDFsMy0zVjJoMTB2Mi4yMDFsMyAzdjIuNTk2bC0zIDMtMi4yOTgtMi4yOTd6IiBpZD0ib3V0bGluZSIgc3R5bGU9ImRpc3BsYXk6IG5vbmU7Ii8+PHBhdGggY2xhc3M9Imljb24tdnMtYmciIGQ9Ik00IDNoOHYyaC0xdi0uNWMwLS4yNzctLjIyNC0uNS0uNS0uNUg5djcuNWMwIC4yNzUuMjI0LjUuNS41aC41djFINnYtMWguNWEuNS41IDAgMCAwIC41LS41VjRINS41YS41LjUgMCAwIDAtLjUuNVY1SDRWM3pNMyA1LjYxNUwuMTE2IDguNSAzIDExLjM4M2wuODg0LS44ODMtMi0yIDItMkwzIDUuNjE1em0xMCAwbC0uODg0Ljg4NSAyIDItMiAyIC44ODQuODgzTDE1Ljg4NCA4LjUgMTMgNS42MTV6IiBpZD0iaWNvbkJnIi8+PC9zdmc+)}.monaco-editor.hc-black .suggest-widget .monaco-list .monaco-list-row .suggest-icon.module:before,.monaco-editor.vs-dark .suggest-widget .monaco-list .monaco-list-row .suggest-icon.module:before{background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNiAxNiI+PHN0eWxlPi5pY29uLWNhbnZhcy10cmFuc3BhcmVudHtvcGFjaXR5OjA7ZmlsbDojMmQyZDMwfS5pY29uLXZzLW91dHtmaWxsOiMyZDJkMzB9Lmljb24tdnMtYmd7ZmlsbDojYzVjNWM1fTwvc3R5bGU+PHBhdGggY2xhc3M9Imljb24tY2FudmFzLXRyYW5zcGFyZW50IiBkPSJNMTYgMTZIMFYwaDE2djE2eiIgaWQ9ImNhbnZhcyIvPjxwYXRoIGNsYXNzPSJpY29uLXZzLW91dCIgZD0iTTkuMjYgMTEuOTg0bC45NzgtLjAyMWEuOTYyLjk2MiAwIDAgMCAuMDktLjAwNmMuMDExLS4wNjMuMDI2LS4xNzkuMDI2LS4zNjFWOS42ODhjMC0uNjc5LjE4NS0xLjI1Ny41My0xLjcwNy0uMzQ2LS40NTItLjUzLTEuMDMtLjUzLTEuNzA1VjQuMzVjMC0uMTY3LS4wMjEtLjI1OS0uMDM0LS4zMDJMOS4yNiA0LjAyVi45NzNsMS4wMTEuMDExYzIuMTY3LjAyNCAzLjQwOSAxLjE1NiAzLjQwOSAzLjEwNXYxLjk2MmMwIC4zNTEuMDcxLjQ2MS4wNzIuNDYybC45MzYuMDYuMDUzLjkyN3YxLjkzNmwtLjkzNi4wNjFjLS4wNzYuMDE2LS4xMjUuMTQ2LS4xMjUuNDI0djIuMDE3YzAgLjkxNC0uMzMyIDMuMDQzLTMuNDA4IDMuMDc4bC0xLjAxMi4wMTF2LTMuMDQzem0tMy41MjEgMy4wMzJjLTMuMDg5LS4wMzUtMy40MjItMi4xNjQtMy40MjItMy4wNzhWOS45MjFjMC0uMzI3LS4wNjYtLjQzMi0uMDY3LS40MzNsLS45MzctLjA2LS4wNjMtLjkyOVY2LjU2M2wuOTQyLS4wNmMuMDU4IDAgLjEyNS0uMTE0LjEyNS0uNDUyVjQuMDljMC0xLjk0OSAxLjI0OC0zLjA4MSAzLjQyMi0zLjEwNUw2Ljc1Ljk3M1Y0LjAybC0uOTc1LjAyM2EuNTcyLjU3MiAwIDAgMC0uMDkzLjAxYy4wMDYuMDIxLS4wMTkuMTE1LS4wMTkuMjk3djEuOTI4YzAgLjY3NS0uMTg2IDEuMjUzLS41MzQgMS43MDUuMzQ4LjQ1LjUzNCAxLjAyOC41MzQgMS43MDd2MS45MDdjMCAuMTc1LjAxNC4yOTEuMDI3LjM2My4wMjMuMDAyIDEuMDYuMDI1IDEuMDYuMDI1djMuMDQzbC0xLjAxMS0uMDEyeiIgaWQ9Im91dGxpbmUiLz48cGF0aCBjbGFzcz0iaWNvbi12cy1iZyIgZD0iTTUuNzUgMTQuMDE2Yy0xLjYyMy0uMDE5LTIuNDM0LS43MTEtMi40MzQtMi4wNzhWOS45MjFjMC0uOTAyLS4zNTUtMS4zNzYtMS4wNjYtMS40MjJ2LS45OThjLjcxMS0uMDQ1IDEuMDY2LS41MjkgMS4wNjYtMS40NDlWNC4wOWMwLTEuMzg1LjgxMS0yLjA4NyAyLjQzNC0yLjEwNXYxLjA2Yy0uNzI1LjAxNy0xLjA4Ny40NTMtMS4wODcgMS4zMDV2MS45MjhjMCAuOTItLjQ1NCAxLjQ4OC0xLjM2IDEuNzAyVjhjLjkwNy4yMDEgMS4zNi43NjMgMS4zNiAxLjY4OHYxLjkwN2MwIC40ODguMDgxLjgzNS4yNDMgMS4wNDIuMTYyLjIwOC40NDMuMzE2Ljg0NC4zMjV2MS4wNTR6bTcuOTktNS41MTdjLS43MDYuMDQ1LTEuMDYuNTItMS4wNiAxLjQyMnYyLjAxN2MwIDEuMzY3LS44MDcgMi4wNi0yLjQyIDIuMDc4di0xLjA1M2MuMzk2LS4wMDkuNjc4LS4xMTguODQ0LS4zMjguMTY3LS4yMS4yNS0uNTU2LjI1LTEuMDM5VjkuNjg4YzAtLjkyNS40NDktMS40ODggMS4zNDctMS42ODh2LS4wMjFjLS44OTgtLjIxNC0xLjM0Ny0uNzgyLTEuMzQ3LTEuNzAyVjQuMzVjMC0uODUyLS4zNjQtMS4yODgtMS4wOTQtMS4zMDZ2LTEuMDZjMS42MTMuMDE4IDIuNDIuNzIgMi40MiAyLjEwNXYxLjk2MmMwIC45Mi4zNTQgMS40MDQgMS4wNiAxLjQ0OXYuOTk5eiIgaWQ9Imljb25CZyIvPjwvc3ZnPg==)}.monaco-editor.hc-black .suggest-widget .monaco-list .monaco-list-row .suggest-icon.property:before,.monaco-editor.vs-dark .suggest-widget .monaco-list .monaco-list-row .suggest-icon.property:before{background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNiAxNiI+PHN0eWxlPi5pY29uLWNhbnZhcy10cmFuc3BhcmVudHtvcGFjaXR5OjA7ZmlsbDojMmQyZDMwfS5pY29uLXZzLW91dHtmaWxsOiMyZDJkMzB9Lmljb24tdnMtYmd7ZmlsbDojYzVjNWM1fTwvc3R5bGU+PHBhdGggY2xhc3M9Imljb24tY2FudmFzLXRyYW5zcGFyZW50IiBkPSJNMTYgMTZIMFYwaDE2djE2eiIgaWQ9ImNhbnZhcyIvPjxwYXRoIGNsYXNzPSJpY29uLXZzLW91dCIgZD0iTTE2IDUuNWE1LjUgNS41IDAgMCAxLTUuNSA1LjVjLS4yNzUgMC0uNTQzLS4wMjctLjgwNy0uMDY2bC0uMDc5LS4wMTJhNS40MjkgNS40MjkgMCAwIDEtLjgxLS4xOTJsLTQuNTM3IDQuNTM3Yy0uNDcyLjQ3My0xLjEuNzMzLTEuNzY3LjczM3MtMS4yOTUtLjI2LTEuNzY4LS43MzJhMi41MDIgMi41MDIgMCAwIDEgMC0zLjUzNWw0LjUzNy00LjUzN2E1LjQ1MiA1LjQ1MiAwIDAgMS0uMTkxLS44MTJjLS4wMDUtLjAyNS0uMDA4LS4wNTEtLjAxMi0uMDc3QTUuNTAzIDUuNTAzIDAgMCAxIDUgNS41YTUuNSA1LjUgMCAxIDEgMTEgMHoiIGlkPSJvdXRsaW5lIi8+PHBhdGggY2xhc3M9Imljb24tdnMtYmciIGQ9Ik0xNSA1LjVhNC41IDQuNSAwIDAgMS00LjUgNC41Yy0uNjkzIDAtMS4zNDItLjE3LTEuOTI5LS40NWwtNS4wMSA1LjAxYy0uMjkzLjI5NC0uNjc3LjQ0LTEuMDYxLjQ0cy0uNzY4LS4xNDYtMS4wNjEtLjQzOWExLjUgMS41IDAgMCAxIDAtMi4xMjFsNS4wMS01LjAxQTQuNDgzIDQuNDgzIDAgMCAxIDYgNS41IDQuNSA0LjUgMCAwIDEgMTAuNSAxYy42OTMgMCAxLjM0Mi4xNyAxLjkyOS40NUw5LjYzNiA0LjI0M2wyLjEyMSAyLjEyMSAyLjc5My0yLjc5M2MuMjguNTg3LjQ1IDEuMjM2LjQ1IDEuOTI5eiIgaWQ9Imljb25CZyIvPjwvc3ZnPg==)}.monaco-editor.hc-black .suggest-widget .monaco-list .monaco-list-row .suggest-icon.unit:before,.monaco-editor.vs-dark .suggest-widget .monaco-list .monaco-list-row .suggest-icon.unit:before{background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNiAxNiI+PHN0eWxlPi5pY29uLWNhbnZhcy10cmFuc3BhcmVudHtvcGFjaXR5OjA7ZmlsbDojMmQyZDMwfS5pY29uLXZzLW91dHtmaWxsOiMyZDJkMzB9Lmljb24tdnMtYmd7ZmlsbDojYzVjNWM1fS5pY29uLXZzLWZne2ZpbGw6IzJiMjgyZX08L3N0eWxlPjxwYXRoIGNsYXNzPSJpY29uLWNhbnZhcy10cmFuc3BhcmVudCIgZD0iTTE2IDE2SDBWMGgxNnYxNnoiIGlkPSJjYW52YXMiLz48cGF0aCBjbGFzcz0iaWNvbi12cy1vdXQiIGQ9Ik0xNiAxMS4wMTNIMVY0aDE1djcuMDEzeiIgaWQ9Im91dGxpbmUiLz48cGF0aCBjbGFzcz0iaWNvbi12cy1mZyIgZD0iTTggOUg3VjZoM3YzSDlWN0g4djJ6TTQgN2gxdjJoMVY2SDN2M2gxVjd6bTggMGgxdjJoMVY2aC0zdjNoMVY3eiIgaWQ9Imljb25GZyIvPjxwYXRoIGNsYXNzPSJpY29uLXZzLWJnIiBkPSJNMiA1djVoMTNWNUgyem00IDRINVY3SDR2MkgzVjZoM3Yzem00IDBIOVY3SDh2Mkg3VjZoM3Yzem00IDBoLTFWN2gtMXYyaC0xVjZoM3YzeiIgaWQ9Imljb25CZyIvPjwvc3ZnPg==)}.monaco-editor.hc-black .suggest-widget .monaco-list .monaco-list-row .suggest-icon.constant:before,.monaco-editor.vs-dark .suggest-widget .monaco-list .monaco-list-row .suggest-icon.constant:before{background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNiAxNiI+PHN0eWxlPi5pY29uLWNhbnZhcy10cmFuc3BhcmVudHtvcGFjaXR5OjA7ZmlsbDojMjUyNTI2fS5pY29uLXZzLW91dHtmaWxsOiMyNTI1MjZ9Lmljb24tdnMtYmd7ZmlsbDojYzVjNWM1fS5pY29uLXZzLWZne2ZpbGw6IzJiMjgyZX0uaWNvbi12cy1hY3Rpb24tYmx1ZXtmaWxsOiM3NWJlZmZ9PC9zdHlsZT48cGF0aCBjbGFzcz0iaWNvbi1jYW52YXMtdHJhbnNwYXJlbnQiIGQ9Ik0xNiAxNkgwVjBoMTZ2MTZ6IiBpZD0iY2FudmFzIi8+PHBhdGggY2xhc3M9Imljb24tdnMtb3V0IiBkPSJNMi44NzkgMTRMMSAxMi4xMjFWMy44NzlMMi44NzkgMmgxMC4yNDJMMTUgMy44Nzl2OC4yNDJMMTMuMTIxIDE0SDIuODc5eiIgaWQ9Im91dGxpbmUiLz48cGF0aCBjbGFzcz0iaWNvbi12cy1mZyIgZD0iTTEyLjI5MyA0SDMuNzA3TDMgNC43MDd2Ni41ODZsLjcwNy43MDdoOC41ODZsLjcwNy0uNzA3VjQuNzA3TDEyLjI5MyA0ek0xMSAxMEg1VjloNnYxem0wLTNINVY2aDZ2MXoiIGlkPSJpY29uRmciLz48ZyBpZD0iaWNvbkJnIj48cGF0aCBjbGFzcz0iaWNvbi12cy1iZyIgZD0iTTEyLjcwNyAxM0gzLjI5M0wyIDExLjcwN1Y0LjI5M0wzLjI5MyAzaDkuNDE0TDE0IDQuMjkzdjcuNDE0TDEyLjcwNyAxM3ptLTktMWg4LjU4NmwuNzA3LS43MDdWNC43MDdMMTIuMjkzIDRIMy43MDdMMyA0LjcwN3Y2LjU4NmwuNzA3LjcwN3oiLz48cGF0aCBjbGFzcz0iaWNvbi12cy1hY3Rpb24tYmx1ZSIgZD0iTTExIDdINVY2aDZ2MXptMCAySDV2MWg2Vjl6Ii8+PC9nPjwvc3ZnPg==)}.monaco-editor.hc-black .suggest-widget .monaco-list .monaco-list-row .suggest-icon.enum:before,.monaco-editor.hc-black .suggest-widget .monaco-list .monaco-list-row .suggest-icon.value:before,.monaco-editor.vs-dark .suggest-widget .monaco-list .monaco-list-row .suggest-icon.enum:before,.monaco-editor.vs-dark .suggest-widget .monaco-list .monaco-list-row .suggest-icon.value:before{background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNiAxNiI+PHN0eWxlPi5pY29uLWNhbnZhcy10cmFuc3BhcmVudHtvcGFjaXR5OjA7ZmlsbDojMmQyZDMwfS5pY29uLXZzLW91dHtmaWxsOiMyZDJkMzB9Lmljb24tdnMtZmd7ZmlsbDojMmIyODJlfS5pY29uLXZzLWFjdGlvbi1vcmFuZ2V7ZmlsbDojZThhYjUzfTwvc3R5bGU+PHBhdGggY2xhc3M9Imljb24tY2FudmFzLXRyYW5zcGFyZW50IiBkPSJNMTYgMTZIMFYwaDE2djE2eiIgaWQ9ImNhbnZhcyIvPjxwYXRoIGNsYXNzPSJpY29uLXZzLW91dCIgZD0iTTE0LjQxNCAxTDE2IDIuNTg2djUuODI4TDE0LjQxNCAxMEgxMHYzLjQxNkw4LjQxNCAxNUgxLjU4NkwwIDEzLjQxNnYtNS44M0wxLjU4NiA2SDZWMi41ODZMNy41ODYgMWg2LjgyOHoiIGlkPSJvdXRsaW5lIi8+PHBhdGggY2xhc3M9Imljb24tdnMtZmciIGQ9Ik0yIDEzaDZWOEgydjV6bTEtNGg0djFIM1Y5em0wIDJoNHYxSDN2LTF6bTExLTVWM0g4djNoLjQxNEw5IDYuNTg2VjZoNHYxSDkuNDE0bC41ODYuNTg2VjhoNFY2em0tMS0xSDlWNGg0djF6IiBpZD0iaWNvbkZnIi8+PHBhdGggY2xhc3M9Imljb24tdnMtYWN0aW9uLW9yYW5nZSIgZD0iTTMgMTFoNC4wMDF2MUgzdi0xem0wLTFoNC4wMDFWOUgzdjF6bTYtMnY1bC0xIDFIMmwtMS0xVjhsMS0xaDZsMSAxek04IDhIMnY1aDZWOHptMS0ybDEgMWgzVjZIOXptMC0xaDRWNEg5djF6bTUtM0g4TDcgM3YzaDFWM2g2djVoLTR2MWg0bDEtMVYzbC0xLTF6IiBpZD0iaWNvbkJnIi8+PC9zdmc+)}.monaco-editor.hc-black .suggest-widget .monaco-list .monaco-list-row .suggest-icon.enum-member:before,.monaco-editor.vs-dark .suggest-widget .monaco-list .monaco-list-row .suggest-icon.enum-member:before{background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNiAxNiI+PHN0eWxlPi5pY29uLWNhbnZhcy10cmFuc3BhcmVudHtvcGFjaXR5OjA7ZmlsbDojMmQyZDMwfS5pY29uLXZzLW91dHtmaWxsOiMyZDJkMzB9Lmljb24tdnMtZmd7ZmlsbDojMmIyODJlfS5pY29uLXZzLWFjdGlvbi1ibHVle2ZpbGw6Izc1YmVmZn08L3N0eWxlPjxwYXRoIGNsYXNzPSJpY29uLWNhbnZhcy10cmFuc3BhcmVudCIgZD0iTTE2IDE2SDBWMGgxNnYxNnoiIGlkPSJjYW52YXMiLz48cGF0aCBjbGFzcz0iaWNvbi12cy1vdXQiIGQ9Ik0wIDE1VjZoNlYyLjU4Nkw3LjU4NSAxaDYuODI5TDE2IDIuNTg2djUuODI5TDE0LjQxNCAxMEgxMHY1SDB6bTMtNnoiIGlkPSJvdXRsaW5lIi8+PHBhdGggY2xhc3M9Imljb24tdnMtZmciIGQ9Ik04IDN2M2g1djFoLTN2MWg0VjNIOHptNSAySDlWNGg0djF6TTIgOHY1aDZWOEgyem01IDNIM3YtMWg0djF6IiBpZD0iaWNvbkZnIi8+PHBhdGggY2xhc3M9Imljb24tdnMtYWN0aW9uLWJsdWUiIGQ9Ik0xMCA2aDN2MWgtM1Y2ek05IDR2MWg0VjRIOXptNS0ySDhMNyAzdjNoMVYzaDZ2NWgtNHYxaDRsMS0xVjNsLTEtMXptLTcgOEgzdjFoNHYtMXptMi0zdjdIMVY3aDh6TTggOEgydjVoNlY4eiIgaWQ9Imljb25CZyIvPjwvc3ZnPg==)}.monaco-editor.hc-black .suggest-widget .monaco-list .monaco-list-row .suggest-icon.keyword:before,.monaco-editor.vs-dark .suggest-widget .monaco-list .monaco-list-row .suggest-icon.keyword:before{background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNiAxNiI+PHN0eWxlPi5pY29uLWNhbnZhcy10cmFuc3BhcmVudHtvcGFjaXR5OjA7ZmlsbDojMmQyZDMwfS5pY29uLXZzLW91dHtmaWxsOiMyZDJkMzB9Lmljb24tdnMtYmd7ZmlsbDojYzVjNWM1fS5pY29uLXZzLWZne2ZpbGw6IzJiMjgyZX08L3N0eWxlPjxwYXRoIGNsYXNzPSJpY29uLWNhbnZhcy10cmFuc3BhcmVudCIgZD0iTTE2IDE2SDBWMGgxNnYxNnoiIGlkPSJjYW52YXMiLz48cGF0aCBjbGFzcz0iaWNvbi12cy1vdXQiIGQ9Ik0xNiA1VjJIOVYxSDB2MTRoMTN2LTNoM1Y5aC0xVjZIOVY1aDd6bS04IDdWOWgxdjNIOHoiIGlkPSJvdXRsaW5lIi8+PHBhdGggY2xhc3M9Imljb24tdnMtZmciIGQ9Ik0yIDNoNXYxSDJWM3oiIGlkPSJpY29uRmciLz48cGF0aCBjbGFzcz0iaWNvbi12cy1iZyIgZD0iTTE1IDRoLTVWM2g1djF6bS0xIDNoLTJ2MWgyVjd6bS00IDBIMXYxaDlWN3ptMiA2SDF2MWgxMXYtMXptLTUtM0gxdjFoNnYtMXptOCAwaC01djFoNXYtMXpNOCAydjNIMVYyaDd6TTcgM0gydjFoNVYzeiIgaWQ9Imljb25CZyIvPjwvc3ZnPg==)}.monaco-editor.hc-black .suggest-widget .monaco-list .monaco-list-row .suggest-icon.text:before,.monaco-editor.vs-dark .suggest-widget .monaco-list .monaco-list-row .suggest-icon.text:before{background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNiAxNiI+PHN0eWxlPi5pY29uLWNhbnZhcy10cmFuc3BhcmVudHtvcGFjaXR5OjA7ZmlsbDojMmQyZDMwfS5pY29uLXZzLW91dHtmaWxsOiMyZDJkMzB9Lmljb24tdnMtYmd7ZmlsbDojYzVjNWM1fS5pY29uLXZzLWZne2ZpbGw6IzJiMjgyZX08L3N0eWxlPjxwYXRoIGNsYXNzPSJpY29uLWNhbnZhcy10cmFuc3BhcmVudCIgZD0iTTE2IDE2SDBWMGgxNnYxNnoiIGlkPSJjYW52YXMiLz48cGF0aCBjbGFzcz0iaWNvbi12cy1vdXQiIGQ9Ik0xNiAxNUgwVjFoMTZ2MTR6IiBpZD0ib3V0bGluZSIvPjxwYXRoIGNsYXNzPSJpY29uLXZzLWZnIiBkPSJNOS4yMjkgNy4zNTRjLjAzNS4xNDYuMDUyLjMxLjA1Mi40OTQgMCAuMjM0LS4wMi40NDEtLjA2LjYyMS0uMDM5LjE4LS4wOTUuMzI4LS4xNjguNDQ1YS42ODcuNjg3IDAgMCAxLS45MTQuMjgxLjc2Ljc2IDAgMCAxLS4yMzctLjIwNy45ODguOTg4IDAgMCAxLS4xNTQtLjMwNiAxLjI2MiAxLjI2MiAwIDAgMS0uMDU3LS4zODF2LS41MDZjMC0uMTcuMDItLjMyNi4wNjEtLjQ2NXMuMDk2LS4yNTguMTY4LS4zNTlhLjc1Ni43NTYgMCAwIDEgLjI1Ny0uMjMyYy4xLS4wNTUuMjEtLjA4Mi4zMzEtLjA4MmEuNjQ2LjY0NiAwIDAgMSAuNTcxLjMyYy4wNjcuMTA1LjExNi4yMy4xNS4zNzd6bS01LjEyNi44NjlhLjU1Ny41NTcgMCAwIDAtLjE5Ni4xMzJjLS4wNDcuMDUzLS4wOC4xMTItLjA5Ny4xOHMtLjAyOC4xNDctLjAyOC4yMzNhLjUxMy41MTMgMCAwIDAgLjE1Ny4zOS41MjguNTI4IDAgMCAwIC4xODYuMTEzLjY4Mi42ODIgMCAwIDAgLjI0Mi4wNDEuNzYuNzYgMCAwIDAgLjU5My0uMjcxLjg5Ny44OTcgMCAwIDAgLjE2NS0uMjk1Yy4wMzgtLjExMy4wNTktLjIzNC4wNTktLjM2NXYtLjM0NmwtLjc2MS4xMWExLjI5IDEuMjkgMCAwIDAtLjMyLjA3OHpNMTQgM3YxMEgyVjNoMTJ6TTUuOTYyIDcuNDY5YzAtLjIzOC0uMDI3LS40NTEtLjA4My0uNjM3YTEuMjg2IDEuMjg2IDAgMCAwLS4yNDktLjQ3MSAxLjA4IDEuMDggMCAwIDAtLjQyNC0uMjk1IDEuNjQ0IDEuNjQ0IDAgMCAwLS42MDgtLjEwMWMtLjExOSAwLS4yNDEuMDEyLS4zNjguMDMzYTMuMjEzIDMuMjEzIDAgMCAwLS42NzMuMTk1IDEuMzEzIDEuMzEzIDAgMCAwLS4yMTIuMTE0di43NjhjLjE1OC0uMTMyLjM0MS0uMjM1LjU0NC0uMzEzLjIwNC0uMDc4LjQxMy0uMTE3LjYyNy0uMTE3LjIxMyAwIC4zNzcuMDYzLjQ5NC4xODYuMTE2LjEyNS4xNzQuMzI0LjE3NC42bC0xLjAzLjE1NGMtLjIwNS4wMjYtLjM4LjA3Ny0uNTI2LjE1MWExLjA4MyAxLjA4MyAwIDAgMC0uNTYzLjY2QTEuNTYyIDEuNTYyIDAgMCAwIDMgOC44NTdjMCAuMTcuMDI1LjMyMy4wNzQuNDYzYS45NDUuOTQ1IDAgMCAwIC41NjguNTk2Yy4xMzkuMDU3LjI5Ny4wODQuNDc4LjA4NC4yMjkgMCAuNDMxLS4wNTMuNjA0LS4xNmExLjMgMS4zIDAgMCAwIC40MzktLjQ2M2guMDE0di41MjloLjc4NVY3LjQ2OXpNMTAgNy44NjFhMy41NCAzLjU0IDAgMCAwLS4wNzQtLjczNCAyLjA0NyAyLjA0NyAwIDAgMC0uMjI4LS42MTEgMS4yMDMgMS4yMDMgMCAwIDAtLjM5NC0uNDE2IDEuMDMgMS4wMyAwIDAgMC0uNTc0LS4xNTNjLS4xMjMgMC0uMjM0LjAxOC0uMzM2LjA1MWExIDEgMCAwIDAtLjI3OC4xNDcgMS4xNTMgMS4xNTMgMCAwIDAtLjIyNS4yMjIgMi4wMjIgMi4wMjIgMCAwIDAtLjE4MS4yODloLS4wMTNWNUg3djQuODg3aC42OTd2LS40ODVoLjAxM2MuMDQ0LjA4Mi4wOTUuMTU4LjE1MS4yMjkuMDU3LjA3LjExOS4xMzMuMTkxLjE4NmEuODM1LjgzNSAwIDAgMCAuMjM4LjEyMS45NDMuOTQzIDAgMCAwIC4yOTMuMDQyYy4yMyAwIC40MzQtLjA1My42MDktLjE2YTEuMzQgMS4zNCAwIDAgMCAuNDQzLS40NDNjLjEyLS4xODguMjExLS40MTIuMjcyLS42NzJBMy42MiAzLjYyIDAgMCAwIDEwIDcuODYxem0zLTEuNjU4YS43LjcgMCAwIDAtLjEwNi0uMDY2IDEuMTgzIDEuMTgzIDAgMCAwLS4xNDItLjA2MyAxLjIzMyAxLjIzMyAwIDAgMC0uMzYzLS4wNjVjLS4yMDkgMC0uMzk5LjA1MS0uNTY5LjE1YTEuMzU1IDEuMzU1IDAgMCAwLS40MzMuNDI0Yy0uMTE4LjE4Mi0uMjEuNDAyLS4yNzMuNjZhMy42MyAzLjYzIDAgMCAwLS4wMDggMS42MTVjLjA2LjIzLjE0My40My4yNTIuNjAyLjEwOS4xNjguMjQxLjMwMy4zOTYuMzk2YS45NzIuOTcyIDAgMCAwIC41MjQuMTQ0Yy4xNTggMCAuMjk2LS4wMjEuNDEzLS4wNjguMTE3LS4wNDUuMjE5LS4xMDguMzA5LS4xODR2LS43N2ExLjA5NCAxLjA5NCAwIDAgMS0uMjg4LjIyNS44MTkuODE5IDAgMCAxLS4xNTguMDY4LjQ4LjQ4IDAgMCAxLS4xNTMuMDI3LjYyLjYyIDAgMCAxLS4yNzQtLjA3NGMtLjI0MS0uMTM2LS40MjMtLjQ3OS0uNDIzLTEuMTQ2IDAtLjcxNS4yMDYtMS4xMi40NjktMS4zMDEuMDc3LS4wMzIuMTUzLS4wNjQuMjM4LS4wNjQuMTEzIDAgLjIyLjAyNy4zMTcuMDgyLjA5Ni4wNTcuMTg4LjEzMS4yNzIuMjIzdi0uODE1eiIgaWQ9Imljb25GZyIvPjxwYXRoIGNsYXNzPSJpY29uLXZzLWJnIiBkPSJNMSAydjEyaDE0VjJIMXptMTMgMTFIMlYzaDEydjEwek01LjYzIDYuMzYxYTEuMDggMS4wOCAwIDAgMC0uNDI0LS4yOTUgMS42NDQgMS42NDQgMCAwIDAtLjYwOC0uMTAxYy0uMTE5IDAtLjI0MS4wMTItLjM2OC4wMzNhMy4yMTMgMy4yMTMgMCAwIDAtLjY3My4xOTUgMS4zMTMgMS4zMTMgMCAwIDAtLjIxMi4xMTR2Ljc2OGMuMTU4LS4xMzIuMzQxLS4yMzUuNTQ0LS4zMTMuMjA0LS4wNzguNDEzLS4xMTcuNjI3LS4xMTcuMjEzIDAgLjM3Ny4wNjMuNDk0LjE4Ni4xMTYuMTI1LjE3NC4zMjQuMTc0LjZsLTEuMDMuMTU0Yy0uMjA1LjAyNi0uMzguMDc3LS41MjYuMTUxYTEuMDgzIDEuMDgzIDAgMCAwLS41NjMuNjZBMS41NjIgMS41NjIgMCAwIDAgMyA4Ljg1N2MwIC4xNy4wMjUuMzIzLjA3NC40NjNhLjk0NS45NDUgMCAwIDAgLjU2OC41OTZjLjEzOS4wNTcuMjk3LjA4NC40NzguMDg0LjIyOSAwIC40MzEtLjA1My42MDQtLjE2YTEuMyAxLjMgMCAwIDAgLjQzOS0uNDYzaC4wMTR2LjUyOWguNzg1VjcuNDY5YzAtLjIzOC0uMDI3LS40NTEtLjA4My0uNjM3YTEuMjg2IDEuMjg2IDAgMCAwLS4yNDktLjQ3MXptLS40NDYgMi4wMmMwIC4xMzEtLjAyLjI1Mi0uMDU5LjM2NWEuODk3Ljg5NyAwIDAgMS0uMTY1LjI5NS43NTguNzU4IDAgMCAxLS41OTMuMjcyLjY4Mi42ODIgMCAwIDEtLjI0Mi0uMDQxLjUwNy41MDcgMCAwIDEtLjMwMi0uMjg2LjU4My41ODMgMCAwIDEtLjA0MS0uMjE4YzAtLjA4Ni4wMS0uMTY0LjAyNy0uMjMycy4wNTEtLjEyNy4wOTgtLjE4YS41NDYuNTQ2IDAgMCAxIC4xOTYtLjEzM2MuMDgzLS4wMzMuMTg5LS4wNjEuMzItLjA3OGwuNzYxLS4xMDl2LjM0NXptNC41MTQtMS44NjVhMS4yMDMgMS4yMDMgMCAwIDAtLjM5NC0uNDE2IDEuMDMgMS4wMyAwIDAgMC0uNTc0LS4xNTNjLS4xMjMgMC0uMjM0LjAxOC0uMzM2LjA1MWExIDEgMCAwIDAtLjI3OC4xNDcgMS4xNTMgMS4xNTMgMCAwIDAtLjIyNS4yMjIgMi4wMjIgMi4wMjIgMCAwIDAtLjE4MS4yODloLS4wMTNWNUg3djQuODg3aC42OTd2LS40ODVoLjAxM2MuMDQ0LjA4Mi4wOTUuMTU4LjE1MS4yMjkuMDU3LjA3LjExOS4xMzMuMTkxLjE4NmEuODM1LjgzNSAwIDAgMCAuMjM4LjEyMS45NDMuOTQzIDAgMCAwIC4yOTMuMDQyYy4yMyAwIC40MzQtLjA1My42MDktLjE2YTEuMzQgMS4zNCAwIDAgMCAuNDQzLS40NDNjLjEyLS4xODguMjExLS40MTIuMjcyLS42NzJBMy42MiAzLjYyIDAgMCAwIDEwIDcuODYxYTMuNTQgMy41NCAwIDAgMC0uMDc0LS43MzQgMi4wNDcgMi4wNDcgMCAwIDAtLjIyOC0uNjExem0tLjQ3NiAxLjk1M2MtLjAzOS4xOC0uMDk1LjMyOC0uMTY4LjQ0NWEuNzU1Ljc1NSAwIDAgMS0uMjY0LjI2Ni42ODcuNjg3IDAgMCAxLS42NTEuMDE1Ljc2Ljc2IDAgMCAxLS4yMzctLjIwNy45ODguOTg4IDAgMCAxLS4xNTQtLjMwNiAxLjI2MiAxLjI2MiAwIDAgMS0uMDU3LS4zODF2LS41MDZjMC0uMTcuMDItLjMyNi4wNjEtLjQ2NXMuMDk2LS4yNTguMTY4LS4zNTlhLjc1Ni43NTYgMCAwIDEgLjI1Ny0uMjMyYy4xLS4wNTUuMjEtLjA4Mi4zMzEtLjA4MmEuNjQ2LjY0NiAwIDAgMSAuNTcxLjMyYy4wNjYuMTA1LjExNi4yMy4xNS4zNzcuMDM1LjE0Ni4wNTIuMzEuMDUyLjQ5NCAwIC4yMzQtLjAxOS40NDEtLjA1OS42MjF6bTMuNjcyLTIuMzMyYS43LjcgMCAwIDEgLjEwNi4wNjZ2LjgxNGExLjE3OCAxLjE3OCAwIDAgMC0uMjczLS4yMjMuNjQ1LjY0NSAwIDAgMC0uMzE3LS4wODFjLS4wODUgMC0uMTYxLjAzMi0uMjM4LjA2NC0uMjYzLjE4MS0uNDY5LjU4Ni0uNDY5IDEuMzAxIDAgLjY2OC4xODIgMS4wMTEuNDIzIDEuMTQ2LjA4NC4wNC4xNzEuMDc0LjI3NC4wNzQuMDQ5IDAgLjEwMS0uMDEuMTUzLS4wMjdhLjg1Ni44NTYgMCAwIDAgLjE1OC0uMDY4IDEuMTYgMS4xNiAwIDAgMCAuMjg4LS4yMjV2Ljc3Yy0uMDkuMDc2LS4xOTIuMTM5LS4zMDkuMTg0YTEuMDk4IDEuMDk4IDAgMCAxLS40MTIuMDY4Ljk3NC45NzQgMCAwIDEtLjUyMy0uMTQzIDEuMjU3IDEuMjU3IDAgMCAxLS4zOTYtLjM5NiAyLjA5OCAyLjA5OCAwIDAgMS0uMjUyLS42MDIgMy4xMTggMy4xMTggMCAwIDEtLjA4OC0uNzU0YzAtLjMxNi4wMzItLjYwNC4wOTYtLjg2MS4wNjMtLjI1OC4xNTUtLjQ3OS4yNzMtLjY2LjExOS0uMTgyLjI2NS0uMzIyLjQzMy0uNDI0YTEuMTAyIDEuMTAyIDAgMCAxIDEuMDczLS4wMjN6IiBpZD0iaWNvbkJnIi8+PC9zdmc+)}.monaco-editor.hc-black .suggest-widget .monaco-list .monaco-list-row .suggest-icon.color:before,.monaco-editor.vs-dark .suggest-widget .monaco-list .monaco-list-row .suggest-icon.color:before{background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNiAxNiI+PHN0eWxlPi5pY29uLWNhbnZhcy10cmFuc3BhcmVudHtvcGFjaXR5OjA7ZmlsbDojMmQyZDMwfS5pY29uLXZzLW91dHtmaWxsOiMyZDJkMzB9Lmljb24tdnMtYmd7ZmlsbDojYzVjNWM1fS5pY29uLXZzLXJlZHtmaWxsOiNmNDg3NzF9Lmljb24tdnMteWVsbG93e2ZpbGw6I2ZmY2MwMH0uaWNvbi12cy1ncmVlbntmaWxsOiMzMzk5MzN9Lmljb24tdnMtYmx1ZXtmaWxsOiMxYmExZTJ9Lmljb24tdnMtYWN0aW9uLXB1cnBsZXtmaWxsOiNiMTgwZDd9Lmljb24td2hpdGV7ZmlsbDojMDAwMDAwfTwvc3R5bGU+PHBhdGggY2xhc3M9Imljb24tY2FudmFzLXRyYW5zcGFyZW50IiBkPSJNMTYgMTZIMFYwaDE2djE2eiIgaWQ9ImNhbnZhcyIvPjxwYXRoIGNsYXNzPSJpY29uLXZzLW91dCIgZD0iTTE2IDhjMCA0LjQxMS0zLjU4OSA4LTggOGEyLjgwMyAyLjgwMyAwIDAgMS0yLjgtMi44YzAtLjgzMy4yNzItMS42MjkuNzY2LTIuMjQxYS41OTYuNTk2IDAgMCAwIC4xMDEtLjM1OS42NjcuNjY3IDAgMCAwLS42NjctLjY2Ni41OC41OCAwIDAgMC0uMzU4LjEwMkEzLjU4NCAzLjU4NCAwIDAgMSAyLjggMTAuOCAyLjgwMyAyLjgwMyAwIDAgMSAwIDhjMC00LjQxMSAzLjU4OS04IDgtOHM4IDMuNTg5IDggOHoiIGlkPSJvdXRsaW5lIi8+PHBhdGggY2xhc3M9Imljb24td2hpdGUiIGQ9Ik01LjQgNy45MzNhMi42NyAyLjY3IDAgMCAxIDIuNjY3IDIuNjY2YzAgLjYwNi0uMTkzIDEuMTc5LS41NDQgMS42MTRhMS41OTkgMS41OTkgMCAwIDAtLjMyMy45ODcuOC44IDAgMCAwIC44LjhjMy4zMDkgMCA2LTIuNjkxIDYtNnMtMi42OTEtNi02LTYtNiAyLjY5MS02IDZjMCAuNDQxLjM1OS44LjguOC4zNzggMCAuNzI5LS4xMTQuOTg2LS4zMjJBMi41NjggMi41NjggMCAwIDEgNS40IDcuOTMzeiIgaWQ9Imljb25GZyIvPjxnIGlkPSJpY29uQmciPjxwYXRoIGNsYXNzPSJpY29uLXZzLWJnIiBkPSJNOCAxNWMtLjk5MiAwLTEuOC0uODA4LTEuOC0xLjggMC0uNjA2LjE5My0xLjE3OS41NDQtMS42MTMuMjA4LS4yNTkuMzIzLS42MDkuMzIzLS45ODcgMC0uOTE5LS43NDgtMS42NjYtMS42NjctMS42NjYtLjM3NyAwLS43MjguMTE1LS45ODYuMzIzQTIuNTggMi41OCAwIDAgMSAyLjggOS44QzEuODA4IDkuOCAxIDguOTkyIDEgOGMwLTMuODYgMy4xNC03IDctNyAzLjg1OSAwIDcgMy4xNCA3IDcgMCAzLjg1OS0zLjE0MSA3LTcgN3pNNS40IDcuOTMzYTIuNjcgMi42NyAwIDAgMSAyLjY2NyAyLjY2NmMwIC42MDYtLjE5MyAxLjE3OS0uNTQ0IDEuNjE0YTEuNTk5IDEuNTk5IDAgMCAwLS4zMjMuOTg3LjguOCAwIDAgMCAuOC44YzMuMzA5IDAgNi0yLjY5MSA2LTZzLTIuNjkxLTYtNi02LTYgMi42OTEtNiA2YzAgLjQ0MS4zNTkuOC44LjguMzc4IDAgLjcyOS0uMTE0Ljk4Ni0uMzIyQTIuNTY4IDIuNTY4IDAgMCAxIDUuNCA3LjkzM3oiLz48cGF0aCBjbGFzcz0iaWNvbi12cy1hY3Rpb24tcHVycGxlIiBkPSJNNC41IDUuMzc1YS44NzUuODc1IDAgMSAwIDAgMS43NS44NzUuODc1IDAgMCAwIDAtMS43NXoiLz48cGF0aCBjbGFzcz0iaWNvbi12cy1ibHVlIiBkPSJNNy4xMjUgMy42MjVhLjg3NS44NzUgMCAxIDAgMCAxLjc1Ljg3NS44NzUgMCAwIDAgMC0xLjc1eiIvPjxwYXRoIGNsYXNzPSJpY29uLXZzLWdyZWVuIiBkPSJNMTAuNjI1IDQuNWEuODc1Ljg3NSAwIDEgMCAwIDEuNzUuODc1Ljg3NSAwIDAgMCAwLTEuNzV6Ii8+PHBhdGggY2xhc3M9Imljb24tdnMteWVsbG93IiBkPSJNMTEuNSA4YS44NzUuODc1IDAgMSAwIDAgMS43NS44NzUuODc1IDAgMCAwIDAtMS43NXoiLz48cGF0aCBjbGFzcz0iaWNvbi12cy1yZWQiIGQ9Ik05Ljc1IDEwLjYyNWEuODc1Ljg3NSAwIDEgMCAwIDEuNzUuODc1Ljg3NSAwIDAgMCAwLTEuNzV6Ii8+PC9nPjwvc3ZnPg==)}.monaco-editor.hc-black .suggest-widget .monaco-list .monaco-list-row .suggest-icon.file:before,.monaco-editor.vs-dark .suggest-widget .monaco-list .monaco-list-row .suggest-icon.file:before{background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNiAxNiI+PHN0eWxlPi5pY29uLWNhbnZhcy10cmFuc3BhcmVudHtvcGFjaXR5OjA7ZmlsbDojMmQyZDMwfS5pY29uLXZzLW91dHtmaWxsOiMyZDJkMzB9Lmljb24tdnMtYmd7ZmlsbDojYzVjNWM1fS5pY29uLXZzLWZne2ZpbGw6IzJiMjgyZX08L3N0eWxlPjxwYXRoIGNsYXNzPSJpY29uLWNhbnZhcy10cmFuc3BhcmVudCIgZD0iTTE2IDE2SDBWMGgxNnYxNnoiIGlkPSJjYW52YXMiLz48cGF0aCBjbGFzcz0iaWNvbi12cy1vdXQiIGQ9Ik0xNSAxNkgyVjBoOC42MjFMMTUgNC4zNzlWMTZ6IiBpZD0ib3V0bGluZSIvPjxwYXRoIGNsYXNzPSJpY29uLXZzLWZnIiBkPSJNMTMgMTRINFYyaDV2NGg0djh6bS0zLTlWMi4yMDdMMTIuNzkzIDVIMTB6IiBpZD0iaWNvbkZnIi8+PHBhdGggY2xhc3M9Imljb24tdnMtYmciIGQ9Ik0zIDF2MTRoMTFWNC43OTNMMTAuMjA3IDFIM3ptMTAgMTNINFYyaDV2NGg0djh6bS0zLTlWMi4yMDdMMTIuNzkzIDVIMTB6IiBpZD0iaWNvbkJnIi8+PC9zdmc+)}.monaco-editor.hc-black .suggest-widget .monaco-list .monaco-list-row .suggest-icon.reference:before,.monaco-editor.vs-dark .suggest-widget .monaco-list .monaco-list-row .suggest-icon.reference:before{background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNiAxNiI+PHN0eWxlPi5pY29uLWNhbnZhcy10cmFuc3BhcmVudHtvcGFjaXR5OjA7ZmlsbDojMmQyZDMwfS5pY29uLXZzLW91dHtmaWxsOiMyZDJkMzB9Lmljb24tdnMtYmd7ZmlsbDojYzVjNWM1fS5pY29uLXZzLWZne2ZpbGw6IzJiMjgyZX0uaWNvbi12cy1hY3Rpb24tYmx1ZXtmaWxsOiM3NWJlZmZ9PC9zdHlsZT48cGF0aCBjbGFzcz0iaWNvbi1jYW52YXMtdHJhbnNwYXJlbnQiIGQ9Ik0xNiAxNkgwVjBoMTZ2MTZ6IiBpZD0iY2FudmFzIi8+PHBhdGggY2xhc3M9Imljb24tdnMtb3V0IiBkPSJNMTQgNC41NTZWMTNjMCAuOTctLjcwMSAyLTIgMkg0Yy0uOTcgMC0yLS43MDEtMi0yVjYuNjQ5QTMuNDk1IDMuNDk1IDAgMCAxIDAgMy41QzAgMS41NyAxLjU3IDAgMy41IDBINXYxaDUuMDYxTDE0IDQuNTU2eiIgaWQ9Im91dGxpbmUiIHN0eWxlPSJkaXNwbGF5OiBub25lOyIvPjxwYXRoIGNsYXNzPSJpY29uLXZzLWJnIiBkPSJNMTMgNXY4cy0uMDM1IDEtMS4wMzUgMWgtOFMzIDE0IDMgMTNWOWgxdjRoOFY2SDkuMzk3bC41MTctLjUyTDkgNC41NzJWM0g3LjQxOUw2LjQxMyAyaDMuMjI4TDEzIDV6IiBpZD0iaWNvbkJnIi8+PHBhdGggY2xhc3M9Imljb24tdnMtZmciIGQ9Ik03LjQxOSAzSDl2MS41NzJMNy40MTkgM3ptMS45NzggM0w2LjQxNiA5SDR2NGg4VjZIOS4zOTd6IiBpZD0iaWNvbkZnIiBzdHlsZT0iZGlzcGxheTogbm9uZTsiLz48cGF0aCBjbGFzcz0iaWNvbi12cy1hY3Rpb24tYmx1ZSIgZD0iTTUuOTg4IDZIMy41YTIuNSAyLjUgMCAxIDEgMC01SDR2MWgtLjVDMi42NzMgMiAyIDIuNjczIDIgMy41UzIuNjczIDUgMy41IDVoMi41MTNMNCAzaDJsMi41IDIuNDg0TDYgOEg0bDEuOTg4LTJ6IiBpZD0iY29sb3JBY3Rpb24iLz48L3N2Zz4=)}.monaco-editor.hc-black .suggest-widget .monaco-list .monaco-list-row .suggest-icon.snippet:before,.monaco-editor.vs-dark .suggest-widget .monaco-list .monaco-list-row .suggest-icon.snippet:before{background-image:url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcKICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIgogICB4bWxuczpjYz0iaHR0cDovL2NyZWF0aXZlY29tbW9ucy5vcmcvbnMjIgogICB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiCiAgIHhtbG5zOnN2Zz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICAgdmVyc2lvbj0iMS4xIgogICBpZD0ic3ZnNDY5NCIKICAgdmlld0JveD0iMCAwIDE2IDE2Ij4KICA8bWV0YWRhdGEKICAgICBpZD0ibWV0YWRhdGE0NzA1Ij4KICAgIDxyZGY6UkRGPgogICAgICA8Y2M6V29yawogICAgICAgICByZGY6YWJvdXQ9IiI+CiAgICAgICAgPGRjOmZvcm1hdD5pbWFnZS9zdmcreG1sPC9kYzpmb3JtYXQ+CiAgICAgICAgPGRjOnR5cGUKICAgICAgICAgICByZGY6cmVzb3VyY2U9Imh0dHA6Ly9wdXJsLm9yZy9kYy9kY21pdHlwZS9TdGlsbEltYWdlIiAvPgogICAgICAgIDxkYzp0aXRsZT48L2RjOnRpdGxlPgogICAgICA8L2NjOldvcms+CiAgICA8L3JkZjpSREY+CiAgPC9tZXRhZGF0YT4KICA8ZGVmcwogICAgIGlkPSJkZWZzNDcwMyIgLz4KICA8c3R5bGUKICAgICBpZD0ic3R5bGU0Njk2Ij4uaWNvbi1jYW52YXMtdHJhbnNwYXJlbnR7b3BhY2l0eTowO2ZpbGw6I2Y2ZjZmNn0uaWNvbi12cy1vdXR7ZmlsbDojZjZmNmY2fS5pY29uLXZzLWFjdGlvbi1vcmFuZ2V7ZmlsbDojYzI3ZDFhfTwvc3R5bGU+CiAgPGcKICAgICBpZD0iZzQ3MjQiCiAgICAgdHJhbnNmb3JtPSJtYXRyaXgoMS4zMzMzMzMzLDAsMCwxLjMzMzMzMzMsLTI0NS45OTk5OSwtMzEuOTk5OTk5KSI+CiAgICA8cGF0aAogICAgICAgZD0ibSAxODUsMjQgMTEsMCAwLDEyIC0xMSwwIHoiCiAgICAgICBpZD0icGF0aDQ1MjgiCiAgICAgICBzdHlsZT0iZmlsbDojMmQyZDMwIiAvPgogICAgPHBhdGgKICAgICAgIGQ9Im0gMTk0LDMzIDAsLTcgLTcsMCAwLDcgLTEsMCAwLC04IDksMCAwLDggeiBtIC04LDEgMSwwIDAsMSAtMSwwIHogbSAyLDAgMSwwIDAsMSAtMSwwIHogbSAyLDAgMSwwIDAsMSAtMSwwIHogbSAyLDAgMSwwIDAsMSAtMSwwIHogbSAyLDAgMSwwIDAsMSAtMSwwIHoiCiAgICAgICBpZD0icGF0aDQ1MzAiCiAgICAgICBzdHlsZT0iZmlsbDojYzVjNWM1IiAvPgogICAgPHBhdGgKICAgICAgIGQ9Im0gMTg3LDI2IDcsMCAwLDcgLTcsMCB6IgogICAgICAgaWQ9InBhdGg0NTMyIgogICAgICAgc3R5bGU9ImZpbGw6IzJiMjgyZSIgLz4KICA8L2c+Cjwvc3ZnPgo=)}.monaco-editor.hc-black .suggest-widget .monaco-list .monaco-list-row .suggest-icon.customcolor:before,.monaco-editor.vs-dark .suggest-widget .monaco-list .monaco-list-row .suggest-icon.customcolor:before{background-image:none}.monaco-editor.hc-black .suggest-widget .monaco-list .monaco-list-row .suggest-icon.folder:before,.monaco-editor.vs-dark .suggest-widget .monaco-list .monaco-list-row .suggest-icon.folder:before{background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiI+PHN0eWxlIHR5cGU9InRleHQvY3NzIj4uaWNvbi1jYW52YXMtdHJhbnNwYXJlbnR7b3BhY2l0eTowO2ZpbGw6I0Y2RjZGNjt9IC5pY29uLXZzLW91dHtvcGFjaXR5OjA7ZmlsbDojRjZGNkY2O30gLmljb24tdnMtZmd7b3BhY2l0eTowO2ZpbGw6I0YwRUZGMTt9IC5pY29uLWZvbGRlcntmaWxsOiNDNUM1QzU7fTwvc3R5bGU+PHBhdGggY2xhc3M9Imljb24tY2FudmFzLXRyYW5zcGFyZW50IiBkPSJNMTYgMTZoLTE2di0xNmgxNnYxNnoiIGlkPSJjYW52YXMiLz48cGF0aCBjbGFzcz0iaWNvbi12cy1vdXQiIGQ9Ik0xNiAyLjV2MTBjMCAuODI3LS42NzMgMS41LTEuNSAxLjVoLTExLjk5NmMtLjgyNyAwLTEuNS0uNjczLTEuNS0xLjV2LThjMC0uODI3LjY3My0xLjUgMS41LTEuNWgyLjg4NmwxLTJoOC4xMWMuODI3IDAgMS41LjY3MyAxLjUgMS41eiIgaWQ9Im91dGxpbmUiLz48cGF0aCBjbGFzcz0iaWNvbi1mb2xkZXIiIGQ9Ik0xNC41IDJoLTcuNDkybC0xIDJoLTMuNTA0Yy0uMjc3IDAtLjUuMjI0LS41LjV2OGMwIC4yNzYuMjIzLjUuNS41aDExLjk5NmMuMjc1IDAgLjUtLjIyNC41LS41di0xMGMwLS4yNzYtLjIyNS0uNS0uNS0uNXptLS40OTYgMmgtNi40OTZsLjUtMWg1Ljk5NnYxeiIgaWQ9Imljb25CZyIvPjxwYXRoIGNsYXNzPSJpY29uLXZzLWZnIiBkPSJNMTQgM3YxaC02LjVsLjUtMWg2eiIgaWQ9Imljb25GZyIvPjwvc3ZnPg==)}.monaco-editor .zone-widget{position:absolute;z-index:10}.monaco-editor .zone-widget .zone-widget-container{border-top-style:solid;border-bottom-style:solid;border-top-width:0;border-bottom-width:0;position:relative}.monaco-editor .accessibilityHelpWidget{padding:10px;vertical-align:middle;overflow:scroll}.monaco-editor .iPadShowKeyboard{width:58px;min-width:0;height:36px;min-height:0;margin:0;padding:0;position:absolute;resize:none;overflow:hidden;background:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1OCIgaGVpZ2h0PSIzNiI+PHBhdGggZmlsbD0iI0YwRUZGMSIgZD0iTTU0IDMydi0yOGgtNTB2MjhoNTB6bS0xNi0yaC0xOHYtNmgxOHY2em02IDBoLTR2LTZoNHY2em04IDBoLTZ2LTZoNnY2em0tNC0yNGg0djRoLTR2LTR6bTAgNmg0djRoLTR2LTR6bTAgNmg0djRoLTR2LTR6bS02LTEyaDR2NGgtNHYtNHptMCA2aDR2NGgtNHYtNHptMCA2aDR2NGgtNHYtNHptLTYtMTJoNHY0aC00di00em0wIDZoNHY0aC00di00em0wIDZoNHY0aC00di00em0tNi0xMmg0djRoLTR2LTR6bTAgNmg0djRoLTR2LTR6bTAgNmg0djRoLTR2LTR6bS02LTEyaDR2NGgtNHYtNHptMCA2aDR2NGgtNHYtNHptMCA2aDR2NGgtNHYtNHptLTYtMTJoNHY0aC00di00em0wIDZoNHY0aC00di00em0wIDZoNHY0aC00di00em0wIDEyaC00di02aDR2NnptLTYtMjRoNHY0aC00di00em0wIDZoNHY0aC00di00em0wIDZoNHY0aC00di00em0tNi0xMmg0djRoLTR2LTR6bTAgNmg0djRoLTR2LTR6bTAgNmg0djRoLTR2LTR6bTAgNmg2djZoLTZ2LTZ6Ii8+PHBhdGggZmlsbD0iIzQyNDI0MiIgZD0iTTU1LjMzNiAwaC01My4yODVjLTEuMzQ0IDAtMi4wNTEuNjU2LTIuMDUxIDJ2MzJjMCAxLjM0NC43MDcgMS45NjUgMi4wNTEgMS45NjVsNTMuOTQ5LjAzNWMxLjM0NCAwIDItLjY1NiAyLTJ2LTMyYzAtMS4zNDQtMS4zMi0yLTIuNjY0LTJ6bS0xLjMzNiAzMmgtNTB2LTI4aDUwdjI4eiIvPjxyZWN0IHg9IjYiIHk9IjEyIiBmaWxsPSIjNDI0MjQyIiB3aWR0aD0iNCIgaGVpZ2h0PSI0Ii8+PHJlY3QgeD0iMTIiIHk9IjEyIiBmaWxsPSIjNDI0MjQyIiB3aWR0aD0iNCIgaGVpZ2h0PSI0Ii8+PHJlY3QgeD0iMTgiIHk9IjEyIiBmaWxsPSIjNDI0MjQyIiB3aWR0aD0iNCIgaGVpZ2h0PSI0Ii8+PHJlY3QgeD0iMjQiIHk9IjEyIiBmaWxsPSIjNDI0MjQyIiB3aWR0aD0iNCIgaGVpZ2h0PSI0Ii8+PHJlY3QgeD0iMzAiIHk9IjEyIiBmaWxsPSIjNDI0MjQyIiB3aWR0aD0iNCIgaGVpZ2h0PSI0Ii8+PHJlY3QgeD0iMzYiIHk9IjEyIiBmaWxsPSIjNDI0MjQyIiB3aWR0aD0iNCIgaGVpZ2h0PSI0Ii8+PHJlY3QgeD0iNDIiIHk9IjEyIiBmaWxsPSIjNDI0MjQyIiB3aWR0aD0iNCIgaGVpZ2h0PSI0Ii8+PHJlY3QgeD0iNDgiIHk9IjEyIiBmaWxsPSIjNDI0MjQyIiB3aWR0aD0iNCIgaGVpZ2h0PSI0Ii8+PHJlY3QgeD0iNiIgeT0iNiIgZmlsbD0iIzQyNDI0MiIgd2lkdGg9IjQiIGhlaWdodD0iNCIvPjxyZWN0IHg9IjEyIiB5PSI2IiBmaWxsPSIjNDI0MjQyIiB3aWR0aD0iNCIgaGVpZ2h0PSI0Ii8+PHJlY3QgeD0iMTgiIHk9IjYiIGZpbGw9IiM0MjQyNDIiIHdpZHRoPSI0IiBoZWlnaHQ9IjQiLz48cmVjdCB4PSIyNCIgeT0iNiIgZmlsbD0iIzQyNDI0MiIgd2lkdGg9IjQiIGhlaWdodD0iNCIvPjxyZWN0IHg9IjMwIiB5PSI2IiBmaWxsPSIjNDI0MjQyIiB3aWR0aD0iNCIgaGVpZ2h0PSI0Ii8+PHJlY3QgeD0iMzYiIHk9IjYiIGZpbGw9IiM0MjQyNDIiIHdpZHRoPSI0IiBoZWlnaHQ9IjQiLz48cmVjdCB4PSI0MiIgeT0iNiIgZmlsbD0iIzQyNDI0MiIgd2lkdGg9IjQiIGhlaWdodD0iNCIvPjxyZWN0IHg9IjQ4IiB5PSI2IiBmaWxsPSIjNDI0MjQyIiB3aWR0aD0iNCIgaGVpZ2h0PSI0Ii8+PHJlY3QgeD0iNiIgeT0iMTgiIGZpbGw9IiM0MjQyNDIiIHdpZHRoPSI0IiBoZWlnaHQ9IjQiLz48cmVjdCB4PSIxMiIgeT0iMTgiIGZpbGw9IiM0MjQyNDIiIHdpZHRoPSI0IiBoZWlnaHQ9IjQiLz48cmVjdCB4PSIxOCIgeT0iMTgiIGZpbGw9IiM0MjQyNDIiIHdpZHRoPSI0IiBoZWlnaHQ9IjQiLz48cmVjdCB4PSIyNCIgeT0iMTgiIGZpbGw9IiM0MjQyNDIiIHdpZHRoPSI0IiBoZWlnaHQ9IjQiLz48cmVjdCB4PSIzMCIgeT0iMTgiIGZpbGw9IiM0MjQyNDIiIHdpZHRoPSI0IiBoZWlnaHQ9IjQiLz48cmVjdCB4PSIzNiIgeT0iMTgiIGZpbGw9IiM0MjQyNDIiIHdpZHRoPSI0IiBoZWlnaHQ9IjQiLz48cmVjdCB4PSI0MiIgeT0iMTgiIGZpbGw9IiM0MjQyNDIiIHdpZHRoPSI0IiBoZWlnaHQ9IjQiLz48cmVjdCB4PSI0OCIgeT0iMTgiIGZpbGw9IiM0MjQyNDIiIHdpZHRoPSI0IiBoZWlnaHQ9IjQiLz48cmVjdCB4PSI2IiB5PSIyNCIgZmlsbD0iIzQyNDI0MiIgd2lkdGg9IjYiIGhlaWdodD0iNiIvPjxyZWN0IHg9IjQ2IiB5PSIyNCIgZmlsbD0iIzQyNDI0MiIgd2lkdGg9IjYiIGhlaWdodD0iNiIvPjxyZWN0IHg9IjIwIiB5PSIyNCIgZmlsbD0iIzQyNDI0MiIgd2lkdGg9IjE4IiBoZWlnaHQ9IjYiLz48cmVjdCB4PSIxNCIgeT0iMjQiIGZpbGw9IiM0MjQyNDIiIHdpZHRoPSI0IiBoZWlnaHQ9IjYiLz48cmVjdCB4PSI0MCIgeT0iMjQiIGZpbGw9IiM0MjQyNDIiIHdpZHRoPSI0IiBoZWlnaHQ9IjYiLz48L3N2Zz4=) 50% no-repeat;border:4px solid #f6f6f6;border-radius:4px}.monaco-editor.vs-dark .iPadShowKeyboard{background:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1OCIgaGVpZ2h0PSIzNiI+PHBhdGggZmlsbD0iIzJCMjgyRSIgZD0iTTU0IDMydi0yOGgtNTB2MjhoNTB6bS0xNi0yaC0xOHYtNmgxOHY2em02IDBoLTR2LTZoNHY2em04IDBoLTZ2LTZoNnY2em0tNC0yNGg0djRoLTR2LTR6bTAgNmg0djRoLTR2LTR6bTAgNmg0djRoLTR2LTR6bS02LTEyaDR2NGgtNHYtNHptMCA2aDR2NGgtNHYtNHptMCA2aDR2NGgtNHYtNHptLTYtMTJoNHY0aC00di00em0wIDZoNHY0aC00di00em0wIDZoNHY0aC00di00em0tNi0xMmg0djRoLTR2LTR6bTAgNmg0djRoLTR2LTR6bTAgNmg0djRoLTR2LTR6bS02LTEyaDR2NGgtNHYtNHptMCA2aDR2NGgtNHYtNHptMCA2aDR2NGgtNHYtNHptLTYtMTJoNHY0aC00di00em0wIDZoNHY0aC00di00em0wIDZoNHY0aC00di00em0wIDEyaC00di02aDR2NnptLTYtMjRoNHY0aC00di00em0wIDZoNHY0aC00di00em0wIDZoNHY0aC00di00em0tNi0xMmg0djRoLTR2LTR6bTAgNmg0djRoLTR2LTR6bTAgNmg0djRoLTR2LTR6bTAgNmg2djZoLTZ2LTZ6Ii8+PHBhdGggZmlsbD0iI0M1QzVDNSIgZD0iTTU1LjMzNiAwaC01My4yODVjLTEuMzQ0IDAtMi4wNTEuNjU2LTIuMDUxIDJ2MzJjMCAxLjM0NC43MDcgMS45NjUgMi4wNTEgMS45NjVsNTMuOTQ5LjAzNWMxLjM0NCAwIDItLjY1NiAyLTJ2LTMyYzAtMS4zNDQtMS4zMi0yLTIuNjY0LTJ6bS0xLjMzNiAzMmgtNTB2LTI4aDUwdjI4eiIvPjxyZWN0IHg9IjYiIHk9IjEyIiBmaWxsPSIjQzVDNUM1IiB3aWR0aD0iNCIgaGVpZ2h0PSI0Ii8+PHJlY3QgeD0iMTIiIHk9IjEyIiBmaWxsPSIjQzVDNUM1IiB3aWR0aD0iNCIgaGVpZ2h0PSI0Ii8+PHJlY3QgeD0iMTgiIHk9IjEyIiBmaWxsPSIjQzVDNUM1IiB3aWR0aD0iNCIgaGVpZ2h0PSI0Ii8+PHJlY3QgeD0iMjQiIHk9IjEyIiBmaWxsPSIjQzVDNUM1IiB3aWR0aD0iNCIgaGVpZ2h0PSI0Ii8+PHJlY3QgeD0iMzAiIHk9IjEyIiBmaWxsPSIjQzVDNUM1IiB3aWR0aD0iNCIgaGVpZ2h0PSI0Ii8+PHJlY3QgeD0iMzYiIHk9IjEyIiBmaWxsPSIjQzVDNUM1IiB3aWR0aD0iNCIgaGVpZ2h0PSI0Ii8+PHJlY3QgeD0iNDIiIHk9IjEyIiBmaWxsPSIjQzVDNUM1IiB3aWR0aD0iNCIgaGVpZ2h0PSI0Ii8+PHJlY3QgeD0iNDgiIHk9IjEyIiBmaWxsPSIjQzVDNUM1IiB3aWR0aD0iNCIgaGVpZ2h0PSI0Ii8+PHJlY3QgeD0iNiIgeT0iNiIgZmlsbD0iI0M1QzVDNSIgd2lkdGg9IjQiIGhlaWdodD0iNCIvPjxyZWN0IHg9IjEyIiB5PSI2IiBmaWxsPSIjQzVDNUM1IiB3aWR0aD0iNCIgaGVpZ2h0PSI0Ii8+PHJlY3QgeD0iMTgiIHk9IjYiIGZpbGw9IiNDNUM1QzUiIHdpZHRoPSI0IiBoZWlnaHQ9IjQiLz48cmVjdCB4PSIyNCIgeT0iNiIgZmlsbD0iI0M1QzVDNSIgd2lkdGg9IjQiIGhlaWdodD0iNCIvPjxyZWN0IHg9IjMwIiB5PSI2IiBmaWxsPSIjQzVDNUM1IiB3aWR0aD0iNCIgaGVpZ2h0PSI0Ii8+PHJlY3QgeD0iMzYiIHk9IjYiIGZpbGw9IiNDNUM1QzUiIHdpZHRoPSI0IiBoZWlnaHQ9IjQiLz48cmVjdCB4PSI0MiIgeT0iNiIgZmlsbD0iI0M1QzVDNSIgd2lkdGg9IjQiIGhlaWdodD0iNCIvPjxyZWN0IHg9IjQ4IiB5PSI2IiBmaWxsPSIjQzVDNUM1IiB3aWR0aD0iNCIgaGVpZ2h0PSI0Ii8+PHJlY3QgeD0iNiIgeT0iMTgiIGZpbGw9IiNDNUM1QzUiIHdpZHRoPSI0IiBoZWlnaHQ9IjQiLz48cmVjdCB4PSIxMiIgeT0iMTgiIGZpbGw9IiNDNUM1QzUiIHdpZHRoPSI0IiBoZWlnaHQ9IjQiLz48cmVjdCB4PSIxOCIgeT0iMTgiIGZpbGw9IiNDNUM1QzUiIHdpZHRoPSI0IiBoZWlnaHQ9IjQiLz48cmVjdCB4PSIyNCIgeT0iMTgiIGZpbGw9IiNDNUM1QzUiIHdpZHRoPSI0IiBoZWlnaHQ9IjQiLz48cmVjdCB4PSIzMCIgeT0iMTgiIGZpbGw9IiNDNUM1QzUiIHdpZHRoPSI0IiBoZWlnaHQ9IjQiLz48cmVjdCB4PSIzNiIgeT0iMTgiIGZpbGw9IiNDNUM1QzUiIHdpZHRoPSI0IiBoZWlnaHQ9IjQiLz48cmVjdCB4PSI0MiIgeT0iMTgiIGZpbGw9IiNDNUM1QzUiIHdpZHRoPSI0IiBoZWlnaHQ9IjQiLz48cmVjdCB4PSI0OCIgeT0iMTgiIGZpbGw9IiNDNUM1QzUiIHdpZHRoPSI0IiBoZWlnaHQ9IjQiLz48cmVjdCB4PSI2IiB5PSIyNCIgZmlsbD0iI0M1QzVDNSIgd2lkdGg9IjYiIGhlaWdodD0iNiIvPjxyZWN0IHg9IjQ2IiB5PSIyNCIgZmlsbD0iI0M1QzVDNSIgd2lkdGg9IjYiIGhlaWdodD0iNiIvPjxyZWN0IHg9IjIwIiB5PSIyNCIgZmlsbD0iI0M1QzVDNSIgd2lkdGg9IjE4IiBoZWlnaHQ9IjYiLz48cmVjdCB4PSIxNCIgeT0iMjQiIGZpbGw9IiNDNUM1QzUiIHdpZHRoPSI0IiBoZWlnaHQ9IjYiLz48cmVjdCB4PSI0MCIgeT0iMjQiIGZpbGw9IiNDNUM1QzUiIHdpZHRoPSI0IiBoZWlnaHQ9IjYiLz48L3N2Zz4=) 50% no-repeat;border:4px solid #252526}.monaco-editor .tokens-inspect-widget{z-index:50;-webkit-user-select:text;-ms-user-select:text;-moz-user-select:text;-o-user-select:text;user-select:text;padding:10px}.tokens-inspect-separator{height:1px;border:0}.monaco-editor .tokens-inspect-widget .tm-token{font-family:monospace}.monaco-editor .tokens-inspect-widget .tm-token-length{font-weight:400;font-size:60%;float:right}.monaco-editor .tokens-inspect-widget .tm-metadata-table{width:100%}.monaco-editor .tokens-inspect-widget .tm-metadata-value{font-family:monospace;text-align:right}.monaco-editor .tokens-inspect-widget .tm-token-type{font-family:monospace}.monaco-quick-open-widget .monaco-list .monaco-list-row .monaco-highlighted-label .highlight,.monaco-quick-open-widget .monaco-tree .monaco-tree-row .monaco-highlighted-label .highlight{color:#0066bf}.vs-dark .monaco-quick-open-widget .monaco-list .monaco-list-row .monaco-highlighted-label .highlight,.vs-dark .monaco-quick-open-widget .monaco-tree .monaco-tree-row .monaco-highlighted-label .highlight{color:#0097fb}.hc-black .monaco-quick-open-widget .monaco-list .monaco-list-row .monaco-highlighted-label .highlight,.hc-black .monaco-quick-open-widget .monaco-tree .monaco-tree-row .monaco-highlighted-label .highlight{color:#f38518}.monaco-quick-open-widget{font-size:13px}.monaco-quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon,.vs-dark .monaco-quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon{background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzMDAiIGhlaWdodD0iNDAiPjxwYXRoIGQ9Ik0yODguNDgzIDMzYy0uNzcyIDAtMS40OTctLjEyMy0yLjE1My0uMzY1LS42NzgtLjI1My0xLjI3LS42MTctMS43Ni0xLjA4NC0uNS0uNDc1LS44OTItMS4wNDktMS4xNjMtMS43MDQtLjI3LS42NDQtLjQwNy0xLjM3MS0uNDA3LTIuMTU4IDAtLjUxNy4wNjEtMS4wMTguMTc4LTEuNDkuMTE2LS40Ny4yOS0uOTI1LjUxNi0xLjM0OC4yMjUtLjQyMi41MDgtLjgxNS44NDQtMS4xNjcuMzM0LS4zNTIuNzE3LS42NTYgMS4xMzktLjkwNS40MTYtLjI0Ni44ODEtLjQ0IDEuMzgtLjU3Ni40OTMtLjEzNCAxLjAyNi0uMjAyIDEuNTg3LS4yMDIuNzA1IDAgMS4zODIuMTA5IDIuMDEzLjMyNC42NDIuMjE3IDEuMjE4LjUzOCAxLjcwOC45NTUuNTAxLjQyNS45MDMuOTQ4IDEuMTkzIDEuNTU2LjI5NC42MjMuNDQyIDEuMzE2LjQ0MiAyLjA2NCAwIC42MTktLjA5IDEuMTg1LS4yNjggMS42NzktLjE3OC40OTItLjQyLjkyLS43MjEgMS4yNzUtLjMzMS4zNzctLjY5OS42NTgtMS4xMDQuODQ3bC0uMDQ4LjAyMnYxLjUzbC0uNTg3LjI2NmMtLjEyOC4wNTktLjI4OC4xMTctLjQ3NC4xNzktLjE5My4wNjItLjQwNC4xMTQtLjY0NS4xNTktLjIyOS4wNC0uNDc3LjA3Ni0uNzUzLjEwMy0uMjcuMDI3LS41NzguMDQtLjkxNy4wNHoiIGZpbGw9IiMyRDJEMkQiLz48cGF0aCBkPSJNMjkxLjcxNiAyNC4wNDFjLS4zOTYtLjMzNi0uODU2LS41OTMtMS4zODQtLjc3MS0uNTI3LS4xOC0xLjA5LS4yNzEtMS42ODktLjI3MS0uNDczIDAtLjkxMi4wNTUtMS4zMjQuMTY3LS40MTQuMTEyLS43OTEuMjctMS4xMzUuNDczLS4zNDIuMjAyLS42NS40NDYtLjkyMi43MzMtLjI3My4yODYtLjUwMi42MDItLjY4Ni45NDktLjE4Ni4zNDctLjMzLjcyMi0uNDI4IDEuMTE5LS4xLjM5OS0uMTQ4LjgxNC0uMTQ4IDEuMjQ3IDAgLjY1Mi4xMDkgMS4yNDcuMzMyIDEuNzc2LjIxOS41MzEuNTMuOTg0LjkyOCAxLjM2MS4zOTYuMzc4Ljg3MS42NjcgMS40MTYuODcuNTQ4LjIwMiAxLjE1Mi4zMDQgMS44MDguMzA0LjMwMiAwIC41NzctLjAxMS44MjMtLjAzNS4yNDYtLjAyMy40NjgtLjA1Ni42NjQtLjA5MS4xOTUtLjAzNi4zNjYtLjA3OC41MTQtLjEyNWwuMzc1LS4xNHYtLjg1NGwtLjQ2My4xODRjLS4xNi4wNTYtLjMzNi4xMDQtLjUyMS4xNDMtLjE4OC4wMzctLjM4Ny4wNjktLjYwNC4wODktLjIxMy4wMjQtLjQ0OC4wMzQtLjcuMDM0LS41NjIgMC0xLjA2NC0uMDg4LTEuNTA5LS4yNjQtLjQ0Mi0uMTc2LS44MTYtLjQyMS0xLjEyNS0uNzMxLS4zMDktLjMxNC0uNTQ1LS42ODctLjcwOC0xLjEyNC0uMTYxLS40MzUtLjI0My0uOTEzLS4yNDMtMS40MzIgMC0uNTQ1LjA5LTEuMDUzLjI3My0xLjUyMi4xODItLjQ3MS40MzUtLjg3OS43NTgtMS4yMjUuMzI0LS4zNDUuNzA4LS42MTcgMS4xNTUtLjgxNS40NDYtLjE5Ni45MzQtLjI5NCAxLjQ1Ny0uMjk0LjQxOSAwIC43OTguMDQ0IDEuMTIyLjEzNi4zMjkuMDkxLjYyLjIxNS44NzEuMzY5LjI1NC4xNTguNDY1LjMzOS42NDMuNTQ3LjE3OS4yMDkuMzI0LjQzMi40MzguNjY3LjExMy4yMzcuMTkzLjQ4LjI0Ni43MzEuMDUxLjI1NC4wNzYuNS4wNzYuNzQxIDAgLjM0NC0uMDMzLjY1My0uMTAyLjkyNi0uMDY4LjI3NC0uMTU4LjUwMy0uMjY5LjY5NC0uMTEuMTg5LS4yMzkuMzM1LS4zODYuNDM0cy0uMjk1LjE0OC0uNDUzLjE0OGwtLjIxNS0uMDQ1Yy0uMDY2LS4wMjktLjExOS0uMDgtLjE2Ni0uMTU2LS4wNDYtLjA3NS0uMDgyLS4xNzctLjEwNy0uMzA2LS4wMjUtLjEyNi0uMDM5LS4yOTItLjAzOS0uNDkybC4wMTgtLjMyNS4wNDEtLjUzLjA1NS0uNjQ0LjA1OC0uNjQ3LjA0OC0uNTQ2LjAyNy0uMzQ0aC0uOTE5bC0uMDU0LjZoLS4wMjFjLS4wMjUtLjEwMy0uMDctLjE5NS0uMTM2LS4yODEtLjA2My0uMDgzLS4xNDEtLjE1NS0uMjMzLS4yMTYtLjA5MS0uMDYxLS4xOTMtLjEwNi0uMzA3LS4xNDEtLjExNS0uMDMzLS4yMzgtLjA0OC0uMzY5LS4wNDgtLjMzNyAwLS42NDYuMDctLjkyNC4yMTYtLjI4MS4xNDQtLjUxOC4zNDQtLjcyMS41OTktLjIwMS4yNTQtLjM1NS41NTYtLjQ2NS45MDUtLjExNS4zNS0uMTcuNzI2LS4xNyAxLjEzNCAwIC4zNDQuMDQ1LjY0NS4xMzUuOTAxLjA4OC4yNi4yMTEuNDczLjM1OS42NDYuMTUzLjE3MS4zMjkuMy41MzQuMzgyLjIuMDg2LjQxNS4xMjkuNjQxLjEyOS4xNzYgMCAuMzQyLS4wMjcuNDk5LS4wODEuMTU0LS4wNTIuMzAyLS4xMy40MzItLjIzMi4xMzQtLjEwNC4yNDgtLjIzLjM0OC0uMzguMTAyLS4xNDkuMTgyLS4zMjMuMjM2LS41MmguMDI3YzAgLjM3Ni4xMDEuNjc0LjMwNy44OTMuMjA3LjIyLjUwMi4zMy44ODkuMzMuMjkyIDAgLjU4LS4wNjQuODYzLS4xOTguMjgzLS4xMzIuNTM2LS4zMjguNzYyLS41ODYuMjIzLS4yNjIuNDA0LS41ODMuNTQzLS45NjYuMTM4LS4zODQuMjA4LS44My4yMDgtMS4zNCAwLS42MDUtLjExNy0xLjE1LS4zNDUtMS42MzQtLjIzMS0uNDgyLS41NDYtLjg5MS0uOTM5LTEuMjI1bS0yLjM2OCAzLjc3NGMtLjA1Ni4yNzctLjEzNi41MTctLjI0Ni43MTktLjEwOS4yMDMtLjI0Ni4zNjMtLjQwNy40ODEtLjE2My4xMTUtLjM1NC4xNzYtLjU3Mi4xNzYtLjEyIDAtLjIzNi0uMDI1LS4zNDQtLjA3OC0uMTA4LS4wNTItLjIwNi0uMTMtLjI4OS0uMjMyLS4wODEtLjEwMy0uMTQ4LS4yMzQtLjE5OC0uMzktLjA0Ni0uMTU2LS4wNy0uMzM3LS4wNy0uNTQ3IDAtLjIzNy4wMjctLjQ4MS4wOC0uNzI5LjA1Ni0uMjQ3LjEzNy0uNDczLjI1LS42NzcuMTA5LS4yLjI1LS4zNjMuNDE2LS40OTIuMTY1LS4xMjcuMzYxLS4xOTEuNTgyLS4xOTEuMTIzIDAgLjIzNC4wMjEuMzQuMDYzLjEwNy4wNDIuMTk4LjEwNy4yNzkuMTk2LjA4LjA4Ny4xNDUuMTk3LjE4OS4zMy4wNDMuMTM0LjA3LjI5NC4wNy40OCAwIC4zMTctLjAzMS42MTUtLjA4Ljg5MSIgZmlsbD0iI0M1QzVDNSIvPjxwYXRoIGQ9Ik0yODguNDgzIDEzYy0uNzcyIDAtMS40OTctLjEyMy0yLjE1My0uMzY1LS42NzgtLjI1My0xLjI3LS42MTctMS43Ni0xLjA4NC0uNS0uNDc1LS44OTItMS4wNDktMS4xNjMtMS43MDQtLjI2OS0uNjQ0LS40MDctMS4zNzEtLjQwNy0yLjE1OSAwLS41MTcuMDYxLTEuMDE4LjE3OC0xLjQ5LjExNi0uNDcuMjktLjkyNS41MTYtMS4zNDguMjI1LS40MjIuNTA4LS44MTUuODQ0LTEuMTY3LjMzNC0uMzUyLjcxNy0uNjU2IDEuMTM5LS45MDUuNDE2LS4yNDYuODgxLS40NCAxLjM4LS41NzYuNDkyLS4xMzQgMS4wMjUtLjIwMiAxLjU4Ni0uMjAyLjcwNSAwIDEuMzgyLjEwOSAyLjAxMy4zMjQuNjQyLjIxNyAxLjIxOC41MzggMS43MDguOTU1LjUwMS40MjUuOTAzLjk0OCAxLjE5MyAxLjU1Ni4yOTUuNjI0LjQ0MyAxLjMxNy40NDMgMi4wNjUgMCAuNjE5LS4wOSAxLjE4NS0uMjY4IDEuNjc5LS4xNzguNDkyLS40Mi45Mi0uNzIxIDEuMjc1LS4zMzEuMzc3LS42OTkuNjU4LTEuMTA0Ljg0N2wtLjA0OC4wMjJ2MS41M2wtLjU4Ny4yNjZjLS4xMjguMDU5LS4yODguMTE3LS40NzQuMTc5LS4xOTMuMDYyLS40MDQuMTE0LS42NDUuMTU5LS4yMjkuMDQtLjQ3Ny4wNzYtLjc1My4xMDMtLjI3LjAyNy0uNTc4LjA0LS45MTcuMDR6IiBmaWxsPSIjRjNGM0YzIi8+PHBhdGggZD0iTTI5MS43MTYgNC4wNDFjLS4zOTYtLjMzNi0uODU2LS41OTMtMS4zODQtLjc3MS0uNTI3LS4xNzktMS4wOS0uMjctMS42ODktLjI3LS40NzMgMC0uOTEyLjA1NS0xLjMyNC4xNjctLjQxNC4xMTItLjc5MS4yNy0xLjEzNS40NzMtLjM0Mi4yMDItLjY1LjQ0Ni0uOTIyLjczMy0uMjczLjI4Ni0uNTAyLjYwMi0uNjg2Ljk0OS0uMTg2LjM0Ny0uMzMuNzIyLS40MjggMS4xMTktLjA5OS40LS4xNDguODE1LS4xNDggMS4yNDcgMCAuNjUyLjEwOSAxLjI0Ny4zMzIgMS43NzYuMjE5LjUzMS41My45ODQuOTI4IDEuMzYxLjM5Ni4zNzguODcxLjY2NyAxLjQxNi44Ny41NDguMjAyIDEuMTUyLjMwNCAxLjgwOC4zMDQuMzAyIDAgLjU3Ny0uMDExLjgyMy0uMDM1LjI0Ni0uMDIzLjQ2OC0uMDU2LjY2NC0uMDkxLjE5NS0uMDM2LjM2Ni0uMDc4LjUxNC0uMTI1bC4zNzUtLjE0di0uODU0bC0uNDYzLjE4NGMtLjE2LjA1Ni0uMzM2LjEwNC0uNTIxLjE0My0uMTg4LjAzNy0uMzg3LjA2OS0uNjA0LjA4OS0uMjEzLjAyNC0uNDQ4LjAzNC0uNy4wMzQtLjU2MiAwLTEuMDY0LS4wODgtMS41MDktLjI2NC0uNDQyLS4xNzYtLjgxNi0uNDIxLTEuMTI1LS43MzEtLjMwOS0uMzE0LS41NDUtLjY4Ny0uNzA4LTEuMTI0LS4xNjEtLjQzNS0uMjQzLS45MTMtLjI0My0xLjQzMiAwLS41NDUuMDktMS4wNTMuMjczLTEuNTIyLjE4Mi0uNDcxLjQzNS0uODc5Ljc1OC0xLjIyNS4zMjQtLjM0NS43MDgtLjYxNyAxLjE1NS0uODE1LjQ0Ni0uMTk2LjkzNC0uMjk0IDEuNDU3LS4yOTQuNDE5IDAgLjc5OC4wNDQgMS4xMjIuMTM2LjMyOS4wOTEuNjIuMjE1Ljg3MS4zNjkuMjU0LjE1OC40NjUuMzM5LjY0My41NDcuMTc5LjIwOS4zMjQuNDMyLjQzOC42NjcuMTEzLjIzNy4xOTMuNDguMjQ2LjczMS4wNTEuMjU0LjA3Ni41LjA3Ni43NDEgMCAuMzQ0LS4wMzMuNjUzLS4xMDIuOTI2LS4wNjguMjc0LS4xNTguNTAzLS4yNjkuNjk0LS4xMS4xODktLjIzOS4zMzUtLjM4Ni40MzRzLS4yOTUuMTQ4LS40NTMuMTQ4bC0uMjE1LS4wNDVjLS4wNjYtLjAyOS0uMTE5LS4wOC0uMTY2LS4xNTYtLjA0Ni0uMDc1LS4wODItLjE3Ny0uMTA3LS4zMDYtLjAyNS0uMTI2LS4wMzktLjI5Mi0uMDM5LS40OTJsLjAxOC0uMzI1LjA0MS0uNTMuMDU1LS42NDQuMDU4LS42NDcuMDQ4LS41NDYuMDI3LS4zNDRoLS45MTlsLS4wNTQuNmgtLjAyMWMtLjAyNS0uMTAzLS4wNy0uMTk1LS4xMzYtLjI4MS0uMDYzLS4wODMtLjE0MS0uMTU1LS4yMzMtLjIxNi0uMDkxLS4wNjEtLjE5My0uMTA2LS4zMDctLjE0MS0uMTE1LS4wMzMtLjIzOC0uMDQ4LS4zNjktLjA0OC0uMzM3IDAtLjY0Ni4wNy0uOTI0LjIxNi0uMjgxLjE0NC0uNTE4LjM0NC0uNzIxLjU5OS0uMjAxLjI1NC0uMzU1LjU1Ni0uNDY1LjkwNS0uMTE1LjM1LS4xNy43MjYtLjE3IDEuMTM0IDAgLjM0NC4wNDUuNjQ1LjEzNS45MDEuMDg4LjI2LjIxMS40NzMuMzU5LjY0Ni4xNTMuMTcxLjMyOS4zLjUzNC4zODIuMi4wODYuNDE1LjEyOS42NDEuMTI5LjE3NiAwIC4zNDItLjAyNy40OTktLjA4MS4xNTQtLjA1Mi4zMDItLjEzLjQzMi0uMjMyLjEzNC0uMTA0LjI0OC0uMjMuMzQ4LS4zOC4xMDItLjE0OS4xODItLjMyMy4yMzYtLjUyaC4wMjdjMCAuMzc2LjEwMS42NzQuMzA3Ljg5My4yMDcuMjIuNTAyLjMzLjg4OS4zMy4yOTIgMCAuNTgtLjA2NC44NjMtLjE5OC4yODMtLjEzMi41MzYtLjMyOC43NjItLjU4Ni4yMjMtLjI2Mi40MDQtLjU4My41NDMtLjk2Ni4xMzgtLjM4NS4yMDgtLjgzMS4yMDgtMS4zNDEgMC0uNjA1LS4xMTctMS4xNS0uMzQ1LTEuNjM0LS4yMzEtLjQ4Mi0uNTQ2LS44OTEtLjkzOS0xLjIyNW0tMi4zNjggMy43NzRjLS4wNTYuMjc3LS4xMzYuNTE3LS4yNDYuNzE5LS4xMDkuMjAzLS4yNDYuMzYzLS40MDcuNDgxLS4xNjMuMTE1LS4zNTQuMTc2LS41NzIuMTc2LS4xMiAwLS4yMzYtLjAyNS0uMzQ0LS4wNzgtLjEwOC0uMDUyLS4yMDYtLjEzLS4yODktLjIzMi0uMDgxLS4xMDMtLjE0OC0uMjM0LS4xOTgtLjM5LS4wNDYtLjE1Ni0uMDctLjMzNy0uMDctLjU0NyAwLS4yMzcuMDI3LS40ODEuMDgtLjcyOS4wNTYtLjI0Ny4xMzctLjQ3My4yNS0uNjc3LjEwOS0uMi4yNS0uMzYzLjQxNi0uNDkyLjE2NS0uMTI3LjM2MS0uMTkxLjU4Mi0uMTkxLjEyMyAwIC4yMzQuMDIxLjM0LjA2My4xMDcuMDQyLjE5OC4xMDcuMjc5LjE5Ni4wOC4wODcuMTQ1LjE5Ny4xODkuMzMuMDQzLjEzNC4wNy4yOTQuMDcuNDggMCAuMzE3LS4wMzEuNjE1LS4wOC44OTEiIGZpbGw9IiM0MjQyNDIiLz48cGF0aCBkPSJNMjY0IDM3di0xNGg4LjYyNWwzLjM3NSAzLjU1NnYxMC40NDRoLTEyeiIgZmlsbD0iIzJEMkQyRCIvPjxwYXRoIGQ9Ik0yNzIgMjRoLTd2MTJoMTB2LTlsLTMtM3ptMiAxMWgtOHYtMTBoNXYzaDN2N3oiIGZpbGw9IiNDNUM1QzUiLz48cG9seWdvbiBwb2ludHM9IjI2NiwyNSAyNzEsMjUgMjcxLDI4IDI3NCwyOCAyNzQsMzUgMjY2LDM1IiBmaWxsPSIjMkQyRDJEIi8+PHBhdGggZD0iTTI2NCAxN3YtMTRoOC42MjVsMy4zNzUgMy41NTZ2MTAuNDQ0aC0xMnoiIGZpbGw9IiNGM0YzRjMiLz48cGF0aCBkPSJNMjcyIDRoLTd2MTJoMTB2LTlsLTMtM3ptMiAxMWgtOHYtMTBoNXYzaDN2N3oiIGZpbGw9IiM0MjQyNDIiLz48cG9seWdvbiBwb2ludHM9IjI2Niw1IDI3MSw1IDI3MSw4IDI3NCw4IDI3NCwxNSAyNjYsMTUiIGZpbGw9IiNGMEVGRjEiLz48cG9seWdvbiBwb2ludHM9IjI0NywzNCAyNDcsMzAgMjQ1LDMwIDI0NSwyNiAyNTUsMjYgMjU1LDM0IiBmaWxsPSIjMkQyRDJEIi8+PHBhdGggZD0iTTI1NCAyOWgtOHYtMmg4djJ6bTAgMWgtNnYxaDZ2LTF6bTAgMmgtNnYxaDZ2LTF6IiBmaWxsPSIjQzVDNUM1Ii8+PHBvbHlnb24gcG9pbnRzPSIyNDcsMTQgMjQ3LDEwIDI0NSwxMCAyNDUsNiAyNTUsNiAyNTUsMTQiIGZpbGw9IiNGM0YzRjMiLz48cGF0aCBkPSJNMjU0IDloLTh2LTJoOHYyem0wIDFoLTZ2MWg2di0xem0wIDJoLTZ2MWg2di0xeiIgZmlsbD0iIzQyNDI0MiIvPjxwYXRoIGQ9Ik0yMzAuNSAyMmMtNC4xNDMgMC03LjUgMy4zNTctNy41IDcuNXMzLjM1NyA3LjUgNy41IDcuNSA3LjUtMy4zNTcgNy41LTcuNS0zLjM1Ny03LjUtNy41LTcuNXptMCAxMWMtMS45MzMgMC0zLjUtMS41NjYtMy41LTMuNXMxLjU2Ny0zLjUgMy41LTMuNSAzLjUgMS41NjYgMy41IDMuNS0xLjU2NyAzLjUtMy41IDMuNXoiIGZpbGw9IiMyRDJEMkQiLz48cGF0aCBkPSJNMjI0LjAyNSAyOWMuMTA4LTEuNDE4LjY2OS0yLjcwOCAxLjU0Mi0zLjcyNmwxLjQzMSAxLjQzMWMtLjUxNi42NDYtLjg1MSAxLjQzLS45NDcgMi4yOTVoLTIuMDI2em0yLjk3MyAzLjI5NWMtLjUxNi0uNjQ2LS44NTEtMS40My0uOTQ3LTIuMjk1aC0yLjAyNWMuMTA4IDEuNDE4LjY2OSAyLjcwNyAxLjU0MiAzLjcyNmwxLjQzLTEuNDMxem00LjAwMi05LjI3djIuMDI1Yy44NjUuMDk3IDEuNjQ5LjQzMiAyLjI5NS45NDdsMS40MzEtMS40MzFjLTEuMDE4LS44NzItMi4zMDgtMS40MzItMy43MjYtMS41NDF6bS0zLjI5NSAyLjk3M2MuNjQ2LS41MTYgMS40My0uODUxIDIuMjk1LS45NDd2LTIuMDI1Yy0xLjQxOC4xMDgtMi43MDguNjY5LTMuNzI2IDEuNTQybDEuNDMxIDEuNDN6bTYuMjk3LjcwN2MuNTE2LjY0Ni44NTEgMS40My45NDcgMi4yOTVoMi4wMjVjLS4xMDgtMS40MTgtLjY2OS0yLjcwOC0xLjU0Mi0zLjcyNmwtMS40MyAxLjQzMXptLTQuMDAyIDcuMjQ0Yy0uODY1LS4wOTctMS42NDktLjQzMi0yLjI5NS0uOTQ3bC0xLjQzMSAxLjQzMWMxLjAxOC44NzMgMi4zMDcgMS40MzQgMy43MjYgMS41NDJ2LTIuMDI2em00Ljk0OS0zLjk0OWMtLjA5Ny44NjUtLjQzMiAxLjY0OC0uOTQ3IDIuMjk1bDEuNDMxIDEuNDMxYy44NzMtMS4wMTkgMS40MzQtMi4zMDggMS41NDItMy43MjZoLTIuMDI2em0tMS42NTQgMy4wMDJjLS42NDYuNTE2LTEuNDMuODUxLTIuMjk1Ljk0N3YyLjAyNWMxLjQxOS0uMTA4IDIuNzA4LS42NjkgMy43MjYtMS41NDJsLTEuNDMxLTEuNDN6IiBmaWxsPSIjQzVDNUM1Ii8+PHBhdGggZD0iTTIzMC41IDJjLTQuMTQzIDAtNy41IDMuMzU4LTcuNSA3LjUgMCA0LjE0MyAzLjM1NyA3LjUgNy41IDcuNXM3LjUtMy4zNTcgNy41LTcuNWMwLTQuMTQyLTMuMzU3LTcuNS03LjUtNy41em0wIDExYy0xLjkzMyAwLTMuNS0xLjU2Ni0zLjUtMy41IDAtMS45MzMgMS41NjctMy41IDMuNS0zLjVzMy41IDEuNTY3IDMuNSAzLjVjMCAxLjkzNC0xLjU2NyAzLjUtMy41IDMuNXoiIGZpbGw9IiNGM0YzRjMiLz48cGF0aCBkPSJNMjI0LjAyNSA5Yy4xMDgtMS40MTguNjY5LTIuNzA4IDEuNTQyLTMuNzI2bDEuNDMxIDEuNDMxYy0uNTE2LjY0Ni0uODUxIDEuNDMtLjk0NyAyLjI5NGgtMi4wMjZ6bTIuOTczIDMuMjk1Yy0uNTE2LS42NDYtLjg1MS0xLjQzLS45NDctMi4yOTVoLTIuMDI1Yy4xMDggMS40MTguNjY5IDIuNzA3IDEuNTQyIDMuNzI2bDEuNDMtMS40MzF6bTQuMDAyLTkuMjd2Mi4wMjVjLjg2NS4wOTcgMS42NDkuNDMyIDIuMjk1Ljk0OGwxLjQzMS0xLjQzMWMtMS4wMTgtLjg3My0yLjMwOC0xLjQzMy0zLjcyNi0xLjU0MnptLTMuMjk1IDIuOTc0Yy42NDYtLjUxNiAxLjQzLS44NTEgMi4yOTUtLjk0OHYtMi4wMjZjLTEuNDE4LjEwOC0yLjcwOC42NjktMy43MjYgMS41NDJsMS40MzEgMS40MzJ6bTYuMjk3LjcwN2MuNTE2LjY0Ni44NTEgMS40My45NDcgMi4yOTRoMi4wMjVjLS4xMDgtMS40MTgtLjY2OS0yLjcwOC0xLjU0Mi0zLjcyNmwtMS40MyAxLjQzMnptLTQuMDAyIDcuMjQzYy0uODY1LS4wOTctMS42NDktLjQzMi0yLjI5NS0uOTQ3bC0xLjQzMSAxLjQzMWMxLjAxOC44NzMgMi4zMDcgMS40MzQgMy43MjYgMS41NDJ2LTIuMDI2em00Ljk0OS0zLjk0OWMtLjA5Ny44NjUtLjQzMiAxLjY0OC0uOTQ3IDIuMjk1bDEuNDMxIDEuNDMxYy44NzMtMS4wMTkgMS40MzQtMi4zMDggMS41NDItMy43MjZoLTIuMDI2em0tMS42NTQgMy4wMDJjLS42NDYuNTE2LTEuNDMuODUxLTIuMjk1Ljk0N3YyLjAyNWMxLjQxOS0uMTA4IDIuNzA4LS42NjkgMy43MjYtMS41NDJsLTEuNDMxLTEuNDN6IiBmaWxsPSIjNDI0MjQyIi8+PHJlY3QgeD0iMjAyIiB5PSIyMyIgd2lkdGg9IjE2IiBoZWlnaHQ9IjE0IiBmaWxsPSIjMkQyRDJEIi8+PHBhdGggZD0iTTIwMyAyNHYxMmgxNHYtMTJoLTE0em0xMyAxMWgtMTJ2LTEwaDEydjEwem0tNi03di0xaC0xdjVoM3YtNGgtMnptMSAzaC0xdi0yaDF2MnptMy0ydjJoMXYxaC0ydi00aDJ2MWgtMXptLTYtMXY0aC0zdi0yaDF2MWgxdi0xaC0xdi0xaC0xdi0xaDN6IiBmaWxsPSIjQzVDNUM1Ii8+PHBhdGggZD0iTTIxMCAyOWgxdjJoLTF2LTJ6bS0zIDJ2LTFoLTF2MWgxem05LTZ2MTBoLTEydi0xMGgxMnptLTggM2gtM3YxaDF2MWgtMXYyaDN2LTR6bTQgMGgtMnYtMWgtMXY1aDN2LTR6bTMgMGgtMnY0aDJ2LTFoLTF2LTJoMXYtMXoiIGZpbGw9IiMyRDJEMkQiLz48cmVjdCB4PSIyMDIiIHk9IjMiIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNCIgZmlsbD0iI0YzRjNGMyIvPjxwYXRoIGQ9Ik0yMDMgNHYxMmgxNHYtMTJoLTE0em0xMyAxMWgtMTJ2LTEwaDEydjEwem0tNi03di0xaC0xdjVoM3YtNGgtMnptMSAzaC0xdi0yaDF2MnptMy0ydjJoMXYxaC0ydi00aDJ2MWgtMXptLTYtMXY0aC0zdi0yaDF2MWgxdi0xaC0xdi0xaC0xdi0xaDN6IiBmaWxsPSIjNDI0MjQyIi8+PHBhdGggZD0iTTIxMCA5aDF2MmgtMXYtMnptLTMgMnYtMWgtMXYxaDF6bTktNnYxMGgtMTJ2LTEwaDEyem0tOCAzaC0zdjFoMXYxaC0xdjJoM3YtNHptNCAwaC0ydi0xaC0xdjVoM3YtNHptMyAwaC0ydjRoMnYtMWgtMXYtMmgxdi0xeiIgZmlsbD0iI0YwRUZGMSIvPjxwYXRoIGQ9Ik0xOTYuNjUyIDMyLjVjLjgxMS0uNTM3IDEuMzQ4LTEuNDU3IDEuMzQ4LTIuNSAwLTEuNjU0LTEuMzQ2LTMtMy0zLS43NzEgMC0xLjQ2OC4zMDEtMiAuNzc5di01Ljc3OWgtMTF2MTJoMy43NjRsLTEuNDUyLjcyNyAxLjQ4MSAxLjQ4Yy4zMjIuMzIyLjgwMy41IDEuMzU0LjUuNDM2IDAgLjg5Ny0uMTExIDEuMzAxLS4zMTNsMy4xNDQtMS41NzJjLjEzNC4wNTMuMjcxLjA5OC40MTQuMTI3bC0uMDA1LjA1MWMwIDEuNjU0IDEuMzQ2IDMgMyAzczMtMS4zNDYgMy0zYy0uMDAxLTEuMDQzLS41MzgtMS45NjMtMS4zNDktMi41eiIgZmlsbD0iIzJEMkQyRCIvPjxwYXRoIGQ9Ik0xOTUgMzNjLS4yOTMgMC0uNTY5LjA2Ni0uODIuMThsLS4yNS0uMjVjLjA0Mi0uMTM3LjA3LS4yNzkuMDctLjQzcy0uMDI4LS4yOTMtLjA3LS40M2wuMjUtLjI1Yy4yNTEuMTEzLjUyNy4xOC44Mi4xOCAxLjEwNCAwIDItLjg5NiAyLTIgMC0xLjEwNS0uODk2LTItMi0ycy0yIC44OTUtMiAyYzAgLjI5My4wNjYuNTY4LjE4LjgybC0uMjUuMjVjLS4xMzctLjA0My0uMjc5LS4wNy0uNDMtLjA3LS4zMzcgMC0uNjQ1LjExNS0uODk1LjMwM2wtMi42MDctMS4zMDUtLjk5OS0uNWMtLjU1Mi0uMjc1LTEuMjIzLS4yNzUtMS40OTkuMDAybC0uNS41IDUgMi41LTUgMi41LjUuNWMuMjc2LjI3NS45NDcuMjc1IDEuNSAwbDEtLjUgMi42MDUtMS4zMDNjLjI1LjE4OC41NTguMzAzLjg5NS4zMDMuMTUgMCAuMjkzLS4wMjkuNDMtLjA3bC4yNS4yNWMtLjExNC4yNS0uMTguNTI3LS4xOC44MiAwIDEuMTA0Ljg5NiAyIDIgMnMyLS44OTYgMi0yYzAtMS4xMDUtLjg5Ni0yLTItMnptMC00Yy41NTMgMCAxIC40NDcgMSAxIDAgLjU1MS0uNDQ3IDEtMSAxcy0xLS40NDktMS0xYzAtLjU1My40NDctMSAxLTF6bS0yLjUgNGMtLjI3NiAwLS41LS4yMjUtLjUtLjUgMC0uMjc3LjIyNC0uNS41LS41cy41LjIyMy41LjVjMCAuMjc1LS4yMjQuNS0uNS41em0yLjUgM2MtLjU1MyAwLTEtLjQ0OS0xLTEgMC0uNTUzLjQ0Ny0xIDEtMXMxIC40NDcgMSAxYzAgLjU1MS0uNDQ3IDEtMSAxem0tMy0xM3Y3LjA1MWMtLjE0Mi4wMjktLjI3OS4wNy0uNDEzLjEyM2wtLjU4Ny0uMTc0di02aC03djdoLTF2LThoOXptLTggMTBoLTF2LTFoMXYxem0yLTFoLTF2MWgxdi0xem0yIDBoLTF2MWgxdi0xeiIgZmlsbD0iI0M1QzVDNSIvPjxwYXRoIGQ9Ik0xODUuNzkzIDI4Ljc5M2wtMS43OTMgMS4yMDd2LTZoN3Y1LjM4MWwtMi41NTQtLjc3N2MtLjgxNi0uNDA5LTEuOTktLjQ3NS0yLjY1My4xODl6bS0uNzkzIDIuMjA3aC43NjRsLS43NjQtLjM4M3YuMzgzem0xMSA0YzAgLjU1MS0uNDQ3IDEtMSAxcy0xLS40NDktMS0xYzAtLjU1My40NDctMSAxLTFzMSAuNDQ3IDEgMXptLTMuNS0zYy0uMjc2IDAtLjUuMjIzLS41LjUgMCAuMjc1LjIyNC41LjUuNXMuNS0uMjI1LjUtLjVjMC0uMjc3LS4yMjQtLjUtLjUtLjV6bTIuNS0zYy0uNTUzIDAtMSAuNDQ3LTEgMSAwIC41NTEuNDQ3IDEgMSAxczEtLjQ0OSAxLTFjMC0uNTUzLS40NDctMS0xLTF6IiBmaWxsPSIjMkQyRDJEIi8+PHBhdGggZD0iTTE5Ni42NTIgMTIuNWMuODExLS41MzggMS4zNDgtMS40NTggMS4zNDgtMi41IDAtMS42NTQtMS4zNDYtMy0zLTMtLjc3MSAwLTEuNDY4LjMwMS0yIC43Nzl2LTUuNzc5aC0xMXYxMmgzLjc2NGwtMS40NTIuNzI3IDEuNDgxIDEuNDhjLjMyMi4zMjIuODAzLjUgMS4zNTQuNS40MzYgMCAuODk3LS4xMTEgMS4zMDEtLjMxM2wzLjE0NC0xLjU3MmMuMTM0LjA1My4yNzEuMDk4LjQxNC4xMjdsLS4wMDUuMDUxYzAgMS42NTQgMS4zNDYgMyAzIDNzMy0xLjM0NiAzLTNjLS4wMDEtMS4wNDMtLjUzOC0xLjk2My0xLjM0OS0yLjV6IiBmaWxsPSIjRjNGM0YzIi8+PHBhdGggZD0iTTE5NSAxM2MtLjI5MyAwLS41NjkuMDY2LS44Mi4xOGwtLjI1LS4yNWMuMDQyLS4xMzcuMDctLjI3OS4wNy0uNDNzLS4wMjgtLjI5My0uMDctLjQzbC4yNS0uMjVjLjI1MS4xMTMuNTI3LjE4LjgyLjE4IDEuMTA0IDAgMi0uODk2IDItMiAwLTEuMTA1LS44OTYtMi0yLTJzLTIgLjg5NS0yIDJjMCAuMjkzLjA2Ni41NjguMTguODJsLS4yNS4yNWMtLjEzNy0uMDQzLS4yNzktLjA3LS40My0uMDctLjMzNyAwLS42NDUuMTE1LS44OTUuMzAzbC0yLjYwNy0xLjMwNC0uOTk5LS41Yy0uNTUyLS4yNzUtMS4yMjMtLjI3NS0xLjQ5OS4wMDJsLS41LjQ5OSA1IDIuNS01IDIuNS41LjVjLjI3Ni4yNzUuOTQ3LjI3NSAxLjUgMGwxLS41IDIuNjA1LTEuMzAzYy4yNS4xODguNTU4LjMwMy44OTUuMzAzLjE1IDAgLjI5My0uMDI5LjQzLS4wN2wuMjUuMjVjLS4xMTMuMjUtLjE4LjUyNy0uMTguODIgMCAxLjEwNC44OTYgMiAyIDJzMi0uODk2IDItMmMwLTEuMTA2LS44OTYtMi0yLTJ6bTAtNGMuNTUzIDAgMSAuNDQ3IDEgMSAwIC41NTEtLjQ0NyAxLTEgMXMtMS0uNDQ5LTEtMWMwLS41NTMuNDQ3LTEgMS0xem0tMi41IDRjLS4yNzYgMC0uNS0uMjI1LS41LS41IDAtLjI3Ny4yMjQtLjUuNS0uNXMuNS4yMjMuNS41YzAgLjI3NS0uMjI0LjUtLjUuNXptMi41IDNjLS41NTMgMC0xLS40NDktMS0xIDAtLjU1My40NDctMSAxLTFzMSAuNDQ3IDEgMWMwIC41NS0uNDQ3IDEtMSAxem0tMy0xM3Y3LjA1MWMtLjE0Mi4wMjktLjI3OS4wNy0uNDEzLjEyM2wtLjU4Ny0uMTc0di02aC03djdoLTF2LThoOXptLTggMTBoLTF2LTFoMXYxem0yLTFoLTF2MWgxdi0xem0yIDBoLTF2MWgxdi0xeiIgZmlsbD0iIzQyNDI0MiIvPjxwYXRoIGQ9Ik0xODUuNzkzIDguNzkzbC0xLjc5MyAxLjIwN3YtNmg3djUuMzgxbC0yLjU1NC0uNzc3Yy0uODE2LS40MDktMS45OS0uNDc1LTIuNjUzLjE4OXptLS43OTMgMi4yMDdoLjc2NGwtLjc2NC0uMzgzdi4zODN6bTExIDRjMCAuNTUxLS40NDcgMS0xIDFzLTEtLjQ0OS0xLTFjMC0uNTUzLjQ0Ny0xIDEtMXMxIC40NDcgMSAxem0tMy41LTNjLS4yNzYgMC0uNS4yMjMtLjUuNSAwIC4yNzUuMjI0LjUuNS41cy41LS4yMjUuNS0uNWMwLS4yNzgtLjIyNC0uNS0uNS0uNXptMi41LTNjLS41NTMgMC0xIC40NDctMSAxIDAgLjU1MS40NDcgMSAxIDFzMS0uNDQ5IDEtMWMwLS41NTMtLjQ0Ny0xLTEtMXoiIGZpbGw9IiNGMEVGRjEiLz48cGF0aCBkPSJNMTc4IDI3di0zaC03di0xaC05djE0aDEzdi0zaDN2LTNoLTF2LTNoLTZ2LTFoN3ptLTggN3YtM2gxdjNoLTF6IiBmaWxsPSIjMkQyRDJEIi8+PHBhdGggZD0iTTE3NyAyNmgtNXYtMWg1djF6bS0xIDNoLTJ2MWgydi0xem0tNCAwaC05djFoOXYtMXptMiA2aC0xMXYxaDExdi0xem0tNS0zaC02djFoNnYtMXptOCAwaC01djFoNXYtMXptLTctOHYzaC03di0zaDd6bS0xIDFoLTV2MWg1di0xeiIgZmlsbD0iI0M1QzVDNSIvPjxyZWN0IHg9IjE2NCIgeT0iMjUiIHdpZHRoPSI1IiBoZWlnaHQ9IjEiIGZpbGw9IiMyRDJEMkQiLz48cGF0aCBkPSJNMTc4IDd2LTNoLTd2LTFoLTl2MTRoMTN2LTNoM3YtM2gtMXYtM2gtNnYtMWg3em0tOCA3di0zaDF2M2gtMXoiIGZpbGw9IiNGM0YzRjMiLz48cGF0aCBkPSJNMTc3IDZoLTV2LTFoNXYxem0tMSAzaC0ydjFoMnYtMXptLTQgMGgtOXYxaDl2LTF6bTIgNmgtMTF2MWgxMXYtMXptLTUtM2gtNnYxaDZ2LTF6bTggMGgtNXYxaDV2LTF6bS03LTh2M2gtN3YtM2g3em0tMSAxaC01djFoNXYtMXoiIGZpbGw9IiM0MjQyNDIiLz48cmVjdCB4PSIxNjQiIHk9IjUiIHdpZHRoPSI1IiBoZWlnaHQ9IjEiIGZpbGw9IiNGMEVGRjEiLz48cG9seWdvbiBwb2ludHM9IjE1NC40MTQsMjQgMTQ5LjU4NiwyNCAxNDgsMjUuNTg2IDE0OCwyOCAxNDQsMjggMTQ0LDM1IDE1MiwzNSAxNTIsMzEgMTU0LjQxNCwzMSAxNTYsMjkuNDE0IDE1NiwyNS41ODYiIGZpbGw9IiMyRDJEMkQiLz48ZyBmaWxsPSIjNzVCRUZGIj48cGF0aCBkPSJNMTU0IDI1aC00bC0xIDF2Mmg1djFoLTJ2MWgybDEtMXYtM2wtMS0xem0wIDJoLTR2LTFoNHYxek0xNDUgMzRoNnYtNWgtNnY1em0xLTNoNHYxaC00di0xeiIvPjwvZz48ZyBmaWxsPSIjMkQyRDJEIj48cmVjdCB4PSIxNDYiIHk9IjMxIiB3aWR0aD0iNCIgaGVpZ2h0PSIxIi8+PHJlY3QgeD0iMTUwIiB5PSIyNiIgd2lkdGg9IjQiIGhlaWdodD0iMSIvPjxyZWN0IHg9IjE1MiIgeT0iMjgiIHdpZHRoPSIyIiBoZWlnaHQ9IjEiLz48L2c+PHBvbHlnb24gcG9pbnRzPSIxNTQuNDE0LDQgMTQ5LjU4Niw0IDE0OCw1LjU4NiAxNDgsOCAxNDQsOCAxNDQsMTUgMTUyLDE1IDE1MiwxMSAxNTQuNDE0LDExIDE1Niw5LjQxNCAxNTYsNS41ODYiIGZpbGw9IiNGM0YzRjMiLz48ZyBmaWxsPSIjMDA1MzlDIj48cGF0aCBkPSJNMTU0IDVoLTRsLTEgMXYyaDV2MWgtMnYxaDJsMS0xdi0zbC0xLTF6bTAgMmgtNHYtMWg0djF6TTE0NSAxNGg2di01aC02djV6bTEtM2g0djFoLTR2LTF6Ii8+PC9nPjxnIGZpbGw9IiNGMEVGRjEiPjxyZWN0IHg9IjE0NiIgeT0iMTEiIHdpZHRoPSI0IiBoZWlnaHQ9IjEiLz48cmVjdCB4PSIxNTAiIHk9IjYiIHdpZHRoPSI0IiBoZWlnaHQ9IjEiLz48cmVjdCB4PSIxNTIiIHk9IjgiIHdpZHRoPSIyIiBoZWlnaHQ9IjEiLz48L2c+PHBhdGggZD0iTTEzOCAyNGgtMTV2NGgtMXY4aDh2LTZoOHYtNnptLTExIDloLTJ2LTJoMnYyeiIgZmlsbD0iIzJEMkQyRCIvPjxwYXRoIGQ9Ik0xMzcgMjloLTd2LTFoLTZ2LTNoMXYyaDF2LTJoMXYyaDF2LTJoMXYyaDF2LTJoMXYyaDF2LTJoMXYyaDF2LTJoMXYyaDF2LTJoMXY0em0tMTIgMXYtMWgtMnY2aDJ2LTFoLTF2LTRoMXptMiA0djFoMnYtNmgtMnYxaDF2NGgtMXoiIGZpbGw9IiNDNUM1QzUiLz48cGF0aCBkPSJNMTI1IDI3di0yaDF2MmgtMXptMyAwdi0yaC0xdjJoMXptMiAwdi0yaC0xdjJoMXptMiAwdi0yaC0xdjJoMXptMiAwdi0yaC0xdjJoMXptMiAwdi0yaC0xdjJoMXoiIGZpbGw9IiMyRDJEMkQiLz48cGF0aCBkPSJNMTM4IDRoLTE1djRoLTF2OGg4di02aDh2LTZ6bS0xMSA5aC0ydi0yaDJ2MnoiIGZpbGw9IiNGM0YzRjMiLz48cGF0aCBkPSJNMTM3IDloLTd2LTFoLTZ2LTNoMXYyaDF2LTJoMXYyaDF2LTJoMXYyaDF2LTJoMXYyaDF2LTJoMXYyaDF2LTJoMXYyaDF2LTJoMXY0em0tMTIgMXYtMWgtMnY2aDJ2LTFoLTF2LTRoMXptMiA0djFoMnYtNmgtMnYxaDF2NGgtMXoiIGZpbGw9IiM0MjQyNDIiLz48cGF0aCBkPSJNMTI1IDd2LTJoMXYyaC0xem0zIDB2LTJoLTF2Mmgxem0yIDB2LTJoLTF2Mmgxem0yIDB2LTJoLTF2Mmgxem0yIDB2LTJoLTF2Mmgxem0yIDB2LTJoLTF2MmgxeiIgZmlsbD0iI0YwRUZGMSIvPjxwYXRoIGQ9Ik0xMTAuNDQ5IDIzYy0xLjYzNyAwLTMuMDc1Ljc5Ny0zLjk4NyAyLjAxMmwuMDAxLjAwMmMtLjYyOC44MzYtMS4wMTQgMS44NjMtMS4wMTQgMi45ODYgMCAuNDY5LjA2Ny45MzMuMiAxLjM4NWwtMi45MDcgMi45MDhjLS42ODcuNjg2LTEuMjUzIDIuMTYxIDAgMy40MTQuNjA5LjYwOSAxLjI0NC43MzYgMS42Ny43MzYuOTU4IDAgMS42MjEtLjYxMyAxLjc0NC0uNzM2bDIuOTA3LTIuOTA4Yy40NTMuMTMzLjkxNy4yMDEgMS4zODYuMjAxIDEuMTIzIDAgMi4xNDktLjM4NyAyLjk4NS0xLjAxNGwuMDAyLjAwMWMxLjIxNi0uOTEyIDIuMDEzLTIuMzUyIDIuMDEzLTMuOTg3IDAtMi43NjItMi4yMzgtNS01LTV6IiBmaWxsPSIjMkQyRDJEIi8+PHBhdGggZD0iTTExNC4wOSAyNi4zNTlsLTIuNjQxIDIuNjQxLTItMiAyLjY0MS0yLjY0MWMtLjUwMi0uMjI3LTEuMDU1LS4zNTktMS42NDEtLjM1OS0yLjIwOSAwLTQgMS43OTEtNCA0IDAgLjU4Ni4xMzMgMS4xMzkuMzU5IDEuNjRsLTMuMzU5IDMuMzZzLTEgMSAwIDJoMmwzLjM1OS0zLjM2Yy41MDIuMjI3IDEuMDU1LjM2IDEuNjQxLjM2IDIuMjA5IDAgNC0xLjc5MSA0LTQgMC0uNTg2LS4xMzMtMS4xMzktLjM1OS0xLjY0MXoiIGZpbGw9IiNDNUM1QzUiLz48cGF0aCBkPSJNMTEwLjQ0OSAzYy0xLjYzNyAwLTMuMDc1Ljc5Ny0zLjk4NyAyLjAxMmwuMDAxLjAwMmMtLjYyOC44MzYtMS4wMTQgMS44NjMtMS4wMTQgMi45ODYgMCAuNDY5LjA2Ny45MzMuMiAxLjM4NWwtMi45MDcgMi45MDhjLS42ODcuNjg2LTEuMjUzIDIuMTYxIDAgMy40MTQuNjA5LjYwOSAxLjI0NC43MzYgMS42Ny43MzYuOTU4IDAgMS42MjEtLjYxMyAxLjc0NC0uNzM2bDIuOTA3LTIuOTA4Yy40NTMuMTMzLjkxNy4yMDEgMS4zODYuMjAxIDEuMTIzIDAgMi4xNDktLjM4NyAyLjk4NS0xLjAxNGwuMDAyLjAwMWMxLjIxNi0uOTEyIDIuMDEzLTIuMzUyIDIuMDEzLTMuOTg3IDAtMi43NjItMi4yMzgtNS01LTV6IiBmaWxsPSIjRjNGM0YzIi8+PHBhdGggZD0iTTExNC4wOSA2LjM1OWwtMi42NDEgMi42NDEtMi0yIDIuNjQxLTIuNjQxYy0uNTAyLS4yMjYtMS4wNTUtLjM1OS0xLjY0MS0uMzU5LTIuMjA5IDAtNCAxLjc5MS00IDQgMCAuNTg2LjEzMyAxLjEzOS4zNTkgMS42NGwtMy4zNTkgMy4zNnMtMSAxIDAgMmgybDMuMzU5LTMuMzZjLjUwMi4yMjcgMS4wNTUuMzYgMS42NDEuMzYgMi4yMDkgMCA0LTEuNzkxIDQtNCAwLS41ODYtLjEzMy0xLjEzOS0uMzU5LTEuNjQxeiIgZmlsbD0iIzQyNDI0MiIvPjxwYXRoIGQ9Ik04OSAzM2gxdi0xYzAtLjUzNy43NDEtMS42MTMgMS0yLS4yNTktLjM4OS0xLTEuNDY3LTEtMnYtMWgtMXYtM2gxYzEuOTY5LjAyMSAzIDEuMjc3IDMgM3YxbDEgMXYybC0xIDF2MWMwIDEuNzA5LTEuMDMxIDIuOTc5LTMgM2gtMXYtM3ptLTIgMGgtMXYtMWMwLS41MzctLjc0MS0xLjYxMy0xLTIgLjI1OS0uMzg5IDEtMS40NjcgMS0ydi0xaDF2LTNoLTFjLTEuOTY5LjAyMS0zIDEuMjc3LTMgM3YxbC0xIDF2MmwxIDF2MWMwIDEuNzA5IDEuMzE3IDIuOTc5IDMuMjg2IDNoLjcxNHYtM3oiIGZpbGw9IiMyRDJEMkQiLz48cGF0aCBkPSJNOTEgMzN2LTFjMC0uODM0LjQ5Ni0xLjczOCAxLTItLjUwNC0uMjctMS0xLjE2OC0xLTJ2LTFjMC0uODQtLjU4NC0xLTEtMXYtMWMyLjA4MyAwIDIgMS4xNjYgMiAydjFjMCAuOTY5LjcwMy45OCAxIDF2MmMtLjMyMi4wMi0xIC4wNTMtMSAxdjFjMCAuODM0LjA4MyAyLTIgMnYtMWMuODMzIDAgMS0xIDEtMXptLTYgMHYtMWMwLS44MzQtLjQ5Ni0xLjczOC0xLTIgLjUwNC0uMjcgMS0xLjE2OCAxLTJ2LTFjMC0uODQuNTg0LTEgMS0xdi0xYy0yLjA4MyAwLTIgMS4xNjYtMiAydjFjMCAuOTY5LS43MDMuOTgtMSAxdjJjLjMyMi4wMiAxIC4wNTMgMSAxdjFjMCAuODM0LS4wODMgMiAyIDJ2LTFjLS44MzMgMC0xLTEtMS0xeiIgZmlsbD0iI0M1QzVDNSIvPjxwYXRoIGQ9Ik04OSAxM2gxdi0xYzAtLjUzNy43NDEtMS42MTMgMS0yLS4yNTktLjM4OS0xLTEuNDY3LTEtMnYtMWgtMXYtM2gxYzEuOTY5LjAyMSAzIDEuMjc3IDMgM3YxbDEgMXYybC0xIDF2MWMwIDEuNzA5LTEuMDMxIDIuOTc5LTMgM2gtMXYtM3ptLTIgMGgtMXYtMWMwLS41MzctLjc0MS0xLjYxMy0xLTIgLjI1OS0uMzg5IDEtMS40NjcgMS0ydi0xaDF2LTNoLTFjLTEuOTY5LjAyMS0zIDEuMjc3LTMgM3YxbC0xIDF2MmwxIDF2MWMwIDEuNzA5IDEuMzE3IDIuOTc5IDMuMjg2IDNoLjcxNHYtM3oiIGZpbGw9IiNGM0YzRjMiLz48cGF0aCBkPSJNOTEgMTN2LTFjMC0uODM0LjQ5Ni0xLjczOCAxLTItLjUwNC0uMjctMS0xLjE2OC0xLTJ2LTFjMC0uODQtLjU4NC0xLTEtMXYtMWMyLjA4MyAwIDIgMS4xNjYgMiAydjFjMCAuOTY5LjcwMy45OCAxIDF2MmMtLjMyMi4wMi0xIC4wNTMtMSAxdjFjMCAuODM0LjA4MyAyLTIgMnYtMWMuODMzIDAgMS0xIDEtMXptLTYgMHYtMWMwLS44MzQtLjQ5Ni0xLjczOC0xLTIgLjUwNC0uMjcgMS0xLjE2OCAxLTJ2LTFjMC0uODQuNTg0LTEgMS0xdi0xYy0yLjA4MyAwLTIgMS4xNjYtMiAydjFjMCAuOTY5LS43MDMuOTgtMSAxdjJjLjMyMi4wMiAxIC4wNTMgMSAxdjFjMCAuODM0LS4wODMgMiAyIDJ2LTFjLS44MzMgMC0xLTEtMS0xeiIgZmlsbD0iIzQyNDI0MiIvPjxwYXRoIGQ9Ik03My41IDM0Yy0xLjkxNCAwLTMuNjAxLTEuMjQyLTQuMjI3LTNoLTEuNjgzYy0uNTI0LjkxLTEuNTAzIDEuNS0yLjU5MSAxLjUtMS42NTQgMC0zLTEuMzQ2LTMtM3MxLjM0Ni0zIDMtM2MxLjA4OCAwIDIuMDY2LjU4OCAyLjU5MSAxLjVoMS42ODNjLjYyNi0xLjc2IDIuMzEzLTMgNC4yMjctMyAyLjQ4MSAwIDQuNSAyLjAxOCA0LjUgNC41IDAgMi40OC0yLjAxOSA0LjUtNC41IDQuNXoiIGZpbGw9IiMyRDJEMkQiLz48cGF0aCBkPSJNNzMuNSAyNmMtMS43NTkgMC0zLjIwNCAxLjMwOC0zLjQ0OSAzaC0zLjEyMmMtLjIyMy0uODYxLS45OTgtMS41LTEuOTI5LTEuNS0xLjEwNCAwLTIgLjg5NS0yIDIgMCAxLjEwNC44OTYgMiAyIDIgLjkzMSAwIDEuNzA2LS42MzkgMS45MjktMS41aDMuMTIyYy4yNDUgMS42OTEgMS42OSAzIDMuNDQ5IDMgMS45MyAwIDMuNS0xLjU3IDMuNS0zLjUgMC0xLjkzMS0xLjU3LTMuNS0zLjUtMy41em0wIDVjLS44MjcgMC0xLjUtLjY3NC0xLjUtMS41IDAtLjgyOC42NzMtMS41IDEuNS0xLjVzMS41LjY3MiAxLjUgMS41YzAgLjgyNi0uNjczIDEuNS0xLjUgMS41eiIgZmlsbD0iIzc1QkVGRiIvPjxjaXJjbGUgY3g9IjczLjUiIGN5PSIyOS41IiByPSIxLjUiIGZpbGw9IiMyRDJEMkQiLz48cGF0aCBkPSJNNzMuNSAxNGMtMS45MTQgMC0zLjYwMS0xLjI0Mi00LjIyNy0zaC0xLjY4M2MtLjUyNC45MS0xLjUwMyAxLjUtMi41OTEgMS41LTEuNjU0IDAtMy0xLjM0Ni0zLTNzMS4zNDYtMyAzLTNjMS4wODggMCAyLjA2Ni41ODggMi41OTEgMS41aDEuNjgzYy42MjYtMS43NiAyLjMxMy0zIDQuMjI3LTMgMi40ODEgMCA0LjUgMi4wMTggNC41IDQuNSAwIDIuNDgtMi4wMTkgNC41LTQuNSA0LjV6IiBmaWxsPSIjRjNGM0YzIi8+PHBhdGggZD0iTTczLjUgNmMtMS43NTkgMC0zLjIwNCAxLjMwOC0zLjQ0OSAzaC0zLjEyMmMtLjIyMy0uODYxLS45OTgtMS41LTEuOTI5LTEuNS0xLjEwNCAwLTIgLjg5NS0yIDIgMCAxLjEwNC44OTYgMiAyIDIgLjkzMSAwIDEuNzA2LS42MzkgMS45MjktMS41aDMuMTIyYy4yNDUgMS42OTEgMS42OSAzIDMuNDQ5IDMgMS45MyAwIDMuNS0xLjU3IDMuNS0zLjUgMC0xLjkzMS0xLjU3LTMuNS0zLjUtMy41em0wIDVjLS44MjcgMC0xLjUtLjY3NC0xLjUtMS41IDAtLjgyOC42NzMtMS41IDEuNS0xLjVzMS41LjY3MiAxLjUgMS41YzAgLjgyNi0uNjczIDEuNS0xLjUgMS41eiIgZmlsbD0iIzAwNTM5QyIvPjxjaXJjbGUgY3g9IjczLjUiIGN5PSI5LjUiIHI9IjEuNSIgZmlsbD0iI0YwRUZGMSIvPjxwYXRoIGQ9Ik01OCAyOC41ODZsLTMtMy0xLjQxNCAxLjQxNGgtMi4xNzJsMS0xLTQtNGgtLjgyOGwtNS41ODYgNS41ODZ2LjgyOGw0IDQgMi40MTQtMi40MTRoLjU4NnY1aDEuNTg2bDMgM2guODI4bDMuNTg2LTMuNTg2di0uODI4bC0yLjA4Ni0yLjA4NiAyLjA4Ni0yLjA4NnYtLjgyOHoiIGZpbGw9IiMyRDJEMkQiLz48cG9seWdvbiBwb2ludHM9IjUzLjk5OCwzMy4wMDIgNTEsMzMgNTEsMjkgNTMsMjkgNTIsMzAgNTQsMzIgNTcsMjkgNTUsMjcgNTQsMjggNDksMjggNTEsMjYgNDgsMjMgNDMsMjggNDYsMzEgNDgsMjkgNTAsMjkgNTAsMzQgNTMsMzQgNTIsMzUgNTQsMzcgNTcsMzQgNTUsMzIiIGZpbGw9IiNDMjdEMUEiLz48cGF0aCBkPSJNNTggOC41ODZsLTMtMy0xLjQxNCAxLjQxNGgtMi4xNzJsMS0xLTQtNGgtLjgyOGwtNS41ODYgNS41ODZ2LjgyOGw0IDQgMi40MTQtMi40MTRoLjU4NnY1aDEuNTg2bDMgM2guODI4bDMuNTg2LTMuNTg2di0uODI4bC0yLjA4Ni0yLjA4NiAyLjA4Ni0yLjA4NnYtLjgyOHoiIGZpbGw9IiNGM0YzRjMiLz48cG9seWdvbiBwb2ludHM9IjUzLjk5OCwxMy4wMDIgNTEsMTMgNTEsOSA1Myw5IDUyLDEwIDU0LDEyIDU3LDkgNTUsNyA1NCw4IDQ5LDggNTEsNiA0OCwzIDQzLDggNDYsMTEgNDgsOSA1MCw5IDUwLDE0IDUzLDE0IDUyLDE1IDU0LDE3IDU3LDE0IDU1LDEyIiBmaWxsPSIjQzI3RDFBIi8+PHBhdGggZD0iTTI5LjI2MyAyNGw0LjczNyAyLjM2OXY1LjIzNmwtNi43OTEgMy4zOTVoLS40MmwtNC43ODktMi4zOTV2LTUuMjM2bDYuNzM5LTMuMzY5aC41MjR6IiBmaWxsPSIjMkQyRDJEIi8+PHBhdGggZD0iTTIzIDI4djRsNCAyIDYtM3YtNGwtNC0yLTYgM3ptNCAxbC0yLTEgNC0yIDIgMS00IDJ6IiBmaWxsPSIjNzVCRUZGIi8+PHBhdGggZD0iTTI5IDI2bDIgMS00IDItMi0xIDQtMnoiIGZpbGw9IiMyRDJEMkQiLz48cGF0aCBkPSJNMjkuMjYzIDRsNC43MzcgMi4zNjl2NS4yMzZsLTYuNzkxIDMuMzk1aC0uNDJsLTQuNzg5LTIuMzk1di01LjIzNmw2LjczOS0zLjM2OWguNTI0eiIgZmlsbD0iI0YzRjNGMyIvPjxwYXRoIGQ9Ik0yMyA4djRsNCAyIDYtM3YtNGwtNC0yLTYgM3ptNCAxbC0yLTEgNC0yIDIgMS00IDJ6IiBmaWxsPSIjMDA1MzlDIi8+PHBhdGggZD0iTTI5IDZsMiAxLTQgMi0yLTEgNC0yeiIgZmlsbD0iI0YwRUZGMSIvPjxwb2x5Z29uIHBvaW50cz0iMiwyNy4zMDggMiwzMi42OTIgNy4yMDksMzYgNy43OTEsMzYgMTMsMzIuNjkyIDEzLDI3LjMwOCA3Ljc5MSwyNCA3LjIwOSwyNCIgZmlsbD0iIzJEMkQyRCIvPjxwYXRoIGQ9Ik03LjUgMjVsLTQuNSAyLjg1N3Y0LjI4NWw0LjUgMi44NTggNC41LTIuODU3di00LjI4NWwtNC41LTIuODU4em0tLjUgOC40OThsLTMtMS45MDV2LTIuODE1bDMgMS45MDV2Mi44MTV6bS0yLjM1OC01LjQ5OGwyLjg1OC0xLjgxNSAyLjg1OCAxLjgxNS0yLjg1OCAxLjgxNS0yLjg1OC0xLjgxNXptNi4zNTggMy41OTNsLTMgMS45MDV2LTIuODE1bDMtMS45MDV2Mi44MTV6IiBmaWxsPSIjQjE4MEQ3Ii8+PHBvbHlnb24gcG9pbnRzPSIxMC4zNTgsMjggNy41LDI5LjgxNSA0LjY0MiwyOCA3LjUsMjYuMTg1IiBmaWxsPSIjMkQyRDJEIi8+PHBvbHlnb24gcG9pbnRzPSI0LDI4Ljc3NyA3LDMwLjY4MyA3LDMzLjQ5OCA0LDMxLjU5MyIgZmlsbD0iIzJEMkQyRCIvPjxwb2x5Z29uIHBvaW50cz0iOCwzMy40OTggOCwzMC42ODMgMTEsMjguNzc3IDExLDMxLjU5MyIgZmlsbD0iIzJEMkQyRCIvPjxwb2x5Z29uIHBvaW50cz0iMiw3LjMwOCAyLDEyLjY5MiA3LjIwOSwxNiA3Ljc5MSwxNiAxMywxMi42OTIgMTMsNy4zMDggNy43OTEsNCA3LjIwOSw0IiBmaWxsPSIjRjNGM0YzIi8+PHBhdGggZD0iTTcuNSA1bC00LjUgMi44NTd2NC4yODVsNC41IDIuODU4IDQuNS0yLjg1N3YtNC4yODZsLTQuNS0yLjg1N3ptLS41IDguNDk4bC0zLTEuOTA1di0yLjgxNmwzIDEuOTA1djIuODE2em0tMi4zNTgtNS40OThsMi44NTgtMS44MTUgMi44NTggMS44MTUtMi44NTggMS44MTUtMi44NTgtMS44MTV6bTYuMzU4IDMuNTkzbC0zIDEuOTA1di0yLjgxNWwzLTEuOTA1djIuODE1eiIgZmlsbD0iIzY1MkQ5MCIvPjxwb2x5Z29uIHBvaW50cz0iMTAuMzU4LDggNy41LDkuODE1IDQuNjQyLDggNy41LDYuMTg1IiBmaWxsPSIjRjBFRkYxIi8+PHBvbHlnb24gcG9pbnRzPSI0LDguNzc3IDcsMTAuNjgzIDcsMTMuNDk4IDQsMTEuNTkzIiBmaWxsPSIjRjBFRkYxIi8+PHBvbHlnb24gcG9pbnRzPSI4LDEzLjQ5OCA4LDEwLjY4MyAxMSw4Ljc3NyAxMSwxMS41OTMiIGZpbGw9IiNGMEVGRjEiLz48L3N2Zz4=);background-repeat:no-repeat}.monaco-quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon.constructor,.monaco-quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon.function,.monaco-quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon.method{background-position:0 -4px}.monaco-quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon.field,.monaco-quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon.variable{background-position:-22px -4px}.monaco-quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon.class{background-position:-43px -3px}.monaco-quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon.interface{background-position:-63px -4px}.monaco-quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon.module{background-position:-82px -4px}.monaco-quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon.property{background-position:-102px -3px}.monaco-quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon.enum{background-position:-122px -3px}.monaco-quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon.rule{background-position:-242px -4px}.monaco-quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon.file{background-position:-262px -4px}.vs-dark .monaco-quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon.constructor,.vs-dark .monaco-quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon.function,.vs-dark .monaco-quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon.method{background-position:0 -24px}.vs-dark .monaco-quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon.field,.vs-dark .monaco-quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon.variable{background-position:-22px -24px}.vs-dark .monaco-quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon.class{background-position:-43px -23px}.vs-dark .monaco-quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon.interface{background-position:-63px -24px}.vs-dark .monaco-quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon.module{background-position:-82px -24px}.vs-dark .monaco-quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon.property{background-position:-102px -23px}.vs-dark .monaco-quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon.enum{background-position:-122px -23px}.vs-dark .monaco-quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon.rule{background-position:-242px -24px}.vs-dark .monaco-quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon.file{background-position:-262px -24px}.hc-black .monaco-quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon{background:none;display:inline}.hc-black .monaco-quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon:before{height:16px;width:16px;display:inline-block}.hc-black .monaco-quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon.constructor:before,.hc-black .monaco-quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon.function:before,.hc-black .monaco-quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon.method:before{content:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiI+PHBhdGggZmlsbD0iI0IxODBENyIgZD0iTTUuNSAzbC00LjUgMi44NTd2NC4yODVsNC41IDIuODU4IDQuNS0yLjg1N3YtNC4yODZsLTQuNS0yLjg1N3ptLS41IDguNDk4bC0zLTEuOTA1di0yLjgxNmwzIDEuOTA1djIuODE2em0tMi4zNTgtNS40OThsMi44NTgtMS44MTUgMi44NTggMS44MTUtMi44NTggMS44MTUtMi44NTgtMS44MTV6bTYuMzU4IDMuNTkzbC0zIDEuOTA1di0yLjgxNWwzLTEuOTA1djIuODE1eiIvPjwvc3ZnPg==);margin-left:2px}.hc-black .monaco-quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon.field:before,.hc-black .monaco-quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon.variable:before{content:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiI+PHBhdGggZmlsbD0iIzc1QkVGRiIgZD0iTTEgNnY0bDQgMiA2LTN2LTRsLTQtMi02IDN6bTQgMWwtMi0xIDQtMiAyIDEtNCAyeiIvPjwvc3ZnPg==);margin-left:2px}.hc-black .monaco-quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon.class:before{content:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiI+PHBvbHlnb24gZmlsbD0iI0U4QUI1MyIgcG9pbnRzPSIxMS45OTgsMTEuMDAyIDksMTEgOSw3IDExLDcgMTAsOCAxMiwxMCAxNSw3IDEzLDUgMTIsNiA3LDYgOSw0IDYsMSAxLDYgNCw5IDYsNyA4LDcgOCwxMiAxMSwxMiAxMCwxMyAxMiwxNSAxNSwxMiAxMywxMCIvPjwvc3ZnPg==)}.hc-black .monaco-quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon.interface:before{content:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiI+PHBhdGggZmlsbD0iIzc1QkVGRiIgZD0iTTExLjUgNGMtMS43NTkgMC0zLjIwNCAxLjMwOC0zLjQ0OSAzaC0zLjEyMmMtLjIyMy0uODYxLS45OTgtMS41LTEuOTI5LTEuNS0xLjEwNCAwLTIgLjg5NS0yIDIgMCAxLjEwNC44OTYgMiAyIDIgLjkzMSAwIDEuNzA2LS42MzkgMS45MjktMS41aDMuMTIyYy4yNDUgMS42OTEgMS42OSAzIDMuNDQ5IDMgMS45MyAwIDMuNS0xLjU3IDMuNS0zLjUgMC0xLjkzMS0xLjU3LTMuNS0zLjUtMy41em0wIDVjLS44MjcgMC0xLjUtLjY3NC0xLjUtMS41IDAtLjgyOC42NzMtMS41IDEuNS0xLjVzMS41LjY3MiAxLjUgMS41YzAgLjgyNi0uNjczIDEuNS0xLjUgMS41eiIvPjwvc3ZnPg==)}.hc-black .monaco-quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon.module:before{content:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiI+PHBhdGggZmlsbD0iI0M1QzVDNSIgZD0iTTkgMTF2LTFjMC0uODM0LjQ5Ni0xLjczOCAxLTItLjUwNC0uMjctMS0xLjE2OC0xLTJ2LTFjMC0uODQtLjU4NC0xLTEtMXYtMWMyLjA4MyAwIDIgMS4xNjYgMiAydjFjMCAuOTY5LjcwMy45OCAxIDF2MmMtLjMyMi4wMi0xIC4wNTMtMSAxdjFjMCAuODM0LjA4MyAyLTIgMnYtMWMuODMzIDAgMS0xIDEtMXptLTYgMHYtMWMwLS44MzQtLjQ5Ni0xLjczOC0xLTIgLjUwNC0uMjcgMS0xLjE2OCAxLTJ2LTFjMC0uODQuNTg0LTEgMS0xdi0xYy0yLjA4MyAwLTIgMS4xNjYtMiAydjFjMCAuOTY5LS43MDMuOTgtMSAxdjJjLjMyMi4wMiAxIC4wNTMgMSAxdjFjMCAuODM0LS4wODMgMiAyIDJ2LTFjLS44MzMgMC0xLTEtMS0xeiIvPjwvc3ZnPg==);margin-left:2px}.hc-black .monaco-quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon.property:before{content:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiI+PHBhdGggZmlsbD0iI0M1QzVDNSIgZD0iTTEyLjA5IDQuMzU5bC0yLjY0MSAyLjY0MS0yLTIgMi42NDEtMi42NDFjLS41MDItLjIyNi0xLjA1NS0uMzU5LTEuNjQxLS4zNTktMi4yMDkgMC00IDEuNzkxLTQgNCAwIC41ODYuMTMzIDEuMTM5LjM1OSAxLjY0bC0zLjM1OSAzLjM2cy0xIDEgMCAyaDJsMy4zNTktMy4zNmMuNTAzLjIyNiAxLjA1NS4zNiAxLjY0MS4zNiAyLjIwOSAwIDQtMS43OTEgNC00IDAtLjU4Ni0uMTMzLTEuMTM5LS4zNTktMS42NDF6Ii8+PC9zdmc+);margin-left:1px}.hc-black .monaco-quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon.enum:before,.hc-black .monaco-quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon.value:before{content:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiI+PGcgZmlsbD0iIzc1QkVGRiI+PHBhdGggZD0iTTEyIDNoLTRsLTEgMXYyaDV2MWgtMnYxaDJsMS0xdi0zbC0xLTF6bTAgMmgtNHYtMWg0djF6TTMgMTJoNnYtNWgtNnY1em0xLTNoNHYxaC00di0xeiIvPjwvZz48L3N2Zz4=)}.hc-black .monaco-quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon.rule:before{content:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMiIgaGVpZ2h0PSIxMiI+PHBhdGggZmlsbD0iI0M1QzVDNSIgZD0iTTEwIDVoLTh2LTJoOHYyem0wIDFoLTZ2MWg2di0xem0wIDJoLTZ2MWg2di0xeiIvPjwvc3ZnPg==)}.hc-black .monaco-quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon.file:before{content:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiI+PHBhdGggZmlsbD0iI0M1QzVDNSIgZD0iTTkuNjc2IDJoLTYuNjc2djEyaDEwdi05bC0zLjMyNC0zem0yLjMyNCAxMWgtOHYtMTBoNXYzaDN2N3oiLz48L3N2Zz4=)}.monaco-editor{font-family:-apple-system,BlinkMacSystemFont,Segoe WPC,Segoe UI,HelveticaNeue-Light,Ubuntu,Droid Sans,sans-serif}.monaco-editor.hc-black .monaco-menu .monaco-action-bar.vertical .action-menu-item:focus .action-label,.monaco-editor.vs-dark .monaco-menu .monaco-action-bar.vertical .action-menu-item:focus .action-label,.monaco-menu .monaco-action-bar.vertical .action-item .action-menu-item:focus .action-label{stroke-width:1.2px}.monaco-editor-hover p{margin:0}.monaco-editor.hc-black{-ms-high-contrast-adjust:none}@media screen and (-ms-high-contrast:active){.monaco-editor.vs-dark .view-overlays .current-line,.monaco-editor.vs .view-overlays .current-line{border-color:windowtext!important;border-left:0;border-right:0}.monaco-editor.vs-dark .cursor,.monaco-editor.vs .cursor{background-color:windowtext!important}.monaco-editor.vs-dark .dnd-target,.monaco-editor.vs .dnd-target{border-color:windowtext!important}.monaco-editor.vs-dark .selected-text,.monaco-editor.vs .selected-text{background-color:highlight!important}.monaco-editor.vs-dark .view-line,.monaco-editor.vs .view-line{-ms-high-contrast-adjust:none}.monaco-editor.vs-dark .view-line span,.monaco-editor.vs .view-line span{color:windowtext!important}.monaco-editor.vs-dark .view-line span.inline-selected-text,.monaco-editor.vs .view-line span.inline-selected-text{color:highlighttext!important}.monaco-editor.vs-dark .view-overlays,.monaco-editor.vs .view-overlays{-ms-high-contrast-adjust:none}.monaco-editor.vs-dark .reference-decoration,.monaco-editor.vs-dark .selectionHighlight,.monaco-editor.vs-dark .wordHighlight,.monaco-editor.vs-dark .wordHighlightStrong,.monaco-editor.vs .reference-decoration,.monaco-editor.vs .selectionHighlight,.monaco-editor.vs .wordHighlight,.monaco-editor.vs .wordHighlightStrong{border:2px dotted highlight!important;background:transparent!important;box-sizing:border-box}.monaco-editor.vs-dark .rangeHighlight,.monaco-editor.vs .rangeHighlight{background:transparent!important;border:1px dotted activeborder!important;box-sizing:border-box}.monaco-editor.vs-dark .bracket-match,.monaco-editor.vs .bracket-match{border-color:windowtext!important;background:transparent!important}.monaco-editor.vs-dark .currentFindMatch,.monaco-editor.vs-dark .findMatch,.monaco-editor.vs .currentFindMatch,.monaco-editor.vs .findMatch{border:2px dotted activeborder!important;background:transparent!important;box-sizing:border-box}.monaco-editor.vs-dark .find-widget,.monaco-editor.vs .find-widget{border:1px solid windowtext}.monaco-editor.vs-dark .monaco-list .monaco-list-row,.monaco-editor.vs .monaco-list .monaco-list-row{-ms-high-contrast-adjust:none;color:windowtext!important}.monaco-editor.vs-dark .monaco-list .monaco-list-row.focused,.monaco-editor.vs .monaco-list .monaco-list-row.focused{color:highlighttext!important;background-color:highlight!important}.monaco-editor.vs-dark .monaco-list .monaco-list-row:hover,.monaco-editor.vs .monaco-list .monaco-list-row:hover{background:transparent!important;border:1px solid highlight;box-sizing:border-box}.monaco-editor.vs-dark .monaco-tree .monaco-tree-row,.monaco-editor.vs .monaco-tree .monaco-tree-row{-ms-high-contrast-adjust:none;color:windowtext!important}.monaco-editor.vs-dark .monaco-tree .monaco-tree-row.focused,.monaco-editor.vs-dark .monaco-tree .monaco-tree-row.selected,.monaco-editor.vs .monaco-tree .monaco-tree-row.focused,.monaco-editor.vs .monaco-tree .monaco-tree-row.selected{color:highlighttext!important;background-color:highlight!important}.monaco-editor.vs-dark .monaco-tree .monaco-tree-row:hover,.monaco-editor.vs .monaco-tree .monaco-tree-row:hover{background:transparent!important;border:1px solid highlight;box-sizing:border-box}.monaco-editor.vs-dark .monaco-scrollable-element>.scrollbar,.monaco-editor.vs .monaco-scrollable-element>.scrollbar{-ms-high-contrast-adjust:none;background:background!important;border:1px solid windowtext;box-sizing:border-box}.monaco-editor.vs-dark .monaco-scrollable-element>.scrollbar>.slider,.monaco-editor.vs .monaco-scrollable-element>.scrollbar>.slider{background:windowtext!important}.monaco-editor.vs-dark .monaco-scrollable-element>.scrollbar>.slider.active,.monaco-editor.vs-dark .monaco-scrollable-element>.scrollbar>.slider:hover,.monaco-editor.vs .monaco-scrollable-element>.scrollbar>.slider.active,.monaco-editor.vs .monaco-scrollable-element>.scrollbar>.slider:hover{background:highlight!important}.monaco-editor.vs-dark .decorationsOverviewRuler,.monaco-editor.vs .decorationsOverviewRuler{opacity:0}.monaco-editor.vs-dark .minimap,.monaco-editor.vs .minimap{display:none}.monaco-editor.vs-dark .squiggly-d-error,.monaco-editor.vs .squiggly-d-error{background:transparent!important;border-bottom:4px double #e47777}.monaco-editor.vs-dark .squiggly-b-info,.monaco-editor.vs-dark .squiggly-c-warning,.monaco-editor.vs .squiggly-b-info,.monaco-editor.vs .squiggly-c-warning{border-bottom:4px double #71b771}.monaco-editor.vs-dark .squiggly-a-hint,.monaco-editor.vs .squiggly-a-hint{border-bottom:4px double #6c6c6c}.monaco-editor.vs-dark .monaco-menu .monaco-action-bar.vertical .action-menu-item:focus .action-label,.monaco-editor.vs .monaco-menu .monaco-action-bar.vertical .action-menu-item:focus .action-label{-ms-high-contrast-adjust:none;color:highlighttext!important;background-color:highlight!important}.monaco-editor.vs-dark .monaco-menu .monaco-action-bar.vertical .action-menu-item:hover .action-label,.monaco-editor.vs .monaco-menu .monaco-action-bar.vertical .action-menu-item:hover .action-label{-ms-high-contrast-adjust:none;background:transparent!important;border:1px solid highlight;box-sizing:border-box}.monaco-diff-editor.vs-dark .diffOverviewRuler,.monaco-diff-editor.vs .diffOverviewRuler{display:none}.monaco-editor.vs-dark .line-delete,.monaco-editor.vs-dark .line-insert,.monaco-editor.vs .line-delete,.monaco-editor.vs .line-insert{background:transparent!important;border:1px solid highlight!important;box-sizing:border-box}.monaco-editor.vs-dark .char-delete,.monaco-editor.vs-dark .char-insert,.monaco-editor.vs .char-delete,.monaco-editor.vs .char-insert{background:transparent!important}}.context-view .monaco-menu{min-width:130px}.context-view-block{position:fixed;left:0;top:0;z-index:-1;width:100%;height:100%} \ No newline at end of file diff --git a/packages/rrweb-snapshot/test/css/style-with-import.css b/packages/rrweb-snapshot/test/css/style-with-import.css new file mode 100644 index 0000000000..61058d7b1e --- /dev/null +++ b/packages/rrweb-snapshot/test/css/style-with-import.css @@ -0,0 +1 @@ +@import "./style.css"; diff --git a/packages/rrweb-snapshot/test/css/style.css b/packages/rrweb-snapshot/test/css/style.css new file mode 100644 index 0000000000..2b3faf2a77 --- /dev/null +++ b/packages/rrweb-snapshot/test/css/style.css @@ -0,0 +1,12 @@ +body { + margin: 0; + background: url('../a.jpg'); + border-image: url('data:image/svg+xml;utf8,'); +} +p { + color: red; + background: url('./b.jpg'); +} +body > p { + color: yellow; +} diff --git a/packages/rrweb-snapshot/test/html/about-mozilla.html b/packages/rrweb-snapshot/test/html/about-mozilla.html new file mode 100644 index 0000000000..f353c48233 --- /dev/null +++ b/packages/rrweb-snapshot/test/html/about-mozilla.html @@ -0,0 +1,57 @@ + + + + + The Book of Mozilla, 11:9 + + + + + +

+ Mammon slept. And the beast reborn spread over the earth and its numbers + grew legion. And they proclaimed the times and sacrificed crops unto the + fire, with the cunning of foxes. And they built a new world in their own + image as promised by the + sacred words, and spoke + of the beast with their children. Mammon awoke, and lo! it was + naught but a follower. +

+ +

+ from The Book of Mozilla, 11:9
(10th Edition) +

+ + + + \ No newline at end of file diff --git a/packages/rrweb-snapshot/test/html/basic.html b/packages/rrweb-snapshot/test/html/basic.html new file mode 100644 index 0000000000..44d38cba8f --- /dev/null +++ b/packages/rrweb-snapshot/test/html/basic.html @@ -0,0 +1,15 @@ + + + + + + + + Document + + + +

Title

+ + + diff --git a/packages/rrweb-snapshot/test/html/block-element.html b/packages/rrweb-snapshot/test/html/block-element.html new file mode 100644 index 0000000000..f9671d2a0b --- /dev/null +++ b/packages/rrweb-snapshot/test/html/block-element.html @@ -0,0 +1,27 @@ + + + + + + + Document + + + + +
block 1
+
record 2
+
block 3
+
block 3
+ + diff --git a/packages/rrweb-snapshot/test/html/compat-mode.html b/packages/rrweb-snapshot/test/html/compat-mode.html new file mode 100644 index 0000000000..61b1544d38 --- /dev/null +++ b/packages/rrweb-snapshot/test/html/compat-mode.html @@ -0,0 +1,14 @@ + + + + Compat Mode; image resizing + + +
+ + + +
+
+ + diff --git a/packages/rrweb-snapshot/test/html/cors-style-sheet.html b/packages/rrweb-snapshot/test/html/cors-style-sheet.html new file mode 100644 index 0000000000..741a136662 --- /dev/null +++ b/packages/rrweb-snapshot/test/html/cors-style-sheet.html @@ -0,0 +1,15 @@ + + + + + + + with style sheet + + + + + diff --git a/packages/rrweb-snapshot/test/html/dynamic-stylesheet.html b/packages/rrweb-snapshot/test/html/dynamic-stylesheet.html new file mode 100644 index 0000000000..83a57ccf75 --- /dev/null +++ b/packages/rrweb-snapshot/test/html/dynamic-stylesheet.html @@ -0,0 +1,20 @@ + + + + + + + dynamic stylesheet + + + + +

p tag

+ + diff --git a/packages/rrweb-snapshot/test/html/form-fields.html b/packages/rrweb-snapshot/test/html/form-fields.html new file mode 100644 index 0000000000..31a35afa10 --- /dev/null +++ b/packages/rrweb-snapshot/test/html/form-fields.html @@ -0,0 +1,42 @@ + + + + + + + form fields + + + +
+ + + + + + +
+ + + diff --git a/packages/rrweb-snapshot/test/html/hover.html b/packages/rrweb-snapshot/test/html/hover.html new file mode 100644 index 0000000000..e6df2126a1 --- /dev/null +++ b/packages/rrweb-snapshot/test/html/hover.html @@ -0,0 +1,31 @@ + + + + + + + + hover selector + + + + +
hover me
+ + + \ No newline at end of file diff --git a/packages/rrweb-snapshot/test/html/iframe-inner.html b/packages/rrweb-snapshot/test/html/iframe-inner.html new file mode 100644 index 0000000000..2ef778d9d8 --- /dev/null +++ b/packages/rrweb-snapshot/test/html/iframe-inner.html @@ -0,0 +1 @@ + diff --git a/packages/rrweb-snapshot/test/html/iframe.html b/packages/rrweb-snapshot/test/html/iframe.html new file mode 100644 index 0000000000..8b45139ed4 --- /dev/null +++ b/packages/rrweb-snapshot/test/html/iframe.html @@ -0,0 +1,12 @@ + + + + + + + iframe + + + + + diff --git a/packages/rrweb-snapshot/test/html/invalid-attribute.html b/packages/rrweb-snapshot/test/html/invalid-attribute.html new file mode 100644 index 0000000000..e2428e2888 --- /dev/null +++ b/packages/rrweb-snapshot/test/html/invalid-attribute.html @@ -0,0 +1,3 @@ + + + diff --git a/packages/rrweb-snapshot/test/html/invalid-doctype.html b/packages/rrweb-snapshot/test/html/invalid-doctype.html new file mode 100644 index 0000000000..395c916d70 --- /dev/null +++ b/packages/rrweb-snapshot/test/html/invalid-doctype.html @@ -0,0 +1,9 @@ + + + + + + Invalid Doctype + + + diff --git a/packages/rrweb-snapshot/test/html/invalid-tagname.html b/packages/rrweb-snapshot/test/html/invalid-tagname.html new file mode 100644 index 0000000000..e28dd7104a --- /dev/null +++ b/packages/rrweb-snapshot/test/html/invalid-tagname.html @@ -0,0 +1,14 @@ + + + + + + + Document + + + Hello + Hello + + + diff --git a/packages/rrweb-snapshot/test/html/mask-text.html b/packages/rrweb-snapshot/test/html/mask-text.html new file mode 100644 index 0000000000..fe177a61c5 --- /dev/null +++ b/packages/rrweb-snapshot/test/html/mask-text.html @@ -0,0 +1,17 @@ + + + + + + + Document + + + +

mask 1

+
+ mask 2 +
+
mask 3
+ + diff --git a/packages/rrweb-snapshot/test/html/picture.html b/packages/rrweb-snapshot/test/html/picture.html new file mode 100644 index 0000000000..e005310b77 --- /dev/null +++ b/packages/rrweb-snapshot/test/html/picture.html @@ -0,0 +1,9 @@ + + + + + + + This is a robot + + diff --git a/packages/rrweb-snapshot/test/html/preload.html b/packages/rrweb-snapshot/test/html/preload.html new file mode 100644 index 0000000000..32e84a260d --- /dev/null +++ b/packages/rrweb-snapshot/test/html/preload.html @@ -0,0 +1,11 @@ + + + + + + Document + + + + + diff --git a/packages/rrweb-snapshot/test/html/shadow-dom.html b/packages/rrweb-snapshot/test/html/shadow-dom.html new file mode 100644 index 0000000000..0050bede01 --- /dev/null +++ b/packages/rrweb-snapshot/test/html/shadow-dom.html @@ -0,0 +1,209 @@ + + + + + + shadow DOM + + + + + + +
content panel 1
+
content panel 2
+
content panel 3
+
+ + + diff --git a/packages/rrweb-snapshot/test/html/svg.html b/packages/rrweb-snapshot/test/html/svg.html new file mode 100644 index 0000000000..3035cd3482 --- /dev/null +++ b/packages/rrweb-snapshot/test/html/svg.html @@ -0,0 +1,54 @@ + + + + IcoMoon - SVG Icons + + + + +
+

Grid Size: 0

+
+
+ + Icon-behance +
+
+
+
+ + Icon-linkedin +
+
+
+ + + + + + + + + + diff --git a/packages/rrweb-snapshot/test/html/video.html b/packages/rrweb-snapshot/test/html/video.html new file mode 100644 index 0000000000..653f7172d6 --- /dev/null +++ b/packages/rrweb-snapshot/test/html/video.html @@ -0,0 +1,19 @@ + + + + + + + video + + + + + diff --git a/packages/rrweb-snapshot/test/html/with-relative-res.html b/packages/rrweb-snapshot/test/html/with-relative-res.html new file mode 100644 index 0000000000..c390dc53cc --- /dev/null +++ b/packages/rrweb-snapshot/test/html/with-relative-res.html @@ -0,0 +1,21 @@ + + + + + + + Document + + + + Hello + Hello + Hello + + + + + + + + \ No newline at end of file diff --git a/packages/rrweb-snapshot/test/html/with-script.html b/packages/rrweb-snapshot/test/html/with-script.html new file mode 100644 index 0000000000..b4812e96b2 --- /dev/null +++ b/packages/rrweb-snapshot/test/html/with-script.html @@ -0,0 +1,18 @@ + + + + + + + + with script + + + + + + + + diff --git a/packages/rrweb-snapshot/test/html/with-style-sheet-with-import.html b/packages/rrweb-snapshot/test/html/with-style-sheet-with-import.html new file mode 100644 index 0000000000..6b45f65bc5 --- /dev/null +++ b/packages/rrweb-snapshot/test/html/with-style-sheet-with-import.html @@ -0,0 +1,16 @@ + + + + + + + + with style sheet with import + + + + + + + + diff --git a/packages/rrweb-snapshot/test/html/with-style-sheet.html b/packages/rrweb-snapshot/test/html/with-style-sheet.html new file mode 100644 index 0000000000..2083dae96f --- /dev/null +++ b/packages/rrweb-snapshot/test/html/with-style-sheet.html @@ -0,0 +1,16 @@ + + + + + + + + with style sheet + + + + + + + + diff --git a/test/html/frame1.html b/packages/rrweb-snapshot/test/iframe-html/frame1.html similarity index 100% rename from test/html/frame1.html rename to packages/rrweb-snapshot/test/iframe-html/frame1.html diff --git a/packages/rrweb-snapshot/test/iframe-html/frame2.html b/packages/rrweb-snapshot/test/iframe-html/frame2.html new file mode 100644 index 0000000000..343240858e --- /dev/null +++ b/packages/rrweb-snapshot/test/iframe-html/frame2.html @@ -0,0 +1,11 @@ + + + + + + Frame 2 + + + frame 2 + + diff --git a/packages/rrweb-snapshot/test/iframe-html/main.html b/packages/rrweb-snapshot/test/iframe-html/main.html new file mode 100644 index 0000000000..d8e712bc85 --- /dev/null +++ b/packages/rrweb-snapshot/test/iframe-html/main.html @@ -0,0 +1,12 @@ + + + + + + Main + + + + + + diff --git a/packages/rrweb-snapshot/test/images/compat-bottom.png b/packages/rrweb-snapshot/test/images/compat-bottom.png new file mode 100644 index 0000000000..bc0267a619 Binary files /dev/null and b/packages/rrweb-snapshot/test/images/compat-bottom.png differ diff --git a/packages/rrweb-snapshot/test/images/compat-top-left.png b/packages/rrweb-snapshot/test/images/compat-top-left.png new file mode 100644 index 0000000000..40dc35b6d2 Binary files /dev/null and b/packages/rrweb-snapshot/test/images/compat-top-left.png differ diff --git a/packages/rrweb-snapshot/test/images/compat-top-right.png b/packages/rrweb-snapshot/test/images/compat-top-right.png new file mode 100644 index 0000000000..0e7a6d2866 Binary files /dev/null and b/packages/rrweb-snapshot/test/images/compat-top-right.png differ diff --git a/packages/rrweb-snapshot/test/images/robot.png b/packages/rrweb-snapshot/test/images/robot.png new file mode 100644 index 0000000000..cc486cc8b7 Binary files /dev/null and b/packages/rrweb-snapshot/test/images/robot.png differ diff --git a/packages/rrweb-snapshot/test/images/symbol-defs.svg b/packages/rrweb-snapshot/test/images/symbol-defs.svg new file mode 100644 index 0000000000..17edc194d4 --- /dev/null +++ b/packages/rrweb-snapshot/test/images/symbol-defs.svg @@ -0,0 +1,12 @@ + + + +behance + + + +linkedin + + + + \ No newline at end of file diff --git a/packages/rrweb-snapshot/test/integration.test.ts b/packages/rrweb-snapshot/test/integration.test.ts new file mode 100644 index 0000000000..0d5af440b6 --- /dev/null +++ b/packages/rrweb-snapshot/test/integration.test.ts @@ -0,0 +1,312 @@ +import * as fs from 'fs'; +import * as path from 'path'; +import * as http from 'http'; +import * as url from 'url'; +import * as puppeteer from 'puppeteer'; +import * as rollup from 'rollup'; +import * as typescript from '@rollup/plugin-typescript'; +import * as assert from 'assert'; + +const _typescript = (typescript as unknown) as typeof typescript.default; + +const htmlFolder = path.join(__dirname, 'html'); +const htmls = fs.readdirSync(htmlFolder).map((filePath) => { + const raw = fs.readFileSync(path.resolve(htmlFolder, filePath), 'utf-8'); + return { + filePath, + src: raw, + }; +}); + +interface IMimeType { + [key: string]: string; +} + +const startServer = () => + new Promise((resolve) => { + const mimeType: IMimeType = { + '.html': 'text/html', + '.js': 'text/javascript', + '.css': 'text/css', + '.png': 'image/png', + }; + const s = http.createServer((req, res) => { + const parsedUrl = url.parse(req.url!); + const sanitizePath = path + .normalize(parsedUrl.pathname!) + .replace(/^(\.\.[\/\\])+/, ''); + let pathname = path.join(__dirname, sanitizePath); + try { + const data = fs.readFileSync(pathname); + const ext = path.parse(pathname).ext; + res.setHeader('Content-type', mimeType[ext] || 'text/plain'); + res.setHeader('Access-Control-Allow-Origin', '*'); + res.setHeader('Access-Control-Allow-Methods', 'GET'); + res.setHeader('Access-Control-Allow-Headers', 'Content-type'); + res.end(data); + } catch (error) { + res.end(); + } + }); + s.listen(3030).on('listening', () => { + resolve(s); + }); + }); + +interface ISuite { + server: http.Server; + browser: puppeteer.Browser; + code: string; +} + +describe('integration tests', function (this: ISuite) { + jest.setTimeout(30_000); + let server: ISuite['server']; + let browser: ISuite['browser']; + let code: ISuite['code']; + + beforeAll(async () => { + server = await startServer(); + browser = await puppeteer.launch({ + // headless: false, + }); + + const bundle = await rollup.rollup({ + input: path.resolve(__dirname, '../src/index.ts'), + plugins: [_typescript()], + }); + const { + output: [{ code: _code }], + } = await bundle.generate({ + name: 'rrweb', + format: 'iife', + }); + code = _code; + }); + + afterAll(async () => { + await browser.close(); + await server.close(); + }); + + for (const html of htmls) { + if (html.filePath.substring(html.filePath.length - 1) === '~') { + continue; + } + const title = '[html file]: ' + html.filePath; + it(title, async () => { + const page: puppeteer.Page = await browser.newPage(); + // console for debug + // tslint:disable-next-line: no-console + page.on('console', (msg) => console.log(msg.text())); + if (html.filePath === 'iframe.html') { + // loading directly is needed to ensure we don't trigger compatMode='BackCompat' + // which happens before setContent can be called + await page.goto(`http://localhost:3030/html/${html.filePath}`, { + waitUntil: 'load', + }); + const outerCompatMode = await page.evaluate('document.compatMode'); + const innerCompatMode = await page.evaluate( + 'document.querySelector("iframe").contentDocument.compatMode', + ); + assert( + outerCompatMode === 'CSS1Compat', + outerCompatMode + + ' for outer iframe.html should be CSS1Compat as it has ""', + ); + // inner omits a doctype so gets rendered in backwards compat mode + // although this was originally accidental, we'll add a synthetic doctype to the rebuild to recreate this + assert( + innerCompatMode === 'BackCompat', + innerCompatMode + + ' for iframe-inner.html should be BackCompat as it lacks ""', + ); + } else { + // loading indirectly is improtant for relative path testing + await page.goto(`http://localhost:3030/html`); + await page.setContent(html.src, { + waitUntil: 'load', + }); + } + const rebuildHtml = ( + await page.evaluate(`${code} + const x = new XMLSerializer(); + const [snap] = rrweb.snapshot(document); + let out = x.serializeToString(rrweb.rebuild(snap, { doc: document })[0]); + if (document.querySelector('html').getAttribute('xmlns') !== 'http://www.w3.org/1999/xhtml') { + // this is just an artefact of serializeToString + out = out.replace(' xmlns=\"http://www.w3.org/1999/xhtml\"', ''); + } + out; // return + `) + ).replace(/\n\n/g, ''); + expect(rebuildHtml).toMatchSnapshot(); + }); + } + + it('correctly triggers backCompat mode and rendering', async () => { + const page: puppeteer.Page = await browser.newPage(); + // console for debug + // tslint:disable-next-line: no-console + page.on('console', (msg) => console.log(msg.text())); + + await page.goto('http://localhost:3030/html/compat-mode.html', { + waitUntil: 'load', + }); + const compatMode = await page.evaluate('document.compatMode'); + assert( + compatMode === 'BackCompat', + compatMode + + ' for compat-mode.html should be BackCompat as DOCTYPE is deliberately omitted', + ); + const renderedHeight = await page.evaluate( + 'document.querySelector("center").clientHeight', + ); + // can remove following assertion if dimensions of page change + assert( + renderedHeight < 400, + `pre-check: images will be rendered ~326px high in BackCompat mode, and ~588px in CSS1Compat mode; getting: ${renderedHeight}px`, + ); + const rebuildRenderedHeight = await page.evaluate(`${code} +const [snap] = rrweb.snapshot(document); +const iframe = document.createElement('iframe'); +iframe.setAttribute('width', document.body.clientWidth) +iframe.setAttribute('height', document.body.clientHeight) +iframe.style.transform = 'scale(0.3)'; // mini-me +document.body.appendChild(iframe); +// magic here! rebuild in a new iframe +const rebuildNode = rrweb.rebuild(snap, { doc: iframe.contentDocument })[0]; +iframe.contentDocument.querySelector('center').clientHeight +`); + const rebuildCompatMode = await page.evaluate( + 'document.querySelector("iframe").contentDocument.compatMode', + ); + assert( + rebuildCompatMode === 'BackCompat', + "rebuilt compatMode should match source compatMode, but doesn't: " + + rebuildCompatMode, + ); + assert( + rebuildRenderedHeight === renderedHeight, + 'rebuilt height (${rebuildRenderedHeight}) should equal original height (${renderedHeight})', + ); + }); + + it('correctly saves images offline', async () => { + const page: puppeteer.Page = await browser.newPage(); + + await page.goto('http://localhost:3030/html/picture.html', { + waitUntil: 'load', + }); + await page.waitForSelector('img', { timeout: 1000 }); + await page.evaluate(`${code}var snapshot = rrweb.snapshot(document, { + dataURLOptions: { type: "image/webp", quality: 0.8 }, + inlineImages: true, + inlineStylesheet: false + })`); + await page.waitFor(100); + const snapshot = await page.evaluate( + 'JSON.stringify(snapshot[0], null, 2);', + ); + assert(snapshot.includes('"rr_dataURL"')); + assert(snapshot.includes('data:image/webp;base64,')); + }); +}); + +describe('iframe integration tests', function (this: ISuite) { + jest.setTimeout(30_000); + let server: ISuite['server']; + let browser: ISuite['browser']; + let code: ISuite['code']; + + beforeAll(async () => { + server = await startServer(); + browser = await puppeteer.launch({ + // headless: false, + }); + + const bundle = await rollup.rollup({ + input: path.resolve(__dirname, '../src/index.ts'), + plugins: [_typescript()], + }); + const { + output: [{ code: _code }], + } = await bundle.generate({ + name: 'rrweb', + format: 'iife', + }); + code = _code; + }); + + afterAll(async () => { + await browser.close(); + await server.close(); + }); + + it('snapshot async iframes', async () => { + const page: puppeteer.Page = await browser.newPage(); + // console for debug + // tslint:disable-next-line: no-console + page.on('console', (msg) => console.log(msg.text())); + await page.goto(`http://localhost:3030/iframe-html/main.html`, { + waitUntil: 'load', + }); + const snapshotResult = JSON.stringify( + await page.evaluate(`${code}; + rrweb.snapshot(document)[0]; + `), + null, + 2, + ); + expect(snapshotResult).toMatchSnapshot(); + }); +}); + +describe('shadow DOM integration tests', function (this: ISuite) { + jest.setTimeout(30_000); + let server: ISuite['server']; + let browser: ISuite['browser']; + let code: ISuite['code']; + + beforeAll(async () => { + server = await startServer(); + browser = await puppeteer.launch({ + // headless: false, + }); + + const bundle = await rollup.rollup({ + input: path.resolve(__dirname, '../src/index.ts'), + plugins: [_typescript()], + }); + const { + output: [{ code: _code }], + } = await bundle.generate({ + name: 'rrweb', + format: 'iife', + }); + code = _code; + }); + + afterAll(async () => { + await browser.close(); + await server.close(); + }); + + it('snapshot shadow DOM', async () => { + const page: puppeteer.Page = await browser.newPage(); + // console for debug + // tslint:disable-next-line: no-console + page.on('console', (msg) => console.log(msg.text())); + await page.goto(`http://localhost:3030/html/shadow-dom.html`, { + waitUntil: 'load', + }); + const snapshotResult = JSON.stringify( + await page.evaluate(`${code}; + rrweb.snapshot(document)[0]; + `), + null, + 2, + ); + expect(snapshotResult).toMatchSnapshot(); + }); +}); diff --git a/packages/rrweb-snapshot/test/js/a.js b/packages/rrweb-snapshot/test/js/a.js new file mode 100644 index 0000000000..7a776f910f --- /dev/null +++ b/packages/rrweb-snapshot/test/js/a.js @@ -0,0 +1 @@ +var a = 1 + 1; diff --git a/packages/rrweb-snapshot/test/rebuild.test.ts b/packages/rrweb-snapshot/test/rebuild.test.ts new file mode 100644 index 0000000000..e669a29a95 --- /dev/null +++ b/packages/rrweb-snapshot/test/rebuild.test.ts @@ -0,0 +1,92 @@ +import * as fs from 'fs'; +import * as path from 'path'; +import { addHoverClass, createCache } from '../src/rebuild'; + +function getDuration(hrtime: [number, number]) { + const [seconds, nanoseconds] = hrtime; + return seconds * 1000 + nanoseconds / 1000000; +} + +describe('add hover class to hover selector related rules', function () { + let cache: ReturnType; + + beforeEach(() => { + cache = createCache(); + }); + + it('will do nothing to css text without :hover', () => { + const cssText = 'body { color: white }'; + expect(addHoverClass(cssText, cache)).toEqual(cssText); + }); + + it('can add hover class to css text', () => { + const cssText = '.a:hover { color: white }'; + expect(addHoverClass(cssText, cache)).toEqual( + '.a:hover, .a.\\:hover { color: white }', + ); + }); + + it('can add hover class when there is multi selector', () => { + const cssText = '.a, .b:hover, .c { color: white }'; + expect(addHoverClass(cssText, cache)).toEqual( + '.a, .b:hover, .b.\\:hover, .c { color: white }', + ); + }); + + it('can add hover class when there is a multi selector with the same prefix', () => { + const cssText = '.a:hover, .a:hover::after { color: white }'; + expect(addHoverClass(cssText, cache)).toEqual( + '.a:hover, .a.\\:hover, .a:hover::after, .a.\\:hover::after { color: white }', + ); + }); + + it('can add hover class when :hover is not the end of selector', () => { + const cssText = 'div:hover::after { color: white }'; + expect(addHoverClass(cssText, cache)).toEqual( + 'div:hover::after, div.\\:hover::after { color: white }', + ); + }); + + it('can add hover class when the selector has multi :hover', () => { + const cssText = 'a:hover b:hover { color: white }'; + expect(addHoverClass(cssText, cache)).toEqual( + 'a:hover b:hover, a.\\:hover b.\\:hover { color: white }', + ); + }); + + it('will ignore :hover in css value', () => { + const cssText = '.a::after { content: ":hover" }'; + expect(addHoverClass(cssText, cache)).toEqual(cssText); + }); + + it('benchmark', () => { + const cssText = fs.readFileSync( + path.resolve(__dirname, './css/benchmark.css'), + 'utf8', + ); + const start = process.hrtime(); + addHoverClass(cssText, cache); + const end = process.hrtime(start); + const duration = getDuration(end); + expect(duration).toBeLessThan(100); + }); + + it('should be a lot faster to add a hover class to a previously processed css string', () => { + const factor = 100; + + let cssText = fs.readFileSync( + path.resolve(__dirname, './css/benchmark.css'), + 'utf8', + ); + + const start = process.hrtime(); + addHoverClass(cssText, cache); + const end = process.hrtime(start); + + const cachedStart = process.hrtime(); + addHoverClass(cssText, cache); + const cachedEnd = process.hrtime(cachedStart); + + expect(getDuration(cachedEnd) * factor).toBeLessThan(getDuration(end)); + }); +}); diff --git a/packages/rrweb-snapshot/test/snapshot.test.ts b/packages/rrweb-snapshot/test/snapshot.test.ts new file mode 100644 index 0000000000..d1e94f9a82 --- /dev/null +++ b/packages/rrweb-snapshot/test/snapshot.test.ts @@ -0,0 +1,181 @@ +/** + * @jest-environment jsdom + */ +import { JSDOM } from 'jsdom'; +import { + absoluteToStylesheet, + serializeNodeWithId, + _isBlockedElement, +} from '../src/snapshot'; +import { serializedNodeWithId } from '../src/types'; + +describe('absolute url to stylesheet', () => { + const href = 'http://localhost/css/style.css'; + + it('can handle relative path', () => { + expect(absoluteToStylesheet('url(a.jpg)', href)).toEqual( + `url(http://localhost/css/a.jpg)`, + ); + }); + + it('can handle same level path', () => { + expect(absoluteToStylesheet('url("./a.jpg")', href)).toEqual( + `url("http://localhost/css/a.jpg")`, + ); + }); + + it('can handle parent level path', () => { + expect(absoluteToStylesheet('url("../a.jpg")', href)).toEqual( + `url("http://localhost/a.jpg")`, + ); + }); + + it('can handle absolute path', () => { + expect(absoluteToStylesheet('url("/a.jpg")', href)).toEqual( + `url("http://localhost/a.jpg")`, + ); + }); + + it('can handle external path', () => { + expect(absoluteToStylesheet('url("http://localhost/a.jpg")', href)).toEqual( + `url("http://localhost/a.jpg")`, + ); + }); + + it('can handle single quote path', () => { + expect(absoluteToStylesheet(`url('./a.jpg')`, href)).toEqual( + `url('http://localhost/css/a.jpg')`, + ); + }); + + it('can handle no quote path', () => { + expect(absoluteToStylesheet('url(./a.jpg)', href)).toEqual( + `url(http://localhost/css/a.jpg)`, + ); + }); + + it('can handle multiple no quote paths', () => { + expect( + absoluteToStylesheet( + 'background-image: url(images/b.jpg);background: #aabbcc url(images/a.jpg) 50% 50% repeat;', + href, + ), + ).toEqual( + `background-image: url(http://localhost/css/images/b.jpg);` + + `background: #aabbcc url(http://localhost/css/images/a.jpg) 50% 50% repeat;`, + ); + }); + + it('can handle data url image', () => { + expect( + absoluteToStylesheet('url(data:image/gif;base64,ABC)', href), + ).toEqual('url(data:image/gif;base64,ABC)'); + expect( + absoluteToStylesheet( + 'url(data:application/font-woff;base64,d09GMgABAAAAAAm)', + href, + ), + ).toEqual('url(data:application/font-woff;base64,d09GMgABAAAAAAm)'); + }); + + it('preserves quotes around inline svgs with spaces', () => { + expect( + absoluteToStylesheet( + "url(\"data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3E%3Cpath fill='%2328a745' d='M3'/%3E%3C/svg%3E\")", + href, + ), + ).toEqual( + "url(\"data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3E%3Cpath fill='%2328a745' d='M3'/%3E%3C/svg%3E\")", + ); + expect( + absoluteToStylesheet( + 'url(\'data:image/svg+xml;utf8,\')', + href, + ), + ).toEqual( + 'url(\'data:image/svg+xml;utf8,\')', + ); + expect( + absoluteToStylesheet( + 'url("data:image/svg+xml;utf8,")', + href, + ), + ).toEqual( + 'url("data:image/svg+xml;utf8,")', + ); + }); + it('can handle empty path', () => { + expect(absoluteToStylesheet(`url('')`, href)).toEqual(`url('')`); + }); +}); + +describe('isBlockedElement()', () => { + const subject = (html: string, opt: any = {}) => + _isBlockedElement(render(html), 'rr-block', opt.blockSelector); + + const render = (html: string): HTMLElement => + JSDOM.fragment(html).querySelector('div')!; + + it('can handle empty elements', () => { + expect(subject('
')).toEqual(false); + }); + + it('blocks prohibited className', () => { + expect(subject('
')).toEqual(true); + }); + + it('does not block random data selector', () => { + expect(subject('
')).toEqual(false); + }); + + it('blocks blocked selector', () => { + expect( + subject('
', { blockSelector: '[data-rr-block]' }), + ).toEqual(true); + }); +}); + +describe('style elements', () => { + const serializeNode = (node: Node): serializedNodeWithId | null => { + return serializeNodeWithId(node, { + doc: document, + map: {}, + blockClass: 'blockblock', + blockSelector: null, + maskTextClass: 'maskmask', + maskTextSelector: null, + skipChild: false, + inlineStylesheet: true, + maskTextFn: undefined, + maskInputFn: undefined, + slimDOMOptions: {}, + }); + }; + + const render = (html: string): HTMLStyleElement => { + document.write(html); + return document.querySelector('style')!; + }; + + it('should serialize all rules of stylesheet when the sheet has a single child node', () => { + const styleEl = render(``); + styleEl.sheet?.insertRule('section { color: blue; }'); + expect(serializeNode(styleEl.childNodes[0])).toMatchObject({ + isStyle: true, + rootId: undefined, + textContent: 'section {color: blue;}body {color: red;}', + type: 3, + }); + }); + + it('should serialize individual text nodes on stylesheets with multiple child nodes', () => { + const styleEl = render(``); + styleEl.append(document.createTextNode('section { color: blue; }')); + expect(serializeNode(styleEl.childNodes[1])).toMatchObject({ + isStyle: true, + rootId: undefined, + textContent: 'section { color: blue; }', + type: 3, + }); + }); +}); diff --git a/packages/rrweb-snapshot/tsconfig.json b/packages/rrweb-snapshot/tsconfig.json new file mode 100644 index 0000000000..2b6d703290 --- /dev/null +++ b/packages/rrweb-snapshot/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "module": "ESNext", + "moduleResolution": "Node", + "noImplicitAny": true, + "strictNullChecks": true, + "removeComments": true, + "preserveConstEnums": true, + "rootDir": "src", + "outDir": "build", + "lib": ["es6", "dom"] + }, + "exclude": ["test"], + "include": ["src"] +} diff --git a/packages/rrweb-snapshot/tslint.json b/packages/rrweb-snapshot/tslint.json new file mode 100644 index 0000000000..a153081c53 --- /dev/null +++ b/packages/rrweb-snapshot/tslint.json @@ -0,0 +1,21 @@ +{ + "defaultSeverity": "error", + "extends": ["tslint:recommended"], + "jsRules": {}, + "rules": { + "no-any": true, + "quotemark": [true, "single"], + "ordered-imports": false, + "object-literal-sort-keys": false, + "no-unused-variable": true, + "object-literal-key-quotes": false, + "variable-name": [ + true, + "ban-keywords", + "check-format", + "allow-leading-underscore" + ], + "arrow-parens": false + }, + "rulesDirectory": [] +} diff --git a/packages/rrweb-snapshot/typings/css.d.ts b/packages/rrweb-snapshot/typings/css.d.ts new file mode 100644 index 0000000000..6207385ab7 --- /dev/null +++ b/packages/rrweb-snapshot/typings/css.d.ts @@ -0,0 +1,92 @@ +export interface ParserOptions { + silent?: boolean; + source?: string; +} +export interface ParserError { + message?: string; + reason?: string; + filename?: string; + line?: number; + column?: number; + source?: string; +} +export interface Loc { + line?: number; + column?: number; +} +export interface Node { + type?: string; + parent?: Node; + position?: { + start?: Loc; + end?: Loc; + source?: string; + content?: string; + }; +} +export interface Rule extends Node { + selectors?: string[]; + declarations?: Array; +} +export interface Declaration extends Node { + property?: string; + value?: string; +} +export interface Comment extends Node { + comment?: string; +} +export interface Charset extends Node { + charset?: string; +} +export interface CustomMedia extends Node { + name?: string; + media?: string; +} +export interface Document extends Node { + document?: string; + vendor?: string; + rules?: Array; +} +export interface FontFace extends Node { + declarations?: Array; +} +export interface Host extends Node { + rules?: Array; +} +export interface Import extends Node { + import?: string; +} +export interface KeyFrames extends Node { + name?: string; + vendor?: string; + keyframes?: Array; +} +export interface KeyFrame extends Node { + values?: string[]; + declarations?: Array; +} +export interface Media extends Node { + media?: string; + rules?: Array; +} +export interface Namespace extends Node { + namespace?: string; +} +export interface Page extends Node { + selectors?: string[]; + declarations?: Array; +} +export interface Supports extends Node { + supports?: string; + rules?: Array; +} +export declare type AtRule = Charset | CustomMedia | Document | FontFace | Host | Import | KeyFrames | Media | Namespace | Page | Supports; +export interface StyleRules { + source?: string; + rules: Array; + parsingErrors?: ParserError[]; +} +export interface Stylesheet extends Node { + stylesheet?: StyleRules; +} +export declare function parse(css: string, options?: ParserOptions): Stylesheet; diff --git a/packages/rrweb-snapshot/typings/index.d.ts b/packages/rrweb-snapshot/typings/index.d.ts new file mode 100644 index 0000000000..efd1fb347e --- /dev/null +++ b/packages/rrweb-snapshot/typings/index.d.ts @@ -0,0 +1,5 @@ +import snapshot, { serializeNodeWithId, transformAttribute, visitSnapshot, cleanupSnapshot, needMaskingText, IGNORED_NODE } from './snapshot'; +import rebuild, { buildNodeWithSN, addHoverClass, createCache } from './rebuild'; +export * from './types'; +export * from './utils'; +export { snapshot, serializeNodeWithId, rebuild, buildNodeWithSN, addHoverClass, createCache, transformAttribute, visitSnapshot, cleanupSnapshot, needMaskingText, IGNORED_NODE, }; diff --git a/packages/rrweb-snapshot/typings/rebuild.d.ts b/packages/rrweb-snapshot/typings/rebuild.d.ts new file mode 100644 index 0000000000..64a564cfd7 --- /dev/null +++ b/packages/rrweb-snapshot/typings/rebuild.d.ts @@ -0,0 +1,19 @@ +import { serializedNodeWithId, idNodeMap, INode, BuildCache } from './types'; +export declare function addHoverClass(cssText: string, cache: BuildCache): string; +export declare function createCache(): BuildCache; +export declare function buildNodeWithSN(n: serializedNodeWithId, options: { + doc: Document; + map: idNodeMap; + skipChild?: boolean; + hackCss: boolean; + afterAppend?: (n: INode) => unknown; + cache: BuildCache; +}): INode | null; +declare function rebuild(n: serializedNodeWithId, options: { + doc: Document; + onVisit?: (node: INode) => unknown; + hackCss?: boolean; + afterAppend?: (n: INode) => unknown; + cache: BuildCache; +}): [Node | null, idNodeMap]; +export default rebuild; diff --git a/packages/rrweb-snapshot/typings/snapshot.d.ts b/packages/rrweb-snapshot/typings/snapshot.d.ts new file mode 100644 index 0000000000..b970f751eb --- /dev/null +++ b/packages/rrweb-snapshot/typings/snapshot.d.ts @@ -0,0 +1,51 @@ +import { serializedNodeWithId, INode, idNodeMap, MaskInputOptions, SlimDOMOptions, DataURLOptions, MaskTextFn, MaskInputFn, KeepIframeSrcFn } from './types'; +export declare const IGNORED_NODE = -2; +export declare function absoluteToStylesheet(cssText: string | null, href: string): string; +export declare function absoluteToDoc(doc: Document, attributeValue: string): string; +export declare function transformAttribute(doc: Document, tagName: string, name: string, value: string): string; +export declare function _isBlockedElement(element: HTMLElement, blockClass: string | RegExp, blockSelector: string | null): boolean; +export declare function needMaskingText(node: Node | null, maskTextClass: string | RegExp, maskTextSelector: string | null): boolean; +export declare function serializeNodeWithId(n: Node | INode, options: { + doc: Document; + map: idNodeMap; + blockClass: string | RegExp; + blockSelector: string | null; + maskTextClass: string | RegExp; + maskTextSelector: string | null; + skipChild: boolean; + inlineStylesheet: boolean; + maskInputOptions?: MaskInputOptions; + maskTextFn: MaskTextFn | undefined; + maskInputFn: MaskInputFn | undefined; + slimDOMOptions: SlimDOMOptions; + dataURLOptions?: DataURLOptions; + keepIframeSrcFn?: KeepIframeSrcFn; + inlineImages?: boolean; + recordCanvas?: boolean; + preserveWhiteSpace?: boolean; + onSerialize?: (n: INode) => unknown; + onIframeLoad?: (iframeINode: INode, node: serializedNodeWithId) => unknown; + iframeLoadTimeout?: number; +}): serializedNodeWithId | null; +declare function snapshot(n: Document, options?: { + blockClass?: string | RegExp; + blockSelector?: string | null; + maskTextClass?: string | RegExp; + maskTextSelector?: string | null; + inlineStylesheet?: boolean; + maskAllInputs?: boolean | MaskInputOptions; + maskTextFn?: MaskTextFn; + maskInputFn?: MaskTextFn; + slimDOM?: boolean | SlimDOMOptions; + dataURLOptions?: DataURLOptions; + inlineImages?: boolean; + recordCanvas?: boolean; + preserveWhiteSpace?: boolean; + onSerialize?: (n: INode) => unknown; + onIframeLoad?: (iframeINode: INode, node: serializedNodeWithId) => unknown; + iframeLoadTimeout?: number; + keepIframeSrcFn?: KeepIframeSrcFn; +}): [serializedNodeWithId | null, idNodeMap]; +export declare function visitSnapshot(node: serializedNodeWithId, onVisit: (node: serializedNodeWithId) => unknown): void; +export declare function cleanupSnapshot(): void; +export default snapshot; diff --git a/packages/rrweb-snapshot/typings/types.d.ts b/packages/rrweb-snapshot/typings/types.d.ts new file mode 100644 index 0000000000..a8ccc0d305 --- /dev/null +++ b/packages/rrweb-snapshot/typings/types.d.ts @@ -0,0 +1,103 @@ +export declare enum NodeType { + Document = 0, + DocumentType = 1, + Element = 2, + Text = 3, + CDATA = 4, + Comment = 5 +} +export declare type documentNode = { + type: NodeType.Document; + childNodes: serializedNodeWithId[]; + compatMode?: string; +}; +export declare type documentTypeNode = { + type: NodeType.DocumentType; + name: string; + publicId: string; + systemId: string; +}; +export declare type attributes = { + [key: string]: string | number | boolean; +}; +export declare type elementNode = { + type: NodeType.Element; + tagName: string; + attributes: attributes; + childNodes: serializedNodeWithId[]; + isSVG?: true; + needBlock?: boolean; +}; +export declare type textNode = { + type: NodeType.Text; + textContent: string; + isStyle?: true; +}; +export declare type cdataNode = { + type: NodeType.CDATA; + textContent: ''; +}; +export declare type commentNode = { + type: NodeType.Comment; + textContent: string; +}; +export declare type serializedNode = (documentNode | documentTypeNode | elementNode | textNode | cdataNode | commentNode) & { + rootId?: number; + isShadowHost?: boolean; + isShadow?: boolean; +}; +export declare type serializedNodeWithId = serializedNode & { + id: number; +}; +export declare type tagMap = { + [key: string]: string; +}; +export interface INode extends Node { + __sn: serializedNodeWithId; +} +export interface ICanvas extends HTMLCanvasElement { + __context: string; +} +export declare type idNodeMap = { + [key: number]: INode; +}; +export declare type MaskInputOptions = Partial<{ + color: boolean; + date: boolean; + 'datetime-local': boolean; + email: boolean; + month: boolean; + number: boolean; + range: boolean; + search: boolean; + tel: boolean; + text: boolean; + time: boolean; + url: boolean; + week: boolean; + textarea: boolean; + select: boolean; + password: boolean; +}>; +export declare type SlimDOMOptions = Partial<{ + script: boolean; + comment: boolean; + headFavicon: boolean; + headWhitespace: boolean; + headMetaDescKeywords: boolean; + headMetaSocial: boolean; + headMetaRobots: boolean; + headMetaHttpEquiv: boolean; + headMetaAuthorship: boolean; + headMetaVerification: boolean; +}>; +export declare type DataURLOptions = Partial<{ + type: string; + quality: number; +}>; +export declare type MaskTextFn = (text: string) => string; +export declare type MaskInputFn = (text: string) => string; +export declare type KeepIframeSrcFn = (src: string) => boolean; +export declare type BuildCache = { + stylesWithHoverClass: Map; +}; diff --git a/packages/rrweb-snapshot/typings/utils.d.ts b/packages/rrweb-snapshot/typings/utils.d.ts new file mode 100644 index 0000000000..6572ab8279 --- /dev/null +++ b/packages/rrweb-snapshot/typings/utils.d.ts @@ -0,0 +1,11 @@ +import { INode, MaskInputFn, MaskInputOptions } from './types'; +export declare function isElement(n: Node | INode): n is Element; +export declare function isShadowRoot(n: Node): n is ShadowRoot; +export declare function maskInputValue({ maskInputOptions, tagName, type, value, maskInputFn, }: { + maskInputOptions: MaskInputOptions; + tagName: string; + type: string | number | boolean | null; + value: string | null; + maskInputFn?: MaskInputFn; +}): string; +export declare function is2DCanvasBlank(canvas: HTMLCanvasElement): boolean; diff --git a/packages/rrweb/.gitignore b/packages/rrweb/.gitignore new file mode 100644 index 0000000000..4875c32f5c --- /dev/null +++ b/packages/rrweb/.gitignore @@ -0,0 +1,16 @@ +.vscode +.idea +node_modules +package-lock.json +# yarn.lock +build +dist +es +lib + +temp + +*.log + +.env +__diff_output__ \ No newline at end of file diff --git a/packages/rrweb/.release-it.json b/packages/rrweb/.release-it.json new file mode 100644 index 0000000000..413a96b8ec --- /dev/null +++ b/packages/rrweb/.release-it.json @@ -0,0 +1,12 @@ +{ + "non-interactive": true, + "hooks": { + "before:init": ["npm run bundle", "npm run typings"] + }, + "git": { + "requireCleanWorkingDir": false + }, + "github": { + "release": true + } +} \ No newline at end of file diff --git a/packages/rrweb/jest.config.js b/packages/rrweb/jest.config.js new file mode 100644 index 0000000000..29db4e7fa0 --- /dev/null +++ b/packages/rrweb/jest.config.js @@ -0,0 +1,9 @@ +/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */ +module.exports = { + preset: 'ts-jest', + testEnvironment: 'node', + testMatch: ['**/**.test.ts'], + moduleNameMapper: { + '\\.css$': 'identity-obj-proxy', + }, +}; diff --git a/packages/rrweb/package.json b/packages/rrweb/package.json new file mode 100644 index 0000000000..1977be7d8d --- /dev/null +++ b/packages/rrweb/package.json @@ -0,0 +1,84 @@ +{ + "name": "rrweb", + "version": "1.1.2", + "description": "record and replay the web", + "scripts": { + "prepare": "npm run prepack", + "prepack": "npm run bundle", + "test": "npm run bundle:browser && jest", + "test:headless": "npm run bundle:browser && PUPPETEER_HEADLESS=true jest", + "test:watch": "PUPPETEER_HEADLESS=true npm run test -- --watch", + "repl": "npm run bundle:browser && node scripts/repl.js", + "dev": "yarn bundle:browser --watch", + "bundle:browser": "cross-env BROWSER_ONLY=true rollup --config", + "bundle": "rollup --config", + "typings": "tsc -d --declarationDir typings", + "check-types": "tsc -noEmit", + "prepublish": "npm run typings && npm run bundle" + }, + "repository": { + "type": "git", + "url": "git+ssh://git@github.com/rrweb-io/rrweb.git" + }, + "keywords": [ + "rrweb" + ], + "main": "lib/rrweb-all.js", + "module": "es/rrweb/packages/rrweb/src/entries/all.js", + "unpkg": "dist/rrweb.js", + "sideEffects": false, + "typings": "typings/entries/all.d.ts", + "files": [ + "dist", + "lib", + "es", + "typings" + ], + "author": "yanzhen@smartx.com", + "license": "MIT", + "bugs": { + "url": "https://github.com/rrweb-io/rrweb/issues" + }, + "homepage": "https://github.com/rrweb-io/rrweb#readme", + "devDependencies": { + "@rollup/plugin-node-resolve": "^7.0.0", + "@rollup/plugin-typescript": "^8.3.1", + "@types/chai": "^4.1.6", + "@types/inquirer": "0.0.43", + "@types/jest": "^27.4.1", + "@types/jest-image-snapshot": "^4.3.1", + "@types/jsdom": "^16.2.14", + "@types/node": "^17.0.21", + "@types/prettier": "^2.3.2", + "@types/puppeteer": "^5.4.4", + "cross-env": "^5.2.0", + "fast-mhtml": "^1.1.9", + "identity-obj-proxy": "^3.0.0", + "ignore-styles": "^5.0.1", + "inquirer": "^6.2.1", + "jest": "^27.5.1", + "jest-image-snapshot": "^4.5.1", + "jest-snapshot": "^23.6.0", + "jsdom": "^17.0.0", + "jsdom-global": "^3.0.2", + "prettier": "2.2.1", + "puppeteer": "^9.1.1", + "rollup": "^2.45.2", + "rollup-plugin-postcss": "^3.1.1", + "rollup-plugin-rename-node-modules": "^1.1.0", + "rollup-plugin-terser": "^7.0.2", + "ts-jest": "^27.1.3", + "ts-node": "^10.7.0", + "tslib": "^2.3.1", + "tslint": "^6.1.3", + "typescript": "^4.6.2" + }, + "dependencies": { + "@types/css-font-loading-module": "0.0.7", + "@xstate/fsm": "^1.4.0", + "base64-arraybuffer": "^1.0.1", + "fflate": "^0.4.4", + "mitt": "^1.1.3", + "rrweb-snapshot": "^1.1.13" + } +} diff --git a/rollup.config.js b/packages/rrweb/rollup.config.js similarity index 56% rename from rollup.config.js rename to packages/rrweb/rollup.config.js index 3381a518c5..8d6a3c6633 100644 --- a/rollup.config.js +++ b/packages/rrweb/rollup.config.js @@ -1,5 +1,5 @@ -import typescript from 'rollup-plugin-typescript'; -import resolve from 'rollup-plugin-node-resolve'; +import typescript from '@rollup/plugin-typescript'; +import resolve from '@rollup/plugin-node-resolve'; import { terser } from 'rollup-plugin-terser'; import postcss from 'rollup-plugin-postcss'; import renameNodeModules from 'rollup-plugin-rename-node-modules'; @@ -33,6 +33,13 @@ function toAllPath(path) { return path.replace('rrweb', 'rrweb-all'); } +function toPluginPath(pluginName, stage) { + return (path) => + path + .replace(/^([\w]+)\//, '$1/plugins/') + .replace('rrweb', `${pluginName}-${stage}`); +} + function toMinPath(path) { return path.replace(/\.js$/, '.min.js'); } @@ -73,13 +80,41 @@ const baseConfigs = [ input: './src/entries/all.ts', name: 'rrweb', pathFn: toAllPath, + esm: true, + }, + // plugins + { + input: './src/plugins/console/record/index.ts', + name: 'rrwebConsoleRecord', + pathFn: toPluginPath('console', 'record'), + }, + { + input: './src/plugins/console/replay/index.ts', + name: 'rrwebConsoleReplay', + pathFn: toPluginPath('console', 'replay'), + }, + { + input: './src/plugins/sequential-id/record/index.ts', + name: 'rrwebSequentialIdRecord', + pathFn: toPluginPath('sequential-id', 'record'), + }, + { + input: './src/plugins/sequential-id/replay/index.ts', + name: 'rrwebSequentialIdReplay', + pathFn: toPluginPath('sequential-id', 'replay'), }, ]; let configs = []; for (const c of baseConfigs) { - const basePlugins = [resolve({ browser: true }), typescript()]; + const basePlugins = [ + resolve({ browser: true }), + typescript({ + // a trick to avoid @rollup/plugin-typescript error + outDir: 'es/rrweb', + }), + ]; const plugins = basePlugins.concat( postcss({ extract: false, @@ -129,43 +164,66 @@ for (const c of baseConfigs) { }, ], }); - // ES module - configs.push({ - input: c.input, - plugins, - preserveModules: true, - output: [ - { - format: 'esm', - dir: 'es/rrweb', - plugins: [renameNodeModules('ext')], - }, - ], - }); + if (c.esm) { + // ES module + configs.push({ + input: c.input, + plugins, + preserveModules: true, + output: [ + { + format: 'esm', + dir: 'es/rrweb', + plugins: [renameNodeModules('ext')], + }, + ], + }); + } } if (process.env.BROWSER_ONLY) { - configs = { - input: './src/index.ts', - plugins: [ + const browserOnlyBaseConfigs = [ + { + input: './src/index.ts', + name: 'rrweb', + pathFn: (p) => p, + }, + { + input: './src/plugins/console/record/index.ts', + name: 'rrwebConsoleRecord', + pathFn: toPluginPath('console', 'record'), + }, + ]; + + configs = []; + + for (const c of browserOnlyBaseConfigs) { + const plugins = [ resolve({ browser: true }), - typescript(), + typescript({ + outDir: null, + }), postcss({ - extract: true, - minimize: true, + extract: false, + inject: false, sourceMap: true, }), terser(), - ], - output: [ - { - name: 'rrweb', - format: 'iife', - file: toMinPath(pkg.unpkg), - sourcemap: true, - }, - ], - }; + ]; + + configs.push({ + input: c.input, + plugins, + output: [ + { + name: c.name, + format: 'iife', + file: toMinPath(c.pathFn(pkg.unpkg)), + sourcemap: true, + }, + ], + }); + } } export default configs; diff --git a/scripts/repl.ts b/packages/rrweb/scripts/repl.js similarity index 71% rename from scripts/repl.ts rename to packages/rrweb/scripts/repl.js index ef97465a7e..e44086b811 100644 --- a/scripts/repl.ts +++ b/packages/rrweb/scripts/repl.js @@ -1,28 +1,42 @@ /* tslint:disable: no-console */ -import * as fs from 'fs'; -import * as path from 'path'; -import * as EventEmitter from 'events'; -import * as inquirer from 'inquirer'; -import * as puppeteer from 'puppeteer'; -import { eventWithTime } from '../src/types'; +const fs = require('fs'); +const path = require('path'); +const EventEmitter = require('events'); +const inquirer = require('inquirer'); +const puppeteer = require('puppeteer'); const emitter = new EventEmitter(); -function getCode(): string { +function getCode() { const bundlePath = path.resolve(__dirname, '../dist/rrweb.min.js'); return fs.readFileSync(bundlePath, 'utf8'); } (async () => { const code = getCode(); - let events: eventWithTime[] = []; + let events = []; start(); + const fakeGoto = async (page, url) => { + const intercept = async (request) => { + await request.respond({ + status: 200, + contentType: 'text/html', + body: ' ', // non-empty string or page will load indefinitely + }); + }; + await page.setRequestInterception(true); + page.on('request', intercept); + await page.goto(url); + await page.setRequestInterception(false); + page.off('request', intercept); + }; + async function start() { events = []; - const { url } = await inquirer.prompt<{ url: string }>([ + const { url } = await inquirer.prompt([ { type: 'input', name: 'url', @@ -35,17 +49,25 @@ function getCode(): string { await record(url); console.log('Ready to record. You can do any interaction on the page.'); - const { shouldReplay } = await inquirer.prompt<{ shouldReplay: boolean }>([ + const { shouldReplay } = await inquirer.prompt([ { - type: 'confirm', + type: 'list', + choices: [ + { name: 'Start replay (default)', value: 'default' }, + { + name: `Start replay on original url (helps when experiencing CORS issues)`, + value: 'replayWithFakeURL', + }, + { name: 'Skip replay', value: false }, + ], name: 'shouldReplay', - message: `Once you want to finish the recording, enter 'y' to start replay: `, + message: `Once you want to finish the recording, choose the following to start replay: `, }, ]); emitter.emit('done', shouldReplay); - const { shouldStore } = await inquirer.prompt<{ shouldStore: boolean }>([ + const { shouldStore } = await inquirer.prompt([ { type: 'confirm', name: 'shouldStore', @@ -57,9 +79,7 @@ function getCode(): string { saveEvents(); } - const { shouldRecordAnother } = await inquirer.prompt<{ - shouldRecordAnother: boolean; - }>([ + const { shouldRecordAnother } = await inquirer.prompt([ { type: 'confirm', name: 'shouldRecordAnother', @@ -74,7 +94,7 @@ function getCode(): string { } } - async function record(url: string) { + async function record(url) { const browser = await puppeteer.launch({ headless: false, defaultViewport: { @@ -90,9 +110,10 @@ function getCode(): string { const page = await browser.newPage(); await page.goto(url, { waitUntil: 'domcontentloaded', + timeout: 300000, }); - await page.exposeFunction('_replLog', (event: eventWithTime) => { + await page.exposeFunction('_replLog', (event) => { events.push(event); }); await page.evaluate(`;${code} @@ -120,14 +141,16 @@ function getCode(): string { }); emitter.once('done', async (shouldReplay) => { + const pages = await browser.pages(); + await Promise.all(pages.map((page) => page.close())); await browser.close(); if (shouldReplay) { - await replay(); + await replay(url, shouldReplay === 'replayWithFakeURL'); } }); } - async function replay() { + async function replay(url, useSpoofedUrl) { const browser = await puppeteer.launch({ headless: false, defaultViewport: { @@ -137,7 +160,12 @@ function getCode(): string { args: ['--start-maximized', '--no-sandbox'], }); const page = await browser.newPage(); - await page.goto('about:blank'); + if (useSpoofedUrl) { + await fakeGoto(page, url); + } else { + await page.goto('about:blank'); + } + await page.addStyleTag({ path: path.resolve(__dirname, '../dist/rrweb.min.css'), }); diff --git a/packages/rrweb/src/entries/all.ts b/packages/rrweb/src/entries/all.ts new file mode 100644 index 0000000000..d67ff92447 --- /dev/null +++ b/packages/rrweb/src/entries/all.ts @@ -0,0 +1,4 @@ +export * from '../index'; +export * from '../packer'; +export * from '../plugins/console/record'; +export * from '../plugins/console/replay'; diff --git a/src/entries/record-pack.ts b/packages/rrweb/src/entries/record-pack.ts similarity index 100% rename from src/entries/record-pack.ts rename to packages/rrweb/src/entries/record-pack.ts diff --git a/src/entries/replay-unpack.ts b/packages/rrweb/src/entries/replay-unpack.ts similarity index 100% rename from src/entries/replay-unpack.ts rename to packages/rrweb/src/entries/replay-unpack.ts diff --git a/src/index.ts b/packages/rrweb/src/index.ts similarity index 67% rename from src/index.ts rename to packages/rrweb/src/index.ts index e57b8e42ba..d8e8dad3ee 100644 --- a/src/index.ts +++ b/packages/rrweb/src/index.ts @@ -1,6 +1,6 @@ import record from './record'; import { Replayer } from './replay'; -import { mirror } from './utils'; +import { _mirror } from './utils'; import * as utils from './utils'; export { @@ -13,4 +13,11 @@ export { const { addCustomEvent } = record; const { freezePage } = record; -export { record, addCustomEvent, freezePage, Replayer, mirror, utils }; +export { + record, + addCustomEvent, + freezePage, + Replayer, + _mirror as mirror, + utils, +}; diff --git a/src/packer/base.ts b/packages/rrweb/src/packer/base.ts similarity index 100% rename from src/packer/base.ts rename to packages/rrweb/src/packer/base.ts diff --git a/src/packer/index.ts b/packages/rrweb/src/packer/index.ts similarity index 100% rename from src/packer/index.ts rename to packages/rrweb/src/packer/index.ts diff --git a/src/packer/pack.ts b/packages/rrweb/src/packer/pack.ts similarity index 100% rename from src/packer/pack.ts rename to packages/rrweb/src/packer/pack.ts diff --git a/src/packer/unpack.ts b/packages/rrweb/src/packer/unpack.ts similarity index 100% rename from src/packer/unpack.ts rename to packages/rrweb/src/packer/unpack.ts diff --git a/packages/rrweb/src/plugins/console/record/error-stack-parser.ts b/packages/rrweb/src/plugins/console/record/error-stack-parser.ts new file mode 100644 index 0000000000..d5244b9832 --- /dev/null +++ b/packages/rrweb/src/plugins/console/record/error-stack-parser.ts @@ -0,0 +1,259 @@ +// tslint:disable +/** + * Class StackFrame is a fork of https://github.com/stacktracejs/stackframe/blob/master/stackframe.js + * I fork it because: + * 1. There are some build issues when importing this package. + * 2. Rewrites into typescript give us a better type interface. + * 3. StackFrame contains some functions we don't need. + */ +export class StackFrame { + private fileName: string; + private functionName: string; + private lineNumber?: number; + private columnNumber?: number; + + constructor(obj: { + fileName?: string; + functionName?: string; + lineNumber?: number; + columnNumber?: number; + }) { + this.fileName = obj.fileName || ''; + this.functionName = obj.functionName || ''; + this.lineNumber = obj.lineNumber; + this.columnNumber = obj.columnNumber; + } + + toString() { + const lineNumber = this.lineNumber || ''; + const columnNumber = this.columnNumber || ''; + if (this.functionName) { + return ( + this.functionName + + ' (' + + this.fileName + + ':' + + lineNumber + + ':' + + columnNumber + + ')' + ); + } + return this.fileName + ':' + lineNumber + ':' + columnNumber; + } +} + +/** + * ErrorStackParser is a fork of https://github.com/stacktracejs/error-stack-parser/blob/master/error-stack-parser.js + * I fork it because: + * 1. There are some build issues when importing this package. + * 2. Rewrites into typescript give us a better type interface. + */ +const FIREFOX_SAFARI_STACK_REGEXP = /(^|@)\S+:\d+/; +const CHROME_IE_STACK_REGEXP = /^\s*at .*(\S+:\d+|\(native\))/m; +const SAFARI_NATIVE_CODE_REGEXP = /^(eval@)?(\[native code])?$/; +export const ErrorStackParser = { + /** + * Given an Error object, extract the most information from it. + * + * @param {Error} error object + * @return {Array} of StackFrames + */ + parse: function (error: Error): StackFrame[] { + // https://github.com/rrweb-io/rrweb/issues/782 + if (!error) { + return []; + } + if ( + // @ts-ignore + typeof error.stacktrace !== 'undefined' || + // @ts-ignore + typeof error['opera#sourceloc'] !== 'undefined' + ) { + return this.parseOpera( + error as { + stacktrace?: string; + message: string; + stack?: string; + }, + ); + } else if (error.stack && error.stack.match(CHROME_IE_STACK_REGEXP)) { + return this.parseV8OrIE(error as { stack: string }); + } else if (error.stack) { + return this.parseFFOrSafari(error as { stack: string }); + } else { + throw new Error('Cannot parse given Error object'); + } + }, + // Separate line and column numbers from a string of the form: (URI:Line:Column) + extractLocation: function (urlLike: string) { + // Fail-fast but return locations like "(native)" + if (urlLike.indexOf(':') === -1) { + return [urlLike]; + } + + const regExp = /(.+?)(?::(\d+))?(?::(\d+))?$/; + const parts = regExp.exec(urlLike.replace(/[()]/g, '')); + if (!parts) throw new Error(`Cannot parse given url: ${urlLike}`); + return [parts[1], parts[2] || undefined, parts[3] || undefined]; + }, + parseV8OrIE: function (error: { stack: string }) { + const filtered = error.stack.split('\n').filter(function (line) { + return !!line.match(CHROME_IE_STACK_REGEXP); + }, this); + + return filtered.map(function (line) { + if (line.indexOf('(eval ') > -1) { + // Throw away eval information until we implement stacktrace.js/stackframe#8 + line = line + .replace(/eval code/g, 'eval') + .replace(/(\(eval at [^()]*)|(\),.*$)/g, ''); + } + let sanitizedLine = line.replace(/^\s+/, '').replace(/\(eval code/g, '('); + + // capture and preseve the parenthesized location "(/foo/my bar.js:12:87)" in + // case it has spaces in it, as the string is split on \s+ later on + const location = sanitizedLine.match(/ (\((.+):(\d+):(\d+)\)$)/); + + // remove the parenthesized location from the line, if it was matched + sanitizedLine = location + ? sanitizedLine.replace(location[0], '') + : sanitizedLine; + + const tokens = sanitizedLine.split(/\s+/).slice(1); + // if a location was matched, pass it to extractLocation() otherwise pop the last token + const locationParts = this.extractLocation( + location ? location[1] : tokens.pop(), + ); + const functionName = tokens.join(' ') || undefined; + const fileName = + ['eval', ''].indexOf(locationParts[0]) > -1 + ? undefined + : locationParts[0]; + + return new StackFrame({ + functionName, + fileName, + lineNumber: locationParts[1], + columnNumber: locationParts[2], + }); + }, this); + }, + parseFFOrSafari: function (error: { stack: string }) { + const filtered = error.stack.split('\n').filter(function (line) { + return !line.match(SAFARI_NATIVE_CODE_REGEXP); + }, this); + + return filtered.map(function (line) { + // Throw away eval information until we implement stacktrace.js/stackframe#8 + if (line.indexOf(' > eval') > -1) { + line = line.replace( + / line (\d+)(?: > eval line \d+)* > eval:\d+:\d+/g, + ':$1', + ); + } + + if (line.indexOf('@') === -1 && line.indexOf(':') === -1) { + // Safari eval frames only have function names and nothing else + return new StackFrame({ + functionName: line, + }); + } else { + const functionNameRegex = /((.*".+"[^@]*)?[^@]*)(?:@)/; + const matches = line.match(functionNameRegex); + const functionName = matches && matches[1] ? matches[1] : undefined; + const locationParts = this.extractLocation( + line.replace(functionNameRegex, ''), + ); + + return new StackFrame({ + functionName, + fileName: locationParts[0], + lineNumber: locationParts[1], + columnNumber: locationParts[2], + }); + } + }, this); + }, + parseOpera: function (e: { + stacktrace?: string; + message: string; + stack?: string; + }): StackFrame[] { + if ( + !e.stacktrace || + (e.message.indexOf('\n') > -1 && + e.message.split('\n').length > e.stacktrace.split('\n').length) + ) { + return this.parseOpera9(e as { message: string }); + } else if (!e.stack) { + return this.parseOpera10(e as { stacktrace: string }); + } else { + return this.parseOpera11(e as { stack: string }); + } + }, + parseOpera9: function (e: { message: string }) { + const lineRE = /Line (\d+).*script (?:in )?(\S+)/i; + const lines = e.message.split('\n'); + const result = []; + + for (let i = 2, len = lines.length; i < len; i += 2) { + const match = lineRE.exec(lines[i]); + if (match) { + result.push( + new StackFrame({ + fileName: match[2], + lineNumber: parseFloat(match[1]), + }), + ); + } + } + + return result; + }, + parseOpera10: function (e: { stacktrace: string }) { + const lineRE = /Line (\d+).*script (?:in )?(\S+)(?:: In function (\S+))?$/i; + const lines = e.stacktrace.split('\n'); + const result = []; + + for (let i = 0, len = lines.length; i < len; i += 2) { + const match = lineRE.exec(lines[i]); + if (match) { + result.push( + new StackFrame({ + functionName: match[3] || undefined, + fileName: match[2], + lineNumber: parseFloat(match[1]), + }), + ); + } + } + + return result; + }, + // Opera 10.65+ Error.stack very similar to FF/Safari + parseOpera11: function (error: { stack: string }) { + const filtered = error.stack.split('\n').filter(function (line) { + return ( + !!line.match(FIREFOX_SAFARI_STACK_REGEXP) && + !line.match(/^Error created at/) + ); + }, this); + + return filtered.map(function (line: string) { + const tokens = line.split('@'); + const locationParts = this.extractLocation(tokens.pop()); + const functionCall = tokens.shift() || ''; + const functionName = + functionCall + .replace(//, '$2') + .replace(/\([^)]*\)/g, '') || undefined; + return new StackFrame({ + functionName, + fileName: locationParts[0], + lineNumber: locationParts[1], + columnNumber: locationParts[2], + }); + }, this); + }, +}; diff --git a/packages/rrweb/src/plugins/console/record/index.ts b/packages/rrweb/src/plugins/console/record/index.ts new file mode 100644 index 0000000000..55b81d0f95 --- /dev/null +++ b/packages/rrweb/src/plugins/console/record/index.ts @@ -0,0 +1,207 @@ +import { listenerHandler, RecordPlugin, IWindow } from '../../../types'; +import { patch } from '../../../utils'; +import { ErrorStackParser, StackFrame } from './error-stack-parser'; +import { stringify } from './stringify'; + +export type StringifyOptions = { + // limit of string length + stringLengthLimit?: number; + /** + * limit of number of keys in an object + * if an object contains more keys than this limit, we would call its toString function directly + */ + numOfKeysLimit: number; + /** + * limit number of depth in an object + * if an object is too deep, toString process may cause browser OOM + */ + depthOfLimit: number; +}; + +type LogRecordOptions = { + level?: LogLevel[]; + lengthThreshold?: number; + stringifyOptions?: StringifyOptions; + logger?: Logger | 'console'; +}; + +const defaultLogOptions: LogRecordOptions = { + level: [ + 'assert', + 'clear', + 'count', + 'countReset', + 'debug', + 'dir', + 'dirxml', + 'error', + 'group', + 'groupCollapsed', + 'groupEnd', + 'info', + 'log', + 'table', + 'time', + 'timeEnd', + 'timeLog', + 'trace', + 'warn', + ], + lengthThreshold: 1000, + logger: 'console', +}; + +export type LogData = { + level: LogLevel; + trace: string[]; + payload: string[]; +}; + +type logCallback = (p: LogData) => void; + +export type LogLevel = + | 'assert' + | 'clear' + | 'count' + | 'countReset' + | 'debug' + | 'dir' + | 'dirxml' + | 'error' + | 'group' + | 'groupCollapsed' + | 'groupEnd' + | 'info' + | 'log' + | 'table' + | 'time' + | 'timeEnd' + | 'timeLog' + | 'trace' + | 'warn'; + +/* fork from interface Console */ +// all kinds of console functions +export type Logger = { + assert?: typeof console.assert; + clear?: typeof console.clear; + count?: typeof console.count; + countReset?: typeof console.countReset; + debug?: typeof console.debug; + dir?: typeof console.dir; + dirxml?: typeof console.dirxml; + error?: typeof console.error; + group?: typeof console.group; + groupCollapsed?: typeof console.groupCollapsed; + groupEnd?: () => void; + info?: typeof console.info; + log?: typeof console.log; + table?: typeof console.table; + time?: typeof console.time; + timeEnd?: typeof console.timeEnd; + timeLog?: typeof console.timeLog; + trace?: typeof console.trace; + warn?: typeof console.warn; +}; + +function initLogObserver( + cb: logCallback, + win: IWindow, // top window or in an iframe + logOptions: LogRecordOptions, +): listenerHandler { + const loggerType = logOptions.logger; + if (!loggerType) { + return () => {}; + } + let logger: Logger; + if (typeof loggerType === 'string') { + logger = win[loggerType]; + } else { + logger = loggerType; + } + let logCount = 0; + const cancelHandlers: listenerHandler[] = []; + // add listener to thrown errors + if (logOptions.level!.includes('error')) { + if (window) { + const errorHandler = (event: ErrorEvent) => { + const { message, error } = event; + const trace: string[] = ErrorStackParser.parse( + error, + ).map((stackFrame: StackFrame) => stackFrame.toString()); + const payload = [stringify(message, logOptions.stringifyOptions)]; + cb({ + level: 'error', + trace, + payload, + }); + }; + window.addEventListener('error', errorHandler); + cancelHandlers.push(() => { + if (window) window.removeEventListener('error', errorHandler); + }); + } + } + for (const levelType of logOptions.level!) { + cancelHandlers.push(replace(logger, levelType)); + } + return () => { + cancelHandlers.forEach((h) => h()); + }; + + /** + * replace the original console function and record logs + * @param logger the logger object such as Console + * @param level the name of log function to be replaced + */ + function replace(_logger: Logger, level: LogLevel) { + if (!_logger[level]) { + return () => {}; + } + // replace the logger.{level}. return a restore function + return patch(_logger, level, (original) => { + return (...args: Array) => { + original.apply(this, args); + try { + const trace = ErrorStackParser.parse(new Error()) + .map((stackFrame: StackFrame) => stackFrame.toString()) + .splice(1); // splice(1) to omit the hijacked log function + const payload = args.map((s) => + stringify(s, logOptions.stringifyOptions), + ); + logCount++; + if (logCount < logOptions.lengthThreshold!) { + cb({ + level, + trace, + payload, + }); + } else if (logCount === logOptions.lengthThreshold) { + // notify the user + cb({ + level: 'warn', + trace: [], + payload: [ + stringify('The number of log records reached the threshold.'), + ], + }); + } + } catch (error) { + original('rrweb logger error:', error, ...args); + } + }; + }); + } +} + +export const PLUGIN_NAME = 'rrweb/console@1'; + +export const getRecordConsolePlugin: ( + options?: LogRecordOptions, +) => RecordPlugin = (options) => ({ + name: PLUGIN_NAME, + observer: initLogObserver, + options: options + ? Object.assign({}, defaultLogOptions, options) + : defaultLogOptions, +}); diff --git a/src/record/stringify.ts b/packages/rrweb/src/plugins/console/record/stringify.ts similarity index 73% rename from src/record/stringify.ts rename to packages/rrweb/src/plugins/console/record/stringify.ts index b1c69b81b9..3804d48e88 100644 --- a/src/record/stringify.ts +++ b/packages/rrweb/src/plugins/console/record/stringify.ts @@ -4,7 +4,7 @@ * */ -import { StringifyOptions } from '../types'; +import { StringifyOptions } from './index'; /** * transfer the node path in Event to string @@ -48,6 +48,31 @@ function pathToSelector(node: HTMLElement): string | '' { return path; } +/** + * judge is object + */ +function isObject(obj: any): boolean { + return Object.prototype.toString.call(obj) === '[object Object]'; +} + +/** + * judge the object's depth + */ +function isObjTooDeep(obj: any, limit: number): boolean { + if (limit === 0) { + return true; + } + + const keys = Object.keys(obj); + for (const key of keys) { + if (isObject(obj[key]) && isObjTooDeep(obj[key], limit - 1)) { + return true; + } + } + + return false; +} + /** * stringify any js object * @param obj the object to stringify @@ -58,6 +83,7 @@ export function stringify( ): string { const options: StringifyOptions = { numOfKeysLimit: 50, + depthOfLimit: 4, }; Object.assign(options, stringifyOptions); const stack: any[] = []; @@ -89,7 +115,7 @@ export function stringify( if (value === null || value === undefined) { return value; } - if (shouldToString(value)) { + if (shouldIgnore(value)) { return toString(value); } if (value instanceof Event) { @@ -110,23 +136,37 @@ export function stringify( return value ? value.outerHTML : ''; } return value.nodeName; + } else if (value instanceof Error) { + return value.stack + ? value.stack + '\nEnd of stack for Error object' + : value.name + ': ' + value.message; } return value; }); /** - * whether we should call toString function of this object + * whether we should ignore obj's info and call toString() function instead */ - function shouldToString(_obj: object): boolean { - if ( - typeof _obj === 'object' && - Object.keys(_obj).length > options.numOfKeysLimit - ) { + function shouldIgnore(_obj: object): boolean { + // outof keys limit + if (isObject(_obj) && Object.keys(_obj).length > options.numOfKeysLimit) { return true; } + + // is function if (typeof _obj === 'function') { return true; } + + /** + * judge object's depth to avoid browser's OOM + * + * issues: https://github.com/rrweb-io/rrweb/issues/653 + */ + if (isObject(_obj) && isObjTooDeep(_obj, options.depthOfLimit)) { + return true; + } + return false; } diff --git a/packages/rrweb/src/plugins/console/replay/index.ts b/packages/rrweb/src/plugins/console/replay/index.ts new file mode 100644 index 0000000000..372a770a40 --- /dev/null +++ b/packages/rrweb/src/plugins/console/replay/index.ts @@ -0,0 +1,144 @@ +import { LogLevel, LogData, PLUGIN_NAME } from '../record'; +import { + eventWithTime, + EventType, + IncrementalSource, + ReplayPlugin, +} from '../../../types'; + +/** + * define an interface to replay log records + * (data: logData) => void> function to display the log data + */ +type ReplayLogger = Partial void>>; + +type LogReplayConfig = { + level?: LogLevel[]; + replayLogger?: ReplayLogger; +}; + +const ORIGINAL_ATTRIBUTE_NAME = '__rrweb_original__'; +type PatchedConsoleLog = { + [ORIGINAL_ATTRIBUTE_NAME]: typeof console.log; +}; + +const defaultLogConfig: LogReplayConfig = { + level: [ + 'assert', + 'clear', + 'count', + 'countReset', + 'debug', + 'dir', + 'dirxml', + 'error', + 'group', + 'groupCollapsed', + 'groupEnd', + 'info', + 'log', + 'table', + 'time', + 'timeEnd', + 'timeLog', + 'trace', + 'warn', + ], + replayLogger: undefined, +}; + +class LogReplayPlugin { + private config: LogReplayConfig; + + constructor(config?: LogReplayConfig) { + this.config = Object.assign(defaultLogConfig, config); + } + + /** + * generate a console log replayer which implement the interface ReplayLogger + */ + public getConsoleLogger(): ReplayLogger { + const replayLogger: ReplayLogger = {}; + for (const level of this.config.level!) { + if (level === 'trace') { + replayLogger[level] = (data: LogData) => { + const logger = ((console.log as unknown) as PatchedConsoleLog)[ + ORIGINAL_ATTRIBUTE_NAME + ] + ? ((console.log as unknown) as PatchedConsoleLog)[ + ORIGINAL_ATTRIBUTE_NAME + ] + : console.log; + logger( + ...data.payload.map((s) => JSON.parse(s)), + this.formatMessage(data), + ); + }; + } else { + replayLogger[level] = (data: LogData) => { + const logger = ((console[level] as unknown) as PatchedConsoleLog)[ + ORIGINAL_ATTRIBUTE_NAME + ] + ? ((console[level] as unknown) as PatchedConsoleLog)[ + ORIGINAL_ATTRIBUTE_NAME + ] + : console[level]; + logger( + ...data.payload.map((s) => JSON.parse(s)), + this.formatMessage(data), + ); + }; + } + } + return replayLogger; + } + + /** + * format the trace data to a string + * @param data the log data + */ + private formatMessage(data: LogData): string { + if (data.trace.length === 0) { + return ''; + } + const stackPrefix = '\n\tat '; + let result = stackPrefix; + result += data.trace.join(stackPrefix); + return result; + } +} + +export const getReplayConsolePlugin: ( + options?: LogReplayConfig, +) => ReplayPlugin = (options) => { + const replayLogger = + options?.replayLogger || new LogReplayPlugin(options).getConsoleLogger(); + + return { + handler(event: eventWithTime, _isSync, context) { + let logData: LogData | null = null; + if ( + event.type === EventType.IncrementalSnapshot && + event.data.source === (IncrementalSource.Log as IncrementalSource) + ) { + logData = (event.data as unknown) as LogData; + } else if ( + event.type === EventType.Plugin && + event.data.plugin === PLUGIN_NAME + ) { + logData = event.data.payload as LogData; + } + if (logData) { + try { + if (typeof replayLogger[logData.level] === 'function') { + replayLogger[logData.level]!(logData); + } + } catch (error) { + if (context.replayer.config.showWarning) { + console.warn(error); + } + } + } + }, + }; +}; diff --git a/packages/rrweb/src/plugins/sequential-id/record/index.ts b/packages/rrweb/src/plugins/sequential-id/record/index.ts new file mode 100644 index 0000000000..a439831191 --- /dev/null +++ b/packages/rrweb/src/plugins/sequential-id/record/index.ts @@ -0,0 +1,31 @@ +import { RecordPlugin } from '../../../types'; + +export type SequentialIdOptions = { + key: string; +}; + +const defaultOptions: SequentialIdOptions = { + key: '_sid', +}; + +export const PLUGIN_NAME = 'rrweb/sequential-id@1'; + +export const getRecordSequentialIdPlugin: ( + options?: Partial, +) => RecordPlugin = (options) => { + const _options = options + ? Object.assign({}, defaultOptions, options) + : defaultOptions; + let id = 0; + + return { + name: PLUGIN_NAME, + eventProcessor(event) { + Object.assign(event, { + [_options.key]: ++id, + }); + return event; + }, + options: _options, + }; +}; diff --git a/packages/rrweb/src/plugins/sequential-id/replay/index.ts b/packages/rrweb/src/plugins/sequential-id/replay/index.ts new file mode 100644 index 0000000000..852a02cf36 --- /dev/null +++ b/packages/rrweb/src/plugins/sequential-id/replay/index.ts @@ -0,0 +1,39 @@ +import type { SequentialIdOptions } from '../record'; +import { ReplayPlugin, eventWithTime } from '../../../types'; + +type Options = SequentialIdOptions & { + warnOnMissingId: boolean; +}; + +const defaultOptions: Options = { + key: '_sid', + warnOnMissingId: true, +}; + +export const getReplaySequentialIdPlugin: ( + options?: Partial, +) => ReplayPlugin = (options) => { + const { key, warnOnMissingId } = options + ? Object.assign({}, defaultOptions, options) + : defaultOptions; + let currentId = 1; + + return { + handler(event: eventWithTime) { + if (key in event) { + const id = ((event as unknown) as Record)[key]; + if (id !== currentId) { + console.error( + `[sequential-id-plugin]: expect to get an id with value "${currentId}", but got "${id}"`, + ); + } else { + currentId++; + } + } else if (warnOnMissingId) { + console.warn( + `[sequential-id-plugin]: failed to get id in key: "${key}"`, + ); + } + }, + }; +}; diff --git a/src/record/iframe-manager.ts b/packages/rrweb/src/record/iframe-manager.ts similarity index 100% rename from src/record/iframe-manager.ts rename to packages/rrweb/src/record/iframe-manager.ts diff --git a/src/record/index.ts b/packages/rrweb/src/record/index.ts similarity index 81% rename from src/record/index.ts rename to packages/rrweb/src/record/index.ts index 57efc29706..418c239343 100644 --- a/src/record/index.ts +++ b/packages/rrweb/src/record/index.ts @@ -1,13 +1,13 @@ import { snapshot, MaskInputOptions, SlimDOMOptions } from 'rrweb-snapshot'; import { initObservers, mutationBuffers } from './observer'; import { - mirror, on, getWindowWidth, getWindowHeight, polyfill, isIframeINode, hasShadowRoot, + createMirror, } from '../utils'; import { EventType, @@ -16,11 +16,13 @@ import { recordOptions, IncrementalSource, listenerHandler, - LogRecordOptions, mutationCallbackParam, + scrollCallback, + canvasMutationParam, } from '../types'; import { IframeManager } from './iframe-manager'; import { ShadowDomManager } from './shadow-dom-manager'; +import { CanvasManager } from './observers/canvas/canvas-manager'; function wrapEvent(e: event): eventWithTime { return { @@ -33,6 +35,7 @@ let wrappedEmit!: (e: eventWithTime, isCheckout?: boolean) => void; let takeFullSnapshot!: (isCheckout?: boolean) => void; +const mirror = createMirror(); function record( options: recordOptions = {}, ): listenerHandler | undefined { @@ -56,8 +59,11 @@ function record( sampling = {}, mousemoveWait, recordCanvas = false, + userTriggeredOnInput = false, collectFonts = false, - recordLog = false, + inlineImages = false, + plugins, + keepIframeSrcFn = () => false, } = options; // runtime checks for user options if (!emit) { @@ -87,10 +93,11 @@ function record( week: true, textarea: true, select: true, + password: true, } : _maskInputOptions !== undefined ? _maskInputOptions - : {}; + : { password: true }; const slimDOMOptions: SlimDOMOptions = _slimDOMOptions === true || _slimDOMOptions === 'all' @@ -111,42 +118,22 @@ function record( : _slimDOMOptions ? _slimDOMOptions : {}; - const defaultLogOptions: LogRecordOptions = { - level: [ - 'assert', - 'clear', - 'count', - 'countReset', - 'debug', - 'dir', - 'dirxml', - 'error', - 'group', - 'groupCollapsed', - 'groupEnd', - 'info', - 'log', - 'table', - 'time', - 'timeEnd', - 'timeLog', - 'trace', - 'warn', - ], - lengthThreshold: 1000, - logger: console, - }; - - const logOptions: LogRecordOptions = recordLog - ? recordLog === true - ? defaultLogOptions - : Object.assign({}, defaultLogOptions, recordLog) - : {}; polyfill(); let lastFullSnapshotEvent: eventWithTime; let incrementalSnapshotCount = 0; + const eventProcessor = (e: eventWithTime): T => { + for (const plugin of plugins || []) { + if (plugin.eventProcessor) { + e = plugin.eventProcessor(e); + } + } + if (packFn) { + e = (packFn(e) as unknown) as eventWithTime; + } + return (e as unknown) as T; + }; wrappedEmit = (e: eventWithTime, isCheckout?: boolean) => { if ( mutationBuffers[0]?.isFrozen() && @@ -161,12 +148,12 @@ function record( mutationBuffers.forEach((buf) => buf.unfreeze()); } - emit(((packFn ? packFn(e) : e) as unknown) as T, isCheckout); + emit(eventProcessor(e), isCheckout); if (e.type === EventType.FullSnapshot) { lastFullSnapshotEvent = e; incrementalSnapshotCount = 0; } else if (e.type === EventType.IncrementalSnapshot) { - // attch iframe should be considered as full snapshot + // attach iframe should be considered as full snapshot if ( e.data.source === IncrementalSource.Mutation && e.data.isAttachIframe @@ -197,13 +184,42 @@ function record( }), ); }; + const wrappedScrollEmit: scrollCallback = (p) => + wrappedEmit( + wrapEvent({ + type: EventType.IncrementalSnapshot, + data: { + source: IncrementalSource.Scroll, + ...p, + }, + }), + ); + const wrappedCanvasMutationEmit = (p: canvasMutationParam) => + wrappedEmit( + wrapEvent({ + type: EventType.IncrementalSnapshot, + data: { + source: IncrementalSource.CanvasMutation, + ...p, + }, + }), + ); const iframeManager = new IframeManager({ mutationCb: wrappedMutationEmit, }); + const canvasManager = new CanvasManager({ + recordCanvas, + mutationCb: wrappedCanvasMutationEmit, + win: window, + blockClass, + mirror, + }); + const shadowDomManager = new ShadowDomManager({ mutationCb: wrappedMutationEmit, + scrollCb: wrappedScrollEmit, bypassOptions: { blockClass, blockSelector, @@ -212,10 +228,15 @@ function record( inlineStylesheet, maskInputOptions, maskTextFn, + maskInputFn, recordCanvas, + inlineImages, + sampling, slimDOMOptions, iframeManager, + canvasManager, }, + mirror, }); takeFullSnapshot = (isCheckout = false) => { @@ -242,6 +263,7 @@ function record( maskTextFn, slimDOM: slimDOMOptions, recordCanvas, + inlineImages, onSerialize: (n) => { if (isIframeINode(n)) { iframeManager.addIframe(n); @@ -252,7 +274,11 @@ function record( }, onIframeLoad: (iframe, childSn) => { iframeManager.attachIframe(iframe, childSn); + shadowDomManager.observeAttachShadow( + (iframe as Node) as HTMLIFrameElement, + ); }, + keepIframeSrcFn, }); if (!node) { @@ -324,16 +350,7 @@ function record( }, }), ), - scrollCb: (p) => - wrappedEmit( - wrapEvent({ - type: EventType.IncrementalSnapshot, - data: { - source: IncrementalSource.Scroll, - ...p, - }, - }), - ), + scrollCb: wrappedScrollEmit, viewportResizeCb: (d) => wrappedEmit( wrapEvent({ @@ -374,16 +391,17 @@ function record( }, }), ), - canvasMutationCb: (p) => + styleDeclarationCb: (r) => wrappedEmit( wrapEvent({ type: EventType.IncrementalSnapshot, data: { - source: IncrementalSource.CanvasMutation, - ...p, + source: IncrementalSource.StyleDeclaration, + ...r, }, }), ), + canvasMutationCb: wrappedCanvasMutationEmit, fontCb: (p) => wrappedEmit( wrapEvent({ @@ -394,16 +412,6 @@ function record( }, }), ), - logCb: (p) => - wrappedEmit( - wrapEvent({ - type: EventType.IncrementalSnapshot, - data: { - source: IncrementalSource.Log, - ...p, - }, - }), - ), blockClass, ignoreClass, maskTextClass, @@ -412,15 +420,35 @@ function record( inlineStylesheet, sampling, recordCanvas, + inlineImages, + userTriggeredOnInput, collectFonts, doc, maskInputFn, maskTextFn, - logOptions, blockSelector, slimDOMOptions, + mirror, iframeManager, shadowDomManager, + canvasManager, + plugins: + plugins + ?.filter((p) => p.observer) + ?.map((p) => ({ + observer: p.observer!, + options: p.options, + callback: (payload: object) => + wrappedEmit( + wrapEvent({ + type: EventType.Plugin, + data: { + plugin: p.name, + payload, + }, + }), + ), + })) || [], }, hooks, ); @@ -491,4 +519,6 @@ record.takeFullSnapshot = (isCheckout?: boolean) => { takeFullSnapshot(isCheckout); }; +record.mirror = mirror; + export default record; diff --git a/src/record/mutation.ts b/packages/rrweb/src/record/mutation.ts similarity index 66% rename from src/record/mutation.ts rename to packages/rrweb/src/record/mutation.ts index 4b65e79656..1e4a8fbe07 100644 --- a/src/record/mutation.ts +++ b/packages/rrweb/src/record/mutation.ts @@ -2,33 +2,30 @@ import { INode, serializeNodeWithId, transformAttribute, - MaskInputOptions, - SlimDOMOptions, IGNORED_NODE, isShadowRoot, needMaskingText, + maskInputValue, } from 'rrweb-snapshot'; import { mutationRecord, - blockClass, - maskTextClass, - mutationCallBack, textCursor, attributeCursor, removedNodeMutation, addedNodeMutation, - MaskTextFn, + Mirror, + styleAttributeValue, + observerParam, + MutationBufferParam, + Optional, } from '../types'; import { - mirror, isBlocked, isAncestorRemoved, isIgnored, isIframeINode, hasShadowRoot, } from '../utils'; -import { IframeManager } from './iframe-manager'; -import { ShadowDomManager } from './shadow-dom-manager'; type DoubleLinkedListNode = { previous: DoubleLinkedListNode | null; @@ -113,7 +110,7 @@ class DoubleLinkedList { } } if (n.__ln) { - delete n.__ln; + delete (n as Optional).__ln; } this.length--; } @@ -159,57 +156,57 @@ export default class MutationBuffer { private movedSet = new Set(); private droppedSet = new Set(); - private emissionCallback: mutationCallBack; - private blockClass: blockClass; - private blockSelector: string | null; - private maskTextClass: maskTextClass; - private maskTextSelector: string | null; - private inlineStylesheet: boolean; - private maskInputOptions: MaskInputOptions; - private maskTextFn: MaskTextFn | undefined; - private recordCanvas: boolean; - private slimDOMOptions: SlimDOMOptions; - private doc: Document; - - private iframeManager: IframeManager; - private shadowDomManager: ShadowDomManager; - - public init( - cb: mutationCallBack, - blockClass: blockClass, - blockSelector: string | null, - maskTextClass: maskTextClass, - maskTextSelector: string | null, - inlineStylesheet: boolean, - maskInputOptions: MaskInputOptions, - maskTextFn: MaskTextFn | undefined, - recordCanvas: boolean, - slimDOMOptions: SlimDOMOptions, - doc: Document, - iframeManager: IframeManager, - shadowDomManager: ShadowDomManager, - ) { - this.blockClass = blockClass; - this.blockSelector = blockSelector; - this.maskTextClass = maskTextClass; - this.maskTextSelector = maskTextSelector; - this.inlineStylesheet = inlineStylesheet; - this.maskInputOptions = maskInputOptions; - this.maskTextFn = maskTextFn; - this.recordCanvas = recordCanvas; - this.slimDOMOptions = slimDOMOptions; - this.emissionCallback = cb; - this.doc = doc; - this.iframeManager = iframeManager; - this.shadowDomManager = shadowDomManager; + private mutationCb: observerParam['mutationCb']; + private blockClass: observerParam['blockClass']; + private blockSelector: observerParam['blockSelector']; + private maskTextClass: observerParam['maskTextClass']; + private maskTextSelector: observerParam['maskTextSelector']; + private inlineStylesheet: observerParam['inlineStylesheet']; + private maskInputOptions: observerParam['maskInputOptions']; + private maskTextFn: observerParam['maskTextFn']; + private maskInputFn: observerParam['maskInputFn']; + private recordCanvas: observerParam['recordCanvas']; + private inlineImages: observerParam['inlineImages']; + private slimDOMOptions: observerParam['slimDOMOptions']; + private doc: observerParam['doc']; + private mirror: observerParam['mirror']; + private iframeManager: observerParam['iframeManager']; + private shadowDomManager: observerParam['shadowDomManager']; + private canvasManager: observerParam['canvasManager']; + + public init(options: MutationBufferParam) { + ([ + 'mutationCb', + 'blockClass', + 'blockSelector', + 'maskTextClass', + 'maskTextSelector', + 'inlineStylesheet', + 'maskInputOptions', + 'maskTextFn', + 'maskInputFn', + 'recordCanvas', + 'inlineImages', + 'slimDOMOptions', + 'doc', + 'mirror', + 'iframeManager', + 'shadowDomManager', + 'canvasManager', + ] as const).forEach((key) => { + // just a type trick, the runtime result is correct + this[key] = options[key] as never; + }); } public freeze() { this.frozen = true; + this.canvasManager.freeze(); } public unfreeze() { this.frozen = false; + this.canvasManager.unfreeze(); this.emit(); } @@ -219,16 +216,23 @@ export default class MutationBuffer { public lock() { this.locked = true; + this.canvasManager.lock(); } public unlock() { this.locked = false; + this.canvasManager.unlock(); this.emit(); } + public reset() { + this.shadowDomManager.reset(); + this.canvasManager.reset(); + } + public processMutations = (mutations: mutationRecord[]) => { - mutations.forEach(this.processMutation); - this.emit(); + mutations.forEach(this.processMutation); // adds mutations to the buffer + this.emit(); // clears buffer if not locked/frozen }; public emit = () => { @@ -251,10 +255,7 @@ export default class MutationBuffer { let nextId: number | null = IGNORED_NODE; // slimDOM: ignored while (nextId === IGNORED_NODE) { ns = ns && ns.nextSibling; - nextId = ns && mirror.getId((ns as unknown) as INode); - } - if (nextId === -1 && isBlocked(n.nextSibling, this.blockClass)) { - nextId = null; + nextId = ns && this.mirror.getId((ns as unknown) as INode); } return nextId; }; @@ -262,20 +263,29 @@ export default class MutationBuffer { const shadowHost: Element | null = n.getRootNode ? (n.getRootNode() as ShadowRoot)?.host : null; - const notInDoc = !this.doc.contains(n) && !this.doc.contains(shadowHost); + // If n is in a nested shadow dom. + let rootShadowHost = shadowHost; + while ((rootShadowHost?.getRootNode?.() as ShadowRoot | undefined)?.host) + rootShadowHost = + (rootShadowHost?.getRootNode?.() as ShadowRoot | undefined)?.host || + null; + // ensure shadowHost is a Node, or doc.contains will throw an error + const notInDoc = + !this.doc.contains(n) && + (rootShadowHost === null || !this.doc.contains(rootShadowHost)); if (!n.parentNode || notInDoc) { return; } const parentId = isShadowRoot(n.parentNode) - ? mirror.getId((shadowHost as unknown) as INode) - : mirror.getId((n.parentNode as Node) as INode); + ? this.mirror.getId((shadowHost as unknown) as INode) + : this.mirror.getId((n.parentNode as Node) as INode); const nextId = getNextId(n); if (parentId === -1 || nextId === -1) { return addList.addNode(n); } let sn = serializeNodeWithId(n, { doc: this.doc, - map: mirror.map, + map: this.mirror.map, blockClass: this.blockClass, blockSelector: this.blockSelector, maskTextClass: this.maskTextClass, @@ -284,8 +294,10 @@ export default class MutationBuffer { inlineStylesheet: this.inlineStylesheet, maskInputOptions: this.maskInputOptions, maskTextFn: this.maskTextFn, + maskInputFn: this.maskInputFn, slimDOMOptions: this.slimDOMOptions, recordCanvas: this.recordCanvas, + inlineImages: this.inlineImages, onSerialize: (currentN) => { if (isIframeINode(currentN)) { this.iframeManager.addIframe(currentN); @@ -296,6 +308,9 @@ export default class MutationBuffer { }, onIframeLoad: (iframe, childSn) => { this.iframeManager.attachIframe(iframe, childSn); + this.shadowDomManager.observeAttachShadow( + (iframe as Node) as HTMLIFrameElement, + ); }, }); if (sn) { @@ -308,12 +323,12 @@ export default class MutationBuffer { }; while (this.mapRemoves.length) { - mirror.removeNodeFromMap(this.mapRemoves.shift() as INode); + this.mirror.removeNodeFromMap(this.mapRemoves.shift() as INode); } for (const n of this.movedSet) { if ( - isParentRemoved(this.removes, n) && + isParentRemoved(this.removes, n, this.mirror) && !this.movedSet.has(n.parentNode!) ) { continue; @@ -324,7 +339,7 @@ export default class MutationBuffer { for (const n of this.addedSet) { if ( !isAncestorInSet(this.droppedSet, n) && - !isParentRemoved(this.removes, n) + !isParentRemoved(this.removes, n, this.mirror) ) { pushAdd(n); } else if (isAncestorInSet(this.movedSet, n)) { @@ -338,7 +353,7 @@ export default class MutationBuffer { while (addList.length) { let node: DoubleLinkedListNode | null = null; if (candidate) { - const parentId = mirror.getId( + const parentId = this.mirror.getId( (candidate.value.parentNode as Node) as INode, ); const nextId = getNextId(candidate.value); @@ -349,13 +364,16 @@ export default class MutationBuffer { if (!node) { for (let index = addList.length - 1; index >= 0; index--) { const _node = addList.get(index)!; - const parentId = mirror.getId( - (_node.value.parentNode as Node) as INode, - ); - const nextId = getNextId(_node.value); - if (parentId !== -1 && nextId !== -1) { - node = _node; - break; + // ensure _node is defined before attempting to find value + if (_node) { + const parentId = this.mirror.getId( + (_node.value.parentNode as Node) as INode, + ); + const nextId = getNextId(_node.value); + if (parentId !== -1 && nextId !== -1) { + node = _node; + break; + } } } } @@ -378,18 +396,18 @@ export default class MutationBuffer { const payload = { texts: this.texts .map((text) => ({ - id: mirror.getId(text.node as INode), + id: this.mirror.getId(text.node as INode), value: text.value, })) // text mutation's id was not in the mirror map means the target node has been removed - .filter((text) => mirror.has(text.id)), + .filter((text) => this.mirror.has(text.id)), attributes: this.attributes .map((attribute) => ({ - id: mirror.getId(attribute.node as INode), + id: this.mirror.getId(attribute.node as INode), attributes: attribute.attributes, })) // attribute mutation's id was not in the mirror map means the target node has been removed - .filter((attribute) => mirror.has(attribute.id)), + .filter((attribute) => this.mirror.has(attribute.id)), removes: this.removes, adds, }; @@ -412,7 +430,7 @@ export default class MutationBuffer { this.droppedSet = new Set(); this.movedMap = {}; - this.emissionCallback(payload); + this.mutationCb(payload); }; private processMutation = (m: mutationRecord) => { @@ -440,7 +458,17 @@ export default class MutationBuffer { break; } case 'attributes': { - const value = (m.target as HTMLElement).getAttribute(m.attributeName!); + const target = m.target as HTMLElement; + let value = (m.target as HTMLElement).getAttribute(m.attributeName!); + if (m.attributeName === 'value') { + value = maskInputValue({ + maskInputOptions: this.maskInputOptions, + tagName: (m.target as HTMLElement).tagName, + type: (m.target as HTMLElement).getAttribute('type'), + value, + maskInputFn: this.maskInputFn, + }); + } if (isBlocked(m.target, this.blockClass) || value === m.oldValue) { return; } @@ -454,27 +482,57 @@ export default class MutationBuffer { }; this.attributes.push(item); } - // overwrite attribute if the mutations was triggered in same time - item.attributes[m.attributeName!] = transformAttribute( - this.doc, - (m.target as HTMLElement).tagName, - m.attributeName!, - value!, - ); + if (m.attributeName === 'style') { + const old = this.doc.createElement('span'); + if (m.oldValue) { + old.setAttribute('style', m.oldValue); + } + if ( + item.attributes.style === undefined || + item.attributes.style === null + ) { + item.attributes.style = {}; + } + const styleObj = item.attributes.style as styleAttributeValue; + for (const pname of Array.from(target.style)) { + const newValue = target.style.getPropertyValue(pname); + const newPriority = target.style.getPropertyPriority(pname); + if ( + newValue !== old.style.getPropertyValue(pname) || + newPriority !== old.style.getPropertyPriority(pname) + ) { + if (newPriority === '') { + styleObj[pname] = newValue; + } else { + styleObj[pname] = [newValue, newPriority]; + } + } + } + for (const pname of Array.from(old.style)) { + if (target.style.getPropertyValue(pname) === '') { + // "if not set, returns the empty string" + styleObj[pname] = false; // delete + } + } + } else { + // overwrite attribute if the mutations was triggered in same time + item.attributes[m.attributeName!] = transformAttribute( + this.doc, + (m.target as HTMLElement).tagName, + m.attributeName!, + value!, + ); + } break; } case 'childList': { m.addedNodes.forEach((n) => this.genAdds(n, m.target)); m.removedNodes.forEach((n) => { - const nodeId = mirror.getId(n as INode); + const nodeId = this.mirror.getId(n as INode); const parentId = isShadowRoot(m.target) - ? mirror.getId((m.target.host as unknown) as INode) - : mirror.getId(m.target as INode); - if ( - isBlocked(n, this.blockClass) || - isBlocked(m.target, this.blockClass) || - isIgnored(n) - ) { + ? this.mirror.getId((m.target.host as unknown) as INode) + : this.mirror.getId(m.target as INode); + if (isBlocked(m.target, this.blockClass) || isIgnored(n)) { return; } // removed node has not been serialized yet, just remove it from the Set @@ -489,7 +547,7 @@ export default class MutationBuffer { * newly added node will be serialized without child nodes. * TODO: verify this */ - } else if (isAncestorRemoved(m.target as INode)) { + } else if (isAncestorRemoved(m.target as INode, this.mirror)) { /** * If parent id was not in the mirror map any more, it * means the parent node has already been removed. So @@ -518,9 +576,7 @@ export default class MutationBuffer { }; private genAdds = (n: Node | INode, target?: Node | INode) => { - if (isBlocked(n, this.blockClass)) { - return; - } + // parent was blocked, so we can ignore this node if (target && isBlocked(target, this.blockClass)) { return; } @@ -540,7 +596,11 @@ export default class MutationBuffer { this.addedSet.add(n); this.droppedSet.delete(n); } - n.childNodes.forEach((childN) => this.genAdds(childN)); + + // if this node is blocked `serializeNode` will turn it into a placeholder element + // but we have to remove it's children otherwise they will be added as placeholders too + if (!isBlocked(n, this.blockClass)) + n.childNodes.forEach((childN) => this.genAdds(childN)); }; } @@ -555,7 +615,11 @@ function deepDelete(addsSet: Set, n: Node) { n.childNodes.forEach((childN) => deepDelete(addsSet, childN)); } -function isParentRemoved(removes: removedNodeMutation[], n: Node): boolean { +function isParentRemoved( + removes: removedNodeMutation[], + n: Node, + mirror: Mirror, +): boolean { const { parentNode } = n; if (!parentNode) { return false; @@ -564,7 +628,7 @@ function isParentRemoved(removes: removedNodeMutation[], n: Node): boolean { if (removes.some((r) => r.id === parentId)) { return true; } - return isParentRemoved(removes, parentNode); + return isParentRemoved(removes, parentNode, mirror); } function isAncestorInSet(set: Set, n: Node): boolean { diff --git a/src/record/observer.ts b/packages/rrweb/src/record/observer.ts similarity index 51% rename from src/record/observer.ts rename to packages/rrweb/src/record/observer.ts index cb42c1a728..bbd05cf151 100644 --- a/src/record/observer.ts +++ b/packages/rrweb/src/record/observer.ts @@ -1,7 +1,6 @@ -import { INode, MaskInputOptions, SlimDOMOptions } from 'rrweb-snapshot'; -import { FontFaceDescriptors, FontFaceSet } from 'css-font-loading-module'; +import { INode, MaskInputOptions, maskInputValue } from 'rrweb-snapshot'; +import { FontFaceSet } from 'css-font-loading-module'; import { - mirror, throttle, on, hookSetter, @@ -25,33 +24,24 @@ import { inputValue, inputCallback, hookResetter, - blockClass, - maskTextClass, IncrementalSource, hooksParam, Arguments, mediaInteractionCallback, MediaInteractions, - SamplingStrategy, canvasMutationCallback, fontCallback, fontParam, - MaskInputFn, - MaskTextFn, - logCallback, - LogRecordOptions, - Logger, - LogLevel, + styleDeclarationCallback, + IWindow, + MutationBufferParam, } from '../types'; import MutationBuffer from './mutation'; -import { stringify } from './stringify'; -import { IframeManager } from './iframe-manager'; -import { ShadowDomManager } from './shadow-dom-manager'; -type WindowWithStoredMutationObserver = Window & { +type WindowWithStoredMutationObserver = IWindow & { __rrMutationObserver?: MutationObserver; }; -type WindowWithAngularZone = Window & { +type WindowWithAngularZone = IWindow & { Zone?: { __symbol__?: (key: string) => string; }; @@ -59,40 +49,40 @@ type WindowWithAngularZone = Window & { export const mutationBuffers: MutationBuffer[] = []; +const isCSSGroupingRuleSupported = typeof CSSGroupingRule !== 'undefined'; +const isCSSMediaRuleSupported = typeof CSSMediaRule !== 'undefined'; +const isCSSSupportsRuleSupported = typeof CSSSupportsRule !== 'undefined'; +const isCSSConditionRuleSupported = typeof CSSConditionRule !== 'undefined'; + +// Event.path is non-standard and used in some older browsers +type NonStandardEvent = Omit & { + path: EventTarget[]; +}; + +function getEventTarget(event: Event | NonStandardEvent): EventTarget | null { + try { + if ('composedPath' in event) { + const path = event.composedPath(); + if (path.length) { + return path[0]; + } + } else if ('path' in event && event.path.length) { + return event.path[0]; + } + return event.target; + } catch { + return event.target; + } +} + export function initMutationObserver( - cb: mutationCallBack, - doc: Document, - blockClass: blockClass, - blockSelector: string | null, - maskTextClass: maskTextClass, - maskTextSelector: string | null, - inlineStylesheet: boolean, - maskInputOptions: MaskInputOptions, - maskTextFn: MaskTextFn | undefined, - recordCanvas: boolean, - slimDOMOptions: SlimDOMOptions, - iframeManager: IframeManager, - shadowDomManager: ShadowDomManager, + options: MutationBufferParam, rootEl: Node, ): MutationObserver { const mutationBuffer = new MutationBuffer(); mutationBuffers.push(mutationBuffer); // see mutation.ts for details - mutationBuffer.init( - cb, - blockClass, - blockSelector, - maskTextClass, - maskTextSelector, - inlineStylesheet, - maskInputOptions, - maskTextFn, - recordCanvas, - slimDOMOptions, - doc, - iframeManager, - shadowDomManager, - ); + mutationBuffer.init(options); let mutationObserverCtor = window.MutationObserver || /** @@ -132,11 +122,12 @@ export function initMutationObserver( return observer; } -function initMoveObserver( - cb: mousemoveCallBack, - sampling: SamplingStrategy, - doc: Document, -): listenerHandler { +function initMoveObserver({ + mousemoveCb, + sampling, + doc, + mirror, +}: observerParam): listenerHandler { if (sampling.mousemove === false) { return () => {}; } @@ -158,7 +149,7 @@ function initMoveObserver( | IncrementalSource.Drag, ) => { const totalOffset = Date.now() - timeBaseline!; - cb( + mousemoveCb( positions.map((p) => { p.timeOffset -= totalOffset; return p; @@ -172,7 +163,7 @@ function initMoveObserver( ); const updatePosition = throttle( (evt) => { - const { target } = evt; + const target = getEventTarget(evt); const { clientX, clientY } = isTouchEvent(evt) ? evt.changedTouches[0] : evt; @@ -185,11 +176,13 @@ function initMoveObserver( id: mirror.getId(target as INode), timeOffset: Date.now() - timeBaseline, }); + // it is possible DragEvent is undefined even on devices + // that support event 'drag' wrappedCb( - evt instanceof MouseEvent - ? IncrementalSource.MouseMove - : evt instanceof DragEvent + typeof DragEvent !== 'undefined' && evt instanceof DragEvent ? IncrementalSource.Drag + : evt instanceof MouseEvent + ? IncrementalSource.MouseMove : IncrementalSource.TouchMove, ); }, @@ -208,12 +201,13 @@ function initMoveObserver( }; } -function initMouseInteractionObserver( - cb: mouseInteractionCallBack, - doc: Document, - blockClass: blockClass, - sampling: SamplingStrategy, -): listenerHandler { +function initMouseInteractionObserver({ + mouseInteractionCb, + doc, + mirror, + blockClass, + sampling, +}: observerParam): listenerHandler { if (sampling.mouseInteraction === false) { return () => {}; } @@ -226,16 +220,17 @@ function initMouseInteractionObserver( const handlers: listenerHandler[] = []; const getHandler = (eventKey: keyof typeof MouseInteractions) => { return (event: MouseEvent | TouchEvent) => { - if (isBlocked(event.target as Node, blockClass)) { + const target = getEventTarget(event) as Node; + if (isBlocked(target as Node, blockClass)) { return; } const e = isTouchEvent(event) ? event.changedTouches[0] : event; if (!e) { return; } - const id = mirror.getId(event.target as INode); + const id = mirror.getId(target as INode); const { clientX, clientY } = e; - cb({ + mouseInteractionCb({ type: MouseInteractions[eventKey], id, x: clientX, @@ -260,45 +255,50 @@ function initMouseInteractionObserver( }; } -function initScrollObserver( - cb: scrollCallback, - doc: Document, - blockClass: blockClass, - sampling: SamplingStrategy, -): listenerHandler { +export function initScrollObserver({ + scrollCb, + doc, + mirror, + blockClass, + sampling, +}: Pick< + observerParam, + 'scrollCb' | 'doc' | 'mirror' | 'blockClass' | 'sampling' +>): listenerHandler { const updatePosition = throttle((evt) => { - if (!evt.target || isBlocked(evt.target as Node, blockClass)) { + const target = getEventTarget(evt); + if (!target || isBlocked(target as Node, blockClass)) { return; } - const id = mirror.getId(evt.target as INode); - if (evt.target === doc) { + const id = mirror.getId(target as INode); + if (target === doc) { const scrollEl = (doc.scrollingElement || doc.documentElement)!; - cb({ + scrollCb({ id, x: scrollEl.scrollLeft, y: scrollEl.scrollTop, }); } else { - cb({ + scrollCb({ id, - x: (evt.target as HTMLElement).scrollLeft, - y: (evt.target as HTMLElement).scrollTop, + x: (target as HTMLElement).scrollLeft, + y: (target as HTMLElement).scrollTop, }); } }, sampling.scroll || 100); - return on('scroll', updatePosition); + return on('scroll', updatePosition, doc); } -function initViewportResizeObserver( - cb: viewportResizeCallback, -): listenerHandler { +function initViewportResizeObserver({ + viewportResizeCb, +}: observerParam): listenerHandler { let lastH = -1; let lastW = -1; const updateDimension = throttle(() => { const height = getWindowHeight(); const width = getWindowWidth(); if (lastH !== height || lastW !== width) { - cb({ + viewportResizeCb({ width: Number(width), height: Number(height), }); @@ -309,21 +309,37 @@ function initViewportResizeObserver( return on('resize', updateDimension, window); } +function wrapEventWithUserTriggeredFlag( + v: inputValue, + enable: boolean, +): inputValue { + const value = { ...v }; + if (!enable) delete value.userTriggered; + return value; +} + export const INPUT_TAGS = ['INPUT', 'TEXTAREA', 'SELECT']; const lastInputValueMap: WeakMap = new WeakMap(); -function initInputObserver( - cb: inputCallback, - doc: Document, - blockClass: blockClass, - ignoreClass: string, - maskInputOptions: MaskInputOptions, - maskInputFn: MaskInputFn | undefined, - sampling: SamplingStrategy, -): listenerHandler { - function eventHandler(event: KeyboardEvent) { // was Event - const { target } = event; - const rrwebGenerated = !event.isTrusted; - +function initInputObserver({ + inputCb, + doc, + mirror, + blockClass, + ignoreClass, + maskInputOptions, + maskInputFn, + sampling, + userTriggeredOnInput, +}: observerParam): listenerHandler { + function eventHandler(event: Event) { + let target = getEventTarget(event); + const userTriggered = event.isTrusted; + /** + * If a site changes the value 'selected' of an option element, the value of its parent element, usually a select element, will be changed as well. + * We can treat this change as a value change of the select element the current target belongs to. + */ + if (target && (target as Element).tagName === 'OPTION') + target = (target as Element).parentElement; if ( !target || !(target as Element).tagName || @@ -338,9 +354,7 @@ function initInputObserver( } const type: string | undefined = (target as HTMLInputElement).type; - if ( - (target as HTMLElement).classList.contains(ignoreClass) - ) { + if ((target as HTMLElement).classList.contains(ignoreClass)) { return; } let text = (target as HTMLInputElement).value; @@ -353,13 +367,21 @@ function initInputObserver( ] || maskInputOptions[type as keyof MaskInputOptions] ) { - if (maskInputFn) { - text = maskInputFn(text); - } else { - text = '*'.repeat(text.length); - } + text = maskInputValue({ + maskInputOptions, + tagName: (target as HTMLElement).tagName, + type, + value: text, + maskInputFn, + }); } - cbWithDedup(target, { text, isChecked, rrwebGenerated }); + cbWithDedup( + target, + wrapEventWithUserTriggeredFlag( + { text, isChecked, userTriggered }, + userTriggeredOnInput, + ), + ); // if a radio was checked // the other radios with the same name attribute will be unchecked. const name: string | undefined = (target as HTMLInputElement).name; @@ -368,11 +390,17 @@ function initInputObserver( .querySelectorAll(`input[type="radio"][name="${name}"]`) .forEach((el) => { if (el !== target) { - cbWithDedup(el, { - text: (el as HTMLInputElement).value, - isChecked: !isChecked, - rrwebGenerated: true, - }); + cbWithDedup( + el, + wrapEventWithUserTriggeredFlag( + { + text: (el as HTMLInputElement).value, + isChecked: !isChecked, + userTriggered: false, + }, + userTriggeredOnInput, + ), + ); } }); } @@ -387,7 +415,7 @@ function initInputObserver( ) { lastInputValueMap.set(target, v); const id = mirror.getId(target as INode); - cb({ + inputCb({ ...v, id, }); @@ -408,6 +436,7 @@ function initInputObserver( [HTMLTextAreaElement.prototype, 'value'], // Some UI library use selectedIndex to set select value [HTMLSelectElement.prototype, 'selectedIndex'], + [HTMLOptionElement.prototype, 'selected'], ]; if (propertyDescriptor && propertyDescriptor.set) { handlers.push( @@ -426,12 +455,57 @@ function initInputObserver( }; } -function initStyleSheetObserver(cb: styleSheetRuleCallback): listenerHandler { - const insertRule = CSSStyleSheet.prototype.insertRule; - CSSStyleSheet.prototype.insertRule = function (rule: string, index?: number) { +type GroupingCSSRule = + | CSSGroupingRule + | CSSMediaRule + | CSSSupportsRule + | CSSConditionRule; +type GroupingCSSRuleTypes = + | typeof CSSGroupingRule + | typeof CSSMediaRule + | typeof CSSSupportsRule + | typeof CSSConditionRule; + +function getNestedCSSRulePositions(rule: CSSRule): number[] { + const positions: number[] = []; + function recurse(childRule: CSSRule, pos: number[]) { + if ( + (isCSSGroupingRuleSupported && + childRule.parentRule instanceof CSSGroupingRule) || + (isCSSMediaRuleSupported && + childRule.parentRule instanceof CSSMediaRule) || + (isCSSSupportsRuleSupported && + childRule.parentRule instanceof CSSSupportsRule) || + (isCSSConditionRuleSupported && + childRule.parentRule instanceof CSSConditionRule) + ) { + const rules = Array.from( + (childRule.parentRule as GroupingCSSRule).cssRules, + ); + const index = rules.indexOf(childRule); + pos.unshift(index); + } else { + const rules = Array.from(childRule.parentStyleSheet!.cssRules); + const index = rules.indexOf(childRule); + pos.unshift(index); + } + return pos; + } + return recurse(rule, positions); +} + +function initStyleSheetObserver( + { styleSheetRuleCb, mirror }: observerParam, + { win }: { win: IWindow }, +): listenerHandler { + const insertRule = win.CSSStyleSheet.prototype.insertRule; + win.CSSStyleSheet.prototype.insertRule = function ( + rule: string, + index?: number, + ) { const id = mirror.getId(this.ownerNode as INode); if (id !== -1) { - cb({ + styleSheetRuleCb({ id, adds: [{ rule, index }], }); @@ -439,11 +513,11 @@ function initStyleSheetObserver(cb: styleSheetRuleCallback): listenerHandler { return insertRule.apply(this, arguments); }; - const deleteRule = CSSStyleSheet.prototype.deleteRule; - CSSStyleSheet.prototype.deleteRule = function (index: number) { + const deleteRule = win.CSSStyleSheet.prototype.deleteRule; + win.CSSStyleSheet.prototype.deleteRule = function (index: number) { const id = mirror.getId(this.ownerNode as INode); if (id !== -1) { - cb({ + styleSheetRuleCb({ id, removes: [{ index }], }); @@ -451,109 +525,179 @@ function initStyleSheetObserver(cb: styleSheetRuleCallback): listenerHandler { return deleteRule.apply(this, arguments); }; + const supportedNestedCSSRuleTypes: { + [key: string]: GroupingCSSRuleTypes; + } = {}; + if (isCSSGroupingRuleSupported) { + supportedNestedCSSRuleTypes.CSSGroupingRule = win.CSSGroupingRule; + } else { + // Some browsers (Safari) don't support CSSGroupingRule + // https://caniuse.com/?search=cssgroupingrule + // fall back to monkey patching classes that would have inherited from CSSGroupingRule + + if (isCSSMediaRuleSupported) { + supportedNestedCSSRuleTypes.CSSMediaRule = win.CSSMediaRule; + } + if (isCSSConditionRuleSupported) { + supportedNestedCSSRuleTypes.CSSConditionRule = win.CSSConditionRule; + } + if (isCSSSupportsRuleSupported) { + supportedNestedCSSRuleTypes.CSSSupportsRule = win.CSSSupportsRule; + } + } + + const unmodifiedFunctions: { + [key: string]: { + insertRule: (rule: string, index?: number) => number; + deleteRule: (index: number) => void; + }; + } = {}; + + Object.entries(supportedNestedCSSRuleTypes).forEach(([typeKey, type]) => { + unmodifiedFunctions[typeKey] = { + insertRule: (type as GroupingCSSRuleTypes).prototype.insertRule, + deleteRule: (type as GroupingCSSRuleTypes).prototype.deleteRule, + }; + + type.prototype.insertRule = function (rule: string, index?: number) { + const id = mirror.getId(this.parentStyleSheet.ownerNode as INode); + if (id !== -1) { + styleSheetRuleCb({ + id, + adds: [ + { + rule, + index: [ + ...getNestedCSSRulePositions(this), + index || 0, // defaults to 0 + ], + }, + ], + }); + } + return unmodifiedFunctions[typeKey].insertRule.apply(this, arguments); + }; + + type.prototype.deleteRule = function (index: number) { + const id = mirror.getId(this.parentStyleSheet.ownerNode as INode); + if (id !== -1) { + styleSheetRuleCb({ + id, + removes: [{ index: [...getNestedCSSRulePositions(this), index] }], + }); + } + return unmodifiedFunctions[typeKey].deleteRule.apply(this, arguments); + }; + }); + return () => { - CSSStyleSheet.prototype.insertRule = insertRule; - CSSStyleSheet.prototype.deleteRule = deleteRule; + win.CSSStyleSheet.prototype.insertRule = insertRule; + win.CSSStyleSheet.prototype.deleteRule = deleteRule; + Object.entries(supportedNestedCSSRuleTypes).forEach(([typeKey, type]) => { + type.prototype.insertRule = unmodifiedFunctions[typeKey].insertRule; + type.prototype.deleteRule = unmodifiedFunctions[typeKey].deleteRule; + }); }; } -function initMediaInteractionObserver( - mediaInteractionCb: mediaInteractionCallback, - blockClass: blockClass, +function initStyleDeclarationObserver( + { styleDeclarationCb, mirror }: observerParam, + { win }: { win: IWindow }, ): listenerHandler { - const handler = (type: 'play' | 'pause') => (event: Event) => { - const { target } = event; - if (!target || isBlocked(target as Node, blockClass)) { - return; + const setProperty = win.CSSStyleDeclaration.prototype.setProperty; + win.CSSStyleDeclaration.prototype.setProperty = function ( + this: CSSStyleDeclaration, + property: string, + value: string, + priority: string, + ) { + const id = mirror.getId( + (this.parentRule?.parentStyleSheet?.ownerNode as unknown) as INode, + ); + if (id !== -1) { + styleDeclarationCb({ + id, + set: { + property, + value, + priority, + }, + index: getNestedCSSRulePositions(this.parentRule!), + }); } - mediaInteractionCb({ - type: type === 'play' ? MediaInteractions.Play : MediaInteractions.Pause, - id: mirror.getId(target as INode), - }); + return setProperty.apply(this, arguments); }; - const handlers = [on('play', handler('play')), on('pause', handler('pause'))]; + + const removeProperty = win.CSSStyleDeclaration.prototype.removeProperty; + win.CSSStyleDeclaration.prototype.removeProperty = function ( + this: CSSStyleDeclaration, + property: string, + ) { + const id = mirror.getId( + (this.parentRule?.parentStyleSheet?.ownerNode as unknown) as INode, + ); + if (id !== -1) { + styleDeclarationCb({ + id, + remove: { + property, + }, + index: getNestedCSSRulePositions(this.parentRule!), + }); + } + return removeProperty.apply(this, arguments); + }; + return () => { - handlers.forEach((h) => h()); + win.CSSStyleDeclaration.prototype.setProperty = setProperty; + win.CSSStyleDeclaration.prototype.removeProperty = removeProperty; }; } -function initCanvasMutationObserver( - cb: canvasMutationCallback, - blockClass: blockClass, -): listenerHandler { - const props = Object.getOwnPropertyNames(CanvasRenderingContext2D.prototype); - const handlers: listenerHandler[] = []; - for (const prop of props) { - try { - if ( - typeof CanvasRenderingContext2D.prototype[ - prop as keyof CanvasRenderingContext2D - ] !== 'function' - ) { - continue; +function initMediaInteractionObserver({ + mediaInteractionCb, + blockClass, + mirror, + sampling, +}: observerParam): listenerHandler { + const handler = (type: MediaInteractions) => + throttle((event: Event) => { + const target = getEventTarget(event); + if (!target || isBlocked(target as Node, blockClass)) { + return; } - const restoreHandler = patch( - CanvasRenderingContext2D.prototype, - prop, - function (original) { - return function ( - this: CanvasRenderingContext2D, - ...args: Array - ) { - if (!isBlocked(this.canvas, blockClass)) { - setTimeout(() => { - const recordArgs = [...args]; - if (prop === 'drawImage') { - if ( - recordArgs[0] && - recordArgs[0] instanceof HTMLCanvasElement - ) { - recordArgs[0] = recordArgs[0].toDataURL(); - } - } - cb({ - id: mirror.getId((this.canvas as unknown) as INode), - property: prop, - args: recordArgs, - }); - }, 0); - } - return original.apply(this, args); - }; - }, - ); - handlers.push(restoreHandler); - } catch { - const hookHandler = hookSetter( - CanvasRenderingContext2D.prototype, - prop, - { - set(v) { - cb({ - id: mirror.getId((this.canvas as unknown) as INode), - property: prop, - args: [v], - setter: true, - }); - }, - }, - ); - handlers.push(hookHandler); - } - } + const { currentTime, volume, muted } = target as HTMLMediaElement; + mediaInteractionCb({ + type, + id: mirror.getId(target as INode), + currentTime, + volume, + muted, + }); + }, sampling.media || 500); + const handlers = [ + on('play', handler(MediaInteractions.Play)), + on('pause', handler(MediaInteractions.Pause)), + on('seeked', handler(MediaInteractions.Seeked)), + on('volumechange', handler(MediaInteractions.VolumeChange)), + ]; return () => { handlers.forEach((h) => h()); }; } -function initFontObserver(cb: fontCallback): listenerHandler { +function initFontObserver({ fontCb, doc }: observerParam): listenerHandler { + const win = doc.defaultView as IWindow; + if (!win) { + return () => {}; + } + const handlers: listenerHandler[] = []; const fontMap = new WeakMap(); - const originalFontFace = FontFace; - // tslint:disable-next-line: no-any - (window as any).FontFace = function FontFace( + const originalFontFace = win.FontFace; + win.FontFace = (function FontFace( family: string, source: string | ArrayBufferView, descriptors?: FontFaceDescriptors, @@ -570,14 +714,14 @@ function initFontObserver(cb: fontCallback): listenerHandler { JSON.stringify(Array.from(new Uint8Array(source as any))), }); return fontFace; - }; + } as unknown) as typeof FontFace; - const restoreHandler = patch(document.fonts, 'add', function (original) { + const restoreHandler = patch(doc.fonts, 'add', function (original) { return function (this: FontFaceSet, fontFace: FontFace) { setTimeout(() => { const p = fontMap.get(fontFace); if (p) { - cb(p); + fontCb(p); fontMap.delete(fontFace); } }, 0); @@ -586,8 +730,7 @@ function initFontObserver(cb: fontCallback): listenerHandler { }); handlers.push(() => { - // tslint:disable-next-line: no-any - (window as any).FonFace = originalFontFace; + win.FontFace = originalFontFace; }); handlers.push(restoreHandler); @@ -596,111 +739,6 @@ function initFontObserver(cb: fontCallback): listenerHandler { }; } -function initLogObserver( - cb: logCallback, - logOptions: LogRecordOptions, -): listenerHandler { - const logger = logOptions.logger; - if (!logger) { - return () => {}; - } - let logCount = 0; - const cancelHandlers: listenerHandler[] = []; - // add listener to thrown errors - if (logOptions.level!.includes('error')) { - if (window) { - const originalOnError = window.onerror; - // tslint:disable-next-line:no-any - window.onerror = (...args: any[]) => { - if (originalOnError) { - originalOnError.apply(this, args); - } - let stack: string[] = []; - if (args[args.length - 1] instanceof Error) { - // 0(the second parameter) tells parseStack that every stack in Error is useful - stack = parseStack(args[args.length - 1].stack, 0); - } - const payload = [stringify(args[0], logOptions.stringifyOptions)]; - cb({ - level: 'error', - trace: stack, - payload, - }); - }; - cancelHandlers.push(() => { - window.onerror = originalOnError; - }); - } - } - for (const levelType of logOptions.level!) { - cancelHandlers.push(replace(logger, levelType)); - } - return () => { - cancelHandlers.forEach((h) => h()); - }; - - /** - * replace the original console function and record logs - * @param logger the logger object such as Console - * @param level the name of log function to be replaced - */ - function replace(_logger: Logger, level: LogLevel) { - if (!_logger[level]) { - return () => {}; - } - // replace the logger.{level}. return a restore function - return patch(_logger, level, (original) => { - // tslint:disable-next-line:no-any - return (...args: any[]) => { - original.apply(this, args); - try { - const stack = parseStack(new Error().stack); - const payload = args.map((s) => - stringify(s, logOptions.stringifyOptions), - ); - logCount++; - if (logCount < logOptions.lengthThreshold!) { - cb({ - level, - trace: stack, - payload, - }); - } else if (logCount === logOptions.lengthThreshold) { - // notify the user - cb({ - level: 'warn', - trace: [], - payload: [ - stringify('The number of log records reached the threshold.'), - ], - }); - } - } catch (error) { - original('rrweb logger error:', error, ...args); - } - }; - }); - } - /** - * parse single stack message to an stack array. - * @param stack the stack message to be parsed - * @param omitDepth omit specific depth of useless stack. omit hijacked log function by default - */ - function parseStack( - stack: string | undefined, - omitDepth: number = 1, - ): string[] { - let stacks: string[] = []; - if (stack) { - stacks = stack - .split('at') - .splice(1 + omitDepth) - .map((s) => s.trim()); - } - return stacks; - } -} - function mergeHooks(o: observerParam, hooks: hooksParam) { const { mutationCb, @@ -711,9 +749,9 @@ function mergeHooks(o: observerParam, hooks: hooksParam) { inputCb, mediaInteractionCb, styleSheetRuleCb, + styleDeclarationCb, canvasMutationCb, fontCb, - logCb, } = o; o.mutationCb = (...p: Arguments) => { if (hooks.mutation) { @@ -763,6 +801,12 @@ function mergeHooks(o: observerParam, hooks: hooksParam) { } styleSheetRuleCb(...p); }; + o.styleDeclarationCb = (...p: Arguments) => { + if (hooks.styleDeclaration) { + hooks.styleDeclaration(...p); + } + styleDeclarationCb(...p); + }; o.canvasMutationCb = (...p: Arguments) => { if (hooks.canvasMutation) { hooks.canvasMutation(...p); @@ -775,72 +819,41 @@ function mergeHooks(o: observerParam, hooks: hooksParam) { } fontCb(...p); }; - o.logCb = (...p: Arguments) => { - if (hooks.log) { - hooks.log(...p); - } - logCb(...p); - }; } export function initObservers( o: observerParam, hooks: hooksParam = {}, ): listenerHandler { + const currentWindow = o.doc.defaultView; // basically document.window + if (!currentWindow) { + return () => {}; + } + mergeHooks(o, hooks); - const mutationObserver = initMutationObserver( - o.mutationCb, - o.doc, - o.blockClass, - o.blockSelector, - o.maskTextClass, - o.maskTextSelector, - o.inlineStylesheet, - o.maskInputOptions, - o.maskTextFn, - o.recordCanvas, - o.slimDOMOptions, - o.iframeManager, - o.shadowDomManager, - o.doc, - ); - const mousemoveHandler = initMoveObserver(o.mousemoveCb, o.sampling, o.doc); - const mouseInteractionHandler = initMouseInteractionObserver( - o.mouseInteractionCb, - o.doc, - o.blockClass, - o.sampling, - ); - const scrollHandler = initScrollObserver( - o.scrollCb, - o.doc, - o.blockClass, - o.sampling, - ); - const viewportResizeHandler = initViewportResizeObserver(o.viewportResizeCb); - const inputHandler = initInputObserver( - o.inputCb, - o.doc, - o.blockClass, - o.ignoreClass, - o.maskInputOptions, - o.maskInputFn, - o.sampling, - ); - const mediaInteractionHandler = initMediaInteractionObserver( - o.mediaInteractionCb, - o.blockClass, - ); - const styleSheetObserver = initStyleSheetObserver(o.styleSheetRuleCb); - const canvasMutationObserver = o.recordCanvas - ? initCanvasMutationObserver(o.canvasMutationCb, o.blockClass) - : () => {}; - const fontObserver = o.collectFonts ? initFontObserver(o.fontCb) : () => {}; - const logObserver = o.logOptions - ? initLogObserver(o.logCb, o.logOptions) - : () => {}; + const mutationObserver = initMutationObserver(o, o.doc); + const mousemoveHandler = initMoveObserver(o); + const mouseInteractionHandler = initMouseInteractionObserver(o); + const scrollHandler = initScrollObserver(o); + const viewportResizeHandler = initViewportResizeObserver(o); + const inputHandler = initInputObserver(o); + const mediaInteractionHandler = initMediaInteractionObserver(o); + + const styleSheetObserver = initStyleSheetObserver(o, { win: currentWindow }); + const styleDeclarationObserver = initStyleDeclarationObserver(o, { + win: currentWindow, + }); + const fontObserver = o.collectFonts ? initFontObserver(o) : () => {}; + // plugins + const pluginHandlers: listenerHandler[] = []; + for (const plugin of o.plugins) { + pluginHandlers.push( + plugin.observer(plugin.callback, currentWindow, plugin.options), + ); + } return () => { + mutationBuffers.forEach((b) => b.reset()); mutationObserver.disconnect(); mousemoveHandler(); mouseInteractionHandler(); @@ -849,8 +862,8 @@ export function initObservers( inputHandler(); mediaInteractionHandler(); styleSheetObserver(); - canvasMutationObserver(); + styleDeclarationObserver(); fontObserver(); - logObserver(); + pluginHandlers.forEach((h) => h()); }; } diff --git a/packages/rrweb/src/record/observers/canvas/2d.ts b/packages/rrweb/src/record/observers/canvas/2d.ts new file mode 100644 index 0000000000..dd63469b1e --- /dev/null +++ b/packages/rrweb/src/record/observers/canvas/2d.ts @@ -0,0 +1,94 @@ +import { INode } from 'rrweb-snapshot'; +import { + blockClass, + CanvasContext, + canvasManagerMutationCallback, + IWindow, + listenerHandler, + Mirror, +} from '../../../types'; +import { hookSetter, isBlocked, patch } from '../../../utils'; + +export default function initCanvas2DMutationObserver( + cb: canvasManagerMutationCallback, + win: IWindow, + blockClass: blockClass, + mirror: Mirror, +): listenerHandler { + const handlers: listenerHandler[] = []; + const props2D = Object.getOwnPropertyNames( + win.CanvasRenderingContext2D.prototype, + ); + for (const prop of props2D) { + try { + if ( + typeof win.CanvasRenderingContext2D.prototype[ + prop as keyof CanvasRenderingContext2D + ] !== 'function' + ) { + continue; + } + const restoreHandler = patch( + win.CanvasRenderingContext2D.prototype, + prop, + function (original) { + return function ( + this: CanvasRenderingContext2D, + ...args: Array + ) { + if (!isBlocked((this.canvas as unknown) as INode, blockClass)) { + // Using setTimeout as getImageData + JSON.stringify can be heavy + // and we'd rather not block the main thread + setTimeout(() => { + const recordArgs = [...args]; + if (prop === 'drawImage') { + if ( + recordArgs[0] && + recordArgs[0] instanceof HTMLCanvasElement + ) { + const canvas = recordArgs[0]; + const ctx = canvas.getContext('2d'); + let imgd = ctx?.getImageData( + 0, + 0, + canvas.width, + canvas.height, + ); + let pix = imgd?.data; + recordArgs[0] = JSON.stringify(pix); + } + } + cb(this.canvas, { + type: CanvasContext['2D'], + property: prop, + args: recordArgs, + }); + }, 0); + } + return original.apply(this, args); + }; + }, + ); + handlers.push(restoreHandler); + } catch { + const hookHandler = hookSetter( + win.CanvasRenderingContext2D.prototype, + prop, + { + set(v) { + cb(this.canvas, { + type: CanvasContext['2D'], + property: prop, + args: [v], + setter: true, + }); + }, + }, + ); + handlers.push(hookHandler); + } + } + return () => { + handlers.forEach((h) => h()); + }; +} diff --git a/packages/rrweb/src/record/observers/canvas/canvas-manager.ts b/packages/rrweb/src/record/observers/canvas/canvas-manager.ts new file mode 100644 index 0000000000..a266e5fb73 --- /dev/null +++ b/packages/rrweb/src/record/observers/canvas/canvas-manager.ts @@ -0,0 +1,154 @@ +import { INode } from 'rrweb-snapshot'; +import { + blockClass, + canvasManagerMutationCallback, + canvasMutationCallback, + canvasMutationCommand, + canvasMutationWithType, + IWindow, + listenerHandler, + Mirror, +} from '../../../types'; +import initCanvas2DMutationObserver from './2d'; +import initCanvasContextObserver from './canvas'; +import initCanvasWebGLMutationObserver from './webgl'; + +export type RafStamps = { latestId: number; invokeId: number | null }; + +type pendingCanvasMutationsMap = Map< + HTMLCanvasElement, + canvasMutationWithType[] +>; + +export class CanvasManager { + private pendingCanvasMutations: pendingCanvasMutationsMap = new Map(); + private rafStamps: RafStamps = { latestId: 0, invokeId: null }; + private mirror: Mirror; + + private mutationCb: canvasMutationCallback; + private resetObservers?: listenerHandler; + private frozen: boolean = false; + private locked: boolean = false; + + public reset() { + this.pendingCanvasMutations.clear(); + this.resetObservers && this.resetObservers(); + } + + public freeze() { + this.frozen = true; + } + + public unfreeze() { + this.frozen = false; + } + + public lock() { + this.locked = true; + } + + public unlock() { + this.locked = false; + } + + constructor(options: { + recordCanvas: boolean | number; + mutationCb: canvasMutationCallback; + win: IWindow; + blockClass: blockClass; + mirror: Mirror; + }) { + this.mutationCb = options.mutationCb; + this.mirror = options.mirror; + + if (options.recordCanvas === true) + this.initCanvasMutationObserver(options.win, options.blockClass); + } + + private processMutation: canvasManagerMutationCallback = function ( + target, + mutation, + ) { + const newFrame = + this.rafStamps.invokeId && + this.rafStamps.latestId !== this.rafStamps.invokeId; + if (newFrame || !this.rafStamps.invokeId) + this.rafStamps.invokeId = this.rafStamps.latestId; + + if (!this.pendingCanvasMutations.has(target)) { + this.pendingCanvasMutations.set(target, []); + } + + this.pendingCanvasMutations.get(target)!.push(mutation); + }; + + private initCanvasMutationObserver( + win: IWindow, + blockClass: blockClass, + ): void { + this.startRAFTimestamping(); + this.startPendingCanvasMutationFlusher(); + + const canvasContextReset = initCanvasContextObserver(win, blockClass); + const canvas2DReset = initCanvas2DMutationObserver( + this.processMutation.bind(this), + win, + blockClass, + this.mirror, + ); + + const canvasWebGL1and2Reset = initCanvasWebGLMutationObserver( + this.processMutation.bind(this), + win, + blockClass, + this.mirror, + ); + + this.resetObservers = () => { + canvasContextReset(); + canvas2DReset(); + canvasWebGL1and2Reset(); + }; + } + + private startPendingCanvasMutationFlusher() { + requestAnimationFrame(() => this.flushPendingCanvasMutations()); + } + + private startRAFTimestamping() { + const setLatestRAFTimestamp = (timestamp: DOMHighResTimeStamp) => { + this.rafStamps.latestId = timestamp; + requestAnimationFrame(setLatestRAFTimestamp); + }; + requestAnimationFrame(setLatestRAFTimestamp); + } + + flushPendingCanvasMutations() { + this.pendingCanvasMutations.forEach( + (values: canvasMutationCommand[], canvas: HTMLCanvasElement) => { + const id = this.mirror.getId((canvas as unknown) as INode); + this.flushPendingCanvasMutationFor(canvas, id); + }, + ); + requestAnimationFrame(() => this.flushPendingCanvasMutations()); + } + + flushPendingCanvasMutationFor(canvas: HTMLCanvasElement, id: number) { + if (this.frozen || this.locked) { + return; + } + + const valuesWithType = this.pendingCanvasMutations.get(canvas); + if (!valuesWithType || id === -1) return; + + const values = valuesWithType.map((value) => { + const { type, ...rest } = value; + return rest; + }); + const { type } = valuesWithType[0]; + + this.mutationCb({ id, type, commands: values }); + + this.pendingCanvasMutations.delete(canvas); + } +} diff --git a/packages/rrweb/src/record/observers/canvas/canvas.ts b/packages/rrweb/src/record/observers/canvas/canvas.ts new file mode 100644 index 0000000000..437af7d5f3 --- /dev/null +++ b/packages/rrweb/src/record/observers/canvas/canvas.ts @@ -0,0 +1,35 @@ +import { INode, ICanvas } from 'rrweb-snapshot'; +import { blockClass, IWindow, listenerHandler } from '../../../types'; +import { isBlocked, patch } from '../../../utils'; + +export default function initCanvasContextObserver( + win: IWindow, + blockClass: blockClass, +): listenerHandler { + const handlers: listenerHandler[] = []; + try { + const restoreHandler = patch( + win.HTMLCanvasElement.prototype, + 'getContext', + function (original) { + return function ( + this: ICanvas, + contextType: string, + ...args: Array + ) { + if (!isBlocked((this as unknown) as INode, blockClass)) { + if (!('__context' in this)) + (this as ICanvas).__context = contextType; + } + return original.apply(this, [contextType, ...args]); + }; + }, + ); + handlers.push(restoreHandler); + } catch { + console.error('failed to patch HTMLCanvasElement.prototype.getContext'); + } + return () => { + handlers.forEach((h) => h()); + }; +} diff --git a/packages/rrweb/src/record/observers/canvas/serialize-args.ts b/packages/rrweb/src/record/observers/canvas/serialize-args.ts new file mode 100644 index 0000000000..e245ee4123 --- /dev/null +++ b/packages/rrweb/src/record/observers/canvas/serialize-args.ts @@ -0,0 +1,166 @@ +import { encode } from 'base64-arraybuffer'; +import { IWindow, SerializedWebGlArg } from '../../../types'; + +// TODO: unify with `replay/webgl.ts` +type GLVarMap = Map; +const webGLVarMap: Map< + WebGLRenderingContext | WebGL2RenderingContext, + GLVarMap +> = new Map(); +export function variableListFor( + ctx: WebGLRenderingContext | WebGL2RenderingContext, + ctor: string, +) { + let contextMap = webGLVarMap.get(ctx); + if (!contextMap) { + contextMap = new Map(); + webGLVarMap.set(ctx, contextMap); + } + if (!contextMap.has(ctor)) { + contextMap.set(ctor, []); + } + return contextMap.get(ctor) as any[]; +} + +export const saveWebGLVar = ( + value: any, + win: IWindow, + ctx: WebGL2RenderingContext | WebGLRenderingContext, +): number | void => { + if ( + !value || + !(isInstanceOfWebGLObject(value, win) || typeof value === 'object') + ) + return; + + const name = value.constructor.name; + const list = variableListFor(ctx, name); + let index = list.indexOf(value); + + if (index === -1) { + index = list.length; + list.push(value); + } + return index; +}; + +// from webgl-recorder: https://github.com/evanw/webgl-recorder/blob/bef0e65596e981ee382126587e2dcbe0fc7748e2/webgl-recorder.js#L50-L77 +export function serializeArg( + value: any, + win: IWindow, + ctx: WebGL2RenderingContext | WebGLRenderingContext, +): SerializedWebGlArg { + if (value instanceof Array) { + return value.map((arg) => serializeArg(arg, win, ctx)); + } else if (value === null) { + return value; + } else if ( + value instanceof Float32Array || + value instanceof Float64Array || + value instanceof Int32Array || + value instanceof Uint32Array || + value instanceof Uint8Array || + value instanceof Uint16Array || + value instanceof Int16Array || + value instanceof Int8Array || + value instanceof Uint8ClampedArray + ) { + const name = value.constructor.name; + return { + rr_type: name, + args: [Object.values(value)], + }; + } else if ( + // SharedArrayBuffer disabled on most browsers due to spectre. + // More info: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer/SharedArrayBuffer + // value instanceof SharedArrayBuffer || + value instanceof ArrayBuffer + ) { + const name = value.constructor.name as 'ArrayBuffer'; + const base64 = encode(value); + + return { + rr_type: name, + base64, + }; + } else if (value instanceof DataView) { + const name = value.constructor.name; + return { + rr_type: name, + args: [ + serializeArg(value.buffer, win, ctx), + value.byteOffset, + value.byteLength, + ], + }; + } else if (value instanceof HTMLImageElement) { + const name = value.constructor.name; + const { src } = value; + return { + rr_type: name, + src, + }; + } else if (value instanceof ImageData) { + const name = value.constructor.name; + return { + rr_type: name, + args: [serializeArg(value.data, win, ctx), value.width, value.height], + }; + } else if (isInstanceOfWebGLObject(value, win) || typeof value === 'object') { + const name = value.constructor.name; + const index = saveWebGLVar(value, win, ctx) as number; + + return { + rr_type: name, + index: index, + }; + } + + return value; +} + +export const serializeArgs = ( + args: Array, + win: IWindow, + ctx: WebGLRenderingContext | WebGL2RenderingContext, +) => { + return [...args].map((arg) => serializeArg(arg, win, ctx)); +}; + +export const isInstanceOfWebGLObject = ( + value: any, + win: IWindow, +): value is + | WebGLActiveInfo + | WebGLBuffer + | WebGLFramebuffer + | WebGLProgram + | WebGLRenderbuffer + | WebGLShader + | WebGLShaderPrecisionFormat + | WebGLTexture + | WebGLUniformLocation + | WebGLVertexArrayObject => { + const webGLConstructorNames: string[] = [ + 'WebGLActiveInfo', + 'WebGLBuffer', + 'WebGLFramebuffer', + 'WebGLProgram', + 'WebGLRenderbuffer', + 'WebGLShader', + 'WebGLShaderPrecisionFormat', + 'WebGLTexture', + 'WebGLUniformLocation', + 'WebGLVertexArrayObject', + // In old Chrome versions, value won't be an instanceof WebGLVertexArrayObject. + 'WebGLVertexArrayObjectOES', + ]; + const supportedWebGLConstructorNames = webGLConstructorNames.filter( + (name: string) => typeof win[name as keyof Window] === 'function', + ); + return Boolean( + supportedWebGLConstructorNames.find( + (name: string) => value instanceof win[name as keyof Window], + ), + ); +}; diff --git a/packages/rrweb/src/record/observers/canvas/webgl.ts b/packages/rrweb/src/record/observers/canvas/webgl.ts new file mode 100644 index 0000000000..45b03928f3 --- /dev/null +++ b/packages/rrweb/src/record/observers/canvas/webgl.ts @@ -0,0 +1,106 @@ +import { INode } from 'rrweb-snapshot'; +import { + blockClass, + CanvasContext, + canvasManagerMutationCallback, + canvasMutationWithType, + IWindow, + listenerHandler, + Mirror, +} from '../../../types'; +import { hookSetter, isBlocked, patch } from '../../../utils'; +import { saveWebGLVar, serializeArgs } from './serialize-args'; + +function patchGLPrototype( + prototype: WebGLRenderingContext | WebGL2RenderingContext, + type: CanvasContext, + cb: canvasManagerMutationCallback, + blockClass: blockClass, + mirror: Mirror, + win: IWindow, +): listenerHandler[] { + const handlers: listenerHandler[] = []; + + const props = Object.getOwnPropertyNames(prototype); + + for (const prop of props) { + try { + if (typeof prototype[prop as keyof typeof prototype] !== 'function') { + continue; + } + const restoreHandler = patch(prototype, prop, function (original) { + return function (this: typeof prototype, ...args: Array) { + const result = original.apply(this, args); + saveWebGLVar(result, win, prototype); + if (!isBlocked((this.canvas as unknown) as INode, blockClass)) { + const id = mirror.getId((this.canvas as unknown) as INode); + + const recordArgs = serializeArgs([...args], win, prototype); + const mutation: canvasMutationWithType = { + type, + property: prop, + args: recordArgs, + }; + // TODO: this could potentially also be an OffscreenCanvas as well as HTMLCanvasElement + cb(this.canvas as HTMLCanvasElement, mutation); + } + + return result; + }; + }); + handlers.push(restoreHandler); + } catch { + const hookHandler = hookSetter(prototype, prop, { + set(v) { + // TODO: this could potentially also be an OffscreenCanvas as well as HTMLCanvasElement + cb(this.canvas as HTMLCanvasElement, { + type, + property: prop, + args: [v], + setter: true, + }); + }, + }); + handlers.push(hookHandler); + } + } + + return handlers; +} + +export default function initCanvasWebGLMutationObserver( + cb: canvasManagerMutationCallback, + win: IWindow, + blockClass: blockClass, + mirror: Mirror, +): listenerHandler { + const handlers: listenerHandler[] = []; + + handlers.push( + ...patchGLPrototype( + win.WebGLRenderingContext.prototype, + CanvasContext.WebGL, + cb, + blockClass, + mirror, + win, + ), + ); + + if (typeof win.WebGL2RenderingContext !== 'undefined') { + handlers.push( + ...patchGLPrototype( + win.WebGL2RenderingContext.prototype, + CanvasContext.WebGL2, + cb, + blockClass, + mirror, + win, + ), + ); + } + + return () => { + handlers.forEach((h) => h()); + }; +} diff --git a/packages/rrweb/src/record/shadow-dom-manager.ts b/packages/rrweb/src/record/shadow-dom-manager.ts new file mode 100644 index 0000000000..e0ad26f33c --- /dev/null +++ b/packages/rrweb/src/record/shadow-dom-manager.ts @@ -0,0 +1,102 @@ +import { + mutationCallBack, + Mirror, + scrollCallback, + MutationBufferParam, + SamplingStrategy, +} from '../types'; +import { initMutationObserver, initScrollObserver } from './observer'; +import { patch } from '../utils'; + +type BypassOptions = Omit< + MutationBufferParam, + 'doc' | 'mutationCb' | 'mirror' | 'shadowDomManager' +> & { + sampling: SamplingStrategy; +}; + +export class ShadowDomManager { + private mutationCb: mutationCallBack; + private scrollCb: scrollCallback; + private bypassOptions: BypassOptions; + private mirror: Mirror; + private restorePatches: (() => void)[] = []; + + constructor(options: { + mutationCb: mutationCallBack; + scrollCb: scrollCallback; + bypassOptions: BypassOptions; + mirror: Mirror; + }) { + this.mutationCb = options.mutationCb; + this.scrollCb = options.scrollCb; + this.bypassOptions = options.bypassOptions; + this.mirror = options.mirror; + + // Patch 'attachShadow' to observe newly added shadow doms. + const manager = this; + this.restorePatches.push( + patch(HTMLElement.prototype, 'attachShadow', function (original) { + return function () { + const shadowRoot = original.apply(this, arguments); + if (this.shadowRoot) + manager.addShadowRoot(this.shadowRoot, this.ownerDocument); + return shadowRoot; + }; + }), + ); + } + + public addShadowRoot(shadowRoot: ShadowRoot, doc: Document) { + initMutationObserver( + { + ...this.bypassOptions, + doc, + mutationCb: this.mutationCb, + mirror: this.mirror, + shadowDomManager: this, + }, + shadowRoot, + ); + initScrollObserver({ + ...this.bypassOptions, + scrollCb: this.scrollCb, + // https://gist.github.com/praveenpuglia/0832da687ed5a5d7a0907046c9ef1813 + // scroll is not allowed to pass the boundary, so we need to listen the shadow document + doc: (shadowRoot as unknown) as Document, + mirror: this.mirror, + }); + } + + /** + * Monkey patch 'attachShadow' of an IFrameElement to observe newly added shadow doms. + */ + public observeAttachShadow(iframeElement: HTMLIFrameElement) { + if (iframeElement.contentWindow) { + const manager = this; + this.restorePatches.push( + patch( + (iframeElement.contentWindow as Window & { + HTMLElement: { prototype: HTMLElement }; + }).HTMLElement.prototype, + 'attachShadow', + function (original) { + return function () { + const shadowRoot = original.apply(this, arguments); + if (this.shadowRoot) + manager.addShadowRoot( + this.shadowRoot, + iframeElement.contentDocument as Document, + ); + return shadowRoot; + }; + }, + ), + ); + } + } + + public reset() { + this.restorePatches.forEach((restorePatch) => restorePatch()); + } +} diff --git a/packages/rrweb/src/replay/canvas/2d.ts b/packages/rrweb/src/replay/canvas/2d.ts new file mode 100644 index 0000000000..b9fde639b3 --- /dev/null +++ b/packages/rrweb/src/replay/canvas/2d.ts @@ -0,0 +1,48 @@ +import { Replayer } from '../'; +import { canvasMutationCommand } from '../../types'; + +export default function canvasMutation({ + event, + mutation, + target, + imageMap, + errorHandler, +}: { + event: Parameters[0]; + mutation: canvasMutationCommand; + target: HTMLCanvasElement; + imageMap: Replayer['imageMap']; + errorHandler: Replayer['warnCanvasMutationFailed']; +}): void { + try { + const ctx = ((target as unknown) as HTMLCanvasElement).getContext('2d')!; + + if (mutation.setter) { + // skip some read-only type checks + // tslint:disable-next-line:no-any + (ctx as any)[mutation.property] = mutation.args[0]; + return; + } + const original = ctx[ + mutation.property as Exclude + ] as Function; + + /** + * We have serialized the image source into base64 string during recording, + * which has been preloaded before replay. + * So we can get call drawImage SYNCHRONOUSLY which avoid some fragile cast. + */ + if ( + mutation.property === 'drawImage' && + typeof mutation.args[0] === 'string' + ) { + const image = imageMap.get(event); + mutation.args[0] = image; + original.apply(ctx, mutation.args); + } else { + original.apply(ctx, mutation.args); + } + } catch (error) { + errorHandler(mutation, error); + } +} diff --git a/packages/rrweb/src/replay/canvas/index.ts b/packages/rrweb/src/replay/canvas/index.ts new file mode 100644 index 0000000000..73411a2b10 --- /dev/null +++ b/packages/rrweb/src/replay/canvas/index.ts @@ -0,0 +1,51 @@ +import { Replayer } from '..'; +import { + CanvasContext, + canvasMutationCommand, + canvasMutationData, +} from '../../types'; +import webglMutation from './webgl'; +import canvas2DMutation from './2d'; + +export default function canvasMutation({ + event, + mutation, + target, + imageMap, + errorHandler, +}: { + event: Parameters[0]; + mutation: canvasMutationData; + target: HTMLCanvasElement; + imageMap: Replayer['imageMap']; + errorHandler: Replayer['warnCanvasMutationFailed']; +}): void { + try { + const mutations: canvasMutationCommand[] = + 'commands' in mutation ? mutation.commands : [mutation]; + + if ([CanvasContext.WebGL, CanvasContext.WebGL2].includes(mutation.type)) { + return mutations.forEach((command) => { + webglMutation({ + mutation: command, + type: mutation.type, + target, + imageMap, + errorHandler, + }); + }); + } + // default is '2d' for backwards compatibility (rrweb below 1.1.x) + return mutations.forEach((command) => { + canvas2DMutation({ + event, + mutation: command, + target, + imageMap, + errorHandler, + }); + }); + } catch (error) { + errorHandler(mutation, error); + } +} diff --git a/packages/rrweb/src/replay/canvas/webgl.ts b/packages/rrweb/src/replay/canvas/webgl.ts new file mode 100644 index 0000000000..58d323dc3d --- /dev/null +++ b/packages/rrweb/src/replay/canvas/webgl.ts @@ -0,0 +1,175 @@ +import { decode } from 'base64-arraybuffer'; +import { Replayer } from '../'; +import { + CanvasContext, + canvasMutationCommand, + SerializedWebGlArg, +} from '../../types'; + +// TODO: add ability to wipe this list +type GLVarMap = Map; +const webGLVarMap: Map< + WebGLRenderingContext | WebGL2RenderingContext, + GLVarMap +> = new Map(); +export function variableListFor( + ctx: WebGLRenderingContext | WebGL2RenderingContext, + ctor: string, +) { + let contextMap = webGLVarMap.get(ctx); + if (!contextMap) { + contextMap = new Map(); + webGLVarMap.set(ctx, contextMap); + } + if (!contextMap.has(ctor)) { + contextMap.set(ctor, []); + } + return contextMap.get(ctor) as any[]; +} + +function getContext( + target: HTMLCanvasElement, + type: CanvasContext, +): WebGLRenderingContext | WebGL2RenderingContext | null { + // Note to whomever is going to implement support for `contextAttributes`: + // if `preserveDrawingBuffer` is set to true, + // you might have to do `ctx.flush()` before every webgl canvas event + try { + if (type === CanvasContext.WebGL) { + return ( + target.getContext('webgl')! || target.getContext('experimental-webgl') + ); + } + return target.getContext('webgl2')!; + } catch (e) { + return null; + } +} + +const WebGLVariableConstructorsNames = [ + 'WebGLActiveInfo', + 'WebGLBuffer', + 'WebGLFramebuffer', + 'WebGLProgram', + 'WebGLRenderbuffer', + 'WebGLShader', + 'WebGLShaderPrecisionFormat', + 'WebGLTexture', + 'WebGLUniformLocation', + 'WebGLVertexArrayObject', +]; + +function saveToWebGLVarMap( + ctx: WebGLRenderingContext | WebGL2RenderingContext, + result: any, +) { + if (!result?.constructor) return; // probably null or undefined + + const { name } = result.constructor; + if (!WebGLVariableConstructorsNames.includes(name)) return; // not a WebGL variable + + const variables = variableListFor(ctx, name); + if (!variables.includes(result)) variables.push(result); +} + +export function deserializeArg( + imageMap: Replayer['imageMap'], + ctx: WebGLRenderingContext | WebGL2RenderingContext, +): (arg: SerializedWebGlArg) => any { + return (arg: SerializedWebGlArg): any => { + if (arg && typeof arg === 'object' && 'rr_type' in arg) { + if ('index' in arg) { + const { rr_type: name, index } = arg; + return variableListFor(ctx, name)[index]; + } else if ('args' in arg) { + const { rr_type: name, args } = arg; + const ctor = window[name as keyof Window]; + + return new ctor(...args.map(deserializeArg(imageMap, ctx))); + } else if ('base64' in arg) { + return decode(arg.base64); + } else if ('src' in arg) { + const image = imageMap.get(arg.src); + if (image) { + return image; + } else { + const image = new Image(); + image.src = arg.src; + imageMap.set(arg.src, image); + return image; + } + } + } else if (Array.isArray(arg)) { + return arg.map(deserializeArg(imageMap, ctx)); + } + return arg; + }; +} + +export default function webglMutation({ + mutation, + target, + type, + imageMap, + errorHandler, +}: { + mutation: canvasMutationCommand; + target: HTMLCanvasElement; + type: CanvasContext; + imageMap: Replayer['imageMap']; + errorHandler: Replayer['warnCanvasMutationFailed']; +}): void { + try { + const ctx = getContext(target, type); + if (!ctx) return; + + // NOTE: if `preserveDrawingBuffer` is set to true, + // we must flush the buffers on every new canvas event + // if (mutation.newFrame) ctx.flush(); + + if (mutation.setter) { + // skip some read-only type checks + // tslint:disable-next-line:no-any + (ctx as any)[mutation.property] = mutation.args[0]; + return; + } + const original = ctx[ + mutation.property as Exclude + ] as Function; + + const args = mutation.args.map(deserializeArg(imageMap, ctx)); + const result = original.apply(ctx, args); + saveToWebGLVarMap(ctx, result); + + // Slows down replay considerably, only use for debugging + const debugMode = false; + if (debugMode) { + if (mutation.property === 'compileShader') { + if (!ctx.getShaderParameter(args[0], ctx.COMPILE_STATUS)) + console.warn( + 'something went wrong in replay', + ctx.getShaderInfoLog(args[0]), + ); + } else if (mutation.property === 'linkProgram') { + ctx.validateProgram(args[0]); + if (!ctx.getProgramParameter(args[0], ctx.LINK_STATUS)) + console.warn( + 'something went wrong in replay', + ctx.getProgramInfoLog(args[0]), + ); + } + const webglError = ctx.getError(); + if (webglError !== ctx.NO_ERROR) { + console.warn( + 'WEBGL ERROR', + webglError, + 'on command:', + mutation.property, + ...args, + ); + } + } + } catch (error) { + errorHandler(mutation, error); + } +} diff --git a/src/replay/index.ts b/packages/rrweb/src/replay/index.ts similarity index 62% rename from src/replay/index.ts rename to packages/rrweb/src/replay/index.ts index 443e19ba3e..715ff79c95 100644 --- a/src/replay/index.ts +++ b/packages/rrweb/src/replay/index.ts @@ -1,4 +1,11 @@ -import { rebuild, buildNodeWithSN, INode, NodeType } from 'rrweb-snapshot'; +import { + rebuild, + buildNodeWithSN, + INode, + NodeType, + BuildCache, + createCache, +} from 'rrweb-snapshot'; import * as mittProxy from 'mitt'; import { polyfill as smoothscrollPolyfill } from './smoothscroll'; import { Timer } from './timer'; @@ -26,13 +33,17 @@ import { scrollData, inputData, canvasMutationData, + Mirror, ElementState, - LogReplayConfig, - logData, - ReplayLogger, + styleAttributeValue, + styleValueWithPriority, + mouseMovePos, + IWindow, + canvasMutationCommand, + textMutation, } from '../types'; import { - mirror, + createMirror, polyfill, TreeIndex, queueToResolveTrees, @@ -44,6 +55,16 @@ import { } from '../utils'; import getInjectStyleRules from './styles/inject-style'; import './styles/style.css'; +import { + applyVirtualStyleRulesToNode, + storeCSSRules, + StyleRuleType, + VirtualStyleRules, + VirtualStyleRulesMap, + getNestedRule, + getPositionsAndIndex, +} from './virtual-styles'; +import canvasMutation from './canvas'; const SKIP_TIME_THRESHOLD = 10 * 1000; const SKIP_TIME_INTERVAL = 5 * 1000; @@ -53,11 +74,6 @@ const SKIP_TIME_INTERVAL = 5 * 1000; const mitt = (mittProxy as any).default || mittProxy; const REPLAY_CONSOLE_PREFIX = '[replayer]'; -const ORIGINAL_ATTRIBUTE_NAME = '__rrweb_original__'; - -type PatchedConsoleLog = { - [ORIGINAL_ATTRIBUTE_NAME]: typeof console.log; -}; const defaultMouseTailConfig = { duration: 500, @@ -66,30 +82,14 @@ const defaultMouseTailConfig = { strokeStyle: 'red', } as const; -const defaultLogConfig: LogReplayConfig = { - level: [ - 'assert', - 'clear', - 'count', - 'countReset', - 'debug', - 'dir', - 'dirxml', - 'error', - 'group', - 'groupCollapsed', - 'groupEnd', - 'info', - 'log', - 'table', - 'time', - 'timeEnd', - 'timeLog', - 'trace', - 'warn', - ], - replayLogger: undefined, -}; +function indicatesTouchDevice(e: eventWithTime) { + return ( + e.type == EventType.IncrementalSnapshot && + (e.data.source == IncrementalSource.TouchMove || + (e.data.source == IncrementalSource.MouseInteraction && + e.data.type == MouseInteractions.TouchStart)) + ); +} export class Replayer { public wrapper: HTMLDivElement; @@ -117,14 +117,23 @@ export class Replayer { private treeIndex!: TreeIndex; private fragmentParentMap!: Map; private elementStateMap!: Map; + // Hold the list of CSSRules for in-memory state restoration + private virtualStyleRulesMap!: VirtualStyleRulesMap; - private imageMap: Map = new Map(); + // The replayer uses the cache to speed up replay and scrubbing. + private cache: BuildCache = createCache(); - /** The first time the player is playing. */ - private firstPlayedEvent: eventWithTime | null = null; + private imageMap: Map = new Map(); + + private mirror: Mirror = createMirror(); + + private firstFullSnapshot: eventWithTime | true | null = null; private newDocumentQueue: addedNodeMutation[] = []; + private mousePos: mouseMovePos | null = null; + private touchActive: boolean | null = null; + constructor( events: Array, config?: Partial, @@ -147,15 +156,12 @@ export class Replayer { UNSAFE_replayCanvas: false, pauseAnimation: true, mouseTail: defaultMouseTailConfig, - logConfig: defaultLogConfig, }; this.config = Object.assign({}, defaultConfig, config); - if (!this.config.logConfig.replayLogger) { - this.config.logConfig.replayLogger = this.getConsoleLogger(); - } this.handleResize = this.handleResize.bind(this); this.getCastFn = this.getCastFn.bind(this); + this.applyEventsSynchronously = this.applyEventsSynchronously.bind(this); this.emitter.on(ReplayerEvents.Resize, this.handleResize as Handler); this.setupDom(); @@ -163,39 +169,38 @@ export class Replayer { this.treeIndex = new TreeIndex(); this.fragmentParentMap = new Map(); this.elementStateMap = new Map(); + this.virtualStyleRulesMap = new Map(); + this.emitter.on(ReplayerEvents.Flush, () => { - const { scrollMap, inputMap } = this.treeIndex.flush(); + const { scrollMap, inputMap, mutationData } = this.treeIndex.flush(); - for (const [frag, parent] of this.fragmentParentMap.entries()) { - mirror.map[parent.__sn.id] = parent; - /** - * If we have already set value attribute on textarea, - * then we could not apply text content as default value any more. - */ - if ( - parent.__sn.type === NodeType.Element && - parent.__sn.tagName === 'textarea' && - frag.textContent - ) { - ((parent as unknown) as HTMLTextAreaElement).value = frag.textContent; - } - parent.appendChild(frag); - // restore state of elements after they are mounted - this.restoreState(parent); + this.fragmentParentMap.forEach((parent, frag) => + this.restoreRealParent(frag, parent), + ); + // apply text needs to happen before virtual style rules gets applied + // as it can overwrite the contents of a stylesheet + for (const d of mutationData.texts) { + this.applyText(d, mutationData); + } + + for (const node of this.virtualStyleRulesMap.keys()) { + // restore css rules of style elements after they are mounted + this.restoreNodeSheet(node); } this.fragmentParentMap.clear(); this.elementStateMap.clear(); + this.virtualStyleRulesMap.clear(); for (const d of scrollMap.values()) { - this.applyScroll(d); + this.applyScroll(d, true); } for (const d of inputMap.values()) { this.applyInput(d); } }); this.emitter.on(ReplayerEvents.PlayBack, () => { - this.firstPlayedEvent = null; - mirror.reset(); + this.firstFullSnapshot = null; + this.mirror.reset(); }); const timer = new Timer([], config?.speed || defaultConfig.speed); @@ -216,6 +221,7 @@ export class Replayer { }, { getCastFn: this.getCastFn, + applyEventsSynchronously: this.applyEventsSynchronously, emitter: this.emitter, }, ); @@ -256,10 +262,11 @@ export class Replayer { if (firstFullsnapshot) { setTimeout(() => { // when something has been played, there is no need to rebuild poster - if (this.firstPlayedEvent) { + if (this.firstFullSnapshot) { + // true if any other fullSnapshot has been executed by Timer already return; } - this.firstPlayedEvent = firstFullsnapshot; + this.firstFullSnapshot = firstFullsnapshot; this.rebuildFullSnapshot( firstFullsnapshot as fullSnapshotEvent & { timestamp: number }, ); @@ -268,6 +275,9 @@ export class Replayer { ); }, 1); } + if (this.service.state.context.events.find(indicatesTouchDevice)) { + this.mouse.classList.add('touch-device'); + } } public on(event: string, handler: Handler) { @@ -275,6 +285,11 @@ export class Replayer { return this; } + public off(event: string, handler: Handler) { + this.emitter.off(event, handler); + return this; + } + public setConfig(config: Partial) { Object.keys(config).forEach((key) => { // @ts-ignore @@ -330,6 +345,10 @@ export class Replayer { return baselineTime - events[0].timestamp; } + public getMirror(): Mirror { + return this.mirror; + } + /** * This API was designed to be used as play at any time offset. * Since we minimized the data collected from recorder, we do not @@ -382,6 +401,9 @@ export class Replayer { const event = this.config.unpackFn ? this.config.unpackFn(rawEvent as string) : (rawEvent as eventWithTime); + if (indicatesTouchDevice(event)) { + this.mouse.classList.add('touch-device'); + } Promise.resolve().then(() => this.service.send({ type: 'ADD_EVENT', payload: { event } }), ); @@ -397,6 +419,14 @@ export class Replayer { this.iframe.style.pointerEvents = 'none'; } + /** + * Empties the replayer's cache and reclaims memory. + * The replayer will use this cache to speed up the playback. + */ + public resetCache() { + this.cache = createCache(); + } + private setupDom() { this.wrapper = document.createElement('div'); this.wrapper.classList.add('replayer-wrapper'); @@ -429,7 +459,7 @@ export class Replayer { this.iframe.contentDocument, ); - polyfill(this.iframe.contentWindow as Window & typeof globalThis); + polyfill(this.iframe.contentWindow as IWindow); } } @@ -444,6 +474,49 @@ export class Replayer { } } + private applyEventsSynchronously(events: Array) { + for (const event of events) { + switch (event.type) { + case EventType.DomContentLoaded: + case EventType.Load: + case EventType.Custom: + continue; + case EventType.FullSnapshot: + case EventType.Meta: + case EventType.Plugin: + break; + case EventType.IncrementalSnapshot: + switch (event.data.source) { + case IncrementalSource.MediaInteraction: + continue; + default: + break; + } + break; + default: + break; + } + const castFn = this.getCastFn(event, true); + castFn(); + } + if (this.mousePos) { + this.moveAndHover( + this.mousePos.x, + this.mousePos.y, + this.mousePos.id, + true, + this.mousePos.debugData, + ); + } + this.mousePos = null; + if (this.touchActive === true) { + this.mouse.classList.add('touch-active'); + } else if (this.touchActive === false) { + this.mouse.classList.remove('touch-active'); + } + this.touchActive = null; + } + private getCastFn(event: eventWithTime, isSync = false) { let castFn: undefined | (() => void); switch (event.type) { @@ -469,9 +542,15 @@ export class Replayer { break; case EventType.FullSnapshot: castFn = () => { - // Don't build a full snapshot during the first play through since we've already built it when the player was mounted. - if (this.firstPlayedEvent && this.firstPlayedEvent === event) { - return; + if (this.firstFullSnapshot) { + if (this.firstFullSnapshot === event) { + // we've already built this exact FullSnapshot when the player was mounted, and haven't built any other FullSnapshot since + this.firstFullSnapshot = true; // forget as we might need to re-execute this FullSnapshot later e.g. to rebuild after scrubbing + return; + } + } else { + // Timer (requestAnimationFrame) can be faster than setTimeout(..., 1) + this.firstFullSnapshot = true; } this.rebuildFullSnapshot(event, isSync); this.iframe.contentWindow!.scrollTo(event.data.initialOffset); @@ -525,16 +604,21 @@ export class Replayer { if (castFn) { castFn(); } + + for (const plugin of this.config.plugins || []) { + plugin.handler(event, isSync, { replayer: this }); + } + this.service.send({ type: 'CAST_EVENT', payload: { event } }); // events are kept sorted by timestamp, check if this is the last event - if ( - event === - this.service.state.context.events[ - this.service.state.context.events.length - 1 - ] - ) { + let last_index = this.service.state.context.events.length - 1; + if (event === this.service.state.context.events[last_index]) { const finish = () => { + if (last_index < this.service.state.context.events.length - 1) { + // more events have been added since the setTimeout + return; + } this.backToNormal(); this.service.send('END'); this.emitter.emit(ReplayerEvents.Finish); @@ -552,6 +636,8 @@ export class Replayer { finish(); } } + + this.emitter.emit(ReplayerEvents.EventCast, event); }; return wrappedCastFn; } @@ -571,21 +657,18 @@ export class Replayer { } this.legacy_missingNodeRetryMap = {}; const collected: AppendedIframe[] = []; - mirror.map = rebuild(event.data.node, { + this.mirror.map = rebuild(event.data.node, { doc: this.iframe.contentDocument, afterAppend: (builtNode) => { this.collectIframeAndAttachDocument(collected, builtNode); }, + cache: this.cache, })[1]; for (const { mutationInQueue, builtNode } of collected) { this.attachDocumentToIframe(mutationInQueue, builtNode); this.newDocumentQueue = this.newDocumentQueue.filter( (m) => m !== mutationInQueue, ); - if (builtNode.contentDocument) { - const { documentElement, head } = builtNode.contentDocument; - this.insertStyleRules(documentElement, head); - } } const { documentElement, head } = this.iframe.contentDocument; this.insertStyleRules(documentElement, head); @@ -614,7 +697,7 @@ export class Replayer { ).concat(this.config.insertStyleRules); if (this.config.pauseAnimation) { injectStylesRules.push( - 'html.rrweb-paused * { animation-play-state: paused !important; }', + 'html.rrweb-paused *, html.rrweb-paused *:before, html.rrweb-paused *:after { animation-play-state: paused !important; }', ); } for (let idx = 0; idx < injectStylesRules.length; idx++) { @@ -627,24 +710,42 @@ export class Replayer { iframeEl: HTMLIFrameElement, ) { const collected: AppendedIframe[] = []; + // If iframeEl is detached from dom, iframeEl.contentDocument is null. + if (!iframeEl.contentDocument) { + let parent = iframeEl.parentNode; + while (parent) { + // The parent of iframeEl is virtual parent and we need to mount it on the dom. + if (this.fragmentParentMap.has((parent as unknown) as INode)) { + const frag = (parent as unknown) as INode; + const realParent = this.fragmentParentMap.get(frag)!; + this.restoreRealParent(frag, realParent); + break; + } + parent = parent.parentNode; + } + } buildNodeWithSN(mutation.node, { doc: iframeEl.contentDocument!, - map: mirror.map, + map: this.mirror.map, hackCss: true, skipChild: false, afterAppend: (builtNode) => { this.collectIframeAndAttachDocument(collected, builtNode); + if ( + builtNode.__sn.type === NodeType.Element && + builtNode.__sn.tagName.toUpperCase() === 'HTML' + ) { + const { documentElement, head } = iframeEl.contentDocument!; + this.insertStyleRules(documentElement, head); + } }, + cache: this.cache, }); for (const { mutationInQueue, builtNode } of collected) { this.attachDocumentToIframe(mutationInQueue, builtNode); this.newDocumentQueue = this.newDocumentQueue.filter( (m) => m !== mutationInQueue, ); - if (builtNode.contentDocument) { - const { documentElement, head } = builtNode.contentDocument; - this.insertStyleRules(documentElement, head); - } } } @@ -669,7 +770,7 @@ export class Replayer { const head = this.iframe.contentDocument?.head; if (head) { const unloadSheets: Set = new Set(); - let timer: number; + let timer: ReturnType | -1; let beforeLoadState = this.service.state; const stateHandler = () => { beforeLoadState = this.service.state; @@ -694,7 +795,7 @@ export class Replayer { } this.emitter.emit(ReplayerEvents.LoadStylesheetEnd); if (timer) { - window.clearTimeout(timer); + clearTimeout(timer); } unsubscribe(); } @@ -706,7 +807,7 @@ export class Replayer { // find some unload sheets after iterate this.service.send({ type: 'PAUSE' }); this.emitter.emit(ReplayerEvents.LoadStylesheetStart); - timer = window.setTimeout(() => { + timer = setTimeout(() => { if (beforeLoadState.matches('playing')) { this.play(this.getCurrentTime()); } @@ -718,6 +819,37 @@ export class Replayer { } } + private hasImageArg(args: any[]): boolean { + for (const arg of args) { + if (!arg || typeof arg !== 'object') { + // do nothing + } else if ('rr_type' in arg && 'args' in arg) { + if (this.hasImageArg(arg.args)) return true; + } else if ('rr_type' in arg && arg.rr_type === 'HTMLImageElement') { + return true; // has image! + } else if (arg instanceof Array) { + if (this.hasImageArg(arg)) return true; + } + } + return false; + } + + private getImageArgs(args: any[]): string[] { + const images: string[] = []; + for (const arg of args) { + if (!arg || typeof arg !== 'object') { + // do nothing + } else if ('rr_type' in arg && 'args' in arg) { + images.push(...this.getImageArgs(arg.args)); + } else if ('rr_type' in arg && arg.rr_type === 'HTMLImageElement') { + images.push(arg.src); + } else if (arg instanceof Array) { + images.push(...this.getImageArgs(arg)); + } + } + return images; + } + /** * pause when there are some canvas drawImage args need to be loaded */ @@ -728,37 +860,37 @@ export class Replayer { }; this.emitter.on(ReplayerEvents.Start, stateHandler); this.emitter.on(ReplayerEvents.Pause, stateHandler); - const unsubscribe = () => { - this.emitter.off(ReplayerEvents.Start, stateHandler); - this.emitter.off(ReplayerEvents.Pause, stateHandler); - }; - let count = 0; - let resolved = 0; for (const event of this.service.state.context.events) { if ( event.type === EventType.IncrementalSnapshot && - event.data.source === IncrementalSource.CanvasMutation && - event.data.property === 'drawImage' && - typeof event.data.args[0] === 'string' && - !this.imageMap.has(event) - ) { - count++; - const image = document.createElement('img'); - image.src = event.data.args[0]; - this.imageMap.set(event, image); - image.onload = () => { - resolved++; - if (resolved === count) { - if (beforeLoadState.matches('playing')) { - this.play(this.getCurrentTime()); - } - unsubscribe(); - } - }; - } + event.data.source === IncrementalSource.CanvasMutation + ) + if ('commands' in event.data) { + event.data.commands.forEach((c) => this.preloadImages(c, event)); + } else { + this.preloadImages(event.data, event); + } } - if (count !== resolved) { - this.service.send({ type: 'PAUSE' }); + } + + private preloadImages(data: canvasMutationCommand, event: eventWithTime) { + if ( + data.property === 'drawImage' && + typeof data.args[0] === 'string' && + !this.imageMap.has(event) + ) { + const canvas = document.createElement('canvas'); + const ctx = canvas.getContext('2d'); + const imgd = ctx?.createImageData(canvas.width, canvas.height); + let d = imgd?.data; + d = JSON.parse(data.args[0]); + ctx?.putImageData(imgd!, 0, 0); + } else if (this.hasImageArg(data.args)) { + this.getImageArgs(data.args).forEach((url) => { + const image = new Image(); + image.src = url; // this preloads the image + this.imageMap.set(url, image); + }); } } @@ -771,22 +903,42 @@ export class Replayer { case IncrementalSource.Mutation: { if (isSync) { d.adds.forEach((m) => this.treeIndex.add(m)); - d.texts.forEach((m) => this.treeIndex.text(m)); + d.texts.forEach((m) => { + const target = this.mirror.getNode(m.id); + const parent = (target?.parentNode as unknown) as INode | null; + // remove any style rules that pending + // for stylesheets where the contents get replaced + if (parent && this.virtualStyleRulesMap.has(parent)) + this.virtualStyleRulesMap.delete(parent); + + this.treeIndex.text(m); + }); d.attributes.forEach((m) => this.treeIndex.attribute(m)); - d.removes.forEach((m) => this.treeIndex.remove(m)); + d.removes.forEach((m) => this.treeIndex.remove(m, this.mirror)); + } + try { + this.applyMutation(d, isSync); + } catch (error) { + this.warn(`Exception in mutation ${error.message || error}`, d); } - this.applyMutation(d, isSync); break; } + case IncrementalSource.Drag: + case IncrementalSource.TouchMove: case IncrementalSource.MouseMove: if (isSync) { const lastPosition = d.positions[d.positions.length - 1]; - this.moveAndHover(d, lastPosition.x, lastPosition.y, lastPosition.id); + this.mousePos = { + x: lastPosition.x, + y: lastPosition.y, + id: lastPosition.id, + debugData: d, + }; } else { d.positions.forEach((p) => { const action = { doAction: () => { - this.moveAndHover(d, p.x, p.y, p.id); + this.moveAndHover(p.x, p.y, p.id, isSync, d); }, delay: p.timeOffset + @@ -810,7 +962,7 @@ export class Replayer { break; } const event = new Event(MouseInteractions[d.type].toLowerCase()); - const target = mirror.getNode(d.id); + const target = this.mirror.getNode(d.id); if (!target) { return this.debugNodeNotFound(d, d.id); } @@ -835,19 +987,50 @@ export class Replayer { case MouseInteractions.Click: case MouseInteractions.TouchStart: case MouseInteractions.TouchEnd: - /** - * Click has no visual impact when replaying and may - * trigger navigation when apply to an link. - * So we will not call click(), instead we add an - * animation to the mouse element which indicate user - * clicked at this moment. - */ - if (!isSync) { - this.moveAndHover(d, d.x, d.y, d.id); - this.mouse.classList.remove('active'); - // tslint:disable-next-line - void this.mouse.offsetWidth; - this.mouse.classList.add('active'); + if (isSync) { + if (d.type === MouseInteractions.TouchStart) { + this.touchActive = true; + } else if (d.type === MouseInteractions.TouchEnd) { + this.touchActive = false; + } + this.mousePos = { + x: d.x, + y: d.y, + id: d.id, + debugData: d, + }; + } else { + if (d.type === MouseInteractions.TouchStart) { + // don't draw a trail as user has lifted finger and is placing at a new point + this.tailPositions.length = 0; + } + this.moveAndHover(d.x, d.y, d.id, isSync, d); + if (d.type === MouseInteractions.Click) { + /* + * don't want target.click() here as could trigger an iframe navigation + * instead any effects of the click should already be covered by mutations + */ + /* + * removal and addition of .active class (along with void line to trigger repaint) + * triggers the 'click' css animation in styles/style.css + */ + this.mouse.classList.remove('active'); + // tslint:disable-next-line + void this.mouse.offsetWidth; + this.mouse.classList.add('active'); + } else if (d.type === MouseInteractions.TouchStart) { + void this.mouse.offsetWidth; // needed for the position update of moveAndHover to apply without the .touch-active transition + this.mouse.classList.add('touch-active'); + } else if (d.type === MouseInteractions.TouchEnd) { + this.mouse.classList.remove('touch-active'); + } + } + break; + case MouseInteractions.TouchCancel: + if (isSync) { + this.touchActive = false; + } else { + this.mouse.classList.remove('touch-active'); } break; default: @@ -866,7 +1049,7 @@ export class Replayer { this.treeIndex.scroll(d); break; } - this.applyScroll(d); + this.applyScroll(d, false); break; } case IncrementalSource.ViewportResize: @@ -893,23 +1076,30 @@ export class Replayer { break; } case IncrementalSource.MediaInteraction: { - const target = mirror.getNode(d.id); + const target = this.mirror.getNode(d.id); if (!target) { return this.debugNodeNotFound(d, d.id); } const mediaEl = (target as Node) as HTMLMediaElement; try { + if (d.currentTime) { + mediaEl.currentTime = d.currentTime; + } + if (d.volume) { + mediaEl.volume = d.volume; + } + if (d.muted) { + mediaEl.muted = d.muted; + } if (d.type === MediaInteractions.Pause) { mediaEl.pause(); } if (d.type === MediaInteractions.Play) { - if (mediaEl.readyState >= HTMLMediaElement.HAVE_CURRENT_DATA) { - mediaEl.play(); - } else { - mediaEl.addEventListener('canplay', () => { - mediaEl.play(); - }); - } + // remove listener for 'canplay' event because play() is async and returns a promise + // i.e. media will evntualy start to play when data is loaded + // 'canplay' event fires even when currentTime attribute changes which may lead to + // unexpeted behavior + mediaEl.play(); } } catch (error) { if (this.config.showWarning) { @@ -921,7 +1111,7 @@ export class Replayer { break; } case IncrementalSource.StyleSheetRule: { - const target = mirror.getNode(d.id); + const target = this.mirror.getNode(d.id); if (!target) { return this.debugNodeNotFound(d, d.id); } @@ -929,102 +1119,171 @@ export class Replayer { const styleEl = (target as Node) as HTMLStyleElement; const parent = (target.parentNode as unknown) as INode; const usingVirtualParent = this.fragmentParentMap.has(parent); - let placeholderNode; - if (usingVirtualParent) { + /** + * Always use existing DOM node, when it's there. + * In in-memory replay, there is virtual node, but it's `sheet` is inaccessible. + * Hence, we buffer all style changes in virtualStyleRulesMap. + */ + const styleSheet = usingVirtualParent ? null : styleEl.sheet; + let rules: VirtualStyleRules; + + if (!styleSheet) { /** * styleEl.sheet is only accessible if the styleEl is part of the - * dom. This doesn't work on DocumentFragments so we have to re-add - * it to the dom temporarily. + * dom. This doesn't work on DocumentFragments so we have to add the + * style mutations to the virtualStyleRulesMap. */ - const domParent = this.fragmentParentMap.get( - (target.parentNode as unknown) as INode, - ); - placeholderNode = document.createTextNode(''); - parent.replaceChild(placeholderNode, target); - domParent!.appendChild(target); - } - const styleSheet: CSSStyleSheet = styleEl.sheet!; + if (this.virtualStyleRulesMap.has(target)) { + rules = this.virtualStyleRulesMap.get(target) as VirtualStyleRules; + } else { + rules = []; + this.virtualStyleRulesMap.set(target, rules); + } + } if (d.adds) { - d.adds.forEach(({ rule, index }) => { - try { - const _index = - index === undefined - ? undefined - : Math.min(index, styleSheet.rules.length); + d.adds.forEach(({ rule, index: nestedIndex }) => { + if (styleSheet) { try { - styleSheet.insertRule(rule, _index); + if (Array.isArray(nestedIndex)) { + const { positions, index } = getPositionsAndIndex( + nestedIndex, + ); + const nestedRule = getNestedRule( + styleSheet.cssRules, + positions, + ); + nestedRule.insertRule(rule, index); + } else { + const index = + nestedIndex === undefined + ? undefined + : Math.min(nestedIndex, styleSheet.cssRules.length); + styleSheet.insertRule(rule, index); + } } catch (e) { /** * sometimes we may capture rules with browser prefix * insert rule with prefixs in other browsers may cause Error */ + /** + * accessing styleSheet rules may cause SecurityError + * for specific access control settings + */ } - } catch (e) { - /** - * accessing styleSheet rules may cause SecurityError - * for specific access control settings - */ + } else { + rules?.push({ + cssText: rule, + index: nestedIndex, + type: StyleRuleType.Insert, + }); } }); } if (d.removes) { - d.removes.forEach(({ index }) => { - try { - styleSheet.deleteRule(index); - } catch (e) { - /** - * same as insertRule - */ + d.removes.forEach(({ index: nestedIndex }) => { + if (usingVirtualParent) { + rules?.push({ index: nestedIndex, type: StyleRuleType.Remove }); + } else { + try { + if (Array.isArray(nestedIndex)) { + const { positions, index } = getPositionsAndIndex( + nestedIndex, + ); + const nestedRule = getNestedRule( + styleSheet!.cssRules, + positions, + ); + nestedRule.deleteRule(index || 0); + } else { + styleSheet?.deleteRule(nestedIndex); + } + } catch (e) { + /** + * same as insertRule + */ + } } }); } + break; + } + case IncrementalSource.StyleDeclaration: { + // same with StyleSheetRule + const target = this.mirror.getNode(d.id); + if (!target) { + return this.debugNodeNotFound(d, d.id); + } + + const styleEl = (target as Node) as HTMLStyleElement; + const parent = (target.parentNode as unknown) as INode; + const usingVirtualParent = this.fragmentParentMap.has(parent); + + const styleSheet = usingVirtualParent ? null : styleEl.sheet; + let rules: VirtualStyleRules = []; - if (usingVirtualParent && placeholderNode) { - parent.replaceChild(target, placeholderNode); + if (!styleSheet) { + if (this.virtualStyleRulesMap.has(target)) { + rules = this.virtualStyleRulesMap.get(target) as VirtualStyleRules; + } else { + rules = []; + this.virtualStyleRulesMap.set(target, rules); + } + } + + if (d.set) { + if (styleSheet) { + const rule = (getNestedRule( + styleSheet.rules, + d.index, + ) as unknown) as CSSStyleRule; + rule.style.setProperty(d.set.property, d.set.value, d.set.priority); + } else { + rules.push({ + type: StyleRuleType.SetProperty, + index: d.index, + ...d.set, + }); + } } + if (d.remove) { + if (styleSheet) { + const rule = (getNestedRule( + styleSheet.rules, + d.index, + ) as unknown) as CSSStyleRule; + rule.style.removeProperty(d.remove.property); + } else { + rules.push({ + type: StyleRuleType.RemoveProperty, + index: d.index, + ...d.remove, + }); + } + } break; } case IncrementalSource.CanvasMutation: { if (!this.config.UNSAFE_replayCanvas) { return; } - const target = mirror.getNode(d.id); + const target = this.mirror.getNode(d.id); if (!target) { return this.debugNodeNotFound(d, d.id); } - try { - const ctx = ((target as unknown) as HTMLCanvasElement).getContext( - '2d', - )!; - if (d.setter) { - // skip some read-only type checks - // tslint:disable-next-line:no-any - (ctx as any)[d.property] = d.args[0]; - return; - } - const original = ctx[ - d.property as keyof CanvasRenderingContext2D - ] as Function; - /** - * We have serialized the image source into base64 string during recording, - * which has been preloaded before replay. - * So we can get call drawImage SYNCHRONOUSLY which avoid some fragile cast. - */ - if (d.property === 'drawImage' && typeof d.args[0] === 'string') { - const image = this.imageMap.get(e); - d.args[0] = image; - original.apply(ctx, d.args); - } else { - original.apply(ctx, d.args); - } - } catch (error) { - this.warnCanvasMutationFailed(d, d.id, error); - } + + canvasMutation({ + event: e, + mutation: d, + target: (target as unknown) as HTMLCanvasElement, + imageMap: this.imageMap, + errorHandler: this.warnCanvasMutationFailed.bind(this), + }); + break; } case IncrementalSource.Font: { @@ -1042,30 +1301,26 @@ export class Replayer { } break; } - case IncrementalSource.Log: { - try { - const logData = e.data as logData; - const replayLogger = this.config.logConfig.replayLogger!; - if (typeof replayLogger[logData.level] === 'function') { - replayLogger[logData.level]!(logData); - } - } catch (error) { - if (this.config.showWarning) { - console.warn(error); - } - } - } default: } } private applyMutation(d: mutationData, useVirtualParent: boolean) { d.removes.forEach((mutation) => { - const target = mirror.getNode(mutation.id); + let target = this.mirror.getNode(mutation.id); if (!target) { + if (d.removes.find((r) => r.id === mutation.parentId)) { + // no need to warn, parent was already removed + return; + } return this.warnNodeNotFound(d, mutation.id); } - let parent: INode | null | ShadowRoot = mirror.getNode(mutation.parentId); + if (this.virtualStyleRulesMap.has(target)) { + this.virtualStyleRulesMap.delete(target); + } + let parent: INode | null | ShadowRoot = this.mirror.getNode( + mutation.parentId, + ); if (!parent) { return this.warnNodeNotFound(d, mutation.parentId); } @@ -1073,22 +1328,37 @@ export class Replayer { parent = parent.shadowRoot; } // target may be removed with its parents before - mirror.removeNodeFromMap(target); + this.mirror.removeNodeFromMap(target); if (parent) { + let realTarget = null; const realParent = '__sn' in parent ? this.fragmentParentMap.get(parent) : undefined; if (realParent && realParent.contains(target)) { - realParent.removeChild(target); + parent = realParent; } else if (this.fragmentParentMap.has(target)) { /** * the target itself is a fragment document and it's not in the dom * so we should remove the real target from its parent */ - const realTarget = this.fragmentParentMap.get(target)!; - parent.removeChild(realTarget); + realTarget = this.fragmentParentMap.get(target)!; this.fragmentParentMap.delete(target); - } else { + target = realTarget; + } + try { parent.removeChild(target); + } catch (error) { + if (error instanceof DOMException) { + this.warn( + 'parent could not remove child in mutation', + parent, + realParent, + target, + realTarget, + d, + ); + } else { + throw error; + } } } }); @@ -1100,10 +1370,10 @@ export class Replayer { const queue: addedNodeMutation[] = []; // next not present at this moment - function nextNotInDOM(mutation: addedNodeMutation) { + const nextNotInDOM = (mutation: addedNodeMutation) => { let next: Node | null = null; if (mutation.nextId) { - next = mirror.getNode(mutation.nextId) as Node; + next = this.mirror.getNode(mutation.nextId) as Node; } // next not present at this moment if ( @@ -1115,13 +1385,15 @@ export class Replayer { return true; } return false; - } + }; const appendNode = (mutation: addedNodeMutation) => { if (!this.iframe.contentDocument) { return console.warn('Looks like your replayer has been destroyed.'); } - let parent: INode | null | ShadowRoot = mirror.getNode(mutation.parentId); + let parent: INode | null | ShadowRoot = this.mirror.getNode( + mutation.parentId, + ); if (!parent) { if (mutation.node.type === NodeType.Document) { // is newly added document, maybe the document node of an iframe @@ -1139,10 +1411,21 @@ export class Replayer { parentInDocument = this.iframe.contentDocument.body.contains(parent); } - // if parent element is an iframe, iframe document can't be appended to virtual parent - if (useVirtualParent && parentInDocument && !isIframeINode(parent)) { + const hasIframeChild = + ((parent as unknown) as HTMLElement).getElementsByTagName?.('iframe') + .length > 0; + /** + * Why !isIframeINode(parent)? If parent element is an iframe, iframe document can't be appended to virtual parent. + * Why !hasIframeChild? If we move iframe elements from dom to fragment document, we will lose the contentDocument of iframe. So we need to disable the virtual dom optimization if a parent node contains iframe elements. + */ + if ( + useVirtualParent && + parentInDocument && + !isIframeINode(parent) && + !hasIframeChild + ) { const virtualParent = (document.createDocumentFragment() as unknown) as INode; - mirror.map[mutation.parentId] = virtualParent; + this.mirror.map[mutation.parentId] = virtualParent; this.fragmentParentMap.set(virtualParent, parent); // store the state, like scroll position, of child nodes before they are unmounted from dom @@ -1154,28 +1437,32 @@ export class Replayer { parent = virtualParent; } - if (mutation.node.isShadow && hasShadowRoot(parent)) { - parent = parent.shadowRoot; + if (mutation.node.isShadow) { + // If the parent is attached a shadow dom after it's created, it won't have a shadow root. + if (!hasShadowRoot(parent)) { + ((parent as Node) as HTMLElement).attachShadow({ mode: 'open' }); + parent = ((parent as Node) as HTMLElement).shadowRoot!; + } else parent = parent.shadowRoot; } let previous: Node | null = null; let next: Node | null = null; if (mutation.previousId) { - previous = mirror.getNode(mutation.previousId) as Node; + previous = this.mirror.getNode(mutation.previousId) as Node; } if (mutation.nextId) { - next = mirror.getNode(mutation.nextId) as Node; + next = this.mirror.getNode(mutation.nextId) as Node; } if (nextNotInDOM(mutation)) { return queue.push(mutation); } - if (mutation.node.rootId && !mirror.getNode(mutation.node.rootId)) { + if (mutation.node.rootId && !this.mirror.getNode(mutation.node.rootId)) { return; } const targetDoc = mutation.node.rootId - ? mirror.getNode(mutation.node.rootId) + ? this.mirror.getNode(mutation.node.rootId) : this.iframe.contentDocument; if (isIframeINode(parent)) { this.attachDocumentToIframe(mutation, parent); @@ -1183,9 +1470,10 @@ export class Replayer { } const target = buildNodeWithSN(mutation.node, { doc: targetDoc as Document, - map: mirror.map, + map: this.mirror.map, skipChild: true, hackCss: true, + cache: this.cache, }) as INode; // legacy data, we should not have -1 siblings any more @@ -1197,6 +1485,21 @@ export class Replayer { return; } + if ( + '__sn' in parent && + parent.__sn.type === NodeType.Element && + parent.__sn.tagName === 'textarea' && + mutation.node.type === NodeType.Text + ) { + // https://github.com/rrweb-io/rrweb/issues/745 + // parent is textarea, will only keep one child node as the value + for (const c of Array.from(parent.childNodes)) { + if (c.nodeType === parent.TEXT_NODE) { + parent.removeChild(c); + } + } + } + if (previous && previous.nextSibling && previous.nextSibling.parentNode) { parent.insertBefore(target, previous.nextSibling); } else if (next && next.parentNode) { @@ -1228,10 +1531,6 @@ export class Replayer { (m) => m !== mutationInQueue, ); } - if (target.contentDocument) { - const { documentElement, head } = target.contentDocument; - this.insertStyleRules(documentElement, head); - } } if (mutation.previousId || mutation.nextId) { @@ -1261,7 +1560,7 @@ export class Replayer { break; } for (const tree of resolveTrees) { - let parent = mirror.getNode(tree.value.parentId); + let parent = this.mirror.getNode(tree.value.parentId); if (!parent) { this.debug( 'Drop resolve tree since there is no parent for the root node.', @@ -1280,8 +1579,12 @@ export class Replayer { } d.texts.forEach((mutation) => { - let target = mirror.getNode(mutation.id); + let target = this.mirror.getNode(mutation.id); if (!target) { + if (d.removes.find((r) => r.id === mutation.id)) { + // no need to warn, element was already removed + return; + } return this.warnNodeNotFound(d, mutation.id); } /** @@ -1293,8 +1596,12 @@ export class Replayer { target.textContent = mutation.value; }); d.attributes.forEach((mutation) => { - let target = mirror.getNode(mutation.id); + let target = this.mirror.getNode(mutation.id); if (!target) { + if (d.removes.find((r) => r.id === mutation.id)) { + // no need to warn, element was already removed + return; + } return this.warnNodeNotFound(d, mutation.id); } if (this.fragmentParentMap.has(target)) { @@ -1303,18 +1610,32 @@ export class Replayer { for (const attributeName in mutation.attributes) { if (typeof attributeName === 'string') { const value = mutation.attributes[attributeName]; - try { - if (value !== null) { + if (value === null) { + ((target as Node) as Element).removeAttribute(attributeName); + } else if (typeof value === 'string') { + try { ((target as Node) as Element).setAttribute(attributeName, value); - } else { - ((target as Node) as Element).removeAttribute(attributeName); + } catch (error) { + if (this.config.showWarning) { + console.warn( + 'An error occurred may due to the checkout feature.', + error, + ); + } } - } catch (error) { - if (this.config.showWarning) { - console.warn( - 'An error occurred may due to the checkout feature.', - error, - ); + } else if (attributeName === 'style') { + let styleValues = value as styleAttributeValue; + const targetEl = (target as Node) as HTMLElement; + for (var s in styleValues) { + if (styleValues[s] === false) { + targetEl.style.removeProperty(s); + } else if (styleValues[s] instanceof Array) { + const svp = styleValues[s] as styleValueWithPriority; + targetEl.style.setProperty(s, svp[0], svp[1]); + } else { + const svs = styleValues[s] as string; + targetEl.style.setProperty(s, svs); + } } } } @@ -1322,8 +1643,14 @@ export class Replayer { }); } - private applyScroll(d: scrollData) { - const target = mirror.getNode(d.id); + /** + * Apply the scroll data on real elements. + * If the replayer is in sync mode, smooth scroll behavior should be disabled. + * @param d the scroll data + * @param isSync whether the replayer is in sync mode(fast-forward) + */ + private applyScroll(d: scrollData, isSync: boolean) { + const target = this.mirror.getNode(d.id); if (!target) { return this.debugNodeNotFound(d, d.id); } @@ -1331,7 +1658,14 @@ export class Replayer { this.iframe.contentWindow!.scrollTo({ top: d.y, left: d.x, - behavior: 'smooth', + behavior: isSync ? 'auto' : 'smooth', + }); + } else if (target.__sn.type === NodeType.Document) { + // nest iframe content document + ((target as unknown) as Document).defaultView!.scrollTo({ + top: d.y, + left: d.x, + behavior: isSync ? 'auto' : 'smooth', }); } else { try { @@ -1347,7 +1681,7 @@ export class Replayer { } private applyInput(d: inputData) { - const target = mirror.getNode(d.id); + const target = this.mirror.getNode(d.id); if (!target) { return this.debugNodeNotFound(d, d.id); } @@ -1359,57 +1693,16 @@ export class Replayer { } } - /** - * format the trace data to a string - * @param data the log data - */ - private formatMessage(data: logData): string { - if (data.trace.length === 0) { - return ''; + private applyText(d: textMutation, mutation: mutationData) { + const target = this.mirror.getNode(d.id); + if (!target) { + return this.debugNodeNotFound(mutation, d.id); } - const stackPrefix = '\n\tat '; - let result = stackPrefix; - result += data.trace.join(stackPrefix); - return result; - } - - /** - * generate a console log replayer which implement the interface ReplayLogger - */ - private getConsoleLogger(): ReplayLogger { - const replayLogger: ReplayLogger = {}; - for (const level of this.config.logConfig.level!) { - if (level === 'trace') { - replayLogger[level] = (data: logData) => { - const logger = ((console.log as unknown) as PatchedConsoleLog)[ - ORIGINAL_ATTRIBUTE_NAME - ] - ? ((console.log as unknown) as PatchedConsoleLog)[ - ORIGINAL_ATTRIBUTE_NAME - ] - : console.log; - logger( - ...data.payload.map((s) => JSON.parse(s)), - this.formatMessage(data), - ); - }; - } else { - replayLogger[level] = (data: logData) => { - const logger = ((console[level] as unknown) as PatchedConsoleLog)[ - ORIGINAL_ATTRIBUTE_NAME - ] - ? ((console[level] as unknown) as PatchedConsoleLog)[ - ORIGINAL_ATTRIBUTE_NAME - ] - : console[level]; - logger( - ...data.payload.map((s) => JSON.parse(s)), - this.formatMessage(data), - ); - }; - } + try { + ((target as Node) as HTMLElement).textContent = d.value; + } catch (error) { + // for safe } - return replayLogger; } private legacy_resolveMissingNode( @@ -1441,10 +1734,16 @@ export class Replayer { } } - private moveAndHover(d: incrementalData, x: number, y: number, id: number) { - const target = mirror.getNode(id); + private moveAndHover( + x: number, + y: number, + id: number, + isSync: boolean, + debugData: incrementalData, + ) { + const target = this.mirror.getNode(id); if (!target) { - return this.debugNodeNotFound(d, id); + return this.debugNodeNotFound(debugData, id); } const base = getBaseDimension(target, this.iframe); @@ -1453,7 +1752,9 @@ export class Replayer { this.mouse.style.left = `${_x}px`; this.mouse.style.top = `${_y}px`; - this.drawMouseTail({ x: _x, y: _y }); + if (!isSync) { + this.drawMouseTail({ x: _x, y: _y }); + } this.hoverElements((target as Node) as Element); } @@ -1529,6 +1830,29 @@ export class Replayer { }); } + /** + * Replace the virtual parent with the real parent. + * @param frag fragment document, the virtual parent + * @param parent real parent element + */ + private restoreRealParent(frag: INode, parent: INode) { + this.mirror.map[parent.__sn.id] = parent; + /** + * If we have already set value attribute on textarea, + * then we could not apply text content as default value any more. + */ + if ( + parent.__sn.type === NodeType.Element && + parent.__sn.tagName === 'textarea' && + frag.textContent + ) { + ((parent as unknown) as HTMLTextAreaElement).value = frag.textContent; + } + parent.appendChild(frag); + // restore state of elements after they are mounted + this.restoreState(parent); + } + /** * store state of elements before unmounted from dom recursively * the state should be restored in the handler of event ReplayerEvents.Flush @@ -1544,6 +1868,11 @@ export class Replayer { scroll: [parentElement.scrollLeft, parentElement.scrollTop], }); } + if (parentElement.tagName === 'STYLE') + storeCSSRules( + parentElement as HTMLStyleElement, + this.virtualStyleRulesMap, + ); const children = parentElement.children; for (const child of Array.from(children)) { this.storeState((child as unknown) as INode); @@ -1575,16 +1904,34 @@ export class Replayer { } } + private restoreNodeSheet(node: INode) { + const storedRules = this.virtualStyleRulesMap.get(node); + if (node.nodeName !== 'STYLE') { + return; + } + + if (!storedRules) { + return; + } + + const styleNode = (node as unknown) as HTMLStyleElement; + + applyVirtualStyleRulesToNode(storedRules, styleNode); + } + private warnNodeNotFound(d: incrementalData, id: number) { - this.warn(`Node with id '${id}' not found in`, d); + if (this.treeIndex.idRemoved(id)) { + this.warn(`Node with id '${id}' was previously removed. `, d); + } else { + this.warn(`Node with id '${id}' not found. `, d); + } } private warnCanvasMutationFailed( - d: canvasMutationData, - id: number, + d: canvasMutationData | canvasMutationCommand, error: unknown, ) { - this.warn(`Has error on update canvas '${id}'`, d, error); + this.warn(`Has error on canvas update`, error, 'canvas mutation:', d); } private debugNodeNotFound(d: incrementalData, id: number) { @@ -1594,7 +1941,15 @@ export class Replayer { * is microtask, so events fired on a removed DOM may emit * snapshots in the reverse order. */ - this.debug(REPLAY_CONSOLE_PREFIX, `Node with id '${id}' not found in`, d); + if (this.treeIndex.idRemoved(id)) { + this.debug( + REPLAY_CONSOLE_PREFIX, + `Node with id '${id}' was previously removed. `, + d, + ); + } else { + this.debug(REPLAY_CONSOLE_PREFIX, `Node with id '${id}' not found. `, d); + } } private warn(...args: Parameters) { diff --git a/src/replay/machine.ts b/packages/rrweb/src/replay/machine.ts similarity index 95% rename from src/replay/machine.ts rename to packages/rrweb/src/replay/machine.ts index 9a9d38fa1f..8c5ca33f9e 100644 --- a/src/replay/machine.ts +++ b/packages/rrweb/src/replay/machine.ts @@ -9,7 +9,6 @@ import { IncrementalSource, } from '../types'; import { Timer, addDelay } from './timer'; -import { needCastInSyncMode } from '../utils'; export type PlayerContext = { events: eventWithTime[]; @@ -77,11 +76,12 @@ export function discardPriorSnapshots( type PlayerAssets = { emitter: Emitter; + applyEventsSynchronously(events: Array): void; getCastFn(event: eventWithTime, isSync: boolean): () => void; }; export function createPlayerService( context: PlayerContext, - { getCastFn, emitter }: PlayerAssets, + { getCastFn, applyEventsSynchronously, emitter }: PlayerAssets, ) { const playerMachine = createMachine( { @@ -167,6 +167,7 @@ export function createPlayerService( play(ctx) { const { timer, events, baselineTime, lastPlayedEvent } = ctx; timer.clear(); + for (const event of events) { // TODO: improve this API addDelay(event, baselineTime); @@ -186,6 +187,7 @@ export function createPlayerService( emitter.emit(ReplayerEvents.PlayBack); } + const syncEvents = new Array(); const actions = new Array(); for (const event of neededEvents) { if ( @@ -196,23 +198,19 @@ export function createPlayerService( ) { continue; } - const isSync = event.timestamp < baselineTime; - if (isSync && !needCastInSyncMode(event)) { - continue; - } - const castFn = getCastFn(event, isSync); - if (isSync) { - castFn(); + if (event.timestamp < baselineTime) { + syncEvents.push(event); } else { + const castFn = getCastFn(event, false); actions.push({ doAction: () => { castFn(); - emitter.emit(ReplayerEvents.EventCast, event); }, delay: event.delay!, }); } } + applyEventsSynchronously(syncEvents); emitter.emit(ReplayerEvents.Flush); timer.addActions(actions); timer.start(); @@ -271,7 +269,6 @@ export function createPlayerService( timer.addAction({ doAction: () => { castFn(); - emitter.emit(ReplayerEvents.EventCast, event); }, delay: event.delay!, }); diff --git a/src/replay/smoothscroll.ts b/packages/rrweb/src/replay/smoothscroll.ts similarity index 100% rename from src/replay/smoothscroll.ts rename to packages/rrweb/src/replay/smoothscroll.ts diff --git a/src/replay/styles/inject-style.ts b/packages/rrweb/src/replay/styles/inject-style.ts similarity index 74% rename from src/replay/styles/inject-style.ts rename to packages/rrweb/src/replay/styles/inject-style.ts index b8b7f09f75..f501856194 100644 --- a/src/replay/styles/inject-style.ts +++ b/packages/rrweb/src/replay/styles/inject-style.ts @@ -1,5 +1,5 @@ const rules: (blockClass: string) => string[] = (blockClass: string) => [ - `.${blockClass} { background: #ccc }`, + `.${blockClass} { background: currentColor }`, 'noscript { display: none !important; }', ]; diff --git a/src/replay/styles/style.css b/packages/rrweb/src/replay/styles/style.css similarity index 54% rename from src/replay/styles/style.css rename to packages/rrweb/src/replay/styles/style.css index 9ed07b7981..b459e51578 100644 --- a/src/replay/styles/style.css +++ b/packages/rrweb/src/replay/styles/style.css @@ -5,25 +5,48 @@ position: absolute; width: 20px; height: 20px; - transition: 0.05s linear; + transition: left 0.05s linear, top 0.05s linear; background-size: contain; background-position: center center; background-repeat: no-repeat; background-image: url('data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9JzMwMHB4JyB3aWR0aD0nMzAwcHgnICBmaWxsPSIjMDAwMDAwIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGRhdGEtbmFtZT0iTGF5ZXIgMSIgdmlld0JveD0iMCAwIDUwIDUwIiB4PSIwcHgiIHk9IjBweCI+PHRpdGxlPkRlc2lnbl90bnA8L3RpdGxlPjxwYXRoIGQ9Ik00OC43MSw0Mi45MUwzNC4wOCwyOC4yOSw0NC4zMywxOEExLDEsMCwwLDAsNDQsMTYuMzlMMi4zNSwxLjA2QTEsMSwwLDAsMCwxLjA2LDIuMzVMMTYuMzksNDRhMSwxLDAsMCwwLDEuNjUuMzZMMjguMjksMzQuMDgsNDIuOTEsNDguNzFhMSwxLDAsMCwwLDEuNDEsMGw0LjM4LTQuMzhBMSwxLDAsMCwwLDQ4LjcxLDQyLjkxWm0tNS4wOSwzLjY3TDI5LDMyYTEsMSwwLDAsMC0xLjQxLDBsLTkuODUsOS44NUwzLjY5LDMuNjlsMzguMTIsMTRMMzIsMjcuNThBMSwxLDAsMCwwLDMyLDI5TDQ2LjU5LDQzLjYyWiI+PC9wYXRoPjwvc3ZnPg=='); + border-color: transparent; /* otherwise we transition from black when .touch-device class is added */ } .replayer-mouse::after { content: ''; display: inline-block; width: 20px; height: 20px; - border-radius: 10px; background: rgb(73, 80, 246); - transform: translate(-10px, -10px); + border-radius: 100%; + transform: translate(-50%, -50%); opacity: 0.3; } .replayer-mouse.active::after { animation: click 0.2s ease-in-out 1; } +.replayer-mouse.touch-device { + background-image: none; /* there's no passive cursor on touch-only screens */ + width: 70px; + height: 70px; + border-width: 4px; + border-style: solid; + border-radius: 100%; + margin-left: -37px; + margin-top: -37px; + border-color: rgba(73, 80, 246, 0); + transition: left 0s linear, top 0s linear, border-color 0.2s ease-in-out; +} +.replayer-mouse.touch-device.touch-active { + border-color: rgba(73, 80, 246, 1); + transition: left 0.25s linear, top 0.25s linear, border-color 0.2s ease-in-out; +} +.replayer-mouse.touch-device::after { + opacity: 0; /* there's no passive cursor on touch-only screens */ +} +.replayer-mouse.touch-device.active::after { + animation: touch-click 0.2s ease-in-out 1; +} .replayer-mouse-tail { position: absolute; pointer-events: none; @@ -34,14 +57,23 @@ opacity: 0.3; width: 20px; height: 20px; - border-radius: 10px; - transform: translate(-10px, -10px); } 50% { opacity: 0.5; width: 10px; height: 10px; - border-radius: 5px; - transform: translate(-5px, -5px); + } +} + +@keyframes touch-click { + 0% { + opacity: 0; + width: 20px; + height: 20px; + } + 50% { + opacity: 0.5; + width: 10px; + height: 10px; } } diff --git a/src/replay/timer.ts b/packages/rrweb/src/replay/timer.ts similarity index 95% rename from src/replay/timer.ts rename to packages/rrweb/src/replay/timer.ts index 2d2d7c236d..f6e7f82628 100644 --- a/src/replay/timer.ts +++ b/packages/rrweb/src/replay/timer.ts @@ -44,6 +44,7 @@ export class Timer { lastTimestamp = time; while (actions.length) { const action = actions[0]; + if (self.timeOffset >= action.delay) { actions.shift(); action.doAction(); @@ -88,7 +89,9 @@ export class Timer { } else if (this.actions[mid].delay > action.delay) { end = mid - 1; } else { - return mid; + // already an action with same delay (timestamp) + // the plus one will splice the new one after the existing one + return mid + 1; } } return start; @@ -109,6 +112,7 @@ export function addDelay(event: eventWithTime, baselineTime: number): number { event.delay = firstTimestamp - baselineTime; return firstTimestamp - baselineTime; } + event.delay = event.timestamp - baselineTime; return event.delay; } diff --git a/packages/rrweb/src/replay/virtual-styles.ts b/packages/rrweb/src/replay/virtual-styles.ts new file mode 100644 index 0000000000..f850df27c9 --- /dev/null +++ b/packages/rrweb/src/replay/virtual-styles.ts @@ -0,0 +1,188 @@ +import { INode } from 'rrweb-snapshot'; + +export enum StyleRuleType { + Insert, + Remove, + Snapshot, + SetProperty, + RemoveProperty, +} + +type InsertRule = { + cssText: string; + type: StyleRuleType.Insert; + index?: number | number[]; +}; +type RemoveRule = { + type: StyleRuleType.Remove; + index: number | number[]; +}; +type SnapshotRule = { + type: StyleRuleType.Snapshot; + cssTexts: string[]; +}; +type SetPropertyRule = { + type: StyleRuleType.SetProperty; + index: number[]; + property: string; + value: string | null; + priority: string | undefined; +}; +type RemovePropertyRule = { + type: StyleRuleType.RemoveProperty; + index: number[]; + property: string; +}; + +export type VirtualStyleRules = Array< + InsertRule | RemoveRule | SnapshotRule | SetPropertyRule | RemovePropertyRule +>; +export type VirtualStyleRulesMap = Map; + +export function getNestedRule( + rules: CSSRuleList, + position: number[], +): CSSGroupingRule { + const rule = rules[position[0]] as CSSGroupingRule; + if (position.length === 1) { + return rule; + } else { + return getNestedRule( + ((rule as CSSGroupingRule).cssRules[position[1]] as CSSGroupingRule) + .cssRules, + position.slice(2), + ); + } +} + +export function getPositionsAndIndex(nestedIndex: number[]) { + const positions = [...nestedIndex]; + const index = positions.pop(); + return { positions, index }; +} + +export function applyVirtualStyleRulesToNode( + storedRules: VirtualStyleRules, + styleNode: HTMLStyleElement, +) { + const { sheet } = styleNode; + if (!sheet) { + // styleNode without sheet means the DOM has been removed + // so the rules no longer need to be applied + return; + } + + storedRules.forEach((rule) => { + if (rule.type === StyleRuleType.Insert) { + try { + if (Array.isArray(rule.index)) { + const { positions, index } = getPositionsAndIndex(rule.index); + const nestedRule = getNestedRule(sheet.cssRules, positions); + nestedRule.insertRule(rule.cssText, index); + } else { + sheet.insertRule(rule.cssText, rule.index); + } + } catch (e) { + /** + * sometimes we may capture rules with browser prefix + * insert rule with prefixs in other browsers may cause Error + */ + } + } else if (rule.type === StyleRuleType.Remove) { + try { + if (Array.isArray(rule.index)) { + const { positions, index } = getPositionsAndIndex(rule.index); + const nestedRule = getNestedRule(sheet.cssRules, positions); + nestedRule.deleteRule(index || 0); + } else { + sheet.deleteRule(rule.index); + } + } catch (e) { + /** + * accessing styleSheet rules may cause SecurityError + * for specific access control settings + */ + } + } else if (rule.type === StyleRuleType.Snapshot) { + restoreSnapshotOfStyleRulesToNode(rule.cssTexts, styleNode); + } else if (rule.type === StyleRuleType.SetProperty) { + const nativeRule = (getNestedRule( + sheet.cssRules, + rule.index, + ) as unknown) as CSSStyleRule; + nativeRule.style.setProperty(rule.property, rule.value, rule.priority); + } else if (rule.type === StyleRuleType.RemoveProperty) { + const nativeRule = (getNestedRule( + sheet.cssRules, + rule.index, + ) as unknown) as CSSStyleRule; + nativeRule.style.removeProperty(rule.property); + } + }); +} + +function restoreSnapshotOfStyleRulesToNode( + cssTexts: string[], + styleNode: HTMLStyleElement, +) { + try { + const existingRules = Array.from(styleNode.sheet?.cssRules || []).map( + (rule) => rule.cssText, + ); + const existingRulesReversed = Object.entries(existingRules).reverse(); + let lastMatch = existingRules.length; + existingRulesReversed.forEach(([index, rule]) => { + const indexOf = cssTexts.indexOf(rule); + if (indexOf === -1 || indexOf > lastMatch) { + try { + styleNode.sheet?.deleteRule(Number(index)); + } catch (e) { + /** + * accessing styleSheet rules may cause SecurityError + * for specific access control settings + */ + } + } + lastMatch = indexOf; + }); + cssTexts.forEach((cssText, index) => { + try { + if (styleNode.sheet?.cssRules[index]?.cssText !== cssText) { + styleNode.sheet?.insertRule(cssText, index); + } + } catch (e) { + /** + * sometimes we may capture rules with browser prefix + * insert rule with prefixs in other browsers may cause Error + */ + } + }); + } catch (e) { + /** + * accessing styleSheet rules may cause SecurityError + * for specific access control settings + */ + } +} + +export function storeCSSRules( + parentElement: HTMLStyleElement, + virtualStyleRulesMap: VirtualStyleRulesMap, +) { + try { + const cssTexts = Array.from( + (parentElement as HTMLStyleElement).sheet?.cssRules || [], + ).map((rule) => rule.cssText); + virtualStyleRulesMap.set((parentElement as unknown) as INode, [ + { + type: StyleRuleType.Snapshot, + cssTexts, + }, + ]); + } catch (e) { + /** + * accessing styleSheet rules may cause SecurityError + * for specific access control settings + */ + } +} diff --git a/src/rrdom/index.ts b/packages/rrweb/src/rrdom/index.ts similarity index 100% rename from src/rrdom/index.ts rename to packages/rrweb/src/rrdom/index.ts diff --git a/src/rrdom/tree-node.ts b/packages/rrweb/src/rrdom/tree-node.ts similarity index 100% rename from src/rrdom/tree-node.ts rename to packages/rrweb/src/rrdom/tree-node.ts diff --git a/src/types.ts b/packages/rrweb/src/types.ts similarity index 72% rename from src/types.ts rename to packages/rrweb/src/types.ts index 10fff3879d..dea29982dc 100644 --- a/src/types.ts +++ b/packages/rrweb/src/types.ts @@ -4,11 +4,14 @@ import { INode, MaskInputOptions, SlimDOMOptions, + MaskInputFn, + MaskTextFn, } from 'rrweb-snapshot'; import { PackFn, UnpackFn } from './packer/base'; -import { FontFaceDescriptors } from 'css-font-loading-module'; import { IframeManager } from './record/iframe-manager'; import { ShadowDomManager } from './record/shadow-dom-manager'; +import type { Replayer } from './replay'; +import { CanvasManager } from './record/observers/canvas/canvas-manager'; export enum EventType { DomContentLoaded, @@ -17,6 +20,7 @@ export enum EventType { IncrementalSnapshot, Meta, Custom, + Plugin, } export type domContentLoadedEvent = { @@ -54,11 +58,6 @@ export type metaEvent = { }; }; -export type logEvent = { - type: EventType.IncrementalSnapshot; - data: incrementalData; -}; - export type customEvent = { type: EventType.Custom; data: { @@ -67,6 +66,14 @@ export type customEvent = { }; }; +export type pluginEvent = { + type: EventType.Plugin; + data: { + plugin: string; + payload: T; + }; +}; + export type styleSheetEvent = {}; export enum IncrementalSource { @@ -83,6 +90,7 @@ export enum IncrementalSource { Font, Log, Drag, + StyleDeclaration, } export type mutationData = { @@ -122,6 +130,10 @@ export type styleSheetRuleData = { source: IncrementalSource.StyleSheetRule; } & styleSheetRuleParam; +export type styleDeclarationData = { + source: IncrementalSource.StyleDeclaration; +} & styleDeclarationParam; + export type canvasMutationData = { source: IncrementalSource.CanvasMutation; } & canvasMutationParam; @@ -130,10 +142,6 @@ export type fontData = { source: IncrementalSource.Font; } & fontParam; -export type logData = { - source: IncrementalSource.Log; -} & LogParam; - export type incrementalData = | mutationData | mousemoveData @@ -145,7 +153,7 @@ export type incrementalData = | styleSheetRuleData | canvasMutationData | fontData - | logData; + | styleDeclarationData; export type event = | domContentLoadedEvent @@ -153,8 +161,8 @@ export type event = | fullSnapshotEvent | incrementalSnapshotEvent | metaEvent - | logEvent - | customEvent; + | customEvent + | pluginEvent; export type eventWithTime = event & { timestamp: number; @@ -184,6 +192,10 @@ export type SamplingStrategy = Partial<{ * number is the throttle threshold of recording scroll */ scroll: number; + /** + * number is the throttle threshold of recording media interactions + */ + media: number; /** * 'all' will record all the input events * 'last' will only record the last input value while input a sequence of chars @@ -191,6 +203,13 @@ export type SamplingStrategy = Partial<{ input: 'all' | 'last'; }>; +export type RecordPlugin = { + name: string; + observer?: (cb: Function, win: IWindow, options: TOptions) => listenerHandler; + eventProcessor?: (event: eventWithTime) => eventWithTime & TExtend; + options: TOptions; +}; + export type recordOptions = { emit?: (e: T, isCheckout?: boolean) => void; checkoutEveryNth?: number; @@ -210,10 +229,13 @@ export type recordOptions = { packFn?: PackFn; sampling?: SamplingStrategy; recordCanvas?: boolean; + userTriggeredOnInput?: boolean; collectFonts?: boolean; + inlineImages?: boolean; + plugins?: RecordPlugin[]; // departed, please use sampling options mousemoveWait?: number; - recordLog?: boolean | LogRecordOptions; + keepIframeSrcFn?: KeepIframeSrcFn; }; export type observerParam = { @@ -234,18 +256,47 @@ export type observerParam = { maskTextFn?: MaskTextFn; inlineStylesheet: boolean; styleSheetRuleCb: styleSheetRuleCallback; + styleDeclarationCb: styleDeclarationCallback; canvasMutationCb: canvasMutationCallback; fontCb: fontCallback; - logCb: logCallback; - logOptions: LogRecordOptions; sampling: SamplingStrategy; recordCanvas: boolean; + inlineImages: boolean; + userTriggeredOnInput: boolean; collectFonts: boolean; slimDOMOptions: SlimDOMOptions; doc: Document; + mirror: Mirror; iframeManager: IframeManager; shadowDomManager: ShadowDomManager; -}; + canvasManager: CanvasManager; + plugins: Array<{ + observer: Function; + callback: Function; + options: unknown; + }>; +}; + +export type MutationBufferParam = Pick< + observerParam, + | 'mutationCb' + | 'blockClass' + | 'blockSelector' + | 'maskTextClass' + | 'maskTextSelector' + | 'inlineStylesheet' + | 'maskInputOptions' + | 'maskTextFn' + | 'maskInputFn' + | 'recordCanvas' + | 'inlineImages' + | 'slimDOMOptions' + | 'doc' + | 'mirror' + | 'iframeManager' + | 'shadowDomManager' + | 'canvasManager' +>; export type hooksParam = { mutation?: mutationCallBack; @@ -256,9 +307,9 @@ export type hooksParam = { input?: inputCallback; mediaInteaction?: mediaInteractionCallback; styleSheetRule?: styleSheetRuleCallback; + styleDeclaration?: styleDeclarationCallback; canvasMutation?: canvasMutationCallback; font?: fontCallback; - log?: logCallback; }; // https://dom.spec.whatwg.org/#interface-mutationrecord @@ -280,16 +331,22 @@ export type textMutation = { value: string | null; }; +export type styleAttributeValue = { + [key: string]: styleValueWithPriority | string | false; +}; + +export type styleValueWithPriority = [string, string]; + export type attributeCursor = { node: Node; attributes: { - [key: string]: string | null; + [key: string]: string | styleAttributeValue | null; }; }; export type attributeMutation = { id: number; attributes: { - [key: string]: string | null; + [key: string]: string | styleAttributeValue | null; }; }; @@ -332,6 +389,13 @@ export type mousePosition = { timeOffset: number; }; +export type mouseMovePos = { + x: number; + y: number; + id: number; + debugData: incrementalData; +}; + export enum MouseInteractions { MouseUp, MouseDown, @@ -343,8 +407,38 @@ export enum MouseInteractions { TouchStart, TouchMove_Departed, // we will start a separate observer for touch move event TouchEnd, + TouchCancel, } +export enum CanvasContext { + '2D', + WebGL, + WebGL2, +} + +export type SerializedWebGlArg = + | { + rr_type: 'ArrayBuffer'; + base64: string; // base64 + } + | { + rr_type: string; + src: string; // url of image + } + | { + rr_type: string; + args: SerializedWebGlArg[]; + } + | { + rr_type: string; + index: number; + } + | string + | number + | boolean + | null + | SerializedWebGlArg[]; + type mouseInteractionParam = { type: MouseInteractions; id: number; @@ -364,11 +458,11 @@ export type scrollCallback = (p: scrollPosition) => void; export type styleSheetAddRule = { rule: string; - index?: number; + index?: number | number[]; }; export type styleSheetDeleteRule = { - index: number; + index: number | number[]; }; export type styleSheetRuleParam = { @@ -379,15 +473,49 @@ export type styleSheetRuleParam = { export type styleSheetRuleCallback = (s: styleSheetRuleParam) => void; -export type canvasMutationCallback = (p: canvasMutationParam) => void; - -export type canvasMutationParam = { +export type styleDeclarationParam = { id: number; + index: number[]; + set?: { + property: string; + value: string | null; + priority: string | undefined; + }; + remove?: { + property: string; + }; +}; + +export type styleDeclarationCallback = (s: styleDeclarationParam) => void; + +export type canvasMutationCommand = { property: string; args: Array; setter?: true; }; +export type canvasMutationParam = + | { + id: number; + type: CanvasContext; + commands: canvasMutationCommand[]; + } + | ({ + id: number; + type: CanvasContext; + } & canvasMutationCommand); + +export type canvasMutationWithType = { + type: CanvasContext; +} & canvasMutationCommand; + +export type canvasMutationCallback = (p: canvasMutationParam) => void; + +export type canvasManagerMutationCallback = ( + target: HTMLCanvasElement, + p: canvasMutationWithType, +) => void; + export type fontParam = { family: string; fontSource: string; @@ -395,67 +523,8 @@ export type fontParam = { descriptors?: FontFaceDescriptors; }; -export type LogLevel = - | 'assert' - | 'clear' - | 'count' - | 'countReset' - | 'debug' - | 'dir' - | 'dirxml' - | 'error' - | 'group' - | 'groupCollapsed' - | 'groupEnd' - | 'info' - | 'log' - | 'table' - | 'time' - | 'timeEnd' - | 'timeLog' - | 'trace' - | 'warn'; - -/* fork from interface Console */ -// all kinds of console functions -export type Logger = { - assert?: typeof console.assert; - clear?: typeof console.clear; - count?: typeof console.count; - countReset?: typeof console.countReset; - debug?: typeof console.debug; - dir?: typeof console.dir; - dirxml?: typeof console.dirxml; - error?: typeof console.error; - group?: typeof console.group; - groupCollapsed?: typeof console.groupCollapsed; - groupEnd?: () => void; - info?: typeof console.info; - log?: typeof console.log; - table?: typeof console.table; - time?: typeof console.time; - timeEnd?: typeof console.timeEnd; - timeLog?: typeof console.timeLog; - trace?: typeof console.trace; - warn?: typeof console.warn; -}; - -/** - * define an interface to replay log records - * (data: logData) => void> function to display the log data - */ -export type ReplayLogger = Partial void>>; - -export type LogParam = { - level: LogLevel; - trace: string[]; - payload: string[]; -}; - export type fontCallback = (p: fontParam) => void; -export type logCallback = (p: LogParam) => void; - export type viewportResizeDimension = { width: number; height: number; @@ -464,10 +533,14 @@ export type viewportResizeDimension = { export type viewportResizeCallback = (d: viewportResizeDimension) => void; export type inputValue = { - text?: string; - isChecked?: boolean; - key?: string; - rrwebGenerated: boolean; + text: string; + isChecked: boolean; + + // `userTriggered` indicates if this event was triggered directly by user (userTriggered: true) + // or was triggered indirectly (userTriggered: false) + // Example of `userTriggered` in action: + // User clicks on radio element (userTriggered: true) which triggers the other radio element to change (userTriggered: false) + userTriggered?: boolean; }; export type inputCallback = (v: inputValue & { id: number }) => void; @@ -475,11 +548,16 @@ export type inputCallback = (v: inputValue & { id: number }) => void; export const enum MediaInteractions { Play, Pause, + Seeked, + VolumeChange, } export type mediaInteractionParam = { type: MediaInteractions; id: number; + currentTime?: number; + volume?: number; + muted?: boolean; }; export type mediaInteractionCallback = (p: mediaInteractionParam) => void; @@ -510,6 +588,13 @@ export type throttleOptions = { export type listenerHandler = () => void; export type hookResetter = () => void; +export type ReplayPlugin = { + handler: ( + event: eventWithTime, + isSync: boolean, + context: { replayer: Replayer }, + ) => void; +}; export type playerConfig = { speed: number; maxSpeed: number; @@ -533,12 +618,7 @@ export type playerConfig = { strokeStyle?: string; }; unpackFn?: UnpackFn; - logConfig: LogReplayConfig; -}; - -export type LogReplayConfig = { - level?: LogLevel[] | undefined; - replayLogger: ReplayLogger | undefined; + plugins?: ReplayPlugin[]; }; export type playerMetaData = { @@ -591,29 +671,20 @@ export enum ReplayerEvents { PlayBack = 'play-back', } -export type MaskInputFn = (text: string) => string; - -export type MaskTextFn = (text: string) => string; - // store the state that would be changed during the process(unmount from dom and mount again) export type ElementState = { // [scrollLeft,scrollTop] scroll?: [number, number]; }; -export type StringifyOptions = { - // limit of string length - stringLengthLimit?: number; - /** - * limit of number of keys in an object - * if an object contains more keys than this limit, we would call its toString function directly - */ - numOfKeysLimit: number; -}; +export type KeepIframeSrcFn = (src: string) => boolean; -export type LogRecordOptions = { - level?: LogLevel[] | undefined; - lengthThreshold?: number; - stringifyOptions?: StringifyOptions; - logger?: Logger; -}; +declare global { + interface Window { + FontFace: typeof FontFace; + } +} + +export type IWindow = Window & typeof globalThis; + +export type Optional = Pick, K> & Omit; diff --git a/src/utils.ts b/packages/rrweb/src/utils.ts similarity index 83% rename from src/utils.ts rename to packages/rrweb/src/utils.ts index ba7bdcdee4..91376444ef 100644 --- a/src/utils.ts +++ b/packages/rrweb/src/utils.ts @@ -4,8 +4,6 @@ import { listenerHandler, hookResetter, blockClass, - eventWithTime, - EventType, IncrementalSource, addedNodeMutation, removedNodeMutation, @@ -15,6 +13,7 @@ import { scrollData, inputData, DocumentDimension, + IWindow, } from './types'; import { INode, @@ -27,42 +26,83 @@ import { export function on( type: string, fn: EventListenerOrEventListenerObject, - target: Document | Window = document, + target: Document | IWindow = document, ): listenerHandler { const options = { capture: true, passive: true }; target.addEventListener(type, fn, options); return () => target.removeEventListener(type, fn, options); } -export const mirror: Mirror = { +export function createMirror(): Mirror { + return { + map: {}, + getId(n) { + // if n is not a serialized INode, use -1 as its id. + if (!n || !n.__sn) { + return -1; + } + return n.__sn.id; + }, + getNode(id) { + return this.map[id] || null; + }, + // TODO: use a weakmap to get rid of manually memory management + removeNodeFromMap(n) { + const id = n.__sn && n.__sn.id; + delete this.map[id]; + if (n.childNodes) { + n.childNodes.forEach((child) => + this.removeNodeFromMap((child as Node) as INode), + ); + } + }, + has(id) { + return this.map.hasOwnProperty(id); + }, + reset() { + this.map = {}; + }, + }; +} + +// https://github.com/rrweb-io/rrweb/pull/407 +const DEPARTED_MIRROR_ACCESS_WARNING = + 'Please stop import mirror directly. Instead of that,' + + '\r\n' + + 'now you can use replayer.getMirror() to access the mirror instance of a replayer,' + + '\r\n' + + 'or you can use record.mirror to access the mirror instance during recording.'; +export let _mirror: Mirror = { map: {}, - getId(n) { - // if n is not a serialized INode, use -1 as its id. - if (!n.__sn) { - return -1; - } - return n.__sn.id; + getId() { + console.error(DEPARTED_MIRROR_ACCESS_WARNING); + return -1; }, - getNode(id) { - return mirror.map[id] || null; + getNode() { + console.error(DEPARTED_MIRROR_ACCESS_WARNING); + return null; }, - // TODO: use a weakmap to get rid of manually memory management - removeNodeFromMap(n) { - const id = n.__sn && n.__sn.id; - delete mirror.map[id]; - if (n.childNodes) { - n.childNodes.forEach((child) => - mirror.removeNodeFromMap((child as Node) as INode), - ); - } + removeNodeFromMap() { + console.error(DEPARTED_MIRROR_ACCESS_WARNING); }, - has(id) { - return mirror.map.hasOwnProperty(id); + has() { + console.error(DEPARTED_MIRROR_ACCESS_WARNING); + return false; }, reset() { - mirror.map = {}; + console.error(DEPARTED_MIRROR_ACCESS_WARNING); }, }; +if (typeof window !== 'undefined' && window.Proxy && window.Reflect) { + _mirror = new Proxy(_mirror, { + get(target, prop, receiver) { + if (prop === 'map') { + console.error(DEPARTED_MIRROR_ACCESS_WARNING); + } + return Reflect.get(target, prop, receiver); + }, + }); +} // copy from underscore and modified export function throttle( @@ -70,7 +110,7 @@ export function throttle( wait: number, options: throttleOptions = {}, ) { - let timeout: number | null = null; + let timeout: ReturnType | null = null; let previous = 0; // tslint:disable-next-line: only-arrow-functions return function (arg: T) { @@ -83,13 +123,13 @@ export function throttle( let args = arguments; if (remaining <= 0 || remaining > wait) { if (timeout) { - window.clearTimeout(timeout); + clearTimeout(timeout); timeout = null; } previous = now; func.apply(context, args); } else if (!timeout && options.trailing !== false) { - timeout = window.setTimeout(() => { + timeout = setTimeout(() => { previous = options.leading === false ? 0 : Date.now(); timeout = null; func.apply(context, args); @@ -190,7 +230,11 @@ export function isBlocked(node: Node | null, blockClass: blockClass): boolean { if (node.nodeType === node.ELEMENT_NODE) { let needBlock = false; if (typeof blockClass === 'string') { - needBlock = (node as HTMLElement).classList.contains(blockClass); + if ((node as HTMLElement).closest !== undefined) { + return (node as HTMLElement).closest('.' + blockClass) !== null; + } else { + needBlock = (node as HTMLElement).classList.contains(blockClass); + } } else { (node as HTMLElement).classList.forEach((className) => { if (blockClass.test(className)) { @@ -216,7 +260,7 @@ export function isIgnored(n: Node | INode): boolean { return false; } -export function isAncestorRemoved(target: INode): boolean { +export function isAncestorRemoved(target: INode, mirror: Mirror): boolean { if (isShadowRoot(target)) { return false; } @@ -234,7 +278,7 @@ export function isAncestorRemoved(target: INode): boolean { if (!target.parentNode) { return true; } - return isAncestorRemoved((target.parentNode as unknown) as INode); + return isAncestorRemoved((target.parentNode as unknown) as INode, mirror); } export function isTouchEvent( @@ -253,37 +297,24 @@ export function polyfill(win = window) { win.DOMTokenList.prototype.forEach = (Array.prototype .forEach as unknown) as DOMTokenList['forEach']; } -} -export function needCastInSyncMode(event: eventWithTime): boolean { - switch (event.type) { - case EventType.DomContentLoaded: - case EventType.Load: - case EventType.Custom: - return false; - case EventType.FullSnapshot: - case EventType.Meta: - return true; - default: - break; - } + // https://github.com/Financial-Times/polyfill-service/pull/183 + if (!Node.prototype.contains) { + Node.prototype.contains = function contains(node) { + if (!(0 in arguments)) { + throw new TypeError('1 argument is required'); + } + + do { + if (this === node) { + return true; + } + // tslint:disable-next-line: no-conditional-assignment + } while ((node = node && node.parentNode)); - switch (event.data.source) { - case IncrementalSource.MouseMove: - case IncrementalSource.MouseInteraction: - case IncrementalSource.TouchMove: - case IncrementalSource.MediaInteraction: return false; - case IncrementalSource.ViewportResize: - case IncrementalSource.StyleSheetRule: - case IncrementalSource.Scroll: - case IncrementalSource.Input: - return true; - default: - break; + }; } - - return true; } export type TreeNode = { @@ -327,7 +358,7 @@ export class TreeIndex { this.indexes.set(treeNode.id, treeNode); } - public remove(mutation: removedNodeMutation) { + public remove(mutation: removedNodeMutation, mirror: Mirror) { const parentTreeNode = this.indexes.get(mutation.parentId); const treeNode = this.indexes.get(mutation.id); @@ -474,6 +505,10 @@ export class TreeIndex { this.scrollMap = new Map(); this.inputMap = new Map(); } + + public idRemoved(id: number): boolean { + return this.removeIdSet.has(id); + } } type ResolveTree = { diff --git a/test/__snapshots__/integration.test.ts.snap b/packages/rrweb/test/__snapshots__/integration.test.ts.snap similarity index 71% rename from test/__snapshots__/integration.test.ts.snap rename to packages/rrweb/test/__snapshots__/integration.test.ts.snap index ac6f2cd344..06290e6068 100644 --- a/test/__snapshots__/integration.test.ts.snap +++ b/packages/rrweb/test/__snapshots__/integration.test.ts.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`attributes 1`] = ` +exports[`record integration tests can freeze mutations 1`] = ` "[ { \\"type\\": 0, @@ -24,6 +24,13 @@ exports[`attributes 1`] = ` \\"node\\": { \\"type\\": 0, \\"childNodes\\": [ + { + \\"type\\": 1, + \\"name\\": \\"html\\", + \\"publicId\\": \\"\\", + \\"systemId\\": \\"\\", + \\"id\\": 2 + }, { \\"type\\": 2, \\"tagName\\": \\"html\\", @@ -34,7 +41,7 @@ exports[`attributes 1`] = ` \\"tagName\\": \\"head\\", \\"attributes\\": {}, \\"childNodes\\": [], - \\"id\\": 3 + \\"id\\": 4 }, { \\"type\\": 2, @@ -44,7 +51,7 @@ exports[`attributes 1`] = ` { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", - \\"id\\": 5 + \\"id\\": 6 }, { \\"type\\": 2, @@ -54,15 +61,15 @@ exports[`attributes 1`] = ` { \\"type\\": 3, \\"textContent\\": \\"mutation observer\\", - \\"id\\": 7 + \\"id\\": 8 } ], - \\"id\\": 6 + \\"id\\": 7 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", - \\"id\\": 8 + \\"id\\": 9 }, { \\"type\\": 2, @@ -72,27 +79,39 @@ exports[`attributes 1`] = ` { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", - \\"id\\": 10 + \\"id\\": 11 }, { \\"type\\": 2, \\"tagName\\": \\"li\\", \\"attributes\\": {}, \\"childNodes\\": [], - \\"id\\": 11 + \\"id\\": 12 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", - \\"id\\": 12 + \\"id\\": 13 } ], - \\"id\\": 9 + \\"id\\": 10 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 14 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"canvas\\", + \\"attributes\\": {}, + \\"childNodes\\": [], + \\"id\\": 15 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n\\\\n \\", - \\"id\\": 13 + \\"id\\": 16 }, { \\"type\\": 2, @@ -102,21 +121,21 @@ exports[`attributes 1`] = ` { \\"type\\": 3, \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", - \\"id\\": 15 + \\"id\\": 18 } ], - \\"id\\": 14 + \\"id\\": 17 }, { \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\\\n \\", - \\"id\\": 16 + \\"textContent\\": \\"\\\\n \\\\n \\\\n\\", + \\"id\\": 19 } ], - \\"id\\": 4 + \\"id\\": 5 } ], - \\"id\\": 2 + \\"id\\": 3 } ], \\"id\\": 1 @@ -134,25 +153,40 @@ exports[`attributes 1`] = ` \\"texts\\": [], \\"attributes\\": [ { - \\"id\\": 4, + \\"id\\": 20, + \\"attributes\\": { + \\"foo\\": \\"bar\\" + } + }, + { + \\"id\\": 5, \\"attributes\\": { \\"test\\": \\"true\\" } } ], - \\"removes\\": [ + \\"removes\\": [], + \\"adds\\": [ { - \\"parentId\\": 4, - \\"id\\": 9 + \\"parentId\\": 10, + \\"nextId\\": null, + \\"node\\": { + \\"type\\": 2, + \\"tagName\\": \\"li\\", + \\"attributes\\": { + \\"foo\\": \\"bar\\" + }, + \\"childNodes\\": [], + \\"id\\": 20 + } } - ], - \\"adds\\": [] + ] } } ]" `; -exports[`block 1`] = ` +exports[`record integration tests can mask character data mutations 1`] = ` "[ { \\"type\\": 0, @@ -186,115 +220,84 @@ exports[`block 1`] = ` { \\"type\\": 2, \\"tagName\\": \\"html\\", - \\"attributes\\": { - \\"lang\\": \\"en\\" - }, + \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 2, \\"tagName\\": \\"head\\", \\"attributes\\": {}, + \\"childNodes\\": [], + \\"id\\": 4 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"body\\", + \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 5 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"meta\\", - \\"attributes\\": { - \\"charset\\": \\"UTF-8\\" - }, - \\"childNodes\\": [], + \\"textContent\\": \\"\\\\n \\", \\"id\\": 6 }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 7 - }, { \\"type\\": 2, - \\"tagName\\": \\"meta\\", - \\"attributes\\": { - \\"name\\": \\"viewport\\", - \\"content\\": \\"width=device-width, initial-scale=1.0\\" - }, - \\"childNodes\\": [], - \\"id\\": 8 + \\"tagName\\": \\"p\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"mutation observer\\", + \\"id\\": 8 + } + ], + \\"id\\": 7 }, { \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", + \\"textContent\\": \\"\\\\n \\", \\"id\\": 9 }, { \\"type\\": 2, - \\"tagName\\": \\"meta\\", - \\"attributes\\": { - \\"http-equiv\\": \\"X-UA-Compatible\\", - \\"content\\": \\"ie=edge\\" - }, - \\"childNodes\\": [], - \\"id\\": 10 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 11 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"title\\", + \\"tagName\\": \\"ul\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, - \\"textContent\\": \\"Block record\\", + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 11 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"li\\", + \\"attributes\\": {}, + \\"childNodes\\": [], + \\"id\\": 12 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", \\"id\\": 13 } ], - \\"id\\": 12 + \\"id\\": 10 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"id\\": 14 - } - ], - \\"id\\": 4 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 15 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"body\\", - \\"attributes\\": {}, - \\"childNodes\\": [ - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 17 }, { \\"type\\": 2, - \\"tagName\\": \\"div\\", - \\"attributes\\": { - \\"class\\": \\"rr-block\\", - \\"rr_width\\": \\"50px\\", - \\"rr_height\\": \\"50px\\" - }, + \\"tagName\\": \\"canvas\\", + \\"attributes\\": {}, \\"childNodes\\": [], - \\"id\\": 18 + \\"id\\": 15 }, { \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\\\n \\", - \\"id\\": 19 + \\"textContent\\": \\"\\\\n\\\\n \\", + \\"id\\": 16 }, { \\"type\\": 2, @@ -304,18 +307,18 @@ exports[`block 1`] = ` { \\"type\\": 3, \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", - \\"id\\": 21 + \\"id\\": 18 } ], - \\"id\\": 20 + \\"id\\": 17 }, { \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\\\n \\\\n\\\\n\\", - \\"id\\": 22 + \\"textContent\\": \\"\\\\n \\\\n \\\\n\\", + \\"id\\": 19 } ], - \\"id\\": 16 + \\"id\\": 5 } ], \\"id\\": 3 @@ -328,11 +331,65 @@ exports[`block 1`] = ` \\"top\\": 0 } } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 0, + \\"texts\\": [], + \\"attributes\\": [ + { + \\"id\\": 7, + \\"attributes\\": { + \\"class\\": \\"rr-mask\\" + } + } + ], + \\"removes\\": [ + { + \\"parentId\\": 7, + \\"id\\": 8 + } + ], + \\"adds\\": [ + { + \\"parentId\\": 10, + \\"nextId\\": null, + \\"node\\": { + \\"type\\": 2, + \\"tagName\\": \\"li\\", + \\"attributes\\": { + \\"class\\": \\"rr-mask\\" + }, + \\"childNodes\\": [], + \\"id\\": 20 + } + }, + { + \\"parentId\\": 20, + \\"nextId\\": null, + \\"node\\": { + \\"type\\": 3, + \\"textContent\\": \\"*** **** ****\\", + \\"id\\": 21 + } + }, + { + \\"parentId\\": 7, + \\"nextId\\": null, + \\"node\\": { + \\"type\\": 3, + \\"textContent\\": \\"*******\\", + \\"id\\": 22 + } + } + ] + } } ]" `; -exports[`canvas 1`] = ` +exports[`record integration tests can record attribute mutation 1`] = ` "[ { \\"type\\": 0, @@ -366,126 +423,84 @@ exports[`canvas 1`] = ` { \\"type\\": 2, \\"tagName\\": \\"html\\", - \\"attributes\\": { - \\"lang\\": \\"en\\" - }, + \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 2, \\"tagName\\": \\"head\\", \\"attributes\\": {}, + \\"childNodes\\": [], + \\"id\\": 4 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"body\\", + \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 5 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"meta\\", - \\"attributes\\": { - \\"charset\\": \\"UTF-8\\" - }, - \\"childNodes\\": [], + \\"textContent\\": \\"\\\\n \\", \\"id\\": 6 }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 7 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"meta\\", - \\"attributes\\": { - \\"name\\": \\"viewport\\", - \\"content\\": \\"width=device-width, initial-scale=1.0\\" - }, - \\"childNodes\\": [], - \\"id\\": 8 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 9 - }, { \\"type\\": 2, - \\"tagName\\": \\"title\\", + \\"tagName\\": \\"p\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, - \\"textContent\\": \\"canvas\\", - \\"id\\": 11 + \\"textContent\\": \\"mutation observer\\", + \\"id\\": 8 } ], - \\"id\\": 10 + \\"id\\": 7 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", - \\"id\\": 12 - } - ], - \\"id\\": 4 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 13 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"body\\", - \\"attributes\\": {}, - \\"childNodes\\": [ - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 15 + \\"id\\": 9 }, { \\"type\\": 2, - \\"tagName\\": \\"canvas\\", - \\"attributes\\": { - \\"id\\": \\"myCanvas\\", - \\"width\\": \\"200\\", - \\"height\\": \\"100\\", - \\"style\\": \\"border: 1px solid #000000;\\", - \\"rr_dataURL\\": \\"LOOKS LIKE WE COULD NOT GET STABLE BASE64 FROM SAME IMAGE.\\" - }, + \\"tagName\\": \\"ul\\", + \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", - \\"id\\": 17 + \\"id\\": 11 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"li\\", + \\"attributes\\": {}, + \\"childNodes\\": [], + \\"id\\": 12 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 13 } ], - \\"id\\": 16 + \\"id\\": 10 }, { \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 18 + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 14 }, { \\"type\\": 2, - \\"tagName\\": \\"script\\", + \\"tagName\\": \\"canvas\\", \\"attributes\\": {}, - \\"childNodes\\": [ - { - \\"type\\": 3, - \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", - \\"id\\": 20 - } - ], - \\"id\\": 19 + \\"childNodes\\": [], + \\"id\\": 15 }, { \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\\\n \\", - \\"id\\": 21 + \\"textContent\\": \\"\\\\n\\\\n \\", + \\"id\\": 16 }, { \\"type\\": 2, @@ -495,18 +510,18 @@ exports[`canvas 1`] = ` { \\"type\\": 3, \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", - \\"id\\": 23 + \\"id\\": 18 } ], - \\"id\\": 22 + \\"id\\": 17 }, { \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\\\n \\\\n\\\\n\\", - \\"id\\": 24 + \\"textContent\\": \\"\\\\n \\\\n \\\\n\\", + \\"id\\": 19 } ], - \\"id\\": 14 + \\"id\\": 5 } ], \\"id\\": 3 @@ -523,40 +538,29 @@ exports[`canvas 1`] = ` { \\"type\\": 3, \\"data\\": { - \\"source\\": 9, - \\"id\\": 16, - \\"property\\": \\"moveTo\\", - \\"args\\": [ - 0, - 0 - ] - } - }, - { - \\"type\\": 3, - \\"data\\": { - \\"source\\": 9, - \\"id\\": 16, - \\"property\\": \\"lineTo\\", - \\"args\\": [ - 200, - 100 - ] - } - }, - { - \\"type\\": 3, - \\"data\\": { - \\"source\\": 9, - \\"id\\": 16, - \\"property\\": \\"stroke\\", - \\"args\\": [] + \\"source\\": 0, + \\"texts\\": [], + \\"attributes\\": [ + { + \\"id\\": 5, + \\"attributes\\": { + \\"test\\": \\"true\\" + } + } + ], + \\"removes\\": [ + { + \\"parentId\\": 5, + \\"id\\": 10 + } + ], + \\"adds\\": [] } } ]" `; -exports[`character-data 1`] = ` +exports[`record integration tests can record character data muatations 1`] = ` "[ { \\"type\\": 0, @@ -580,6 +584,13 @@ exports[`character-data 1`] = ` \\"node\\": { \\"type\\": 0, \\"childNodes\\": [ + { + \\"type\\": 1, + \\"name\\": \\"html\\", + \\"publicId\\": \\"\\", + \\"systemId\\": \\"\\", + \\"id\\": 2 + }, { \\"type\\": 2, \\"tagName\\": \\"html\\", @@ -590,7 +601,7 @@ exports[`character-data 1`] = ` \\"tagName\\": \\"head\\", \\"attributes\\": {}, \\"childNodes\\": [], - \\"id\\": 3 + \\"id\\": 4 }, { \\"type\\": 2, @@ -600,7 +611,7 @@ exports[`character-data 1`] = ` { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", - \\"id\\": 5 + \\"id\\": 6 }, { \\"type\\": 2, @@ -610,15 +621,15 @@ exports[`character-data 1`] = ` { \\"type\\": 3, \\"textContent\\": \\"mutation observer\\", - \\"id\\": 7 + \\"id\\": 8 } ], - \\"id\\": 6 + \\"id\\": 7 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", - \\"id\\": 8 + \\"id\\": 9 }, { \\"type\\": 2, @@ -628,27 +639,39 @@ exports[`character-data 1`] = ` { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", - \\"id\\": 10 + \\"id\\": 11 }, { \\"type\\": 2, \\"tagName\\": \\"li\\", \\"attributes\\": {}, \\"childNodes\\": [], - \\"id\\": 11 + \\"id\\": 12 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", - \\"id\\": 12 + \\"id\\": 13 } ], - \\"id\\": 9 + \\"id\\": 10 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 14 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"canvas\\", + \\"attributes\\": {}, + \\"childNodes\\": [], + \\"id\\": 15 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n\\\\n \\", - \\"id\\": 13 + \\"id\\": 16 }, { \\"type\\": 2, @@ -658,21 +681,21 @@ exports[`character-data 1`] = ` { \\"type\\": 3, \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", - \\"id\\": 15 + \\"id\\": 18 } ], - \\"id\\": 14 + \\"id\\": 17 }, { \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\\\n \\", - \\"id\\": 16 + \\"textContent\\": \\"\\\\n \\\\n \\\\n\\", + \\"id\\": 19 } ], - \\"id\\": 4 + \\"id\\": 5 } ], - \\"id\\": 2 + \\"id\\": 3 } ], \\"id\\": 1 @@ -691,22 +714,22 @@ exports[`character-data 1`] = ` \\"attributes\\": [], \\"removes\\": [ { - \\"parentId\\": 4, - \\"id\\": 9 + \\"parentId\\": 5, + \\"id\\": 10 }, { - \\"parentId\\": 6, - \\"id\\": 7 + \\"parentId\\": 7, + \\"id\\": 8 } ], \\"adds\\": [ { - \\"parentId\\": 6, + \\"parentId\\": 7, \\"nextId\\": null, \\"node\\": { \\"type\\": 3, \\"textContent\\": \\"mutated\\", - \\"id\\": 17 + \\"id\\": 20 } } ] @@ -715,7 +738,7 @@ exports[`character-data 1`] = ` ]" `; -exports[`child-list 1`] = ` +exports[`record integration tests can record childList mutations 1`] = ` "[ { \\"type\\": 0, @@ -739,6 +762,13 @@ exports[`child-list 1`] = ` \\"node\\": { \\"type\\": 0, \\"childNodes\\": [ + { + \\"type\\": 1, + \\"name\\": \\"html\\", + \\"publicId\\": \\"\\", + \\"systemId\\": \\"\\", + \\"id\\": 2 + }, { \\"type\\": 2, \\"tagName\\": \\"html\\", @@ -749,7 +779,7 @@ exports[`child-list 1`] = ` \\"tagName\\": \\"head\\", \\"attributes\\": {}, \\"childNodes\\": [], - \\"id\\": 3 + \\"id\\": 4 }, { \\"type\\": 2, @@ -759,7 +789,7 @@ exports[`child-list 1`] = ` { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", - \\"id\\": 5 + \\"id\\": 6 }, { \\"type\\": 2, @@ -769,15 +799,15 @@ exports[`child-list 1`] = ` { \\"type\\": 3, \\"textContent\\": \\"mutation observer\\", - \\"id\\": 7 + \\"id\\": 8 } ], - \\"id\\": 6 + \\"id\\": 7 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", - \\"id\\": 8 + \\"id\\": 9 }, { \\"type\\": 2, @@ -787,27 +817,39 @@ exports[`child-list 1`] = ` { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", - \\"id\\": 10 + \\"id\\": 11 }, { \\"type\\": 2, \\"tagName\\": \\"li\\", \\"attributes\\": {}, \\"childNodes\\": [], - \\"id\\": 11 + \\"id\\": 12 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", - \\"id\\": 12 + \\"id\\": 13 } ], - \\"id\\": 9 + \\"id\\": 10 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 14 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"canvas\\", + \\"attributes\\": {}, + \\"childNodes\\": [], + \\"id\\": 15 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n\\\\n \\", - \\"id\\": 13 + \\"id\\": 16 }, { \\"type\\": 2, @@ -817,21 +859,21 @@ exports[`child-list 1`] = ` { \\"type\\": 3, \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", - \\"id\\": 15 + \\"id\\": 18 } ], - \\"id\\": 14 + \\"id\\": 17 }, { \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\\\n \\", - \\"id\\": 16 + \\"textContent\\": \\"\\\\n \\\\n \\\\n\\", + \\"id\\": 19 } ], - \\"id\\": 4 + \\"id\\": 5 } ], - \\"id\\": 2 + \\"id\\": 3 } ], \\"id\\": 1 @@ -850,20 +892,20 @@ exports[`child-list 1`] = ` \\"attributes\\": [], \\"removes\\": [ { - \\"parentId\\": 4, - \\"id\\": 9 + \\"parentId\\": 5, + \\"id\\": 10 } ], \\"adds\\": [ { - \\"parentId\\": 6, + \\"parentId\\": 7, \\"nextId\\": null, \\"node\\": { \\"type\\": 2, \\"tagName\\": \\"span\\", \\"attributes\\": {}, \\"childNodes\\": [], - \\"id\\": 17 + \\"id\\": 20 } } ] @@ -872,7 +914,7 @@ exports[`child-list 1`] = ` ]" `; -exports[`form 1`] = ` +exports[`record integration tests can record form interactions 1`] = ` "[ { \\"type\\": 0, @@ -917,7 +959,7 @@ exports[`form 1`] = ` \\"childNodes\\": [ { \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", + \\"textContent\\": \\"\\\\n \\", \\"id\\": 5 }, { @@ -931,7 +973,7 @@ exports[`form 1`] = ` }, { \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", + \\"textContent\\": \\"\\\\n \\", \\"id\\": 7 }, { @@ -946,7 +988,7 @@ exports[`form 1`] = ` }, { \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", + \\"textContent\\": \\"\\\\n \\", \\"id\\": 9 }, { @@ -961,7 +1003,7 @@ exports[`form 1`] = ` }, { \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", + \\"textContent\\": \\"\\\\n \\", \\"id\\": 11 }, { @@ -979,7 +1021,7 @@ exports[`form 1`] = ` }, { \\"type\\": 3, - \\"textContent\\": \\"\\\\n\\", + \\"textContent\\": \\"\\\\n \\", \\"id\\": 14 } ], @@ -987,7 +1029,7 @@ exports[`form 1`] = ` }, { \\"type\\": 3, - \\"textContent\\": \\"\\\\n\\\\n\\", + \\"textContent\\": \\"\\\\n\\\\n \\", \\"id\\": 15 }, { @@ -997,7 +1039,7 @@ exports[`form 1`] = ` \\"childNodes\\": [ { \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", + \\"textContent\\": \\"\\\\n \\", \\"id\\": 17 }, { @@ -1007,7 +1049,7 @@ exports[`form 1`] = ` \\"childNodes\\": [ { \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", + \\"textContent\\": \\"\\\\n \\", \\"id\\": 19 }, { @@ -1019,7 +1061,7 @@ exports[`form 1`] = ` \\"childNodes\\": [ { \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", + \\"textContent\\": \\"\\\\n \\", \\"id\\": 21 }, { @@ -1033,7 +1075,7 @@ exports[`form 1`] = ` }, { \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", + \\"textContent\\": \\"\\\\n \\", \\"id\\": 23 } ], @@ -1041,33 +1083,33 @@ exports[`form 1`] = ` }, { \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", + \\"textContent\\": \\"\\\\n \\", \\"id\\": 24 }, { \\"type\\": 2, \\"tagName\\": \\"label\\", - \\"attributes\\": { - \\"for\\": \\"radio\\" - }, + \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", + \\"textContent\\": \\"\\\\n \\", \\"id\\": 26 }, { \\"type\\": 2, \\"tagName\\": \\"input\\", \\"attributes\\": { - \\"type\\": \\"radio\\" + \\"type\\": \\"radio\\", + \\"name\\": \\"toggle\\", + \\"value\\": \\"on\\" }, \\"childNodes\\": [], \\"id\\": 27 }, { \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", + \\"textContent\\": \\"\\\\n \\", \\"id\\": 28 } ], @@ -1075,33 +1117,34 @@ exports[`form 1`] = ` }, { \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", + \\"textContent\\": \\"\\\\n \\", \\"id\\": 29 }, { \\"type\\": 2, \\"tagName\\": \\"label\\", - \\"attributes\\": { - \\"for\\": \\"checkbox\\" - }, + \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", + \\"textContent\\": \\"\\\\n \\", \\"id\\": 31 }, { \\"type\\": 2, \\"tagName\\": \\"input\\", \\"attributes\\": { - \\"type\\": \\"checkbox\\" + \\"type\\": \\"radio\\", + \\"name\\": \\"toggle\\", + \\"value\\": \\"off\\", + \\"checked\\": true }, \\"childNodes\\": [], \\"id\\": 32 }, { \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", + \\"textContent\\": \\"\\\\n \\", \\"id\\": 33 } ], @@ -1109,21 +1152,55 @@ exports[`form 1`] = ` }, { \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", + \\"textContent\\": \\"\\\\n \\", \\"id\\": 34 }, { \\"type\\": 2, \\"tagName\\": \\"label\\", \\"attributes\\": { - \\"for\\": \\"textarea\\" + \\"for\\": \\"checkbox\\" }, \\"childNodes\\": [ { \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", + \\"textContent\\": \\"\\\\n \\", \\"id\\": 36 }, + { + \\"type\\": 2, + \\"tagName\\": \\"input\\", + \\"attributes\\": { + \\"type\\": \\"checkbox\\" + }, + \\"childNodes\\": [], + \\"id\\": 37 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 38 + } + ], + \\"id\\": 35 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 39 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"label\\", + \\"attributes\\": { + \\"for\\": \\"textarea\\" + }, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 41 + }, { \\"type\\": 2, \\"tagName\\": \\"textarea\\", @@ -1134,20 +1211,20 @@ exports[`form 1`] = ` \\"rows\\": \\"10\\" }, \\"childNodes\\": [], - \\"id\\": 37 + \\"id\\": 42 }, { \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 38 + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 43 } ], - \\"id\\": 35 + \\"id\\": 40 }, { \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 39 + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 44 }, { \\"type\\": 2, @@ -1158,8 +1235,8 @@ exports[`form 1`] = ` \\"childNodes\\": [ { \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 41 + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 46 }, { \\"type\\": 2, @@ -1172,8 +1249,8 @@ exports[`form 1`] = ` \\"childNodes\\": [ { \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 43 + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 48 }, { \\"type\\": 2, @@ -1186,15 +1263,15 @@ exports[`form 1`] = ` { \\"type\\": 3, \\"textContent\\": \\"1\\", - \\"id\\": 45 + \\"id\\": 50 } ], - \\"id\\": 44 + \\"id\\": 49 }, { \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 46 + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 51 }, { \\"type\\": 2, @@ -1206,39 +1283,73 @@ exports[`form 1`] = ` { \\"type\\": 3, \\"textContent\\": \\"2\\", - \\"id\\": 48 + \\"id\\": 53 } ], - \\"id\\": 47 + \\"id\\": 52 }, { \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 49 + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 54 } ], - \\"id\\": 42 + \\"id\\": 47 }, { \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 50 + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 55 } ], - \\"id\\": 40 + \\"id\\": 45 }, { \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 51 + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 56 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"label\\", + \\"attributes\\": { + \\"for\\": \\"password\\" + }, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 58 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"input\\", + \\"attributes\\": { + \\"type\\": \\"password\\" + }, + \\"childNodes\\": [], + \\"id\\": 59 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 60 + } + ], + \\"id\\": 57 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 61 } ], \\"id\\": 18 }, { \\"type\\": 3, - \\"textContent\\": \\"\\\\n\\\\n \\", - \\"id\\": 52 + \\"textContent\\": \\"\\\\n \\\\n \\", + \\"id\\": 62 }, { \\"type\\": 2, @@ -1248,15 +1359,15 @@ exports[`form 1`] = ` { \\"type\\": 3, \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", - \\"id\\": 54 + \\"id\\": 64 } ], - \\"id\\": 53 + \\"id\\": 63 }, { \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\\\n \\\\n\\\\n\\\\n\\", - \\"id\\": 55 + \\"textContent\\": \\"\\\\n \\\\n \\\\n\\\\n\\", + \\"id\\": 65 } ], \\"id\\": 16 @@ -1366,12 +1477,21 @@ exports[`form 1`] = ` \\"id\\": 27 } }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 5, + \\"text\\": \\"off\\", + \\"isChecked\\": false, + \\"id\\": 32 + } + }, { \\"type\\": 3, \\"data\\": { \\"source\\": 2, \\"type\\": 1, - \\"id\\": 32 + \\"id\\": 37 } }, { @@ -1387,7 +1507,7 @@ exports[`form 1`] = ` \\"data\\": { \\"source\\": 2, \\"type\\": 5, - \\"id\\": 32 + \\"id\\": 37 } }, { @@ -1395,7 +1515,7 @@ exports[`form 1`] = ` \\"data\\": { \\"source\\": 2, \\"type\\": 0, - \\"id\\": 32 + \\"id\\": 37 } }, { @@ -1403,7 +1523,7 @@ exports[`form 1`] = ` \\"data\\": { \\"source\\": 2, \\"type\\": 2, - \\"id\\": 32 + \\"id\\": 37 } }, { @@ -1412,7 +1532,7 @@ exports[`form 1`] = ` \\"source\\": 5, \\"text\\": \\"on\\", \\"isChecked\\": true, - \\"id\\": 32 + \\"id\\": 37 } }, { @@ -1420,7 +1540,7 @@ exports[`form 1`] = ` \\"data\\": { \\"source\\": 2, \\"type\\": 6, - \\"id\\": 32 + \\"id\\": 37 } }, { @@ -1428,7 +1548,7 @@ exports[`form 1`] = ` \\"data\\": { \\"source\\": 2, \\"type\\": 5, - \\"id\\": 37 + \\"id\\": 42 } }, { @@ -1437,7 +1557,7 @@ exports[`form 1`] = ` \\"source\\": 5, \\"text\\": \\"t\\", \\"isChecked\\": false, - \\"id\\": 37 + \\"id\\": 42 } }, { @@ -1446,7 +1566,7 @@ exports[`form 1`] = ` \\"source\\": 5, \\"text\\": \\"te\\", \\"isChecked\\": false, - \\"id\\": 37 + \\"id\\": 42 } }, { @@ -1455,7 +1575,7 @@ exports[`form 1`] = ` \\"source\\": 5, \\"text\\": \\"tex\\", \\"isChecked\\": false, - \\"id\\": 37 + \\"id\\": 42 } }, { @@ -1464,7 +1584,7 @@ exports[`form 1`] = ` \\"source\\": 5, \\"text\\": \\"text\\", \\"isChecked\\": false, - \\"id\\": 37 + \\"id\\": 42 } }, { @@ -1473,7 +1593,7 @@ exports[`form 1`] = ` \\"source\\": 5, \\"text\\": \\"texta\\", \\"isChecked\\": false, - \\"id\\": 37 + \\"id\\": 42 } }, { @@ -1482,7 +1602,7 @@ exports[`form 1`] = ` \\"source\\": 5, \\"text\\": \\"textar\\", \\"isChecked\\": false, - \\"id\\": 37 + \\"id\\": 42 } }, { @@ -1491,7 +1611,7 @@ exports[`form 1`] = ` \\"source\\": 5, \\"text\\": \\"textare\\", \\"isChecked\\": false, - \\"id\\": 37 + \\"id\\": 42 } }, { @@ -1500,7 +1620,7 @@ exports[`form 1`] = ` \\"source\\": 5, \\"text\\": \\"textarea\\", \\"isChecked\\": false, - \\"id\\": 37 + \\"id\\": 42 } }, { @@ -1509,7 +1629,7 @@ exports[`form 1`] = ` \\"source\\": 5, \\"text\\": \\"textarea \\", \\"isChecked\\": false, - \\"id\\": 37 + \\"id\\": 42 } }, { @@ -1518,7 +1638,7 @@ exports[`form 1`] = ` \\"source\\": 5, \\"text\\": \\"textarea t\\", \\"isChecked\\": false, - \\"id\\": 37 + \\"id\\": 42 } }, { @@ -1527,7 +1647,7 @@ exports[`form 1`] = ` \\"source\\": 5, \\"text\\": \\"textarea te\\", \\"isChecked\\": false, - \\"id\\": 37 + \\"id\\": 42 } }, { @@ -1536,7 +1656,7 @@ exports[`form 1`] = ` \\"source\\": 5, \\"text\\": \\"textarea tes\\", \\"isChecked\\": false, - \\"id\\": 37 + \\"id\\": 42 } }, { @@ -1545,7 +1665,7 @@ exports[`form 1`] = ` \\"source\\": 5, \\"text\\": \\"textarea test\\", \\"isChecked\\": false, - \\"id\\": 37 + \\"id\\": 42 } }, { @@ -1554,13 +1674,13 @@ exports[`form 1`] = ` \\"source\\": 5, \\"text\\": \\"1\\", \\"isChecked\\": false, - \\"id\\": 42 + \\"id\\": 47 } } ]" `; -exports[`frozen 1`] = ` +exports[`record integration tests can record node mutations 1`] = ` "[ { \\"type\\": 0, @@ -1584,22 +1704,24 @@ exports[`frozen 1`] = ` \\"node\\": { \\"type\\": 0, \\"childNodes\\": [ + { + \\"type\\": 1, + \\"name\\": \\"html\\", + \\"publicId\\": \\"\\", + \\"systemId\\": \\"\\", + \\"id\\": 2 + }, { \\"type\\": 2, \\"tagName\\": \\"html\\", - \\"attributes\\": {}, + \\"attributes\\": { + \\"lang\\": \\"en\\" + }, \\"childNodes\\": [ { \\"type\\": 2, \\"tagName\\": \\"head\\", \\"attributes\\": {}, - \\"childNodes\\": [], - \\"id\\": 3 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"body\\", - \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, @@ -1608,293 +1730,88 @@ exports[`frozen 1`] = ` }, { \\"type\\": 2, - \\"tagName\\": \\"p\\", - \\"attributes\\": {}, - \\"childNodes\\": [ - { - \\"type\\": 3, - \\"textContent\\": \\"mutation observer\\", - \\"id\\": 7 - } - ], + \\"tagName\\": \\"meta\\", + \\"attributes\\": { + \\"charset\\": \\"UTF-8\\" + }, + \\"childNodes\\": [], \\"id\\": 6 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", - \\"id\\": 8 + \\"id\\": 7 }, { \\"type\\": 2, - \\"tagName\\": \\"ul\\", - \\"attributes\\": {}, - \\"childNodes\\": [ - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 10 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"li\\", - \\"attributes\\": {}, - \\"childNodes\\": [], - \\"id\\": 11 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 12 - } - ], + \\"tagName\\": \\"meta\\", + \\"attributes\\": { + \\"name\\": \\"viewport\\", + \\"content\\": \\"width=device-width, initial-scale=1.0\\" + }, + \\"childNodes\\": [], + \\"id\\": 8 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", \\"id\\": 9 }, + { + \\"type\\": 2, + \\"tagName\\": \\"meta\\", + \\"attributes\\": { + \\"http-equiv\\": \\"X-UA-Compatible\\", + \\"content\\": \\"ie=edge\\" + }, + \\"childNodes\\": [], + \\"id\\": 10 + }, { \\"type\\": 3, - \\"textContent\\": \\"\\\\n\\\\n \\", - \\"id\\": 13 + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 11 }, { \\"type\\": 2, - \\"tagName\\": \\"script\\", + \\"tagName\\": \\"title\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, - \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", - \\"id\\": 15 + \\"textContent\\": \\"Select2 3.5\\", + \\"id\\": 13 } ], - \\"id\\": 14 + \\"id\\": 12 }, { \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\\\n \\", - \\"id\\": 16 - } - ], - \\"id\\": 4 - } - ], - \\"id\\": 2 - } - ], - \\"id\\": 1 - }, - \\"initialOffset\\": { - \\"left\\": 0, - \\"top\\": 0 - } - } - }, - { - \\"type\\": 3, - \\"data\\": { - \\"source\\": 0, - \\"texts\\": [], - \\"attributes\\": [ - { - \\"id\\": 17, - \\"attributes\\": { - \\"foo\\": \\"bar\\" - } - }, - { - \\"id\\": 4, - \\"attributes\\": { - \\"test\\": \\"true\\" - } - } - ], - \\"removes\\": [], - \\"adds\\": [ - { - \\"parentId\\": 9, - \\"nextId\\": null, - \\"node\\": { - \\"type\\": 2, - \\"tagName\\": \\"li\\", - \\"attributes\\": { - \\"foo\\": \\"bar\\" - }, - \\"childNodes\\": [], - \\"id\\": 17 - } - } - ] - } - } -]" -`; - -exports[`iframe 1`] = ` -"[ - { - \\"type\\": 0, - \\"data\\": {} - }, - { - \\"type\\": 1, - \\"data\\": {} - }, - { - \\"type\\": 4, - \\"data\\": { - \\"href\\": \\"about:blank\\", - \\"width\\": 1920, - \\"height\\": 1080 - } - }, - { - \\"type\\": 3, - \\"data\\": { - \\"source\\": 0, - \\"adds\\": [ - { - \\"parentId\\": 19, - \\"nextId\\": null, - \\"node\\": { - \\"type\\": 0, - \\"childNodes\\": [ - { - \\"type\\": 2, - \\"tagName\\": \\"html\\", - \\"attributes\\": {}, - \\"childNodes\\": [ - { - \\"type\\": 2, - \\"tagName\\": \\"head\\", - \\"attributes\\": {}, - \\"childNodes\\": [], - \\"rootId\\": 20, - \\"id\\": 22 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"body\\", - \\"attributes\\": {}, - \\"childNodes\\": [], - \\"rootId\\": 20, - \\"id\\": 23 - } - ], - \\"rootId\\": 20, - \\"id\\": 21 - } - ], - \\"id\\": 20 - } - } - ], - \\"removes\\": [], - \\"texts\\": [], - \\"attributes\\": [], - \\"isAttachIframe\\": true - } - }, - { - \\"type\\": 2, - \\"data\\": { - \\"node\\": { - \\"type\\": 0, - \\"childNodes\\": [ - { - \\"type\\": 1, - \\"name\\": \\"html\\", - \\"publicId\\": \\"\\", - \\"systemId\\": \\"\\", - \\"id\\": 2 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"html\\", - \\"attributes\\": { - \\"lang\\": \\"en\\" - }, - \\"childNodes\\": [ - { - \\"type\\": 2, - \\"tagName\\": \\"head\\", - \\"attributes\\": {}, - \\"childNodes\\": [ - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 5 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"meta\\", - \\"attributes\\": { - \\"charset\\": \\"UTF-8\\" - }, - \\"childNodes\\": [], - \\"id\\": 6 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 7 + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 14 }, { \\"type\\": 2, - \\"tagName\\": \\"meta\\", + \\"tagName\\": \\"link\\", \\"attributes\\": { - \\"name\\": \\"viewport\\", - \\"content\\": \\"width=device-width, initial-scale=1.0\\" + \\"rel\\": \\"stylesheet\\", + \\"href\\": \\"https://cdn.jsdelivr.net/npm/select2@3.5.1/select2.css\\" }, \\"childNodes\\": [], - \\"id\\": 8 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 9 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"title\\", - \\"attributes\\": {}, - \\"childNodes\\": [ - { - \\"type\\": 3, - \\"textContent\\": \\"Main\\", - \\"id\\": 11 - } - ], - \\"id\\": 10 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 12 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"style\\", - \\"attributes\\": {}, - \\"childNodes\\": [ - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n iframe {\\\\n width: 500px;\\\\n height: 500px;\\\\n }\\\\n \\", - \\"isStyle\\": true, - \\"id\\": 14 - } - ], - \\"id\\": 13 + \\"id\\": 15 }, { \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 15 + \\"textContent\\": \\"\\\\n\\", + \\"id\\": 16 } ], \\"id\\": 4 }, { \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 16 + \\"textContent\\": \\"\\\\n\\", + \\"id\\": 17 }, { \\"type\\": 2, @@ -1903,507 +1820,402 @@ exports[`iframe 1`] = ` \\"childNodes\\": [ { \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 18 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"iframe\\", - \\"attributes\\": { - \\"id\\": \\"one\\" - }, - \\"childNodes\\": [], + \\"textContent\\": \\"\\\\n \\", \\"id\\": 19 }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\\\n \\", - \\"id\\": 24 - }, { \\"type\\": 2, - \\"tagName\\": \\"script\\", + \\"tagName\\": \\"blockquote\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, - \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", - \\"id\\": 26 + \\"textContent\\": \\"\\\\n Select2 is a jQuery replacement for select boxes.\\\\n \\", + \\"id\\": 21 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"br\\", + \\"attributes\\": {}, + \\"childNodes\\": [], + \\"id\\": 22 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n In the 3.5 version it use a quite complicated DOM generation strategy which is a good battle-test for rrweb's recorder.\\\\n \\", + \\"id\\": 23 } ], - \\"id\\": 25 + \\"id\\": 20 }, { \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\\\n \\\\n \\", - \\"id\\": 27 + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 24 }, { \\"type\\": 2, - \\"tagName\\": \\"script\\", - \\"attributes\\": {}, + \\"tagName\\": \\"div\\", + \\"attributes\\": { + \\"class\\": \\"select2-container\\", + \\"id\\": \\"s2id_el\\" + }, \\"childNodes\\": [ { - \\"type\\": 3, - \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", - \\"id\\": 29 - } - ], - \\"id\\": 28 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n\\\\n\\", - \\"id\\": 30 - } - ], - \\"id\\": 17 - } - ], - \\"id\\": 3 - } - ], - \\"id\\": 1 - }, - \\"initialOffset\\": { - \\"left\\": 0, - \\"top\\": 0 - } - } - }, - { - \\"type\\": 3, - \\"data\\": { - \\"source\\": 0, - \\"texts\\": [], - \\"attributes\\": [], - \\"removes\\": [], - \\"adds\\": [ - { - \\"parentId\\": 17, - \\"nextId\\": null, - \\"node\\": { - \\"type\\": 2, - \\"tagName\\": \\"iframe\\", - \\"attributes\\": { - \\"id\\": \\"two\\" - }, - \\"childNodes\\": [], - \\"id\\": 31 - } - } - ] - } - }, - { - \\"type\\": 3, - \\"data\\": { - \\"source\\": 0, - \\"adds\\": [ - { - \\"parentId\\": 47, - \\"nextId\\": null, - \\"node\\": { - \\"type\\": 0, - \\"childNodes\\": [ - { - \\"type\\": 2, - \\"tagName\\": \\"html\\", - \\"attributes\\": {}, - \\"childNodes\\": [ - { - \\"type\\": 2, - \\"tagName\\": \\"head\\", - \\"attributes\\": {}, - \\"childNodes\\": [], - \\"rootId\\": 48, - \\"id\\": 50 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"body\\", - \\"attributes\\": {}, - \\"childNodes\\": [], - \\"rootId\\": 48, - \\"id\\": 51 - } - ], - \\"rootId\\": 48, - \\"id\\": 49 - } - ], - \\"id\\": 48 - } - } - ], - \\"removes\\": [], - \\"texts\\": [], - \\"attributes\\": [], - \\"isAttachIframe\\": true - } - }, - { - \\"type\\": 3, - \\"data\\": { - \\"source\\": 0, - \\"adds\\": [ - { - \\"parentId\\": 53, - \\"nextId\\": null, - \\"node\\": { - \\"type\\": 0, - \\"childNodes\\": [ - { - \\"type\\": 1, - \\"name\\": \\"html\\", - \\"publicId\\": \\"\\", - \\"systemId\\": \\"\\", - \\"rootId\\": 54, - \\"id\\": 55 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"html\\", - \\"attributes\\": { - \\"lang\\": \\"en\\" - }, - \\"childNodes\\": [ - { - \\"type\\": 2, - \\"tagName\\": \\"head\\", - \\"attributes\\": {}, - \\"childNodes\\": [ - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"rootId\\": 54, - \\"id\\": 58 + \\"type\\": 2, + \\"tagName\\": \\"a\\", + \\"attributes\\": { + \\"href\\": \\"javascript:void(0)\\", + \\"class\\": \\"select2-choice\\", + \\"tabindex\\": \\"-1\\" + }, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\" \\", + \\"id\\": 27 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"span\\", + \\"attributes\\": { + \\"class\\": \\"select2-chosen\\", + \\"id\\": \\"select2-chosen-1\\" + }, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"A\\", + \\"id\\": 29 + } + ], + \\"id\\": 28 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"abbr\\", + \\"attributes\\": { + \\"class\\": \\"select2-search-choice-close\\" + }, + \\"childNodes\\": [], + \\"id\\": 30 + }, + { + \\"type\\": 3, + \\"textContent\\": \\" \\", + \\"id\\": 31 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"span\\", + \\"attributes\\": { + \\"class\\": \\"select2-arrow\\", + \\"role\\": \\"presentation\\" + }, + \\"childNodes\\": [ + { + \\"type\\": 2, + \\"tagName\\": \\"b\\", + \\"attributes\\": { + \\"role\\": \\"presentation\\" + }, + \\"childNodes\\": [], + \\"id\\": 33 + } + ], + \\"id\\": 32 + } + ], + \\"id\\": 26 }, { \\"type\\": 2, - \\"tagName\\": \\"meta\\", + \\"tagName\\": \\"label\\", \\"attributes\\": { - \\"charset\\": \\"UTF-8\\" + \\"for\\": \\"s2id_autogen1\\", + \\"class\\": \\"select2-offscreen\\" }, \\"childNodes\\": [], - \\"rootId\\": 54, - \\"id\\": 59 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"rootId\\": 54, - \\"id\\": 60 + \\"id\\": 34 }, { \\"type\\": 2, - \\"tagName\\": \\"meta\\", + \\"tagName\\": \\"input\\", \\"attributes\\": { - \\"name\\": \\"viewport\\", - \\"content\\": \\"width=device-width, initial-scale=1.0\\" + \\"class\\": \\"select2-focusser select2-offscreen\\", + \\"type\\": \\"text\\", + \\"aria-haspopup\\": \\"true\\", + \\"role\\": \\"button\\", + \\"aria-labelledby\\": \\"select2-chosen-1\\", + \\"id\\": \\"s2id_autogen1\\" }, \\"childNodes\\": [], - \\"rootId\\": 54, - \\"id\\": 61 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"rootId\\": 54, - \\"id\\": 62 + \\"id\\": 35 }, { \\"type\\": 2, - \\"tagName\\": \\"title\\", - \\"attributes\\": {}, + \\"tagName\\": \\"div\\", + \\"attributes\\": { + \\"class\\": \\"select2-drop select2-display-none select2-with-searchbox\\" + }, \\"childNodes\\": [ { \\"type\\": 3, - \\"textContent\\": \\"Frame 2\\", - \\"rootId\\": 54, - \\"id\\": 64 - } - ], - \\"rootId\\": 54, - \\"id\\": 63 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"rootId\\": 54, - \\"id\\": 65 - } - ], - \\"rootId\\": 54, - \\"id\\": 57 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"rootId\\": 54, - \\"id\\": 66 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"body\\", - \\"attributes\\": {}, - \\"childNodes\\": [ - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n frame 2\\\\n \\\\n \\", - \\"rootId\\": 54, - \\"id\\": 68 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"script\\", - \\"attributes\\": {}, - \\"childNodes\\": [ + \\"textContent\\": \\" \\", + \\"id\\": 37 + }, { - \\"type\\": 3, - \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", - \\"rootId\\": 54, - \\"id\\": 70 + \\"type\\": 2, + \\"tagName\\": \\"div\\", + \\"attributes\\": { + \\"class\\": \\"select2-search\\" + }, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\" \\", + \\"id\\": 39 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"label\\", + \\"attributes\\": { + \\"for\\": \\"s2id_autogen1_search\\", + \\"class\\": \\"select2-offscreen\\" + }, + \\"childNodes\\": [], + \\"id\\": 40 + }, + { + \\"type\\": 3, + \\"textContent\\": \\" \\", + \\"id\\": 41 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"input\\", + \\"attributes\\": { + \\"type\\": \\"text\\", + \\"autocomplete\\": \\"off\\", + \\"autocorrect\\": \\"off\\", + \\"autocapitalize\\": \\"off\\", + \\"spellcheck\\": \\"false\\", + \\"class\\": \\"select2-input\\", + \\"role\\": \\"combobox\\", + \\"aria-expanded\\": \\"true\\", + \\"aria-autocomplete\\": \\"list\\", + \\"aria-owns\\": \\"select2-results-1\\", + \\"id\\": \\"s2id_autogen1_search\\", + \\"placeholder\\": \\"\\" + }, + \\"childNodes\\": [], + \\"id\\": 42 + }, + { + \\"type\\": 3, + \\"textContent\\": \\" \\", + \\"id\\": 43 + } + ], + \\"id\\": 38 + }, + { + \\"type\\": 3, + \\"textContent\\": \\" \\", + \\"id\\": 44 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"ul\\", + \\"attributes\\": { + \\"class\\": \\"select2-results\\", + \\"role\\": \\"listbox\\", + \\"id\\": \\"select2-results-1\\" + }, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\" \\", + \\"id\\": 46 + } + ], + \\"id\\": 45 } ], - \\"rootId\\": 54, - \\"id\\": 69 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n\\\\n\\", - \\"rootId\\": 54, - \\"id\\": 71 + \\"id\\": 36 } ], - \\"rootId\\": 54, - \\"id\\": 67 - } - ], - \\"rootId\\": 54, - \\"id\\": 56 - } - ], - \\"id\\": 54 - } - } - ], - \\"removes\\": [], - \\"texts\\": [], - \\"attributes\\": [], - \\"isAttachIframe\\": true - } - }, - { - \\"type\\": 3, - \\"data\\": { - \\"source\\": 0, - \\"adds\\": [ - { - \\"parentId\\": 31, - \\"nextId\\": null, - \\"node\\": { - \\"type\\": 0, - \\"childNodes\\": [ - { - \\"type\\": 1, - \\"name\\": \\"html\\", - \\"publicId\\": \\"\\", - \\"systemId\\": \\"\\", - \\"rootId\\": 32, - \\"id\\": 33 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"html\\", - \\"attributes\\": { - \\"lang\\": \\"en\\" - }, - \\"childNodes\\": [ + \\"id\\": 25 + }, { \\"type\\": 2, - \\"tagName\\": \\"head\\", - \\"attributes\\": {}, + \\"tagName\\": \\"select\\", + \\"attributes\\": { + \\"id\\": \\"el\\", + \\"tabindex\\": \\"-1\\", + \\"title\\": \\"\\", + \\"style\\": \\"display: none;\\", + \\"value\\": \\"a\\" + }, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", - \\"rootId\\": 32, - \\"id\\": 36 + \\"id\\": 48 }, { \\"type\\": 2, - \\"tagName\\": \\"meta\\", + \\"tagName\\": \\"option\\", \\"attributes\\": { - \\"charset\\": \\"UTF-8\\" + \\"value\\": \\"a\\", + \\"selected\\": true }, - \\"childNodes\\": [], - \\"rootId\\": 32, - \\"id\\": 37 + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"A\\", + \\"id\\": 50 + } + ], + \\"id\\": 49 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", - \\"rootId\\": 32, - \\"id\\": 38 + \\"id\\": 51 }, { \\"type\\": 2, - \\"tagName\\": \\"meta\\", + \\"tagName\\": \\"option\\", \\"attributes\\": { - \\"name\\": \\"viewport\\", - \\"content\\": \\"width=device-width, initial-scale=1.0\\" + \\"value\\": \\"b\\" }, - \\"childNodes\\": [], - \\"rootId\\": 32, - \\"id\\": 39 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"rootId\\": 32, - \\"id\\": 40 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"title\\", - \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, - \\"textContent\\": \\"Frame 1\\", - \\"rootId\\": 32, - \\"id\\": 42 + \\"textContent\\": \\"B\\", + \\"id\\": 53 } ], - \\"rootId\\": 32, - \\"id\\": 41 + \\"id\\": 52 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", - \\"rootId\\": 32, - \\"id\\": 43 + \\"id\\": 54 } ], - \\"rootId\\": 32, - \\"id\\": 35 + \\"id\\": 47 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", - \\"rootId\\": 32, - \\"id\\": 44 + \\"id\\": 55 }, { \\"type\\": 2, - \\"tagName\\": \\"body\\", + \\"tagName\\": \\"script\\", + \\"attributes\\": { + \\"src\\": \\"https://cdn.jsdelivr.net/npm/jquery@3.3.1/dist/jquery.min.js\\" + }, + \\"childNodes\\": [], + \\"id\\": 56 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 57 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"script\\", + \\"attributes\\": { + \\"src\\": \\"https://cdn.jsdelivr.net/npm/select2@3.5.2-browserify/select2.min.js\\" + }, + \\"childNodes\\": [], + \\"id\\": 58 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 59 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"script\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, - \\"textContent\\": \\"\\\\n frame 1\\\\n \\", - \\"rootId\\": 32, - \\"id\\": 46 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"iframe\\", - \\"attributes\\": { - \\"id\\": \\"three\\", - \\"frameborder\\": \\"0\\" - }, - \\"childNodes\\": [], - \\"rootId\\": 32, - \\"id\\": 47 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"rootId\\": 32, - \\"id\\": 52 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"iframe\\", - \\"attributes\\": { - \\"id\\": \\"four\\", - \\"frameborder\\": \\"0\\" - }, - \\"childNodes\\": [], - \\"rootId\\": 32, - \\"id\\": 53 - }, + \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", + \\"id\\": 61 + } + ], + \\"id\\": 60 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"span\\", + \\"attributes\\": { + \\"role\\": \\"status\\", + \\"aria-live\\": \\"polite\\", + \\"class\\": \\"select2-hidden-accessible\\" + }, + \\"childNodes\\": [], + \\"id\\": 62 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n\\\\n \\", + \\"id\\": 63 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"script\\", + \\"attributes\\": {}, + \\"childNodes\\": [ { \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\\\n\\\\n\\", - \\"rootId\\": 32, - \\"id\\": 72 + \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", + \\"id\\": 65 } ], - \\"rootId\\": 32, - \\"id\\": 45 + \\"id\\": 64 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\\\n \\\\n\\", + \\"id\\": 66 } ], - \\"rootId\\": 32, - \\"id\\": 34 + \\"id\\": 18 } ], - \\"id\\": 32 + \\"id\\": 3 } - } - ], - \\"removes\\": [], - \\"texts\\": [], - \\"attributes\\": [], - \\"isAttachIframe\\": true + ], + \\"id\\": 1 + }, + \\"initialOffset\\": { + \\"left\\": 0, + \\"top\\": 0 + } } }, { \\"type\\": 3, \\"data\\": { - \\"source\\": 0, - \\"adds\\": [ - { - \\"parentId\\": 73, - \\"nextId\\": null, - \\"node\\": { - \\"type\\": 0, - \\"childNodes\\": [ - { - \\"type\\": 2, - \\"tagName\\": \\"html\\", - \\"attributes\\": {}, - \\"childNodes\\": [ - { - \\"type\\": 2, - \\"tagName\\": \\"head\\", - \\"attributes\\": {}, - \\"childNodes\\": [], - \\"rootId\\": 74, - \\"id\\": 76 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"body\\", - \\"attributes\\": {}, - \\"childNodes\\": [], - \\"rootId\\": 74, - \\"id\\": 77 - } - ], - \\"rootId\\": 74, - \\"id\\": 75 - } - ], - \\"id\\": 74 - } - } - ], - \\"removes\\": [], - \\"texts\\": [], - \\"attributes\\": [], - \\"isAttachIframe\\": true + \\"source\\": 2, + \\"type\\": 1, + \\"id\\": 26 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 2, + \\"type\\": 5, + \\"id\\": 42 } }, { @@ -2411,1934 +2223,519 @@ exports[`iframe 1`] = ` \\"data\\": { \\"source\\": 0, \\"texts\\": [], - \\"attributes\\": [], - \\"removes\\": [], + \\"attributes\\": [ + { + \\"id\\": 25, + \\"attributes\\": { + \\"class\\": \\"select2-container select2-dropdown-open select2-container-active\\" + } + }, + { + \\"id\\": 36, + \\"attributes\\": { + \\"id\\": \\"select2-drop\\", + \\"style\\": { + \\"left\\": \\"Npx\\", + \\"width\\": \\"Npx\\", + \\"top\\": \\"Npx\\", + \\"bottom\\": \\"auto\\", + \\"display\\": \\"block\\", + \\"position\\": false, + \\"visibility\\": false + }, + \\"class\\": \\"select2-drop select2-display-none select2-with-searchbox select2-drop-active\\" + } + }, + { + \\"id\\": 70, + \\"attributes\\": { + \\"style\\": { + \\"display\\": false + } + } + }, + { + \\"id\\": 42, + \\"attributes\\": { + \\"class\\": \\"select2-input select2-focused\\", + \\"aria-activedescendant\\": \\"select2-result-label-2\\" + } + }, + { + \\"id\\": 35, + \\"attributes\\": { + \\"disabled\\": \\"\\" + } + }, + { + \\"id\\": 72, + \\"attributes\\": { + \\"class\\": \\"select2-results-dept-0 select2-result select2-result-selectable select2-highlighted\\" + } + } + ], + \\"removes\\": [ + { + \\"parentId\\": 25, + \\"id\\": 26 + }, + { + \\"parentId\\": 25, + \\"id\\": 36 + }, + { + \\"parentId\\": 45, + \\"id\\": 46 + } + ], \\"adds\\": [ { - \\"parentId\\": 67, - \\"nextId\\": null, + \\"parentId\\": 25, + \\"nextId\\": 34, \\"node\\": { \\"type\\": 2, - \\"tagName\\": \\"iframe\\", + \\"tagName\\": \\"a\\", \\"attributes\\": { - \\"id\\": \\"five\\" + \\"href\\": \\"javascript:void(0)\\", + \\"class\\": \\"select2-choice\\", + \\"tabindex\\": \\"-1\\" }, \\"childNodes\\": [], - \\"rootId\\": 54, - \\"id\\": 73 + \\"id\\": 26 } - } - ] - } - } -]" -`; - -exports[`ignore 1`] = ` -"[ - { - \\"type\\": 0, - \\"data\\": {} - }, - { - \\"type\\": 1, - \\"data\\": {} - }, - { - \\"type\\": 4, - \\"data\\": { - \\"href\\": \\"about:blank\\", - \\"width\\": 1920, - \\"height\\": 1080 - } - }, - { - \\"type\\": 2, - \\"data\\": { - \\"node\\": { - \\"type\\": 0, - \\"childNodes\\": [ - { - \\"type\\": 1, - \\"name\\": \\"html\\", - \\"publicId\\": \\"\\", - \\"systemId\\": \\"\\", - \\"id\\": 2 - }, - { + }, + { + \\"parentId\\": 26, + \\"nextId\\": 28, + \\"node\\": { + \\"type\\": 3, + \\"textContent\\": \\" \\", + \\"id\\": 27 + } + }, + { + \\"parentId\\": 26, + \\"nextId\\": 30, + \\"node\\": { \\"type\\": 2, - \\"tagName\\": \\"html\\", + \\"tagName\\": \\"span\\", \\"attributes\\": { - \\"lang\\": \\"en\\" + \\"class\\": \\"select2-chosen\\", + \\"id\\": \\"select2-chosen-1\\" }, - \\"childNodes\\": [ - { - \\"type\\": 2, - \\"tagName\\": \\"head\\", - \\"attributes\\": {}, - \\"childNodes\\": [ - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 5 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"meta\\", - \\"attributes\\": { - \\"charset\\": \\"UTF-8\\" - }, - \\"childNodes\\": [], - \\"id\\": 6 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 7 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"meta\\", - \\"attributes\\": { - \\"name\\": \\"viewport\\", - \\"content\\": \\"width=device-width, initial-scale=1.0\\" - }, - \\"childNodes\\": [], - \\"id\\": 8 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 9 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"meta\\", - \\"attributes\\": { - \\"http-equiv\\": \\"X-UA-Compatible\\", - \\"content\\": \\"ie=edge\\" - }, - \\"childNodes\\": [], - \\"id\\": 10 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 11 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"title\\", - \\"attributes\\": {}, - \\"childNodes\\": [ - { - \\"type\\": 3, - \\"textContent\\": \\"ignore fields\\", - \\"id\\": 13 - } - ], - \\"id\\": 12 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 14 - } - ], - \\"id\\": 4 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n\\\\n \\", - \\"id\\": 15 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"body\\", - \\"attributes\\": {}, - \\"childNodes\\": [ - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 17 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"form\\", - \\"attributes\\": {}, - \\"childNodes\\": [ - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 19 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"label\\", - \\"attributes\\": { - \\"for\\": \\"password\\" - }, - \\"childNodes\\": [ - { - \\"type\\": 3, - \\"textContent\\": \\" \\", - \\"id\\": 21 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"input\\", - \\"attributes\\": { - \\"type\\": \\"password\\" - }, - \\"childNodes\\": [], - \\"id\\": 22 - }, - { - \\"type\\": 3, - \\"textContent\\": \\" \\", - \\"id\\": 23 - } - ], - \\"id\\": 20 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 24 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"label\\", - \\"attributes\\": { - \\"for\\": \\"ignore text\\" - }, - \\"childNodes\\": [ - { - \\"type\\": 3, - \\"textContent\\": \\" \\", - \\"id\\": 26 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"input\\", - \\"attributes\\": { - \\"type\\": \\"text\\", - \\"class\\": \\"rr-ignore\\" - }, - \\"childNodes\\": [], - \\"id\\": 27 - }, - { - \\"type\\": 3, - \\"textContent\\": \\" \\", - \\"id\\": 28 - } - ], - \\"id\\": 25 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 29 - } - ], - \\"id\\": 18 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\\\n \\", - \\"id\\": 30 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"script\\", - \\"attributes\\": {}, - \\"childNodes\\": [ - { - \\"type\\": 3, - \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", - \\"id\\": 32 - } - ], - \\"id\\": 31 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\\\n \\\\n\\\\n\\", - \\"id\\": 33 - } - ], - \\"id\\": 16 - } - ], - \\"id\\": 3 + \\"childNodes\\": [], + \\"id\\": 28 } - ], - \\"id\\": 1 - }, - \\"initialOffset\\": { - \\"left\\": 0, - \\"top\\": 0 - } - } - }, - { - \\"type\\": 3, - \\"data\\": { - \\"source\\": 2, - \\"type\\": 5, - \\"id\\": 22 - } - }, - { - \\"type\\": 3, - \\"data\\": { - \\"source\\": 2, - \\"type\\": 6, - \\"id\\": 22 - } - }, - { - \\"type\\": 3, - \\"data\\": { - \\"source\\": 2, - \\"type\\": 5, - \\"id\\": 27 - } - } -]" -`; - -exports[`log`] = ` -"[ - { - \\"type\\": 0, - \\"data\\": {} - }, - { - \\"type\\": 1, - \\"data\\": {} - }, - { - \\"type\\": 4, - \\"data\\": { - \\"href\\": \\"about:blank\\", - \\"width\\": 1920, - \\"height\\": 1080 - } - }, - { - \\"type\\": 2, - \\"data\\": { - \\"node\\": { - \\"type\\": 0, - \\"childNodes\\": [ - { - \\"type\\": 1, - \\"name\\": \\"html\\", - \\"publicId\\": \\"\\", - \\"systemId\\": \\"\\", - \\"id\\": 2 - }, - { + }, + { + \\"parentId\\": 28, + \\"nextId\\": null, + \\"node\\": { + \\"type\\": 3, + \\"textContent\\": \\"A\\", + \\"id\\": 29 + } + }, + { + \\"parentId\\": 26, + \\"nextId\\": 31, + \\"node\\": { \\"type\\": 2, - \\"tagName\\": \\"html\\", + \\"tagName\\": \\"abbr\\", \\"attributes\\": { - \\"lang\\": \\"en\\" + \\"class\\": \\"select2-search-choice-close\\" }, - \\"childNodes\\": [ - { - \\"type\\": 2, - \\"tagName\\": \\"head\\", - \\"attributes\\": {}, - \\"childNodes\\": [ - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 5 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"meta\\", - \\"attributes\\": { - \\"charset\\": \\"UTF-8\\" - }, - \\"childNodes\\": [], - \\"id\\": 6 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 7 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"meta\\", - \\"attributes\\": { - \\"name\\": \\"viewport\\", - \\"content\\": \\"width=device-width, initial-scale=1.0\\" - }, - \\"childNodes\\": [], - \\"id\\": 8 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 9 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"meta\\", - \\"attributes\\": { - \\"http-equiv\\": \\"X-UA-Compatible\\", - \\"content\\": \\"ie=edge\\" - }, - \\"childNodes\\": [], - \\"id\\": 10 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 11 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"title\\", - \\"attributes\\": {}, - \\"childNodes\\": [ - { - \\"type\\": 3, - \\"textContent\\": \\"Log record\\", - \\"id\\": 13 - } - ], - \\"id\\": 12 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 14 - } - ], - \\"id\\": 4 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 15 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"body\\", - \\"attributes\\": {}, - \\"childNodes\\": [ - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 17 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"script\\", - \\"attributes\\": {}, - \\"childNodes\\": [ - { - \\"type\\": 3, - \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", - \\"id\\": 19 - } - ], - \\"id\\": 18 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\\\n \\\\n\\\\n\\", - \\"id\\": 20 - } - ], - \\"id\\": 16 - } - ], - \\"id\\": 3 + \\"childNodes\\": [], + \\"id\\": 30 } - ], - \\"id\\": 1 - }, - \\"initialOffset\\": { - \\"left\\": 0, - \\"top\\": 0 - } - } - }, - { - \\"type\\": 3, - \\"data\\": { - \\"source\\": 11, - \\"level\\": \\"assert\\", - \\"payload\\": [ - \\"true\\", - \\"\\"assert\\"\\" - ] - } - }, - { - \\"type\\": 3, - \\"data\\": { - \\"source\\": 11, - \\"level\\": \\"count\\", - \\"payload\\": [ - \\"\\"count\\"\\" - ] - } - }, - { - \\"type\\": 3, - \\"data\\": { - \\"source\\": 11, - \\"level\\": \\"countReset\\", - \\"payload\\": [ - \\"\\"count\\"\\" - ] - } - }, - { - \\"type\\": 3, - \\"data\\": { - \\"source\\": 11, - \\"level\\": \\"debug\\", - \\"payload\\": [ - \\"\\"debug\\"\\" - ] - } - }, - { - \\"type\\": 3, - \\"data\\": { - \\"source\\": 11, - \\"level\\": \\"dir\\", - \\"payload\\": [ - \\"\\"dir\\"\\" - ] - } - }, - { - \\"type\\": 3, - \\"data\\": { - \\"source\\": 11, - \\"level\\": \\"dirxml\\", - \\"payload\\": [ - \\"\\"dirxml\\"\\" - ] - } - }, - { - \\"type\\": 3, - \\"data\\": { - \\"source\\": 11, - \\"level\\": \\"group\\", - \\"payload\\": [] - } - }, - { - \\"type\\": 3, - \\"data\\": { - \\"source\\": 11, - \\"level\\": \\"groupCollapsed\\", - \\"payload\\": [] - } - }, - { - \\"type\\": 3, - \\"data\\": { - \\"source\\": 11, - \\"level\\": \\"info\\", - \\"payload\\": [ - \\"\\"info\\"\\" - ] - } - }, - { - \\"type\\": 3, - \\"data\\": { - \\"source\\": 11, - \\"level\\": \\"log\\", - \\"payload\\": [ - \\"\\"log\\"\\" - ] - } - }, - { - \\"type\\": 3, - \\"data\\": { - \\"source\\": 11, - \\"level\\": \\"table\\", - \\"payload\\": [ - \\"\\"table\\"\\" - ] - } - }, - { - \\"type\\": 3, - \\"data\\": { - \\"source\\": 11, - \\"level\\": \\"time\\", - \\"payload\\": [] - } - }, - { - \\"type\\": 3, - \\"data\\": { - \\"source\\": 11, - \\"level\\": \\"timeEnd\\", - \\"payload\\": [] - } - }, - { - \\"type\\": 3, - \\"data\\": { - \\"source\\": 11, - \\"level\\": \\"timeLog\\", - \\"payload\\": [] - } - }, - { - \\"type\\": 3, - \\"data\\": { - \\"source\\": 11, - \\"level\\": \\"trace\\", - \\"payload\\": [ - \\"\\"trace\\"\\" - ] - } - }, - { - \\"type\\": 3, - \\"data\\": { - \\"source\\": 11, - \\"level\\": \\"warn\\", - \\"payload\\": [ - \\"\\"warn\\"\\" - ] - } - }, - { - \\"type\\": 3, - \\"data\\": { - \\"source\\": 11, - \\"level\\": \\"clear\\", - \\"payload\\": [] - } - } -]" -`; - -exports[`log 1`] = ` -"[ - { - \\"type\\": 0, - \\"data\\": {} - }, - { - \\"type\\": 1, - \\"data\\": {} - }, - { - \\"type\\": 4, - \\"data\\": { - \\"href\\": \\"about:blank\\", - \\"width\\": 1920, - \\"height\\": 1080 - } - }, - { - \\"type\\": 2, - \\"data\\": { - \\"node\\": { - \\"type\\": 0, - \\"childNodes\\": [ - { - \\"type\\": 1, - \\"name\\": \\"html\\", - \\"publicId\\": \\"\\", - \\"systemId\\": \\"\\", - \\"id\\": 2 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"html\\", + }, + { + \\"parentId\\": 26, + \\"nextId\\": 32, + \\"node\\": { + \\"type\\": 3, + \\"textContent\\": \\" \\", + \\"id\\": 31 + } + }, + { + \\"parentId\\": 26, + \\"nextId\\": null, + \\"node\\": { + \\"type\\": 2, + \\"tagName\\": \\"span\\", \\"attributes\\": { - \\"lang\\": \\"en\\" + \\"class\\": \\"select2-arrow\\", + \\"role\\": \\"presentation\\" }, - \\"childNodes\\": [ - { - \\"type\\": 2, - \\"tagName\\": \\"head\\", - \\"attributes\\": {}, - \\"childNodes\\": [ - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 5 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"meta\\", - \\"attributes\\": { - \\"charset\\": \\"UTF-8\\" - }, - \\"childNodes\\": [], - \\"id\\": 6 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 7 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"meta\\", - \\"attributes\\": { - \\"name\\": \\"viewport\\", - \\"content\\": \\"width=device-width, initial-scale=1.0\\" - }, - \\"childNodes\\": [], - \\"id\\": 8 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 9 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"meta\\", - \\"attributes\\": { - \\"http-equiv\\": \\"X-UA-Compatible\\", - \\"content\\": \\"ie=edge\\" - }, - \\"childNodes\\": [], - \\"id\\": 10 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 11 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"title\\", - \\"attributes\\": {}, - \\"childNodes\\": [ - { - \\"type\\": 3, - \\"textContent\\": \\"Log record\\", - \\"id\\": 13 - } - ], - \\"id\\": 12 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 14 - } - ], - \\"id\\": 4 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 15 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"body\\", - \\"attributes\\": {}, - \\"childNodes\\": [ - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 17 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"script\\", - \\"attributes\\": {}, - \\"childNodes\\": [ - { - \\"type\\": 3, - \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", - \\"id\\": 19 - } - ], - \\"id\\": 18 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\\\n \\\\n\\\\n\\", - \\"id\\": 20 - } - ], - \\"id\\": 16 - } - ], - \\"id\\": 3 + \\"childNodes\\": [], + \\"id\\": 32 } - ], - \\"id\\": 1 - }, - \\"initialOffset\\": { - \\"left\\": 0, - \\"top\\": 0 - } - } - }, - { - \\"type\\": 3, - \\"data\\": { - \\"source\\": 11, - \\"level\\": \\"assert\\", - \\"trace\\": [ - \\"__puppeteer_evalu\\", - \\"ion_script__:2:37\\" - ], - \\"payload\\": [ - \\"true\\", - \\"\\\\\\"assert\\\\\\"\\" - ] - } - }, - { - \\"type\\": 3, - \\"data\\": { - \\"source\\": 11, - \\"level\\": \\"count\\", - \\"trace\\": [ - \\"__puppeteer_evalu\\", - \\"ion_script__:3:37\\" - ], - \\"payload\\": [ - \\"\\\\\\"count\\\\\\"\\" - ] - } - }, - { - \\"type\\": 3, - \\"data\\": { - \\"source\\": 11, - \\"level\\": \\"countReset\\", - \\"trace\\": [ - \\"__puppeteer_evalu\\", - \\"ion_script__:4:37\\" - ], - \\"payload\\": [ - \\"\\\\\\"count\\\\\\"\\" - ] - } - }, - { - \\"type\\": 3, - \\"data\\": { - \\"source\\": 11, - \\"level\\": \\"debug\\", - \\"trace\\": [ - \\"__puppeteer_evalu\\", - \\"ion_script__:5:37\\" - ], - \\"payload\\": [ - \\"\\\\\\"debug\\\\\\"\\" - ] - } - }, - { - \\"type\\": 3, - \\"data\\": { - \\"source\\": 11, - \\"level\\": \\"dir\\", - \\"trace\\": [ - \\"__puppeteer_evalu\\", - \\"ion_script__:6:37\\" - ], - \\"payload\\": [ - \\"\\\\\\"dir\\\\\\"\\" - ] - } - }, - { - \\"type\\": 3, - \\"data\\": { - \\"source\\": 11, - \\"level\\": \\"dirxml\\", - \\"trace\\": [ - \\"__puppeteer_evalu\\", - \\"ion_script__:7:37\\" - ], - \\"payload\\": [ - \\"\\\\\\"dirxml\\\\\\"\\" - ] - } - }, - { - \\"type\\": 3, - \\"data\\": { - \\"source\\": 11, - \\"level\\": \\"group\\", - \\"trace\\": [ - \\"__puppeteer_evalu\\", - \\"ion_script__:8:37\\" - ], - \\"payload\\": [] - } - }, - { - \\"type\\": 3, - \\"data\\": { - \\"source\\": 11, - \\"level\\": \\"groupCollapsed\\", - \\"trace\\": [ - \\"__puppeteer_evalu\\", - \\"ion_script__:9:37\\" - ], - \\"payload\\": [] - } - }, - { - \\"type\\": 3, - \\"data\\": { - \\"source\\": 11, - \\"level\\": \\"info\\", - \\"trace\\": [ - \\"__puppeteer_evalu\\", - \\"ion_script__:10:37\\" - ], - \\"payload\\": [ - \\"\\\\\\"info\\\\\\"\\" - ] - } - }, - { - \\"type\\": 3, - \\"data\\": { - \\"source\\": 11, - \\"level\\": \\"log\\", - \\"trace\\": [ - \\"__puppeteer_evalu\\", - \\"ion_script__:11:37\\" - ], - \\"payload\\": [ - \\"\\\\\\"log\\\\\\"\\" - ] - } - }, - { - \\"type\\": 3, - \\"data\\": { - \\"source\\": 11, - \\"level\\": \\"table\\", - \\"trace\\": [ - \\"__puppeteer_evalu\\", - \\"ion_script__:12:37\\" - ], - \\"payload\\": [ - \\"\\\\\\"table\\\\\\"\\" - ] - } - }, - { - \\"type\\": 3, - \\"data\\": { - \\"source\\": 11, - \\"level\\": \\"time\\", - \\"trace\\": [ - \\"__puppeteer_evalu\\", - \\"ion_script__:13:37\\" - ], - \\"payload\\": [] - } - }, - { - \\"type\\": 3, - \\"data\\": { - \\"source\\": 11, - \\"level\\": \\"timeEnd\\", - \\"trace\\": [ - \\"__puppeteer_evalu\\", - \\"ion_script__:14:37\\" - ], - \\"payload\\": [] - } - }, - { - \\"type\\": 3, - \\"data\\": { - \\"source\\": 11, - \\"level\\": \\"timeLog\\", - \\"trace\\": [ - \\"__puppeteer_evalu\\", - \\"ion_script__:15:37\\" - ], - \\"payload\\": [] - } - }, - { - \\"type\\": 3, - \\"data\\": { - \\"source\\": 11, - \\"level\\": \\"trace\\", - \\"trace\\": [ - \\"__puppeteer_evalu\\", - \\"ion_script__:16:37\\" - ], - \\"payload\\": [ - \\"\\\\\\"trace\\\\\\"\\" - ] - } - }, - { - \\"type\\": 3, - \\"data\\": { - \\"source\\": 11, - \\"level\\": \\"warn\\", - \\"trace\\": [ - \\"__puppeteer_evalu\\", - \\"ion_script__:17:37\\" - ], - \\"payload\\": [ - \\"\\\\\\"warn\\\\\\"\\" - ] - } - }, - { - \\"type\\": 3, - \\"data\\": { - \\"source\\": 11, - \\"level\\": \\"clear\\", - \\"trace\\": [ - \\"__puppeteer_evalu\\", - \\"ion_script__:18:37\\" - ], - \\"payload\\": [] - } - } -]" -`; - -exports[`mask 1`] = ` -"[ - { - \\"type\\": 0, - \\"data\\": {} - }, - { - \\"type\\": 1, - \\"data\\": {} - }, - { - \\"type\\": 4, - \\"data\\": { - \\"href\\": \\"about:blank\\", - \\"width\\": 1920, - \\"height\\": 1080 - } - }, - { - \\"type\\": 2, - \\"data\\": { - \\"node\\": { - \\"type\\": 0, - \\"childNodes\\": [ - { - \\"type\\": 1, - \\"name\\": \\"html\\", - \\"publicId\\": \\"\\", - \\"systemId\\": \\"\\", - \\"id\\": 2 - }, - { + }, + { + \\"parentId\\": 32, + \\"nextId\\": null, + \\"node\\": { \\"type\\": 2, - \\"tagName\\": \\"html\\", + \\"tagName\\": \\"b\\", \\"attributes\\": { - \\"lang\\": \\"en\\" + \\"role\\": \\"presentation\\" }, - \\"childNodes\\": [ - { - \\"type\\": 2, - \\"tagName\\": \\"head\\", - \\"attributes\\": {}, - \\"childNodes\\": [ - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 5 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"meta\\", - \\"attributes\\": { - \\"charset\\": \\"UTF-8\\" - }, - \\"childNodes\\": [], - \\"id\\": 6 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 7 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"meta\\", - \\"attributes\\": { - \\"name\\": \\"viewport\\", - \\"content\\": \\"width=device-width, initial-scale=1.0\\" - }, - \\"childNodes\\": [], - \\"id\\": 8 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 9 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"meta\\", - \\"attributes\\": { - \\"http-equiv\\": \\"X-UA-Compatible\\", - \\"content\\": \\"ie=edge\\" - }, - \\"childNodes\\": [], - \\"id\\": 10 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 11 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"title\\", - \\"attributes\\": {}, - \\"childNodes\\": [ - { - \\"type\\": 3, - \\"textContent\\": \\"form fields\\", - \\"id\\": 13 - } - ], - \\"id\\": 12 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n\\", - \\"id\\": 14 - } - ], - \\"id\\": 4 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n\\\\n\\", - \\"id\\": 15 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"body\\", - \\"attributes\\": {}, - \\"childNodes\\": [ - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 17 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"form\\", - \\"attributes\\": {}, - \\"childNodes\\": [ - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 19 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"label\\", - \\"attributes\\": { - \\"for\\": \\"text\\" - }, - \\"childNodes\\": [ - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 21 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"input\\", - \\"attributes\\": { - \\"type\\": \\"text\\" - }, - \\"childNodes\\": [], - \\"id\\": 22 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 23 - } - ], - \\"id\\": 20 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 24 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"label\\", - \\"attributes\\": { - \\"for\\": \\"radio\\" - }, - \\"childNodes\\": [ - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 26 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"input\\", - \\"attributes\\": { - \\"type\\": \\"radio\\" - }, - \\"childNodes\\": [], - \\"id\\": 27 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 28 - } - ], - \\"id\\": 25 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 29 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"label\\", - \\"attributes\\": { - \\"for\\": \\"checkbox\\" - }, - \\"childNodes\\": [ - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 31 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"input\\", - \\"attributes\\": { - \\"type\\": \\"checkbox\\" - }, - \\"childNodes\\": [], - \\"id\\": 32 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 33 - } - ], - \\"id\\": 30 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 34 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"label\\", - \\"attributes\\": { - \\"for\\": \\"textarea\\" - }, - \\"childNodes\\": [ - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 36 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"textarea\\", - \\"attributes\\": { - \\"name\\": \\"\\", - \\"id\\": \\"\\", - \\"cols\\": \\"30\\", - \\"rows\\": \\"10\\" - }, - \\"childNodes\\": [], - \\"id\\": 37 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 38 - } - ], - \\"id\\": 35 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 39 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"label\\", - \\"attributes\\": { - \\"for\\": \\"select\\" - }, - \\"childNodes\\": [ - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 41 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"select\\", - \\"attributes\\": { - \\"name\\": \\"\\", - \\"id\\": \\"\\", - \\"value\\": \\"*\\" - }, - \\"childNodes\\": [ - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 43 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"option\\", - \\"attributes\\": { - \\"value\\": \\"1\\", - \\"selected\\": true - }, - \\"childNodes\\": [ - { - \\"type\\": 3, - \\"textContent\\": \\"1\\", - \\"id\\": 45 - } - ], - \\"id\\": 44 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 46 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"option\\", - \\"attributes\\": { - \\"value\\": \\"2\\" - }, - \\"childNodes\\": [ - { - \\"type\\": 3, - \\"textContent\\": \\"2\\", - \\"id\\": 48 - } - ], - \\"id\\": 47 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 49 - } - ], - \\"id\\": 42 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 50 - } - ], - \\"id\\": 40 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 51 - } - ], - \\"id\\": 18 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n\\\\n \\", - \\"id\\": 52 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"script\\", - \\"attributes\\": {}, - \\"childNodes\\": [ - { - \\"type\\": 3, - \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", - \\"id\\": 54 - } - ], - \\"id\\": 53 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\\\n \\\\n\\\\n\\\\n\\", - \\"id\\": 55 - } - ], - \\"id\\": 16 - } - ], - \\"id\\": 3 + \\"childNodes\\": [], + \\"id\\": 33 } - ], - \\"id\\": 1 - }, - \\"initialOffset\\": { - \\"left\\": 0, - \\"top\\": 0 - } - } - }, - { - \\"type\\": 3, - \\"data\\": { - \\"source\\": 2, - \\"type\\": 5, - \\"id\\": 22 - } - }, - { - \\"type\\": 3, - \\"data\\": { - \\"source\\": 5, - \\"text\\": \\"*\\", - \\"isChecked\\": false, - \\"id\\": 22 - } - }, - { - \\"type\\": 3, - \\"data\\": { - \\"source\\": 5, - \\"text\\": \\"**\\", - \\"isChecked\\": false, - \\"id\\": 22 - } - }, - { - \\"type\\": 3, - \\"data\\": { - \\"source\\": 5, - \\"text\\": \\"***\\", - \\"isChecked\\": false, - \\"id\\": 22 - } - }, - { - \\"type\\": 3, - \\"data\\": { - \\"source\\": 5, - \\"text\\": \\"****\\", - \\"isChecked\\": false, - \\"id\\": 22 - } - }, - { - \\"type\\": 3, - \\"data\\": { - \\"source\\": 2, - \\"type\\": 1, - \\"id\\": 27 - } - }, - { - \\"type\\": 3, - \\"data\\": { - \\"source\\": 2, - \\"type\\": 6, - \\"id\\": 22 - } - }, - { - \\"type\\": 3, - \\"data\\": { - \\"source\\": 2, - \\"type\\": 5, - \\"id\\": 27 - } - }, - { - \\"type\\": 3, - \\"data\\": { - \\"source\\": 2, - \\"type\\": 0, - \\"id\\": 27 - } - }, - { - \\"type\\": 3, - \\"data\\": { - \\"source\\": 2, - \\"type\\": 2, - \\"id\\": 27 - } - }, - { - \\"type\\": 3, - \\"data\\": { - \\"source\\": 5, - \\"text\\": \\"on\\", - \\"isChecked\\": true, - \\"id\\": 27 - } - }, - { - \\"type\\": 3, - \\"data\\": { - \\"source\\": 2, - \\"type\\": 1, - \\"id\\": 32 - } - }, - { - \\"type\\": 3, - \\"data\\": { - \\"source\\": 2, - \\"type\\": 6, - \\"id\\": 27 - } - }, - { - \\"type\\": 3, - \\"data\\": { - \\"source\\": 2, - \\"type\\": 5, - \\"id\\": 32 - } - }, - { - \\"type\\": 3, - \\"data\\": { - \\"source\\": 2, - \\"type\\": 0, - \\"id\\": 32 - } - }, - { - \\"type\\": 3, - \\"data\\": { - \\"source\\": 2, - \\"type\\": 2, - \\"id\\": 32 - } - }, - { - \\"type\\": 3, - \\"data\\": { - \\"source\\": 5, - \\"text\\": \\"on\\", - \\"isChecked\\": true, - \\"id\\": 32 - } - }, - { - \\"type\\": 3, - \\"data\\": { - \\"source\\": 2, - \\"type\\": 6, - \\"id\\": 32 - } - }, - { - \\"type\\": 3, - \\"data\\": { - \\"source\\": 2, - \\"type\\": 5, - \\"id\\": 37 - } - }, - { - \\"type\\": 3, - \\"data\\": { - \\"source\\": 5, - \\"text\\": \\"*\\", - \\"isChecked\\": false, - \\"id\\": 37 - } - }, - { - \\"type\\": 3, - \\"data\\": { - \\"source\\": 5, - \\"text\\": \\"**\\", - \\"isChecked\\": false, - \\"id\\": 37 - } - }, - { - \\"type\\": 3, - \\"data\\": { - \\"source\\": 5, - \\"text\\": \\"***\\", - \\"isChecked\\": false, - \\"id\\": 37 - } - }, - { - \\"type\\": 3, - \\"data\\": { - \\"source\\": 5, - \\"text\\": \\"****\\", - \\"isChecked\\": false, - \\"id\\": 37 - } - }, - { - \\"type\\": 3, - \\"data\\": { - \\"source\\": 5, - \\"text\\": \\"*****\\", - \\"isChecked\\": false, - \\"id\\": 37 - } - }, - { - \\"type\\": 3, - \\"data\\": { - \\"source\\": 5, - \\"text\\": \\"******\\", - \\"isChecked\\": false, - \\"id\\": 37 - } - }, - { - \\"type\\": 3, - \\"data\\": { - \\"source\\": 5, - \\"text\\": \\"*******\\", - \\"isChecked\\": false, - \\"id\\": 37 - } - }, - { - \\"type\\": 3, - \\"data\\": { - \\"source\\": 5, - \\"text\\": \\"********\\", - \\"isChecked\\": false, - \\"id\\": 37 - } - }, - { - \\"type\\": 3, - \\"data\\": { - \\"source\\": 5, - \\"text\\": \\"*********\\", - \\"isChecked\\": false, - \\"id\\": 37 - } - }, - { - \\"type\\": 3, - \\"data\\": { - \\"source\\": 5, - \\"text\\": \\"**********\\", - \\"isChecked\\": false, - \\"id\\": 37 - } - }, - { - \\"type\\": 3, - \\"data\\": { - \\"source\\": 5, - \\"text\\": \\"***********\\", - \\"isChecked\\": false, - \\"id\\": 37 - } - }, - { - \\"type\\": 3, - \\"data\\": { - \\"source\\": 5, - \\"text\\": \\"************\\", - \\"isChecked\\": false, - \\"id\\": 37 - } - }, - { - \\"type\\": 3, - \\"data\\": { - \\"source\\": 5, - \\"text\\": \\"*************\\", - \\"isChecked\\": false, - \\"id\\": 37 - } - }, - { - \\"type\\": 3, - \\"data\\": { - \\"source\\": 5, - \\"text\\": \\"*\\", - \\"isChecked\\": false, - \\"id\\": 42 - } - } -]" -`; - -exports[`mask-character-data 1`] = ` -"[ - { - \\"type\\": 0, - \\"data\\": {} - }, - { - \\"type\\": 1, - \\"data\\": {} - }, - { - \\"type\\": 4, - \\"data\\": { - \\"href\\": \\"about:blank\\", - \\"width\\": 1920, - \\"height\\": 1080 - } - }, - { - \\"type\\": 2, - \\"data\\": { - \\"node\\": { - \\"type\\": 0, - \\"childNodes\\": [ - { + }, + { + \\"parentId\\": 18, + \\"nextId\\": null, + \\"node\\": { \\"type\\": 2, - \\"tagName\\": \\"html\\", - \\"attributes\\": {}, - \\"childNodes\\": [ - { - \\"type\\": 2, - \\"tagName\\": \\"head\\", - \\"attributes\\": {}, - \\"childNodes\\": [], - \\"id\\": 3 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"body\\", - \\"attributes\\": {}, - \\"childNodes\\": [ - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 5 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"p\\", - \\"attributes\\": {}, - \\"childNodes\\": [ - { - \\"type\\": 3, - \\"textContent\\": \\"mutation observer\\", - \\"id\\": 7 - } - ], - \\"id\\": 6 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 8 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"ul\\", - \\"attributes\\": {}, - \\"childNodes\\": [ - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 10 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"li\\", - \\"attributes\\": {}, - \\"childNodes\\": [], - \\"id\\": 11 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 12 - } - ], - \\"id\\": 9 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n\\\\n \\", - \\"id\\": 13 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"script\\", - \\"attributes\\": {}, - \\"childNodes\\": [ - { - \\"type\\": 3, - \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", - \\"id\\": 15 - } - ], - \\"id\\": 14 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\\\n \\", - \\"id\\": 16 - } - ], - \\"id\\": 4 - } - ], - \\"id\\": 2 + \\"tagName\\": \\"div\\", + \\"attributes\\": { + \\"class\\": \\"select2-drop select2-display-none select2-with-searchbox select2-drop-active\\", + \\"id\\": \\"select2-drop\\", + \\"style\\": \\"left: Npx; width: Npx; top: Npx; bottom: auto; display: block;\\" + }, + \\"childNodes\\": [], + \\"id\\": 36 } - ], - \\"id\\": 1 - }, - \\"initialOffset\\": { - \\"left\\": 0, - \\"top\\": 0 - } - } - }, - { - \\"type\\": 3, - \\"data\\": { - \\"source\\": 0, - \\"texts\\": [], - \\"attributes\\": [ + }, { - \\"id\\": 6, - \\"attributes\\": { - \\"class\\": \\"rr-mask\\" + \\"parentId\\": 36, + \\"nextId\\": 38, + \\"node\\": { + \\"type\\": 3, + \\"textContent\\": \\" \\", + \\"id\\": 37 } - } - ], - \\"removes\\": [ + }, { - \\"parentId\\": 6, - \\"id\\": 7 - } - ], - \\"adds\\": [ + \\"parentId\\": 36, + \\"nextId\\": 44, + \\"node\\": { + \\"type\\": 2, + \\"tagName\\": \\"div\\", + \\"attributes\\": { + \\"class\\": \\"select2-search\\" + }, + \\"childNodes\\": [], + \\"id\\": 38 + } + }, + { + \\"parentId\\": 38, + \\"nextId\\": 40, + \\"node\\": { + \\"type\\": 3, + \\"textContent\\": \\" \\", + \\"id\\": 39 + } + }, + { + \\"parentId\\": 38, + \\"nextId\\": 41, + \\"node\\": { + \\"type\\": 2, + \\"tagName\\": \\"label\\", + \\"attributes\\": { + \\"for\\": \\"s2id_autogen1_search\\", + \\"class\\": \\"select2-offscreen\\" + }, + \\"childNodes\\": [], + \\"id\\": 40 + } + }, + { + \\"parentId\\": 38, + \\"nextId\\": 42, + \\"node\\": { + \\"type\\": 3, + \\"textContent\\": \\" \\", + \\"id\\": 41 + } + }, + { + \\"parentId\\": 38, + \\"nextId\\": 43, + \\"node\\": { + \\"type\\": 2, + \\"tagName\\": \\"input\\", + \\"attributes\\": { + \\"type\\": \\"text\\", + \\"autocomplete\\": \\"off\\", + \\"autocorrect\\": \\"off\\", + \\"autocapitalize\\": \\"off\\", + \\"spellcheck\\": \\"false\\", + \\"class\\": \\"select2-input select2-focused\\", + \\"role\\": \\"combobox\\", + \\"aria-expanded\\": \\"true\\", + \\"aria-autocomplete\\": \\"list\\", + \\"aria-owns\\": \\"select2-results-1\\", + \\"id\\": \\"s2id_autogen1_search\\", + \\"placeholder\\": \\"\\", + \\"aria-activedescendant\\": \\"select2-result-label-2\\" + }, + \\"childNodes\\": [], + \\"id\\": 42 + } + }, + { + \\"parentId\\": 38, + \\"nextId\\": null, + \\"node\\": { + \\"type\\": 3, + \\"textContent\\": \\" \\", + \\"id\\": 43 + } + }, + { + \\"parentId\\": 36, + \\"nextId\\": 45, + \\"node\\": { + \\"type\\": 3, + \\"textContent\\": \\" \\", + \\"id\\": 44 + } + }, { - \\"parentId\\": 9, + \\"parentId\\": 36, + \\"nextId\\": null, + \\"node\\": { + \\"type\\": 2, + \\"tagName\\": \\"ul\\", + \\"attributes\\": { + \\"class\\": \\"select2-results\\", + \\"role\\": \\"listbox\\", + \\"id\\": \\"select2-results-1\\" + }, + \\"childNodes\\": [], + \\"id\\": 45 + } + }, + { + \\"parentId\\": 45, \\"nextId\\": null, \\"node\\": { \\"type\\": 2, \\"tagName\\": \\"li\\", \\"attributes\\": { - \\"class\\": \\"rr-mask\\" + \\"class\\": \\"select2-results-dept-0 select2-result select2-result-selectable\\", + \\"role\\": \\"presentation\\" }, \\"childNodes\\": [], - \\"id\\": 17 + \\"id\\": 67 } }, { - \\"parentId\\": 17, + \\"parentId\\": 67, \\"nextId\\": null, \\"node\\": { - \\"type\\": 3, - \\"textContent\\": \\"*** **** ****\\", - \\"id\\": 18 + \\"type\\": 2, + \\"tagName\\": \\"div\\", + \\"attributes\\": { + \\"class\\": \\"select2-result-label\\", + \\"id\\": \\"select2-result-label-3\\", + \\"role\\": \\"option\\" + }, + \\"childNodes\\": [], + \\"id\\": 68 } }, { - \\"parentId\\": 6, + \\"parentId\\": 68, \\"nextId\\": null, \\"node\\": { \\"type\\": 3, - \\"textContent\\": \\"*******\\", - \\"id\\": 19 + \\"textContent\\": \\"B\\", + \\"id\\": 69 } - } - ] - } - } -]" -`; - -exports[`mask-text 1`] = ` -"[ - { - \\"type\\": 0, - \\"data\\": {} - }, - { - \\"type\\": 1, - \\"data\\": {} - }, - { - \\"type\\": 4, - \\"data\\": { - \\"href\\": \\"about:blank\\", - \\"width\\": 1920, - \\"height\\": 1080 - } - }, - { - \\"type\\": 2, - \\"data\\": { - \\"node\\": { - \\"type\\": 0, - \\"childNodes\\": [ - { - \\"type\\": 1, - \\"name\\": \\"html\\", - \\"publicId\\": \\"\\", - \\"systemId\\": \\"\\", - \\"id\\": 2 - }, - { + }, + { + \\"parentId\\": 18, + \\"nextId\\": 36, + \\"node\\": { \\"type\\": 2, - \\"tagName\\": \\"html\\", + \\"tagName\\": \\"div\\", \\"attributes\\": { - \\"lang\\": \\"en\\" + \\"id\\": \\"select2-drop-mask\\", + \\"class\\": \\"select2-drop-mask\\", + \\"style\\": \\"\\" }, - \\"childNodes\\": [ - { - \\"type\\": 2, - \\"tagName\\": \\"head\\", - \\"attributes\\": {}, - \\"childNodes\\": [ - { + \\"childNodes\\": [], + \\"id\\": 70 + } + }, + { + \\"parentId\\": 62, + \\"nextId\\": null, + \\"node\\": { + \\"type\\": 3, + \\"textContent\\": \\"2 results are available, use up and down arrow keys to navigate.\\", + \\"id\\": 71 + } + }, + { + \\"parentId\\": 45, + \\"nextId\\": 67, + \\"node\\": { + \\"type\\": 2, + \\"tagName\\": \\"li\\", + \\"attributes\\": { + \\"class\\": \\"select2-results-dept-0 select2-result select2-result-selectable select2-highlighted\\", + \\"role\\": \\"presentation\\" + }, + \\"childNodes\\": [], + \\"id\\": 72 + } + }, + { + \\"parentId\\": 72, + \\"nextId\\": null, + \\"node\\": { + \\"type\\": 2, + \\"tagName\\": \\"div\\", + \\"attributes\\": { + \\"class\\": \\"select2-result-label\\", + \\"id\\": \\"select2-result-label-2\\", + \\"role\\": \\"option\\" + }, + \\"childNodes\\": [], + \\"id\\": 73 + } + }, + { + \\"parentId\\": 73, + \\"nextId\\": null, + \\"node\\": { + \\"type\\": 3, + \\"textContent\\": \\"A\\", + \\"id\\": 74 + } + }, + { + \\"parentId\\": 73, + \\"nextId\\": 74, + \\"node\\": { + \\"type\\": 2, + \\"tagName\\": \\"span\\", + \\"attributes\\": { + \\"class\\": \\"select2-match\\" + }, + \\"childNodes\\": [], + \\"id\\": 75 + } + }, + { + \\"parentId\\": 68, + \\"nextId\\": 69, + \\"node\\": { + \\"type\\": 2, + \\"tagName\\": \\"span\\", + \\"attributes\\": { + \\"class\\": \\"select2-match\\" + }, + \\"childNodes\\": [], + \\"id\\": 76 + } + } + ] + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 5, + \\"text\\": \\"\\", + \\"isChecked\\": false, + \\"id\\": 42 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 5, + \\"text\\": \\"\\", + \\"isChecked\\": false, + \\"id\\": 35 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 2, + \\"type\\": 0, + \\"id\\": 70 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 0, + \\"texts\\": [], + \\"attributes\\": [ + { + \\"id\\": 36, + \\"attributes\\": { + \\"style\\": { + \\"color\\": [ + \\"black\\", + \\"important\\" + ] + } + } + } + ], + \\"removes\\": [], + \\"adds\\": [] + } + } +]" +`; + +exports[`record integration tests can use maskInputOptions to configure which type of inputs should be masked 1`] = ` +"[ + { + \\"type\\": 0, + \\"data\\": {} + }, + { + \\"type\\": 1, + \\"data\\": {} + }, + { + \\"type\\": 4, + \\"data\\": { + \\"href\\": \\"about:blank\\", + \\"width\\": 1920, + \\"height\\": 1080 + } + }, + { + \\"type\\": 2, + \\"data\\": { + \\"node\\": { + \\"type\\": 0, + \\"childNodes\\": [ + { + \\"type\\": 1, + \\"name\\": \\"html\\", + \\"publicId\\": \\"\\", + \\"systemId\\": \\"\\", + \\"id\\": 2 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"html\\", + \\"attributes\\": { + \\"lang\\": \\"en\\" + }, + \\"childNodes\\": [ + { + \\"type\\": 2, + \\"tagName\\": \\"head\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"id\\": 5 @@ -4394,7 +2791,7 @@ exports[`mask-text 1`] = ` \\"childNodes\\": [ { \\"type\\": 3, - \\"textContent\\": \\"Mask text\\", + \\"textContent\\": \\"form fields\\", \\"id\\": 13 } ], @@ -4410,7 +2807,7 @@ exports[`mask-text 1`] = ` }, { \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", + \\"textContent\\": \\"\\\\n\\\\n \\", \\"id\\": 15 }, { @@ -4425,117 +2822,312 @@ exports[`mask-text 1`] = ` }, { \\"type\\": 2, - \\"tagName\\": \\"p\\", - \\"attributes\\": { - \\"class\\": \\"rr-mask\\" - }, - \\"childNodes\\": [ - { - \\"type\\": 3, - \\"textContent\\": \\"*****\\", - \\"id\\": 19 - } - ], - \\"id\\": 18 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 20 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"div\\", - \\"attributes\\": { - \\"class\\": \\"rr-mask\\" - }, + \\"tagName\\": \\"form\\", + \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", - \\"id\\": 22 + \\"id\\": 19 }, { \\"type\\": 2, - \\"tagName\\": \\"span\\", - \\"attributes\\": {}, + \\"tagName\\": \\"label\\", + \\"attributes\\": { + \\"for\\": \\"text\\" + }, \\"childNodes\\": [ { \\"type\\": 3, - \\"textContent\\": \\"*****\\", - \\"id\\": 24 + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 21 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"input\\", + \\"attributes\\": { + \\"type\\": \\"text\\" + }, + \\"childNodes\\": [], + \\"id\\": 22 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 23 } ], - \\"id\\": 23 + \\"id\\": 20 }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 25 - } - ], - \\"id\\": 21 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 26 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"div\\", - \\"attributes\\": { - \\"data-masking\\": \\"true\\" - }, - \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", - \\"id\\": 28 + \\"id\\": 24 }, { \\"type\\": 2, - \\"tagName\\": \\"div\\", + \\"tagName\\": \\"label\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", - \\"id\\": 30 + \\"id\\": 26 }, { \\"type\\": 2, - \\"tagName\\": \\"div\\", - \\"attributes\\": {}, - \\"childNodes\\": [ - { + \\"tagName\\": \\"input\\", + \\"attributes\\": { + \\"type\\": \\"radio\\", + \\"name\\": \\"toggle\\", + \\"value\\": \\"on\\" + }, + \\"childNodes\\": [], + \\"id\\": 27 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 28 + } + ], + \\"id\\": 25 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 29 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"label\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 31 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"input\\", + \\"attributes\\": { + \\"type\\": \\"radio\\", + \\"name\\": \\"toggle\\", + \\"value\\": \\"off\\", + \\"checked\\": true + }, + \\"childNodes\\": [], + \\"id\\": 32 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 33 + } + ], + \\"id\\": 30 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 34 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"label\\", + \\"attributes\\": { + \\"for\\": \\"checkbox\\" + }, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 36 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"input\\", + \\"attributes\\": { + \\"type\\": \\"checkbox\\" + }, + \\"childNodes\\": [], + \\"id\\": 37 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 38 + } + ], + \\"id\\": 35 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 39 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"label\\", + \\"attributes\\": { + \\"for\\": \\"textarea\\" + }, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 41 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"textarea\\", + \\"attributes\\": { + \\"name\\": \\"\\", + \\"id\\": \\"\\", + \\"cols\\": \\"30\\", + \\"rows\\": \\"10\\" + }, + \\"childNodes\\": [], + \\"id\\": 42 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 43 + } + ], + \\"id\\": 40 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 44 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"label\\", + \\"attributes\\": { + \\"for\\": \\"select\\" + }, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 46 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"select\\", + \\"attributes\\": { + \\"name\\": \\"\\", + \\"id\\": \\"\\", + \\"value\\": \\"1\\" + }, + \\"childNodes\\": [ + { \\"type\\": 3, - \\"textContent\\": \\"*****\\", - \\"id\\": 32 + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 48 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"option\\", + \\"attributes\\": { + \\"value\\": \\"1\\", + \\"selected\\": true + }, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"1\\", + \\"id\\": 50 + } + ], + \\"id\\": 49 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 51 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"option\\", + \\"attributes\\": { + \\"value\\": \\"2\\" + }, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"2\\", + \\"id\\": 53 + } + ], + \\"id\\": 52 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 54 } ], - \\"id\\": 31 + \\"id\\": 47 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", - \\"id\\": 33 + \\"id\\": 55 } ], - \\"id\\": 29 + \\"id\\": 45 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 56 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"label\\", + \\"attributes\\": { + \\"for\\": \\"password\\" + }, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 58 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"input\\", + \\"attributes\\": { + \\"type\\": \\"password\\" + }, + \\"childNodes\\": [], + \\"id\\": 59 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 60 + } + ], + \\"id\\": 57 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", - \\"id\\": 34 + \\"id\\": 61 } ], - \\"id\\": 27 + \\"id\\": 18 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\\\n \\", - \\"id\\": 35 + \\"id\\": 62 }, { \\"type\\": 2, @@ -4545,15 +3137,15 @@ exports[`mask-text 1`] = ` { \\"type\\": 3, \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", - \\"id\\": 37 + \\"id\\": 64 } ], - \\"id\\": 36 + \\"id\\": 63 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\\\n \\\\n\\\\n\\", - \\"id\\": 38 + \\"id\\": 65 } ], \\"id\\": 16 @@ -4569,738 +3161,72 @@ exports[`mask-text 1`] = ` \\"top\\": 0 } } - } -]" -`; - -exports[`mask-text-fn 1`] = ` -"[ + }, { - \\"type\\": 0, - \\"data\\": {} + \\"type\\": 3, + \\"data\\": { + \\"source\\": 2, + \\"type\\": 5, + \\"id\\": 22 + } }, { - \\"type\\": 1, - \\"data\\": {} + \\"type\\": 3, + \\"data\\": { + \\"source\\": 5, + \\"text\\": \\"t\\", + \\"isChecked\\": false, + \\"id\\": 22 + } }, { - \\"type\\": 4, + \\"type\\": 3, \\"data\\": { - \\"href\\": \\"about:blank\\", - \\"width\\": 1920, - \\"height\\": 1080 + \\"source\\": 5, + \\"text\\": \\"te\\", + \\"isChecked\\": false, + \\"id\\": 22 } }, { - \\"type\\": 2, + \\"type\\": 3, \\"data\\": { - \\"node\\": { - \\"type\\": 0, - \\"childNodes\\": [ - { - \\"type\\": 1, - \\"name\\": \\"html\\", - \\"publicId\\": \\"\\", - \\"systemId\\": \\"\\", - \\"id\\": 2 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"html\\", - \\"attributes\\": { - \\"lang\\": \\"en\\" - }, - \\"childNodes\\": [ - { - \\"type\\": 2, - \\"tagName\\": \\"head\\", - \\"attributes\\": {}, - \\"childNodes\\": [ - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 5 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"meta\\", - \\"attributes\\": { - \\"charset\\": \\"UTF-8\\" - }, - \\"childNodes\\": [], - \\"id\\": 6 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 7 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"meta\\", - \\"attributes\\": { - \\"name\\": \\"viewport\\", - \\"content\\": \\"width=device-width, initial-scale=1.0\\" - }, - \\"childNodes\\": [], - \\"id\\": 8 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 9 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"meta\\", - \\"attributes\\": { - \\"http-equiv\\": \\"X-UA-Compatible\\", - \\"content\\": \\"ie=edge\\" - }, - \\"childNodes\\": [], - \\"id\\": 10 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 11 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"title\\", - \\"attributes\\": {}, - \\"childNodes\\": [ - { - \\"type\\": 3, - \\"textContent\\": \\"Mask text\\", - \\"id\\": 13 - } - ], - \\"id\\": 12 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 14 - } - ], - \\"id\\": 4 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 15 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"body\\", - \\"attributes\\": {}, - \\"childNodes\\": [ - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 17 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"p\\", - \\"attributes\\": { - \\"class\\": \\"rr-mask\\" - }, - \\"childNodes\\": [ - { - \\"type\\": 3, - \\"textContent\\": \\"****1\\", - \\"id\\": 19 - } - ], - \\"id\\": 18 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 20 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"div\\", - \\"attributes\\": { - \\"class\\": \\"rr-mask\\" - }, - \\"childNodes\\": [ - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 22 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"span\\", - \\"attributes\\": {}, - \\"childNodes\\": [ - { - \\"type\\": 3, - \\"textContent\\": \\"****2\\", - \\"id\\": 24 - } - ], - \\"id\\": 23 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 25 - } - ], - \\"id\\": 21 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 26 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"div\\", - \\"attributes\\": { - \\"data-masking\\": \\"true\\" - }, - \\"childNodes\\": [ - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 28 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"div\\", - \\"attributes\\": {}, - \\"childNodes\\": [ - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 30 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"div\\", - \\"attributes\\": {}, - \\"childNodes\\": [ - { - \\"type\\": 3, - \\"textContent\\": \\"****3\\", - \\"id\\": 32 - } - ], - \\"id\\": 31 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 33 - } - ], - \\"id\\": 29 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 34 - } - ], - \\"id\\": 27 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\\\n \\", - \\"id\\": 35 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"script\\", - \\"attributes\\": {}, - \\"childNodes\\": [ - { - \\"type\\": 3, - \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", - \\"id\\": 37 - } - ], - \\"id\\": 36 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\\\n \\\\n\\\\n\\", - \\"id\\": 38 - } - ], - \\"id\\": 16 - } - ], - \\"id\\": 3 - } - ], - \\"id\\": 1 - }, - \\"initialOffset\\": { - \\"left\\": 0, - \\"top\\": 0 - } + \\"source\\": 5, + \\"text\\": \\"tes\\", + \\"isChecked\\": false, + \\"id\\": 22 } - } -]" -`; - -exports[`maskInputOptions 1`] = ` -"[ + }, { - \\"type\\": 0, - \\"data\\": {} + \\"type\\": 3, + \\"data\\": { + \\"source\\": 5, + \\"text\\": \\"test\\", + \\"isChecked\\": false, + \\"id\\": 22 + } }, { - \\"type\\": 1, - \\"data\\": {} + \\"type\\": 3, + \\"data\\": { + \\"source\\": 2, + \\"type\\": 1, + \\"id\\": 27 + } }, { - \\"type\\": 4, + \\"type\\": 3, \\"data\\": { - \\"href\\": \\"about:blank\\", - \\"width\\": 1920, - \\"height\\": 1080 + \\"source\\": 2, + \\"type\\": 6, + \\"id\\": 22 } }, { - \\"type\\": 2, - \\"data\\": { - \\"node\\": { - \\"type\\": 0, - \\"childNodes\\": [ - { - \\"type\\": 1, - \\"name\\": \\"html\\", - \\"publicId\\": \\"\\", - \\"systemId\\": \\"\\", - \\"id\\": 2 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"html\\", - \\"attributes\\": { - \\"lang\\": \\"en\\" - }, - \\"childNodes\\": [ - { - \\"type\\": 2, - \\"tagName\\": \\"head\\", - \\"attributes\\": {}, - \\"childNodes\\": [ - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 5 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"meta\\", - \\"attributes\\": { - \\"charset\\": \\"UTF-8\\" - }, - \\"childNodes\\": [], - \\"id\\": 6 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 7 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"meta\\", - \\"attributes\\": { - \\"name\\": \\"viewport\\", - \\"content\\": \\"width=device-width, initial-scale=1.0\\" - }, - \\"childNodes\\": [], - \\"id\\": 8 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 9 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"meta\\", - \\"attributes\\": { - \\"http-equiv\\": \\"X-UA-Compatible\\", - \\"content\\": \\"ie=edge\\" - }, - \\"childNodes\\": [], - \\"id\\": 10 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 11 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"title\\", - \\"attributes\\": {}, - \\"childNodes\\": [ - { - \\"type\\": 3, - \\"textContent\\": \\"form fields\\", - \\"id\\": 13 - } - ], - \\"id\\": 12 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n\\", - \\"id\\": 14 - } - ], - \\"id\\": 4 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n\\\\n\\", - \\"id\\": 15 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"body\\", - \\"attributes\\": {}, - \\"childNodes\\": [ - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 17 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"form\\", - \\"attributes\\": {}, - \\"childNodes\\": [ - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 19 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"label\\", - \\"attributes\\": { - \\"for\\": \\"text\\" - }, - \\"childNodes\\": [ - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 21 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"input\\", - \\"attributes\\": { - \\"type\\": \\"text\\" - }, - \\"childNodes\\": [], - \\"id\\": 22 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 23 - } - ], - \\"id\\": 20 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 24 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"label\\", - \\"attributes\\": { - \\"for\\": \\"radio\\" - }, - \\"childNodes\\": [ - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 26 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"input\\", - \\"attributes\\": { - \\"type\\": \\"radio\\" - }, - \\"childNodes\\": [], - \\"id\\": 27 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 28 - } - ], - \\"id\\": 25 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 29 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"label\\", - \\"attributes\\": { - \\"for\\": \\"checkbox\\" - }, - \\"childNodes\\": [ - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 31 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"input\\", - \\"attributes\\": { - \\"type\\": \\"checkbox\\" - }, - \\"childNodes\\": [], - \\"id\\": 32 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 33 - } - ], - \\"id\\": 30 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 34 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"label\\", - \\"attributes\\": { - \\"for\\": \\"textarea\\" - }, - \\"childNodes\\": [ - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 36 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"textarea\\", - \\"attributes\\": { - \\"name\\": \\"\\", - \\"id\\": \\"\\", - \\"cols\\": \\"30\\", - \\"rows\\": \\"10\\" - }, - \\"childNodes\\": [], - \\"id\\": 37 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 38 - } - ], - \\"id\\": 35 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 39 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"label\\", - \\"attributes\\": { - \\"for\\": \\"select\\" - }, - \\"childNodes\\": [ - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 41 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"select\\", - \\"attributes\\": { - \\"name\\": \\"\\", - \\"id\\": \\"\\", - \\"value\\": \\"1\\" - }, - \\"childNodes\\": [ - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 43 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"option\\", - \\"attributes\\": { - \\"value\\": \\"1\\", - \\"selected\\": true - }, - \\"childNodes\\": [ - { - \\"type\\": 3, - \\"textContent\\": \\"1\\", - \\"id\\": 45 - } - ], - \\"id\\": 44 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 46 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"option\\", - \\"attributes\\": { - \\"value\\": \\"2\\" - }, - \\"childNodes\\": [ - { - \\"type\\": 3, - \\"textContent\\": \\"2\\", - \\"id\\": 48 - } - ], - \\"id\\": 47 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 49 - } - ], - \\"id\\": 42 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 50 - } - ], - \\"id\\": 40 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 51 - } - ], - \\"id\\": 18 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n\\\\n \\", - \\"id\\": 52 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"script\\", - \\"attributes\\": {}, - \\"childNodes\\": [ - { - \\"type\\": 3, - \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", - \\"id\\": 54 - } - ], - \\"id\\": 53 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\\\n \\\\n\\\\n\\\\n\\", - \\"id\\": 55 - } - ], - \\"id\\": 16 - } - ], - \\"id\\": 3 - } - ], - \\"id\\": 1 - }, - \\"initialOffset\\": { - \\"left\\": 0, - \\"top\\": 0 - } - } - }, - { - \\"type\\": 3, + \\"type\\": 3, \\"data\\": { \\"source\\": 2, \\"type\\": 5, - \\"id\\": 22 - } - }, - { - \\"type\\": 3, - \\"data\\": { - \\"source\\": 5, - \\"text\\": \\"t\\", - \\"isChecked\\": false, - \\"id\\": 22 - } - }, - { - \\"type\\": 3, - \\"data\\": { - \\"source\\": 5, - \\"text\\": \\"te\\", - \\"isChecked\\": false, - \\"id\\": 22 - } - }, - { - \\"type\\": 3, - \\"data\\": { - \\"source\\": 5, - \\"text\\": \\"tes\\", - \\"isChecked\\": false, - \\"id\\": 22 - } - }, - { - \\"type\\": 3, - \\"data\\": { - \\"source\\": 5, - \\"text\\": \\"test\\", - \\"isChecked\\": false, - \\"id\\": 22 - } - }, - { - \\"type\\": 3, - \\"data\\": { - \\"source\\": 2, - \\"type\\": 1, \\"id\\": 27 } }, @@ -5308,15 +3234,7 @@ exports[`maskInputOptions 1`] = ` \\"type\\": 3, \\"data\\": { \\"source\\": 2, - \\"type\\": 6, - \\"id\\": 22 - } - }, - { - \\"type\\": 3, - \\"data\\": { - \\"source\\": 2, - \\"type\\": 5, + \\"type\\": 0, \\"id\\": 27 } }, @@ -5324,15 +3242,16 @@ exports[`maskInputOptions 1`] = ` \\"type\\": 3, \\"data\\": { \\"source\\": 2, - \\"type\\": 0, + \\"type\\": 2, \\"id\\": 27 } }, { \\"type\\": 3, \\"data\\": { - \\"source\\": 2, - \\"type\\": 2, + \\"source\\": 5, + \\"text\\": \\"on\\", + \\"isChecked\\": true, \\"id\\": 27 } }, @@ -5340,9 +3259,9 @@ exports[`maskInputOptions 1`] = ` \\"type\\": 3, \\"data\\": { \\"source\\": 5, - \\"text\\": \\"on\\", - \\"isChecked\\": true, - \\"id\\": 27 + \\"text\\": \\"off\\", + \\"isChecked\\": false, + \\"id\\": 32 } }, { @@ -5350,7 +3269,7 @@ exports[`maskInputOptions 1`] = ` \\"data\\": { \\"source\\": 2, \\"type\\": 1, - \\"id\\": 32 + \\"id\\": 37 } }, { @@ -5366,7 +3285,7 @@ exports[`maskInputOptions 1`] = ` \\"data\\": { \\"source\\": 2, \\"type\\": 5, - \\"id\\": 32 + \\"id\\": 37 } }, { @@ -5374,7 +3293,7 @@ exports[`maskInputOptions 1`] = ` \\"data\\": { \\"source\\": 2, \\"type\\": 0, - \\"id\\": 32 + \\"id\\": 37 } }, { @@ -5382,7 +3301,7 @@ exports[`maskInputOptions 1`] = ` \\"data\\": { \\"source\\": 2, \\"type\\": 2, - \\"id\\": 32 + \\"id\\": 37 } }, { @@ -5391,7 +3310,7 @@ exports[`maskInputOptions 1`] = ` \\"source\\": 5, \\"text\\": \\"on\\", \\"isChecked\\": true, - \\"id\\": 32 + \\"id\\": 37 } }, { @@ -5399,7 +3318,7 @@ exports[`maskInputOptions 1`] = ` \\"data\\": { \\"source\\": 2, \\"type\\": 6, - \\"id\\": 32 + \\"id\\": 37 } }, { @@ -5407,7 +3326,7 @@ exports[`maskInputOptions 1`] = ` \\"data\\": { \\"source\\": 2, \\"type\\": 5, - \\"id\\": 37 + \\"id\\": 42 } }, { @@ -5416,7 +3335,7 @@ exports[`maskInputOptions 1`] = ` \\"source\\": 5, \\"text\\": \\"t\\", \\"isChecked\\": false, - \\"id\\": 37 + \\"id\\": 42 } }, { @@ -5425,7 +3344,7 @@ exports[`maskInputOptions 1`] = ` \\"source\\": 5, \\"text\\": \\"te\\", \\"isChecked\\": false, - \\"id\\": 37 + \\"id\\": 42 } }, { @@ -5434,7 +3353,7 @@ exports[`maskInputOptions 1`] = ` \\"source\\": 5, \\"text\\": \\"tex\\", \\"isChecked\\": false, - \\"id\\": 37 + \\"id\\": 42 } }, { @@ -5443,7 +3362,7 @@ exports[`maskInputOptions 1`] = ` \\"source\\": 5, \\"text\\": \\"text\\", \\"isChecked\\": false, - \\"id\\": 37 + \\"id\\": 42 } }, { @@ -5452,7 +3371,7 @@ exports[`maskInputOptions 1`] = ` \\"source\\": 5, \\"text\\": \\"texta\\", \\"isChecked\\": false, - \\"id\\": 37 + \\"id\\": 42 } }, { @@ -5461,7 +3380,7 @@ exports[`maskInputOptions 1`] = ` \\"source\\": 5, \\"text\\": \\"textar\\", \\"isChecked\\": false, - \\"id\\": 37 + \\"id\\": 42 } }, { @@ -5470,7 +3389,7 @@ exports[`maskInputOptions 1`] = ` \\"source\\": 5, \\"text\\": \\"textare\\", \\"isChecked\\": false, - \\"id\\": 37 + \\"id\\": 42 } }, { @@ -5479,7 +3398,7 @@ exports[`maskInputOptions 1`] = ` \\"source\\": 5, \\"text\\": \\"textarea\\", \\"isChecked\\": false, - \\"id\\": 37 + \\"id\\": 42 } }, { @@ -5488,7 +3407,7 @@ exports[`maskInputOptions 1`] = ` \\"source\\": 5, \\"text\\": \\"textarea \\", \\"isChecked\\": false, - \\"id\\": 37 + \\"id\\": 42 } }, { @@ -5497,7 +3416,7 @@ exports[`maskInputOptions 1`] = ` \\"source\\": 5, \\"text\\": \\"textarea t\\", \\"isChecked\\": false, - \\"id\\": 37 + \\"id\\": 42 } }, { @@ -5506,7 +3425,7 @@ exports[`maskInputOptions 1`] = ` \\"source\\": 5, \\"text\\": \\"textarea te\\", \\"isChecked\\": false, - \\"id\\": 37 + \\"id\\": 42 } }, { @@ -5515,7 +3434,7 @@ exports[`maskInputOptions 1`] = ` \\"source\\": 5, \\"text\\": \\"textarea tes\\", \\"isChecked\\": false, - \\"id\\": 37 + \\"id\\": 42 } }, { @@ -5524,22 +3443,110 @@ exports[`maskInputOptions 1`] = ` \\"source\\": 5, \\"text\\": \\"textarea test\\", \\"isChecked\\": false, - \\"id\\": 37 + \\"id\\": 42 } }, { \\"type\\": 3, \\"data\\": { - \\"source\\": 5, - \\"text\\": \\"1\\", - \\"isChecked\\": false, - \\"id\\": 42 + \\"source\\": 2, + \\"type\\": 6, + \\"id\\": 42 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 2, + \\"type\\": 5, + \\"id\\": 59 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 5, + \\"text\\": \\"*\\", + \\"isChecked\\": false, + \\"id\\": 59 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 5, + \\"text\\": \\"**\\", + \\"isChecked\\": false, + \\"id\\": 59 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 5, + \\"text\\": \\"***\\", + \\"isChecked\\": false, + \\"id\\": 59 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 5, + \\"text\\": \\"****\\", + \\"isChecked\\": false, + \\"id\\": 59 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 5, + \\"text\\": \\"*****\\", + \\"isChecked\\": false, + \\"id\\": 59 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 5, + \\"text\\": \\"******\\", + \\"isChecked\\": false, + \\"id\\": 59 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 5, + \\"text\\": \\"*******\\", + \\"isChecked\\": false, + \\"id\\": 59 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 5, + \\"text\\": \\"********\\", + \\"isChecked\\": false, + \\"id\\": 59 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 5, + \\"text\\": \\"1\\", + \\"isChecked\\": false, + \\"id\\": 47 } } ]" `; -exports[`move-node-1 1`] = ` +exports[`record integration tests should mask texts 1`] = ` "[ { \\"type\\": 0, @@ -5563,17 +3570,99 @@ exports[`move-node-1 1`] = ` \\"node\\": { \\"type\\": 0, \\"childNodes\\": [ + { + \\"type\\": 1, + \\"name\\": \\"html\\", + \\"publicId\\": \\"\\", + \\"systemId\\": \\"\\", + \\"id\\": 2 + }, { \\"type\\": 2, \\"tagName\\": \\"html\\", - \\"attributes\\": {}, + \\"attributes\\": { + \\"lang\\": \\"en\\" + }, \\"childNodes\\": [ { \\"type\\": 2, \\"tagName\\": \\"head\\", \\"attributes\\": {}, - \\"childNodes\\": [], - \\"id\\": 3 + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 5 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"meta\\", + \\"attributes\\": { + \\"charset\\": \\"UTF-8\\" + }, + \\"childNodes\\": [], + \\"id\\": 6 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 7 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"meta\\", + \\"attributes\\": { + \\"name\\": \\"viewport\\", + \\"content\\": \\"width=device-width, initial-scale=1.0\\" + }, + \\"childNodes\\": [], + \\"id\\": 8 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 9 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"meta\\", + \\"attributes\\": { + \\"http-equiv\\": \\"X-UA-Compatible\\", + \\"content\\": \\"ie=edge\\" + }, + \\"childNodes\\": [], + \\"id\\": 10 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 11 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"title\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"Mask text\\", + \\"id\\": 13 + } + ], + \\"id\\": 12 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 14 + } + ], + \\"id\\": 4 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 15 }, { \\"type\\": 2, @@ -5583,91 +3672,121 @@ exports[`move-node-1 1`] = ` { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", - \\"id\\": 5 + \\"id\\": 17 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"p\\", + \\"attributes\\": { + \\"class\\": \\"rr-mask\\" + }, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"*****\\", + \\"id\\": 19 + } + ], + \\"id\\": 18 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 20 }, { \\"type\\": 2, \\"tagName\\": \\"div\\", - \\"attributes\\": {}, + \\"attributes\\": { + \\"class\\": \\"rr-mask\\" + }, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", - \\"id\\": 7 + \\"id\\": 22 }, { \\"type\\": 2, - \\"tagName\\": \\"p\\", + \\"tagName\\": \\"span\\", \\"attributes\\": {}, - \\"childNodes\\": [], - \\"id\\": 8 + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"*****\\", + \\"id\\": 24 + } + ], + \\"id\\": 23 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", - \\"id\\": 9 + \\"id\\": 25 } ], - \\"id\\": 6 + \\"id\\": 21 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", - \\"id\\": 10 + \\"id\\": 26 }, { \\"type\\": 2, - \\"tagName\\": \\"span\\", - \\"attributes\\": {}, + \\"tagName\\": \\"div\\", + \\"attributes\\": { + \\"data-masking\\": \\"true\\" + }, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", - \\"id\\": 12 + \\"id\\": 28 }, { \\"type\\": 2, - \\"tagName\\": \\"i\\", + \\"tagName\\": \\"div\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", - \\"id\\": 14 + \\"id\\": 30 }, { \\"type\\": 2, - \\"tagName\\": \\"b\\", + \\"tagName\\": \\"div\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, - \\"textContent\\": \\"1\\", - \\"id\\": 16 + \\"textContent\\": \\"*****\\", + \\"id\\": 32 } ], - \\"id\\": 15 + \\"id\\": 31 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", - \\"id\\": 17 + \\"id\\": 33 } ], - \\"id\\": 13 + \\"id\\": 29 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", - \\"id\\": 18 + \\"id\\": 34 } ], - \\"id\\": 11 + \\"id\\": 27 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\\\n \\", - \\"id\\": 19 + \\"id\\": 35 }, { \\"type\\": 2, @@ -5677,21 +3796,21 @@ exports[`move-node-1 1`] = ` { \\"type\\": 3, \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", - \\"id\\": 21 + \\"id\\": 37 } ], - \\"id\\": 20 + \\"id\\": 36 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\\\n \\\\n\\\\n\\", - \\"id\\": 22 + \\"id\\": 38 } ], - \\"id\\": 4 + \\"id\\": 16 } ], - \\"id\\": 2 + \\"id\\": 3 } ], \\"id\\": 1 @@ -5701,105 +3820,11 @@ exports[`move-node-1 1`] = ` \\"top\\": 0 } } - }, - { - \\"type\\": 3, - \\"data\\": { - \\"source\\": 0, - \\"texts\\": [], - \\"attributes\\": [], - \\"removes\\": [ - { - \\"parentId\\": 4, - \\"id\\": 11 - } - ], - \\"adds\\": [ - { - \\"parentId\\": 6, - \\"nextId\\": null, - \\"node\\": { - \\"type\\": 2, - \\"tagName\\": \\"span\\", - \\"attributes\\": {}, - \\"childNodes\\": [], - \\"id\\": 11 - } - }, - { - \\"parentId\\": 11, - \\"nextId\\": 13, - \\"node\\": { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 12 - } - }, - { - \\"parentId\\": 11, - \\"nextId\\": 18, - \\"node\\": { - \\"type\\": 2, - \\"tagName\\": \\"i\\", - \\"attributes\\": {}, - \\"childNodes\\": [], - \\"id\\": 13 - } - }, - { - \\"parentId\\": 13, - \\"nextId\\": 15, - \\"node\\": { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 14 - } - }, - { - \\"parentId\\": 13, - \\"nextId\\": 17, - \\"node\\": { - \\"type\\": 2, - \\"tagName\\": \\"b\\", - \\"attributes\\": {}, - \\"childNodes\\": [], - \\"id\\": 15 - } - }, - { - \\"parentId\\": 15, - \\"nextId\\": null, - \\"node\\": { - \\"type\\": 3, - \\"textContent\\": \\"1\\", - \\"id\\": 16 - } - }, - { - \\"parentId\\": 13, - \\"nextId\\": null, - \\"node\\": { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 17 - } - }, - { - \\"parentId\\": 11, - \\"nextId\\": null, - \\"node\\": { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 18 - } - } - ] - } } ]" `; -exports[`move-node-2 1`] = ` +exports[`record integration tests should mask texts using maskTextFn 1`] = ` "[ { \\"type\\": 0, @@ -5823,17 +3848,99 @@ exports[`move-node-2 1`] = ` \\"node\\": { \\"type\\": 0, \\"childNodes\\": [ + { + \\"type\\": 1, + \\"name\\": \\"html\\", + \\"publicId\\": \\"\\", + \\"systemId\\": \\"\\", + \\"id\\": 2 + }, { \\"type\\": 2, \\"tagName\\": \\"html\\", - \\"attributes\\": {}, + \\"attributes\\": { + \\"lang\\": \\"en\\" + }, \\"childNodes\\": [ { \\"type\\": 2, \\"tagName\\": \\"head\\", \\"attributes\\": {}, - \\"childNodes\\": [], - \\"id\\": 3 + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 5 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"meta\\", + \\"attributes\\": { + \\"charset\\": \\"UTF-8\\" + }, + \\"childNodes\\": [], + \\"id\\": 6 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 7 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"meta\\", + \\"attributes\\": { + \\"name\\": \\"viewport\\", + \\"content\\": \\"width=device-width, initial-scale=1.0\\" + }, + \\"childNodes\\": [], + \\"id\\": 8 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 9 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"meta\\", + \\"attributes\\": { + \\"http-equiv\\": \\"X-UA-Compatible\\", + \\"content\\": \\"ie=edge\\" + }, + \\"childNodes\\": [], + \\"id\\": 10 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 11 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"title\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"Mask text\\", + \\"id\\": 13 + } + ], + \\"id\\": 12 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 14 + } + ], + \\"id\\": 4 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 15 }, { \\"type\\": 2, @@ -5843,91 +3950,121 @@ exports[`move-node-2 1`] = ` { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", - \\"id\\": 5 + \\"id\\": 17 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"p\\", + \\"attributes\\": { + \\"class\\": \\"rr-mask\\" + }, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"****1\\", + \\"id\\": 19 + } + ], + \\"id\\": 18 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 20 }, { \\"type\\": 2, \\"tagName\\": \\"div\\", - \\"attributes\\": {}, + \\"attributes\\": { + \\"class\\": \\"rr-mask\\" + }, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", - \\"id\\": 7 + \\"id\\": 22 }, { \\"type\\": 2, - \\"tagName\\": \\"p\\", + \\"tagName\\": \\"span\\", \\"attributes\\": {}, - \\"childNodes\\": [], - \\"id\\": 8 + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"****2\\", + \\"id\\": 24 + } + ], + \\"id\\": 23 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", - \\"id\\": 9 + \\"id\\": 25 } ], - \\"id\\": 6 + \\"id\\": 21 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", - \\"id\\": 10 + \\"id\\": 26 }, { \\"type\\": 2, - \\"tagName\\": \\"span\\", - \\"attributes\\": {}, + \\"tagName\\": \\"div\\", + \\"attributes\\": { + \\"data-masking\\": \\"true\\" + }, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", - \\"id\\": 12 + \\"id\\": 28 }, { \\"type\\": 2, - \\"tagName\\": \\"i\\", + \\"tagName\\": \\"div\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", - \\"id\\": 14 + \\"id\\": 30 }, { \\"type\\": 2, - \\"tagName\\": \\"b\\", + \\"tagName\\": \\"div\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, - \\"textContent\\": \\"1\\", - \\"id\\": 16 + \\"textContent\\": \\"****3\\", + \\"id\\": 32 } ], - \\"id\\": 15 + \\"id\\": 31 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", - \\"id\\": 17 + \\"id\\": 33 } ], - \\"id\\": 13 + \\"id\\": 29 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", - \\"id\\": 18 + \\"id\\": 34 } ], - \\"id\\": 11 + \\"id\\": 27 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\\\n \\", - \\"id\\": 19 + \\"id\\": 35 }, { \\"type\\": 2, @@ -5937,21 +4074,21 @@ exports[`move-node-2 1`] = ` { \\"type\\": 3, \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", - \\"id\\": 21 + \\"id\\": 37 } ], - \\"id\\": 20 + \\"id\\": 36 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\\\n \\\\n\\\\n\\", - \\"id\\": 22 + \\"id\\": 38 } ], - \\"id\\": 4 + \\"id\\": 16 } ], - \\"id\\": 2 + \\"id\\": 3 } ], \\"id\\": 1 @@ -5961,120 +4098,15 @@ exports[`move-node-2 1`] = ` \\"top\\": 0 } } - }, - { - \\"type\\": 3, - \\"data\\": { - \\"source\\": 0, - \\"texts\\": [], - \\"attributes\\": [], - \\"removes\\": [ - { - \\"parentId\\": 4, - \\"id\\": 11 - } - ], - \\"adds\\": [ - { - \\"parentId\\": 11, - \\"nextId\\": 13, - \\"node\\": { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 12 - } - }, - { - \\"parentId\\": 11, - \\"nextId\\": 18, - \\"node\\": { - \\"type\\": 2, - \\"tagName\\": \\"i\\", - \\"attributes\\": {}, - \\"childNodes\\": [], - \\"id\\": 13 - } - }, - { - \\"parentId\\": 13, - \\"nextId\\": 15, - \\"node\\": { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 14 - } - }, - { - \\"parentId\\": 13, - \\"nextId\\": 17, - \\"node\\": { - \\"type\\": 2, - \\"tagName\\": \\"b\\", - \\"attributes\\": {}, - \\"childNodes\\": [], - \\"id\\": 15 - } - }, - { - \\"parentId\\": 15, - \\"nextId\\": null, - \\"node\\": { - \\"type\\": 3, - \\"textContent\\": \\"1\\", - \\"id\\": 16 - } - }, - { - \\"parentId\\": 13, - \\"nextId\\": null, - \\"node\\": { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 17 - } - }, - { - \\"parentId\\": 11, - \\"nextId\\": null, - \\"node\\": { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 18 - } - }, - { - \\"parentId\\": 4, - \\"nextId\\": null, - \\"node\\": { - \\"type\\": 2, - \\"tagName\\": \\"div\\", - \\"attributes\\": {}, - \\"childNodes\\": [], - \\"id\\": 23 - } - }, - { - \\"parentId\\": 23, - \\"nextId\\": null, - \\"node\\": { - \\"type\\": 2, - \\"tagName\\": \\"span\\", - \\"attributes\\": {}, - \\"childNodes\\": [], - \\"id\\": 11 - } - } - ] - } - } -]" -`; - -exports[`react-styled-components 1`] = ` -"[ - { - \\"type\\": 0, - \\"data\\": {} + } +]" +`; + +exports[`record integration tests should mask value attribute with maskInputOptions 1`] = ` +"[ + { + \\"type\\": 0, + \\"data\\": {} }, { \\"type\\": 1, @@ -6136,8 +4168,8 @@ exports[`react-styled-components 1`] = ` \\"type\\": 2, \\"tagName\\": \\"meta\\", \\"attributes\\": { - \\"name\\": \\"viewport\\", - \\"content\\": \\"width=device-width, initial-scale=1.0\\" + \\"http-equiv\\": \\"X-UA-Compatible\\", + \\"content\\": \\"IE=edge\\" }, \\"childNodes\\": [], \\"id\\": 8 @@ -6151,8 +4183,8 @@ exports[`react-styled-components 1`] = ` \\"type\\": 2, \\"tagName\\": \\"meta\\", \\"attributes\\": { - \\"http-equiv\\": \\"X-UA-Compatible\\", - \\"content\\": \\"ie=edge\\" + \\"name\\": \\"viewport\\", + \\"content\\": \\"width=device-width, initial-scale=1.0\\" }, \\"childNodes\\": [], \\"id\\": 10 @@ -6169,7 +4201,7 @@ exports[`react-styled-components 1`] = ` \\"childNodes\\": [ { \\"type\\": 3, - \\"textContent\\": \\"react styled components\\", + \\"textContent\\": \\"Document\\", \\"id\\": 13 } ], @@ -6179,37 +4211,6 @@ exports[`react-styled-components 1`] = ` \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"id\\": 14 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"script\\", - \\"attributes\\": {}, - \\"childNodes\\": [ - { - \\"type\\": 3, - \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", - \\"id\\": 16 - } - ], - \\"id\\": 15 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"style\\", - \\"attributes\\": { - \\"data-styled\\": \\"active\\", - \\"data-styled-version\\": \\"5.0.1\\", - \\"_cssText\\": \\".ixzlRK { font-size: 1.5em; text-align: center; color: palevioletred; }.eOXmez { font-size: 1.5em; text-align: center; color: rebeccapurple; }.bJCmFu { padding: 4em; background: papayawhip; }\\" - }, - \\"childNodes\\": [ - { - \\"type\\": 3, - \\"textContent\\": \\"\\", - \\"isStyle\\": true, - \\"id\\": 18 - } - ], - \\"id\\": 17 } ], \\"id\\": 4 @@ -6217,7 +4218,7 @@ exports[`react-styled-components 1`] = ` { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", - \\"id\\": 19 + \\"id\\": 15 }, { \\"type\\": 2, @@ -6227,174 +4228,61 @@ exports[`react-styled-components 1`] = ` { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", - \\"id\\": 21 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"div\\", - \\"attributes\\": { - \\"id\\": \\"app\\" - }, - \\"childNodes\\": [ - { - \\"type\\": 2, - \\"tagName\\": \\"section\\", - \\"attributes\\": { - \\"class\\": \\"sc-AxirZ bJCmFu\\" - }, - \\"childNodes\\": [ - { - \\"type\\": 2, - \\"tagName\\": \\"h1\\", - \\"attributes\\": { - \\"class\\": \\"sc-AxjAm ixzlRK\\" - }, - \\"childNodes\\": [ - { - \\"type\\": 3, - \\"textContent\\": \\"Hello World!\\", - \\"id\\": 25 - } - ], - \\"id\\": 24 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"h1\\", - \\"attributes\\": { - \\"class\\": \\"sc-AxjAm eOXmez toggle\\", - \\"color\\": \\"rebeccapurple\\" - }, - \\"childNodes\\": [ - { - \\"type\\": 3, - \\"textContent\\": \\"Hello World!\\", - \\"id\\": 27 - } - ], - \\"id\\": 26 - } - ], - \\"id\\": 23 - } - ], - \\"id\\": 22 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 28 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"script\\", - \\"attributes\\": { - \\"src\\": \\"https://cdn.jsdelivr.net/npm/react@16/umd/react.production.min.js\\" - }, - \\"childNodes\\": [], - \\"id\\": 29 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 30 + \\"id\\": 17 }, { \\"type\\": 2, - \\"tagName\\": \\"script\\", + \\"tagName\\": \\"input\\", \\"attributes\\": { - \\"src\\": \\"https://cdn.jsdelivr.net/npm/react-dom@16/umd/react-dom.production.min.js\\" + \\"type\\": \\"password\\", + \\"id\\": \\"password\\" }, \\"childNodes\\": [], - \\"id\\": 31 + \\"id\\": 18 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", - \\"id\\": 32 + \\"id\\": 19 }, { \\"type\\": 2, \\"tagName\\": \\"script\\", - \\"attributes\\": { - \\"src\\": \\"https://cdn.jsdelivr.net/npm/react-is@16.13.1/umd/react-is.production.min.js\\" - }, - \\"childNodes\\": [], - \\"id\\": 33 + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", + \\"id\\": 21 + } + ], + \\"id\\": 20 }, { \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 34 + \\"textContent\\": \\"\\\\n \\\\n \\", + \\"id\\": 22 }, { \\"type\\": 2, \\"tagName\\": \\"script\\", - \\"attributes\\": { - \\"src\\": \\"https://cdn.jsdelivr.net/npm/styled-components@5.0.1/dist/styled-components.min.js\\" - }, - \\"childNodes\\": [], - \\"id\\": 35 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 36 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"script\\", - \\"attributes\\": { - \\"src\\": \\"https://cdn.jsdelivr.net/npm/babel-standalone@6/babel.min.js\\" - }, - \\"childNodes\\": [], - \\"id\\": 37 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 38 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"script\\", - \\"attributes\\": { - \\"type\\": \\"text/babel\\" - }, - \\"childNodes\\": [ - { - \\"type\\": 3, - \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", - \\"id\\": 40 - } - ], - \\"id\\": 39 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\\\n \\", - \\"id\\": 41 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"script\\", - \\"attributes\\": {}, - \\"childNodes\\": [ - { - \\"type\\": 3, - \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", - \\"id\\": 43 - } - ], - \\"id\\": 42 + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", + \\"id\\": 24 + } + ], + \\"id\\": 23 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\\\n \\\\n\\\\n\\", - \\"id\\": 44 + \\"id\\": 25 } ], - \\"id\\": 20 + \\"id\\": 16 } ], \\"id\\": 3 @@ -6412,37 +4300,69 @@ exports[`react-styled-components 1`] = ` \\"type\\": 3, \\"data\\": { \\"source\\": 2, - \\"type\\": 1, - \\"id\\": 26 + \\"type\\": 5, + \\"id\\": 18 } }, { \\"type\\": 3, \\"data\\": { - \\"source\\": 2, - \\"type\\": 0, - \\"id\\": 26 + \\"source\\": 5, + \\"text\\": \\"*\\", + \\"isChecked\\": false, + \\"id\\": 18 } }, { \\"type\\": 3, \\"data\\": { - \\"source\\": 2, - \\"type\\": 2, - \\"id\\": 26 + \\"source\\": 0, + \\"texts\\": [], + \\"attributes\\": [ + { + \\"id\\": 18, + \\"attributes\\": { + \\"value\\": \\"*\\" + } + } + ], + \\"removes\\": [], + \\"adds\\": [] } }, { \\"type\\": 3, \\"data\\": { - \\"source\\": 8, - \\"id\\": 17, - \\"adds\\": [ + \\"source\\": 5, + \\"text\\": \\"**\\", + \\"isChecked\\": false, + \\"id\\": 18 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 0, + \\"texts\\": [], + \\"attributes\\": [ { - \\"rule\\": \\".pqkNE{font-size:1.5em;text-align:center;color:pink;}\\", - \\"index\\": 2 + \\"id\\": 18, + \\"attributes\\": { + \\"value\\": \\"**\\" + } } - ] + ], + \\"removes\\": [], + \\"adds\\": [] + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 5, + \\"text\\": \\"***\\", + \\"isChecked\\": false, + \\"id\\": 18 } }, { @@ -6452,10 +4372,87 @@ exports[`react-styled-components 1`] = ` \\"texts\\": [], \\"attributes\\": [ { - \\"id\\": 26, + \\"id\\": 18, \\"attributes\\": { - \\"class\\": \\"sc-AxjAm pqkNE toggle\\", - \\"color\\": \\"pink\\" + \\"value\\": \\"***\\" + } + } + ], + \\"removes\\": [], + \\"adds\\": [] + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 5, + \\"text\\": \\"****\\", + \\"isChecked\\": false, + \\"id\\": 18 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 0, + \\"texts\\": [], + \\"attributes\\": [ + { + \\"id\\": 18, + \\"attributes\\": { + \\"value\\": \\"****\\" + } + } + ], + \\"removes\\": [], + \\"adds\\": [] + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 5, + \\"text\\": \\"*****\\", + \\"isChecked\\": false, + \\"id\\": 18 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 0, + \\"texts\\": [], + \\"attributes\\": [ + { + \\"id\\": 18, + \\"attributes\\": { + \\"value\\": \\"*****\\" + } + } + ], + \\"removes\\": [], + \\"adds\\": [] + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 5, + \\"text\\": \\"******\\", + \\"isChecked\\": false, + \\"id\\": 18 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 0, + \\"texts\\": [], + \\"attributes\\": [ + { + \\"id\\": 18, + \\"attributes\\": { + \\"value\\": \\"******\\" } } ], @@ -6466,7 +4463,7 @@ exports[`react-styled-components 1`] = ` ]" `; -exports[`select2 1`] = ` +exports[`record integration tests should nest record iframe 1`] = ` "[ { \\"type\\": 0, @@ -6511,7 +4508,7 @@ exports[`select2 1`] = ` \\"childNodes\\": [ { \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", + \\"textContent\\": \\"\\\\n \\", \\"id\\": 5 }, { @@ -6525,7 +4522,7 @@ exports[`select2 1`] = ` }, { \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", + \\"textContent\\": \\"\\\\n \\", \\"id\\": 7 }, { @@ -6540,64 +4537,53 @@ exports[`select2 1`] = ` }, { \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", + \\"textContent\\": \\"\\\\n \\", \\"id\\": 9 }, { \\"type\\": 2, - \\"tagName\\": \\"meta\\", - \\"attributes\\": { - \\"http-equiv\\": \\"X-UA-Compatible\\", - \\"content\\": \\"ie=edge\\" - }, - \\"childNodes\\": [], + \\"tagName\\": \\"title\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"Main\\", + \\"id\\": 11 + } + ], \\"id\\": 10 }, { \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 11 + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 12 }, { \\"type\\": 2, - \\"tagName\\": \\"title\\", + \\"tagName\\": \\"style\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, - \\"textContent\\": \\"Select2 3.5\\", - \\"id\\": 13 + \\"textContent\\": \\"iframe { width: 500px; height: 500px; }\\", + \\"isStyle\\": true, + \\"id\\": 14 } ], - \\"id\\": 12 + \\"id\\": 13 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", - \\"id\\": 14 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"link\\", - \\"attributes\\": { - \\"rel\\": \\"stylesheet\\", - \\"href\\": \\"https://cdn.jsdelivr.net/npm/select2@3.5.1/select2.css\\" - }, - \\"childNodes\\": [], \\"id\\": 15 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n\\", - \\"id\\": 16 } ], \\"id\\": 4 }, { \\"type\\": 3, - \\"textContent\\": \\"\\\\n\\", - \\"id\\": 17 + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 16 }, { \\"type\\": 2, @@ -6606,402 +4592,558 @@ exports[`select2 1`] = ` \\"childNodes\\": [ { \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 18 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"iframe\\", + \\"attributes\\": { + \\"id\\": \\"one\\" + }, + \\"childNodes\\": [], \\"id\\": 19 }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\\\n \\", + \\"id\\": 20 + }, { \\"type\\": 2, - \\"tagName\\": \\"blockquote\\", + \\"tagName\\": \\"script\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, - \\"textContent\\": \\"\\\\n Select2 is a jQuery replacement for select boxes.\\\\n \\", - \\"id\\": 21 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"br\\", - \\"attributes\\": {}, - \\"childNodes\\": [], + \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", \\"id\\": 22 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n In the 3.5 version it use a quite complicated DOM generation strategy which is a good battle-test for rrweb's recorder.\\\\n \\", - \\"id\\": 23 } ], - \\"id\\": 20 + \\"id\\": 21 }, { \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", + \\"textContent\\": \\"\\\\n \\\\n \\\\n \\", + \\"id\\": 23 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"script\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", + \\"id\\": 25 + } + ], \\"id\\": 24 }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n\\\\n\\", + \\"id\\": 26 + } + ], + \\"id\\": 17 + } + ], + \\"id\\": 3 + } + ], + \\"id\\": 1 + }, + \\"initialOffset\\": { + \\"left\\": 0, + \\"top\\": 0 + } + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 0, + \\"adds\\": [ + { + \\"parentId\\": 19, + \\"nextId\\": null, + \\"node\\": { + \\"type\\": 0, + \\"childNodes\\": [ + { + \\"type\\": 2, + \\"tagName\\": \\"html\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 2, + \\"tagName\\": \\"head\\", + \\"attributes\\": {}, + \\"childNodes\\": [], + \\"rootId\\": 27, + \\"id\\": 29 + }, { \\"type\\": 2, - \\"tagName\\": \\"div\\", - \\"attributes\\": { - \\"class\\": \\"select2-container\\", - \\"id\\": \\"s2id_el\\" - }, + \\"tagName\\": \\"body\\", + \\"attributes\\": {}, + \\"childNodes\\": [], + \\"rootId\\": 27, + \\"id\\": 30 + } + ], + \\"rootId\\": 27, + \\"id\\": 28 + } + ], + \\"compatMode\\": \\"BackCompat\\", + \\"id\\": 27 + } + } + ], + \\"removes\\": [], + \\"texts\\": [], + \\"attributes\\": [], + \\"isAttachIframe\\": true + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 0, + \\"texts\\": [], + \\"attributes\\": [], + \\"removes\\": [], + \\"adds\\": [ + { + \\"parentId\\": 17, + \\"nextId\\": null, + \\"node\\": { + \\"type\\": 2, + \\"tagName\\": \\"iframe\\", + \\"attributes\\": { + \\"id\\": \\"two\\" + }, + \\"childNodes\\": [], + \\"id\\": 31 + } + } + ] + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 0, + \\"adds\\": [ + { + \\"parentId\\": 31, + \\"nextId\\": null, + \\"node\\": { + \\"type\\": 0, + \\"childNodes\\": [ + { + \\"type\\": 1, + \\"name\\": \\"html\\", + \\"publicId\\": \\"\\", + \\"systemId\\": \\"\\", + \\"rootId\\": 32, + \\"id\\": 33 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"html\\", + \\"attributes\\": { + \\"lang\\": \\"en\\" + }, + \\"childNodes\\": [ + { + \\"type\\": 2, + \\"tagName\\": \\"head\\", + \\"attributes\\": {}, \\"childNodes\\": [ { - \\"type\\": 2, - \\"tagName\\": \\"a\\", - \\"attributes\\": { - \\"href\\": \\"javascript:void(0)\\", - \\"class\\": \\"select2-choice\\", - \\"tabindex\\": \\"-1\\" - }, - \\"childNodes\\": [ - { - \\"type\\": 3, - \\"textContent\\": \\" \\", - \\"id\\": 27 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"span\\", - \\"attributes\\": { - \\"class\\": \\"select2-chosen\\", - \\"id\\": \\"select2-chosen-1\\" - }, - \\"childNodes\\": [ - { - \\"type\\": 3, - \\"textContent\\": \\"A\\", - \\"id\\": 29 - } - ], - \\"id\\": 28 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"abbr\\", - \\"attributes\\": { - \\"class\\": \\"select2-search-choice-close\\" - }, - \\"childNodes\\": [], - \\"id\\": 30 - }, - { - \\"type\\": 3, - \\"textContent\\": \\" \\", - \\"id\\": 31 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"span\\", - \\"attributes\\": { - \\"class\\": \\"select2-arrow\\", - \\"role\\": \\"presentation\\" - }, - \\"childNodes\\": [ - { - \\"type\\": 2, - \\"tagName\\": \\"b\\", - \\"attributes\\": { - \\"role\\": \\"presentation\\" - }, - \\"childNodes\\": [], - \\"id\\": 33 - } - ], - \\"id\\": 32 - } - ], - \\"id\\": 26 + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"rootId\\": 32, + \\"id\\": 36 }, { \\"type\\": 2, - \\"tagName\\": \\"label\\", + \\"tagName\\": \\"meta\\", \\"attributes\\": { - \\"for\\": \\"s2id_autogen1\\", - \\"class\\": \\"select2-offscreen\\" + \\"charset\\": \\"UTF-8\\" }, \\"childNodes\\": [], - \\"id\\": 34 + \\"rootId\\": 32, + \\"id\\": 37 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"rootId\\": 32, + \\"id\\": 38 }, { \\"type\\": 2, - \\"tagName\\": \\"input\\", + \\"tagName\\": \\"meta\\", \\"attributes\\": { - \\"class\\": \\"select2-focusser select2-offscreen\\", - \\"type\\": \\"text\\", - \\"aria-haspopup\\": \\"true\\", - \\"role\\": \\"button\\", - \\"aria-labelledby\\": \\"select2-chosen-1\\", - \\"id\\": \\"s2id_autogen1\\" + \\"name\\": \\"viewport\\", + \\"content\\": \\"width=device-width, initial-scale=1.0\\" }, \\"childNodes\\": [], - \\"id\\": 35 + \\"rootId\\": 32, + \\"id\\": 39 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"rootId\\": 32, + \\"id\\": 40 }, { \\"type\\": 2, - \\"tagName\\": \\"div\\", - \\"attributes\\": { - \\"class\\": \\"select2-drop select2-display-none select2-with-searchbox\\" - }, + \\"tagName\\": \\"title\\", + \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, - \\"textContent\\": \\" \\", - \\"id\\": 37 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"div\\", - \\"attributes\\": { - \\"class\\": \\"select2-search\\" - }, - \\"childNodes\\": [ - { - \\"type\\": 3, - \\"textContent\\": \\" \\", - \\"id\\": 39 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"label\\", - \\"attributes\\": { - \\"for\\": \\"s2id_autogen1_search\\", - \\"class\\": \\"select2-offscreen\\" - }, - \\"childNodes\\": [], - \\"id\\": 40 - }, - { - \\"type\\": 3, - \\"textContent\\": \\" \\", - \\"id\\": 41 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"input\\", - \\"attributes\\": { - \\"type\\": \\"text\\", - \\"autocomplete\\": \\"off\\", - \\"autocorrect\\": \\"off\\", - \\"autocapitalize\\": \\"off\\", - \\"spellcheck\\": \\"false\\", - \\"class\\": \\"select2-input\\", - \\"role\\": \\"combobox\\", - \\"aria-expanded\\": \\"true\\", - \\"aria-autocomplete\\": \\"list\\", - \\"aria-owns\\": \\"select2-results-1\\", - \\"id\\": \\"s2id_autogen1_search\\", - \\"placeholder\\": \\"\\" - }, - \\"childNodes\\": [], - \\"id\\": 42 - }, - { - \\"type\\": 3, - \\"textContent\\": \\" \\", - \\"id\\": 43 - } - ], - \\"id\\": 38 - }, - { - \\"type\\": 3, - \\"textContent\\": \\" \\", - \\"id\\": 44 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"ul\\", - \\"attributes\\": { - \\"class\\": \\"select2-results\\", - \\"role\\": \\"listbox\\", - \\"id\\": \\"select2-results-1\\" - }, - \\"childNodes\\": [ - { - \\"type\\": 3, - \\"textContent\\": \\" \\", - \\"id\\": 46 - } - ], - \\"id\\": 45 + \\"textContent\\": \\"Frame 1\\", + \\"rootId\\": 32, + \\"id\\": 42 } ], - \\"id\\": 36 + \\"rootId\\": 32, + \\"id\\": 41 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"rootId\\": 32, + \\"id\\": 43 } ], - \\"id\\": 25 + \\"rootId\\": 32, + \\"id\\": 35 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"rootId\\": 32, + \\"id\\": 44 }, { \\"type\\": 2, - \\"tagName\\": \\"select\\", - \\"attributes\\": { - \\"id\\": \\"el\\", - \\"tabindex\\": \\"-1\\", - \\"title\\": \\"\\", - \\"style\\": \\"display: none;\\", - \\"value\\": \\"a\\" - }, + \\"tagName\\": \\"body\\", + \\"attributes\\": {}, \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n frame 1\\\\n \\", + \\"rootId\\": 32, + \\"id\\": 46 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"iframe\\", + \\"attributes\\": { + \\"id\\": \\"three\\", + \\"frameborder\\": \\"0\\" + }, + \\"childNodes\\": [], + \\"rootId\\": 32, + \\"id\\": 47 + }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", + \\"rootId\\": 32, \\"id\\": 48 }, { \\"type\\": 2, - \\"tagName\\": \\"option\\", + \\"tagName\\": \\"iframe\\", \\"attributes\\": { - \\"value\\": \\"a\\", - \\"selected\\": true + \\"id\\": \\"four\\", + \\"frameborder\\": \\"0\\" }, - \\"childNodes\\": [ - { - \\"type\\": 3, - \\"textContent\\": \\"A\\", - \\"id\\": 50 - } - ], + \\"childNodes\\": [], + \\"rootId\\": 32, \\"id\\": 49 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", - \\"id\\": 51 + \\"rootId\\": 32, + \\"id\\": 50 }, { \\"type\\": 2, - \\"tagName\\": \\"option\\", + \\"tagName\\": \\"svg\\", \\"attributes\\": { - \\"value\\": \\"b\\" + \\"xmlns\\": \\"http://www.w3.org/2000/svg\\", + \\"xmlns:xlink\\": \\"http://www.w3.org/1999/xlink\\", + \\"width\\": \\"300\\", + \\"height\\": \\"300\\" }, \\"childNodes\\": [ { \\"type\\": 3, - \\"textContent\\": \\"B\\", + \\"textContent\\": \\"\\\\n \\", + \\"rootId\\": 32, + \\"id\\": 52 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"rect\\", + \\"attributes\\": { + \\"id\\": \\"el\\", + \\"width\\": \\"100\\", + \\"height\\": \\"50\\", + \\"x\\": \\"40\\", + \\"y\\": \\"20\\", + \\"fill\\": \\"red\\" + }, + \\"childNodes\\": [], + \\"isSVG\\": true, + \\"rootId\\": 32, \\"id\\": 53 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"rootId\\": 32, + \\"id\\": 54 } ], - \\"id\\": 52 + \\"isSVG\\": true, + \\"rootId\\": 32, + \\"id\\": 51 }, { \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 54 - } - ], - \\"id\\": 47 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 55 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"script\\", - \\"attributes\\": { - \\"src\\": \\"https://cdn.jsdelivr.net/npm/jquery@3.3.1/dist/jquery.min.js\\" - }, - \\"childNodes\\": [], - \\"id\\": 56 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 57 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"script\\", - \\"attributes\\": { - \\"src\\": \\"https://cdn.jsdelivr.net/npm/select2@3.5.2-browserify/select2.min.js\\" - }, - \\"childNodes\\": [], - \\"id\\": 58 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 59 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"script\\", - \\"attributes\\": {}, - \\"childNodes\\": [ - { - \\"type\\": 3, - \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", - \\"id\\": 61 - } - ], - \\"id\\": 60 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"span\\", - \\"attributes\\": { - \\"role\\": \\"status\\", - \\"aria-live\\": \\"polite\\", - \\"class\\": \\"select2-hidden-accessible\\" - }, - \\"childNodes\\": [], - \\"id\\": 62 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n\\\\n \\", - \\"id\\": 63 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"script\\", - \\"attributes\\": {}, - \\"childNodes\\": [ - { - \\"type\\": 3, - \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", - \\"id\\": 65 + \\"textContent\\": \\"\\\\n \\\\n\\\\n\\", + \\"rootId\\": 32, + \\"id\\": 55 } ], - \\"id\\": 64 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\\\n \\\\n\\", - \\"id\\": 66 + \\"rootId\\": 32, + \\"id\\": 45 } ], - \\"id\\": 18 + \\"rootId\\": 32, + \\"id\\": 34 } ], - \\"id\\": 3 + \\"id\\": 32 } - ], - \\"id\\": 1 - }, - \\"initialOffset\\": { - \\"left\\": 0, - \\"top\\": 0 - } + } + ], + \\"removes\\": [], + \\"texts\\": [], + \\"attributes\\": [], + \\"isAttachIframe\\": true } }, { \\"type\\": 3, \\"data\\": { - \\"source\\": 2, - \\"type\\": 1, - \\"id\\": 26 + \\"source\\": 0, + \\"adds\\": [ + { + \\"parentId\\": 47, + \\"nextId\\": null, + \\"node\\": { + \\"type\\": 0, + \\"childNodes\\": [ + { + \\"type\\": 2, + \\"tagName\\": \\"html\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 2, + \\"tagName\\": \\"head\\", + \\"attributes\\": {}, + \\"childNodes\\": [], + \\"rootId\\": 56, + \\"id\\": 58 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"body\\", + \\"attributes\\": {}, + \\"childNodes\\": [], + \\"rootId\\": 56, + \\"id\\": 59 + } + ], + \\"rootId\\": 56, + \\"id\\": 57 + } + ], + \\"compatMode\\": \\"BackCompat\\", + \\"id\\": 56 + } + } + ], + \\"removes\\": [], + \\"texts\\": [], + \\"attributes\\": [], + \\"isAttachIframe\\": true } }, { \\"type\\": 3, \\"data\\": { - \\"source\\": 2, - \\"type\\": 5, - \\"id\\": 42 + \\"source\\": 0, + \\"adds\\": [ + { + \\"parentId\\": 49, + \\"nextId\\": null, + \\"node\\": { + \\"type\\": 0, + \\"childNodes\\": [ + { + \\"type\\": 1, + \\"name\\": \\"html\\", + \\"publicId\\": \\"\\", + \\"systemId\\": \\"\\", + \\"rootId\\": 60, + \\"id\\": 61 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"html\\", + \\"attributes\\": { + \\"lang\\": \\"en\\" + }, + \\"childNodes\\": [ + { + \\"type\\": 2, + \\"tagName\\": \\"head\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"rootId\\": 60, + \\"id\\": 64 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"meta\\", + \\"attributes\\": { + \\"charset\\": \\"UTF-8\\" + }, + \\"childNodes\\": [], + \\"rootId\\": 60, + \\"id\\": 65 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"rootId\\": 60, + \\"id\\": 66 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"meta\\", + \\"attributes\\": { + \\"name\\": \\"viewport\\", + \\"content\\": \\"width=device-width, initial-scale=1.0\\" + }, + \\"childNodes\\": [], + \\"rootId\\": 60, + \\"id\\": 67 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"rootId\\": 60, + \\"id\\": 68 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"title\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"Frame 2\\", + \\"rootId\\": 60, + \\"id\\": 70 + } + ], + \\"rootId\\": 60, + \\"id\\": 69 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"rootId\\": 60, + \\"id\\": 71 + } + ], + \\"rootId\\": 60, + \\"id\\": 63 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"rootId\\": 60, + \\"id\\": 72 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"body\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n frame 2\\\\n \\\\n \\", + \\"rootId\\": 60, + \\"id\\": 74 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"script\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", + \\"rootId\\": 60, + \\"id\\": 76 + } + ], + \\"rootId\\": 60, + \\"id\\": 75 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n\\\\n\\", + \\"rootId\\": 60, + \\"id\\": 77 + } + ], + \\"rootId\\": 60, + \\"id\\": 73 + } + ], + \\"rootId\\": 60, + \\"id\\": 62 + } + ], + \\"id\\": 60 + } + } + ], + \\"removes\\": [], + \\"texts\\": [], + \\"attributes\\": [], + \\"isAttachIframe\\": true } }, { @@ -7009,547 +5151,4438 @@ exports[`select2 1`] = ` \\"data\\": { \\"source\\": 0, \\"texts\\": [], - \\"attributes\\": [ - { - \\"id\\": 25, - \\"attributes\\": { - \\"class\\": \\"select2-container select2-dropdown-open select2-container-active\\" - } - }, - { - \\"id\\": 36, - \\"attributes\\": { - \\"id\\": \\"select2-drop\\", - \\"style\\": \\"left: Npx; width: Npx; top: Npx; bottom: auto; display: block;\\", - \\"class\\": \\"select2-drop select2-display-none select2-with-searchbox select2-drop-active\\" - } - }, - { - \\"id\\": 70, - \\"attributes\\": { - \\"style\\": \\"\\" - } - }, - { - \\"id\\": 42, - \\"attributes\\": { - \\"class\\": \\"select2-input select2-focused\\", - \\"aria-activedescendant\\": \\"select2-result-label-2\\" - } - }, - { - \\"id\\": 35, - \\"attributes\\": { - \\"disabled\\": \\"\\" - } - }, - { - \\"id\\": 72, - \\"attributes\\": { - \\"class\\": \\"select2-results-dept-0 select2-result select2-result-selectable select2-highlighted\\" - } - } - ], - \\"removes\\": [ - { - \\"parentId\\": 25, - \\"id\\": 26 - }, - { - \\"parentId\\": 25, - \\"id\\": 36 - }, - { - \\"parentId\\": 45, - \\"id\\": 46 - } - ], + \\"attributes\\": [], + \\"removes\\": [], \\"adds\\": [ { - \\"parentId\\": 25, - \\"nextId\\": 34, + \\"parentId\\": 73, + \\"nextId\\": null, \\"node\\": { \\"type\\": 2, - \\"tagName\\": \\"a\\", + \\"tagName\\": \\"iframe\\", \\"attributes\\": { - \\"href\\": \\"javascript:void(0)\\", - \\"class\\": \\"select2-choice\\", - \\"tabindex\\": \\"-1\\" + \\"id\\": \\"five\\" }, \\"childNodes\\": [], - \\"id\\": 26 - } - }, - { - \\"parentId\\": 26, - \\"nextId\\": 28, - \\"node\\": { - \\"type\\": 3, - \\"textContent\\": \\" \\", - \\"id\\": 27 + \\"rootId\\": 60, + \\"id\\": 78 } - }, + } + ] + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 0, + \\"adds\\": [ { - \\"parentId\\": 26, - \\"nextId\\": 30, + \\"parentId\\": 78, + \\"nextId\\": null, \\"node\\": { - \\"type\\": 2, - \\"tagName\\": \\"span\\", - \\"attributes\\": { - \\"class\\": \\"select2-chosen\\", - \\"id\\": \\"select2-chosen-1\\" - }, - \\"childNodes\\": [], - \\"id\\": 28 - } - }, - { - \\"parentId\\": 28, - \\"nextId\\": null, - \\"node\\": { - \\"type\\": 3, - \\"textContent\\": \\"A\\", - \\"id\\": 29 - } - }, - { - \\"parentId\\": 26, - \\"nextId\\": 31, - \\"node\\": { - \\"type\\": 2, - \\"tagName\\": \\"abbr\\", - \\"attributes\\": { - \\"class\\": \\"select2-search-choice-close\\" - }, - \\"childNodes\\": [], - \\"id\\": 30 - } - }, - { - \\"parentId\\": 26, - \\"nextId\\": 32, - \\"node\\": { - \\"type\\": 3, - \\"textContent\\": \\" \\", - \\"id\\": 31 - } - }, - { - \\"parentId\\": 26, - \\"nextId\\": null, - \\"node\\": { - \\"type\\": 2, - \\"tagName\\": \\"span\\", - \\"attributes\\": { - \\"class\\": \\"select2-arrow\\", - \\"role\\": \\"presentation\\" - }, - \\"childNodes\\": [], - \\"id\\": 32 + \\"type\\": 0, + \\"childNodes\\": [ + { + \\"type\\": 2, + \\"tagName\\": \\"html\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 2, + \\"tagName\\": \\"head\\", + \\"attributes\\": {}, + \\"childNodes\\": [], + \\"rootId\\": 79, + \\"id\\": 81 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"body\\", + \\"attributes\\": {}, + \\"childNodes\\": [], + \\"rootId\\": 79, + \\"id\\": 82 + } + ], + \\"rootId\\": 79, + \\"id\\": 80 + } + ], + \\"compatMode\\": \\"BackCompat\\", + \\"id\\": 79 } - }, - { - \\"parentId\\": 32, - \\"nextId\\": null, - \\"node\\": { + } + ], + \\"removes\\": [], + \\"texts\\": [], + \\"attributes\\": [], + \\"isAttachIframe\\": true + } + } +]" +`; + +exports[`record integration tests should not record blocked elements and its child nodes 1`] = ` +"[ + { + \\"type\\": 0, + \\"data\\": {} + }, + { + \\"type\\": 1, + \\"data\\": {} + }, + { + \\"type\\": 4, + \\"data\\": { + \\"href\\": \\"about:blank\\", + \\"width\\": 1920, + \\"height\\": 1080 + } + }, + { + \\"type\\": 2, + \\"data\\": { + \\"node\\": { + \\"type\\": 0, + \\"childNodes\\": [ + { + \\"type\\": 1, + \\"name\\": \\"html\\", + \\"publicId\\": \\"\\", + \\"systemId\\": \\"\\", + \\"id\\": 2 + }, + { \\"type\\": 2, - \\"tagName\\": \\"b\\", + \\"tagName\\": \\"html\\", \\"attributes\\": { - \\"role\\": \\"presentation\\" + \\"lang\\": \\"en\\" }, - \\"childNodes\\": [], - \\"id\\": 33 + \\"childNodes\\": [ + { + \\"type\\": 2, + \\"tagName\\": \\"head\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 5 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"meta\\", + \\"attributes\\": { + \\"charset\\": \\"UTF-8\\" + }, + \\"childNodes\\": [], + \\"id\\": 6 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 7 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"meta\\", + \\"attributes\\": { + \\"name\\": \\"viewport\\", + \\"content\\": \\"width=device-width, initial-scale=1.0\\" + }, + \\"childNodes\\": [], + \\"id\\": 8 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 9 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"meta\\", + \\"attributes\\": { + \\"http-equiv\\": \\"X-UA-Compatible\\", + \\"content\\": \\"ie=edge\\" + }, + \\"childNodes\\": [], + \\"id\\": 10 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 11 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"title\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"Block record\\", + \\"id\\": 13 + } + ], + \\"id\\": 12 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 14 + } + ], + \\"id\\": 4 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 15 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"body\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 17 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"div\\", + \\"attributes\\": { + \\"class\\": \\"rr-block\\", + \\"rr_width\\": \\"50px\\", + \\"rr_height\\": \\"50px\\" + }, + \\"childNodes\\": [], + \\"id\\": 18 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\\\n \\", + \\"id\\": 19 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"script\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", + \\"id\\": 21 + } + ], + \\"id\\": 20 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\\\n \\\\n\\\\n\\", + \\"id\\": 22 + } + ], + \\"id\\": 16 + } + ], + \\"id\\": 3 } - }, - { - \\"parentId\\": 18, - \\"nextId\\": null, - \\"node\\": { - \\"type\\": 2, - \\"tagName\\": \\"div\\", - \\"attributes\\": { - \\"class\\": \\"select2-drop select2-display-none select2-with-searchbox select2-drop-active\\", - \\"id\\": \\"select2-drop\\", - \\"style\\": \\"left: Npx; width: Npx; top: Npx; bottom: auto; display: block;\\" - }, - \\"childNodes\\": [], - \\"id\\": 36 - } - }, - { - \\"parentId\\": 36, - \\"nextId\\": 38, - \\"node\\": { - \\"type\\": 3, - \\"textContent\\": \\" \\", - \\"id\\": 37 - } - }, - { - \\"parentId\\": 36, - \\"nextId\\": 44, - \\"node\\": { + ], + \\"id\\": 1 + }, + \\"initialOffset\\": { + \\"left\\": 0, + \\"top\\": 0 + } + } + } +]" +`; + +exports[`record integration tests should not record blocked elements dynamically added 1`] = ` +"[ + { + \\"type\\": 0, + \\"data\\": {} + }, + { + \\"type\\": 1, + \\"data\\": {} + }, + { + \\"type\\": 4, + \\"data\\": { + \\"href\\": \\"about:blank\\", + \\"width\\": 1920, + \\"height\\": 1080 + } + }, + { + \\"type\\": 2, + \\"data\\": { + \\"node\\": { + \\"type\\": 0, + \\"childNodes\\": [ + { + \\"type\\": 1, + \\"name\\": \\"html\\", + \\"publicId\\": \\"\\", + \\"systemId\\": \\"\\", + \\"id\\": 2 + }, + { \\"type\\": 2, - \\"tagName\\": \\"div\\", + \\"tagName\\": \\"html\\", \\"attributes\\": { - \\"class\\": \\"select2-search\\" + \\"lang\\": \\"en\\" }, - \\"childNodes\\": [], - \\"id\\": 38 - } - }, - { - \\"parentId\\": 38, - \\"nextId\\": 40, - \\"node\\": { - \\"type\\": 3, - \\"textContent\\": \\" \\", - \\"id\\": 39 + \\"childNodes\\": [ + { + \\"type\\": 2, + \\"tagName\\": \\"head\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 5 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"meta\\", + \\"attributes\\": { + \\"charset\\": \\"UTF-8\\" + }, + \\"childNodes\\": [], + \\"id\\": 6 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 7 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"meta\\", + \\"attributes\\": { + \\"name\\": \\"viewport\\", + \\"content\\": \\"width=device-width, initial-scale=1.0\\" + }, + \\"childNodes\\": [], + \\"id\\": 8 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 9 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"meta\\", + \\"attributes\\": { + \\"http-equiv\\": \\"X-UA-Compatible\\", + \\"content\\": \\"ie=edge\\" + }, + \\"childNodes\\": [], + \\"id\\": 10 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 11 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"title\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"Block record\\", + \\"id\\": 13 + } + ], + \\"id\\": 12 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 14 + } + ], + \\"id\\": 4 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 15 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"body\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 17 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"div\\", + \\"attributes\\": { + \\"class\\": \\"rr-block\\", + \\"rr_width\\": \\"50px\\", + \\"rr_height\\": \\"50px\\" + }, + \\"childNodes\\": [], + \\"id\\": 18 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\\\n \\", + \\"id\\": 19 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"script\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", + \\"id\\": 21 + } + ], + \\"id\\": 20 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\\\n \\\\n\\\\n\\", + \\"id\\": 22 + } + ], + \\"id\\": 16 + } + ], + \\"id\\": 3 } - }, + ], + \\"id\\": 1 + }, + \\"initialOffset\\": { + \\"left\\": 0, + \\"top\\": 0 + } + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 0, + \\"texts\\": [], + \\"attributes\\": [], + \\"removes\\": [], + \\"adds\\": [ { - \\"parentId\\": 38, - \\"nextId\\": 41, + \\"parentId\\": 16, + \\"nextId\\": 18, \\"node\\": { \\"type\\": 2, - \\"tagName\\": \\"label\\", + \\"tagName\\": \\"button\\", \\"attributes\\": { - \\"for\\": \\"s2id_autogen1_search\\", - \\"class\\": \\"select2-offscreen\\" + \\"class\\": \\"rr-block\\", + \\"rr_width\\": \\"100px\\", + \\"rr_height\\": \\"100px\\" }, \\"childNodes\\": [], - \\"id\\": 40 - } - }, - { - \\"parentId\\": 38, - \\"nextId\\": 42, - \\"node\\": { - \\"type\\": 3, - \\"textContent\\": \\" \\", - \\"id\\": 41 + \\"id\\": 23 } - }, - { - \\"parentId\\": 38, - \\"nextId\\": 43, - \\"node\\": { - \\"type\\": 2, - \\"tagName\\": \\"input\\", - \\"attributes\\": { - \\"type\\": \\"text\\", - \\"autocomplete\\": \\"off\\", - \\"autocorrect\\": \\"off\\", - \\"autocapitalize\\": \\"off\\", - \\"spellcheck\\": \\"false\\", - \\"class\\": \\"select2-input select2-focused\\", - \\"role\\": \\"combobox\\", - \\"aria-expanded\\": \\"true\\", - \\"aria-autocomplete\\": \\"list\\", - \\"aria-owns\\": \\"select2-results-1\\", - \\"id\\": \\"s2id_autogen1_search\\", - \\"placeholder\\": \\"\\", - \\"aria-activedescendant\\": \\"select2-result-label-2\\" - }, - \\"childNodes\\": [], - \\"id\\": 42 - } - }, - { - \\"parentId\\": 38, - \\"nextId\\": null, - \\"node\\": { - \\"type\\": 3, - \\"textContent\\": \\" \\", - \\"id\\": 43 - } - }, - { - \\"parentId\\": 36, - \\"nextId\\": 45, - \\"node\\": { - \\"type\\": 3, - \\"textContent\\": \\" \\", - \\"id\\": 44 - } - }, - { - \\"parentId\\": 36, - \\"nextId\\": null, - \\"node\\": { + } + ] + } + } +]" +`; + +exports[`record integration tests should not record input events on ignored elements 1`] = ` +"[ + { + \\"type\\": 0, + \\"data\\": {} + }, + { + \\"type\\": 1, + \\"data\\": {} + }, + { + \\"type\\": 4, + \\"data\\": { + \\"href\\": \\"about:blank\\", + \\"width\\": 1920, + \\"height\\": 1080 + } + }, + { + \\"type\\": 2, + \\"data\\": { + \\"node\\": { + \\"type\\": 0, + \\"childNodes\\": [ + { + \\"type\\": 1, + \\"name\\": \\"html\\", + \\"publicId\\": \\"\\", + \\"systemId\\": \\"\\", + \\"id\\": 2 + }, + { \\"type\\": 2, - \\"tagName\\": \\"ul\\", + \\"tagName\\": \\"html\\", \\"attributes\\": { - \\"class\\": \\"select2-results\\", - \\"role\\": \\"listbox\\", - \\"id\\": \\"select2-results-1\\" + \\"lang\\": \\"en\\" }, - \\"childNodes\\": [], - \\"id\\": 45 + \\"childNodes\\": [ + { + \\"type\\": 2, + \\"tagName\\": \\"head\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 5 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"meta\\", + \\"attributes\\": { + \\"charset\\": \\"UTF-8\\" + }, + \\"childNodes\\": [], + \\"id\\": 6 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 7 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"meta\\", + \\"attributes\\": { + \\"name\\": \\"viewport\\", + \\"content\\": \\"width=device-width, initial-scale=1.0\\" + }, + \\"childNodes\\": [], + \\"id\\": 8 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 9 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"meta\\", + \\"attributes\\": { + \\"http-equiv\\": \\"X-UA-Compatible\\", + \\"content\\": \\"ie=edge\\" + }, + \\"childNodes\\": [], + \\"id\\": 10 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 11 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"title\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"ignore fields\\", + \\"id\\": 13 + } + ], + \\"id\\": 12 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 14 + } + ], + \\"id\\": 4 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n\\\\n \\", + \\"id\\": 15 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"body\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 17 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"form\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 19 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"label\\", + \\"attributes\\": { + \\"for\\": \\"ignore text\\" + }, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\" \\", + \\"id\\": 21 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"input\\", + \\"attributes\\": { + \\"type\\": \\"text\\", + \\"class\\": \\"rr-ignore\\" + }, + \\"childNodes\\": [], + \\"id\\": 22 + }, + { + \\"type\\": 3, + \\"textContent\\": \\" \\", + \\"id\\": 23 + } + ], + \\"id\\": 20 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 24 + } + ], + \\"id\\": 18 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\\\n \\", + \\"id\\": 25 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"script\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", + \\"id\\": 27 + } + ], + \\"id\\": 26 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\\\n \\\\n\\\\n\\", + \\"id\\": 28 + } + ], + \\"id\\": 16 + } + ], + \\"id\\": 3 } - }, - { - \\"parentId\\": 45, - \\"nextId\\": null, - \\"node\\": { - \\"type\\": 2, - \\"tagName\\": \\"li\\", + ], + \\"id\\": 1 + }, + \\"initialOffset\\": { + \\"left\\": 0, + \\"top\\": 0 + } + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 2, + \\"type\\": 5, + \\"id\\": 22 + } + } +]" +`; + +exports[`record integration tests should not record input values if maskAllInputs is enabled 1`] = ` +"[ + { + \\"type\\": 0, + \\"data\\": {} + }, + { + \\"type\\": 1, + \\"data\\": {} + }, + { + \\"type\\": 4, + \\"data\\": { + \\"href\\": \\"about:blank\\", + \\"width\\": 1920, + \\"height\\": 1080 + } + }, + { + \\"type\\": 2, + \\"data\\": { + \\"node\\": { + \\"type\\": 0, + \\"childNodes\\": [ + { + \\"type\\": 1, + \\"name\\": \\"html\\", + \\"publicId\\": \\"\\", + \\"systemId\\": \\"\\", + \\"id\\": 2 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"html\\", \\"attributes\\": { - \\"class\\": \\"select2-results-dept-0 select2-result select2-result-selectable\\", - \\"role\\": \\"presentation\\" + \\"lang\\": \\"en\\" }, - \\"childNodes\\": [], - \\"id\\": 67 - } - }, - { - \\"parentId\\": 67, - \\"nextId\\": null, - \\"node\\": { + \\"childNodes\\": [ + { + \\"type\\": 2, + \\"tagName\\": \\"head\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 5 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"meta\\", + \\"attributes\\": { + \\"charset\\": \\"UTF-8\\" + }, + \\"childNodes\\": [], + \\"id\\": 6 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 7 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"meta\\", + \\"attributes\\": { + \\"name\\": \\"viewport\\", + \\"content\\": \\"width=device-width, initial-scale=1.0\\" + }, + \\"childNodes\\": [], + \\"id\\": 8 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 9 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"meta\\", + \\"attributes\\": { + \\"http-equiv\\": \\"X-UA-Compatible\\", + \\"content\\": \\"ie=edge\\" + }, + \\"childNodes\\": [], + \\"id\\": 10 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 11 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"title\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"form fields\\", + \\"id\\": 13 + } + ], + \\"id\\": 12 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 14 + } + ], + \\"id\\": 4 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n\\\\n \\", + \\"id\\": 15 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"body\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 17 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"form\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 19 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"label\\", + \\"attributes\\": { + \\"for\\": \\"text\\" + }, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 21 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"input\\", + \\"attributes\\": { + \\"type\\": \\"text\\" + }, + \\"childNodes\\": [], + \\"id\\": 22 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 23 + } + ], + \\"id\\": 20 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 24 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"label\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 26 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"input\\", + \\"attributes\\": { + \\"type\\": \\"radio\\", + \\"name\\": \\"toggle\\", + \\"value\\": \\"on\\" + }, + \\"childNodes\\": [], + \\"id\\": 27 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 28 + } + ], + \\"id\\": 25 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 29 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"label\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 31 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"input\\", + \\"attributes\\": { + \\"type\\": \\"radio\\", + \\"name\\": \\"toggle\\", + \\"value\\": \\"off\\", + \\"checked\\": true + }, + \\"childNodes\\": [], + \\"id\\": 32 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 33 + } + ], + \\"id\\": 30 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 34 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"label\\", + \\"attributes\\": { + \\"for\\": \\"checkbox\\" + }, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 36 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"input\\", + \\"attributes\\": { + \\"type\\": \\"checkbox\\" + }, + \\"childNodes\\": [], + \\"id\\": 37 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 38 + } + ], + \\"id\\": 35 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 39 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"label\\", + \\"attributes\\": { + \\"for\\": \\"textarea\\" + }, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 41 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"textarea\\", + \\"attributes\\": { + \\"name\\": \\"\\", + \\"id\\": \\"\\", + \\"cols\\": \\"30\\", + \\"rows\\": \\"10\\" + }, + \\"childNodes\\": [], + \\"id\\": 42 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 43 + } + ], + \\"id\\": 40 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 44 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"label\\", + \\"attributes\\": { + \\"for\\": \\"select\\" + }, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 46 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"select\\", + \\"attributes\\": { + \\"name\\": \\"\\", + \\"id\\": \\"\\", + \\"value\\": \\"*\\" + }, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 48 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"option\\", + \\"attributes\\": { + \\"value\\": \\"1\\" + }, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"1\\", + \\"id\\": 50 + } + ], + \\"id\\": 49 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 51 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"option\\", + \\"attributes\\": { + \\"value\\": \\"2\\" + }, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"2\\", + \\"id\\": 53 + } + ], + \\"id\\": 52 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 54 + } + ], + \\"id\\": 47 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 55 + } + ], + \\"id\\": 45 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 56 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"label\\", + \\"attributes\\": { + \\"for\\": \\"password\\" + }, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 58 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"input\\", + \\"attributes\\": { + \\"type\\": \\"password\\" + }, + \\"childNodes\\": [], + \\"id\\": 59 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 60 + } + ], + \\"id\\": 57 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 61 + } + ], + \\"id\\": 18 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\\\n \\", + \\"id\\": 62 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"script\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", + \\"id\\": 64 + } + ], + \\"id\\": 63 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\\\n \\\\n\\\\n\\", + \\"id\\": 65 + } + ], + \\"id\\": 16 + } + ], + \\"id\\": 3 + } + ], + \\"id\\": 1 + }, + \\"initialOffset\\": { + \\"left\\": 0, + \\"top\\": 0 + } + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 2, + \\"type\\": 5, + \\"id\\": 22 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 5, + \\"text\\": \\"*\\", + \\"isChecked\\": false, + \\"id\\": 22 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 5, + \\"text\\": \\"**\\", + \\"isChecked\\": false, + \\"id\\": 22 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 5, + \\"text\\": \\"***\\", + \\"isChecked\\": false, + \\"id\\": 22 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 5, + \\"text\\": \\"****\\", + \\"isChecked\\": false, + \\"id\\": 22 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 2, + \\"type\\": 1, + \\"id\\": 27 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 2, + \\"type\\": 6, + \\"id\\": 22 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 2, + \\"type\\": 5, + \\"id\\": 27 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 2, + \\"type\\": 0, + \\"id\\": 27 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 2, + \\"type\\": 2, + \\"id\\": 27 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 5, + \\"text\\": \\"on\\", + \\"isChecked\\": true, + \\"id\\": 27 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 5, + \\"text\\": \\"off\\", + \\"isChecked\\": false, + \\"id\\": 32 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 2, + \\"type\\": 1, + \\"id\\": 37 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 2, + \\"type\\": 6, + \\"id\\": 27 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 2, + \\"type\\": 5, + \\"id\\": 37 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 2, + \\"type\\": 0, + \\"id\\": 37 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 2, + \\"type\\": 2, + \\"id\\": 37 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 5, + \\"text\\": \\"on\\", + \\"isChecked\\": true, + \\"id\\": 37 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 2, + \\"type\\": 6, + \\"id\\": 37 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 2, + \\"type\\": 5, + \\"id\\": 59 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 5, + \\"text\\": \\"*\\", + \\"isChecked\\": false, + \\"id\\": 59 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 5, + \\"text\\": \\"**\\", + \\"isChecked\\": false, + \\"id\\": 59 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 5, + \\"text\\": \\"***\\", + \\"isChecked\\": false, + \\"id\\": 59 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 5, + \\"text\\": \\"****\\", + \\"isChecked\\": false, + \\"id\\": 59 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 5, + \\"text\\": \\"*****\\", + \\"isChecked\\": false, + \\"id\\": 59 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 5, + \\"text\\": \\"******\\", + \\"isChecked\\": false, + \\"id\\": 59 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 5, + \\"text\\": \\"*******\\", + \\"isChecked\\": false, + \\"id\\": 59 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 5, + \\"text\\": \\"********\\", + \\"isChecked\\": false, + \\"id\\": 59 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 2, + \\"type\\": 6, + \\"id\\": 59 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 2, + \\"type\\": 5, + \\"id\\": 42 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 5, + \\"text\\": \\"*\\", + \\"isChecked\\": false, + \\"id\\": 42 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 5, + \\"text\\": \\"**\\", + \\"isChecked\\": false, + \\"id\\": 42 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 5, + \\"text\\": \\"***\\", + \\"isChecked\\": false, + \\"id\\": 42 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 5, + \\"text\\": \\"****\\", + \\"isChecked\\": false, + \\"id\\": 42 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 5, + \\"text\\": \\"*****\\", + \\"isChecked\\": false, + \\"id\\": 42 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 5, + \\"text\\": \\"******\\", + \\"isChecked\\": false, + \\"id\\": 42 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 5, + \\"text\\": \\"*******\\", + \\"isChecked\\": false, + \\"id\\": 42 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 5, + \\"text\\": \\"********\\", + \\"isChecked\\": false, + \\"id\\": 42 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 5, + \\"text\\": \\"*********\\", + \\"isChecked\\": false, + \\"id\\": 42 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 5, + \\"text\\": \\"**********\\", + \\"isChecked\\": false, + \\"id\\": 42 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 5, + \\"text\\": \\"***********\\", + \\"isChecked\\": false, + \\"id\\": 42 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 5, + \\"text\\": \\"************\\", + \\"isChecked\\": false, + \\"id\\": 42 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 5, + \\"text\\": \\"*************\\", + \\"isChecked\\": false, + \\"id\\": 42 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 5, + \\"text\\": \\"*\\", + \\"isChecked\\": false, + \\"id\\": 47 + } + } +]" +`; + +exports[`record integration tests should record DOM node movement 1 1`] = ` +"[ + { + \\"type\\": 0, + \\"data\\": {} + }, + { + \\"type\\": 1, + \\"data\\": {} + }, + { + \\"type\\": 4, + \\"data\\": { + \\"href\\": \\"about:blank\\", + \\"width\\": 1920, + \\"height\\": 1080 + } + }, + { + \\"type\\": 2, + \\"data\\": { + \\"node\\": { + \\"type\\": 0, + \\"childNodes\\": [ + { + \\"type\\": 1, + \\"name\\": \\"html\\", + \\"publicId\\": \\"\\", + \\"systemId\\": \\"\\", + \\"id\\": 2 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"html\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 2, + \\"tagName\\": \\"head\\", + \\"attributes\\": {}, + \\"childNodes\\": [], + \\"id\\": 4 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"body\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 6 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"div\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 8 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"p\\", + \\"attributes\\": {}, + \\"childNodes\\": [], + \\"id\\": 9 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 10 + } + ], + \\"id\\": 7 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 11 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"span\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 13 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"i\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 15 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"b\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"1\\", + \\"id\\": 17 + } + ], + \\"id\\": 16 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 18 + } + ], + \\"id\\": 14 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 19 + } + ], + \\"id\\": 12 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\\\n \\", + \\"id\\": 20 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"script\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", + \\"id\\": 22 + } + ], + \\"id\\": 21 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\\\n \\\\n\\\\n\\", + \\"id\\": 23 + } + ], + \\"id\\": 5 + } + ], + \\"id\\": 3 + } + ], + \\"id\\": 1 + }, + \\"initialOffset\\": { + \\"left\\": 0, + \\"top\\": 0 + } + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 0, + \\"texts\\": [], + \\"attributes\\": [], + \\"removes\\": [ + { + \\"parentId\\": 5, + \\"id\\": 12 + } + ], + \\"adds\\": [ + { + \\"parentId\\": 7, + \\"nextId\\": null, + \\"node\\": { + \\"type\\": 2, + \\"tagName\\": \\"span\\", + \\"attributes\\": {}, + \\"childNodes\\": [], + \\"id\\": 12 + } + }, + { + \\"parentId\\": 12, + \\"nextId\\": 14, + \\"node\\": { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 13 + } + }, + { + \\"parentId\\": 12, + \\"nextId\\": 19, + \\"node\\": { + \\"type\\": 2, + \\"tagName\\": \\"i\\", + \\"attributes\\": {}, + \\"childNodes\\": [], + \\"id\\": 14 + } + }, + { + \\"parentId\\": 14, + \\"nextId\\": 16, + \\"node\\": { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 15 + } + }, + { + \\"parentId\\": 14, + \\"nextId\\": 18, + \\"node\\": { + \\"type\\": 2, + \\"tagName\\": \\"b\\", + \\"attributes\\": {}, + \\"childNodes\\": [], + \\"id\\": 16 + } + }, + { + \\"parentId\\": 16, + \\"nextId\\": null, + \\"node\\": { + \\"type\\": 3, + \\"textContent\\": \\"1\\", + \\"id\\": 17 + } + }, + { + \\"parentId\\": 14, + \\"nextId\\": null, + \\"node\\": { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 18 + } + }, + { + \\"parentId\\": 12, + \\"nextId\\": null, + \\"node\\": { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 19 + } + } + ] + } + } +]" +`; + +exports[`record integration tests should record DOM node movement 2 1`] = ` +"[ + { + \\"type\\": 0, + \\"data\\": {} + }, + { + \\"type\\": 1, + \\"data\\": {} + }, + { + \\"type\\": 4, + \\"data\\": { + \\"href\\": \\"about:blank\\", + \\"width\\": 1920, + \\"height\\": 1080 + } + }, + { + \\"type\\": 2, + \\"data\\": { + \\"node\\": { + \\"type\\": 0, + \\"childNodes\\": [ + { + \\"type\\": 1, + \\"name\\": \\"html\\", + \\"publicId\\": \\"\\", + \\"systemId\\": \\"\\", + \\"id\\": 2 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"html\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 2, + \\"tagName\\": \\"head\\", + \\"attributes\\": {}, + \\"childNodes\\": [], + \\"id\\": 4 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"body\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 6 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"div\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 8 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"p\\", + \\"attributes\\": {}, + \\"childNodes\\": [], + \\"id\\": 9 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 10 + } + ], + \\"id\\": 7 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 11 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"span\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 13 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"i\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 15 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"b\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"1\\", + \\"id\\": 17 + } + ], + \\"id\\": 16 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 18 + } + ], + \\"id\\": 14 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 19 + } + ], + \\"id\\": 12 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\\\n \\", + \\"id\\": 20 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"script\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", + \\"id\\": 22 + } + ], + \\"id\\": 21 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\\\n \\\\n\\\\n\\", + \\"id\\": 23 + } + ], + \\"id\\": 5 + } + ], + \\"id\\": 3 + } + ], + \\"id\\": 1 + }, + \\"initialOffset\\": { + \\"left\\": 0, + \\"top\\": 0 + } + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 0, + \\"texts\\": [], + \\"attributes\\": [], + \\"removes\\": [ + { + \\"parentId\\": 5, + \\"id\\": 12 + } + ], + \\"adds\\": [ + { + \\"parentId\\": 12, + \\"nextId\\": 14, + \\"node\\": { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 13 + } + }, + { + \\"parentId\\": 12, + \\"nextId\\": 19, + \\"node\\": { + \\"type\\": 2, + \\"tagName\\": \\"i\\", + \\"attributes\\": {}, + \\"childNodes\\": [], + \\"id\\": 14 + } + }, + { + \\"parentId\\": 14, + \\"nextId\\": 16, + \\"node\\": { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 15 + } + }, + { + \\"parentId\\": 14, + \\"nextId\\": 18, + \\"node\\": { + \\"type\\": 2, + \\"tagName\\": \\"b\\", + \\"attributes\\": {}, + \\"childNodes\\": [], + \\"id\\": 16 + } + }, + { + \\"parentId\\": 16, + \\"nextId\\": null, + \\"node\\": { + \\"type\\": 3, + \\"textContent\\": \\"1\\", + \\"id\\": 17 + } + }, + { + \\"parentId\\": 14, + \\"nextId\\": null, + \\"node\\": { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 18 + } + }, + { + \\"parentId\\": 12, + \\"nextId\\": null, + \\"node\\": { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 19 + } + }, + { + \\"parentId\\": 5, + \\"nextId\\": null, + \\"node\\": { + \\"type\\": 2, + \\"tagName\\": \\"div\\", + \\"attributes\\": {}, + \\"childNodes\\": [], + \\"id\\": 24 + } + }, + { + \\"parentId\\": 24, + \\"nextId\\": null, + \\"node\\": { + \\"type\\": 2, + \\"tagName\\": \\"span\\", + \\"attributes\\": {}, + \\"childNodes\\": [], + \\"id\\": 12 + } + } + ] + } + } +]" +`; + +exports[`record integration tests should record canvas mutations 1`] = ` +"[ + { + \\"type\\": 0, + \\"data\\": {} + }, + { + \\"type\\": 1, + \\"data\\": {} + }, + { + \\"type\\": 4, + \\"data\\": { + \\"href\\": \\"about:blank\\", + \\"width\\": 1920, + \\"height\\": 1080 + } + }, + { + \\"type\\": 2, + \\"data\\": { + \\"node\\": { + \\"type\\": 0, + \\"childNodes\\": [ + { + \\"type\\": 1, + \\"name\\": \\"html\\", + \\"publicId\\": \\"\\", + \\"systemId\\": \\"\\", + \\"id\\": 2 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"html\\", + \\"attributes\\": { + \\"lang\\": \\"en\\" + }, + \\"childNodes\\": [ + { + \\"type\\": 2, + \\"tagName\\": \\"head\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 5 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"meta\\", + \\"attributes\\": { + \\"charset\\": \\"UTF-8\\" + }, + \\"childNodes\\": [], + \\"id\\": 6 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 7 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"meta\\", + \\"attributes\\": { + \\"name\\": \\"viewport\\", + \\"content\\": \\"width=device-width, initial-scale=1.0\\" + }, + \\"childNodes\\": [], + \\"id\\": 8 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 9 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"title\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"canvas\\", + \\"id\\": 11 + } + ], + \\"id\\": 10 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 12 + } + ], + \\"id\\": 4 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 13 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"body\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 15 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"canvas\\", + \\"attributes\\": { + \\"id\\": \\"myCanvas\\", + \\"width\\": \\"200\\", + \\"height\\": \\"100\\", + \\"style\\": \\"border: 1px solid #000000;\\", + \\"rr_dataURL\\": \\"LOOKS LIKE WE COULD NOT GET STABLE BASE64 FROM SAME IMAGE.\\" + }, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 17 + } + ], + \\"id\\": 16 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 18 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"script\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", + \\"id\\": 20 + } + ], + \\"id\\": 19 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\\\n \\", + \\"id\\": 21 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"script\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", + \\"id\\": 23 + } + ], + \\"id\\": 22 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\\\n \\\\n\\\\n\\", + \\"id\\": 24 + } + ], + \\"id\\": 14 + } + ], + \\"id\\": 3 + } + ], + \\"id\\": 1 + }, + \\"initialOffset\\": { + \\"left\\": 0, + \\"top\\": 0 + } + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 9, + \\"id\\": 16, + \\"type\\": 0, + \\"commands\\": [ + { + \\"property\\": \\"moveTo\\", + \\"args\\": [ + 0, + 0 + ] + }, + { + \\"property\\": \\"lineTo\\", + \\"args\\": [ + 200, + 100 + ] + }, + { + \\"property\\": \\"stroke\\", + \\"args\\": [] + } + ] + } + } +]" +`; + +exports[`record integration tests should record console messages 1`] = ` +"[ + { + \\"type\\": 0, + \\"data\\": {} + }, + { + \\"type\\": 1, + \\"data\\": {} + }, + { + \\"type\\": 4, + \\"data\\": { + \\"href\\": \\"about:blank\\", + \\"width\\": 1920, + \\"height\\": 1080 + } + }, + { + \\"type\\": 2, + \\"data\\": { + \\"node\\": { + \\"type\\": 0, + \\"childNodes\\": [ + { + \\"type\\": 1, + \\"name\\": \\"html\\", + \\"publicId\\": \\"\\", + \\"systemId\\": \\"\\", + \\"id\\": 2 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"html\\", + \\"attributes\\": { + \\"lang\\": \\"en\\" + }, + \\"childNodes\\": [ + { + \\"type\\": 2, + \\"tagName\\": \\"head\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 5 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"meta\\", + \\"attributes\\": { + \\"charset\\": \\"UTF-8\\" + }, + \\"childNodes\\": [], + \\"id\\": 6 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 7 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"meta\\", + \\"attributes\\": { + \\"name\\": \\"viewport\\", + \\"content\\": \\"width=device-width, initial-scale=1.0\\" + }, + \\"childNodes\\": [], + \\"id\\": 8 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 9 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"meta\\", + \\"attributes\\": { + \\"http-equiv\\": \\"X-UA-Compatible\\", + \\"content\\": \\"ie=edge\\" + }, + \\"childNodes\\": [], + \\"id\\": 10 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 11 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"title\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"Log record\\", + \\"id\\": 13 + } + ], + \\"id\\": 12 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 14 + } + ], + \\"id\\": 4 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 15 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"body\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 17 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"script\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", + \\"id\\": 19 + } + ], + \\"id\\": 18 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\\\n \\\\n\\\\n\\", + \\"id\\": 20 + } + ], + \\"id\\": 16 + } + ], + \\"id\\": 3 + } + ], + \\"id\\": 1 + }, + \\"initialOffset\\": { + \\"left\\": 0, + \\"top\\": 0 + } + } + }, + { + \\"type\\": 6, + \\"data\\": { + \\"plugin\\": \\"rrweb/console@1\\", + \\"payload\\": { + \\"level\\": \\"assert\\", + \\"trace\\": [ + \\"__puppeteer_evaluation_script__:2:37\\" + ], + \\"payload\\": [ + \\"true\\", + \\"\\\\\\"assert\\\\\\"\\" + ] + } + } + }, + { + \\"type\\": 6, + \\"data\\": { + \\"plugin\\": \\"rrweb/console@1\\", + \\"payload\\": { + \\"level\\": \\"count\\", + \\"trace\\": [ + \\"__puppeteer_evaluation_script__:3:37\\" + ], + \\"payload\\": [ + \\"\\\\\\"count\\\\\\"\\" + ] + } + } + }, + { + \\"type\\": 6, + \\"data\\": { + \\"plugin\\": \\"rrweb/console@1\\", + \\"payload\\": { + \\"level\\": \\"countReset\\", + \\"trace\\": [ + \\"__puppeteer_evaluation_script__:4:37\\" + ], + \\"payload\\": [ + \\"\\\\\\"count\\\\\\"\\" + ] + } + } + }, + { + \\"type\\": 6, + \\"data\\": { + \\"plugin\\": \\"rrweb/console@1\\", + \\"payload\\": { + \\"level\\": \\"debug\\", + \\"trace\\": [ + \\"__puppeteer_evaluation_script__:5:37\\" + ], + \\"payload\\": [ + \\"\\\\\\"debug\\\\\\"\\" + ] + } + } + }, + { + \\"type\\": 6, + \\"data\\": { + \\"plugin\\": \\"rrweb/console@1\\", + \\"payload\\": { + \\"level\\": \\"dir\\", + \\"trace\\": [ + \\"__puppeteer_evaluation_script__:6:37\\" + ], + \\"payload\\": [ + \\"\\\\\\"dir\\\\\\"\\" + ] + } + } + }, + { + \\"type\\": 6, + \\"data\\": { + \\"plugin\\": \\"rrweb/console@1\\", + \\"payload\\": { + \\"level\\": \\"dirxml\\", + \\"trace\\": [ + \\"__puppeteer_evaluation_script__:7:37\\" + ], + \\"payload\\": [ + \\"\\\\\\"dirxml\\\\\\"\\" + ] + } + } + }, + { + \\"type\\": 6, + \\"data\\": { + \\"plugin\\": \\"rrweb/console@1\\", + \\"payload\\": { + \\"level\\": \\"group\\", + \\"trace\\": [ + \\"__puppeteer_evaluation_script__:8:37\\" + ], + \\"payload\\": [] + } + } + }, + { + \\"type\\": 6, + \\"data\\": { + \\"plugin\\": \\"rrweb/console@1\\", + \\"payload\\": { + \\"level\\": \\"groupCollapsed\\", + \\"trace\\": [ + \\"__puppeteer_evaluation_script__:9:37\\" + ], + \\"payload\\": [] + } + } + }, + { + \\"type\\": 6, + \\"data\\": { + \\"plugin\\": \\"rrweb/console@1\\", + \\"payload\\": { + \\"level\\": \\"info\\", + \\"trace\\": [ + \\"__puppeteer_evaluation_script__:10:37\\" + ], + \\"payload\\": [ + \\"\\\\\\"info\\\\\\"\\" + ] + } + } + }, + { + \\"type\\": 6, + \\"data\\": { + \\"plugin\\": \\"rrweb/console@1\\", + \\"payload\\": { + \\"level\\": \\"log\\", + \\"trace\\": [ + \\"__puppeteer_evaluation_script__:11:37\\" + ], + \\"payload\\": [ + \\"\\\\\\"log\\\\\\"\\" + ] + } + } + }, + { + \\"type\\": 6, + \\"data\\": { + \\"plugin\\": \\"rrweb/console@1\\", + \\"payload\\": { + \\"level\\": \\"table\\", + \\"trace\\": [ + \\"__puppeteer_evaluation_script__:12:37\\" + ], + \\"payload\\": [ + \\"\\\\\\"table\\\\\\"\\" + ] + } + } + }, + { + \\"type\\": 6, + \\"data\\": { + \\"plugin\\": \\"rrweb/console@1\\", + \\"payload\\": { + \\"level\\": \\"time\\", + \\"trace\\": [ + \\"__puppeteer_evaluation_script__:13:37\\" + ], + \\"payload\\": [] + } + } + }, + { + \\"type\\": 6, + \\"data\\": { + \\"plugin\\": \\"rrweb/console@1\\", + \\"payload\\": { + \\"level\\": \\"timeEnd\\", + \\"trace\\": [ + \\"__puppeteer_evaluation_script__:14:37\\" + ], + \\"payload\\": [] + } + } + }, + { + \\"type\\": 6, + \\"data\\": { + \\"plugin\\": \\"rrweb/console@1\\", + \\"payload\\": { + \\"level\\": \\"timeLog\\", + \\"trace\\": [ + \\"__puppeteer_evaluation_script__:15:37\\" + ], + \\"payload\\": [] + } + } + }, + { + \\"type\\": 6, + \\"data\\": { + \\"plugin\\": \\"rrweb/console@1\\", + \\"payload\\": { + \\"level\\": \\"trace\\", + \\"trace\\": [ + \\"__puppeteer_evaluation_script__:16:37\\" + ], + \\"payload\\": [ + \\"\\\\\\"trace\\\\\\"\\" + ] + } + } + }, + { + \\"type\\": 6, + \\"data\\": { + \\"plugin\\": \\"rrweb/console@1\\", + \\"payload\\": { + \\"level\\": \\"warn\\", + \\"trace\\": [ + \\"__puppeteer_evaluation_script__:17:37\\" + ], + \\"payload\\": [ + \\"\\\\\\"warn\\\\\\"\\" + ] + } + } + }, + { + \\"type\\": 6, + \\"data\\": { + \\"plugin\\": \\"rrweb/console@1\\", + \\"payload\\": { + \\"level\\": \\"clear\\", + \\"trace\\": [ + \\"__puppeteer_evaluation_script__:18:37\\" + ], + \\"payload\\": [] + } + } + }, + { + \\"type\\": 6, + \\"data\\": { + \\"plugin\\": \\"rrweb/console@1\\", + \\"payload\\": { + \\"level\\": \\"log\\", + \\"trace\\": [ + \\"__puppeteer_evaluation_script__:19:37\\" + ], + \\"payload\\": [ + \\"\\\\\\"TypeError: a message\\\\\\\\n at __puppeteer_evaluation_script__:19:41\\\\\\\\nEnd of stack for Error object\\\\\\"\\" + ] + } + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 0, + \\"texts\\": [], + \\"attributes\\": [], + \\"removes\\": [], + \\"adds\\": [ + { + \\"parentId\\": 16, + \\"nextId\\": null, + \\"node\\": { + \\"type\\": 2, + \\"tagName\\": \\"iframe\\", + \\"attributes\\": {}, + \\"childNodes\\": [], + \\"id\\": 21 + } + } + ] + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 0, + \\"adds\\": [ + { + \\"parentId\\": 21, + \\"nextId\\": null, + \\"node\\": { + \\"type\\": 0, + \\"childNodes\\": [ + { + \\"type\\": 2, + \\"tagName\\": \\"html\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 2, + \\"tagName\\": \\"head\\", + \\"attributes\\": {}, + \\"childNodes\\": [], + \\"rootId\\": 22, + \\"id\\": 24 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"body\\", + \\"attributes\\": {}, + \\"childNodes\\": [], + \\"rootId\\": 22, + \\"id\\": 25 + } + ], + \\"rootId\\": 22, + \\"id\\": 23 + } + ], + \\"compatMode\\": \\"BackCompat\\", + \\"id\\": 22 + } + } + ], + \\"removes\\": [], + \\"texts\\": [], + \\"attributes\\": [], + \\"isAttachIframe\\": true + } + }, + { + \\"type\\": 6, + \\"data\\": { + \\"plugin\\": \\"rrweb/console@1\\", + \\"payload\\": { + \\"level\\": \\"log\\", + \\"trace\\": [], + \\"payload\\": [ + \\"\\\\\\"from iframe\\\\\\"\\" + ] + } + } + } +]" +`; + +exports[`record integration tests should record dynamic CSS changes 1`] = ` +"[ + { + \\"type\\": 0, + \\"data\\": {} + }, + { + \\"type\\": 1, + \\"data\\": {} + }, + { + \\"type\\": 4, + \\"data\\": { + \\"href\\": \\"about:blank\\", + \\"width\\": 1920, + \\"height\\": 1080 + } + }, + { + \\"type\\": 2, + \\"data\\": { + \\"node\\": { + \\"type\\": 0, + \\"childNodes\\": [ + { + \\"type\\": 1, + \\"name\\": \\"html\\", + \\"publicId\\": \\"\\", + \\"systemId\\": \\"\\", + \\"id\\": 2 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"html\\", + \\"attributes\\": { + \\"lang\\": \\"en\\" + }, + \\"childNodes\\": [ + { + \\"type\\": 2, + \\"tagName\\": \\"head\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 5 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"meta\\", + \\"attributes\\": { + \\"charset\\": \\"UTF-8\\" + }, + \\"childNodes\\": [], + \\"id\\": 6 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 7 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"meta\\", + \\"attributes\\": { + \\"name\\": \\"viewport\\", + \\"content\\": \\"width=device-width, initial-scale=1.0\\" + }, + \\"childNodes\\": [], + \\"id\\": 8 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 9 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"meta\\", + \\"attributes\\": { + \\"http-equiv\\": \\"X-UA-Compatible\\", + \\"content\\": \\"ie=edge\\" + }, + \\"childNodes\\": [], + \\"id\\": 10 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 11 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"title\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"react styled components\\", + \\"id\\": 13 + } + ], + \\"id\\": 12 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 14 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"script\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", + \\"id\\": 16 + } + ], + \\"id\\": 15 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"style\\", + \\"attributes\\": { + \\"data-styled\\": \\"active\\", + \\"data-styled-version\\": \\"5.0.1\\", + \\"_cssText\\": \\".ixzlRK { font-size: 1.5em; text-align: center; color: palevioletred; }.eOXmez { font-size: 1.5em; text-align: center; color: rebeccapurple; }.bJCmFu { padding: 4em; background: papayawhip; }\\" + }, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"\\", + \\"isStyle\\": true, + \\"id\\": 18 + } + ], + \\"id\\": 17 + } + ], + \\"id\\": 4 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 19 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"body\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 21 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"div\\", + \\"attributes\\": { + \\"id\\": \\"app\\" + }, + \\"childNodes\\": [ + { + \\"type\\": 2, + \\"tagName\\": \\"section\\", + \\"attributes\\": { + \\"class\\": \\"sc-AxirZ bJCmFu\\" + }, + \\"childNodes\\": [ + { + \\"type\\": 2, + \\"tagName\\": \\"h1\\", + \\"attributes\\": { + \\"class\\": \\"sc-AxjAm ixzlRK\\" + }, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"Hello World!\\", + \\"id\\": 25 + } + ], + \\"id\\": 24 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"h1\\", + \\"attributes\\": { + \\"class\\": \\"sc-AxjAm eOXmez toggle\\", + \\"color\\": \\"rebeccapurple\\" + }, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"Hello World!\\", + \\"id\\": 27 + } + ], + \\"id\\": 26 + } + ], + \\"id\\": 23 + } + ], + \\"id\\": 22 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 28 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"script\\", + \\"attributes\\": { + \\"src\\": \\"https://cdn.jsdelivr.net/npm/react@16/umd/react.production.min.js\\" + }, + \\"childNodes\\": [], + \\"id\\": 29 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 30 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"script\\", + \\"attributes\\": { + \\"src\\": \\"https://cdn.jsdelivr.net/npm/react-dom@16/umd/react-dom.production.min.js\\" + }, + \\"childNodes\\": [], + \\"id\\": 31 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 32 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"script\\", + \\"attributes\\": { + \\"src\\": \\"https://cdn.jsdelivr.net/npm/react-is@16.13.1/umd/react-is.production.min.js\\" + }, + \\"childNodes\\": [], + \\"id\\": 33 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 34 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"script\\", + \\"attributes\\": { + \\"src\\": \\"https://cdn.jsdelivr.net/npm/styled-components@5.0.1/dist/styled-components.min.js\\" + }, + \\"childNodes\\": [], + \\"id\\": 35 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 36 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"script\\", + \\"attributes\\": { + \\"src\\": \\"https://cdn.jsdelivr.net/npm/babel-standalone@6/babel.min.js\\" + }, + \\"childNodes\\": [], + \\"id\\": 37 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 38 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"script\\", + \\"attributes\\": { + \\"type\\": \\"text/babel\\" + }, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", + \\"id\\": 40 + } + ], + \\"id\\": 39 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\\\n \\", + \\"id\\": 41 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"script\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", + \\"id\\": 43 + } + ], + \\"id\\": 42 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\\\n \\\\n\\\\n\\", + \\"id\\": 44 + } + ], + \\"id\\": 20 + } + ], + \\"id\\": 3 + } + ], + \\"id\\": 1 + }, + \\"initialOffset\\": { + \\"left\\": 0, + \\"top\\": 0 + } + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 2, + \\"type\\": 1, + \\"id\\": 26 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 2, + \\"type\\": 0, + \\"id\\": 26 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 2, + \\"type\\": 2, + \\"id\\": 26 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 8, + \\"id\\": 17, + \\"adds\\": [ + { + \\"rule\\": \\".pqkNE{font-size:1.5em;text-align:center;color:pink;}\\", + \\"index\\": 2 + } + ] + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 0, + \\"texts\\": [], + \\"attributes\\": [ + { + \\"id\\": 26, + \\"attributes\\": { + \\"class\\": \\"sc-AxjAm pqkNE toggle\\", + \\"color\\": \\"pink\\" + } + } + ], + \\"removes\\": [], + \\"adds\\": [] + } + } +]" +`; + +exports[`record integration tests should record input userTriggered values if userTriggeredOnInput is enabled 1`] = ` +"[ + { + \\"type\\": 0, + \\"data\\": {} + }, + { + \\"type\\": 1, + \\"data\\": {} + }, + { + \\"type\\": 4, + \\"data\\": { + \\"href\\": \\"about:blank\\", + \\"width\\": 1920, + \\"height\\": 1080 + } + }, + { + \\"type\\": 2, + \\"data\\": { + \\"node\\": { + \\"type\\": 0, + \\"childNodes\\": [ + { + \\"type\\": 1, + \\"name\\": \\"html\\", + \\"publicId\\": \\"\\", + \\"systemId\\": \\"\\", + \\"id\\": 2 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"html\\", + \\"attributes\\": { + \\"lang\\": \\"en\\" + }, + \\"childNodes\\": [ + { + \\"type\\": 2, + \\"tagName\\": \\"head\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 5 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"meta\\", + \\"attributes\\": { + \\"charset\\": \\"UTF-8\\" + }, + \\"childNodes\\": [], + \\"id\\": 6 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 7 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"meta\\", + \\"attributes\\": { + \\"name\\": \\"viewport\\", + \\"content\\": \\"width=device-width, initial-scale=1.0\\" + }, + \\"childNodes\\": [], + \\"id\\": 8 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 9 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"meta\\", + \\"attributes\\": { + \\"http-equiv\\": \\"X-UA-Compatible\\", + \\"content\\": \\"ie=edge\\" + }, + \\"childNodes\\": [], + \\"id\\": 10 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 11 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"title\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"form fields\\", + \\"id\\": 13 + } + ], + \\"id\\": 12 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 14 + } + ], + \\"id\\": 4 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n\\\\n \\", + \\"id\\": 15 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"body\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 17 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"form\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 19 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"label\\", + \\"attributes\\": { + \\"for\\": \\"text\\" + }, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 21 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"input\\", + \\"attributes\\": { + \\"type\\": \\"text\\" + }, + \\"childNodes\\": [], + \\"id\\": 22 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 23 + } + ], + \\"id\\": 20 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 24 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"label\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 26 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"input\\", + \\"attributes\\": { + \\"type\\": \\"radio\\", + \\"name\\": \\"toggle\\", + \\"value\\": \\"on\\" + }, + \\"childNodes\\": [], + \\"id\\": 27 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 28 + } + ], + \\"id\\": 25 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 29 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"label\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 31 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"input\\", + \\"attributes\\": { + \\"type\\": \\"radio\\", + \\"name\\": \\"toggle\\", + \\"value\\": \\"off\\", + \\"checked\\": true + }, + \\"childNodes\\": [], + \\"id\\": 32 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 33 + } + ], + \\"id\\": 30 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 34 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"label\\", + \\"attributes\\": { + \\"for\\": \\"checkbox\\" + }, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 36 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"input\\", + \\"attributes\\": { + \\"type\\": \\"checkbox\\" + }, + \\"childNodes\\": [], + \\"id\\": 37 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 38 + } + ], + \\"id\\": 35 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 39 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"label\\", + \\"attributes\\": { + \\"for\\": \\"textarea\\" + }, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 41 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"textarea\\", + \\"attributes\\": { + \\"name\\": \\"\\", + \\"id\\": \\"\\", + \\"cols\\": \\"30\\", + \\"rows\\": \\"10\\" + }, + \\"childNodes\\": [], + \\"id\\": 42 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 43 + } + ], + \\"id\\": 40 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 44 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"label\\", + \\"attributes\\": { + \\"for\\": \\"select\\" + }, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 46 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"select\\", + \\"attributes\\": { + \\"name\\": \\"\\", + \\"id\\": \\"\\", + \\"value\\": \\"1\\" + }, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 48 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"option\\", + \\"attributes\\": { + \\"value\\": \\"1\\", + \\"selected\\": true + }, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"1\\", + \\"id\\": 50 + } + ], + \\"id\\": 49 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 51 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"option\\", + \\"attributes\\": { + \\"value\\": \\"2\\" + }, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"2\\", + \\"id\\": 53 + } + ], + \\"id\\": 52 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 54 + } + ], + \\"id\\": 47 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 55 + } + ], + \\"id\\": 45 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 56 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"label\\", + \\"attributes\\": { + \\"for\\": \\"password\\" + }, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 58 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"input\\", + \\"attributes\\": { + \\"type\\": \\"password\\" + }, + \\"childNodes\\": [], + \\"id\\": 59 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 60 + } + ], + \\"id\\": 57 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 61 + } + ], + \\"id\\": 18 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\\\n \\", + \\"id\\": 62 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"script\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", + \\"id\\": 64 + } + ], + \\"id\\": 63 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\\\n \\\\n\\\\n\\", + \\"id\\": 65 + } + ], + \\"id\\": 16 + } + ], + \\"id\\": 3 + } + ], + \\"id\\": 1 + }, + \\"initialOffset\\": { + \\"left\\": 0, + \\"top\\": 0 + } + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 2, + \\"type\\": 5, + \\"id\\": 22 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 5, + \\"text\\": \\"t\\", + \\"isChecked\\": false, + \\"userTriggered\\": true, + \\"id\\": 22 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 5, + \\"text\\": \\"te\\", + \\"isChecked\\": false, + \\"userTriggered\\": true, + \\"id\\": 22 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 5, + \\"text\\": \\"tes\\", + \\"isChecked\\": false, + \\"userTriggered\\": true, + \\"id\\": 22 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 5, + \\"text\\": \\"test\\", + \\"isChecked\\": false, + \\"userTriggered\\": true, + \\"id\\": 22 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 2, + \\"type\\": 1, + \\"id\\": 27 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 2, + \\"type\\": 6, + \\"id\\": 22 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 2, + \\"type\\": 5, + \\"id\\": 27 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 2, + \\"type\\": 0, + \\"id\\": 27 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 2, + \\"type\\": 2, + \\"id\\": 27 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 5, + \\"text\\": \\"on\\", + \\"isChecked\\": true, + \\"userTriggered\\": true, + \\"id\\": 27 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 5, + \\"text\\": \\"off\\", + \\"isChecked\\": false, + \\"userTriggered\\": false, + \\"id\\": 32 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 2, + \\"type\\": 1, + \\"id\\": 37 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 2, + \\"type\\": 6, + \\"id\\": 27 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 2, + \\"type\\": 5, + \\"id\\": 37 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 2, + \\"type\\": 0, + \\"id\\": 37 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 2, + \\"type\\": 2, + \\"id\\": 37 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 5, + \\"text\\": \\"on\\", + \\"isChecked\\": true, + \\"userTriggered\\": true, + \\"id\\": 37 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 2, + \\"type\\": 6, + \\"id\\": 37 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 2, + \\"type\\": 5, + \\"id\\": 59 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 5, + \\"text\\": \\"*\\", + \\"isChecked\\": false, + \\"userTriggered\\": true, + \\"id\\": 59 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 5, + \\"text\\": \\"**\\", + \\"isChecked\\": false, + \\"userTriggered\\": true, + \\"id\\": 59 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 5, + \\"text\\": \\"***\\", + \\"isChecked\\": false, + \\"userTriggered\\": true, + \\"id\\": 59 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 5, + \\"text\\": \\"****\\", + \\"isChecked\\": false, + \\"userTriggered\\": true, + \\"id\\": 59 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 5, + \\"text\\": \\"*****\\", + \\"isChecked\\": false, + \\"userTriggered\\": true, + \\"id\\": 59 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 5, + \\"text\\": \\"******\\", + \\"isChecked\\": false, + \\"userTriggered\\": true, + \\"id\\": 59 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 5, + \\"text\\": \\"*******\\", + \\"isChecked\\": false, + \\"userTriggered\\": true, + \\"id\\": 59 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 5, + \\"text\\": \\"********\\", + \\"isChecked\\": false, + \\"userTriggered\\": true, + \\"id\\": 59 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 2, + \\"type\\": 6, + \\"id\\": 59 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 2, + \\"type\\": 5, + \\"id\\": 42 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 5, + \\"text\\": \\"t\\", + \\"isChecked\\": false, + \\"userTriggered\\": true, + \\"id\\": 42 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 5, + \\"text\\": \\"te\\", + \\"isChecked\\": false, + \\"userTriggered\\": true, + \\"id\\": 42 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 5, + \\"text\\": \\"tex\\", + \\"isChecked\\": false, + \\"userTriggered\\": true, + \\"id\\": 42 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 5, + \\"text\\": \\"text\\", + \\"isChecked\\": false, + \\"userTriggered\\": true, + \\"id\\": 42 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 5, + \\"text\\": \\"texta\\", + \\"isChecked\\": false, + \\"userTriggered\\": true, + \\"id\\": 42 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 5, + \\"text\\": \\"textar\\", + \\"isChecked\\": false, + \\"userTriggered\\": true, + \\"id\\": 42 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 5, + \\"text\\": \\"textare\\", + \\"isChecked\\": false, + \\"userTriggered\\": true, + \\"id\\": 42 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 5, + \\"text\\": \\"textarea\\", + \\"isChecked\\": false, + \\"userTriggered\\": true, + \\"id\\": 42 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 5, + \\"text\\": \\"textarea \\", + \\"isChecked\\": false, + \\"userTriggered\\": true, + \\"id\\": 42 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 5, + \\"text\\": \\"textarea t\\", + \\"isChecked\\": false, + \\"userTriggered\\": true, + \\"id\\": 42 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 5, + \\"text\\": \\"textarea te\\", + \\"isChecked\\": false, + \\"userTriggered\\": true, + \\"id\\": 42 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 5, + \\"text\\": \\"textarea tes\\", + \\"isChecked\\": false, + \\"userTriggered\\": true, + \\"id\\": 42 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 5, + \\"text\\": \\"textarea test\\", + \\"isChecked\\": false, + \\"userTriggered\\": true, + \\"id\\": 42 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 5, + \\"text\\": \\"1\\", + \\"isChecked\\": false, + \\"userTriggered\\": false, + \\"id\\": 47 + } + } +]" +`; + +exports[`record integration tests should record nested iframes and shadow doms 1`] = ` +"[ + { + \\"type\\": 0, + \\"data\\": {} + }, + { + \\"type\\": 1, + \\"data\\": {} + }, + { + \\"type\\": 4, + \\"data\\": { + \\"href\\": \\"about:blank\\", + \\"width\\": 1920, + \\"height\\": 1080 + } + }, + { + \\"type\\": 2, + \\"data\\": { + \\"node\\": { + \\"type\\": 0, + \\"childNodes\\": [ + { + \\"type\\": 1, + \\"name\\": \\"html\\", + \\"publicId\\": \\"\\", + \\"systemId\\": \\"\\", + \\"id\\": 2 + }, + { \\"type\\": 2, - \\"tagName\\": \\"div\\", + \\"tagName\\": \\"html\\", \\"attributes\\": { - \\"class\\": \\"select2-result-label\\", - \\"id\\": \\"select2-result-label-3\\", - \\"role\\": \\"option\\" + \\"lang\\": \\"en\\" }, - \\"childNodes\\": [], - \\"id\\": 68 + \\"childNodes\\": [ + { + \\"type\\": 2, + \\"tagName\\": \\"head\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 5 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"meta\\", + \\"attributes\\": { + \\"charset\\": \\"UTF-8\\" + }, + \\"childNodes\\": [], + \\"id\\": 6 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 7 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"meta\\", + \\"attributes\\": { + \\"name\\": \\"viewport\\", + \\"content\\": \\"width=device-width, initial-scale=1.0\\" + }, + \\"childNodes\\": [], + \\"id\\": 8 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 9 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"title\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"Frame 2\\", + \\"id\\": 11 + } + ], + \\"id\\": 10 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 12 + } + ], + \\"id\\": 4 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 13 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"body\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n frame 2\\\\n \\\\n \\", + \\"id\\": 15 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"script\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", + \\"id\\": 17 + } + ], + \\"id\\": 16 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\\\n \\\\n \\", + \\"id\\": 18 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"script\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", + \\"id\\": 20 + } + ], + \\"id\\": 19 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n\\\\n\\", + \\"id\\": 21 + } + ], + \\"id\\": 14 + } + ], + \\"id\\": 3 } - }, + ], + \\"id\\": 1 + }, + \\"initialOffset\\": { + \\"left\\": 0, + \\"top\\": 0 + } + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 0, + \\"texts\\": [], + \\"attributes\\": [], + \\"removes\\": [], + \\"adds\\": [ { - \\"parentId\\": 68, + \\"parentId\\": 14, \\"nextId\\": null, - \\"node\\": { - \\"type\\": 3, - \\"textContent\\": \\"B\\", - \\"id\\": 69 - } - }, - { - \\"parentId\\": 18, - \\"nextId\\": 36, \\"node\\": { \\"type\\": 2, - \\"tagName\\": \\"div\\", + \\"tagName\\": \\"iframe\\", \\"attributes\\": { - \\"id\\": \\"select2-drop-mask\\", - \\"class\\": \\"select2-drop-mask\\", - \\"style\\": \\"\\" + \\"id\\": \\"five\\" }, \\"childNodes\\": [], - \\"id\\": 70 + \\"id\\": 22 } - }, + } + ] + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 0, + \\"adds\\": [ { - \\"parentId\\": 62, + \\"parentId\\": 22, \\"nextId\\": null, \\"node\\": { - \\"type\\": 3, - \\"textContent\\": \\"2 results are available, use up and down arrow keys to navigate.\\", - \\"id\\": 71 - } - }, - { - \\"parentId\\": 45, - \\"nextId\\": 67, - \\"node\\": { - \\"type\\": 2, - \\"tagName\\": \\"li\\", - \\"attributes\\": { - \\"class\\": \\"select2-results-dept-0 select2-result select2-result-selectable select2-highlighted\\", - \\"role\\": \\"presentation\\" - }, - \\"childNodes\\": [], - \\"id\\": 72 + \\"type\\": 0, + \\"childNodes\\": [ + { + \\"type\\": 2, + \\"tagName\\": \\"html\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 2, + \\"tagName\\": \\"head\\", + \\"attributes\\": {}, + \\"childNodes\\": [], + \\"rootId\\": 23, + \\"id\\": 25 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"body\\", + \\"attributes\\": {}, + \\"childNodes\\": [], + \\"rootId\\": 23, + \\"id\\": 26 + } + ], + \\"rootId\\": 23, + \\"id\\": 24 + } + ], + \\"compatMode\\": \\"BackCompat\\", + \\"id\\": 23 } - }, + } + ], + \\"removes\\": [], + \\"texts\\": [], + \\"attributes\\": [], + \\"isAttachIframe\\": true + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 0, + \\"texts\\": [], + \\"attributes\\": [], + \\"removes\\": [], + \\"adds\\": [ { - \\"parentId\\": 72, + \\"parentId\\": 26, \\"nextId\\": null, \\"node\\": { \\"type\\": 2, \\"tagName\\": \\"div\\", - \\"attributes\\": { - \\"class\\": \\"select2-result-label\\", - \\"id\\": \\"select2-result-label-2\\", - \\"role\\": \\"option\\" - }, + \\"attributes\\": {}, \\"childNodes\\": [], - \\"id\\": 73 + \\"rootId\\": 23, + \\"id\\": 27, + \\"isShadow\\": true } }, { - \\"parentId\\": 73, + \\"parentId\\": 27, \\"nextId\\": null, - \\"node\\": { - \\"type\\": 3, - \\"textContent\\": \\"A\\", - \\"id\\": 74 - } - }, - { - \\"parentId\\": 73, - \\"nextId\\": 74, - \\"node\\": { - \\"type\\": 2, - \\"tagName\\": \\"span\\", - \\"attributes\\": { - \\"class\\": \\"select2-match\\" - }, - \\"childNodes\\": [], - \\"id\\": 75 - } - }, - { - \\"parentId\\": 68, - \\"nextId\\": 69, \\"node\\": { \\"type\\": 2, - \\"tagName\\": \\"span\\", - \\"attributes\\": { - \\"class\\": \\"select2-match\\" - }, + \\"tagName\\": \\"iframe\\", + \\"attributes\\": {}, \\"childNodes\\": [], - \\"id\\": 76 + \\"rootId\\": 23, + \\"id\\": 28 } } ] } }, - { - \\"type\\": 3, - \\"data\\": { - \\"source\\": 2, - \\"type\\": 0, - \\"id\\": 70 - } - }, - { - \\"type\\": 3, - \\"data\\": { - \\"source\\": 5, - \\"text\\": \\"\\", - \\"isChecked\\": false, - \\"id\\": 42 - } - }, - { - \\"type\\": 3, - \\"data\\": { - \\"source\\": 5, - \\"text\\": \\"\\", - \\"isChecked\\": false, - \\"id\\": 35 - } - }, - { - \\"type\\": 3, - \\"data\\": { - \\"source\\": 2, - \\"type\\": 1, - \\"id\\": 70 - } - }, - { - \\"type\\": 3, - \\"data\\": { - \\"source\\": 2, - \\"type\\": 6, - \\"id\\": 42 - } - }, - { - \\"type\\": 3, - \\"data\\": { - \\"source\\": 2, - \\"type\\": 5, - \\"id\\": 35 - } - }, { \\"type\\": 3, \\"data\\": { \\"source\\": 0, - \\"texts\\": [], - \\"attributes\\": [ - { - \\"id\\": 70, - \\"attributes\\": { - \\"style\\": \\"display: none;\\" - } - }, - { - \\"id\\": 36, - \\"attributes\\": { - \\"id\\": null, - \\"style\\": \\"left: Npx; width: Npx; top: Npx; bottom: auto; display: none;\\" - } - }, - { - \\"id\\": 25, - \\"attributes\\": { - \\"class\\": \\"select2-container select2-container-active\\" - } - }, - { - \\"id\\": 35, - \\"attributes\\": { - \\"disabled\\": null - } - }, + \\"adds\\": [ { - \\"id\\": 42, - \\"attributes\\": { - \\"class\\": \\"select2-input\\" + \\"parentId\\": 28, + \\"nextId\\": null, + \\"node\\": { + \\"type\\": 0, + \\"childNodes\\": [ + { + \\"type\\": 2, + \\"tagName\\": \\"html\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 2, + \\"tagName\\": \\"head\\", + \\"attributes\\": {}, + \\"childNodes\\": [], + \\"rootId\\": 29, + \\"id\\": 31 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"body\\", + \\"attributes\\": {}, + \\"childNodes\\": [], + \\"rootId\\": 29, + \\"id\\": 32 + } + ], + \\"rootId\\": 29, + \\"id\\": 30 + } + ], + \\"compatMode\\": \\"BackCompat\\", + \\"id\\": 29 } } ], - \\"removes\\": [ - { - \\"parentId\\": 18, - \\"id\\": 70 - }, - { - \\"parentId\\": 45, - \\"id\\": 72 - }, - { - \\"parentId\\": 45, - \\"id\\": 67 - } - ], + \\"removes\\": [], + \\"texts\\": [], + \\"attributes\\": [], + \\"isAttachIframe\\": true + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 0, + \\"texts\\": [], + \\"attributes\\": [], + \\"removes\\": [], \\"adds\\": [ { - \\"parentId\\": 18, - \\"nextId\\": 36, + \\"parentId\\": 32, + \\"nextId\\": null, \\"node\\": { \\"type\\": 2, - \\"tagName\\": \\"div\\", - \\"attributes\\": { - \\"id\\": \\"select2-drop-mask\\", - \\"class\\": \\"select2-drop-mask\\", - \\"style\\": \\"display: none;\\" - }, + \\"tagName\\": \\"span\\", + \\"attributes\\": {}, \\"childNodes\\": [], - \\"id\\": 70 + \\"rootId\\": 29, + \\"id\\": 33, + \\"isShadow\\": true } } ] } - }, - { - \\"type\\": 3, - \\"data\\": { - \\"source\\": 2, - \\"type\\": 0, - \\"id\\": 26 - } } ]" `; -exports[`serialize-before-record 1`] = ` +exports[`record integration tests should record shadow DOM 1`] = ` "[ { \\"type\\": 0, @@ -7573,17 +9606,103 @@ exports[`serialize-before-record 1`] = ` \\"node\\": { \\"type\\": 0, \\"childNodes\\": [ + { + \\"type\\": 1, + \\"name\\": \\"html\\", + \\"publicId\\": \\"\\", + \\"systemId\\": \\"\\", + \\"id\\": 2 + }, { \\"type\\": 2, \\"tagName\\": \\"html\\", - \\"attributes\\": {}, + \\"attributes\\": { + \\"lang\\": \\"en\\" + }, \\"childNodes\\": [ { \\"type\\": 2, \\"tagName\\": \\"head\\", \\"attributes\\": {}, - \\"childNodes\\": [], - \\"id\\": 3 + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 5 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"meta\\", + \\"attributes\\": { + \\"charset\\": \\"UTF-8\\" + }, + \\"childNodes\\": [], + \\"id\\": 6 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 7 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"meta\\", + \\"attributes\\": { + \\"name\\": \\"viewport\\", + \\"content\\": \\"width=device-width, initial-scale=1.0\\" + }, + \\"childNodes\\": [], + \\"id\\": 8 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 9 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"title\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"Shadow DOM Observer\\", + \\"id\\": 11 + } + ], + \\"id\\": 10 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 12 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"style\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\".my-element { margin: 0px 0px 1rem; }iframe { border: 0px; width: 100%; padding: 0px; }body { max-width: 400px; margin: 1rem auto; padding: 0px 1rem; font-family: \\\\\\"comic sans ms\\\\\\"; }\\", + \\"isStyle\\": true, + \\"id\\": 14 + } + ], + \\"id\\": 13 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 15 + } + ], + \\"id\\": 4 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 16 }, { \\"type\\": 2, @@ -7592,8 +9711,8 @@ exports[`serialize-before-record 1`] = ` \\"childNodes\\": [ { \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 5 + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 18 }, { \\"type\\": 2, @@ -7602,46 +9721,130 @@ exports[`serialize-before-record 1`] = ` \\"childNodes\\": [ { \\"type\\": 3, - \\"textContent\\": \\"mutation observer\\", - \\"id\\": 7 + \\"textContent\\": \\"\\\\n Lorem ipsum dolor sit amet, consectetur adipisicing elit. Repellat odit\\\\n officiis necessitatibus laborum asperiores et adipisci dolores corporis,\\\\n vero distinctio voluptas, suscipit commodi architecto, aliquam fugit.\\\\n Nesciunt labore reiciendis blanditiis!\\\\n \\", + \\"id\\": 20 } ], - \\"id\\": 6 + \\"id\\": 19 }, { \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 8 + \\"textContent\\": \\"\\\\n\\\\n \\", + \\"id\\": 21 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"div\\", + \\"attributes\\": { + \\"class\\": \\"my-element\\" + }, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 23 + }, + { + \\"type\\": 5, + \\"textContent\\": \\" Also could be a \\\\n \\\\n \\", + \\"id\\": 24 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 25 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 26, + \\"isShadow\\": true + }, + { + \\"type\\": 2, + \\"tagName\\": \\"style\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"body { margin: 0px; }p { border: 1px solid rgb(204, 204, 204); padding: 1rem; color: red; font-family: sans-serif; }\\", + \\"isStyle\\": true, + \\"id\\": 28 + } + ], + \\"id\\": 27, + \\"isShadow\\": true + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n\\\\n \\", + \\"id\\": 29, + \\"isShadow\\": true + }, + { + \\"type\\": 2, + \\"tagName\\": \\"p\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"Element with Shadow DOM\\", + \\"id\\": 31 + } + ], + \\"id\\": 30, + \\"isShadow\\": true + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n\\", + \\"id\\": 32, + \\"isShadow\\": true + } + ], + \\"id\\": 22, + \\"isShadowHost\\": true + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n\\\\n \\", + \\"id\\": 33 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"p\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n Lorem ipsum dolor sit amet, consectetur adipisicing elit. Repellat odit\\\\n officiis necessitatibus laborum asperiores et adipisci dolores corporis,\\\\n vero distinctio voluptas, suscipit commodi architecto, aliquam fugit.\\\\n Nesciunt labore reiciendis blanditiis!\\\\n \\", + \\"id\\": 35 + } + ], + \\"id\\": 34 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 36 }, { \\"type\\": 2, - \\"tagName\\": \\"ul\\", + \\"tagName\\": \\"script\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 10 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"li\\", - \\"attributes\\": {}, - \\"childNodes\\": [], - \\"id\\": 11 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 12 + \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", + \\"id\\": 38 } ], - \\"id\\": 9 + \\"id\\": 37 }, { \\"type\\": 3, - \\"textContent\\": \\"\\\\n\\\\n \\", - \\"id\\": 13 + \\"textContent\\": \\"\\\\n \\\\n \\\\n \\", + \\"id\\": 39 }, { \\"type\\": 2, @@ -7651,21 +9854,21 @@ exports[`serialize-before-record 1`] = ` { \\"type\\": 3, \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", - \\"id\\": 15 + \\"id\\": 41 } ], - \\"id\\": 14 + \\"id\\": 40 }, { \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\\\n \\", - \\"id\\": 16 + \\"textContent\\": \\"\\\\n \\\\n \\\\n\\\\n\\", + \\"id\\": 42 } ], - \\"id\\": 4 + \\"id\\": 17 } ], - \\"id\\": 2 + \\"id\\": 3 } ], \\"id\\": 1 @@ -7685,36 +9888,130 @@ exports[`serialize-before-record 1`] = ` \\"removes\\": [], \\"adds\\": [ { - \\"parentId\\": 9, + \\"parentId\\": 22, \\"nextId\\": null, \\"node\\": { \\"type\\": 2, - \\"tagName\\": \\"li\\", + \\"tagName\\": \\"p\\", \\"attributes\\": {}, \\"childNodes\\": [], - \\"id\\": 17 + \\"id\\": 43, + \\"isShadow\\": true } - }, + } + ] + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 0, + \\"texts\\": [], + \\"attributes\\": [], + \\"removes\\": [], + \\"adds\\": [ { - \\"parentId\\": 9, - \\"nextId\\": 17, + \\"parentId\\": 43, + \\"nextId\\": null, \\"node\\": { \\"type\\": 2, - \\"tagName\\": \\"li\\", + \\"tagName\\": \\"p\\", \\"attributes\\": {}, \\"childNodes\\": [], - \\"id\\": 18 + \\"id\\": 44 } - }, + } + ] + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 0, + \\"texts\\": [], + \\"attributes\\": [], + \\"removes\\": [ { - \\"parentId\\": 9, - \\"nextId\\": 18, + \\"parentId\\": 22, + \\"id\\": 30, + \\"isShadow\\": true + } + ], + \\"adds\\": [] + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 0, + \\"texts\\": [], + \\"attributes\\": [], + \\"removes\\": [], + \\"adds\\": [ + { + \\"parentId\\": 44, + \\"nextId\\": null, + \\"node\\": { + \\"type\\": 3, + \\"textContent\\": \\"hi\\", + \\"id\\": 45 + } + } + ] + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 0, + \\"texts\\": [], + \\"attributes\\": [], + \\"removes\\": [ + { + \\"parentId\\": 44, + \\"id\\": 45 + } + ], + \\"adds\\": [ + { + \\"parentId\\": 44, + \\"nextId\\": null, + \\"node\\": { + \\"type\\": 3, + \\"textContent\\": \\"123\\", + \\"id\\": 46 + } + } + ] + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 0, + \\"texts\\": [], + \\"attributes\\": [], + \\"removes\\": [], + \\"adds\\": [ + { + \\"parentId\\": 44, + \\"nextId\\": null, \\"node\\": { \\"type\\": 2, - \\"tagName\\": \\"li\\", + \\"tagName\\": \\"span\\", \\"attributes\\": {}, \\"childNodes\\": [], - \\"id\\": 19 + \\"id\\": 47, + \\"isShadow\\": true + } + }, + { + \\"parentId\\": 47, + \\"nextId\\": null, + \\"node\\": { + \\"type\\": 3, + \\"textContent\\": \\"nested shadow dom\\", + \\"id\\": 48 } } ] @@ -7723,7 +10020,7 @@ exports[`serialize-before-record 1`] = ` ]" `; -exports[`shadow-dom 1`] = ` +exports[`record integration tests should record webgl canvas mutations 1`] = ` "[ { \\"type\\": 0, @@ -7807,7 +10104,7 @@ exports[`shadow-dom 1`] = ` \\"childNodes\\": [ { \\"type\\": 3, - \\"textContent\\": \\"Shadow DOM Observer\\", + \\"textContent\\": \\"canvas\\", \\"id\\": 11 } ], @@ -7815,35 +10112,171 @@ exports[`shadow-dom 1`] = ` }, { \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", + \\"textContent\\": \\"\\\\n \\", \\"id\\": 12 + } + ], + \\"id\\": 4 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 13 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"body\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 15 }, { \\"type\\": 2, - \\"tagName\\": \\"style\\", + \\"tagName\\": \\"canvas\\", + \\"attributes\\": { + \\"id\\": \\"myCanvas\\", + \\"width\\": \\"200\\", + \\"height\\": \\"100\\", + \\"style\\": \\"border: 1px solid #000000\\" + }, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 17 + } + ], + \\"id\\": 16 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 18 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"script\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, - \\"textContent\\": \\"\\\\n .my-element {\\\\n margin: 0 0 1rem 0;\\\\n }\\\\n iframe {\\\\n border: 0;\\\\n width: 100%;\\\\n padding: 0;\\\\n }\\\\n\\\\n body {\\\\n max-width: 400px;\\\\n margin: 1rem auto;\\\\n padding: 0 1rem;\\\\n font-family: 'comic sans ms';\\\\n }\\\\n \\", - \\"isStyle\\": true, - \\"id\\": 14 + \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", + \\"id\\": 20 } ], - \\"id\\": 13 + \\"id\\": 19 }, { \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 15 + \\"textContent\\": \\"\\\\n \\\\n \\", + \\"id\\": 21 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"script\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", + \\"id\\": 23 + } + ], + \\"id\\": 22 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\\\n \\\\n\\\\n\\", + \\"id\\": 24 } ], - \\"id\\": 4 - }, + \\"id\\": 14 + } + ], + \\"id\\": 3 + } + ], + \\"id\\": 1 + }, + \\"initialOffset\\": { + \\"left\\": 0, + \\"top\\": 0 + } + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 9, + \\"id\\": 16, + \\"type\\": 1, + \\"commands\\": [ + { + \\"property\\": \\"clearColor\\", + \\"args\\": [ + 1, + 0, + 0, + 1 + ] + }, + { + \\"property\\": \\"clear\\", + \\"args\\": [ + 16384 + ] + } + ] + } + } +]" +`; + +exports[`record integration tests will serialize node before record 1`] = ` +"[ + { + \\"type\\": 0, + \\"data\\": {} + }, + { + \\"type\\": 1, + \\"data\\": {} + }, + { + \\"type\\": 4, + \\"data\\": { + \\"href\\": \\"about:blank\\", + \\"width\\": 1920, + \\"height\\": 1080 + } + }, + { + \\"type\\": 2, + \\"data\\": { + \\"node\\": { + \\"type\\": 0, + \\"childNodes\\": [ + { + \\"type\\": 1, + \\"name\\": \\"html\\", + \\"publicId\\": \\"\\", + \\"systemId\\": \\"\\", + \\"id\\": 2 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"html\\", + \\"attributes\\": {}, + \\"childNodes\\": [ { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 16 + \\"type\\": 2, + \\"tagName\\": \\"head\\", + \\"attributes\\": {}, + \\"childNodes\\": [], + \\"id\\": 4 }, { \\"type\\": 2, @@ -7852,8 +10285,8 @@ exports[`shadow-dom 1`] = ` \\"childNodes\\": [ { \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 18 + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 6 }, { \\"type\\": 2, @@ -7862,130 +10295,58 @@ exports[`shadow-dom 1`] = ` \\"childNodes\\": [ { \\"type\\": 3, - \\"textContent\\": \\"\\\\n Lorem ipsum dolor sit amet, consectetur adipisicing elit. Repellat odit\\\\n officiis necessitatibus laborum asperiores et adipisci dolores corporis,\\\\n vero distinctio voluptas, suscipit commodi architecto, aliquam fugit.\\\\n Nesciunt labore reiciendis blanditiis!\\\\n \\", - \\"id\\": 20 + \\"textContent\\": \\"mutation observer\\", + \\"id\\": 8 } ], - \\"id\\": 19 + \\"id\\": 7 }, { \\"type\\": 3, - \\"textContent\\": \\"\\\\n\\\\n \\", - \\"id\\": 21 + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 9 }, { \\"type\\": 2, - \\"tagName\\": \\"div\\", - \\"attributes\\": { - \\"class\\": \\"my-element\\" - }, + \\"tagName\\": \\"ul\\", + \\"attributes\\": {}, \\"childNodes\\": [ - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 23 - }, - { - \\"type\\": 5, - \\"textContent\\": \\" Also could be a \\\\n \\\\n \\", - \\"id\\": 24 - }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", - \\"id\\": 25 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 26, - \\"isShadow\\": true - }, - { - \\"type\\": 2, - \\"tagName\\": \\"style\\", - \\"attributes\\": {}, - \\"childNodes\\": [ - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n body { /* for fallback iframe */\\\\n margin: 0;\\\\n }\\\\n p { \\\\n border: 1px solid #ccc;\\\\n padding: 1rem;\\\\n color: red;\\\\n font-family: sans-serif;\\\\n }\\\\n \\", - \\"isStyle\\": true, - \\"id\\": 28 - } - ], - \\"id\\": 27, - \\"isShadow\\": true - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n\\\\n \\", - \\"id\\": 29, - \\"isShadow\\": true + \\"id\\": 11 }, { \\"type\\": 2, - \\"tagName\\": \\"p\\", + \\"tagName\\": \\"li\\", \\"attributes\\": {}, - \\"childNodes\\": [ - { - \\"type\\": 3, - \\"textContent\\": \\"Element with Shadow DOM\\", - \\"id\\": 31 - } - ], - \\"id\\": 30, - \\"isShadow\\": true + \\"childNodes\\": [], + \\"id\\": 12 }, { \\"type\\": 3, - \\"textContent\\": \\"\\\\n\\", - \\"id\\": 32, - \\"isShadow\\": true - } - ], - \\"id\\": 22, - \\"isShadowHost\\": true - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n\\\\n \\", - \\"id\\": 33 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"p\\", - \\"attributes\\": {}, - \\"childNodes\\": [ - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n Lorem ipsum dolor sit amet, consectetur adipisicing elit. Repellat odit\\\\n officiis necessitatibus laborum asperiores et adipisci dolores corporis,\\\\n vero distinctio voluptas, suscipit commodi architecto, aliquam fugit.\\\\n Nesciunt labore reiciendis blanditiis!\\\\n \\", - \\"id\\": 35 + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 13 } ], - \\"id\\": 34 + \\"id\\": 10 }, { \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 36 + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 14 }, { \\"type\\": 2, - \\"tagName\\": \\"script\\", + \\"tagName\\": \\"canvas\\", \\"attributes\\": {}, - \\"childNodes\\": [ - { - \\"type\\": 3, - \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", - \\"id\\": 38 - } - ], - \\"id\\": 37 + \\"childNodes\\": [], + \\"id\\": 15 }, { \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\\\n \\\\n \\", - \\"id\\": 39 + \\"textContent\\": \\"\\\\n\\\\n \\", + \\"id\\": 16 }, { \\"type\\": 2, @@ -7995,18 +10356,18 @@ exports[`shadow-dom 1`] = ` { \\"type\\": 3, \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", - \\"id\\": 41 + \\"id\\": 18 } ], - \\"id\\": 40 + \\"id\\": 17 }, { \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\\\n \\\\n\\\\n\\", - \\"id\\": 42 + \\"textContent\\": \\"\\\\n \\\\n \\\\n\\", + \\"id\\": 19 } ], - \\"id\\": 17 + \\"id\\": 5 } ], \\"id\\": 3 @@ -8029,98 +10390,36 @@ exports[`shadow-dom 1`] = ` \\"removes\\": [], \\"adds\\": [ { - \\"parentId\\": 22, + \\"parentId\\": 10, \\"nextId\\": null, \\"node\\": { \\"type\\": 2, - \\"tagName\\": \\"p\\", + \\"tagName\\": \\"li\\", \\"attributes\\": {}, \\"childNodes\\": [], - \\"id\\": 43, - \\"isShadow\\": true + \\"id\\": 20 } - } - ] - } - }, - { - \\"type\\": 3, - \\"data\\": { - \\"source\\": 0, - \\"texts\\": [], - \\"attributes\\": [], - \\"removes\\": [], - \\"adds\\": [ + }, { - \\"parentId\\": 43, - \\"nextId\\": null, + \\"parentId\\": 10, + \\"nextId\\": 20, \\"node\\": { \\"type\\": 2, - \\"tagName\\": \\"p\\", + \\"tagName\\": \\"li\\", \\"attributes\\": {}, \\"childNodes\\": [], - \\"id\\": 44 - } - } - ] - } - }, - { - \\"type\\": 3, - \\"data\\": { - \\"source\\": 0, - \\"texts\\": [], - \\"attributes\\": [], - \\"removes\\": [ - { - \\"parentId\\": 22, - \\"id\\": 30, - \\"isShadow\\": true - } - ], - \\"adds\\": [] - } - }, - { - \\"type\\": 3, - \\"data\\": { - \\"source\\": 0, - \\"texts\\": [], - \\"attributes\\": [], - \\"removes\\": [], - \\"adds\\": [ - { - \\"parentId\\": 44, - \\"nextId\\": null, - \\"node\\": { - \\"type\\": 3, - \\"textContent\\": \\"hi\\", - \\"id\\": 45 + \\"id\\": 21 } - } - ] - } - }, - { - \\"type\\": 3, - \\"data\\": { - \\"source\\": 0, - \\"texts\\": [], - \\"attributes\\": [], - \\"removes\\": [ - { - \\"parentId\\": 44, - \\"id\\": 45 - } - ], - \\"adds\\": [ + }, { - \\"parentId\\": 44, - \\"nextId\\": null, + \\"parentId\\": 10, + \\"nextId\\": 21, \\"node\\": { - \\"type\\": 3, - \\"textContent\\": \\"123\\", - \\"id\\": 46 + \\"type\\": 2, + \\"tagName\\": \\"li\\", + \\"attributes\\": {}, + \\"childNodes\\": [], + \\"id\\": 22 } } ] diff --git a/packages/rrweb/test/__snapshots__/packer.test.ts.snap b/packages/rrweb/test/__snapshots__/packer.test.ts.snap new file mode 100644 index 0000000000..2323b5a7af Binary files /dev/null and b/packages/rrweb/test/__snapshots__/packer.test.ts.snap differ diff --git a/packages/rrweb/test/__snapshots__/record.test.ts.snap b/packages/rrweb/test/__snapshots__/record.test.ts.snap new file mode 100644 index 0000000000..011db6fc59 --- /dev/null +++ b/packages/rrweb/test/__snapshots__/record.test.ts.snap @@ -0,0 +1,1151 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`record can add custom event 1`] = ` +"[ + { + \\"type\\": 4, + \\"data\\": { + \\"href\\": \\"about:blank\\", + \\"width\\": 1920, + \\"height\\": 1080 + } + }, + { + \\"type\\": 2, + \\"data\\": { + \\"node\\": { + \\"type\\": 0, + \\"childNodes\\": [ + { + \\"type\\": 1, + \\"name\\": \\"html\\", + \\"publicId\\": \\"\\", + \\"systemId\\": \\"\\", + \\"id\\": 2 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"html\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 2, + \\"tagName\\": \\"head\\", + \\"attributes\\": {}, + \\"childNodes\\": [], + \\"id\\": 4 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"body\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 6 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"input\\", + \\"attributes\\": { + \\"type\\": \\"text\\", + \\"size\\": \\"40\\" + }, + \\"childNodes\\": [], + \\"id\\": 7 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\\\n \\\\n \\", + \\"id\\": 8 + } + ], + \\"id\\": 5 + } + ], + \\"id\\": 3 + } + ], + \\"id\\": 1 + }, + \\"initialOffset\\": { + \\"left\\": 0, + \\"top\\": 0 + } + } + }, + { + \\"type\\": 5, + \\"data\\": { + \\"tag\\": \\"tag1\\", + \\"payload\\": 1 + } + }, + { + \\"type\\": 5, + \\"data\\": { + \\"tag\\": \\"tag2\\", + \\"payload\\": { + \\"a\\": \\"b\\" + } + } + } +]" +`; + +exports[`record captures nested stylesheet rules 1`] = ` +"[ + { + \\"type\\": 4, + \\"data\\": { + \\"href\\": \\"about:blank\\", + \\"width\\": 1920, + \\"height\\": 1080 + } + }, + { + \\"type\\": 2, + \\"data\\": { + \\"node\\": { + \\"type\\": 0, + \\"childNodes\\": [ + { + \\"type\\": 1, + \\"name\\": \\"html\\", + \\"publicId\\": \\"\\", + \\"systemId\\": \\"\\", + \\"id\\": 2 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"html\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 2, + \\"tagName\\": \\"head\\", + \\"attributes\\": {}, + \\"childNodes\\": [], + \\"id\\": 4 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"body\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 6 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"input\\", + \\"attributes\\": { + \\"type\\": \\"text\\", + \\"size\\": \\"40\\" + }, + \\"childNodes\\": [], + \\"id\\": 7 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\\\n \\\\n \\", + \\"id\\": 8 + } + ], + \\"id\\": 5 + } + ], + \\"id\\": 3 + } + ], + \\"id\\": 1 + }, + \\"initialOffset\\": { + \\"left\\": 0, + \\"top\\": 0 + } + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 0, + \\"texts\\": [], + \\"attributes\\": [], + \\"removes\\": [], + \\"adds\\": [ + { + \\"parentId\\": 4, + \\"nextId\\": null, + \\"node\\": { + \\"type\\": 2, + \\"tagName\\": \\"style\\", + \\"attributes\\": { + \\"_cssText\\": \\"@media {\\\\n body { background: rgb(0, 0, 0); }\\\\n}\\" + }, + \\"childNodes\\": [], + \\"id\\": 9 + } + } + ] + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 8, + \\"id\\": 9, + \\"adds\\": [ + { + \\"rule\\": \\"body { color: #fff; }\\", + \\"index\\": [ + 0, + 0 + ] + } + ] + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 8, + \\"id\\": 9, + \\"removes\\": [ + { + \\"index\\": [ + 0, + 0 + ] + } + ] + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 8, + \\"id\\": 9, + \\"adds\\": [ + { + \\"rule\\": \\"body { color: #ccc; }\\", + \\"index\\": [ + 0, + 0 + ] + } + ] + } + } +]" +`; + +exports[`record captures style property changes 1`] = ` +"[ + { + \\"type\\": 4, + \\"data\\": { + \\"href\\": \\"about:blank\\", + \\"width\\": 1920, + \\"height\\": 1080 + } + }, + { + \\"type\\": 2, + \\"data\\": { + \\"node\\": { + \\"type\\": 0, + \\"childNodes\\": [ + { + \\"type\\": 1, + \\"name\\": \\"html\\", + \\"publicId\\": \\"\\", + \\"systemId\\": \\"\\", + \\"id\\": 2 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"html\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 2, + \\"tagName\\": \\"head\\", + \\"attributes\\": {}, + \\"childNodes\\": [], + \\"id\\": 4 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"body\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 6 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"input\\", + \\"attributes\\": { + \\"type\\": \\"text\\", + \\"size\\": \\"40\\" + }, + \\"childNodes\\": [], + \\"id\\": 7 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\\\n \\\\n \\", + \\"id\\": 8 + } + ], + \\"id\\": 5 + } + ], + \\"id\\": 3 + } + ], + \\"id\\": 1 + }, + \\"initialOffset\\": { + \\"left\\": 0, + \\"top\\": 0 + } + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 0, + \\"texts\\": [], + \\"attributes\\": [], + \\"removes\\": [], + \\"adds\\": [ + { + \\"parentId\\": 4, + \\"nextId\\": null, + \\"node\\": { + \\"type\\": 2, + \\"tagName\\": \\"style\\", + \\"attributes\\": { + \\"_cssText\\": \\"body { background: rgb(0, 0, 0); }\\" + }, + \\"childNodes\\": [], + \\"id\\": 9 + } + } + ] + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 13, + \\"id\\": 9, + \\"set\\": { + \\"property\\": \\"color\\", + \\"value\\": \\"green\\" + }, + \\"index\\": [ + 0 + ] + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 13, + \\"id\\": 9, + \\"remove\\": { + \\"property\\": \\"background\\" + }, + \\"index\\": [ + 0 + ] + } + } +]" +`; + +exports[`record captures stylesheet rules 1`] = ` +"[ + { + \\"type\\": 4, + \\"data\\": { + \\"href\\": \\"about:blank\\", + \\"width\\": 1920, + \\"height\\": 1080 + } + }, + { + \\"type\\": 2, + \\"data\\": { + \\"node\\": { + \\"type\\": 0, + \\"childNodes\\": [ + { + \\"type\\": 1, + \\"name\\": \\"html\\", + \\"publicId\\": \\"\\", + \\"systemId\\": \\"\\", + \\"id\\": 2 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"html\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 2, + \\"tagName\\": \\"head\\", + \\"attributes\\": {}, + \\"childNodes\\": [], + \\"id\\": 4 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"body\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 6 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"input\\", + \\"attributes\\": { + \\"type\\": \\"text\\", + \\"size\\": \\"40\\" + }, + \\"childNodes\\": [], + \\"id\\": 7 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\\\n \\\\n \\", + \\"id\\": 8 + } + ], + \\"id\\": 5 + } + ], + \\"id\\": 3 + } + ], + \\"id\\": 1 + }, + \\"initialOffset\\": { + \\"left\\": 0, + \\"top\\": 0 + } + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 0, + \\"texts\\": [], + \\"attributes\\": [], + \\"removes\\": [], + \\"adds\\": [ + { + \\"parentId\\": 4, + \\"nextId\\": null, + \\"node\\": { + \\"type\\": 2, + \\"tagName\\": \\"style\\", + \\"attributes\\": { + \\"_cssText\\": \\"body { background: rgb(0, 0, 0); }\\" + }, + \\"childNodes\\": [], + \\"id\\": 9 + } + } + ] + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 8, + \\"id\\": 9, + \\"adds\\": [ + { + \\"rule\\": \\"body { color: #fff; }\\" + } + ] + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 8, + \\"id\\": 9, + \\"removes\\": [ + { + \\"index\\": 0 + } + ] + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 8, + \\"id\\": 9, + \\"adds\\": [ + { + \\"rule\\": \\"body { color: #ccc; }\\" + } + ] + } + } +]" +`; + +exports[`record iframes captures stylesheet mutations in iframes 1`] = ` +"[ + { + \\"type\\": 4, + \\"data\\": { + \\"href\\": \\"about:blank\\", + \\"width\\": 1920, + \\"height\\": 1080 + } + }, + { + \\"type\\": 2, + \\"data\\": { + \\"node\\": { + \\"type\\": 0, + \\"childNodes\\": [ + { + \\"type\\": 1, + \\"name\\": \\"html\\", + \\"publicId\\": \\"\\", + \\"systemId\\": \\"\\", + \\"id\\": 2 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"html\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 2, + \\"tagName\\": \\"head\\", + \\"attributes\\": {}, + \\"childNodes\\": [], + \\"id\\": 4 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"body\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 6 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"iframe\\", + \\"attributes\\": { + \\"srcdoc\\": \\"\\" + }, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\\\n \\\\n \\", + \\"id\\": 8 + } + ], + \\"id\\": 7 + } + ], + \\"id\\": 5 + } + ], + \\"id\\": 3 + } + ], + \\"id\\": 1 + }, + \\"initialOffset\\": { + \\"left\\": 0, + \\"top\\": 0 + } + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 0, + \\"adds\\": [ + { + \\"parentId\\": 7, + \\"nextId\\": null, + \\"node\\": { + \\"type\\": 0, + \\"childNodes\\": [ + { + \\"type\\": 2, + \\"tagName\\": \\"html\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 2, + \\"tagName\\": \\"head\\", + \\"attributes\\": {}, + \\"childNodes\\": [], + \\"rootId\\": 9, + \\"id\\": 11 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"body\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 2, + \\"tagName\\": \\"button\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"Mysterious Button\\", + \\"rootId\\": 9, + \\"id\\": 14 + } + ], + \\"rootId\\": 9, + \\"id\\": 13 + } + ], + \\"rootId\\": 9, + \\"id\\": 12 + } + ], + \\"rootId\\": 9, + \\"id\\": 10 + } + ], + \\"id\\": 9 + } + } + ], + \\"removes\\": [], + \\"texts\\": [], + \\"attributes\\": [], + \\"isAttachIframe\\": true + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 0, + \\"texts\\": [], + \\"attributes\\": [], + \\"removes\\": [], + \\"adds\\": [ + { + \\"parentId\\": 11, + \\"nextId\\": null, + \\"node\\": { + \\"type\\": 2, + \\"tagName\\": \\"style\\", + \\"attributes\\": { + \\"_cssText\\": \\"body { background: rgb(0, 0, 0); }@media {\\\\n body { background: rgb(0, 0, 0); }\\\\n}\\" + }, + \\"childNodes\\": [], + \\"rootId\\": 9, + \\"id\\": 15 + } + } + ] + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 8, + \\"id\\": 15, + \\"adds\\": [ + { + \\"rule\\": \\"body { color: #fff; }\\" + } + ] + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 8, + \\"id\\": 15, + \\"adds\\": [ + { + \\"rule\\": \\"body { color: #ccc; }\\", + \\"index\\": [ + 2, + 0 + ] + } + ] + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 8, + \\"id\\": 15, + \\"removes\\": [ + { + \\"index\\": 0 + } + ] + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 13, + \\"id\\": 15, + \\"set\\": { + \\"property\\": \\"color\\", + \\"value\\": \\"green\\" + }, + \\"index\\": [ + 0 + ] + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 8, + \\"id\\": 15, + \\"removes\\": [ + { + \\"index\\": [ + 1, + 0 + ] + } + ] + } + } +]" +`; + +exports[`record is safe to checkout during async callbacks 1`] = ` +"[ + { + \\"type\\": 4, + \\"data\\": { + \\"href\\": \\"about:blank\\", + \\"width\\": 1920, + \\"height\\": 1080 + } + }, + { + \\"type\\": 2, + \\"data\\": { + \\"node\\": { + \\"type\\": 0, + \\"childNodes\\": [ + { + \\"type\\": 1, + \\"name\\": \\"html\\", + \\"publicId\\": \\"\\", + \\"systemId\\": \\"\\", + \\"id\\": 2 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"html\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 2, + \\"tagName\\": \\"head\\", + \\"attributes\\": {}, + \\"childNodes\\": [], + \\"id\\": 4 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"body\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 6 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"input\\", + \\"attributes\\": { + \\"type\\": \\"text\\", + \\"size\\": \\"40\\" + }, + \\"childNodes\\": [], + \\"id\\": 7 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\\\n \\\\n \\", + \\"id\\": 8 + } + ], + \\"id\\": 5 + } + ], + \\"id\\": 3 + } + ], + \\"id\\": 1 + }, + \\"initialOffset\\": { + \\"left\\": 0, + \\"top\\": 0 + } + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 0, + \\"texts\\": [], + \\"attributes\\": [], + \\"removes\\": [ + { + \\"parentId\\": 5, + \\"id\\": 7 + } + ], + \\"adds\\": [ + { + \\"parentId\\": 5, + \\"nextId\\": null, + \\"node\\": { + \\"type\\": 2, + \\"tagName\\": \\"p\\", + \\"attributes\\": {}, + \\"childNodes\\": [], + \\"id\\": 9 + } + }, + { + \\"parentId\\": 9, + \\"nextId\\": null, + \\"node\\": { + \\"type\\": 2, + \\"tagName\\": \\"span\\", + \\"attributes\\": {}, + \\"childNodes\\": [], + \\"id\\": 10 + } + } + ] + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 0, + \\"texts\\": [], + \\"attributes\\": [], + \\"removes\\": [], + \\"adds\\": [ + { + \\"parentId\\": 10, + \\"nextId\\": null, + \\"node\\": { + \\"type\\": 3, + \\"textContent\\": \\"test\\", + \\"id\\": 11 + } + } + ] + } + }, + { + \\"type\\": 4, + \\"data\\": { + \\"href\\": \\"about:blank\\", + \\"width\\": 1920, + \\"height\\": 1080 + } + }, + { + \\"type\\": 2, + \\"data\\": { + \\"node\\": { + \\"type\\": 0, + \\"childNodes\\": [ + { + \\"type\\": 1, + \\"name\\": \\"html\\", + \\"publicId\\": \\"\\", + \\"systemId\\": \\"\\", + \\"id\\": 2 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"html\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 2, + \\"tagName\\": \\"head\\", + \\"attributes\\": {}, + \\"childNodes\\": [], + \\"id\\": 4 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"body\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 6 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\\\n \\\\n \\", + \\"id\\": 8 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"p\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 2, + \\"tagName\\": \\"span\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"test\\", + \\"id\\": 11 + } + ], + \\"id\\": 10 + } + ], + \\"id\\": 9 + } + ], + \\"id\\": 5 + } + ], + \\"id\\": 3 + } + ], + \\"id\\": 1 + }, + \\"initialOffset\\": { + \\"left\\": 0, + \\"top\\": 0 + } + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 0, + \\"texts\\": [], + \\"attributes\\": [], + \\"removes\\": [ + { + \\"parentId\\": 9, + \\"id\\": 10 + } + ], + \\"adds\\": [ + { + \\"parentId\\": 5, + \\"nextId\\": null, + \\"node\\": { + \\"type\\": 2, + \\"tagName\\": \\"span\\", + \\"attributes\\": {}, + \\"childNodes\\": [], + \\"id\\": 10 + } + }, + { + \\"parentId\\": 10, + \\"nextId\\": null, + \\"node\\": { + \\"type\\": 3, + \\"textContent\\": \\"test\\", + \\"id\\": 11 + } + } + ] + } + } +]" +`; + +exports[`record without CSSGroupingRule support captures nested stylesheet rules 1`] = ` +"[ + { + \\"type\\": 4, + \\"data\\": { + \\"href\\": \\"about:blank\\", + \\"width\\": 1920, + \\"height\\": 1080 + } + }, + { + \\"type\\": 2, + \\"data\\": { + \\"node\\": { + \\"type\\": 0, + \\"childNodes\\": [ + { + \\"type\\": 1, + \\"name\\": \\"html\\", + \\"publicId\\": \\"\\", + \\"systemId\\": \\"\\", + \\"id\\": 2 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"html\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 2, + \\"tagName\\": \\"head\\", + \\"attributes\\": {}, + \\"childNodes\\": [], + \\"id\\": 4 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"body\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 6 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"input\\", + \\"attributes\\": { + \\"type\\": \\"text\\", + \\"size\\": \\"40\\" + }, + \\"childNodes\\": [], + \\"id\\": 7 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\\\n \\\\n \\", + \\"id\\": 8 + } + ], + \\"id\\": 5 + } + ], + \\"id\\": 3 + } + ], + \\"id\\": 1 + }, + \\"initialOffset\\": { + \\"left\\": 0, + \\"top\\": 0 + } + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 0, + \\"texts\\": [], + \\"attributes\\": [], + \\"removes\\": [], + \\"adds\\": [ + { + \\"parentId\\": 4, + \\"nextId\\": null, + \\"node\\": { + \\"type\\": 2, + \\"tagName\\": \\"style\\", + \\"attributes\\": { + \\"_cssText\\": \\"@media {\\\\n body { background: rgb(0, 0, 0); }\\\\n}\\" + }, + \\"childNodes\\": [], + \\"id\\": 9 + } + } + ] + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 8, + \\"id\\": 9, + \\"adds\\": [ + { + \\"rule\\": \\"body { color: #fff; }\\", + \\"index\\": [ + 0, + 0 + ] + } + ] + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 8, + \\"id\\": 9, + \\"removes\\": [ + { + \\"index\\": [ + 0, + 0 + ] + } + ] + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 8, + \\"id\\": 9, + \\"adds\\": [ + { + \\"rule\\": \\"body { color: #ccc; }\\", + \\"index\\": [ + 0, + 0 + ] + } + ] + } + } +]" +`; diff --git a/packages/rrweb/test/__snapshots__/replayer.test.ts.snap b/packages/rrweb/test/__snapshots__/replayer.test.ts.snap new file mode 100644 index 0000000000..c471f9d958 --- /dev/null +++ b/packages/rrweb/test/__snapshots__/replayer.test.ts.snap @@ -0,0 +1,242 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`replayer can fast forward past StyleSheetRule changes on virtual elements 1`] = ` +"file-frame-4 + + + + + +
+
+ +
+ + + + +file-frame-5 + + + + + + + + + + +
string + + + + +file-cid-0 +@charset \\"utf-8\\"; + +.rr-block { background: currentcolor; } + +noscript { display: none !important; } + +html.rrweb-paused *, html.rrweb-paused ::before, html.rrweb-paused ::after { animation-play-state: paused !important; } + + +file-cid-1 +@charset \\"utf-8\\"; + +.css-added-at-500 { padding: 1.3125rem; flex: 0 0 auto; width: 100%; } + + +file-cid-2 +@charset \\"utf-8\\"; + +.css-added-at-200-overwritten-at-3000 { opacity: 1; transform: translateX(0px); } + +.css-added-at-400-overwritten-at-3000 { border: 1px solid blue; } + + +file-cid-3 +@charset \\"utf-8\\"; + +.css-added-at-200 { position: fixed; top: 0px; right: 0px; left: 4rem; z-index: 15; flex-shrink: 0; height: 0.25rem; overflow: hidden; background-color: rgb(17, 171, 209); } + +.css-added-at-200.alt { height: 0.25rem; background-color: rgb(190, 232, 242); opacity: 0; transition: opacity 0.5s ease 0s; } + +.css-added-at-1000-deleted-at-2500 { display: flex; flex-direction: column; min-width: 60rem; min-height: 100vh; color: blue; } + +.css-added-at-200.alt2 { padding-left: 4rem; } +" +`; + +exports[`replayer can fast forward past StyleSheetRule deletion on virtual elements 1`] = ` +"file-frame-0 + + + + + + +" +`; + +exports[`replayer can handle removing style elements 1`] = ` +"file-frame-1 + + + + + +
+
+ +
+ + + + +file-frame-2 + + + + + + + + + +file-cid-0 +@charset \\"utf-8\\"; + +.rr-block { background: currentcolor; } + +noscript { display: none !important; } + +html.rrweb-paused *, html.rrweb-paused ::before, html.rrweb-paused ::after { animation-play-state: paused !important; } +" +`; + +exports[`replayer replays same timestamp events in correct order (with addAction) 1`] = ` +"file-frame-1 + + + + + +
+
+ +
+ + + + +file-frame-2 + + + + + + + + Final - correct + + + + +file-cid-0 +@charset \\"utf-8\\"; + +.rr-block { background: currentcolor; } + +noscript { display: none !important; } + +html.rrweb-paused *, html.rrweb-paused ::before, html.rrweb-paused ::after { animation-play-state: paused !important; } +" +`; + +exports[`replayer replays same timestamp events in correct order 1`] = ` +"file-frame-1 + + + + + +
+
+ +
+ + + + +file-frame-2 + + + + + + + + Final - correct + + + + +file-cid-0 +@charset \\"utf-8\\"; + +.rr-block { background: currentcolor; } + +noscript { display: none !important; } + +html.rrweb-paused *, html.rrweb-paused ::before, html.rrweb-paused ::after { animation-play-state: paused !important; } +" +`; diff --git a/packages/rrweb/test/e2e/__image_snapshots__/webgl-test-ts-e-2-e-webgl-will-record-and-replay-a-webgl-image-1-snap.png b/packages/rrweb/test/e2e/__image_snapshots__/webgl-test-ts-e-2-e-webgl-will-record-and-replay-a-webgl-image-1-snap.png new file mode 100644 index 0000000000..731898701b Binary files /dev/null and b/packages/rrweb/test/e2e/__image_snapshots__/webgl-test-ts-e-2-e-webgl-will-record-and-replay-a-webgl-image-1-snap.png differ diff --git a/packages/rrweb/test/e2e/__image_snapshots__/webgl-test-ts-e-2-e-webgl-will-record-and-replay-a-webgl-square-1-snap.png b/packages/rrweb/test/e2e/__image_snapshots__/webgl-test-ts-e-2-e-webgl-will-record-and-replay-a-webgl-square-1-snap.png new file mode 100644 index 0000000000..e22bc48831 Binary files /dev/null and b/packages/rrweb/test/e2e/__image_snapshots__/webgl-test-ts-e-2-e-webgl-will-record-and-replay-a-webgl-square-1-snap.png differ diff --git a/packages/rrweb/test/e2e/webgl.test.ts b/packages/rrweb/test/e2e/webgl.test.ts new file mode 100644 index 0000000000..2be0d33f0c --- /dev/null +++ b/packages/rrweb/test/e2e/webgl.test.ts @@ -0,0 +1,175 @@ +import * as http from 'http'; +import * as fs from 'fs'; +import * as path from 'path'; +import * as puppeteer from 'puppeteer'; +import { + startServer, + launchPuppeteer, + getServerURL, + replaceLast, + waitForRAF, +} from '../utils'; +import { + recordOptions, + eventWithTime, + EventType, + IncrementalSource, +} from '../../src/types'; +import { toMatchImageSnapshot } from 'jest-image-snapshot'; +expect.extend({ toMatchImageSnapshot }); + +interface ISuite { + code: string; + browser: puppeteer.Browser; + server: http.Server; + page: puppeteer.Page; + events: eventWithTime[]; + serverURL: string; +} + +describe('e2e webgl', () => { + let code: ISuite['code']; + let page: ISuite['page']; + let browser: ISuite['browser']; + let server: ISuite['server']; + let serverURL: ISuite['serverURL']; + + beforeAll(async () => { + server = await startServer(); + serverURL = getServerURL(server); + browser = await launchPuppeteer(); + + const bundlePath = path.resolve(__dirname, '../../dist/rrweb.min.js'); + code = fs.readFileSync(bundlePath, 'utf8'); + }); + + afterEach(async () => { + await page.close(); + }); + + afterAll(async () => { + await server.close(); + await browser.close(); + }); + + const getHtml = ( + fileName: string, + options: recordOptions = {}, + ): string => { + const filePath = path.resolve(__dirname, `../html/${fileName}`); + const html = fs.readFileSync(filePath, 'utf8'); + return replaceLast( + html, + '', + ` + + + `, + ); + }; + + const fakeGoto = async (page: puppeteer.Page, url: string) => { + const intercept = async (request: puppeteer.HTTPRequest) => { + await request.respond({ + status: 200, + contentType: 'text/html', + body: ' ', // non-empty string or page will load indefinitely + }); + }; + await page.setRequestInterception(true); + page.on('request', intercept); + await page.goto(url); + page.off('request', intercept); + await page.setRequestInterception(false); + }; + + const hideMouseAnimation = async (page: puppeteer.Page) => { + await page.addStyleTag({ + content: '.replayer-mouse-tail{display: none !important;}', + }); + }; + + it('will record and replay a webgl square', async () => { + page = await browser.newPage(); + await fakeGoto(page, `${serverURL}/html/canvas-webgl-square.html`); + + await page.setContent( + getHtml.call(this, 'canvas-webgl-square.html', { recordCanvas: true }), + ); + + await waitForRAF(page); + + const snapshots: eventWithTime[] = await page.evaluate('window.snapshots'); + + page = await browser.newPage(); + + await page.goto('about:blank'); + await page.evaluate(code); + + await hideMouseAnimation(page); + await page.evaluate(`let events = ${JSON.stringify(snapshots)}`); + await page.evaluate(` + const { Replayer } = rrweb; + const replayer = new Replayer(events, { + UNSAFE_replayCanvas: true, + }); + replayer.play(500); + `); + await page.waitForTimeout(50); + + const element = await page.$('iframe'); + const frameImage = await element!.screenshot(); + + expect(frameImage).toMatchImageSnapshot(); + }); + + it('will record and replay a webgl image', async () => { + page = await browser.newPage(); + await fakeGoto(page, `${serverURL}/html/canvas-webgl-image.html`); + + await page.setContent( + getHtml.call(this, 'canvas-webgl-image.html', { recordCanvas: true }), + ); + + await page.waitForTimeout(100); + const snapshots: eventWithTime[] = await page.evaluate('window.snapshots'); + + page = await browser.newPage(); + + await page.goto('about:blank'); + await page.evaluate(code); + + await hideMouseAnimation(page); + await page.evaluate(`let events = ${JSON.stringify(snapshots)}`); + await page.evaluate(` + const { Replayer } = rrweb; + const replayer = new Replayer(events, { + UNSAFE_replayCanvas: true, + }); + `); + // wait for iframe to get added and `preloadAllImages` to ge called + await page.waitForSelector('iframe'); + await page.evaluate(`replayer.play(500);`); + await page.waitForTimeout(50); + + const element = await page.$('iframe'); + const frameImage = await element!.screenshot(); + + expect(frameImage).toMatchImageSnapshot(); + }); +}); diff --git a/packages/rrweb/test/events/iframe.ts b/packages/rrweb/test/events/iframe.ts new file mode 100644 index 0000000000..d4110d2070 --- /dev/null +++ b/packages/rrweb/test/events/iframe.ts @@ -0,0 +1,591 @@ +import { EventType, eventWithTime, IncrementalSource } from '../../src/types'; + +const now = Date.now(); + +const events: eventWithTime[] = [ + { + type: EventType.DomContentLoaded, + data: {}, + timestamp: now, + }, + { + type: EventType.Load, + data: {}, + timestamp: now + 100, + }, + { + type: EventType.Meta, + data: { + href: 'http://localhost', + width: 1200, + height: 500, + }, + timestamp: now + 100, + }, + { + type: EventType.FullSnapshot, + data: { + node: { + id: 1, + type: 0, + childNodes: [ + { id: 2, name: 'html', type: 1, publicId: '', systemId: '' }, + { + id: 3, + type: 2, + tagName: 'html', + attributes: { lang: 'en' }, + childNodes: [ + { + id: 4, + type: 2, + tagName: 'head', + attributes: {}, + childNodes: [], + }, + { + id: 5, + type: 2, + tagName: 'body', + attributes: {}, + childNodes: [], + }, + ], + }, + ], + }, + initialOffset: { top: 0, left: 0 }, + }, + timestamp: now + 200, + }, + { + type: EventType.IncrementalSnapshot, + data: { + source: IncrementalSource.Mutation, + texts: [], + attributes: [], + removes: [], + adds: [ + { + parentId: 5, + nextId: null, + node: { + type: 2, + tagName: 'iframe', + attributes: { id: 'one' }, + childNodes: [], + id: 6, + }, + }, + ], + }, + timestamp: now + 500, + }, + // add iframe one + { + type: EventType.IncrementalSnapshot, + data: { + source: IncrementalSource.Mutation, + adds: [ + { + parentId: 6, + nextId: null, + node: { + type: 0, + childNodes: [ + { + type: 1, + name: 'html', + publicId: '', + systemId: '', + rootId: 7, + id: 8, + }, + { + type: 2, + tagName: 'html', + attributes: { lang: 'en' }, + childNodes: [ + { + type: 2, + tagName: 'head', + attributes: {}, + childNodes: [], + rootId: 7, + id: 10, + }, + { + type: 2, + tagName: 'body', + attributes: {}, + childNodes: [ + { + type: 2, + tagName: 'div', + attributes: {}, + childNodes: [ + { + type: 3, + textContent: '\n\t\tiframe 1\n\t', + rootId: 7, + id: 13, + }, + ], + rootId: 7, + id: 12, + }, + { type: 3, textContent: '\n\t', rootId: 7, id: 14 }, + { + type: 2, + tagName: 'script', + attributes: {}, + childNodes: [ + { + type: 3, + textContent: 'SCRIPT_PLACEHOLDER', + rootId: 7, + id: 16, + }, + ], + rootId: 7, + id: 15, + }, + { type: 3, textContent: '\t\n', rootId: 7, id: 17 }, + ], + rootId: 7, + id: 11, + }, + ], + rootId: 7, + id: 9, + }, + ], + id: 7, + }, + }, + ], + removes: [], + texts: [], + attributes: [], + isAttachIframe: true, + }, + timestamp: now + 500, + }, + { + type: EventType.IncrementalSnapshot, + data: { + source: IncrementalSource.Mutation, + texts: [], + attributes: [], + removes: [], + adds: [ + { + parentId: 5, + nextId: null, + node: { + type: 2, + tagName: 'iframe', + attributes: { id: 'two' }, + childNodes: [], + id: 38, + }, + }, + ], + }, + timestamp: now + 1000, + }, + // add iframe two + { + type: EventType.IncrementalSnapshot, + data: { + source: IncrementalSource.Mutation, + adds: [ + { + parentId: 38, + nextId: null, + node: { + type: 0, + childNodes: [ + { + type: 1, + name: 'html', + publicId: '', + systemId: '', + rootId: 39, + id: 40, + }, + { + type: 2, + tagName: 'html', + attributes: { lang: 'en' }, + childNodes: [ + { + type: 2, + tagName: 'head', + attributes: {}, + childNodes: [ + { type: 3, textContent: '\n ', rootId: 39, id: 43 }, + { + type: 2, + tagName: 'meta', + attributes: { charset: 'UTF-8' }, + childNodes: [], + rootId: 39, + id: 44, + }, + { type: 3, textContent: '\n ', rootId: 39, id: 45 }, + { + type: 2, + tagName: 'meta', + attributes: { + name: 'viewport', + content: 'width=device-width, initial-scale=1.0', + }, + childNodes: [], + rootId: 39, + id: 46, + }, + { type: 3, textContent: '\n ', rootId: 39, id: 47 }, + { + type: 2, + tagName: 'title', + attributes: {}, + childNodes: [ + { + type: 3, + textContent: 'iframe 2', + rootId: 39, + id: 49, + }, + ], + rootId: 39, + id: 48, + }, + { type: 3, textContent: '\n ', rootId: 39, id: 50 }, + ], + rootId: 39, + id: 42, + }, + { type: 3, textContent: '\n ', rootId: 39, id: 51 }, + { + type: 2, + tagName: 'body', + attributes: {}, + childNodes: [ + { + type: 3, + textContent: '\n iframe 2\n ', + rootId: 39, + id: 53, + }, + { + type: 2, + tagName: 'iframe', + attributes: { id: 'three', frameborder: '0' }, + childNodes: [], + rootId: 39, + id: 54, + }, + { type: 3, textContent: '\n ', rootId: 39, id: 55 }, + { + type: 2, + tagName: 'iframe', + attributes: { id: 'four', frameborder: '0' }, + childNodes: [], + rootId: 39, + id: 56, + }, + { type: 3, textContent: '\n \n\n', rootId: 39, id: 57 }, + ], + rootId: 39, + id: 52, + }, + ], + rootId: 39, + id: 41, + }, + ], + id: 39, + }, + }, + ], + removes: [], + texts: [], + attributes: [], + isAttachIframe: true, + }, + timestamp: now + 1000, + }, + // add iframe three + { + type: EventType.IncrementalSnapshot, + data: { + source: IncrementalSource.Mutation, + adds: [ + { + parentId: 54, + nextId: null, + node: { + type: 0, + childNodes: [ + { + type: 2, + tagName: 'html', + attributes: {}, + childNodes: [ + { + type: 2, + tagName: 'head', + attributes: {}, + childNodes: [], + rootId: 58, + id: 60, + }, + { + type: 2, + tagName: 'body', + attributes: {}, + childNodes: [], + rootId: 58, + id: 61, + }, + ], + rootId: 58, + id: 59, + }, + ], + id: 58, + }, + }, + ], + removes: [], + texts: [], + attributes: [], + isAttachIframe: true, + }, + timestamp: now + 1000, + }, + // add iframe four + { + type: EventType.IncrementalSnapshot, + data: { + source: IncrementalSource.Mutation, + adds: [ + { + parentId: 56, + nextId: null, + node: { + type: 0, + childNodes: [ + { + type: 1, + name: 'html', + publicId: '', + systemId: '', + rootId: 62, + id: 63, + }, + { + type: 2, + tagName: 'html', + attributes: { lang: 'en' }, + childNodes: [ + { + type: 2, + tagName: 'head', + attributes: {}, + childNodes: [ + { type: 3, textContent: '\n ', rootId: 62, id: 66 }, + { + type: 2, + tagName: 'meta', + attributes: { charset: 'UTF-8' }, + childNodes: [], + rootId: 62, + id: 67, + }, + { type: 3, textContent: '\n ', rootId: 62, id: 68 }, + { + type: 2, + tagName: 'meta', + attributes: { + name: 'viewport', + content: 'width=device-width, initial-scale=1.0', + }, + childNodes: [], + rootId: 62, + id: 69, + }, + { type: 3, textContent: '\n ', rootId: 62, id: 70 }, + { + type: 2, + tagName: 'title', + attributes: {}, + childNodes: [ + { + type: 3, + textContent: 'iframe 4', + rootId: 62, + id: 72, + }, + ], + rootId: 62, + id: 71, + }, + { type: 3, textContent: '\n ', rootId: 62, id: 73 }, + ], + rootId: 62, + id: 65, + }, + { type: 3, textContent: '\n ', rootId: 62, id: 74 }, + { + type: 2, + tagName: 'body', + attributes: {}, + childNodes: [ + { + type: 3, + textContent: '\n iframe 4\n \n ', + rootId: 62, + id: 76, + }, + { + type: 2, + tagName: 'script', + attributes: {}, + childNodes: [ + { + type: 3, + textContent: 'SCRIPT_PLACEHOLDER', + rootId: 62, + id: 78, + }, + ], + rootId: 62, + id: 77, + }, + { type: 3, textContent: '\n\n', rootId: 62, id: 79 }, + ], + rootId: 62, + id: 75, + }, + ], + rootId: 62, + id: 64, + }, + ], + id: 62, + }, + }, + ], + removes: [], + texts: [], + attributes: [], + isAttachIframe: true, + }, + timestamp: now + 1500, + }, + // add iframe five + { + type: EventType.IncrementalSnapshot, + data: { + source: IncrementalSource.Mutation, + adds: [ + { + parentId: 80, + nextId: null, + node: { + type: 0, + childNodes: [ + { + type: 2, + tagName: 'html', + attributes: {}, + childNodes: [ + { + type: 2, + tagName: 'head', + attributes: {}, + childNodes: [], + rootId: 81, + id: 83, + }, + { + type: 2, + tagName: 'body', + attributes: {}, + childNodes: [ + { + type: 2, + tagName: 'script', + attributes: {}, + childNodes: [ + { + type: 3, + textContent: 'SCRIPT_PLACEHOLDER', + rootId: 81, + id: 86, + }, + ], + rootId: 81, + id: 85, + }, + ], + rootId: 81, + id: 84, + }, + ], + rootId: 81, + id: 82, + }, + ], + id: 81, + }, + }, + ], + removes: [], + texts: [], + attributes: [], + isAttachIframe: true, + }, + timestamp: now + 2000, + }, + { + type: EventType.IncrementalSnapshot, + data: { + source: IncrementalSource.Mutation, + texts: [], + attributes: [], + removes: [], + adds: [ + { + parentId: 75, + nextId: null, + node: { + type: 2, + tagName: 'iframe', + attributes: { id: 'five' }, + childNodes: [], + rootId: 62, + id: 80, + }, + }, + ], + }, + timestamp: now + 2000, + }, + // remove the html element of iframe four + { + type: EventType.IncrementalSnapshot, + data: { + source: IncrementalSource.Mutation, + texts: [], + attributes: [], + removes: [{ parentId: 62, id: 64 }], + adds: [], + }, + timestamp: now + 2500, + }, +]; + +export default events; diff --git a/packages/rrweb/test/events/ordering.ts b/packages/rrweb/test/events/ordering.ts new file mode 100644 index 0000000000..083c687011 --- /dev/null +++ b/packages/rrweb/test/events/ordering.ts @@ -0,0 +1,123 @@ +import { EventType, eventWithTime, IncrementalSource } from '../../src/types'; + +const now = Date.now(); +const events: eventWithTime[] = [ + { + type: EventType.DomContentLoaded, + data: {}, + timestamp: now, + }, + { + type: EventType.Load, + data: {}, + timestamp: now + 10, + }, + { + type: EventType.Meta, + data: { + href: 'http://localhost', + width: 1000, + height: 800, + }, + timestamp: now + 10, + }, + // full snapshot: + { + data: { + node: { + id: 1, + type: 0, + childNodes: [ + { id: 2, name: 'html', type: 1, publicId: '', systemId: '' }, + { + id: 3, + type: 2, + tagName: 'html', + attributes: { lang: 'en' }, + childNodes: [ + { + id: 4, + type: 2, + tagName: 'head', + attributes: {}, + childNodes: [], + }, + { + id: 100, + type: 2, + tagName: 'body', + attributes: {}, + childNodes: [ + { + id: 101, + type: 2, + tagName: 'span', + attributes: {}, + childNodes: [ + { + id: 102, + type: 3, + textContent: 'Initial', + }, + ], + }, + ], + }, + ], + }, + ], + }, + initialOffset: { top: 0, left: 0 }, + }, + type: EventType.FullSnapshot, + timestamp: now + 20, + }, + // 1st mutation that modifies text content + { + data: { + adds: [], + texts: [ + { + id: 102, + value: 'Intermediate - incorrect', + } + ], + source: IncrementalSource.Mutation, + removes: [], + attributes: [], + }, + type: EventType.IncrementalSnapshot, + timestamp: now + 30, + }, + // 2nd mutation (with same timestamp) that modifies text content + { + data: { + adds: [], + texts: [ + { + id: 102, + value: 'Final - correct', + } + ], + source: IncrementalSource.Mutation, + removes: [], + attributes: [], + }, + type: EventType.IncrementalSnapshot, + timestamp: now + 30, + }, + // dummy - presence triggers a bug + { + data: { + adds: [], + texts: [], + source: IncrementalSource.Mutation, + removes: [], + attributes: [], + }, + type: EventType.IncrementalSnapshot, + timestamp: now + 35, + }, +]; + +export default events; diff --git a/test/events/style-sheet-rule-events.ts b/packages/rrweb/test/events/style-sheet-rule-events.ts similarity index 53% rename from test/events/style-sheet-rule-events.ts rename to packages/rrweb/test/events/style-sheet-rule-events.ts index 5740f50215..0536ea56e3 100644 --- a/test/events/style-sheet-rule-events.ts +++ b/packages/rrweb/test/events/style-sheet-rule-events.ts @@ -46,8 +46,7 @@ const events: eventWithTime[] = [ type: 2, tagName: 'style', attributes: { - 'data-jss': '', - 'data-meta': 'sk, Unthemed, Static', + 'data-meta': 'from full-snapshot, gets rule added at 500', }, childNodes: [ { @@ -55,7 +54,7 @@ const events: eventWithTime[] = [ type: 3, isStyle: true, textContent: - '\n.c01x {\n opacity: 1;\n transform: translateX(0);\n}\n', + '\n.css-added-at-200-overwritten-at-3000 {\n opacity: 1;\n transform: translateX(0);\n}\n', }, ], }, @@ -65,7 +64,7 @@ const events: eventWithTime[] = [ tagName: 'style', attributes: { _cssText: - '.css-1uxxxx3 { position: fixed; top: 0px; right: 0px; left: 4rem; z-index: 15; flex-shrink: 0; height: 0.25rem; overflow: hidden; background-color: rgb(17, 171, 209); }.css-1c9xxxx { height: 0.25rem; background-color: rgb(190, 232, 242); opacity: 0; transition: opacity 0.5s ease 0s; }.css-lsxxx { padding-left: 4rem; }', + '.css-added-at-200 { position: fixed; top: 0px; right: 0px; left: 4rem; z-index: 15; flex-shrink: 0; height: 0.25rem; overflow: hidden; background-color: rgb(17, 171, 209); }.css-added-at-200.alt { height: 0.25rem; background-color: rgb(190, 232, 242); opacity: 0; transition: opacity 0.5s ease 0s; }.css-added-at-200.alt2 { padding-left: 4rem; }', 'data-emotion': 'css', }, childNodes: [ @@ -79,7 +78,23 @@ const events: eventWithTime[] = [ type: 2, tagName: 'body', attributes: {}, - childNodes: [], + childNodes: [ + { + id: 108, + type: 2, + tagName: 'a', + attributes: { + class: 'css-added-at-1000-deleted-at-2500', + }, + childNodes: [ + { + id: 109, + type: 3, + textContent: 'string', + }, + ], + }, + ], }, ], }, @@ -90,6 +105,22 @@ const events: eventWithTime[] = [ type: EventType.FullSnapshot, timestamp: now + 100, }, + // mutation that adds style rule to existing stylesheet + { + data: { + id: 101, + adds: [ + { + rule: + '.css-added-at-400-overwritten-at-3000 {border: 1px solid blue;}', + index: 1, + }, + ], + source: IncrementalSource.StyleSheetRule, + }, + type: EventType.IncrementalSnapshot, + timestamp: now + 400, + }, // mutation that adds stylesheet { data: { @@ -111,7 +142,7 @@ const events: eventWithTime[] = [ type: 3, isStyle: true, textContent: - '\n.c011xx {\n padding: 1.3125rem;\n flex: none;\n width: 100%;\n}\n', + '\n.css-added-at-500 {\n padding: 1.3125rem;\n flex: none;\n width: 100%;\n}\n', }, nextId: null, parentId: 255, @@ -132,7 +163,7 @@ const events: eventWithTime[] = [ adds: [ { rule: - '.css-1fbxx79{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;min-width:60rem;min-height:100vh;}', + '.css-added-at-1000-deleted-at-2500{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;min-width:60rem;min-height:100vh;color:blue;}', index: 2, }, ], @@ -141,6 +172,50 @@ const events: eventWithTime[] = [ type: EventType.IncrementalSnapshot, timestamp: now + 1000, }, + { + data: { + id: 105, + removes: [ + { + index: 2, + }, + ], + source: IncrementalSource.StyleSheetRule, + }, + type: EventType.IncrementalSnapshot, + timestamp: now + 2500, + }, + // overwrite all contents of stylesheet + { + data: { + texts: [ + { + id: 102, + value: '.all-css-overwritten-at-3000 { color: indigo; }', + }, + ], + attributes: [], + removes: [], + adds: [], + source: IncrementalSource.Mutation, + }, + type: EventType.IncrementalSnapshot, + timestamp: now + 3000, + }, + { + data: { + id: 101, + adds: [ + { + rule: '.css-added-at-3100{color:blue;}', + index: 1, + }, + ], + source: IncrementalSource.StyleSheetRule, + }, + type: EventType.IncrementalSnapshot, + timestamp: now + 3100, + }, ]; export default events; diff --git a/packages/rrweb/test/events/webgl.ts b/packages/rrweb/test/events/webgl.ts new file mode 100644 index 0000000000..513c1ade48 --- /dev/null +++ b/packages/rrweb/test/events/webgl.ts @@ -0,0 +1,118 @@ +export default [ + { + type: 4, + data: { + href: '', + width: 1600, + height: 900, + }, + timestamp: 1636379531385, + }, + { + type: 2, + data: { + node: { + type: 0, + childNodes: [ + { type: 1, name: 'html', publicId: '', systemId: '', id: 2 }, + { + type: 2, + tagName: 'html', + attributes: { lang: 'en' }, + childNodes: [ + { + type: 2, + tagName: 'head', + attributes: {}, + childNodes: [ + { type: 3, textContent: '\n ', id: 5 }, + { + type: 2, + tagName: 'meta', + attributes: { charset: 'UTF-8' }, + childNodes: [], + id: 6, + }, + { type: 3, textContent: '\n ', id: 7 }, + { + type: 2, + tagName: 'meta', + attributes: { + name: 'viewport', + content: 'width=device-width, initial-scale=1.0', + }, + childNodes: [], + id: 8, + }, + { type: 3, textContent: '\n ', id: 9 }, + { + type: 2, + tagName: 'title', + attributes: {}, + childNodes: [{ type: 3, textContent: 'canvas', id: 11 }], + id: 10, + }, + { type: 3, textContent: '\n ', id: 12 }, + ], + id: 4, + }, + { type: 3, textContent: '\n ', id: 13 }, + { + type: 2, + tagName: 'body', + attributes: {}, + childNodes: [ + { type: 3, textContent: '\n ', id: 15 }, + { + type: 2, + tagName: 'canvas', + attributes: { + id: 'myCanvas', + width: '200', + height: '100', + style: 'border: 1px solid #000000', + }, + childNodes: [{ type: 3, textContent: '\n ', id: 17 }], + id: 16, + }, + { type: 3, textContent: '\n ', id: 18 }, + { + type: 2, + tagName: 'script', + attributes: {}, + childNodes: [ + { type: 3, textContent: 'SCRIPT_PLACEHOLDER', id: 20 }, + ], + id: 19, + }, + { type: 3, textContent: '\n \n\n', id: 21 }, + ], + id: 14, + }, + ], + id: 3, + }, + ], + id: 1, + }, + initialOffset: { left: 0, top: 0 }, + }, + timestamp: 1636379531389, + }, + { + type: 3, + data: { + source: 9, + id: 16, + type: 1, + property: 'clearColor', + args: [1, 0, 0, 1], + }, + timestamp: 1636379532355, + }, + { + type: 3, + data: { source: 9, id: 16, type: 1, property: 'clear', args: [16384] }, + timestamp: 1636379532356, + }, +]; diff --git a/packages/rrweb/test/html/assets/webgl-utils.js b/packages/rrweb/test/html/assets/webgl-utils.js new file mode 100644 index 0000000000..183a5acf41 --- /dev/null +++ b/packages/rrweb/test/html/assets/webgl-utils.js @@ -0,0 +1,1496 @@ +/* + * Copyright 2021 GFXFundamentals. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of GFXFundamentals. nor the names of his + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +(function (root, factory) { + // eslint-disable-line + if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + define([], function () { + return factory.call(root); + }); + } else { + // Browser globals + root.webglUtils = factory.call(root); + } +})(this, function () { + 'use strict'; + + const topWindow = this; + + /** @module webgl-utils */ + + function isInIFrame(w) { + w = w || topWindow; + return w !== w.top; + } + + if (!isInIFrame()) { + console.log( + '%c%s', + 'color:blue;font-weight:bold;', + 'for more about webgl-utils.js see:', + ); // eslint-disable-line + console.log( + '%c%s', + 'color:blue;font-weight:bold;', + 'https://webglfundamentals.org/webgl/lessons/webgl-boilerplate.html', + ); // eslint-disable-line + } + + /** + * Wrapped logging function. + * @param {string} msg The message to log. + */ + function error(msg) { + if (topWindow.console) { + if (topWindow.console.error) { + topWindow.console.error(msg); + } else if (topWindow.console.log) { + topWindow.console.log(msg); + } + } + } + + /** + * Error Callback + * @callback ErrorCallback + * @param {string} msg error message. + * @memberOf module:webgl-utils + */ + + /** + * Loads a shader. + * @param {WebGLRenderingContext} gl The WebGLRenderingContext to use. + * @param {string} shaderSource The shader source. + * @param {number} shaderType The type of shader. + * @param {module:webgl-utils.ErrorCallback} opt_errorCallback callback for errors. + * @return {WebGLShader} The created shader. + */ + function loadShader(gl, shaderSource, shaderType, opt_errorCallback) { + const errFn = opt_errorCallback || error; + // Create the shader object + const shader = gl.createShader(shaderType); + + // Load the shader source + gl.shaderSource(shader, shaderSource); + + // Compile the shader + gl.compileShader(shader); + + // Check the compile status + const compiled = gl.getShaderParameter(shader, gl.COMPILE_STATUS); + if (!compiled) { + // Something went wrong during compilation; get the error + const lastError = gl.getShaderInfoLog(shader); + errFn( + "*** Error compiling shader '" + + shader + + "':" + + lastError + + `\n` + + shaderSource + .split('\n') + .map((l, i) => `${i + 1}: ${l}`) + .join('\n'), + ); + gl.deleteShader(shader); + return null; + } + + return shader; + } + + /** + * Creates a program, attaches shaders, binds attrib locations, links the + * program and calls useProgram. + * @param {WebGLShader[]} shaders The shaders to attach + * @param {string[]} [opt_attribs] An array of attribs names. Locations will be assigned by index if not passed in + * @param {number[]} [opt_locations] The locations for the. A parallel array to opt_attribs letting you assign locations. + * @param {module:webgl-utils.ErrorCallback} opt_errorCallback callback for errors. By default it just prints an error to the console + * on error. If you want something else pass an callback. It's passed an error message. + * @memberOf module:webgl-utils + */ + function createProgram( + gl, + shaders, + opt_attribs, + opt_locations, + opt_errorCallback, + ) { + const errFn = opt_errorCallback || error; + const program = gl.createProgram(); + shaders.forEach(function (shader) { + gl.attachShader(program, shader); + }); + if (opt_attribs) { + opt_attribs.forEach(function (attrib, ndx) { + gl.bindAttribLocation( + program, + opt_locations ? opt_locations[ndx] : ndx, + attrib, + ); + }); + } + gl.linkProgram(program); + + // Check the link status + const linked = gl.getProgramParameter(program, gl.LINK_STATUS); + if (!linked) { + // something went wrong with the link + const lastError = gl.getProgramInfoLog(program); + errFn('Error in program linking:' + lastError); + + gl.deleteProgram(program); + return null; + } + return program; + } + + /** + * Loads a shader from a script tag. + * @param {WebGLRenderingContext} gl The WebGLRenderingContext to use. + * @param {string} scriptId The id of the script tag. + * @param {number} opt_shaderType The type of shader. If not passed in it will + * be derived from the type of the script tag. + * @param {module:webgl-utils.ErrorCallback} opt_errorCallback callback for errors. + * @return {WebGLShader} The created shader. + */ + function createShaderFromScript( + gl, + scriptId, + opt_shaderType, + opt_errorCallback, + ) { + let shaderSource = ''; + let shaderType; + const shaderScript = document.getElementById(scriptId); + if (!shaderScript) { + throw '*** Error: unknown script element' + scriptId; + } + shaderSource = shaderScript.text; + + if (!opt_shaderType) { + if (shaderScript.type === 'x-shader/x-vertex') { + shaderType = gl.VERTEX_SHADER; + } else if (shaderScript.type === 'x-shader/x-fragment') { + shaderType = gl.FRAGMENT_SHADER; + } else if ( + shaderType !== gl.VERTEX_SHADER && + shaderType !== gl.FRAGMENT_SHADER + ) { + throw '*** Error: unknown shader type'; + } + } + + return loadShader( + gl, + shaderSource, + opt_shaderType ? opt_shaderType : shaderType, + opt_errorCallback, + ); + } + + const defaultShaderType = ['VERTEX_SHADER', 'FRAGMENT_SHADER']; + + /** + * Creates a program from 2 script tags. + * + * @param {WebGLRenderingContext} gl The WebGLRenderingContext + * to use. + * @param {string[]} shaderScriptIds Array of ids of the script + * tags for the shaders. The first is assumed to be the + * vertex shader, the second the fragment shader. + * @param {string[]} [opt_attribs] An array of attribs names. Locations will be assigned by index if not passed in + * @param {number[]} [opt_locations] The locations for the. A parallel array to opt_attribs letting you assign locations. + * @param {module:webgl-utils.ErrorCallback} opt_errorCallback callback for errors. By default it just prints an error to the console + * on error. If you want something else pass an callback. It's passed an error message. + * @return {WebGLProgram} The created program. + * @memberOf module:webgl-utils + */ + function createProgramFromScripts( + gl, + shaderScriptIds, + opt_attribs, + opt_locations, + opt_errorCallback, + ) { + const shaders = []; + for (let ii = 0; ii < shaderScriptIds.length; ++ii) { + shaders.push( + createShaderFromScript( + gl, + shaderScriptIds[ii], + gl[defaultShaderType[ii]], + opt_errorCallback, + ), + ); + } + return createProgram( + gl, + shaders, + opt_attribs, + opt_locations, + opt_errorCallback, + ); + } + + /** + * Creates a program from 2 sources. + * + * @param {WebGLRenderingContext} gl The WebGLRenderingContext + * to use. + * @param {string[]} shaderSourcess Array of sources for the + * shaders. The first is assumed to be the vertex shader, + * the second the fragment shader. + * @param {string[]} [opt_attribs] An array of attribs names. Locations will be assigned by index if not passed in + * @param {number[]} [opt_locations] The locations for the. A parallel array to opt_attribs letting you assign locations. + * @param {module:webgl-utils.ErrorCallback} opt_errorCallback callback for errors. By default it just prints an error to the console + * on error. If you want something else pass an callback. It's passed an error message. + * @return {WebGLProgram} The created program. + * @memberOf module:webgl-utils + */ + function createProgramFromSources( + gl, + shaderSources, + opt_attribs, + opt_locations, + opt_errorCallback, + ) { + const shaders = []; + for (let ii = 0; ii < shaderSources.length; ++ii) { + shaders.push( + loadShader( + gl, + shaderSources[ii], + gl[defaultShaderType[ii]], + opt_errorCallback, + ), + ); + } + return createProgram( + gl, + shaders, + opt_attribs, + opt_locations, + opt_errorCallback, + ); + } + + /** + * Returns the corresponding bind point for a given sampler type + */ + function getBindPointForSamplerType(gl, type) { + if (type === gl.SAMPLER_2D) return gl.TEXTURE_2D; // eslint-disable-line + if (type === gl.SAMPLER_CUBE) return gl.TEXTURE_CUBE_MAP; // eslint-disable-line + return undefined; + } + + /** + * @typedef {Object.} Setters + */ + + /** + * Creates setter functions for all uniforms of a shader + * program. + * + * @see {@link module:webgl-utils.setUniforms} + * + * @param {WebGLProgram} program the program to create setters for. + * @returns {Object.} an object with a setter by name for each uniform + * @memberOf module:webgl-utils + */ + function createUniformSetters(gl, program) { + let textureUnit = 0; + + /** + * Creates a setter for a uniform of the given program with it's + * location embedded in the setter. + * @param {WebGLProgram} program + * @param {WebGLUniformInfo} uniformInfo + * @returns {function} the created setter. + */ + function createUniformSetter(program, uniformInfo) { + const location = gl.getUniformLocation(program, uniformInfo.name); + const type = uniformInfo.type; + // Check if this uniform is an array + const isArray = + uniformInfo.size > 1 && uniformInfo.name.substr(-3) === '[0]'; + if (type === gl.FLOAT && isArray) { + return function (v) { + gl.uniform1fv(location, v); + }; + } + if (type === gl.FLOAT) { + return function (v) { + gl.uniform1f(location, v); + }; + } + if (type === gl.FLOAT_VEC2) { + return function (v) { + gl.uniform2fv(location, v); + }; + } + if (type === gl.FLOAT_VEC3) { + return function (v) { + gl.uniform3fv(location, v); + }; + } + if (type === gl.FLOAT_VEC4) { + return function (v) { + gl.uniform4fv(location, v); + }; + } + if (type === gl.INT && isArray) { + return function (v) { + gl.uniform1iv(location, v); + }; + } + if (type === gl.INT) { + return function (v) { + gl.uniform1i(location, v); + }; + } + if (type === gl.INT_VEC2) { + return function (v) { + gl.uniform2iv(location, v); + }; + } + if (type === gl.INT_VEC3) { + return function (v) { + gl.uniform3iv(location, v); + }; + } + if (type === gl.INT_VEC4) { + return function (v) { + gl.uniform4iv(location, v); + }; + } + if (type === gl.BOOL) { + return function (v) { + gl.uniform1iv(location, v); + }; + } + if (type === gl.BOOL_VEC2) { + return function (v) { + gl.uniform2iv(location, v); + }; + } + if (type === gl.BOOL_VEC3) { + return function (v) { + gl.uniform3iv(location, v); + }; + } + if (type === gl.BOOL_VEC4) { + return function (v) { + gl.uniform4iv(location, v); + }; + } + if (type === gl.FLOAT_MAT2) { + return function (v) { + gl.uniformMatrix2fv(location, false, v); + }; + } + if (type === gl.FLOAT_MAT3) { + return function (v) { + gl.uniformMatrix3fv(location, false, v); + }; + } + if (type === gl.FLOAT_MAT4) { + return function (v) { + gl.uniformMatrix4fv(location, false, v); + }; + } + if ((type === gl.SAMPLER_2D || type === gl.SAMPLER_CUBE) && isArray) { + const units = []; + for (let ii = 0; ii < info.size; ++ii) { + units.push(textureUnit++); + } + return (function (bindPoint, units) { + return function (textures) { + gl.uniform1iv(location, units); + textures.forEach(function (texture, index) { + gl.activeTexture(gl.TEXTURE0 + units[index]); + gl.bindTexture(bindPoint, texture); + }); + }; + })(getBindPointForSamplerType(gl, type), units); + } + if (type === gl.SAMPLER_2D || type === gl.SAMPLER_CUBE) { + return (function (bindPoint, unit) { + return function (texture) { + gl.uniform1i(location, unit); + gl.activeTexture(gl.TEXTURE0 + unit); + gl.bindTexture(bindPoint, texture); + }; + })(getBindPointForSamplerType(gl, type), textureUnit++); + } + throw 'unknown type: 0x' + type.toString(16); // we should never get here. + } + + const uniformSetters = {}; + const numUniforms = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS); + + for (let ii = 0; ii < numUniforms; ++ii) { + const uniformInfo = gl.getActiveUniform(program, ii); + if (!uniformInfo) { + break; + } + let name = uniformInfo.name; + // remove the array suffix. + if (name.substr(-3) === '[0]') { + name = name.substr(0, name.length - 3); + } + const setter = createUniformSetter(program, uniformInfo); + uniformSetters[name] = setter; + } + return uniformSetters; + } + + /** + * Set uniforms and binds related textures. + * + * Example: + * + * let programInfo = createProgramInfo( + * gl, ["some-vs", "some-fs"]); + * + * let tex1 = gl.createTexture(); + * let tex2 = gl.createTexture(); + * + * ... assume we setup the textures with data ... + * + * let uniforms = { + * u_someSampler: tex1, + * u_someOtherSampler: tex2, + * u_someColor: [1,0,0,1], + * u_somePosition: [0,1,1], + * u_someMatrix: [ + * 1,0,0,0, + * 0,1,0,0, + * 0,0,1,0, + * 0,0,0,0, + * ], + * }; + * + * gl.useProgram(program); + * + * This will automatically bind the textures AND set the + * uniforms. + * + * setUniforms(programInfo.uniformSetters, uniforms); + * + * For the example above it is equivalent to + * + * let texUnit = 0; + * gl.activeTexture(gl.TEXTURE0 + texUnit); + * gl.bindTexture(gl.TEXTURE_2D, tex1); + * gl.uniform1i(u_someSamplerLocation, texUnit++); + * gl.activeTexture(gl.TEXTURE0 + texUnit); + * gl.bindTexture(gl.TEXTURE_2D, tex2); + * gl.uniform1i(u_someSamplerLocation, texUnit++); + * gl.uniform4fv(u_someColorLocation, [1, 0, 0, 1]); + * gl.uniform3fv(u_somePositionLocation, [0, 1, 1]); + * gl.uniformMatrix4fv(u_someMatrix, false, [ + * 1,0,0,0, + * 0,1,0,0, + * 0,0,1,0, + * 0,0,0,0, + * ]); + * + * Note it is perfectly reasonable to call `setUniforms` multiple times. For example + * + * let uniforms = { + * u_someSampler: tex1, + * u_someOtherSampler: tex2, + * }; + * + * let moreUniforms { + * u_someColor: [1,0,0,1], + * u_somePosition: [0,1,1], + * u_someMatrix: [ + * 1,0,0,0, + * 0,1,0,0, + * 0,0,1,0, + * 0,0,0,0, + * ], + * }; + * + * setUniforms(programInfo.uniformSetters, uniforms); + * setUniforms(programInfo.uniformSetters, moreUniforms); + * + * @param {Object.|module:webgl-utils.ProgramInfo} setters the setters returned from + * `createUniformSetters` or a ProgramInfo from {@link module:webgl-utils.createProgramInfo}. + * @param {Object.} an object with values for the + * uniforms. + * @memberOf module:webgl-utils + */ + function setUniforms(setters, ...values) { + setters = setters.uniformSetters || setters; + for (const uniforms of values) { + Object.keys(uniforms).forEach(function (name) { + const setter = setters[name]; + if (setter) { + setter(uniforms[name]); + } + }); + } + } + + /** + * Creates setter functions for all attributes of a shader + * program. You can pass this to {@link module:webgl-utils.setBuffersAndAttributes} to set all your buffers and attributes. + * + * @see {@link module:webgl-utils.setAttributes} for example + * @param {WebGLProgram} program the program to create setters for. + * @return {Object.} an object with a setter for each attribute by name. + * @memberOf module:webgl-utils + */ + function createAttributeSetters(gl, program) { + const attribSetters = {}; + + function createAttribSetter(index) { + return function (b) { + if (b.value) { + gl.disableVertexAttribArray(index); + switch (b.value.length) { + case 4: + gl.vertexAttrib4fv(index, b.value); + break; + case 3: + gl.vertexAttrib3fv(index, b.value); + break; + case 2: + gl.vertexAttrib2fv(index, b.value); + break; + case 1: + gl.vertexAttrib1fv(index, b.value); + break; + default: + throw new Error( + 'the length of a float constant value must be between 1 and 4!', + ); + } + } else { + gl.bindBuffer(gl.ARRAY_BUFFER, b.buffer); + gl.enableVertexAttribArray(index); + gl.vertexAttribPointer( + index, + b.numComponents || b.size, + b.type || gl.FLOAT, + b.normalize || false, + b.stride || 0, + b.offset || 0, + ); + } + }; + } + + const numAttribs = gl.getProgramParameter(program, gl.ACTIVE_ATTRIBUTES); + for (let ii = 0; ii < numAttribs; ++ii) { + const attribInfo = gl.getActiveAttrib(program, ii); + if (!attribInfo) { + break; + } + const index = gl.getAttribLocation(program, attribInfo.name); + attribSetters[attribInfo.name] = createAttribSetter(index); + } + + return attribSetters; + } + + /** + * Sets attributes and binds buffers (deprecated... use {@link module:webgl-utils.setBuffersAndAttributes}) + * + * Example: + * + * let program = createProgramFromScripts( + * gl, ["some-vs", "some-fs"]); + * + * let attribSetters = createAttributeSetters(program); + * + * let positionBuffer = gl.createBuffer(); + * let texcoordBuffer = gl.createBuffer(); + * + * let attribs = { + * a_position: {buffer: positionBuffer, numComponents: 3}, + * a_texcoord: {buffer: texcoordBuffer, numComponents: 2}, + * }; + * + * gl.useProgram(program); + * + * This will automatically bind the buffers AND set the + * attributes. + * + * setAttributes(attribSetters, attribs); + * + * Properties of attribs. For each attrib you can add + * properties: + * + * * type: the type of data in the buffer. Default = gl.FLOAT + * * normalize: whether or not to normalize the data. Default = false + * * stride: the stride. Default = 0 + * * offset: offset into the buffer. Default = 0 + * + * For example if you had 3 value float positions, 2 value + * float texcoord and 4 value uint8 colors you'd setup your + * attribs like this + * + * let attribs = { + * a_position: {buffer: positionBuffer, numComponents: 3}, + * a_texcoord: {buffer: texcoordBuffer, numComponents: 2}, + * a_color: { + * buffer: colorBuffer, + * numComponents: 4, + * type: gl.UNSIGNED_BYTE, + * normalize: true, + * }, + * }; + * + * @param {Object.|model:webgl-utils.ProgramInfo} setters Attribute setters as returned from createAttributeSetters or a ProgramInfo as returned {@link module:webgl-utils.createProgramInfo} + * @param {Object.} attribs AttribInfos mapped by attribute name. + * @memberOf module:webgl-utils + * @deprecated use {@link module:webgl-utils.setBuffersAndAttributes} + */ + function setAttributes(setters, attribs) { + setters = setters.attribSetters || setters; + Object.keys(attribs).forEach(function (name) { + const setter = setters[name]; + if (setter) { + setter(attribs[name]); + } + }); + } + + /** + * Creates a vertex array object and then sets the attributes + * on it + * + * @param {WebGLRenderingContext} gl The WebGLRenderingContext + * to use. + * @param {Object.} setters Attribute setters as returned from createAttributeSetters + * @param {Object.} attribs AttribInfos mapped by attribute name. + * @param {WebGLBuffer} [indices] an optional ELEMENT_ARRAY_BUFFER of indices + */ + function createVAOAndSetAttributes(gl, setters, attribs, indices) { + const vao = gl.createVertexArray(); + gl.bindVertexArray(vao); + setAttributes(setters, attribs); + if (indices) { + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indices); + } + // We unbind this because otherwise any change to ELEMENT_ARRAY_BUFFER + // like when creating buffers for other stuff will mess up this VAO's binding + gl.bindVertexArray(null); + return vao; + } + + /** + * Creates a vertex array object and then sets the attributes + * on it + * + * @param {WebGLRenderingContext} gl The WebGLRenderingContext + * to use. + * @param {Object.| module:webgl-utils.ProgramInfo} programInfo as returned from createProgramInfo or Attribute setters as returned from createAttributeSetters + * @param {module:webgl-utils:BufferInfo} bufferInfo BufferInfo as returned from createBufferInfoFromArrays etc... + * @param {WebGLBuffer} [indices] an optional ELEMENT_ARRAY_BUFFER of indices + */ + function createVAOFromBufferInfo(gl, programInfo, bufferInfo) { + return createVAOAndSetAttributes( + gl, + programInfo.attribSetters || programInfo, + bufferInfo.attribs, + bufferInfo.indices, + ); + } + + /** + * @typedef {Object} ProgramInfo + * @property {WebGLProgram} program A shader program + * @property {Object} uniformSetters: object of setters as returned from createUniformSetters, + * @property {Object} attribSetters: object of setters as returned from createAttribSetters, + * @memberOf module:webgl-utils + */ + + /** + * Creates a ProgramInfo from 2 sources. + * + * A ProgramInfo contains + * + * programInfo = { + * program: WebGLProgram, + * uniformSetters: object of setters as returned from createUniformSetters, + * attribSetters: object of setters as returned from createAttribSetters, + * } + * + * @param {WebGLRenderingContext} gl The WebGLRenderingContext + * to use. + * @param {string[]} shaderSourcess Array of sources for the + * shaders or ids. The first is assumed to be the vertex shader, + * the second the fragment shader. + * @param {string[]} [opt_attribs] An array of attribs names. Locations will be assigned by index if not passed in + * @param {number[]} [opt_locations] The locations for the. A parallel array to opt_attribs letting you assign locations. + * @param {module:webgl-utils.ErrorCallback} opt_errorCallback callback for errors. By default it just prints an error to the console + * on error. If you want something else pass an callback. It's passed an error message. + * @return {module:webgl-utils.ProgramInfo} The created program. + * @memberOf module:webgl-utils + */ + function createProgramInfo( + gl, + shaderSources, + opt_attribs, + opt_locations, + opt_errorCallback, + ) { + shaderSources = shaderSources.map(function (source) { + const script = document.getElementById(source); + return script ? script.text : source; + }); + const program = webglUtils.createProgramFromSources( + gl, + shaderSources, + opt_attribs, + opt_locations, + opt_errorCallback, + ); + if (!program) { + return null; + } + const uniformSetters = createUniformSetters(gl, program); + const attribSetters = createAttributeSetters(gl, program); + return { + program: program, + uniformSetters: uniformSetters, + attribSetters: attribSetters, + }; + } + + /** + * Sets attributes and buffers including the `ELEMENT_ARRAY_BUFFER` if appropriate + * + * Example: + * + * let programInfo = createProgramInfo( + * gl, ["some-vs", "some-fs"]); + * + * let arrays = { + * position: { numComponents: 3, data: [0, 0, 0, 10, 0, 0, 0, 10, 0, 10, 10, 0], }, + * texcoord: { numComponents: 2, data: [0, 0, 0, 1, 1, 0, 1, 1], }, + * }; + * + * let bufferInfo = createBufferInfoFromArrays(gl, arrays); + * + * gl.useProgram(programInfo.program); + * + * This will automatically bind the buffers AND set the + * attributes. + * + * setBuffersAndAttributes(programInfo.attribSetters, bufferInfo); + * + * For the example above it is equivilent to + * + * gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer); + * gl.enableVertexAttribArray(a_positionLocation); + * gl.vertexAttribPointer(a_positionLocation, 3, gl.FLOAT, false, 0, 0); + * gl.bindBuffer(gl.ARRAY_BUFFER, texcoordBuffer); + * gl.enableVertexAttribArray(a_texcoordLocation); + * gl.vertexAttribPointer(a_texcoordLocation, 4, gl.FLOAT, false, 0, 0); + * + * @param {WebGLRenderingContext} gl A WebGLRenderingContext. + * @param {Object.} setters Attribute setters as returned from `createAttributeSetters` + * @param {module:webgl-utils.BufferInfo} buffers a BufferInfo as returned from `createBufferInfoFromArrays`. + * @memberOf module:webgl-utils + */ + function setBuffersAndAttributes(gl, setters, buffers) { + setAttributes(setters, buffers.attribs); + if (buffers.indices) { + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, buffers.indices); + } + } + + // Add your prefix here. + const browserPrefixes = ['', 'MOZ_', 'OP_', 'WEBKIT_']; + + /** + * Given an extension name like WEBGL_compressed_texture_s3tc + * returns the supported version extension, like + * WEBKIT_WEBGL_compressed_teture_s3tc + * @param {string} name Name of extension to look for + * @return {WebGLExtension} The extension or undefined if not + * found. + * @memberOf module:webgl-utils + */ + function getExtensionWithKnownPrefixes(gl, name) { + for (let ii = 0; ii < browserPrefixes.length; ++ii) { + const prefixedName = browserPrefixes[ii] + name; + const ext = gl.getExtension(prefixedName); + if (ext) { + return ext; + } + } + return undefined; + } + + /** + * Resize a canvas to match the size its displayed. + * @param {HTMLCanvasElement} canvas The canvas to resize. + * @param {number} [multiplier] amount to multiply by. + * Pass in window.devicePixelRatio for native pixels. + * @return {boolean} true if the canvas was resized. + * @memberOf module:webgl-utils + */ + function resizeCanvasToDisplaySize(canvas, multiplier) { + multiplier = multiplier || 1; + const width = (canvas.clientWidth * multiplier) | 0; + const height = (canvas.clientHeight * multiplier) | 0; + if (canvas.width !== width || canvas.height !== height) { + canvas.width = width; + canvas.height = height; + return true; + } + return false; + } + + // Add `push` to a typed array. It just keeps a 'cursor' + // and allows use to `push` values into the array so we + // don't have to manually compute offsets + function augmentTypedArray(typedArray, numComponents) { + let cursor = 0; + typedArray.push = function () { + for (let ii = 0; ii < arguments.length; ++ii) { + const value = arguments[ii]; + if ( + value instanceof Array || + (value.buffer && value.buffer instanceof ArrayBuffer) + ) { + for (let jj = 0; jj < value.length; ++jj) { + typedArray[cursor++] = value[jj]; + } + } else { + typedArray[cursor++] = value; + } + } + }; + typedArray.reset = function (opt_index) { + cursor = opt_index || 0; + }; + typedArray.numComponents = numComponents; + Object.defineProperty(typedArray, 'numElements', { + get: function () { + return (this.length / this.numComponents) | 0; + }, + }); + return typedArray; + } + + /** + * creates a typed array with a `push` function attached + * so that you can easily *push* values. + * + * `push` can take multiple arguments. If an argument is an array each element + * of the array will be added to the typed array. + * + * Example: + * + * let array = createAugmentedTypedArray(3, 2); // creates a Float32Array with 6 values + * array.push(1, 2, 3); + * array.push([4, 5, 6]); + * // array now contains [1, 2, 3, 4, 5, 6] + * + * Also has `numComponents` and `numElements` properties. + * + * @param {number} numComponents number of components + * @param {number} numElements number of elements. The total size of the array will be `numComponents * numElements`. + * @param {constructor} opt_type A constructor for the type. Default = `Float32Array`. + * @return {ArrayBuffer} A typed array. + * @memberOf module:webgl-utils + */ + function createAugmentedTypedArray(numComponents, numElements, opt_type) { + const Type = opt_type || Float32Array; + return augmentTypedArray( + new Type(numComponents * numElements), + numComponents, + ); + } + + function createBufferFromTypedArray(gl, array, type, drawType) { + type = type || gl.ARRAY_BUFFER; + const buffer = gl.createBuffer(); + gl.bindBuffer(type, buffer); + gl.bufferData(type, array, drawType || gl.STATIC_DRAW); + return buffer; + } + + function allButIndices(name) { + return name !== 'indices'; + } + + function createMapping(obj) { + const mapping = {}; + Object.keys(obj) + .filter(allButIndices) + .forEach(function (key) { + mapping['a_' + key] = key; + }); + return mapping; + } + + function getGLTypeForTypedArray(gl, typedArray) { + if (typedArray instanceof Int8Array) { + return gl.BYTE; + } // eslint-disable-line + if (typedArray instanceof Uint8Array) { + return gl.UNSIGNED_BYTE; + } // eslint-disable-line + if (typedArray instanceof Int16Array) { + return gl.SHORT; + } // eslint-disable-line + if (typedArray instanceof Uint16Array) { + return gl.UNSIGNED_SHORT; + } // eslint-disable-line + if (typedArray instanceof Int32Array) { + return gl.INT; + } // eslint-disable-line + if (typedArray instanceof Uint32Array) { + return gl.UNSIGNED_INT; + } // eslint-disable-line + if (typedArray instanceof Float32Array) { + return gl.FLOAT; + } // eslint-disable-line + throw 'unsupported typed array type'; + } + + // This is really just a guess. Though I can't really imagine using + // anything else? Maybe for some compression? + function getNormalizationForTypedArray(typedArray) { + if (typedArray instanceof Int8Array) { + return true; + } // eslint-disable-line + if (typedArray instanceof Uint8Array) { + return true; + } // eslint-disable-line + return false; + } + + function isArrayBuffer(a) { + return a.buffer && a.buffer instanceof ArrayBuffer; + } + + function guessNumComponentsFromName(name, length) { + let numComponents; + if (name.indexOf('coord') >= 0) { + numComponents = 2; + } else if (name.indexOf('color') >= 0) { + numComponents = 4; + } else { + numComponents = 3; // position, normals, indices ... + } + + if (length % numComponents > 0) { + throw 'can not guess numComponents. You should specify it.'; + } + + return numComponents; + } + + function makeTypedArray(array, name) { + if (isArrayBuffer(array)) { + return array; + } + + if (array.data && isArrayBuffer(array.data)) { + return array.data; + } + + if (Array.isArray(array)) { + array = { + data: array, + }; + } + + if (!array.numComponents) { + array.numComponents = guessNumComponentsFromName(name, array.length); + } + + let type = array.type; + if (!type) { + if (name === 'indices') { + type = Uint16Array; + } + } + const typedArray = createAugmentedTypedArray( + array.numComponents, + (array.data.length / array.numComponents) | 0, + type, + ); + typedArray.push(array.data); + return typedArray; + } + + /** + * @typedef {Object} AttribInfo + * @property {number} [numComponents] the number of components for this attribute. + * @property {number} [size] the number of components for this attribute. + * @property {number} [type] the type of the attribute (eg. `gl.FLOAT`, `gl.UNSIGNED_BYTE`, etc...) Default = `gl.FLOAT` + * @property {boolean} [normalized] whether or not to normalize the data. Default = false + * @property {number} [offset] offset into buffer in bytes. Default = 0 + * @property {number} [stride] the stride in bytes per element. Default = 0 + * @property {WebGLBuffer} buffer the buffer that contains the data for this attribute + * @memberOf module:webgl-utils + */ + + /** + * Creates a set of attribute data and WebGLBuffers from set of arrays + * + * Given + * + * let arrays = { + * position: { numComponents: 3, data: [0, 0, 0, 10, 0, 0, 0, 10, 0, 10, 10, 0], }, + * texcoord: { numComponents: 2, data: [0, 0, 0, 1, 1, 0, 1, 1], }, + * normal: { numComponents: 3, data: [0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1], }, + * color: { numComponents: 4, data: [255, 255, 255, 255, 255, 0, 0, 255, 0, 0, 255, 255], type: Uint8Array, }, + * indices: { numComponents: 3, data: [0, 1, 2, 1, 2, 3], }, + * }; + * + * returns something like + * + * let attribs = { + * a_position: { numComponents: 3, type: gl.FLOAT, normalize: false, buffer: WebGLBuffer, }, + * a_texcoord: { numComponents: 2, type: gl.FLOAT, normalize: false, buffer: WebGLBuffer, }, + * a_normal: { numComponents: 3, type: gl.FLOAT, normalize: false, buffer: WebGLBuffer, }, + * a_color: { numComponents: 4, type: gl.UNSIGNED_BYTE, normalize: true, buffer: WebGLBuffer, }, + * }; + * + * @param {WebGLRenderingContext} gl The webgl rendering context. + * @param {Object.} arrays The arrays + * @param {Object.} [opt_mapping] mapping from attribute name to array name. + * if not specified defaults to "a_name" -> "name". + * @return {Object.} the attribs + * @memberOf module:webgl-utils + */ + function createAttribsFromArrays(gl, arrays, opt_mapping) { + const mapping = opt_mapping || createMapping(arrays); + const attribs = {}; + Object.keys(mapping).forEach(function (attribName) { + const bufferName = mapping[attribName]; + const origArray = arrays[bufferName]; + if (origArray.value) { + attribs[attribName] = { + value: origArray.value, + }; + } else { + const array = makeTypedArray(origArray, bufferName); + attribs[attribName] = { + buffer: createBufferFromTypedArray(gl, array), + numComponents: + origArray.numComponents || + array.numComponents || + guessNumComponentsFromName(bufferName), + type: getGLTypeForTypedArray(gl, array), + normalize: getNormalizationForTypedArray(array), + }; + } + }); + return attribs; + } + + function getArray(array) { + return array.length ? array : array.data; + } + + const texcoordRE = /coord|texture/i; + const colorRE = /color|colour/i; + + function guessNumComponentsFromName(name, length) { + let numComponents; + if (texcoordRE.test(name)) { + numComponents = 2; + } else if (colorRE.test(name)) { + numComponents = 4; + } else { + numComponents = 3; // position, normals, indices ... + } + + if (length % numComponents > 0) { + throw new Error( + `Can not guess numComponents for attribute '${name}'. Tried ${numComponents} but ${length} values is not evenly divisible by ${numComponents}. You should specify it.`, + ); + } + + return numComponents; + } + + function getNumComponents(array, arrayName) { + return ( + array.numComponents || + array.size || + guessNumComponentsFromName(arrayName, getArray(array).length) + ); + } + + /** + * tries to get the number of elements from a set of arrays. + */ + const positionKeys = ['position', 'positions', 'a_position']; + function getNumElementsFromNonIndexedArrays(arrays) { + let key; + for (const k of positionKeys) { + if (k in arrays) { + key = k; + break; + } + } + key = key || Object.keys(arrays)[0]; + const array = arrays[key]; + const length = getArray(array).length; + const numComponents = getNumComponents(array, key); + const numElements = length / numComponents; + if (length % numComponents > 0) { + throw new Error( + `numComponents ${numComponents} not correct for length ${length}`, + ); + } + return numElements; + } + + /** + * @typedef {Object} BufferInfo + * @property {number} numElements The number of elements to pass to `gl.drawArrays` or `gl.drawElements`. + * @property {WebGLBuffer} [indices] The indices `ELEMENT_ARRAY_BUFFER` if any indices exist. + * @property {Object.} attribs The attribs approriate to call `setAttributes` + * @memberOf module:webgl-utils + */ + + /** + * Creates a BufferInfo from an object of arrays. + * + * This can be passed to {@link module:webgl-utils.setBuffersAndAttributes} and to + * {@link module:webgl-utils:drawBufferInfo}. + * + * Given an object like + * + * let arrays = { + * position: { numComponents: 3, data: [0, 0, 0, 10, 0, 0, 0, 10, 0, 10, 10, 0], }, + * texcoord: { numComponents: 2, data: [0, 0, 0, 1, 1, 0, 1, 1], }, + * normal: { numComponents: 3, data: [0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1], }, + * indices: { numComponents: 3, data: [0, 1, 2, 1, 2, 3], }, + * }; + * + * Creates an BufferInfo like this + * + * bufferInfo = { + * numElements: 4, // or whatever the number of elements is + * indices: WebGLBuffer, // this property will not exist if there are no indices + * attribs: { + * a_position: { buffer: WebGLBuffer, numComponents: 3, }, + * a_normal: { buffer: WebGLBuffer, numComponents: 3, }, + * a_texcoord: { buffer: WebGLBuffer, numComponents: 2, }, + * }, + * }; + * + * The properties of arrays can be JavaScript arrays in which case the number of components + * will be guessed. + * + * let arrays = { + * position: [0, 0, 0, 10, 0, 0, 0, 10, 0, 10, 10, 0], + * texcoord: [0, 0, 0, 1, 1, 0, 1, 1], + * normal: [0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1], + * indices: [0, 1, 2, 1, 2, 3], + * }; + * + * They can also by TypedArrays + * + * let arrays = { + * position: new Float32Array([0, 0, 0, 10, 0, 0, 0, 10, 0, 10, 10, 0]), + * texcoord: new Float32Array([0, 0, 0, 1, 1, 0, 1, 1]), + * normal: new Float32Array([0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1]), + * indices: new Uint16Array([0, 1, 2, 1, 2, 3]), + * }; + * + * Or augmentedTypedArrays + * + * let positions = createAugmentedTypedArray(3, 4); + * let texcoords = createAugmentedTypedArray(2, 4); + * let normals = createAugmentedTypedArray(3, 4); + * let indices = createAugmentedTypedArray(3, 2, Uint16Array); + * + * positions.push([0, 0, 0, 10, 0, 0, 0, 10, 0, 10, 10, 0]); + * texcoords.push([0, 0, 0, 1, 1, 0, 1, 1]); + * normals.push([0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1]); + * indices.push([0, 1, 2, 1, 2, 3]); + * + * let arrays = { + * position: positions, + * texcoord: texcoords, + * normal: normals, + * indices: indices, + * }; + * + * For the last example it is equivalent to + * + * let bufferInfo = { + * attribs: { + * a_position: { numComponents: 3, buffer: gl.createBuffer(), }, + * a_texcoods: { numComponents: 2, buffer: gl.createBuffer(), }, + * a_normals: { numComponents: 3, buffer: gl.createBuffer(), }, + * }, + * indices: gl.createBuffer(), + * numElements: 6, + * }; + * + * gl.bindBuffer(gl.ARRAY_BUFFER, bufferInfo.attribs.a_position.buffer); + * gl.bufferData(gl.ARRAY_BUFFER, arrays.position, gl.STATIC_DRAW); + * gl.bindBuffer(gl.ARRAY_BUFFER, bufferInfo.attribs.a_texcoord.buffer); + * gl.bufferData(gl.ARRAY_BUFFER, arrays.texcoord, gl.STATIC_DRAW); + * gl.bindBuffer(gl.ARRAY_BUFFER, bufferInfo.attribs.a_normal.buffer); + * gl.bufferData(gl.ARRAY_BUFFER, arrays.normal, gl.STATIC_DRAW); + * gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, bufferInfo.indices); + * gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, arrays.indices, gl.STATIC_DRAW); + * + * @param {WebGLRenderingContext} gl A WebGLRenderingContext + * @param {Object.} arrays Your data + * @param {Object.} [opt_mapping] an optional mapping of attribute to array name. + * If not passed in it's assumed the array names will be mapped to an attribute + * of the same name with "a_" prefixed to it. An other words. + * + * let arrays = { + * position: ..., + * texcoord: ..., + * normal: ..., + * indices: ..., + * }; + * + * bufferInfo = createBufferInfoFromArrays(gl, arrays); + * + * Is the same as + * + * let arrays = { + * position: ..., + * texcoord: ..., + * normal: ..., + * indices: ..., + * }; + * + * let mapping = { + * a_position: "position", + * a_texcoord: "texcoord", + * a_normal: "normal", + * }; + * + * bufferInfo = createBufferInfoFromArrays(gl, arrays, mapping); + * + * @return {module:webgl-utils.BufferInfo} A BufferInfo + * @memberOf module:webgl-utils + */ + function createBufferInfoFromArrays(gl, arrays, opt_mapping) { + const bufferInfo = { + attribs: createAttribsFromArrays(gl, arrays, opt_mapping), + }; + let indices = arrays.indices; + if (indices) { + indices = makeTypedArray(indices, 'indices'); + bufferInfo.indices = createBufferFromTypedArray( + gl, + indices, + gl.ELEMENT_ARRAY_BUFFER, + ); + bufferInfo.numElements = indices.length; + } else { + bufferInfo.numElements = getNumElementsFromNonIndexedArrays(arrays); + } + + return bufferInfo; + } + + /** + * Creates buffers from typed arrays + * + * Given something like this + * + * let arrays = { + * positions: [1, 2, 3], + * normals: [0, 0, 1], + * } + * + * returns something like + * + * buffers = { + * positions: WebGLBuffer, + * normals: WebGLBuffer, + * } + * + * If the buffer is named 'indices' it will be made an ELEMENT_ARRAY_BUFFER. + * + * @param {WebGLRenderingContext} gl A WebGLRenderingContext. + * @param {Object} arrays + * @return {Object} returns an object with one WebGLBuffer per array + * @memberOf module:webgl-utils + */ + function createBuffersFromArrays(gl, arrays) { + const buffers = {}; + Object.keys(arrays).forEach(function (key) { + const type = + key === 'indices' ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; + const array = makeTypedArray(arrays[key], name); + buffers[key] = createBufferFromTypedArray(gl, array, type); + }); + + // hrm + if (arrays.indices) { + buffers.numElements = arrays.indices.length; + } else if (arrays.position) { + buffers.numElements = arrays.position.length / 3; + } + + return buffers; + } + + /** + * Calls `gl.drawElements` or `gl.drawArrays`, whichever is appropriate + * + * normally you'd call `gl.drawElements` or `gl.drawArrays` yourself + * but calling this means if you switch from indexed data to non-indexed + * data you don't have to remember to update your draw call. + * + * @param {WebGLRenderingContext} gl A WebGLRenderingContext + * @param {module:webgl-utils.BufferInfo} bufferInfo as returned from createBufferInfoFromArrays + * @param {enum} [primitiveType] eg (gl.TRIANGLES, gl.LINES, gl.POINTS, gl.TRIANGLE_STRIP, ...) + * @param {number} [count] An optional count. Defaults to bufferInfo.numElements + * @param {number} [offset] An optional offset. Defaults to 0. + * @memberOf module:webgl-utils + */ + function drawBufferInfo(gl, bufferInfo, primitiveType, count, offset) { + const indices = bufferInfo.indices; + primitiveType = primitiveType === undefined ? gl.TRIANGLES : primitiveType; + const numElements = count === undefined ? bufferInfo.numElements : count; + offset = offset === undefined ? 0 : offset; + if (indices) { + gl.drawElements(primitiveType, numElements, gl.UNSIGNED_SHORT, offset); + } else { + gl.drawArrays(primitiveType, offset, numElements); + } + } + + /** + * @typedef {Object} DrawObject + * @property {module:webgl-utils.ProgramInfo} programInfo A ProgramInfo as returned from createProgramInfo + * @property {module:webgl-utils.BufferInfo} bufferInfo A BufferInfo as returned from createBufferInfoFromArrays + * @property {Object} uniforms The values for the uniforms + * @memberOf module:webgl-utils + */ + + /** + * Draws a list of objects + * @param {WebGLRenderingContext} gl A WebGLRenderingContext + * @param {DrawObject[]} objectsToDraw an array of objects to draw. + * @memberOf module:webgl-utils + */ + function drawObjectList(gl, objectsToDraw) { + let lastUsedProgramInfo = null; + let lastUsedBufferInfo = null; + + objectsToDraw.forEach(function (object) { + const programInfo = object.programInfo; + const bufferInfo = object.bufferInfo; + let bindBuffers = false; + + if (programInfo !== lastUsedProgramInfo) { + lastUsedProgramInfo = programInfo; + gl.useProgram(programInfo.program); + bindBuffers = true; + } + + // Setup all the needed attributes. + if (bindBuffers || bufferInfo !== lastUsedBufferInfo) { + lastUsedBufferInfo = bufferInfo; + setBuffersAndAttributes(gl, programInfo.attribSetters, bufferInfo); + } + + // Set the uniforms. + setUniforms(programInfo.uniformSetters, object.uniforms); + + // Draw + drawBufferInfo(gl, bufferInfo); + }); + } + + function glEnumToString(gl, v) { + const results = []; + for (const key in gl) { + if (gl[key] === v) { + results.push(key); + } + } + return results.length ? results.join(' | ') : `0x${v.toString(16)}`; + } + + const isIE = /*@cc_on!@*/ false || !!document.documentMode; + // Edge 20+ + const isEdge = !isIE && !!window.StyleMedia; + if (isEdge) { + // Hack for Edge. Edge's WebGL implmentation is crap still and so they + // only respond to "experimental-webgl". I don't want to clutter the + // examples with that so his hack works around it + HTMLCanvasElement.prototype.getContext = (function (origFn) { + return function () { + let args = arguments; + const type = args[0]; + if (type === 'webgl') { + args = [].slice.call(arguments); + args[0] = 'experimental-webgl'; + } + return origFn.apply(this, args); + }; + })(HTMLCanvasElement.prototype.getContext); + } + + return { + createAugmentedTypedArray: createAugmentedTypedArray, + createAttribsFromArrays: createAttribsFromArrays, + createBuffersFromArrays: createBuffersFromArrays, + createBufferInfoFromArrays: createBufferInfoFromArrays, + createAttributeSetters: createAttributeSetters, + createProgram: createProgram, + createProgramFromScripts: createProgramFromScripts, + createProgramFromSources: createProgramFromSources, + createProgramInfo: createProgramInfo, + createUniformSetters: createUniformSetters, + createVAOAndSetAttributes: createVAOAndSetAttributes, + createVAOFromBufferInfo: createVAOFromBufferInfo, + drawBufferInfo: drawBufferInfo, + drawObjectList: drawObjectList, + glEnumToString: glEnumToString, + getExtensionWithKnownPrefixes: getExtensionWithKnownPrefixes, + resizeCanvasToDisplaySize: resizeCanvasToDisplaySize, + setAttributes: setAttributes, + setBuffersAndAttributes: setBuffersAndAttributes, + setUniforms: setUniforms, + }; +}); diff --git a/test/html/block.html b/packages/rrweb/test/html/block.html similarity index 100% rename from test/html/block.html rename to packages/rrweb/test/html/block.html diff --git a/packages/rrweb/test/html/canvas-webgl-image.html b/packages/rrweb/test/html/canvas-webgl-image.html new file mode 100644 index 0000000000..96bc31031e --- /dev/null +++ b/packages/rrweb/test/html/canvas-webgl-image.html @@ -0,0 +1,149 @@ + + + + + + + Document + + + + + + + + + + + + diff --git a/packages/rrweb/test/html/canvas-webgl-square.html b/packages/rrweb/test/html/canvas-webgl-square.html new file mode 100644 index 0000000000..cbdc7ec62e --- /dev/null +++ b/packages/rrweb/test/html/canvas-webgl-square.html @@ -0,0 +1,110 @@ + + + + + + canvas webgl square + + + + + + + + + diff --git a/packages/rrweb/test/html/canvas-webgl.html b/packages/rrweb/test/html/canvas-webgl.html new file mode 100644 index 0000000000..52aacabca6 --- /dev/null +++ b/packages/rrweb/test/html/canvas-webgl.html @@ -0,0 +1,27 @@ + + + + + + canvas + + + + + + + diff --git a/test/html/canvas.html b/packages/rrweb/test/html/canvas.html similarity index 100% rename from test/html/canvas.html rename to packages/rrweb/test/html/canvas.html diff --git a/packages/rrweb/test/html/form.html b/packages/rrweb/test/html/form.html new file mode 100644 index 0000000000..a89f11ff74 --- /dev/null +++ b/packages/rrweb/test/html/form.html @@ -0,0 +1,38 @@ + + + + + + + form fields + + + +
+ + + + + + + +
+ + diff --git a/packages/rrweb/test/html/frame1.html b/packages/rrweb/test/html/frame1.html new file mode 100644 index 0000000000..36a4d335be --- /dev/null +++ b/packages/rrweb/test/html/frame1.html @@ -0,0 +1,21 @@ + + + + + + Frame 1 + + + frame 1 + + + + + + + diff --git a/test/html/frame2.html b/packages/rrweb/test/html/frame2.html similarity index 100% rename from test/html/frame2.html rename to packages/rrweb/test/html/frame2.html diff --git a/test/html/ignore.html b/packages/rrweb/test/html/ignore.html similarity index 85% rename from test/html/ignore.html rename to packages/rrweb/test/html/ignore.html index 91e0652d9e..f46c2efd00 100644 --- a/test/html/ignore.html +++ b/packages/rrweb/test/html/ignore.html @@ -9,7 +9,6 @@
-
diff --git a/test/html/log.html b/packages/rrweb/test/html/log.html similarity index 100% rename from test/html/log.html rename to packages/rrweb/test/html/log.html diff --git a/test/html/main.html b/packages/rrweb/test/html/main.html similarity index 97% rename from test/html/main.html rename to packages/rrweb/test/html/main.html index a37d9bea35..9363103a49 100644 --- a/test/html/main.html +++ b/packages/rrweb/test/html/main.html @@ -20,6 +20,6 @@ iframe2.src = './html/frame1.html'; setTimeout(() => { document.body.appendChild(iframe2); - }, 10); + }, 100); diff --git a/test/html/mask-text.html b/packages/rrweb/test/html/mask-text.html similarity index 100% rename from test/html/mask-text.html rename to packages/rrweb/test/html/mask-text.html diff --git a/test/html/move-node.html b/packages/rrweb/test/html/move-node.html similarity index 89% rename from test/html/move-node.html rename to packages/rrweb/test/html/move-node.html index bbe607b700..cf08e23406 100644 --- a/test/html/move-node.html +++ b/packages/rrweb/test/html/move-node.html @@ -1,3 +1,4 @@ +
diff --git a/test/html/mutation-observer.html b/packages/rrweb/test/html/mutation-observer.html similarity index 58% rename from test/html/mutation-observer.html rename to packages/rrweb/test/html/mutation-observer.html index d5b8405972..9d149c5a22 100644 --- a/test/html/mutation-observer.html +++ b/packages/rrweb/test/html/mutation-observer.html @@ -1,6 +1,8 @@ +

mutation observer

- \ No newline at end of file + + diff --git a/packages/rrweb/test/html/password.html b/packages/rrweb/test/html/password.html new file mode 100644 index 0000000000..59ab933101 --- /dev/null +++ b/packages/rrweb/test/html/password.html @@ -0,0 +1,18 @@ + + + + + + + Document + + + + + + diff --git a/test/html/react-styled-components.html b/packages/rrweb/test/html/react-styled-components.html similarity index 100% rename from test/html/react-styled-components.html rename to packages/rrweb/test/html/react-styled-components.html diff --git a/test/html/select2.html b/packages/rrweb/test/html/select2.html similarity index 100% rename from test/html/select2.html rename to packages/rrweb/test/html/select2.html diff --git a/test/html/shadow-dom.html b/packages/rrweb/test/html/shadow-dom.html similarity index 100% rename from test/html/shadow-dom.html rename to packages/rrweb/test/html/shadow-dom.html diff --git a/test/html/shuffle.html b/packages/rrweb/test/html/shuffle.html similarity index 100% rename from test/html/shuffle.html rename to packages/rrweb/test/html/shuffle.html diff --git a/test/integration.test.ts b/packages/rrweb/test/integration.test.ts similarity index 60% rename from test/integration.test.ts rename to packages/rrweb/test/integration.test.ts index 207b878273..6999e3caee 100644 --- a/test/integration.test.ts +++ b/packages/rrweb/test/integration.test.ts @@ -1,16 +1,21 @@ import * as fs from 'fs'; import * as path from 'path'; import * as http from 'http'; -import * as url from 'url'; import * as puppeteer from 'puppeteer'; -import { assertSnapshot, launchPuppeteer } from './utils'; -import { Suite } from 'mocha'; -import { expect } from 'chai'; +import { + assertSnapshot, + startServer, + getServerURL, + launchPuppeteer, + waitForRAF, + replaceLast, +} from './utils'; import { recordOptions, eventWithTime, EventType } from '../src/types'; import { visitSnapshot, NodeType } from 'rrweb-snapshot'; -interface ISuite extends Suite { +interface ISuite { server: http.Server; + serverURL: string; code: string; browser: puppeteer.Browser; } @@ -19,41 +24,8 @@ interface IMimeType { [key: string]: string; } -const server = () => - new Promise((resolve) => { - const mimeType: IMimeType = { - '.html': 'text/html', - '.js': 'text/javascript', - '.css': 'text/css', - }; - const s = http.createServer((req, res) => { - const parsedUrl = url.parse(req.url!); - const sanitizePath = path - .normalize(parsedUrl.pathname!) - .replace(/^(\.\.[\/\\])+/, ''); - let pathname = path.join(__dirname, sanitizePath); - try { - const data = fs.readFileSync(pathname); - const ext = path.parse(pathname).ext; - res.setHeader('Content-type', mimeType[ext] || 'text/plain'); - res.setHeader('Access-Control-Allow-Origin', '*'); - res.setHeader('Access-Control-Allow-Methods', 'GET'); - res.setHeader('Access-Control-Allow-Headers', 'Content-type'); - setTimeout(() => { - res.end(data); - // mock delay - }, 100); - } catch (error) { - res.end(); - } - }); - s.listen(3030).on('listening', () => { - resolve(s); - }); - }); - describe('record integration tests', function (this: ISuite) { - this.timeout(10_000); + jest.setTimeout(10_000); const getHtml = ( fileName: string, @@ -61,11 +33,12 @@ describe('record integration tests', function (this: ISuite) { ): string => { const filePath = path.resolve(__dirname, `./html/${fileName}`); const html = fs.readFileSync(filePath, 'utf8'); - return html.replace( + return replaceLast( + html, '', ` @@ -85,21 +59,32 @@ describe('record integration tests', function (this: ISuite) { ); }; - before(async () => { - this.server = await server(); - this.browser = await launchPuppeteer(); + let server: ISuite['server']; + let serverURL: string; + let code: ISuite['code']; + let browser: ISuite['browser']; + + beforeAll(async () => { + server = await startServer(); + serverURL = getServerURL(server); + browser = await launchPuppeteer(); const bundlePath = path.resolve(__dirname, '../dist/rrweb.min.js'); - this.code = fs.readFileSync(bundlePath, 'utf8'); + const pluginsCode = [ + path.resolve(__dirname, '../dist/plugins/console-record.min.js'), + ] + .map((path) => fs.readFileSync(path, 'utf8')) + .join(); + code = fs.readFileSync(bundlePath, 'utf8') + pluginsCode; }); - after(async () => { - await this.browser.close(); - this.server.close(); + afterAll(async () => { + await browser.close(); + server.close(); }); it('can record form interactions', async () => { - const page: puppeteer.Page = await this.browser.newPage(); + const page: puppeteer.Page = await browser.newPage(); await page.goto('about:blank'); await page.setContent(getHtml.call(this, 'form.html')); @@ -110,11 +95,11 @@ describe('record integration tests', function (this: ISuite) { await page.select('select', '1'); const snapshots = await page.evaluate('window.snapshots'); - assertSnapshot(snapshots, __filename, 'form'); + assertSnapshot(snapshots); }); it('can record childList mutations', async () => { - const page: puppeteer.Page = await this.browser.newPage(); + const page: puppeteer.Page = await browser.newPage(); await page.goto('about:blank'); await page.setContent(getHtml.call(this, 'mutation-observer.html')); @@ -128,11 +113,11 @@ describe('record integration tests', function (this: ISuite) { }); const snapshots = await page.evaluate('window.snapshots'); - assertSnapshot(snapshots, __filename, 'child-list'); + assertSnapshot(snapshots); }); it('can record character data muatations', async () => { - const page: puppeteer.Page = await this.browser.newPage(); + const page: puppeteer.Page = await browser.newPage(); await page.goto('about:blank'); await page.setContent(getHtml.call(this, 'mutation-observer.html')); @@ -148,11 +133,11 @@ describe('record integration tests', function (this: ISuite) { }); const snapshots = await page.evaluate('window.snapshots'); - assertSnapshot(snapshots, __filename, 'character-data'); + assertSnapshot(snapshots); }); it('can record attribute mutation', async () => { - const page: puppeteer.Page = await this.browser.newPage(); + const page: puppeteer.Page = await browser.newPage(); await page.goto('about:blank'); await page.setContent(getHtml.call(this, 'mutation-observer.html')); @@ -166,28 +151,32 @@ describe('record integration tests', function (this: ISuite) { }); const snapshots = await page.evaluate('window.snapshots'); - assertSnapshot(snapshots, __filename, 'attributes'); + assertSnapshot(snapshots); }); it('can record node mutations', async () => { - const page: puppeteer.Page = await this.browser.newPage(); + const page: puppeteer.Page = await browser.newPage(); await page.goto('about:blank'); await page.setContent(getHtml.call(this, 'select2.html'), { waitUntil: 'networkidle0', }); // toggle the select box - await page.click('.select2-container'); - await page.click('.select2-container'); - + await page.click('.select2-container', { clickCount: 2, delay: 100 }); + // test storage of !important style + await page.evaluate( + 'document.getElementById("select2-drop").setAttribute("style", document.getElementById("select2-drop").style.cssText + "color:black !important")', + ); const snapshots = await page.evaluate('window.snapshots'); - assertSnapshot(snapshots, __filename, 'select2'); + assertSnapshot(snapshots); }); it('can freeze mutations', async () => { - const page: puppeteer.Page = await this.browser.newPage(); + const page: puppeteer.Page = await browser.newPage(); await page.goto('about:blank'); - await page.setContent(getHtml.call(this, 'mutation-observer.html')); + await page.setContent( + getHtml.call(this, 'mutation-observer.html', { recordCanvas: true }), + ); await page.evaluate(() => { const li = document.createElement('li'); @@ -199,6 +188,9 @@ describe('record integration tests', function (this: ISuite) { await page.evaluate('rrweb.freezePage()'); await page.evaluate(() => { document.body.setAttribute('test', 'bad'); + const canvas = document.querySelector('canvas') as HTMLCanvasElement; + const gl = canvas.getContext('webgl') as WebGLRenderingContext; + gl.getExtension('bad'); const ul = document.querySelector('ul') as HTMLUListElement; const li = document.createElement('li'); li.setAttribute('bad-attr', 'bad'); @@ -206,24 +198,26 @@ describe('record integration tests', function (this: ISuite) { ul.appendChild(li); document.body.removeChild(ul); }); + + await waitForRAF(page); + const snapshots = await page.evaluate('window.snapshots'); - assertSnapshot(snapshots, __filename, 'frozen'); + assertSnapshot(snapshots); }); it('should not record input events on ignored elements', async () => { - const page: puppeteer.Page = await this.browser.newPage(); + const page: puppeteer.Page = await browser.newPage(); await page.goto('about:blank'); await page.setContent(getHtml.call(this, 'ignore.html')); - await page.type('input[type="password"]', 'password'); await page.type('.rr-ignore', 'secret'); const snapshots = await page.evaluate('window.snapshots'); - assertSnapshot(snapshots, __filename, 'ignore'); + assertSnapshot(snapshots); }); it('should not record input values if maskAllInputs is enabled', async () => { - const page: puppeteer.Page = await this.browser.newPage(); + const page: puppeteer.Page = await browser.newPage(); await page.goto('about:blank'); await page.setContent( getHtml.call(this, 'form.html', { maskAllInputs: true }), @@ -232,37 +226,75 @@ describe('record integration tests', function (this: ISuite) { await page.type('input[type="text"]', 'test'); await page.click('input[type="radio"]'); await page.click('input[type="checkbox"]'); + await page.type('input[type="password"]', 'password'); await page.type('textarea', 'textarea test'); await page.select('select', '1'); const snapshots = await page.evaluate('window.snapshots'); - assertSnapshot(snapshots, __filename, 'mask'); + assertSnapshot(snapshots); }); it('can use maskInputOptions to configure which type of inputs should be masked', async () => { - const page: puppeteer.Page = await this.browser.newPage(); + const page: puppeteer.Page = await browser.newPage(); await page.goto('about:blank'); await page.setContent( getHtml.call(this, 'form.html', { maskInputOptions: { text: false, textarea: false, + password: true, + }, + }), + ); + + await page.type('input[type="text"]', 'test'); + await page.click('input[type="radio"]'); + await page.click('input[type="checkbox"]'); + await page.type('textarea', 'textarea test'); + await page.type('input[type="password"]', 'password'); + await page.select('select', '1'); + + const snapshots = await page.evaluate('window.snapshots'); + assertSnapshot(snapshots); + }); + + it('should mask value attribute with maskInputOptions', async () => { + const page: puppeteer.Page = await browser.newPage(); + await page.goto('about:blank'); + await page.setContent( + getHtml.call(this, 'password.html', { + maskInputOptions: { + password: true, }, }), ); + await page.type('input[type="password"]', 'secr3t'); + + const snapshots = await page.evaluate('window.snapshots'); + assertSnapshot(snapshots); + }); + + it('should record input userTriggered values if userTriggeredOnInput is enabled', async () => { + const page: puppeteer.Page = await browser.newPage(); + await page.goto('about:blank'); + await page.setContent( + getHtml.call(this, 'form.html', { userTriggeredOnInput: true }), + ); + await page.type('input[type="text"]', 'test'); await page.click('input[type="radio"]'); await page.click('input[type="checkbox"]'); + await page.type('input[type="password"]', 'password'); await page.type('textarea', 'textarea test'); await page.select('select', '1'); const snapshots = await page.evaluate('window.snapshots'); - assertSnapshot(snapshots, __filename, 'maskInputOptions'); + assertSnapshot(snapshots); }); it('should not record blocked elements and its child nodes', async () => { - const page: puppeteer.Page = await this.browser.newPage(); + const page: puppeteer.Page = await browser.newPage(); await page.goto('about:blank'); await page.setContent(getHtml.call(this, 'block.html')); @@ -271,11 +303,31 @@ describe('record integration tests', function (this: ISuite) { await page.click('#text'); const snapshots = await page.evaluate('window.snapshots'); - assertSnapshot(snapshots, __filename, 'block'); + assertSnapshot(snapshots); + }); + + it('should not record blocked elements dynamically added', async () => { + const page: puppeteer.Page = await browser.newPage(); + await page.goto('about:blank'); + await page.setContent(getHtml.call(this, 'block.html')); + + await page.evaluate(() => { + const el = document.createElement('button'); + el.className = 'rr-block'; + el.style.width = '100px'; + el.style.height = '100px'; + el.innerText = 'Should not be recorded'; + + const nextElement = document.querySelector('.rr-block')!; + nextElement.parentNode!.insertBefore(el, nextElement); + }); + + const snapshots = await page.evaluate('window.snapshots'); + assertSnapshot(snapshots); }); it('should record DOM node movement 1', async () => { - const page: puppeteer.Page = await this.browser.newPage(); + const page: puppeteer.Page = await browser.newPage(); await page.goto('about:blank'); await page.setContent(getHtml.call(this, 'move-node.html')); @@ -289,11 +341,11 @@ describe('record integration tests', function (this: ISuite) { div.appendChild(span); }); const snapshots = await page.evaluate('window.snapshots'); - assertSnapshot(snapshots, __filename, 'move-node-1'); + assertSnapshot(snapshots); }); it('should record DOM node movement 2', async () => { - const page: puppeteer.Page = await this.browser.newPage(); + const page: puppeteer.Page = await browser.newPage(); await page.goto('about:blank'); await page.setContent(getHtml.call(this, 'move-node.html')); @@ -304,27 +356,27 @@ describe('record integration tests', function (this: ISuite) { div.appendChild(span); }); const snapshots = await page.evaluate('window.snapshots'); - assertSnapshot(snapshots, __filename, 'move-node-2'); + assertSnapshot(snapshots); }); it('should record dynamic CSS changes', async () => { - const page: puppeteer.Page = await this.browser.newPage(); + const page: puppeteer.Page = await browser.newPage(); await page.goto('about:blank'); await page.setContent(getHtml.call(this, 'react-styled-components.html')); await page.click('.toggle'); const snapshots = await page.evaluate('window.snapshots'); - assertSnapshot(snapshots, __filename, 'react-styled-components'); + assertSnapshot(snapshots); }); it('should record canvas mutations', async () => { - const page: puppeteer.Page = await this.browser.newPage(); + const page: puppeteer.Page = await browser.newPage(); await page.goto('about:blank'); await page.setContent( getHtml.call(this, 'canvas.html', { recordCanvas: true, }), ); - await page.waitFor(50); + await waitForRAF(page); const snapshots = await page.evaluate('window.snapshots'); for (const event of snapshots) { if (event.type === EventType.FullSnapshot) { @@ -335,11 +387,24 @@ describe('record integration tests', function (this: ISuite) { }); } } - assertSnapshot(snapshots, __filename, 'canvas'); + assertSnapshot(snapshots); + }); + + it('should record webgl canvas mutations', async () => { + const page: puppeteer.Page = await browser.newPage(); + await page.goto('about:blank'); + await page.setContent( + getHtml.call(this, 'canvas-webgl.html', { + recordCanvas: true, + }), + ); + await page.waitForTimeout(50); + const snapshots = await page.evaluate('window.snapshots'); + assertSnapshot(snapshots); }); it('will serialize node before record', async () => { - const page: puppeteer.Page = await this.browser.newPage(); + const page: puppeteer.Page = await browser.newPage(); await page.goto('about:blank'); await page.setContent(getHtml.call(this, 'mutation-observer.html')); @@ -354,11 +419,11 @@ describe('record integration tests', function (this: ISuite) { }); const snapshots = await page.evaluate('window.snapshots'); - assertSnapshot(snapshots, __filename, 'serialize-before-record'); + assertSnapshot(snapshots); }); it('will defer missing next node mutation', async () => { - const page: puppeteer.Page = await this.browser.newPage(); + const page: puppeteer.Page = await browser.newPage(); await page.goto('about:blank'); await page.setContent(getHtml.call(this, 'shuffle.html')); @@ -376,20 +441,20 @@ describe('record integration tests', function (this: ISuite) { return parent.innerText; }); - expect(text).to.equal('4\n3\n2\n1\n5'); + expect(text).toEqual('4\n3\n2\n1\n5'); }); - it('can record log mutation', async () => { - const page: puppeteer.Page = await this.browser.newPage(); + it('should record console messages', async () => { + const page: puppeteer.Page = await browser.newPage(); await page.goto('about:blank'); await page.setContent( getHtml.call(this, 'log.html', { - recordLog: true, + plugins: '[rrwebConsoleRecord.getRecordConsolePlugin()]', }), ); await page.evaluate(() => { - console.assert(0 == 0, 'assert'); + console.assert(0 === 0, 'assert'); console.count('count'); console.countReset('count'); console.debug('debug'); @@ -406,24 +471,38 @@ describe('record integration tests', function (this: ISuite) { console.trace('trace'); console.warn('warn'); console.clear(); + console.log(new TypeError('a message')); + const iframe = document.createElement('iframe'); + document.body.appendChild(iframe); + }); + + await page.frames()[1].evaluate(() => { + console.log('from iframe'); }); const snapshots = await page.evaluate('window.snapshots'); - assertSnapshot(snapshots, __filename, 'log'); + assertSnapshot(snapshots); }); it('should nest record iframe', async () => { - const page: puppeteer.Page = await this.browser.newPage(); - await page.goto(`http://localhost:3030/html`); + const page: puppeteer.Page = await browser.newPage(); + await page.goto(`${serverURL}/html`); await page.setContent(getHtml.call(this, 'main.html')); - await page.waitFor(500); + await page.waitForSelector('#two'); + const frameIdTwo = await page.frames()[2]; + await frameIdTwo.waitForSelector('#four'); + const frameIdFour = frameIdTwo.childFrames()[1]; + await frameIdFour.waitForSelector('#five'); + + await page.waitForTimeout(50); + const snapshots = await page.evaluate('window.snapshots'); - assertSnapshot(snapshots, __filename, 'iframe'); + assertSnapshot(snapshots); }); it('should record shadow DOM', async () => { - const page: puppeteer.Page = await this.browser.newPage(); + const page: puppeteer.Page = await browser.newPage(); await page.goto('about:blank'); await page.setContent(getHtml.call(this, 'shadow-dom.html')); @@ -451,16 +530,66 @@ describe('record integration tests', function (this: ISuite) { .then(() => { (shadowRoot.lastChild!.childNodes[0] as HTMLElement).innerText = '123'; + const nestedShadowElement = shadowRoot.lastChild! + .childNodes[0] as HTMLElement; + nestedShadowElement.attachShadow({ + mode: 'open', + }); + nestedShadowElement.shadowRoot!.appendChild( + document.createElement('span'), + ); + (nestedShadowElement.shadowRoot!.lastChild as HTMLElement).innerText = + 'nested shadow dom'; + }); + }); + await page.waitForTimeout(50); + + const snapshots = await page.evaluate('window.snapshots'); + assertSnapshot(snapshots); + }); + + it('should record nested iframes and shadow doms', async () => { + const page: puppeteer.Page = await browser.newPage(); + await page.goto('about:blank'); + await page.setContent(getHtml.call(this, 'frame2.html')); + + await page.evaluate(() => { + const sleep = (ms: number) => + new Promise((resolve) => setTimeout(resolve, ms)); + let iframe: HTMLIFrameElement; + sleep(10) + .then(() => { + // get contentDocument of iframe five + const contentDocument1 = document.querySelector('iframe')! + .contentDocument!; + // create shadow dom #1 + contentDocument1.body.attachShadow({ mode: 'open' }); + contentDocument1.body.shadowRoot!.appendChild( + document.createElement('div'), + ); + const div = contentDocument1.body.shadowRoot!.childNodes[0]; + iframe = contentDocument1.createElement('iframe'); + // append an iframe to shadow dom #1 + div.appendChild(iframe); + return sleep(10); + }) + .then(() => { + const contentDocument2 = iframe.contentDocument!; + // create shadow dom #2 in the iframe + contentDocument2.body.attachShadow({ mode: 'open' }); + contentDocument2.body.shadowRoot!.appendChild( + document.createElement('span'), + ); }); }); - await page.waitFor(50); + await page.waitForTimeout(50); const snapshots = await page.evaluate('window.snapshots'); - assertSnapshot(snapshots, __filename, 'shadow-dom'); + assertSnapshot(snapshots); }); it('should mask texts', async () => { - const page: puppeteer.Page = await this.browser.newPage(); + const page: puppeteer.Page = await browser.newPage(); await page.goto('about:blank'); await page.setContent( getHtml.call(this, 'mask-text.html', { @@ -469,11 +598,11 @@ describe('record integration tests', function (this: ISuite) { ); const snapshots = await page.evaluate('window.snapshots'); - assertSnapshot(snapshots, __filename, 'mask-text'); + assertSnapshot(snapshots); }); it('should mask texts using maskTextFn', async () => { - const page: puppeteer.Page = await this.browser.newPage(); + const page: puppeteer.Page = await browser.newPage(); await page.goto('about:blank'); await page.setContent( getHtml.call(this, 'mask-text.html', { @@ -483,11 +612,11 @@ describe('record integration tests', function (this: ISuite) { ); const snapshots = await page.evaluate('window.snapshots'); - assertSnapshot(snapshots, __filename, 'mask-text-fn'); + assertSnapshot(snapshots); }); it('can mask character data mutations', async () => { - const page: puppeteer.Page = await this.browser.newPage(); + const page: puppeteer.Page = await browser.newPage(); await page.goto('about:blank'); await page.setContent(getHtml.call(this, 'mutation-observer.html')); @@ -504,6 +633,6 @@ describe('record integration tests', function (this: ISuite) { }); const snapshots = await page.evaluate('window.snapshots'); - assertSnapshot(snapshots, __filename, 'mask-character-data'); + assertSnapshot(snapshots); }); }); diff --git a/test/machine.test.ts b/packages/rrweb/test/machine.test.ts similarity index 90% rename from test/machine.test.ts rename to packages/rrweb/test/machine.test.ts index 8e4e27fcbd..1260a5e64f 100644 --- a/test/machine.test.ts +++ b/packages/rrweb/test/machine.test.ts @@ -1,4 +1,3 @@ -import { expect } from 'chai'; import { discardPriorSnapshots } from '../src/replay/machine'; import { sampleEvents } from './utils'; import { EventType } from '../src/types'; @@ -17,7 +16,7 @@ const nextNextEvents = nextEvents.map((e) => ({ describe('get last session', () => { it('will return all the events when there is only one session', () => { - expect(discardPriorSnapshots(events, events[0].timestamp)).to.deep.equal(events); + expect(discardPriorSnapshots(events, events[0].timestamp)).toEqual(events); }); it('will return last session when there is more than one in the events', () => { @@ -27,7 +26,7 @@ describe('get last session', () => { multiple, nextNextEvents[nextNextEvents.length - 1].timestamp, ), - ).to.deep.equal(nextNextEvents); + ).toEqual(nextNextEvents); }); it('will return last session when baseline time is future time', () => { @@ -37,11 +36,11 @@ describe('get last session', () => { multiple, nextNextEvents[nextNextEvents.length - 1].timestamp + 1000, ), - ).to.deep.equal(nextNextEvents); + ).toEqual(nextNextEvents); }); it('will return all sessions when baseline time is prior time', () => { - expect(discardPriorSnapshots(events, events[0].timestamp - 1000)).to.deep.equal( + expect(discardPriorSnapshots(events, events[0].timestamp - 1000)).toEqual( events, ); }); diff --git a/test/packer.test.ts b/packages/rrweb/test/packer.test.ts similarity index 70% rename from test/packer.test.ts rename to packages/rrweb/test/packer.test.ts index 7b4dda54a5..08b35c6cca 100644 --- a/test/packer.test.ts +++ b/packages/rrweb/test/packer.test.ts @@ -1,5 +1,3 @@ -import { expect } from 'chai'; -import { matchSnapshot } from './utils'; import { pack, unpack } from '../src/packer'; import { eventWithTime, EventType } from '../src/types'; import { MARK } from '../src/packer/base'; @@ -13,30 +11,36 @@ const event: eventWithTime = { describe('pack', () => { it('can pack event', () => { const packedData = pack(event); - const result = matchSnapshot(packedData, __filename, 'pack'); - expect(result.pass).to.true; + expect(packedData).toMatchSnapshot(); }); }); describe('unpack', () => { it('is compatible with unpacked data 1', () => { const result = unpack((event as unknown) as string); - expect(result).to.deep.equal(event); + expect(result).toEqual(event); }); it('is compatible with unpacked data 2', () => { const result = unpack(JSON.stringify(event)); - expect(result).to.deep.equal(event); + expect(result).toEqual(event); }); it('stop on unknown data format', () => { - expect(() => unpack('[""]')).to.throw(''); + const consoleSpy = jest + .spyOn(console, 'error') + .mockImplementation(() => {}); + + expect(() => unpack('[""]')).toThrow(''); + + expect(consoleSpy).toHaveBeenCalled(); + jest.resetAllMocks(); }); it('can unpack packed data', () => { const packedData = pack(event); const result = unpack(packedData); - expect(result).to.deep.equal({ + expect(result).toEqual({ ...event, v: MARK, }); diff --git a/packages/rrweb/test/record.test.ts b/packages/rrweb/test/record.test.ts new file mode 100644 index 0000000000..5b9e82b851 --- /dev/null +++ b/packages/rrweb/test/record.test.ts @@ -0,0 +1,447 @@ +/* tslint:disable no-console */ + +import * as fs from 'fs'; +import * as path from 'path'; +import * as puppeteer from 'puppeteer'; +import { + recordOptions, + listenerHandler, + eventWithTime, + EventType, + IncrementalSource, + styleSheetRuleData, +} from '../src/types'; +import { assertSnapshot, launchPuppeteer } from './utils'; + +interface ISuite { + code: string; + browser: puppeteer.Browser; + page: puppeteer.Page; + events: eventWithTime[]; +} + +interface IWindow extends Window { + rrweb: { + record: ( + options: recordOptions, + ) => listenerHandler | undefined; + addCustomEvent(tag: string, payload: T): void; + }; + emit: (e: eventWithTime) => undefined; +} + +const setup = function (this: ISuite, content: string): ISuite { + const ctx = {} as ISuite; + + beforeAll(async () => { + ctx.browser = await launchPuppeteer(); + + const bundlePath = path.resolve(__dirname, '../dist/rrweb.min.js'); + ctx.code = fs.readFileSync(bundlePath, 'utf8'); + }); + + beforeEach(async () => { + ctx.page = await ctx.browser.newPage(); + await ctx.page.goto('about:blank'); + await ctx.page.setContent(content); + await ctx.page.evaluate(ctx.code); + ctx.events = []; + await ctx.page.exposeFunction('emit', (e: eventWithTime) => { + if (e.type === EventType.DomContentLoaded || e.type === EventType.Load) { + return; + } + ctx.events.push(e); + }); + + ctx.page.on('console', (msg) => console.log('PAGE LOG:', msg.text())); + }); + + afterEach(async () => { + await ctx.page.close(); + }); + + afterAll(async () => { + await ctx.browser.close(); + }); + + return ctx; +}; + +describe('record', function (this: ISuite) { + jest.setTimeout(10_000); + + const ctx: ISuite = setup.call( + this, + ` + + + + + + + `, + ); + + it('will only have one full snapshot without checkout config', async () => { + await ctx.page.evaluate(() => { + const { record } = ((window as unknown) as IWindow).rrweb; + record({ + emit: ((window as unknown) as IWindow).emit, + }); + }); + let count = 30; + while (count--) { + await ctx.page.type('input', 'a'); + } + await ctx.page.waitForTimeout(10); + expect(ctx.events.length).toEqual(33); + expect( + ctx.events.filter((event: eventWithTime) => event.type === EventType.Meta) + .length, + ).toEqual(1); + expect( + ctx.events.filter( + (event: eventWithTime) => event.type === EventType.FullSnapshot, + ).length, + ).toEqual(1); + }); + + it('can checkout full snapshot by count', async () => { + await ctx.page.evaluate(() => { + const { record } = ((window as unknown) as IWindow).rrweb; + record({ + emit: ((window as unknown) as IWindow).emit, + checkoutEveryNth: 10, + }); + }); + let count = 30; + while (count--) { + await ctx.page.type('input', 'a'); + } + await ctx.page.waitForTimeout(10); + expect(ctx.events.length).toEqual(39); + expect( + ctx.events.filter((event: eventWithTime) => event.type === EventType.Meta) + .length, + ).toEqual(4); + expect( + ctx.events.filter( + (event: eventWithTime) => event.type === EventType.FullSnapshot, + ).length, + ).toEqual(4); + expect(ctx.events[1].type).toEqual(EventType.FullSnapshot); + expect(ctx.events[13].type).toEqual(EventType.FullSnapshot); + expect(ctx.events[25].type).toEqual(EventType.FullSnapshot); + expect(ctx.events[37].type).toEqual(EventType.FullSnapshot); + }); + + it('can checkout full snapshot by time', async () => { + await ctx.page.evaluate(() => { + const { record } = ((window as unknown) as IWindow).rrweb; + record({ + emit: ((window as unknown) as IWindow).emit, + checkoutEveryNms: 500, + }); + }); + let count = 30; + while (count--) { + await ctx.page.type('input', 'a'); + } + await ctx.page.waitForTimeout(300); + expect(ctx.events.length).toEqual(33); // before first automatic snapshot + await ctx.page.waitForTimeout(200); // could be 33 or 35 events by now depending on speed of test env + await ctx.page.type('input', 'a'); + await ctx.page.waitForTimeout(10); + expect(ctx.events.length).toEqual(36); // additionally includes the 2 checkout events + expect( + ctx.events.filter((event: eventWithTime) => event.type === EventType.Meta) + .length, + ).toEqual(2); + expect( + ctx.events.filter( + (event: eventWithTime) => event.type === EventType.FullSnapshot, + ).length, + ).toEqual(2); + expect(ctx.events[1].type).toEqual(EventType.FullSnapshot); + expect(ctx.events[35].type).toEqual(EventType.FullSnapshot); + }); + + it('is safe to checkout during async callbacks', async () => { + await ctx.page.evaluate(() => { + const { record } = ((window as unknown) as IWindow).rrweb; + record({ + emit: ((window as unknown) as IWindow).emit, + checkoutEveryNth: 2, + }); + const p = document.createElement('p'); + const span = document.createElement('span'); + setTimeout(() => { + document.body.appendChild(p); + p.appendChild(span); + document.body.removeChild(document.querySelector('input')!); + }, 0); + setTimeout(() => { + span.innerText = 'test'; + }, 10); + setTimeout(() => { + p.removeChild(span); + document.body.appendChild(span); + }, 10); + }); + await ctx.page.waitForTimeout(100); + assertSnapshot(ctx.events); + }); + + it('can add custom event', async () => { + await ctx.page.evaluate(() => { + const { record, addCustomEvent } = ((window as unknown) as IWindow).rrweb; + record({ + emit: ((window as unknown) as IWindow).emit, + }); + addCustomEvent('tag1', 1); + addCustomEvent<{ a: string }>('tag2', { + a: 'b', + }); + }); + await ctx.page.waitForTimeout(50); + assertSnapshot(ctx.events); + }); + + it('captures stylesheet rules', async () => { + await ctx.page.evaluate(() => { + const { record } = ((window as unknown) as IWindow).rrweb; + + record({ + emit: ((window as unknown) as IWindow).emit, + }); + + const styleElement = document.createElement('style'); + document.head.appendChild(styleElement); + + const styleSheet = styleElement.sheet; + // begin: pre-serialization + const ruleIdx0 = styleSheet.insertRule('body { background: #000; }'); + const ruleIdx1 = styleSheet.insertRule('body { background: #111; }'); + styleSheet.deleteRule(ruleIdx1); + // end: pre-serialization + setTimeout(() => { + styleSheet.insertRule('body { color: #fff; }'); + }, 0); + setTimeout(() => { + styleSheet.deleteRule(ruleIdx0); + }, 5); + setTimeout(() => { + styleSheet.insertRule('body { color: #ccc; }'); + }, 10); + }); + await ctx.page.waitForTimeout(50); + const styleSheetRuleEvents = ctx.events.filter( + (e) => + e.type === EventType.IncrementalSnapshot && + e.data.source === IncrementalSource.StyleSheetRule, + ); + const addRules = styleSheetRuleEvents.filter((e) => + Boolean((e.data as styleSheetRuleData).adds), + ); + const removeRuleCount = styleSheetRuleEvents.filter((e) => + Boolean((e.data as styleSheetRuleData).removes), + ).length; + // pre-serialization insert/delete should be ignored + expect(addRules.length).toEqual(2); + expect((addRules[0].data as styleSheetRuleData).adds).toEqual([ + { + rule: 'body { color: #fff; }', + }, + ]); + expect(removeRuleCount).toEqual(1); + assertSnapshot(ctx.events); + }); + + const captureNestedStylesheetRulesTest = async () => { + await ctx.page.evaluate(() => { + const { record } = ((window as unknown) as IWindow).rrweb; + + record({ + emit: ((window as unknown) as IWindow).emit, + }); + + const styleElement = document.createElement('style'); + document.head.appendChild(styleElement); + + const styleSheet = styleElement.sheet; + styleSheet.insertRule('@media {}'); + const atMediaRule = styleSheet.cssRules[0] as CSSMediaRule; + + const ruleIdx0 = atMediaRule.insertRule('body { background: #000; }', 0); + const ruleIdx1 = atMediaRule.insertRule('body { background: #111; }', 0); + atMediaRule.deleteRule(ruleIdx1); + setTimeout(() => { + atMediaRule.insertRule('body { color: #fff; }', 0); + }, 0); + setTimeout(() => { + atMediaRule.deleteRule(ruleIdx0); + }, 5); + setTimeout(() => { + atMediaRule.insertRule('body { color: #ccc; }', 0); + }, 10); + }); + await ctx.page.waitForTimeout(50); + const styleSheetRuleEvents = ctx.events.filter( + (e) => + e.type === EventType.IncrementalSnapshot && + e.data.source === IncrementalSource.StyleSheetRule, + ); + const addRuleCount = styleSheetRuleEvents.filter((e) => + Boolean((e.data as styleSheetRuleData).adds), + ).length; + const removeRuleCount = styleSheetRuleEvents.filter((e) => + Boolean((e.data as styleSheetRuleData).removes), + ).length; + // sync insert/delete should be ignored + expect(addRuleCount).toEqual(2); + expect(removeRuleCount).toEqual(1); + assertSnapshot(ctx.events); + }; + it('captures nested stylesheet rules', captureNestedStylesheetRulesTest); + + describe('without CSSGroupingRule support', () => { + // Safari currently doesn't support CSSGroupingRule, let's test without that + // https://caniuse.com/?search=CSSGroupingRule + beforeEach(async () => { + await ctx.page.evaluate(() => { + /* @ts-ignore: override CSSGroupingRule */ + CSSGroupingRule = undefined; + }); + // load a fresh rrweb recorder without CSSGroupingRule + await ctx.page.evaluate(ctx.code); + }); + it('captures nested stylesheet rules', captureNestedStylesheetRulesTest); + }); + + it('captures style property changes', async () => { + await ctx.page.evaluate(() => { + const { record } = ((window as unknown) as IWindow).rrweb; + + record({ + emit: ((window as unknown) as IWindow).emit, + }); + + const styleElement = document.createElement('style'); + document.head.appendChild(styleElement); + + const styleSheet = styleElement.sheet; + styleSheet.insertRule('body { background: #000; }'); + setTimeout(() => { + (styleSheet.cssRules[0] as CSSStyleRule).style.setProperty( + 'color', + 'green', + ); + (styleSheet.cssRules[0] as CSSStyleRule).style.removeProperty( + 'background', + ); + }, 0); + }); + await ctx.page.waitForTimeout(50); + assertSnapshot(ctx.events); + }); +}); + +describe('record iframes', function (this: ISuite) { + jest.setTimeout(10_000); + + const ctx: ISuite = setup.call( + this, + ` + + + +